From 7a968e8c9db63b79a6026ec25ff6ef1d6462a9f3 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 31 Jan 2025 21:05:05 -0800 Subject: [PATCH] [UI] Data source code snippets (#321) * Export snackbar and move to match designs Signed-off-by: brookewp * Create code snippet component Signed-off-by: brookewp * Add to settings page Signed-off-by: brookewp * Add method to fetch snippets Signed-off-by: brookewp * Replace duplicate snackbar function Signed-off-by: brookewp * Update logic and replace modal for working esc Signed-off-by: brookewp * Add TabPanel to handle array of snippets Signed-off-by: brookewp * Add title and icon to code snippet modal Signed-off-by: brookewp * Switch to light build Signed-off-by: brookewp --------- Signed-off-by: brookewp --- package-lock.json | 290 +++++++++++++++++- package.json | 4 +- .../components/modals/BaseModal.tsx | 10 +- src/data-sources/DataSourceList.scss | 30 ++ src/data-sources/DataSourceList.tsx | 94 ++++-- src/data-sources/components/CodeSnippet.tsx | 55 ++++ src/data-sources/hooks/useDataSources.ts | 26 ++ src/settings/SettingsPage.tsx | 4 +- src/settings/index.scss | 6 +- 9 files changed, 486 insertions(+), 33 deletions(-) create mode 100644 src/data-sources/components/CodeSnippet.tsx diff --git a/package-lock.json b/package-lock.json index e8b8e7c3c..c01eb735c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "GPL-2.0-or-later", "dependencies": { "@automattic/calypso-analytics": "^1.1.3", - "@wordpress/dataviews": "^4.10.0" + "@wordpress/dataviews": "^4.10.0", + "react-syntax-highlighter": "^15.6.1" }, "devDependencies": { "@automattic/eslint-plugin-wpvip": "0.13.0", @@ -18,6 +19,7 @@ "@playwright/test": "^1.50.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", + "@types/react-syntax-highlighter": "^15.5.13", "@types/wordpress__block-editor": "11.5.16", "@types/wordpress__blocks": "12.5.16", "@vitest/coverage-v8": "3.0.4", @@ -5204,6 +5206,15 @@ "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", "integrity": "sha512-XDbrTSBlQV9nxE1GiDL3FaOPy4G/KaJkhDutBX48Kg8CYZMBARyyDFGCWfWJn4pobmInmwud1xxH7VJMAr0CKQ==" }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/highlight-words-core": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", @@ -5372,6 +5383,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -5483,6 +5504,12 @@ "node": ">=0.10.0" } }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/@types/webpack": { "version": "4.41.39", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.39.tgz", @@ -12305,6 +12332,36 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -12795,6 +12852,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -15736,6 +15803,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -16210,6 +16290,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -16866,6 +16954,33 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -16880,6 +16995,21 @@ "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.2.tgz", "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==" }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -17435,6 +17565,30 @@ "node": ">=8" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -17588,6 +17742,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -17678,6 +17842,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -20137,6 +20311,20 @@ "node": ">=8" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -21626,6 +21814,24 @@ "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", "dev": true }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -22700,6 +22906,15 @@ "dev": true, "peer": true }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -22745,6 +22960,19 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -23191,6 +23419,23 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -23373,6 +23618,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -24886,6 +25155,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawnd": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-9.0.2.tgz", @@ -27915,6 +28194,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y-indexeddb": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.12.tgz", diff --git a/package.json b/package.json index f10d78fae..1e3b3a8ef 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@playwright/test": "^1.50.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", + "@types/react-syntax-highlighter": "^15.5.13", "@types/wordpress__block-editor": "11.5.16", "@types/wordpress__blocks": "12.5.16", "@vitest/coverage-v8": "3.0.4", @@ -111,6 +112,7 @@ }, "dependencies": { "@automattic/calypso-analytics": "^1.1.3", - "@wordpress/dataviews": "^4.10.0" + "@wordpress/dataviews": "^4.10.0", + "react-syntax-highlighter": "^15.6.1" } } diff --git a/src/blocks/remote-data-container/components/modals/BaseModal.tsx b/src/blocks/remote-data-container/components/modals/BaseModal.tsx index 0ec73b131..aac6fca2b 100644 --- a/src/blocks/remote-data-container/components/modals/BaseModal.tsx +++ b/src/blocks/remote-data-container/components/modals/BaseModal.tsx @@ -1,16 +1,14 @@ import { Button, Modal } from '@wordpress/components'; +import { ModalProps } from '@wordpress/components/build-types/modal/types'; import { __ } from '@/utils/i18n'; -export interface BaseModalProps { +export type BaseModalProps = Omit< ModalProps, 'onRequestClose' > & { children: JSX.Element; - className?: string; headerActions?: JSX.Element; headerImage?: string; onClose: () => void; - size?: 'small' | 'medium' | 'large' | 'fill'; - title: string; -} +}; export function BaseModal( props: BaseModalProps ) { return ( @@ -30,7 +28,7 @@ export function BaseModal( props: BaseModalProps ) { } onRequestClose={ props.onClose } size={ props.size ?? 'fill' } - title={ props.title } + { ...props } > { props.children } diff --git a/src/data-sources/DataSourceList.scss b/src/data-sources/DataSourceList.scss index 6b656a8ec..b259eba74 100644 --- a/src/data-sources/DataSourceList.scss +++ b/src/data-sources/DataSourceList.scss @@ -75,3 +75,33 @@ table.data-source-list { padding: 4px 8px; vertical-align: baseline; } + +.settings_page_remote-data-blocks-settings .components-modal__frame.has-size-medium:not(.components-confirm-dialog) { + max-width: calc(100% - 32px); + max-height: calc(100% - 120px); + + .components-modal__header { + padding: 24px 32px 8px 56px; + } +} + +.rdb-settings-page_data-source-code-snippet { + + .components-tab-panel__tabs { + border-bottom: 1px solid var(--Alias-border-border-input, #aeaeae); + // to match tab panel in editor sidebar + height: 48px; + } + + .components-tab-panel__tab-content { + border: 1px solid var(--Alias-border-border-input, #aeaeae); + border-top: none; + + } +} + +.rdb-settings-page_data-source-code-snippet-modal svg { + width: 25px; + margin-right: 8px; +} + diff --git a/src/data-sources/DataSourceList.tsx b/src/data-sources/DataSourceList.tsx index 76bf24cf2..075db6e4a 100644 --- a/src/data-sources/DataSourceList.tsx +++ b/src/data-sources/DataSourceList.tsx @@ -1,9 +1,10 @@ import { __experimentalConfirmDialog as ConfirmDialog, + ExternalLink, Icon, Placeholder, + TabPanel, } from '@wordpress/components'; -import { useDispatch } from '@wordpress/data'; import { Action, DataViews, @@ -14,9 +15,11 @@ import { import { useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { info } from '@wordpress/icons'; -import { store as noticesStore, NoticeStoreActions, WPNotice } from '@wordpress/notices'; +import CodeSnippet from './components/CodeSnippet'; import { SUPPORTED_SERVICES, SUPPORTED_SERVICES_LABELS } from './constants'; +import { BaseModal } from '@/blocks/remote-data-container/components/modals/BaseModal'; +import { useModalState } from '@/blocks/remote-data-container/hooks/useModalState'; import DataSourceMetaTags from '@/data-sources/DataSourceMetaTags'; import { useDataSources } from '@/data-sources/hooks/useDataSources'; import { DataSourceConfig } from '@/data-sources/types'; @@ -28,19 +31,27 @@ import HttpIcon from '@/settings/icons/HttpIcon'; import { ShopifyIcon } from '@/settings/icons/ShopifyIcon'; const DataSourceList = () => { - const { createSuccessNotice, createErrorNotice } = - useDispatch< NoticeStoreActions >( noticesStore ); const { dataSources, loadingDataSources, deleteDataSource, deleteMultipleDataSources, fetchDataSources, + getDataSourceSnippet, addDataSource, + showSnackbar, } = useDataSources(); const [ dataSourceToDelete, setDataSourceToDelete ] = useState< DataSourceConfig | DataSourceConfig[] | null >( null ); + const [ codeSnippets, setCodeSnippets ] = useState< + { + name: string; + code: string; + }[] + >( [] ); + const [ currentSource, setCurrentSource ] = useState< DataSourceConfig | null >( null ); + const { close, isOpen, open } = useModalState(); const { pushState } = useSettingsContext(); const onCancelDeleteDialog = () => { @@ -71,21 +82,6 @@ const DataSourceList = () => { return SUPPORTED_SERVICES_LABELS[ service ] ?? 'HTTP'; }; - function showSnackbar( type: 'success' | 'error', message: string ): void { - const SNACKBAR_OPTIONS: Partial< WPNotice > = { - isDismissible: true, - }; - - switch ( type ) { - case 'success': - createSuccessNotice( message, { ...SNACKBAR_OPTIONS, icon: '✅' } ); - break; - case 'error': - createErrorNotice( message, { ...SNACKBAR_OPTIONS, icon: '❌' } ); - break; - } - } - const getServiceIcon = ( service: ( typeof SUPPORTED_SERVICES )[ number ] ): React.ReactElement => { @@ -241,6 +237,26 @@ const DataSourceList = () => { } }, }, + { + id: 'view-code', + label: __( 'View Code', 'remote-data-blocks' ), + isEligible: ( item: DataSourceConfig ) => Boolean( item?.uuid ), + callback: ( [ item ]: DataSourceConfig[] ) => { + if ( item?.uuid ) { + setCurrentSource( item ); + getDataSourceSnippet( item.uuid ) + .then( snippets => { + if ( snippets ) { + setCodeSnippets( snippets ); + open(); + } + } ) + .catch( () => { + showSnackbar( 'error', __( 'Failed to load code snippets.', 'remote-data-blocks' ) ); + } ); + } + }, + }, ]; if ( dataSources.length === 0 ) { @@ -289,6 +305,46 @@ const DataSourceList = () => { ) } ) } + { codeSnippets && isOpen && ( + { + close(); + setCodeSnippets( [] ); // Clear snippets when closing + } } + > + <> +

+ { __( + "Below, you'll find the code used to register the block(s) for this data source, which can be used as a reference for extending the data source.\nTo get started, copy the code below and add it to your plugin directory. " + ) } + + { __( 'Learn more about extending', 'remote-data-blocks' ) } + +

+ ( { + name, + title: name, + } ) ) } + > + { tab => { + return ( + snippet.name === tab.name )?.code ?? '' } + /> + ); + } } + + +
+ ) } ); }; diff --git a/src/data-sources/components/CodeSnippet.tsx b/src/data-sources/components/CodeSnippet.tsx new file mode 100644 index 000000000..3b8de6c20 --- /dev/null +++ b/src/data-sources/components/CodeSnippet.tsx @@ -0,0 +1,55 @@ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { copy } from '@wordpress/icons'; +import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; +import php from 'react-syntax-highlighter/dist/esm/languages/prism/php'; +import coy from 'react-syntax-highlighter/dist/esm/styles/prism/coy'; + +import { useDataSources } from '../hooks/useDataSources'; + +SyntaxHighlighter.registerLanguage( 'php', php ); + +const CodeSnippet = ( { code }: { code: string } ) => { + const { showSnackbar } = useDataSources(); + + const handleCopy = () => { + navigator.clipboard + .writeText( code ) + .then( () => { + showSnackbar( 'success', __( 'Code copied to clipboard!', 'remote-data-blocks' ) ); + } ) + .catch( () => { + showSnackbar( 'error', __( 'Failed to copy code', 'remote-data-blocks' ) ); + } ); + }; + + return ( +
+ + + { code } + +
+ ); +}; + +export default CodeSnippet; diff --git a/src/data-sources/hooks/useDataSources.ts b/src/data-sources/hooks/useDataSources.ts index 58a70441d..426d0968d 100644 --- a/src/data-sources/hooks/useDataSources.ts +++ b/src/data-sources/hooks/useDataSources.ts @@ -149,6 +149,30 @@ export const useDataSources = < SourceConfig extends DataSourceConfig = DataSour ); } + async function getDataSourceSnippet( uuid: DataSourceConfig[ 'uuid' ] ) { + try { + const response = await apiFetch( { + path: `${ REST_BASE_DATA_SOURCES }/snippets/${ uuid }`, + method: 'GET', + } ); + const result = response as { + snippets: { + name: string; + code: string; + }[]; + }; + return result.snippets; + } catch ( error ) { + if ( error instanceof Error ) { + showSnackbar( + 'error', + sprintf( __( 'Failed to get code snippet: %s', 'remote-data-blocks' ), error.message ) + ); + } + throw error; + } + } + async function onSave( config: SourceConfig, mode: 'add' | 'edit' ): Promise< void > { if ( mode === 'add' ) { await addDataSource( config ); @@ -185,9 +209,11 @@ export const useDataSources = < SourceConfig extends DataSourceConfig = DataSour dataSources, deleteDataSource, deleteMultipleDataSources, + getDataSourceSnippet, loadingDataSources, updateDataSource, fetchDataSources, onSave, + showSnackbar, }; }; diff --git a/src/settings/SettingsPage.tsx b/src/settings/SettingsPage.tsx index bed2c0906..629fb0ff7 100644 --- a/src/settings/SettingsPage.tsx +++ b/src/settings/SettingsPage.tsx @@ -15,6 +15,8 @@ const SettingsPage = () => { return (
+ +
{ addOrEditScreen ? ( @@ -55,8 +57,6 @@ const SettingsPage = () => { addOrEditScreen ? 'rdb-settings-page_add-edit' : 'rdb-settings-page_sources' }` } > - - { addOrEditScreen ? : }
diff --git a/src/settings/index.scss b/src/settings/index.scss index d6e9dbc81..f39d656cf 100644 --- a/src/settings/index.scss +++ b/src/settings/index.scss @@ -36,10 +36,8 @@ body { /* Snackbar notice */ .components-snackbar-list { - position: fixed; - bottom: 30px; - left: 160px; - width: calc(100% - 160px); + top: 4px; + z-index: 1000002; } &.folded .components-snackbar-list {