From 79f22b24e16a8a297c79b798f1690ffcda9debb7 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Tue, 15 Oct 2024 22:35:52 -0500 Subject: [PATCH 1/8] Need to fix the update part of this --- frontend/package-lock.json | 209 ++++++++++++++++++ frontend/package.json | 2 + .../components/visualizations/JSON/JSON.tsx | 73 ++++++ .../visualizations/JSON/manifest.json | 19 ++ frontend/src/visualization.config.ts | 8 + 5 files changed, 311 insertions(+) create mode 100644 frontend/src/components/visualizations/JSON/JSON.tsx create mode 100644 frontend/src/components/visualizations/JSON/manifest.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f04b9abb1..6ada17ad5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@appbaseio/reactivesearch": "^3.40.2", + "@codemirror/lang-json": "^6.0.1", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@material-ui/core": "^4.12.3", @@ -25,6 +26,7 @@ "@rjsf/validator-ajv8": "^5.18.4", "@types/d3": "^7.4.3", "@types/react-gravatar": "^2.6.10", + "@uiw/react-codemirror": "^4.23.5", "babel-polyfill": "^6.26.0", "classnames": "^2.2.6", "csvtojson": "^2.0.10", @@ -2725,6 +2727,102 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", + "integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.0.tgz", + "integrity": "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz", + "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz", + "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz", + "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", + "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.34.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz", + "integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@date-io/core": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", @@ -3441,6 +3539,37 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, + "node_modules/@lezer/common": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.2.tgz", + "integrity": "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@mapbox/jsonlint-lines-primitives": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", @@ -5467,6 +5596,57 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.5.tgz", + "integrity": "sha512-eTMfT8TejVN/D5vvuz9Lab+MIoRYdtqa2ftZZmU3JpcDIXf9KaExPo+G2Rl9HqySzaasgGXOOG164MAnj3MSIw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.5.tgz", + "integrity": "sha512-2zzGpx61L4mq9zDG/hfsO4wAH209TBE8VVsoj/qrccRe6KfcneCwKgRxtQjxBCCnO0Q5S+IP+uwCx5bXRzgQFQ==", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.23.5", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -8028,6 +8208,20 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -8371,6 +8565,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-blob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/cross-blob/-/cross-blob-2.0.1.tgz", @@ -20959,6 +21158,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, "node_modules/style-to-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz", @@ -22701,6 +22905,11 @@ "brace": "^0.11.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/wallaby-webpack": { "version": "3.9.16", "resolved": "https://registry.npmjs.org/wallaby-webpack/-/wallaby-webpack-3.9.16.tgz", diff --git a/frontend/package.json b/frontend/package.json index a06899a63..700e95d4b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "author": "NCSA", "dependencies": { "@appbaseio/reactivesearch": "^3.40.2", + "@codemirror/lang-json": "^6.0.1", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@material-ui/core": "^4.12.3", @@ -49,6 +50,7 @@ "@rjsf/validator-ajv8": "^5.18.4", "@types/d3": "^7.4.3", "@types/react-gravatar": "^2.6.10", + "@uiw/react-codemirror": "^4.23.5", "babel-polyfill": "^6.26.0", "classnames": "^2.2.6", "csvtojson": "^2.0.10", diff --git a/frontend/src/components/visualizations/JSON/JSON.tsx b/frontend/src/components/visualizations/JSON/JSON.tsx new file mode 100644 index 000000000..d75f6733d --- /dev/null +++ b/frontend/src/components/visualizations/JSON/JSON.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from "react"; +import { Button, Card, CardContent, CardActions } from "@mui/material"; +import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor +import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror +import { + downloadVisData, + fileDownloaded, + updateFile, +} from "../../../utils/visualization"; +import { readTextFromFile } from "../../../utils/common"; +import { downloadPublicVisData } from "../../../actions/public_visualization"; +import { filePublicDownloaded } from "../../../actions/public_file"; + +type jsonProps = { + fileId?: string; + visualizationId?: string; + publicView?: boolean; +}; + +export default function JSON(props: jsonProps) { + const { fileId, visualizationId, publicView } = props; + const [jsonContent, setJsonContent] = useState(); + + useEffect(() => { + const fetchData = async () => { + try { + let blob; + if (visualizationId) { + blob = publicView + ? await downloadPublicVisData(visualizationId) + : await downloadVisData(visualizationId); + } else { + blob = publicView + ? await filePublicDownloaded(fileId) + : await fileDownloaded(fileId, 0); + } + + const file = new File([blob], "data.json"); + const text = await readTextFromFile(file); + setJsonContent(text); + } catch (error) { + console.error("Error fetching data:", error); + } + }; + fetchData(); + }, [visualizationId, fileId, publicView]); + + const handleSave = async () => { + try { + await updateFile(fileId, jsonContent); + } catch (error) { + console.error("Error updating file:", error); + } + }; + + return ( + + + setJsonContent(value)} + theme="dark" + /> + + + + + + ); +} diff --git a/frontend/src/components/visualizations/JSON/manifest.json b/frontend/src/components/visualizations/JSON/manifest.json new file mode 100644 index 000000000..e6d5de031 --- /dev/null +++ b/frontend/src/components/visualizations/JSON/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "json-editor", + "version": "1.0.0", + "description": "A React component for visualizing and editing JSON data", + "main": "JSON.tsx", + "dependencies": { + "clowder2-core": "1.0.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "@uiw/react-codemirror": "^4.23.5", + "@codemirror/lang-json": "^6.0.1" + }, + "visConfig": { + "name": "JSON", + "mainType": "application", + "mimeTypes": ["application/json"], + "fields": [] + } +} diff --git a/frontend/src/visualization.config.ts b/frontend/src/visualization.config.ts index 35e8c627e..156739591 100644 --- a/frontend/src/visualization.config.ts +++ b/frontend/src/visualization.config.ts @@ -92,4 +92,12 @@ visComponentDefinitions.push({ component: React.createElement(registerComponent(configWordCloudSpec)), }); +const configJSON = require("./components/visualizations/JSON/manifest.json"); +visComponentDefinitions.push({ + name: configJSON.name, + mainType: configJSON.visConfig.mainType, + mimeTypes: configJSON.visConfig.mimeTypes, + component: React.createElement(registerComponent(configJSON)), +}); + export { visComponentDefinitions }; From e271848578a6b33d6833a9ee266655ed4298e117 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Wed, 16 Oct 2024 14:00:04 -0500 Subject: [PATCH 2/8] Need to fix the update part of this --- frontend/src/utils/visualization.js | 22 ++++++++++++++++++++++ frontend/src/visualization.config.ts | 16 ++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/frontend/src/utils/visualization.js b/frontend/src/utils/visualization.js index e743d3eed..0ee8b0ca8 100644 --- a/frontend/src/utils/visualization.js +++ b/frontend/src/utils/visualization.js @@ -62,6 +62,28 @@ export async function fileDownloaded(fileId, fileVersionNum = 0) { } } +export async function updateFile(fileId, fileData) { + const endpoint = `${config.hostname}/api/v2/files/${fileId}`; + // console.log("fileData", fileData); + // Create binary file string + const blob = new Blob([fileData], { type: "application/octet-stream" }); + const file = new File([blob], "file"); + const response = await fetch(endpoint, { + method: "PUT", + mode: "cors", + headers: await getHeader(), + body: { + file: file, + }, + }); + + if (response.status === 200) { + return true; + } else { + return false; + } +} + export async function publicFileDownloaded(fileId) { const endpoint = `${config.hostname}/api/v2/public_files/${fileId}?increment=False`; const response = await fetch(endpoint, { diff --git a/frontend/src/visualization.config.ts b/frontend/src/visualization.config.ts index 156739591..742f3e8ec 100644 --- a/frontend/src/visualization.config.ts +++ b/frontend/src/visualization.config.ts @@ -83,14 +83,14 @@ visComponentDefinitions.push({ mimeTypes: configVega.visConfig.mimeTypes, component: React.createElement(registerComponent(configVega)), }); - -const configWordCloudSpec = require("./components/visualizations/VegaSpec/manifest.json"); -visComponentDefinitions.push({ - name: configWordCloudSpec.name, - mainType: configWordCloudSpec.visConfig.mainType, - mimeTypes: configWordCloudSpec.visConfig.mimeTypes, - component: React.createElement(registerComponent(configWordCloudSpec)), -}); +// +// const configWordCloudSpec = require("./components/visualizations/VegaSpec/manifest.json"); +// visComponentDefinitions.push({ +// name: configWordCloudSpec.name, +// mainType: configWordCloudSpec.visConfig.mainType, +// mimeTypes: configWordCloudSpec.visConfig.mimeTypes, +// component: React.createElement(registerComponent(configWordCloudSpec)), +// }); const configJSON = require("./components/visualizations/JSON/manifest.json"); visComponentDefinitions.push({ From 66a2e1f5d464b4306309de489dbe688a73cf20e7 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Mon, 21 Oct 2024 16:47:36 -0500 Subject: [PATCH 3/8] The update is now working on save However the interactions with file versions is currently broken and need something --- .../components/visualizations/JSON/JSON.tsx | 73 ----------- .../JSONVisualizer/JSONVisualizer.tsx | 116 ++++++++++++++++++ .../{JSON => JSONVisualizer}/manifest.json | 4 +- frontend/src/visualization.config.ts | 2 +- 4 files changed, 119 insertions(+), 76 deletions(-) delete mode 100644 frontend/src/components/visualizations/JSON/JSON.tsx create mode 100644 frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx rename frontend/src/components/visualizations/{JSON => JSONVisualizer}/manifest.json (87%) diff --git a/frontend/src/components/visualizations/JSON/JSON.tsx b/frontend/src/components/visualizations/JSON/JSON.tsx deleted file mode 100644 index d75f6733d..000000000 --- a/frontend/src/components/visualizations/JSON/JSON.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Button, Card, CardContent, CardActions } from "@mui/material"; -import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor -import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror -import { - downloadVisData, - fileDownloaded, - updateFile, -} from "../../../utils/visualization"; -import { readTextFromFile } from "../../../utils/common"; -import { downloadPublicVisData } from "../../../actions/public_visualization"; -import { filePublicDownloaded } from "../../../actions/public_file"; - -type jsonProps = { - fileId?: string; - visualizationId?: string; - publicView?: boolean; -}; - -export default function JSON(props: jsonProps) { - const { fileId, visualizationId, publicView } = props; - const [jsonContent, setJsonContent] = useState(); - - useEffect(() => { - const fetchData = async () => { - try { - let blob; - if (visualizationId) { - blob = publicView - ? await downloadPublicVisData(visualizationId) - : await downloadVisData(visualizationId); - } else { - blob = publicView - ? await filePublicDownloaded(fileId) - : await fileDownloaded(fileId, 0); - } - - const file = new File([blob], "data.json"); - const text = await readTextFromFile(file); - setJsonContent(text); - } catch (error) { - console.error("Error fetching data:", error); - } - }; - fetchData(); - }, [visualizationId, fileId, publicView]); - - const handleSave = async () => { - try { - await updateFile(fileId, jsonContent); - } catch (error) { - console.error("Error updating file:", error); - } - }; - - return ( - - - setJsonContent(value)} - theme="dark" - /> - - - - - - ); -} diff --git a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx new file mode 100644 index 000000000..93c3b50fb --- /dev/null +++ b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useState } from "react"; +import { + Button, + Card, + CardContent, + CardActions, + CircularProgress, +} from "@mui/material"; +import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor +import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror +import { downloadVisData, fileDownloaded } from "../../../utils/visualization"; +import { updateFile as updateFileAction } from "../../../actions/file"; +import { readTextFromFile } from "../../../utils/common"; +import { downloadPublicVisData } from "../../../actions/public_visualization"; +import { filePublicDownloaded } from "../../../actions/public_file"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../../../types/data"; + +type jsonProps = { + fileId?: string; + visualizationId?: string; + publicView?: boolean; +}; + +export default function JSONVisualizer(props: jsonProps) { + const { fileId, visualizationId, publicView } = props; + const [jsonContent, setJsonContent] = useState(); + const [fileName, setFileName] = useState(); + const [loading, setLoading] = useState(false); + + // use useSelector to get fileSummary to get filename. + const fileData = useSelector((state: RootState) => state.file); + + // use useDispatch to update file + const dispatch = useDispatch(); + const updateFile = async (file: File, fileId: string | undefined) => + dispatch(updateFileAction(file, fileId)); + + useEffect(() => { + if (fileData.fileSummary) { + setFileName(fileData.fileSummary.name); + } + }, [fileData.fileSummary]); + + useEffect(() => { + console.log("fileData", fileData.fileSummary.content_type); + }, [fileName]); + + useEffect(() => { + const fetchData = async () => { + try { + let blob; + if (visualizationId) { + blob = publicView + ? await downloadPublicVisData(visualizationId) + : await downloadVisData(visualizationId); + } else { + blob = publicView + ? await filePublicDownloaded(fileId) + : await fileDownloaded(fileId, 0); + } + + const file = new File([blob], fileName); + const text = await readTextFromFile(file); + setJsonContent(text); + } catch (error) { + console.error("Error fetching data:", error); + } + }; + fetchData(); + }, [visualizationId, fileId, publicView]); + + const handleSave = async () => { + try { + if ( + jsonContent !== undefined && + fileName && + fileData.fileSummary?.content_type + ) { + // Parse the jsonContent to ensure it's valid JSON + const textBlob = new Blob([jsonContent], { type: "text/plain" }); + const file = new File([textBlob], fileName, { + type: fileData.fileSummary.content_type.content_type, + }); + + setLoading(true); + await updateFile(file, fileId); + setLoading(false); + } + } catch (error) { + console.error("Error updating file:", error); + } + }; + + return ( + + + {loading ? ( + + ) : ( + setJsonContent(value)} + theme="dark" + /> + )} + + + + + + ); +} diff --git a/frontend/src/components/visualizations/JSON/manifest.json b/frontend/src/components/visualizations/JSONVisualizer/manifest.json similarity index 87% rename from frontend/src/components/visualizations/JSON/manifest.json rename to frontend/src/components/visualizations/JSONVisualizer/manifest.json index e6d5de031..91680965b 100644 --- a/frontend/src/components/visualizations/JSON/manifest.json +++ b/frontend/src/components/visualizations/JSONVisualizer/manifest.json @@ -2,7 +2,7 @@ "name": "json-editor", "version": "1.0.0", "description": "A React component for visualizing and editing JSON data", - "main": "JSON.tsx", + "main": "JSONVisualizer.tsx", "dependencies": { "clowder2-core": "1.0.0", "react": "^17.0.2", @@ -11,7 +11,7 @@ "@codemirror/lang-json": "^6.0.1" }, "visConfig": { - "name": "JSON", + "name": "JSONVisualizer", "mainType": "application", "mimeTypes": ["application/json"], "fields": [] diff --git a/frontend/src/visualization.config.ts b/frontend/src/visualization.config.ts index 742f3e8ec..e1717516a 100644 --- a/frontend/src/visualization.config.ts +++ b/frontend/src/visualization.config.ts @@ -92,7 +92,7 @@ visComponentDefinitions.push({ // component: React.createElement(registerComponent(configWordCloudSpec)), // }); -const configJSON = require("./components/visualizations/JSON/manifest.json"); +const configJSON = require("./components/visualizations/JSONVisualizer/manifest.json"); visComponentDefinitions.push({ name: configJSON.name, mainType: configJSON.visConfig.mainType, From c1558f11cd4ab585f13bb5303234ed5c77c192f2 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Wed, 13 Nov 2024 08:40:14 -0600 Subject: [PATCH 4/8] Updating to check if any changes before saving and also validating JSON --- .../JSONVisualizer/JSONVisualizer.tsx | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx index 93c3b50fb..4a998b57c 100644 --- a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx +++ b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx @@ -24,9 +24,15 @@ type jsonProps = { export default function JSONVisualizer(props: jsonProps) { const { fileId, visualizationId, publicView } = props; + + // State to store the original content of the file and the displayed JSON content that can be edited + const [originalContent, setOriginalContent] = useState(); const [jsonContent, setJsonContent] = useState(); + + // Utility states to help with saving the file, displaying loading spinner and validating JSON const [fileName, setFileName] = useState(); const [loading, setLoading] = useState(false); + const [validJson, setValidJson] = useState(true); // use useSelector to get fileSummary to get filename. const fileData = useSelector((state: RootState) => state.file); @@ -42,10 +48,6 @@ export default function JSONVisualizer(props: jsonProps) { } }, [fileData.fileSummary]); - useEffect(() => { - console.log("fileData", fileData.fileSummary.content_type); - }, [fileName]); - useEffect(() => { const fetchData = async () => { try { @@ -62,6 +64,7 @@ export default function JSONVisualizer(props: jsonProps) { const file = new File([blob], fileName); const text = await readTextFromFile(file); + setOriginalContent(text); setJsonContent(text); } catch (error) { console.error("Error fetching data:", error); @@ -70,6 +73,20 @@ export default function JSONVisualizer(props: jsonProps) { fetchData(); }, [visualizationId, fileId, publicView]); + const validateJson = (jsonString: string) => { + try { + JSON.parse(jsonString); + return true; + } catch (error) { + return false; + } + } + + const handleChange = (value: string) => { + setJsonContent(value); + setValidJson(validateJson(value)); + } + const handleSave = async () => { try { if ( @@ -77,7 +94,6 @@ export default function JSONVisualizer(props: jsonProps) { fileName && fileData.fileSummary?.content_type ) { - // Parse the jsonContent to ensure it's valid JSON const textBlob = new Blob([jsonContent], { type: "text/plain" }); const file = new File([textBlob], fileName, { type: fileData.fileSummary.content_type.content_type, @@ -86,12 +102,19 @@ export default function JSONVisualizer(props: jsonProps) { setLoading(true); await updateFile(file, fileId); setLoading(false); + + // Refreshing the page to reflect the changes. TODO: Find a better way to update the version + window.location.reload(); } } catch (error) { console.error("Error updating file:", error); } }; + const disableSaveButton = () => { + return originalContent === jsonContent || !validJson; + } + return ( @@ -101,13 +124,13 @@ export default function JSONVisualizer(props: jsonProps) { setJsonContent(value)} + onChange={(value) => handleChange(value)} theme="dark" /> )} - From 115b0b11171ca3a64edf90b363708263db8b9c38 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Mon, 18 Nov 2024 10:58:52 -0600 Subject: [PATCH 5/8] Updating JSON Visualizer to track file version change Redux bug regarding selected fileversion for file --- frontend/src/actions/file.js | 2 +- frontend/src/components/files/File.tsx | 9 ++++- .../JSONVisualizer/JSONVisualizer.tsx | 35 ++++++++++++------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/frontend/src/actions/file.js b/frontend/src/actions/file.js index 45d73c8b8..1168a3051 100644 --- a/frontend/src/actions/file.js +++ b/frontend/src/actions/file.js @@ -231,7 +231,7 @@ export function changeSelectedVersion(fileId, selectedVersion) { return (dispatch) => { dispatch({ type: CHANGE_SELECTED_VERSION, - version: selectedVersion, + selected_version: selectedVersion, receivedAt: Date.now(), }); }; diff --git a/frontend/src/components/files/File.tsx b/frontend/src/components/files/File.tsx index 53853f788..e8948a423 100644 --- a/frontend/src/components/files/File.tsx +++ b/frontend/src/components/files/File.tsx @@ -15,7 +15,7 @@ import { useParams, useSearchParams } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { a11yProps, TabPanel } from "../tabs/TabComponent"; -import { fetchFileSummary, fetchFileVersions } from "../../actions/file"; +import { fetchFileSummary, fetchFileVersions, changeSelectedVersion } from "../../actions/file"; import { MainBreadcrumbs } from "../navigation/BreadCrumb"; import { FileVersionHistory } from "../versions/FileVersionHistory"; import { DisplayMetadata } from "../metadata/DisplayMetadata"; @@ -77,6 +77,8 @@ export const File = (): JSX.Element => { dispatch(deleteFileMetadataAction(fileId, metadata)); const getFolderPath = (folderId: string | null) => dispatch(fetchFolderPath(folderId)); + const changeFileVersion = (fileId: string | undefined, version: number) => + dispatch(changeSelectedVersion(fileId, version)); const file = useSelector((state: RootState) => state.file); const latestVersionNum = useSelector( @@ -179,6 +181,11 @@ export const File = (): JSX.Element => { setSelectedTabIndex(newTabIndex); }; + // Set file version + useEffect(() => { + changeFileVersion(fileId, selectedVersionNum); + }, [selectedVersionNum]); + const setMetadata = (metadata: any) => { // TODO wrap this in to a function setMetadataRequestForms((prevState) => { diff --git a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx index 4a998b57c..144540ad9 100644 --- a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx +++ b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx @@ -9,7 +9,7 @@ import { import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror import { downloadVisData, fileDownloaded } from "../../../utils/visualization"; -import { updateFile as updateFileAction } from "../../../actions/file"; +import {fetchFileVersions, updateFile as updateFileAction} from "../../../actions/file"; import { readTextFromFile } from "../../../utils/common"; import { downloadPublicVisData } from "../../../actions/public_visualization"; import { filePublicDownloaded } from "../../../actions/public_file"; @@ -23,32 +23,41 @@ type jsonProps = { }; export default function JSONVisualizer(props: jsonProps) { - const { fileId, visualizationId, publicView } = props; + const { visualizationId, publicView } = props; + // TODO: Use fileData to get the fileid to reflect version change + const fileId = useSelector((state: RootState) => state.file.fileSummary?.id); + const versionNum = useSelector((state: RootState) => state.file.selected_version_num); + const fileSummary = useSelector((state: RootState) => state.file.fileSummary); + const fileData = useSelector((state: RootState) => state.file); // State to store the original content of the file and the displayed JSON content that can be edited const [originalContent, setOriginalContent] = useState(); const [jsonContent, setJsonContent] = useState(); - // Utility states to help with saving the file, displaying loading spinner and validating JSON + // Utility state to help with saving the file, displaying loading spinner and validating JSON const [fileName, setFileName] = useState(); const [loading, setLoading] = useState(false); const [validJson, setValidJson] = useState(true); - // use useSelector to get fileSummary to get filename. - const fileData = useSelector((state: RootState) => state.file); - // use useDispatch to update file const dispatch = useDispatch(); const updateFile = async (file: File, fileId: string | undefined) => dispatch(updateFileAction(file, fileId)); useEffect(() => { - if (fileData.fileSummary) { - setFileName(fileData.fileSummary.name); + console.log("File Data: ", fileData); + }, [fileData]); + + useEffect(() => { + if (fileSummary) { + setFileName(fileSummary.name); + console.log("File Summary: ", fileSummary); } - }, [fileData.fileSummary]); + }, [fileSummary]); useEffect(() => { + console.log("File ID: ", fileId); + console.log("Version Num: ", versionNum); const fetchData = async () => { try { let blob; @@ -59,7 +68,7 @@ export default function JSONVisualizer(props: jsonProps) { } else { blob = publicView ? await filePublicDownloaded(fileId) - : await fileDownloaded(fileId, 0); + : await fileDownloaded(fileId, versionNum); } const file = new File([blob], fileName); @@ -71,7 +80,7 @@ export default function JSONVisualizer(props: jsonProps) { } }; fetchData(); - }, [visualizationId, fileId, publicView]); + }, [visualizationId, fileId, publicView, versionNum]); const validateJson = (jsonString: string) => { try { @@ -92,11 +101,11 @@ export default function JSONVisualizer(props: jsonProps) { if ( jsonContent !== undefined && fileName && - fileData.fileSummary?.content_type + fileSummary?.content_type ) { const textBlob = new Blob([jsonContent], { type: "text/plain" }); const file = new File([textBlob], fileName, { - type: fileData.fileSummary.content_type.content_type, + type: fileSummary.content_type.content_type, }); setLoading(true); From 5126bd21f06db25e3051b93787b2e0e4dcfb1398 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Mon, 18 Nov 2024 11:01:42 -0600 Subject: [PATCH 6/8] forgot to lint --- frontend/src/components/files/File.tsx | 6 +++- .../JSONVisualizer/JSONVisualizer.tsx | 30 +++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/files/File.tsx b/frontend/src/components/files/File.tsx index e8948a423..39f5b5ed8 100644 --- a/frontend/src/components/files/File.tsx +++ b/frontend/src/components/files/File.tsx @@ -15,7 +15,11 @@ import { useParams, useSearchParams } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { a11yProps, TabPanel } from "../tabs/TabComponent"; -import { fetchFileSummary, fetchFileVersions, changeSelectedVersion } from "../../actions/file"; +import { + fetchFileSummary, + fetchFileVersions, + changeSelectedVersion, +} from "../../actions/file"; import { MainBreadcrumbs } from "../navigation/BreadCrumb"; import { FileVersionHistory } from "../versions/FileVersionHistory"; import { DisplayMetadata } from "../metadata/DisplayMetadata"; diff --git a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx index 144540ad9..43fe761af 100644 --- a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx +++ b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx @@ -9,7 +9,10 @@ import { import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror import { downloadVisData, fileDownloaded } from "../../../utils/visualization"; -import {fetchFileVersions, updateFile as updateFileAction} from "../../../actions/file"; +import { + fetchFileVersions, + updateFile as updateFileAction, +} from "../../../actions/file"; import { readTextFromFile } from "../../../utils/common"; import { downloadPublicVisData } from "../../../actions/public_visualization"; import { filePublicDownloaded } from "../../../actions/public_file"; @@ -23,10 +26,12 @@ type jsonProps = { }; export default function JSONVisualizer(props: jsonProps) { - const { visualizationId, publicView } = props; + const { visualizationId, publicView } = props; // TODO: Use fileData to get the fileid to reflect version change const fileId = useSelector((state: RootState) => state.file.fileSummary?.id); - const versionNum = useSelector((state: RootState) => state.file.selected_version_num); + const versionNum = useSelector( + (state: RootState) => state.file.selected_version_num + ); const fileSummary = useSelector((state: RootState) => state.file.fileSummary); const fileData = useSelector((state: RootState) => state.file); @@ -89,20 +94,16 @@ export default function JSONVisualizer(props: jsonProps) { } catch (error) { return false; } - } + }; const handleChange = (value: string) => { setJsonContent(value); setValidJson(validateJson(value)); - } + }; const handleSave = async () => { try { - if ( - jsonContent !== undefined && - fileName && - fileSummary?.content_type - ) { + if (jsonContent !== undefined && fileName && fileSummary?.content_type) { const textBlob = new Blob([jsonContent], { type: "text/plain" }); const file = new File([textBlob], fileName, { type: fileSummary.content_type.content_type, @@ -122,7 +123,7 @@ export default function JSONVisualizer(props: jsonProps) { const disableSaveButton = () => { return originalContent === jsonContent || !validJson; - } + }; return ( @@ -139,7 +140,12 @@ export default function JSONVisualizer(props: jsonProps) { )} - From 77121418988f5226cd0e1375dcfa74420571cea5 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Mon, 18 Nov 2024 11:17:05 -0600 Subject: [PATCH 7/8] cleaning up --- .../JSONVisualizer/JSONVisualizer.tsx | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx index 43fe761af..81e870c74 100644 --- a/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx +++ b/frontend/src/components/visualizations/JSONVisualizer/JSONVisualizer.tsx @@ -8,11 +8,9 @@ import { } from "@mui/material"; import CodeMirror from "@uiw/react-codemirror"; // CodeMirror editor import { json } from "@codemirror/lang-json"; // JSON language support for CodeMirror + import { downloadVisData, fileDownloaded } from "../../../utils/visualization"; -import { - fetchFileVersions, - updateFile as updateFileAction, -} from "../../../actions/file"; +import { updateFile as updateFileAction } from "../../../actions/file"; import { readTextFromFile } from "../../../utils/common"; import { downloadPublicVisData } from "../../../actions/public_visualization"; import { filePublicDownloaded } from "../../../actions/public_file"; @@ -26,14 +24,11 @@ type jsonProps = { }; export default function JSONVisualizer(props: jsonProps) { - const { visualizationId, publicView } = props; - // TODO: Use fileData to get the fileid to reflect version change - const fileId = useSelector((state: RootState) => state.file.fileSummary?.id); - const versionNum = useSelector( + const { fileId, visualizationId, publicView } = props; + const selectedFileVersion = useSelector( (state: RootState) => state.file.selected_version_num ); const fileSummary = useSelector((state: RootState) => state.file.fileSummary); - const fileData = useSelector((state: RootState) => state.file); // State to store the original content of the file and the displayed JSON content that can be edited const [originalContent, setOriginalContent] = useState(); @@ -49,20 +44,13 @@ export default function JSONVisualizer(props: jsonProps) { const updateFile = async (file: File, fileId: string | undefined) => dispatch(updateFileAction(file, fileId)); - useEffect(() => { - console.log("File Data: ", fileData); - }, [fileData]); - useEffect(() => { if (fileSummary) { setFileName(fileSummary.name); - console.log("File Summary: ", fileSummary); } }, [fileSummary]); useEffect(() => { - console.log("File ID: ", fileId); - console.log("Version Num: ", versionNum); const fetchData = async () => { try { let blob; @@ -73,7 +61,7 @@ export default function JSONVisualizer(props: jsonProps) { } else { blob = publicView ? await filePublicDownloaded(fileId) - : await fileDownloaded(fileId, versionNum); + : await fileDownloaded(fileId, selectedFileVersion); } const file = new File([blob], fileName); @@ -85,7 +73,7 @@ export default function JSONVisualizer(props: jsonProps) { } }; fetchData(); - }, [visualizationId, fileId, publicView, versionNum]); + }, [visualizationId, fileId, publicView, selectedFileVersion]); const validateJson = (jsonString: string) => { try { @@ -125,6 +113,10 @@ export default function JSONVisualizer(props: jsonProps) { return originalContent === jsonContent || !validJson; }; + // Flag for previous versions + const isPreviousVersion = () => { + return selectedFileVersion !== fileSummary?.version_num; + }; return ( @@ -140,14 +132,16 @@ export default function JSONVisualizer(props: jsonProps) { )} - + {!isPreviousVersion() && ( + + )} ); From 0335158115110dbd3e8cac4a94f22f7c27a86c79 Mon Sep 17 00:00:00 2001 From: Vismayak Mohanarajan Date: Mon, 18 Nov 2024 11:32:23 -0600 Subject: [PATCH 8/8] Fixed download button to download selection versions of file --- frontend/src/components/files/FileActionsMenu.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/files/FileActionsMenu.tsx b/frontend/src/components/files/FileActionsMenu.tsx index bf3a6dea2..0a687d551 100644 --- a/frontend/src/components/files/FileActionsMenu.tsx +++ b/frontend/src/components/files/FileActionsMenu.tsx @@ -38,6 +38,9 @@ type FileActionsMenuProps = { export const FileActionsMenu = (props: FileActionsMenuProps): JSX.Element => { const { fileId, datasetId, setSelectedVersion } = props; + const selectedFileVersion = useSelector( + (state: RootState) => state.file.selected_version_num + ); const [anchorEl, setAnchorEl] = React.useState(null); const [fileShareModalOpen, setFileShareModalOpen] = useState(false); @@ -158,7 +161,7 @@ export const FileActionsMenu = (props: FileActionsMenuProps): JSX.Element => { type: INCREMENT_FILE_DOWNLOADS, receivedAt: Date.now(), }); - window.location.href = `${config.hostname}/api/v2/files/${fileId}`; + window.location.href = `${config.hostname}/api/v2/files/${fileId}?version=${selectedFileVersion}`; }} endIcon={} >