From 696ce017604a3d3c05b85720a676de5b9f091d98 Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Thu, 8 Aug 2024 23:12:33 -0700 Subject: [PATCH] feat: text component (#1392) --- v3/.vscode/settings.json | 1 + v3/cypress/e2e/text.spec.ts | 57 ++ v3/cypress/e2e/toolbar.spec.ts | 3 +- v3/cypress/support/elements/text-tile.ts | 13 + v3/package-lock.json | 787 +++++++++++++++--- v3/package.json | 1 + v3/src/components/codap-component.tsx | 48 +- v3/src/components/container/container.tsx | 20 +- .../container/free-tile-component.tsx | 56 +- v3/src/components/text/text-defs.ts | 2 + v3/src/components/text/text-model.test.ts | 15 + v3/src/components/text/text-model.ts | 53 ++ .../components/text/text-registration.test.ts | 87 ++ v3/src/components/text/text-registration.ts | 62 +- v3/src/components/text/text-tile.scss | 43 + v3/src/components/text/text-tile.tsx | 96 +++ v3/src/components/text/text-toolbar.tsx | 63 ++ v3/src/hooks/use-codap-component-context.ts | 8 + v3/src/hooks/use-component-wrapper-context.ts | 8 + .../hooks/use-document-container-context.ts | 8 + v3/src/hooks/use-mutation-observer.ts | 19 + v3/src/models/codap/add-default-content.ts | 10 + v3/src/test/setupTests.ts | 9 +- v3/src/test/v2/rich-text.codap | 38 + v3/src/test/v2/simple-text.codap | 38 + v3/src/v2/codap-v2-types.ts | 18 +- 26 files changed, 1369 insertions(+), 194 deletions(-) create mode 100644 v3/cypress/e2e/text.spec.ts create mode 100644 v3/cypress/support/elements/text-tile.ts create mode 100644 v3/src/components/text/text-defs.ts create mode 100644 v3/src/components/text/text-model.test.ts create mode 100644 v3/src/components/text/text-model.ts create mode 100644 v3/src/components/text/text-registration.test.ts create mode 100644 v3/src/components/text/text-tile.scss create mode 100644 v3/src/components/text/text-tile.tsx create mode 100644 v3/src/components/text/text-toolbar.tsx create mode 100644 v3/src/hooks/use-codap-component-context.ts create mode 100644 v3/src/hooks/use-component-wrapper-context.ts create mode 100644 v3/src/hooks/use-document-container-context.ts create mode 100644 v3/src/hooks/use-mutation-observer.ts create mode 100644 v3/src/test/v2/rich-text.codap create mode 100644 v3/src/test/v2/simple-text.codap diff --git a/v3/.vscode/settings.json b/v3/.vscode/settings.json index 6998287648..714cf796fc 100644 --- a/v3/.vscode/settings.json +++ b/v3/.vscode/settings.json @@ -6,6 +6,7 @@ }, "typescript.tsdk": "./node_modules/typescript/lib", "cSpell.words": [ + "ccrte", "Chakra", "clsx", "colord", diff --git a/v3/cypress/e2e/text.spec.ts b/v3/cypress/e2e/text.spec.ts new file mode 100644 index 0000000000..9c0c9d79ac --- /dev/null +++ b/v3/cypress/e2e/text.spec.ts @@ -0,0 +1,57 @@ +import { ComponentElements as c } from "../support/elements/component-elements" +import { kTextTileTestId, TextTileElements as t } from "../support/elements/text-tile" +import { ToolbarElements as toolbar } from "../support/elements/toolbar-elements" + +const textDefaultTitle = "Text" + +context("Text tile", () => { + beforeEach(function () { + const queryParams = "?sample=mammals&dashboard&mouseSensor" + const url = `${Cypress.config("index")}${queryParams}` + cy.visit(url) + cy.wait(1000) // Ensuring the page and components are fully loaded. + }) + it("updates text title with undo/redo", () => { + const newTextTileName = "My Text" + c.getComponentTitle(kTextTileTestId).should("have.text", textDefaultTitle) + c.changeComponentTitle(kTextTileTestId, newTextTileName) + c.getComponentTitle(kTextTileTestId).should("have.text", newTextTileName) + + cy.log("Check update text title with undo/redo") + // Undo title change + toolbar.getUndoTool().click() + c.getComponentTitle(kTextTileTestId).should("have.text", textDefaultTitle) + + // Redo title change + toolbar.getRedoTool().click() + c.getComponentTitle(kTextTileTestId).should("have.text", newTextTileName) + }) + it("close text tile from close button with undo/redo", () => { + c.closeComponent(kTextTileTestId) + c.checkComponentDoesNotExist(kTextTileTestId) + + // Undo closing text tile (Reopen) + toolbar.getUndoTool().click() + c.checkComponentExists(kTextTileTestId) + + // Redo closing text tile + toolbar.getRedoTool().click() + c.checkComponentDoesNotExist(kTextTileTestId) + }) + it("can type in text tile", () => { + // focus the text tile + t.getTextTileContent().click() + const textToType = "This is some text." + t.getTextTileContent().type(textToType) + t.getTextTileContent().should("have.text", textToType) + // blur the text tile + cy.get(".codap-container").click() + t.getTextTileContent().should("have.text", textToType) + // Undo typing + toolbar.getUndoTool().click() + t.getTextTileContent().should("not.have.text", textToType) + // Redo typing + toolbar.getRedoTool().click() + t.getTextTileContent().should("have.text", textToType) + }) +}) diff --git a/v3/cypress/e2e/toolbar.spec.ts b/v3/cypress/e2e/toolbar.spec.ts index 6b69368a34..8a15a43221 100644 --- a/v3/cypress/e2e/toolbar.spec.ts +++ b/v3/cypress/e2e/toolbar.spec.ts @@ -104,9 +104,10 @@ context("codap toolbar", () => { cy.visit("#file=examples:Four%20Seals") toolbar.getTilesButton().click() toolbar.getTilesListMenu().should("be.visible") - toolbar.getTilesListMenuItem().should("have.length", 3) + toolbar.getTilesListMenuItem().should("have.length", 4) toolbar.getTilesListMenuItem().eq(0).should("have.text", "Tracks/Measurements") toolbar.getTilesListMenuItem().eq(1).should("have.text", "Measurements") toolbar.getTilesListMenuItem().eq(2).should("have.text", "Measurements") + toolbar.getTilesListMenuItem().eq(3).should("have.text", "Getting Started") }) }) diff --git a/v3/cypress/support/elements/text-tile.ts b/v3/cypress/support/elements/text-tile.ts new file mode 100644 index 0000000000..ac5eaba7f5 --- /dev/null +++ b/v3/cypress/support/elements/text-tile.ts @@ -0,0 +1,13 @@ +import { ComponentElements as c } from "./component-elements" + +export const kTextTileTestId = "codap-text" +export const kTextContentClass = ".codap-text-content" + +export const TextTileElements = { + getTextTile() { + return c.getComponentTile(kTextTileTestId) + }, + getTextTileContent() { + return cy.get(kTextContentClass) + } +} diff --git a/v3/package-lock.json b/v3/package-lock.json index 1bc0e1e497..023177ff26 100644 --- a/v3/package-lock.json +++ b/v3/package-lock.json @@ -13,6 +13,7 @@ "@chakra-ui/react": "^2.8.2", "@concord-consortium/cloud-file-manager": "^2.0.0-pre.3", "@concord-consortium/mobx-state-tree": "^6.0.0-cc.1", + "@concord-consortium/slate-editor": "^0.10.1", "@dnd-kit/core": "^6.1.0", "@floating-ui/react": "^0.26.19", "clsx": "^2.1.1", @@ -3283,6 +3284,42 @@ "mobx": "^6.3.0" } }, + "node_modules/@concord-consortium/slate-editor": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@concord-consortium/slate-editor/-/slate-editor-0.10.1.tgz", + "integrity": "sha512-RK86HWXAb6lg1ocbDC05C2Zfb2sxvZncvO0gjDNk6ToQK9M/R1k3cbas+Ft0xmsFBEDXG5Z9iG2bnymvHYI0zw==", + "dependencies": { + "@emotion/css": "^11.13.0", + "classnames": "^2.5.1", + "eventemitter3": "^5.0.1", + "html-escaper": "^3.0.3", + "is-hotkey": "^0.2.0", + "lodash": "^4.17.21", + "react-select": "^5.8.0", + "rgb-hex": "^3.0.0", + "slate": "^0.103.0", + "slate-history": "^0.100.0", + "slate-hyperscript": "^0.100.0", + "slate-react": "~0.98.4", + "style-to-object": "^1.0.6", + "tslib": "^2.6.3", + "valid-url": "^1.0.9" + }, + "peerDependencies": { + "react": ">=16.14", + "react-dom": ">=16.14" + } + }, + "node_modules/@concord-consortium/slate-editor/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/@concord-consortium/slate-editor/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" + }, "node_modules/@concord-consortium/token-service": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@concord-consortium/token-service/-/token-service-2.1.0.tgz", @@ -3542,16 +3579,15 @@ } }, "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "peer": true, + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -3560,11 +3596,15 @@ "stylis": "4.2.0" } }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "peer": true, "engines": { "node": ">=10" }, @@ -3576,29 +3616,48 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@emotion/css": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.0.tgz", + "integrity": "sha512-BUk99ylT+YHl+W/HN7nv1RCTkDYmKKqa1qbvM/qLSQEg61gipuBF5Hptk/2/ERmX2DCv0ccuFGhz9i0KSZOqPg==", + "dependencies": { + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0" + } + }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", - "peer": true + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.1", @@ -3619,7 +3678,6 @@ "version": "11.11.1", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -3640,23 +3698,26 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", - "peer": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz", + "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==", "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.9.0", + "@emotion/utils": "^1.4.0", "csstype": "^3.0.2" } }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", - "peer": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { "version": "11.11.0", @@ -3682,31 +3743,27 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "peer": true + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz", + "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "peer": true, "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", - "peer": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" }, "node_modules/@emotion/weak-memoize": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", - "peer": true + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", @@ -4966,6 +5023,11 @@ "tslib": "2" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -6843,6 +6905,11 @@ "@types/node": "*" } }, + "node_modules/@types/is-hotkey": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz", + "integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -7005,8 +7072,7 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "peer": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/pluralize": { "version": "0.0.33", @@ -7017,8 +7083,7 @@ "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -7036,7 +7101,6 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -7060,6 +7124,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -8961,7 +9033,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -9967,7 +10038,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "peer": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -11375,6 +11445,18 @@ "node": ">=8" } }, + "node_modules/direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -11415,6 +11497,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -13311,8 +13402,7 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "peer": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "node_modules/find-up": { "version": "5.0.0", @@ -14267,7 +14357,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "peer": true, "dependencies": { "react-is": "^16.7.0" } @@ -14644,6 +14733,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", @@ -14726,6 +14824,11 @@ "node": ">=10" } }, + "node_modules/inline-style-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", + "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -15009,6 +15112,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -18442,6 +18550,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -20942,6 +21055,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/react-select": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", + "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -20964,6 +21097,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -21330,6 +21478,14 @@ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, + "node_modules/rgb-hex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-3.0.0.tgz", + "integrity": "sha512-8h7ZcwxCBDKvchSWbWngJuSCqJGQ6nDuLLg+QcRyQDbX9jMWt+PpPeXAhSla0GOooEomk3lCprUpGkMdsLjKyg==", + "engines": { + "node": ">=8" + } + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -21581,6 +21737,19 @@ "raw-loader": "~0.5.1" } }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scroll-into-view-if-needed/node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, "node_modules/seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -21931,6 +22100,101 @@ "node": ">=8" } }, + "node_modules/slate": { + "version": "0.103.0", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.103.0.tgz", + "integrity": "sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==", + "dependencies": { + "immer": "^10.0.3", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.100.0.tgz", + "integrity": "sha512-x5rUuWLNtH97hs9PrFovGgt3Qc5zkTm/5mcUB+0NR/TK923eLax4HsL6xACLHMs245nI6aJElyM1y6hN0y5W/Q==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-history/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slate-hyperscript": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/slate-hyperscript/-/slate-hyperscript-0.100.0.tgz", + "integrity": "sha512-fb2KdAYg6RkrQGlqaIi4wdqz3oa0S4zKNBJlbnJbNOwa23+9FLD6oPVx9zUGqCSIpy+HIpOeqXrg0Kzwh/Ii4A==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-hyperscript/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slate-react": { + "version": "0.98.4", + "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.98.4.tgz", + "integrity": "sha512-8Of3v9hFuX8rIRc86LuuBhU9t8ps+9ARKL4yyhCrKQYZ93Ep/LFA3GvPGvtf3zYuVadZ8tkhRH8tbHOGNAndLw==", + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "@types/is-hotkey": "^0.1.1", + "@types/lodash": "^4.14.149", + "direction": "^1.0.3", + "is-hotkey": "^0.1.6", + "is-plain-object": "^5.0.0", + "lodash": "^4.17.4", + "scroll-into-view-if-needed": "^2.2.20", + "tiny-invariant": "1.0.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "slate": ">=0.65.3" + } + }, + "node_modules/slate-react/node_modules/is-hotkey": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", + "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" + }, + "node_modules/slate-react/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slate-react/node_modules/tiny-invariant": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + }, + "node_modules/slate/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -22467,11 +22731,18 @@ "webpack": "^5.27.0" } }, + "node_modules/style-to-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", + "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", + "dependencies": { + "inline-style-parser": "0.2.3" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "peer": true + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "node_modules/supports-color": { "version": "5.5.0", @@ -22842,6 +23113,11 @@ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -23643,6 +23919,19 @@ "react": ">=16.8.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", @@ -23733,6 +24022,11 @@ "node": ">=10.12.0" } }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -24744,7 +25038,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "peer": true, "engines": { "node": ">= 6" } @@ -27073,6 +27366,40 @@ "integrity": "sha512-dfzSe4ba2dNreouI3YXsUv1tmNlyx2Lj3oa5QoEi2WQazMgCqQY/mCMM87z1HfflYlCnLFoDu8KP2XTp3ljTvg==", "requires": {} }, + "@concord-consortium/slate-editor": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@concord-consortium/slate-editor/-/slate-editor-0.10.1.tgz", + "integrity": "sha512-RK86HWXAb6lg1ocbDC05C2Zfb2sxvZncvO0gjDNk6ToQK9M/R1k3cbas+Ft0xmsFBEDXG5Z9iG2bnymvHYI0zw==", + "requires": { + "@emotion/css": "^11.13.0", + "classnames": "^2.5.1", + "eventemitter3": "^5.0.1", + "html-escaper": "^3.0.3", + "is-hotkey": "^0.2.0", + "lodash": "^4.17.21", + "react-select": "^5.8.0", + "rgb-hex": "^3.0.0", + "slate": "^0.103.0", + "slate-history": "^0.100.0", + "slate-hyperscript": "^0.100.0", + "slate-react": "~0.98.4", + "style-to-object": "^1.0.6", + "tslib": "^2.6.3", + "valid-url": "^1.0.9" + }, + "dependencies": { + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" + } + } + }, "@concord-consortium/token-service": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@concord-consortium/token-service/-/token-service-2.1.0.tgz", @@ -27281,16 +27608,15 @@ } }, "@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "peer": true, + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", "requires": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -27299,38 +27625,63 @@ "stylis": "4.2.0" }, "dependencies": { + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "peer": true + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" } } }, "@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "peer": true, - "requires": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + } + } + }, + "@emotion/css": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.0.tgz", + "integrity": "sha512-BUk99ylT+YHl+W/HN7nv1RCTkDYmKKqa1qbvM/qLSQEg61gipuBF5Hptk/2/ERmX2DCv0ccuFGhz9i0KSZOqPg==", + "requires": { + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0" } }, "@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", - "peer": true + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "@emotion/is-prop-valid": { "version": "1.2.1", @@ -27351,7 +27702,6 @@ "version": "11.11.1", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", - "peer": true, "requires": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -27364,23 +27714,28 @@ } }, "@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", - "peer": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz", + "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==", "requires": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.9.0", + "@emotion/utils": "^1.4.0", "csstype": "^3.0.2" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + } } }, "@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", - "peer": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "@emotion/styled": { "version": "11.11.0", @@ -27397,29 +27752,25 @@ } }, "@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "peer": true + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz", + "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==" }, "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "peer": true, "requires": {} }, "@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", - "peer": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" }, "@emotion/weak-memoize": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", - "peer": true + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "@eslint-community/eslint-utils": { "version": "4.4.0", @@ -28356,6 +28707,11 @@ "dev": true, "requires": {} }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -29741,6 +30097,11 @@ "@types/node": "*" } }, + "@types/is-hotkey": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz", + "integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -29896,8 +30257,7 @@ "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "peer": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "@types/pluralize": { "version": "0.0.33", @@ -29908,8 +30268,7 @@ "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "@types/qs": { "version": "6.9.7", @@ -29927,7 +30286,6 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -29951,6 +30309,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "requires": { + "@types/react": "*" + } + }, "@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -31341,7 +31707,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "peer": true, "requires": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -32090,7 +32455,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "peer": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -33129,6 +33493,11 @@ "path-type": "^4.0.0" } }, + "direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==" + }, "dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -33163,6 +33532,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -34583,8 +34961,7 @@ "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "peer": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "find-up": { "version": "5.0.0", @@ -35265,7 +35642,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "peer": true, "requires": { "react-is": "^16.7.0" } @@ -35537,6 +35913,11 @@ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true }, + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + }, "immutable": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", @@ -35595,6 +35976,11 @@ "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "dev": true }, + "inline-style-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", + "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" + }, "internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -35782,6 +36168,11 @@ "is-extglob": "^2.1.1" } }, + "is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, "is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -38347,6 +38738,11 @@ "fs-monkey": "^1.0.3" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -40163,6 +40559,22 @@ "lodash": "^4.17.21" } }, + "react-select": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", + "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", + "requires": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + } + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -40173,6 +40585,17 @@ "tslib": "^2.0.0" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -40460,6 +40883,11 @@ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, + "rgb-hex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-3.0.0.tgz", + "integrity": "sha512-8h7ZcwxCBDKvchSWbWngJuSCqJGQ6nDuLLg+QcRyQDbX9jMWt+PpPeXAhSla0GOooEomk3lCprUpGkMdsLjKyg==" + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -40609,6 +41037,21 @@ "raw-loader": "~0.5.1" } }, + "scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "requires": { + "compute-scroll-into-view": "^1.0.20" + }, + "dependencies": { + "compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + } + } + }, "seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -40894,6 +41337,86 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slate": { + "version": "0.103.0", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.103.0.tgz", + "integrity": "sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==", + "requires": { + "immer": "^10.0.3", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, + "slate-history": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.100.0.tgz", + "integrity": "sha512-x5rUuWLNtH97hs9PrFovGgt3Qc5zkTm/5mcUB+0NR/TK923eLax4HsL6xACLHMs245nI6aJElyM1y6hN0y5W/Q==", + "requires": { + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, + "slate-hyperscript": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/slate-hyperscript/-/slate-hyperscript-0.100.0.tgz", + "integrity": "sha512-fb2KdAYg6RkrQGlqaIi4wdqz3oa0S4zKNBJlbnJbNOwa23+9FLD6oPVx9zUGqCSIpy+HIpOeqXrg0Kzwh/Ii4A==", + "requires": { + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, + "slate-react": { + "version": "0.98.4", + "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.98.4.tgz", + "integrity": "sha512-8Of3v9hFuX8rIRc86LuuBhU9t8ps+9ARKL4yyhCrKQYZ93Ep/LFA3GvPGvtf3zYuVadZ8tkhRH8tbHOGNAndLw==", + "requires": { + "@juggle/resize-observer": "^3.4.0", + "@types/is-hotkey": "^0.1.1", + "@types/lodash": "^4.14.149", + "direction": "^1.0.3", + "is-hotkey": "^0.1.6", + "is-plain-object": "^5.0.0", + "lodash": "^4.17.4", + "scroll-into-view-if-needed": "^2.2.20", + "tiny-invariant": "1.0.6" + }, + "dependencies": { + "is-hotkey": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", + "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "tiny-invariant": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + } + } + }, "slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -41303,11 +41826,18 @@ "dev": true, "requires": {} }, + "style-to-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", + "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", + "requires": { + "inline-style-parser": "0.2.3" + } + }, "stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "peer": true + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "supports-color": { "version": "5.5.0", @@ -41585,6 +42115,11 @@ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -42130,6 +42665,12 @@ "dequal": "2.0.3" } }, + "use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "requires": {} + }, "use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", @@ -42198,6 +42739,11 @@ "convert-source-map": "^1.6.0" } }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -42923,8 +43469,7 @@ "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "peer": true + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { "version": "17.7.2", diff --git a/v3/package.json b/v3/package.json index c2b38f0ddc..1d8f3d67de 100644 --- a/v3/package.json +++ b/v3/package.json @@ -181,6 +181,7 @@ "@chakra-ui/react": "^2.8.2", "@concord-consortium/cloud-file-manager": "^2.0.0-pre.3", "@concord-consortium/mobx-state-tree": "^6.0.0-cc.1", + "@concord-consortium/slate-editor": "^0.10.1", "@dnd-kit/core": "^6.1.0", "@floating-ui/react": "^0.26.19", "clsx": "^2.1.1", diff --git a/v3/src/components/codap-component.tsx b/v3/src/components/codap-component.tsx index 1b433fa5cb..9b32c7d892 100644 --- a/v3/src/components/codap-component.tsx +++ b/v3/src/components/codap-component.tsx @@ -1,7 +1,8 @@ import { clsx } from "clsx" import { observer } from "mobx-react-lite" import { isAlive } from "mobx-state-tree" -import React from "react" +import React, { useRef } from "react" +import { CodapComponentContext } from "../hooks/use-codap-component-context" import { TileModelContext } from "../hooks/use-tile-model-context" import { InspectorPanelWrapper } from "./inspector-panel-wrapper" import { ITileBaseProps } from "./tiles/tile-base-props" @@ -29,6 +30,7 @@ export const CodapComponent = observer(function CodapComponent({ onRightPointerDown, onBottomPointerDown, onLeftPointerDown }: IProps) { const info = getTileComponentInfo(tile.content.type) + const codapComponentRef = useRef(null) function handleFocusTile() { if (isAlive(tile)) { @@ -46,28 +48,30 @@ export const CodapComponent = observer(function CodapComponent({ { shadowed: uiState.isFocusedTile(tile.id) || uiState.isHoveredTile(tile.id) }) return ( -
- - - {onRightPointerDown && !isFixedWidth && !isMinimized && -
} - {onBottomPointerDown && !isFixedHeight && !isMinimized && -
} - {onLeftPointerDown && !isFixedWidth && !isMinimized && -
} - {onBottomLeftPointerDown && !(isFixedWidth && isFixedHeight) && !isMinimized && -
- } - {onBottomRightPointerDown && !(isFixedWidth && isFixedHeight) && !isMinimized && -
- {(uiState.isFocusedTile(tile.id)) && !isMinimized && - } -
- } + +
+ + + {onRightPointerDown && !isFixedWidth && !isMinimized && +
} + {onBottomPointerDown && !isFixedHeight && !isMinimized && +
} + {onLeftPointerDown && !isFixedWidth && !isMinimized && +
} + {onBottomLeftPointerDown && !(isFixedWidth && isFixedHeight) && !isMinimized && +
+ } + {onBottomRightPointerDown && !(isFixedWidth && isFixedHeight) && !isMinimized && +
+ {(uiState.isFocusedTile(tile.id)) && !isMinimized && + } +
+ } -
- +
+ + ) }) diff --git a/v3/src/components/container/container.tsx b/v3/src/components/container/container.tsx index e9aa1bbfc7..4829a2e13a 100644 --- a/v3/src/components/container/container.tsx +++ b/v3/src/components/container/container.tsx @@ -1,5 +1,7 @@ +import { useMergeRefs } from "@chakra-ui/react" import { clsx } from "clsx" -import React, { useCallback } from "react" +import React, { useCallback, useRef } from "react" +import { DocumentContainerContext } from "../../hooks/use-document-container-context" import { useDocumentContent } from "../../hooks/use-document-content" import { useContainerDroppable, getDragTileId } from "../../hooks/use-drag-drop" import { isFreeTileRow } from "../../models/document/free-tile-row" @@ -17,6 +19,7 @@ export const Container: React.FC = () => { // TODO: handle the possibility of multiple rows const row = documentContent?.getRowByIndex(0) const getTile = useCallback((tileId: string) => documentContent?.getTile(tileId), [documentContent]) + const containerRef = useRef(null) const handleCloseTile = useCallback((tileId: string) => { const tile = getTile(tileId) @@ -50,14 +53,17 @@ export const Container: React.FC = () => { } } }) + const mergedContainerRef = useMergeRefs(containerRef, setNodeRef) const classes = clsx("codap-container", { "scroll-behavior-auto": isScrollBehaviorAuto }) return ( -
- {isMosaicTileRow(row) && - } - {isFreeTileRow(row) && - } -
+ +
+ {isMosaicTileRow(row) && + } + {isFreeTileRow(row) && + } +
+
) } diff --git a/v3/src/components/container/free-tile-component.tsx b/v3/src/components/container/free-tile-component.tsx index d010869948..27dcc3c743 100644 --- a/v3/src/components/container/free-tile-component.tsx +++ b/v3/src/components/container/free-tile-component.tsx @@ -1,7 +1,9 @@ +import { useMergeRefs } from "@chakra-ui/react" import { useDndContext } from "@dnd-kit/core" import { clsx } from "clsx" import { observer } from "mobx-react-lite" -import React, { useCallback, useEffect, useState } from "react" +import React, { PointerEvent, useCallback, useEffect, useRef, useState } from "react" +import { ComponentWrapperContext } from "../../hooks/use-component-wrapper-context" import { getDragTileId, IUseDraggableTile, useDraggableTile } from "../../hooks/use-drag-drop" import { IFreeTileLayout, IFreeTileRow, isFreeTileRow } from "../../models/document/free-tile-row" import { getTileComponentInfo } from "../../models/tiles/tile-component-info" @@ -18,6 +20,7 @@ interface IProps { export const FreeTileComponent = observer(function FreeTileComponent({ row, tile, onCloseTile}: IProps) { const { active } = useDndContext() + const componentRef = useRef(null) const { id: tileId, content: { type: tileType } } = tile const [useDefaultCreationStyle, setUseDefaultCreationStyle] = useState(row.animateCreationTiles.has(tileId)) const [resizingTileStyle, setResizingTileStyle] = @@ -46,25 +49,26 @@ export const FreeTileComponent = observer(function FreeTileComponent({ row, tile } } }) + const mergedComponentRef = useMergeRefs(componentRef, setNodeRef) const handleMinimizeTile = useCallback(() => { rowTile?.setMinimized(!rowTile.isMinimized) }, [rowTile]) - const handleResizePointerDown = useCallback((e: React.PointerEvent, mtile: IFreeTileLayout, direction: string) => { - const startWidth = mtile.width - const startHeight = mtile.height + const handleResizePointerDown = useCallback((e: PointerEvent, tileLayout: IFreeTileLayout, direction: string) => { + const startWidth = tileLayout.width + const startHeight = tileLayout.height const startPosition = {x: e.pageX, y: e.pageY} - let resizingWidth = startWidth, resizingHeight = startHeight, resizingLeft = mtile.x + let resizingWidth = startWidth, resizingHeight = startHeight, resizingLeft = tileLayout.x // Because user can start drag 8px within the border, the component's startPosition.x moves by number of pixels // the pointer down event location, which moves the entire component to the right by the same number of pixels. // So we force it to always be the left position of the component // const startLeft = startPosition.x > tile.x ? tile.x : startPosition.x - const startLeft = mtile.x + const startLeft = tileLayout.x const onPointerMove = (pointerMoveEvent: { pageX: number; pageY: number }) => { - setResizingTileId(mtile.tileId) + setResizingTileId(tileLayout.tileId) const xDelta = pointerMoveEvent.pageX - startPosition.x const yDelta = pointerMoveEvent.pageY - startPosition.y const addIfDefined = (x: number | undefined, delta: number) => x != null ? x + delta : x @@ -81,9 +85,9 @@ export const FreeTileComponent = observer(function FreeTileComponent({ row, tile } setResizingTileStyle({ - left: resizingLeft, top: mtile.y, + left: resizingLeft, top: tileLayout.y, width: resizingWidth, height: resizingHeight, - zIndex: mtile.zIndex, + zIndex: tileLayout.zIndex, transition: "none" }) } @@ -91,8 +95,8 @@ export const FreeTileComponent = observer(function FreeTileComponent({ row, tile document.body.removeEventListener("pointermove", onPointerMove, { capture: true }) document.body.removeEventListener("pointerup", onPointerUp, { capture: true }) row.applyModelChange(() => { - mtile.setSize(resizingWidth, resizingHeight) - mtile.setPosition(resizingLeft, mtile.y) + tileLayout.setSize(resizingWidth, resizingHeight) + tileLayout.setPosition(resizingLeft, tileLayout.y) }, { undoStringKey: "DG.Undo.componentResize", redoStringKey: "DG.Redo.componentResize" @@ -160,19 +164,21 @@ export const FreeTileComponent = observer(function FreeTileComponent({ row, tile if (!info || rowTile?.isHidden) return null return ( -
- {tile && rowTile && - - } -
+ +
+ {tile && rowTile && + + } +
+
) }) diff --git a/v3/src/components/text/text-defs.ts b/v3/src/components/text/text-defs.ts new file mode 100644 index 0000000000..b6f09eab41 --- /dev/null +++ b/v3/src/components/text/text-defs.ts @@ -0,0 +1,2 @@ +export const kTextTileType = "CodapText" +export const kTextTileClass = "codap-text" diff --git a/v3/src/components/text/text-model.test.ts b/v3/src/components/text/text-model.test.ts new file mode 100644 index 0000000000..babaee9aae --- /dev/null +++ b/v3/src/components/text/text-model.test.ts @@ -0,0 +1,15 @@ +import { textToSlate } from "@concord-consortium/slate-editor" +import { isTextModel, TextModel } from "./text-model" + +describe("TextModel", () => { + it("works as expected", () => { + const model = TextModel.create() + expect(isTextModel(undefined)).toBe(false) + expect(isTextModel(model)).toBe(true) + expect(model.textContent).toBe("") + expect(model.isEquivalent(textToSlate(model.textContent))).toBe(true) + model.setTextContent("Some text") + expect(model.textContent).toBe("Some text") + expect(model.isEquivalent(textToSlate(model.textContent))).toBe(true) + }) +}) diff --git a/v3/src/components/text/text-model.ts b/v3/src/components/text/text-model.ts new file mode 100644 index 0000000000..160814d4b8 --- /dev/null +++ b/v3/src/components/text/text-model.ts @@ -0,0 +1,53 @@ +import { + EditorValue, serializeValue, SlateExchangeValue, slateToText, textToSlate +} from "@concord-consortium/slate-editor" +import { Instance, SnapshotIn, types } from "mobx-state-tree" +import { ITileContentModel, TileContentModel } from "../../models/tiles/tile-content" +import { safeJsonParse } from "../../utilities/js-utils" +import { kTextTileType } from "./text-defs" + +export function editorValueToModelValue(value: EditorValue) { + return JSON.stringify(serializeValue(value)) +} + +export function modelValueToEditorValue(value?: string) { + const parsedValue = safeJsonParse(value) + return parsedValue?.document?.children ?? textToSlate("") +} + +export const TextModel = TileContentModel + .named("TextModel") + .props({ + type: types.optional(types.literal(kTextTileType), kTextTileType), + value: types.optional(types.string, () => editorValueToModelValue(textToSlate(""))) + }) + .volatile(self => ({ + isSettingValue: false + })) + .views(self => ({ + isEquivalent(value: EditorValue) { + return self.value === editorValueToModelValue(value) + }, + get textContent() { + const slate = safeJsonParse(self.value) + return slateToText(slate?.document?.children) + } + })) + .actions(self => ({ + setValue(value: EditorValue) { + self.isSettingValue = true + self.value = editorValueToModelValue(value) + self.isSettingValue = false + } + })) + .actions(self => ({ + setTextContent(text: string) { + self.setValue(textToSlate(text)) + } + })) +export interface ITextModel extends Instance {} +export interface ITextSnapshot extends SnapshotIn {} + +export function isTextModel(model?: ITileContentModel): model is ITextModel { + return model?.type === kTextTileType +} diff --git a/v3/src/components/text/text-registration.test.ts b/v3/src/components/text/text-registration.test.ts new file mode 100644 index 0000000000..752d352062 --- /dev/null +++ b/v3/src/components/text/text-registration.test.ts @@ -0,0 +1,87 @@ +import { DocumentContentModel } from "../../models/document/document-content" +import { FreeTileRow } from "../../models/document/free-tile-row" +import { getTileComponentInfo } from "../../models/tiles/tile-component-info" +import { getTileContentInfo } from "../../models/tiles/tile-content-info" +import { ITileModelSnapshotIn } from "../../models/tiles/tile-model" +import { CodapV2Document } from "../../v2/codap-v2-document" +import { importV2Component } from "../../v2/codap-v2-tile-importers" +import { ICodapV2DocumentJson } from "../../v2/codap-v2-types" +import { kTextTileType } from "./text-defs" +import { isTextModel, ITextModel } from "./text-model" +import "./text-registration" + +const fs = require("fs") +const path = require("path") + +describe("Text registration", () => { + it("registers content and component info", () => { + const textContentInfo = getTileContentInfo(kTextTileType) + expect(textContentInfo).toBeDefined() + expect(getTileComponentInfo(kTextTileType)).toBeDefined() + const textContent = textContentInfo?.defaultContent() + expect(textContent).toBeDefined() + }) + it("imports v2 text components with simple text (legacy v2 format)", () => { + const file = path.join(__dirname, "../../test/v2", "simple-text.codap") + const textJson = fs.readFileSync(file, "utf8") + const textDoc = JSON.parse(textJson) as ICodapV2DocumentJson + const v2Document = new CodapV2Document(textDoc) + + const docContent = DocumentContentModel.create() + docContent.setRowCreator(() => FreeTileRow.create()) + const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => { + return docContent?.insertTileSnapshotInDefaultRow(tileSnap) + }) + + const tile = importV2Component({ + v2Component: v2Document.components[0], + v2Document, + insertTile: mockInsertTile + }) + expect(tile).toBeDefined() + expect(mockInsertTile).toHaveBeenCalledTimes(1) + expect(isTextModel(tile!.content)).toBe(true) + expect((tile!.content as ITextModel).textContent).toBe("This is some simple text.") + }) + it("imports v2 text components with rich text (newer v2 format)", () => { + const file = path.join(__dirname, "../../test/v2", "rich-text.codap") + const textJson = fs.readFileSync(file, "utf8") + const textDoc = JSON.parse(textJson) as ICodapV2DocumentJson + const v2Document = new CodapV2Document(textDoc) + + const docContent = DocumentContentModel.create() + docContent.setRowCreator(() => FreeTileRow.create()) + const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => { + return docContent?.insertTileSnapshotInDefaultRow(tileSnap) + }) + + const tile = importV2Component({ + v2Component: v2Document.components[0], + v2Document, + insertTile: mockInsertTile + }) + expect(tile).toBeDefined() + expect(mockInsertTile).toHaveBeenCalledTimes(1) + expect(isTextModel(tile!.content)).toBe(true) + expect((tile!.content as ITextModel).textContent).toBe("This is some bold italic underlined deleted red rich text.") + }) + it("doesn't import invalid v2 text components", () => { + const file = path.join(__dirname, "../../test/v2", "simple-text.codap") + const textJson = fs.readFileSync(file, "utf8") + const textDoc = JSON.parse(textJson) as ICodapV2DocumentJson + const v2Document = new CodapV2Document(textDoc) + + const docContent = DocumentContentModel.create() + docContent.setRowCreator(() => FreeTileRow.create()) + const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => { + return docContent?.insertTileSnapshotInDefaultRow(tileSnap) + }) + + const tileWithInvalidComponent = importV2Component({ + v2Component: {} as any, + v2Document, + insertTile: mockInsertTile + }) + expect(tileWithInvalidComponent).toBeUndefined() + }) +}) diff --git a/v3/src/components/text/text-registration.ts b/v3/src/components/text/text-registration.ts index ee859b910a..ede94956a7 100644 --- a/v3/src/components/text/text-registration.ts +++ b/v3/src/components/text/text-registration.ts @@ -1,23 +1,36 @@ -import { registerTileComponentInfo } from "../../models/tiles/tile-component-info" -// import { registerTileContentInfo } from "../../models/tiles/tile-content-info" -import { PlaceholderTileComponent } from "../tiles/placeholder/placeholder-tile" +import { serializeValue, textToSlate } from "@concord-consortium/slate-editor" +import { SetRequired } from "type-fest" import { ComponentTitleBar } from "../component-title-bar" -import TextIcon from '../../assets/icons/icon-text.svg' +import { registerTileComponentInfo } from "../../models/tiles/tile-component-info" +import { ITileLikeModel, registerTileContentInfo } from "../../models/tiles/tile-content-info" +import { ITileModelSnapshotIn } from "../../models/tiles/tile-model" +import { toV3Id } from "../../utilities/codap-utils" +import { safeJsonParse } from "../../utilities/js-utils" +import { t } from "../../utilities/translation/translate" +import { registerV2TileImporter } from "../../v2/codap-v2-tile-importers" +import { isV2TextComponent } from "../../v2/codap-v2-types" +import { kTextTileClass, kTextTileType } from "./text-defs" +import { ITextSnapshot, TextModel } from "./text-model" +import { TextTile } from "./text-tile" +import TextIcon from "../../assets/icons/icon-text.svg" export const kTextIdPrefix = "TEXT" -// registerTileContentInfo({ -// type: "CodapText", -// prefix: kTextIdPrefix, -// modelClass: TextModel, -// defaultContent: () => ({ type: kCodapTextTileType }) -// }) +registerTileContentInfo({ + type: kTextTileType, + prefix: kTextIdPrefix, + modelClass: TextModel, + defaultContent: () => ({ type: kTextTileType }), + getTitle: (tile: ITileLikeModel) => { + return tile.title || t("DG.DocumentController.textTitle") + } +}) registerTileComponentInfo({ - type: "CodapText", + type: kTextTileType, TitleBar: ComponentTitleBar, - Component: PlaceholderTileComponent, - tileEltClass: "codap-text", + Component: TextTile, + tileEltClass: kTextTileClass, Icon: TextIcon, shelf: { position: 6, @@ -27,5 +40,26 @@ registerTileComponentInfo({ redoStringKey: "DG.Redo.textComponent.create" }, defaultWidth: 300, - defaultHeight: 300 + defaultHeight: 100 +}) + +registerV2TileImporter("DG.TextView", ({ v2Component, insertTile }) => { + if (!isV2TextComponent(v2Component)) return + + const { guid, componentStorage: { title = "", text } } = v2Component + + // According to a comment in the v2 code: "Prior to build 0535 this was simple text. + // As of 0535 it is a JSON representation of the rich text content." + // For v3, we make sure we're always dealing with rich-text JSON. + const json = safeJsonParse(text) + const value = json != null && typeof json === "object" ? text : JSON.stringify(serializeValue(textToSlate(text))) + + const content: SetRequired = { + type: kTextTileType, + value + } + const textTileSnap: ITileModelSnapshotIn = { id: toV3Id(kTextIdPrefix, guid), _title: title, content } + const textTile = insertTile(textTileSnap) + + return textTile }) diff --git a/v3/src/components/text/text-tile.scss b/v3/src/components/text/text-tile.scss new file mode 100644 index 0000000000..65ea2f6cae --- /dev/null +++ b/v3/src/components/text/text-tile.scss @@ -0,0 +1,43 @@ +@use "../vars.scss"; + +$text-toolbar-width: 52px; + +.codap-text-content { + width: 100%; + height: calc(100% - vars.$title-bar-height); + background: white; + border-radius: 0 0 6px 6px; + overflow: auto; + + .ccrte-editor { + font-size: 12px; + font-family: 'Helvetica', sans-serif; + outline: transparent; + } +} + +.codap-toolbar-wrapper { + position: absolute; + top: 0; + // position toolbar to right of component + right: -$text-toolbar-width; + + .editor-toolbar-container { + border-radius: 6px; + } +} + +.ccrte-modal-dialog { + .ccrte-form-field { + // reinstate borders clobbered by Chakra/emotion + input { + border: 1px solid black; + } + + // center check in checkbox + .ccrte-checkbox-field > input:checked + span::before { + left: 0.5rem; + top: 0.2rem; + } + } +} diff --git a/v3/src/components/text/text-tile.tsx b/v3/src/components/text/text-tile.tsx new file mode 100644 index 0000000000..6d115130cf --- /dev/null +++ b/v3/src/components/text/text-tile.tsx @@ -0,0 +1,96 @@ +import { createEditor, defaultHotkeyMap, ReactEditor, Slate, SlateEditor } from "@concord-consortium/slate-editor" +import { observer } from "mobx-react-lite" +import { addDisposer, onPatch } from "mobx-state-tree" +import React, { useEffect, useRef, useState } from "react" +import { useMemo } from "use-memo-one" +import { useTileModelContext } from "../../hooks/use-tile-model-context" +import { ITileBaseProps } from "../tiles/tile-base-props" +import { isTextModel, modelValueToEditorValue } from "./text-model" +import { TextToolbar } from "./text-toolbar" + +import "@concord-consortium/slate-editor/dist/index.css" +import "./text-tile.scss" + +export const TextTile = observer(function TextTile({ tile }: ITileBaseProps) { + const textModel = isTextModel(tile?.content) ? tile.content : undefined + const { selectTile } = useTileModelContext() + const textOnFocus = useRef("") + + const [initialValue, setInitialValue] = useState(() => modelValueToEditorValue(textModel?.value)) + const editor = useMemo(() => { + // slate doesn't have a convenient API for replacing the value in an existing editor, + // so we create a new editor instance when the value changes externally (e.g. undo/redo). + initialValue // eslint-disable-line no-unused-expressions + return createEditor() + }, [initialValue]) + // changes to this value trigger a remount of the slate editor + const mountKey = useRef(0) + + const textTileRef = useRef(null) + useEffect(() => { + if (textModel) { + return addDisposer(textModel, onPatch(textModel, ({ op, path }) => { + if (op === "replace" && path === "/value") { + // don't respond to patch if the value is being set explicitly, + // only external changes, e.g. undo/redo + if (!textModel.isSettingValue) { + // set the new value and remount the editor + setInitialValue(modelValueToEditorValue(textModel?.value)) + ++mountKey.current + } + } + })) + } + }, [textModel]) + + if (!textModel) return null + + function handleFocus() { + // remember text content on focus so we can compare it on blur + textOnFocus.current = textModel?.textContent ?? "" + } + + function handleBlur() { + // update the model on blur, not on every change (e.g. keystroke) + if (textModel && !textModel.isEquivalent(editor.children)) { + const textDidChange = textOnFocus.current !== textModel.textContent + textModel?.applyModelChange(() => { + textModel.setValue(editor.children) + }, { + // log only when the text actually changed, e.g. not on style changes + // Note that logging of text changes was commented out in v2 in build 0601. ¯\_(ツ)_/¯ + // For now, we log just the text content, not the full JSON-stringified slate value. + log: textDidChange ? () => `Edited text component: ${textModel.textContent}` : undefined, + undoStringKey: "DG.Undo.textComponent.edit", + redoStringKey: "DG.Redo.textComponent.edit" + }) + } + } + + function handlePointerDownInTile(e: React.MouseEvent) { + const isWrapperClick = e.target === textTileRef.current + selectTile() + if (e.target === textTileRef.current) { + // clicks in the background of the tile focus the editor + isWrapperClick && ReactEditor.focus(editor) + e.preventDefault() + } + } + + return ( +
+ + + + +
+ ) +}) diff --git a/v3/src/components/text/text-toolbar.tsx b/v3/src/components/text/text-toolbar.tsx new file mode 100644 index 0000000000..31492b183e --- /dev/null +++ b/v3/src/components/text/text-toolbar.tsx @@ -0,0 +1,63 @@ +import { IButtonSpec, SlateToolbar, ToolbarTransform } from "@concord-consortium/slate-editor" +import { observer } from "mobx-react-lite" +import React from "react" +import { useTileModelContext } from "../../hooks/use-tile-model-context" +import { useDocumentContainerContext } from "../../hooks/use-document-container-context" + +const buttonOrder = [ + //column 1 + "bold", + "italic", + "underlined", + "deleted", + "code", + "superscript", + "subscript", + "color", + "image", + + //column 2 + "link", + "heading1", + "heading2", + "heading3", + "block-quote", + "ordered-list", + "bulleted-list" + // "fontIncrease", + // "fontDecrease", +] + +export const transformToolbarButtons: ToolbarTransform = (buttons) => { + // arrange the buttons + const buttonMap = new Map() + buttons.forEach(buttonSpec => { + if (buttonSpec.format) { + buttonMap.set(buttonSpec.format, buttonSpec) + } + }) + return buttonOrder.map(format => buttonMap.get(format)).filter(button => !!button) +} + +export const TextToolbar = observer(function TextToolbar() { + const documentContainerRef = useDocumentContainerContext() + const { isTileSelected } = useTileModelContext() + + return ( +
+ +
+ ) +}) diff --git a/v3/src/hooks/use-codap-component-context.ts b/v3/src/hooks/use-codap-component-context.ts new file mode 100644 index 0000000000..3abb9ada45 --- /dev/null +++ b/v3/src/hooks/use-codap-component-context.ts @@ -0,0 +1,8 @@ +import { createContext, createRef, useContext } from "react" + +const defaultRef = createRef() +export const CodapComponentContext = createContext>(defaultRef) + +export function useCodapComponentContext() { + return useContext(CodapComponentContext) +} diff --git a/v3/src/hooks/use-component-wrapper-context.ts b/v3/src/hooks/use-component-wrapper-context.ts new file mode 100644 index 0000000000..e14e62b3af --- /dev/null +++ b/v3/src/hooks/use-component-wrapper-context.ts @@ -0,0 +1,8 @@ +import { createContext, createRef, useContext } from "react" + +const defaultRef = createRef() +export const ComponentWrapperContext = createContext>(defaultRef) + +export function useComponentWrapperContext() { + return useContext(ComponentWrapperContext) +} diff --git a/v3/src/hooks/use-document-container-context.ts b/v3/src/hooks/use-document-container-context.ts new file mode 100644 index 0000000000..f752e56f91 --- /dev/null +++ b/v3/src/hooks/use-document-container-context.ts @@ -0,0 +1,8 @@ +import { createContext, createRef, useContext } from "react" + +const defaultRef = createRef() +export const DocumentContainerContext = createContext>(defaultRef) + +export function useDocumentContainerContext() { + return useContext(DocumentContainerContext) +} diff --git a/v3/src/hooks/use-mutation-observer.ts b/v3/src/hooks/use-mutation-observer.ts new file mode 100644 index 0000000000..c3dcb35537 --- /dev/null +++ b/v3/src/hooks/use-mutation-observer.ts @@ -0,0 +1,19 @@ +import { useEffect } from "react" +import { useDeepCompareMemo } from "use-deep-compare" +import { useMemo } from "use-memo-one" +import { useCallbackRef } from "./use-callback-ref" + +type Signature = (target: HTMLElement | null, options: MutationObserverInit, callback: MutationCallback) => void + +export const useMutationObserver: Signature = (target, _options, _callback) => { + const options = useDeepCompareMemo(() => _options, [_options]) + const callback = useCallbackRef(_callback) + const observer = useMemo(() => new MutationObserver(callback), [callback]) + + useEffect(() => { + if (target) { + observer.observe(target, options) + return () => observer.disconnect() + } + }, [observer, options, target]) +} diff --git a/v3/src/models/codap/add-default-content.ts b/v3/src/models/codap/add-default-content.ts index b11e93533e..695053c8e9 100644 --- a/v3/src/models/codap/add-default-content.ts +++ b/v3/src/models/codap/add-default-content.ts @@ -3,6 +3,7 @@ import { kCaseTableTileType } from "../../components/case-table/case-table-defs" import { kGraphTileType } from "../../components/graph/graph-defs" import { isGraphContentModel } from "../../components/graph/models/graph-content-model" import { kSliderTileType } from "../../components/slider/slider-defs" +import { kTextTileType } from "../../components/text/text-defs" import { urlParams } from "../../utilities/url-params" import { appState } from "../app-state" import { IFreeTileInRowOptions } from "../document/free-tile-row" @@ -34,6 +35,7 @@ export function addDefaultComponents() { const kWidth25 = kFullWidth / 4 const kWidth75 = kFullWidth * 3 / 4 const kFullHeight = 300 + const kHalfHeight = kFullHeight / 2 const kGap = 10 const sharedData = manager?.findFirstSharedModelByType(SharedDataSet) @@ -77,6 +79,14 @@ export function addDefaultComponents() { content.insertTileInRow(sliderTile, row, sliderOptions) } + const textTile = createDefaultTileOfType(kTextTileType) + if (textTile) { + const textOptions = isMosaicTileRow(row) + ? { splitTileId: sliderTile!.id, direction: "row" } + : { x: kFullWidth + kWidth25 + kGap, y: 110, width: kWidth75, height: kHalfHeight } + content.insertTileInRow(textTile, row, textOptions) + } + const graphTile = createDefaultTileOfType(kGraphTileType) if (graphTile) { const graphOptions = isMosaicTileRow(row) diff --git a/v3/src/test/setupTests.ts b/v3/src/test/setupTests.ts index 16a8c8604e..5b9ab0f8c9 100644 --- a/v3/src/test/setupTests.ts +++ b/v3/src/test/setupTests.ts @@ -4,12 +4,15 @@ import { enableFetchMocks } from "jest-fetch-mock" import "jest-webgl-canvas-mock" import { isEqual, isEqualWith } from "lodash" import ResizeObserverPolyfill from "resize-observer-polyfill" - +global.ResizeObserver = ResizeObserverPolyfill +// polyfill `TextEncoder` since it's not provided by JSDOM +// https://github.com/jsdom/jsdom/issues/2524 +// https://github.com/inrupt/solid-client-authn-js/issues/1676#issuecomment-917016646 +import { TextEncoder } from "util" +global.TextEncoder = TextEncoder import { assertIsDefined } from "./assert-is-defined" import { ConsoleMethod, IJestSpyConsoleOptions, jestSpyConsole, JestSpyConsoleFn } from "./jest-spy-console" -global.ResizeObserver = ResizeObserverPolyfill - // mock DOM APIs not supported by JSDOM Element.prototype.scrollIntoView = jest.fn() diff --git a/v3/src/test/v2/rich-text.codap b/v3/src/test/v2/rich-text.codap new file mode 100644 index 0000000000..43651caee4 --- /dev/null +++ b/v3/src/test/v2/rich-text.codap @@ -0,0 +1,38 @@ +{ + "name": "Untitled Document", + "guid": 1, + "id": 1, + "components": [ + { + "type": "DG.TextView", + "guid": 2, + "id": 2, + "componentStorage": { + "text": "{\"object\":\"value\",\"document\":{\"children\":[{\"type\":\"paragraph\",\"children\":[{\"text\":\"This is some \"},{\"text\":\"bold\",\"bold\":true},{\"text\":\" \"},{\"text\":\"italic\",\"italic\":true},{\"text\":\" \"},{\"text\":\"underlined\",\"underlined\":true},{\"text\":\" \"},{\"text\":\"deleted\",\"deleted\":true},{\"text\":\" \"},{\"text\":\"red\",\"color\":\"#ff0000\"},{\"text\":\" rich text.\"}]}],\"objTypes\":{\"paragraph\":\"block\"}}}", + "title": "Text", + "name": "Text", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "width": 300, + "height": 100, + "zIndex": 103, + "left": 5, + "top": 5, + "isVisible": true, + "right": 305, + "bottom": 105 + }, + "savedHeight": null + } + ], + "contexts": [], + "globalValues": [], + "appName": "DG", + "appVersion": "2.0", + "appBuildNum": "0727", + "lang": "en", + "idCount": 2, + "metadata": {} +} diff --git a/v3/src/test/v2/simple-text.codap b/v3/src/test/v2/simple-text.codap new file mode 100644 index 0000000000..b21a96232f --- /dev/null +++ b/v3/src/test/v2/simple-text.codap @@ -0,0 +1,38 @@ +{ + "name": "Untitled Document", + "guid": 1, + "id": 1, + "components": [ + { + "type": "DG.TextView", + "guid": 2, + "id": 2, + "componentStorage": { + "text": "This is some simple text.", + "title": "Text", + "name": "Text", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "width": 300, + "height": 100, + "zIndex": 103, + "left": 5, + "top": 5, + "isVisible": true, + "right": 305, + "bottom": 105 + }, + "savedHeight": null + } + ], + "contexts": [], + "globalValues": [], + "appName": "DG", + "appVersion": "2.0", + "appBuildNum": "0727", + "lang": "en", + "idCount": 2, + "metadata": {} +} diff --git a/v3/src/v2/codap-v2-types.ts b/v3/src/v2/codap-v2-types.ts index 00f2b23efe..91d63ee2cb 100644 --- a/v3/src/v2/codap-v2-types.ts +++ b/v3/src/v2/codap-v2-types.ts @@ -417,6 +417,13 @@ export interface ICodapV2GuideStorage extends ICodapV2BaseComponentStorage { items: Array<{ itemTitle: string, url: string }> } +export interface ICodapV2TextStorage extends ICodapV2BaseComponentStorage { + text: string + // v2's TextController.restoreComponentStorage references an `apiText` property, + // but TextController.createComponentStorage doesn't write one out. ¯\_(ツ)_/¯ + // apiText: string +} + export interface ICodapV2BaseComponent { type: string // e.g. "DG.TableView", "DG.GraphView", "DG.GuideView", etc. guid: number @@ -486,7 +493,16 @@ export interface ICodapV2GuideComponent extends ICodapV2BaseComponent { export const isV2GuideComponent = (component: ICodapV2BaseComponent): component is ICodapV2GuideComponent => component.type === "DG.GuideView" -export type CodapV2Component = ICodapV2GraphComponent | ICodapV2GuideComponent | ICodapV2TableComponent +export interface ICodapV2TextComponent extends ICodapV2BaseComponent { + type: "DG.TextView" + componentStorage: ICodapV2TextStorage +} +export const isV2TextComponent = (component: ICodapV2BaseComponent): component is ICodapV2TextComponent => + component.type === "DG.TextView" + +export type CodapV2Component = ICodapV2CalculatorComponent | ICodapGameViewComponent | ICodapV2GraphComponent | + ICodapV2GuideComponent | ICodapV2MapComponent | ICodapV2SliderComponent | + ICodapV2TableComponent | ICodapV2TextComponent | ICodapV2WebViewComponent export interface ICodapV2DocumentJson { type?: string // "DG.Document"