From a50e3ca50e9e6aed1942d85034bbb58594576c4e Mon Sep 17 00:00:00 2001 From: joostdecock Date: Sun, 23 Feb 2025 12:31:52 +0100 Subject: [PATCH] feat: EditSettings view --- config/dependencies.yaml | 4 + config/exceptions.yaml | 2 + package-lock.json | 268 +++++++++++++++++- .../react/components/DiffViewer/index.mjs | 8 + .../components/views/EditSettingsView.mjs | 216 +++++--------- .../Editor/components/views/index.mjs | 2 +- packages/react/hooks/useStateObject/index.mjs | 48 ++++ packages/react/package.json | 8 +- 8 files changed, 406 insertions(+), 150 deletions(-) create mode 100644 packages/react/components/DiffViewer/index.mjs create mode 100644 packages/react/hooks/useStateObject/index.mjs diff --git a/config/dependencies.yaml b/config/dependencies.yaml index 138aeb8d713..3e864fb06f0 100644 --- a/config/dependencies.yaml +++ b/config/dependencies.yaml @@ -238,9 +238,11 @@ collection: '@freesewing/snapseries': *freesewing react: _: + "@codemirror/lang-yaml": "^6.1.2" "@uiw/react-codemirror": "^4.23.8" d3-drag: "3.0.0" d3-selection: "3.0.0" + diff: "^7.0.0" file-saver: "^2.0.5" "highlight.js": "^11.11.1" html-react-parser: "^5.2.2" @@ -248,11 +250,13 @@ react: jotai-location: "^0.5.5" luxon: "^3.5.0" pdfkit: "^0.16.0" + react-diff-viewer-continued: "^4.0.5" react-dropzone: "^14.3.5" react-zoom-pan-pinch: "^3.7.0" svg-to-pdfkit: "^0.1.8" use-local-storage-state: "^19.5.0" web-worker: "^1.5.0" + yaml: "^2.7.0" peer: react: &react "^19.0.0" utils: diff --git a/config/exceptions.yaml b/config/exceptions.yaml index ad5c5eb86cd..e0f508cc6d4 100644 --- a/config/exceptions.yaml +++ b/config/exceptions.yaml @@ -81,6 +81,7 @@ packageJson: "./components/Control": "./components/Control/index.mjs" "./components/CopyToClipboardButton": "./components/CopyToClipboardButton/index.mjs" "./components/Design": "./components/Design/index.mjs" + "./components/DiffViewer": "./components/DiffViewer/index.mjs" "./components/Docusaurus": "./components/Docusaurus/index.mjs" "./components/Editor": "./components/Editor/index.mjs" "./components/Heading": "./components/Heading/index.mjs" @@ -120,6 +121,7 @@ packageJson: "./hooks/useDesign": "./hooks/useDesign/index.mjs" "./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs" "./hooks/useSelection": "./hooks/useSelection/index.mjs" + "./hooks/useStateObject": "./hooks/useStateObject/index.mjs" # Lib "./lib/RestClient": "./lib/RestClient/index.mjs" "./lib/logoPath": "./components/Logo/path.mjs" diff --git a/package-lock.json b/package-lock.json index 166c71e141a..a8fadd8224c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23951,6 +23951,21 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.10.8", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz", @@ -26309,6 +26324,188 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -27721,6 +27918,17 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/yaml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", + "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -34097,6 +34305,12 @@ "node": ">=8" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -37368,7 +37582,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -39868,6 +40081,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "license": "MIT" + }, "node_modules/find-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", @@ -48070,6 +48289,12 @@ "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==", + "license": "MIT" + }, "node_modules/meow": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", @@ -54643,6 +54868,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react-diff-viewer-continued": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-4.0.5.tgz", + "integrity": "sha512-L43gIPdhHgu1MYdip4vNqAt5s2JLICKe2/RyGUr2ohAxfhYaH1+QZ6vBO0qgo4xGBhE3jmvbOA/swq4/gdS/0g==", + "license": "MIT", + "dependencies": { + "@emotion/css": "^11.13.5", + "@emotion/react": "^11.14.0", + "classnames": "^2.5.1", + "diff": "^5.2.0", + "memoize-one": "^6.0.0" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -57881,6 +58126,12 @@ "postcss": "^8.4.31" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -61968,9 +62219,11 @@ "version": "3.3.0-rc.1", "license": "MIT", "dependencies": { + "@codemirror/lang-yaml": "^6.1.2", "@uiw/react-codemirror": "^4.23.8", "d3-drag": "3.0.0", "d3-selection": "3.0.0", + "diff": "^7.0.0", "file-saver": "^2.0.5", "highlight.js": "^11.11.1", "html-react-parser": "^5.2.2", @@ -61978,11 +62231,13 @@ "jotai-location": "^0.5.5", "luxon": "^3.5.0", "pdfkit": "^0.16.0", + "react-diff-viewer-continued": "^4.0.5", "react-dropzone": "^14.3.5", "react-zoom-pan-pinch": "^3.7.0", "svg-to-pdfkit": "^0.1.8", "use-local-storage-state": "^19.5.0", - "web-worker": "^1.5.0" + "web-worker": "^1.5.0", + "yaml": "^2.7.0" }, "devDependencies": {}, "engines": { @@ -61996,6 +62251,15 @@ "react": "^19.0.0" } }, + "packages/react/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "packages/rehype-highlight-lines": { "version": "3.3.0-rc.1", "extraneous": true, diff --git a/packages/react/components/DiffViewer/index.mjs b/packages/react/components/DiffViewer/index.mjs new file mode 100644 index 00000000000..693422bd280 --- /dev/null +++ b/packages/react/components/DiffViewer/index.mjs @@ -0,0 +1,8 @@ +import React from 'react' +import { diffWords, diffJson } from 'diff' +import ReactDiffViewer from 'react-diff-viewer-continued' + +export const diffJSON = (from, to) => diffJson(from, to) +export const diffCheck = (from, to) => diffWords(from, to) + +export const DiffViewer = (props) => diff --git a/packages/react/components/Editor/components/views/EditSettingsView.mjs b/packages/react/components/Editor/components/views/EditSettingsView.mjs index f412e16134c..f3ab518f5bf 100644 --- a/packages/react/components/Editor/components/views/EditSettingsView.mjs +++ b/packages/react/components/Editor/components/views/EditSettingsView.mjs @@ -1,14 +1,26 @@ // Dependencies -import { linkClasses, horFlexClasses, patternUrlFromState } from '@freesewing/utils' +import { linkClasses, horFlexClasses, patternUrlFromState, clone } from '@freesewing/utils' import { exportTypes, handleExport } from '../../lib/export/index.mjs' +import yaml from 'yaml' // Hooks import React, { useState } from 'react' +import { useStateObject } from '@freesewing/react/hooks/useStateObject' // Components -import { H1, H2, H3, H5 } from '@freesewing/react/components/Heading' +import { H1, H2, H3, H4, H5 } from '@freesewing/react/components/Heading' import { Popout } from '@freesewing/react/components/Popout' +import { DiffViewer, diffCheck } from '@freesewing/react/components/DiffViewer' import { HeaderMenu } from '../HeaderMenu.mjs' -import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/components/Icon' +import { + ResetIcon, + OkIcon, + EditIcon, + ExpandIcon, + CodeIcon, + TipIcon, + PrintIcon, +} from '@freesewing/react/components/Icon' import CodeMirror from '@uiw/react-codemirror' +import { yaml as yamlLang } from '@codemirror/lang-yaml' /** * This is the editSettings view @@ -20,13 +32,20 @@ import CodeMirror from '@uiw/react-codemirror' */ export const EditSettingsView = (props) => { const [settings, setSettings] = useState(props.state.settings) + const { state, config, update } = props return ( <> -
-

Documenation

- +
+

Edit settings by hand

+

+ You can hand-edit your pattern settings below. +
+ The changes will not take effect until you click the Apply changes button at the + bottom. +

+
) @@ -39,70 +58,38 @@ export const PrimedSettingsEditor = (props) => { /* * Destructure props */ - const { runningSettings } = props + const { state } = props /* * React state */ /* eslint-disable-next-line no-unused-vars */ - const [mSettings, update, setMSettings] = useStateObject(runningSettings) // Holds the settings - const [validationReport, setValidationReport] = useState(false) // Holds the validatino report + const [settings, update, setSettings] = useStateObject(state.settings) // Holds the settings const [showDelta, setShowDelta] = useState(false) - const [deployOngoing, setDeployOngoing] = useState(false) - const [doValidate, setDoValidate] = useState(false) - const [kiosk, setKiosk] = useState(false) - const [localJson, setLocalJson] = useState(JSON.stringify(runningSettings, null, 2)) // Holds the settings as JSON - const [localYaml, setLocalYaml] = useState(yaml.stringify(runningSettings)) // Holds the settings as YAML + const [localYaml, setLocalYaml] = useState(yaml.stringify(state.settings)) // Holds the settings as YAML /* - * Method to revert to running settings + * Method to revert to the settings in the editor state */ - const revert = () => setMSettings(cloneAsPojo(runningSettings)) - - /* - * API client - */ - const { api } = useApi() - - /* - * Loading context - */ - const { setLoadingStatus } = useContext(LoadingStatusContext) - - /* - * Helper method to deploy the settings - */ - const deploy = async () => { - setLoadingStatus([true, 'Uploading settings']) - setDeployOngoing(true) - const result = await api.deploy(mSettings) - return result[1] === 204 - ? setLoadingStatus([true, 'Settings updated', true, true]) - : setLoadingStatus([true, `Unable to deploy the settings`, true, false]) + const revert = () => { + setSettings(clone(state.settings)) + setLocalYaml(yaml.stringify(state.settings)) } - if (!mSettings.cluster) return null + /* + * Method to save to the settings into the editor state + */ + const save = () => { + props.update.state('settings', yaml.parse(localYaml)) + } + + if (!settings) return null /* * Handle settings delta */ const delta = - diffCheck(yaml.stringify(runningSettings), yaml.stringify(mSettings)).length > 1 ? true : false - - if (deployOngoing) - return ( - <> - -
-
- -
- Settings are being deployed -
-
-

Please wait as Morio applies the new settings.

- - ) + diffCheck(yaml.stringify(state.settings), yaml.stringify(settings)).length > 1 ? true : false const onChangeYaml = (input) => { let newSettings @@ -110,122 +97,59 @@ export const PrimedSettingsEditor = (props) => { newSettings = yaml.parse(input) if (newSettings) { setLocalYaml(input) - setMSettings(newSettings) + setSettings(newSettings) } } catch (err) { // This is fine } } - const onChangeJson = (input) => { - let newSettings - try { - newSettings = JSON.parse(input) - if (newSettings) { - setLocalJson(input) - setMSettings(newSettings) - } - } catch (err) { - console.log(err) - // This is fine - } - } return ( <> - {mSettings.preseed?.base ? ( - -
These settings are preseeded
-

- This Morio deployment uses preseeded settings. This means the settings are loaded from a - remote system, typically a version control system like GitLab or GitHub. -

-

- While you can update the settings here, those settings will be lost next time - Morio is reseeded. -
- You probably should update the preseeded setting instead. -

-
- ) : null} - {doValidate ? ( + + {delta ? ( <> - -

+

You have made changes

+

Your settings have been edited, and are now different from the editor settings.

+
-

- - ) : ( -
- - - - - - - - -
-
-
- )} - {!doValidate && delta ? ( - -

You have made changes that are yet to be deployed

-

The settings have been edited, and are now different from the deployed settings.

{showDelta ? ( -
+
) : null} -
- - -
- + ) : null} ) diff --git a/packages/react/components/Editor/components/views/index.mjs b/packages/react/components/Editor/components/views/index.mjs index 89ac31ac5b6..74d3177a3d4 100644 --- a/packages/react/components/Editor/components/views/index.mjs +++ b/packages/react/components/Editor/components/views/index.mjs @@ -105,7 +105,7 @@ export const viewLabels = { t: 'Export Pattern', d: 'Export this pattern into a variety of formats', }, - edit: { + editSettings: { t: 'Edit settings by hand', d: "Throw caution to the wind, and hand-edit the pattern's settings", }, diff --git a/packages/react/hooks/useStateObject/index.mjs b/packages/react/hooks/useStateObject/index.mjs new file mode 100644 index 00000000000..71e48aa7a53 --- /dev/null +++ b/packages/react/hooks/useStateObject/index.mjs @@ -0,0 +1,48 @@ +import React, { useState } from 'react' +import set from 'lodash/set.js' +import unset from 'lodash/unset.js' + +/* + * Helper method to handle object updates + * + * This is a wrapper around lodash.set() with extra support for unsetting data. + * + * @param {object} obj - The object to update + * @param {array|string} path - The path to the property to update either as an array or string in dot notation + * @param {mixed} val - The value to set. If the value holds the string 'unset' the property will be removed + * + * @return {object} obj - The mutated object + */ +export const objUpdate = (obj = {}, path, val = undefined) => { + if (val === undefined) { + if (Array.isArray(path) && Array.isArray(path[0])) { + for (const [ipath, ival = undefined] of path) { + if (ival === undefined) unset(obj, ipath) + else set(obj, ipath, ival) + } + } else unset(obj, path) + } else set(obj, path, val) + + return obj +} + +/* + * This hooks provides an React state update with an + * update method that allows us to set deeply nested + * properties. + */ +export const useStateObject = (dflt = {}) => { + const [obj, setObj] = useState(dflt) + + const replace = (val) => setObj(val) + + const update = (path, val, altObj = false) => { + const newObj = altObj ? { ...altObj } : { ...obj } + objUpdate(newObj, path, val) + setObj(newObj) + + return newObj + } + + return [obj, update, replace] +} diff --git a/packages/react/package.json b/packages/react/package.json index a656f989001..59d4166a807 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -34,6 +34,7 @@ "./components/Control": "./components/Control/index.mjs", "./components/CopyToClipboardButton": "./components/CopyToClipboardButton/index.mjs", "./components/Design": "./components/Design/index.mjs", + "./components/DiffViewer": "./components/DiffViewer/index.mjs", "./components/Docusaurus": "./components/Docusaurus/index.mjs", "./components/Editor": "./components/Editor/index.mjs", "./components/Heading": "./components/Heading/index.mjs", @@ -71,6 +72,7 @@ "./hooks/useDesign": "./hooks/useDesign/index.mjs", "./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs", "./hooks/useSelection": "./hooks/useSelection/index.mjs", + "./hooks/useStateObject": "./hooks/useStateObject/index.mjs", "./lib/RestClient": "./lib/RestClient/index.mjs", "./lib/logoPath": "./components/Logo/path.mjs" }, @@ -82,9 +84,11 @@ "react": "^19.0.0" }, "dependencies": { + "@codemirror/lang-yaml": "^6.1.2", "@uiw/react-codemirror": "^4.23.8", "d3-drag": "3.0.0", "d3-selection": "3.0.0", + "diff": "^7.0.0", "file-saver": "^2.0.5", "highlight.js": "^11.11.1", "html-react-parser": "^5.2.2", @@ -92,11 +96,13 @@ "jotai-location": "^0.5.5", "luxon": "^3.5.0", "pdfkit": "^0.16.0", + "react-diff-viewer-continued": "^4.0.5", "react-dropzone": "^14.3.5", "react-zoom-pan-pinch": "^3.7.0", "svg-to-pdfkit": "^0.1.8", "use-local-storage-state": "^19.5.0", - "web-worker": "^1.5.0" + "web-worker": "^1.5.0", + "yaml": "^2.7.0" }, "devDependencies": {}, "files": [