From 64fb4183e9ff8a020197859af771b854bd29f47a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 16 Dec 2024 12:51:00 +0100 Subject: [PATCH] Transfer all knowledge from #6050 --- .../manage/Controlpanels/VersionOverview.jsx | 79 +++++ .../src/addon-registry/addon-registry.ts | 5 +- packages/volto-slate/package.json | 4 + .../volto-slate/src/blocks/Table/Cell.jsx | 2 +- .../src/blocks/Text/PluginSidebar.jsx | 2 +- .../volto-slate/src/editor/SlateEditor.jsx | 2 +- packages/volto/.gitignore | 1 + packages/volto/Makefile | 18 +- packages/volto/{babel.js => babel.cjs} | 1 - packages/volto/babel.config.cjs | 1 + packages/volto/babel.config.js | 1 - packages/volto/cypress.config.js | 4 +- packages/volto/index.html | 14 + packages/volto/package.json | 30 +- packages/volto/razzle.config.js | 14 +- packages/volto/server.js | 155 +++++++++ packages/volto/src/client.js | 7 - .../components/LinkButton/AddLinkForm.jsx | 2 +- .../BlockChooser/BlockChooserButton.jsx | 2 +- .../manage/Blocks/Block/BlocksForm.jsx | 8 +- .../manage/Blocks/Block/Order/Order.jsx | 40 +-- .../Blocks/Block/Order/SortableItem.jsx | 17 +- .../manage/Blocks/Description/Edit.jsx | 2 +- .../components/manage/Blocks/HTML/Edit.jsx | 34 +- .../manage/Blocks/Listing/ImageGallery.jsx | 5 +- .../Search/components/DateRangeFacet.jsx | 14 +- .../Blocks/Search/components/SelectFacet.jsx | 11 +- .../Blocks/Search/components/SortOn.jsx | 13 +- .../Blocks/Search/components/ViewSwitcher.jsx | 17 +- .../Search/widgets/SelectMetadataField.jsx | 11 +- .../components/manage/Blocks/Title/Edit.jsx | 2 +- .../components/manage/Contents/Contents.jsx | 27 +- .../manage/Contents/ContentsIndexHeader.jsx | 7 +- .../manage/Contents/ContentsItem.jsx | 9 +- .../manage/Contents/ContentsUploadModal.jsx | 5 +- .../src/components/manage/Contents/index.tsx | 21 +- .../components/manage/Controlpanels/index.tsx | 48 +-- .../volto/src/components/manage/Diff/Diff.jsx | 7 +- .../src/components/manage/Diff/DiffField.jsx | 19 +- .../src/components/manage/Display/Display.jsx | 39 ++- .../manage/DragDropList/DragDropList.jsx | 7 +- .../volto/src/components/manage/Edit/Edit.jsx | 2 - .../src/components/manage/Form/Field.jsx | 5 +- .../src/components/manage/Form/index.tsx | 18 +- .../ReactVirtualized/DynamicRowHeightList.jsx | 9 +- .../src/components/manage/Rules/index.tsx | 4 +- .../manage/Sidebar/SidebarPopup.jsx | 2 +- .../manage/TextLineEdit/TextLineEdit.jsx | 2 +- .../src/components/manage/Toolbar/Toolbar.jsx | 2 +- .../components/manage/Widgets/ArrayWidget.jsx | 11 +- .../manage/Widgets/DatetimeWidget.jsx | 40 +-- .../components/manage/Widgets/FileWidget.jsx | 5 +- .../components/manage/Widgets/ImageWidget.jsx | 5 +- .../manage/Widgets/ObjectListWidget.jsx | 7 +- .../manage/Widgets/QuerySortOnWidget.jsx | 10 +- .../components/manage/Widgets/QueryWidget.jsx | 9 +- .../manage/Widgets/QueryWidget.stories.jsx | 6 +- .../Widgets/RecurrenceWidget/ByDayField.jsx | 7 +- .../Widgets/RecurrenceWidget/ByMonthField.jsx | 5 +- .../Widgets/RecurrenceWidget/ByYearField.jsx | 9 +- .../RecurrenceWidget/MonthOfTheYearField.jsx | 13 +- .../Widgets/RecurrenceWidget/Occurences.jsx | 6 +- .../RecurrenceWidget/RecurrenceWidget.jsx | 15 +- .../Widgets/RecurrenceWidget/SelectInput.jsx | 17 +- .../WeekdayOfTheMonthField.jsx | 7 +- .../manage/Widgets/RegistryImageWidget.jsx | 5 +- .../manage/Widgets/SchemaWidget.jsx | 7 +- .../manage/Widgets/SchemaWidgetFieldset.jsx | 17 +- .../manage/Widgets/SelectAutoComplete.jsx | 7 +- .../SelectAutocompleteWidget.stories.jsx | 22 +- .../manage/Widgets/SelectStyling.jsx | 118 ++++--- .../manage/Widgets/SelectWidget.jsx | 7 +- .../manage/Widgets/SelectWidget.stories.jsx | 16 +- .../components/manage/Widgets/TokenWidget.jsx | 7 +- .../manage/Widgets/VocabularyTermsWidget.jsx | 7 +- .../src/components/manage/Widgets/index.tsx | 68 ++-- .../components/manage/Workflow/Workflow.jsx | 64 ++-- .../volto/src/components/theme/App/App.jsx | 11 +- .../components/theme/Comments/Comments.jsx | 7 +- .../src/components/theme/Comments/index.tsx | 4 +- .../ConnectionRefused/ConnectionRefused.jsx | 4 +- .../theme/EventDetails/EventDetails.jsx | 18 +- .../src/components/theme/Sitemap/Sitemap.jsx | 2 +- .../components/theme/View/EventDatesInfo.jsx | 20 +- .../src/components/theme/View/LinkView.jsx | 4 +- .../volto/src/components/theme/View/View.jsx | 11 +- packages/volto/src/config/Loadables.jsx | 56 ---- packages/volto/src/config/Views.jsx | 4 +- packages/volto/src/config/index.js | 49 ++- packages/volto/src/config/server.js | 25 +- packages/volto/src/entry-client.tsx | 92 ++++++ packages/volto/src/entry-server.tsx | 299 ++++++++++++++++++ packages/volto/src/error.jsx | 2 +- .../volto/src/express-middleware/devproxy.js | 47 +-- .../volto/src/express-middleware/files.js | 2 +- .../src/express-middleware/hostDetect.js | 24 ++ .../volto/src/express-middleware/images.js | 2 +- .../volto/src/express-middleware/robotstxt.js | 4 +- .../src/helpers/Api/APIResourceWithAuth.js | 2 +- packages/volto/src/helpers/Api/Api.js | 8 +- .../volto/src/helpers/AuthToken/AuthToken.js | 11 +- packages/volto/src/helpers/Html/Html.jsx | 78 ++++- packages/volto/src/helpers/Site/index.js | 2 +- packages/volto/src/helpers/Url/Url.js | 2 +- .../src/helpers/Utils/doesNodeContainClick.js | 63 ++++ .../helpers/Utils/useDetectClickOutside.js | 2 +- packages/volto/src/hydration-overlay.js | 21 ++ packages/volto/src/index.js | 7 - packages/volto/src/middleware/api.js | 2 +- .../volto/src/middleware/userSessionReset.js | 2 +- ...start-server.js => razzle-start-server.js} | 0 packages/volto/src/start-client.jsx | 78 ----- packages/volto/src/store.js | 8 +- packages/volto/src/test_loadable.tsx | 23 ++ packages/volto/theme/theme.config | 8 +- packages/volto/tsconfig.json | 12 +- packages/volto/vite-plugins/svg.js | 81 +++++ packages/volto/vite-plugins/volto.js | 177 +++++++++++ packages/volto/vite.config.js | 23 ++ packages/volto/volto.config.js | 10 +- 120 files changed, 1767 insertions(+), 808 deletions(-) create mode 100644 packages/coresandbox/src/customizations/volto/components/manage/Controlpanels/VersionOverview.jsx rename packages/volto/{babel.js => babel.cjs} (92%) create mode 100644 packages/volto/babel.config.cjs delete mode 100644 packages/volto/babel.config.js create mode 100644 packages/volto/index.html create mode 100644 packages/volto/server.js delete mode 100644 packages/volto/src/client.js delete mode 100644 packages/volto/src/config/Loadables.jsx create mode 100644 packages/volto/src/entry-client.tsx create mode 100644 packages/volto/src/entry-server.tsx create mode 100644 packages/volto/src/express-middleware/hostDetect.js create mode 100644 packages/volto/src/helpers/Utils/doesNodeContainClick.js create mode 100644 packages/volto/src/hydration-overlay.js delete mode 100644 packages/volto/src/index.js rename packages/volto/src/{start-server.js => razzle-start-server.js} (100%) delete mode 100644 packages/volto/src/start-client.jsx create mode 100644 packages/volto/src/test_loadable.tsx create mode 100644 packages/volto/vite-plugins/svg.js create mode 100644 packages/volto/vite-plugins/volto.js create mode 100644 packages/volto/vite.config.js diff --git a/packages/coresandbox/src/customizations/volto/components/manage/Controlpanels/VersionOverview.jsx b/packages/coresandbox/src/customizations/volto/components/manage/Controlpanels/VersionOverview.jsx new file mode 100644 index 0000000000..5f9d33d4ee --- /dev/null +++ b/packages/coresandbox/src/customizations/volto/components/manage/Controlpanels/VersionOverview.jsx @@ -0,0 +1,79 @@ +import { FormattedMessage } from 'react-intl'; +import isEmpty from 'lodash/isEmpty'; + +import voltoPackageJson from '@plone/volto/../package.json'; +import projectPackageJson from '@root/../package.json'; + +import { defineMessages, useIntl } from 'react-intl'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + no_addons: { + id: 'No addons found', + defaultMessage: 'No addons found', + }, +}); + +const voltoVersion = voltoPackageJson.version; + +const VersionOverview = ({ + cmf_version, + debug_mode, + pil_version, + plone_version, + plone_restapi_version, + python_version, + zope_version, +}) => { + const intl = useIntl(); + const { addonsInfo } = config.settings; + const isProject = voltoPackageJson.name !== projectPackageJson.name; + + return ( + <> +

+ This is a customized Route Component from @plone/volto-coresandbox +

+ +

Add-ons

+ {isEmpty(addonsInfo) ? ( +

{intl.formatMessage(messages.no_addons)}

+ ) : ( + + )} + {debug_mode !== 'No' && ( +

+ +

+ )} + + ); +}; + +export default VersionOverview; diff --git a/packages/registry/src/addon-registry/addon-registry.ts b/packages/registry/src/addon-registry/addon-registry.ts index e4eb233c89..1fe5ee8533 100644 --- a/packages/registry/src/addon-registry/addon-registry.ts +++ b/packages/registry/src/addon-registry/addon-registry.ts @@ -546,7 +546,10 @@ class AddonRegistry { options[item][0].replace(/\/\*$/, ''), ); - aliases[name] = value; + // We don't want to alias Volto itself, since that's fixed + if (name !== '@plone/volto') { + aliases[name] = value; + } }); return aliases; diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index b98b7b27bc..5395592331 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -25,8 +25,12 @@ "is-url": "1.2.4", "jsdom": "^16.6.0", "lodash": "4.17.21", + "lodash-es": "^4.17.21", + "promise-file-reader": "1.0.2", + "prop-types": "15.7.2", "react": "18.2.0", "react-dom": "18.2.0", + "react-dropzone": "11.1.0", "react-intersection-observer": "9.1.0", "react-intl": "3.12.1", "react-redux": "8.1.2", diff --git a/packages/volto-slate/src/blocks/Table/Cell.jsx b/packages/volto-slate/src/blocks/Table/Cell.jsx index ecd98f1078..dbe49f93e8 100644 --- a/packages/volto-slate/src/blocks/Table/Cell.jsx +++ b/packages/volto-slate/src/blocks/Table/Cell.jsx @@ -62,7 +62,7 @@ class Cell extends Component { render() { return ( - __CLIENT__ && ( + !import.meta.env.SSR && ( { return ( <> {selected && - __CLIENT__ && + !import.meta.env.SSR && createPortal( <>{children}, document.getElementById('slate-plugin-sidebar'), diff --git a/packages/volto-slate/src/editor/SlateEditor.jsx b/packages/volto-slate/src/editor/SlateEditor.jsx index 6327abb883..d0bb829ac8 100644 --- a/packages/volto-slate/src/editor/SlateEditor.jsx +++ b/packages/volto-slate/src/editor/SlateEditor.jsx @@ -375,6 +375,6 @@ SlateEditor.defaultProps = { }; // May be needed to wrap in React.memo(), it used to be wrapped in connect() -export default __CLIENT__ && window?.Cypress +export default !import.meta.env.SSR && window?.Cypress ? withTestingFeatures(SlateEditor) : SlateEditor; diff --git a/packages/volto/.gitignore b/packages/volto/.gitignore index 6d1cc045b4..a337db6cc0 100644 --- a/packages/volto/.gitignore +++ b/packages/volto/.gitignore @@ -10,6 +10,7 @@ eslint.xml yarn-error.log build .changelog.draft +dist # yarn 3 .pnp.* diff --git a/packages/volto/Makefile b/packages/volto/Makefile index 1a346e111f..063cd4e8b3 100644 --- a/packages/volto/Makefile +++ b/packages/volto/Makefile @@ -49,7 +49,7 @@ start: ## Starts Volto, allowing reloading of the add-on during development .PHONY: build build: ## Build a production bundle for distribution - pnpm i && RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build + pnpm i && PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build .PHONY: test test: ## Run unit tests @@ -118,7 +118,7 @@ frontend-docker-start: ## Starts a Docker-based frontend for development .PHONY: acceptance-frontend-dev-start acceptance-frontend-dev-start: ## Start acceptance frontend in development mode - RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm start + PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm start ######### Core Acceptance tests @@ -136,7 +136,7 @@ ci-acceptance-backend-start: ## Start backend acceptance server in headless mode .PHONY: acceptance-frontend-prod-start acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode - RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod + PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod .PHONY: acceptance-test acceptance-test: ## Start Cypress in interactive mode @@ -172,7 +172,7 @@ deployment-ci-acceptance-test-run-all: ## With a single command, run the backend .PHONY: project-acceptance-frontend-prod-start project-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for project tests - (cd ../../my-volto-app && RAZZLE_API_PATH=http://127.0.0.1:55001/plone yarn build && yarn start:prod) + (cd ../../my-volto-app && PLONE_API_PATH=http://127.0.0.1:55001/plone yarn build && yarn start:prod) ######### Core Sandbox Acceptance tests @@ -182,11 +182,11 @@ coresandbox-acceptance-backend-start: ## Start backend acceptance server for cor .PHONY: coresandbox-acceptance-frontend-prod-start coresandbox-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for core sandbox tests - ADDONS=@plone/volto-coresandbox RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod + ADDONS=@plone/volto-coresandbox PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod .PHONY: coresandbox-acceptance-frontend-dev-start coresandbox-acceptance-frontend-dev-start: build-deps ## Start acceptance frontend in development mode for core sandbox tests - ADDONS=@plone/volto-coresandbox RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm start + ADDONS=@plone/volto-coresandbox PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm start .PHONY: coresandbox-acceptance-test coresandbox-acceptance-test: ## Start Cypress in interactive mode for core sandbox tests @@ -208,7 +208,7 @@ multilingual-acceptance-backend-start: ## Start backend acceptance server for mu .PHONY: multilingual-acceptance-frontend-prod-start multilingual-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for multilingual tests - ADDONS=@plone/volto-coresandbox:multilingualFixture RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod + ADDONS=@plone/volto-coresandbox:multilingualFixture PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod .PHONY: multilingual-acceptance-test multilingual-acceptance-test: ## Start Cypress in interactive mode for multilingual tests @@ -252,7 +252,7 @@ working-copy-acceptance-backend-start: ## Start backend acceptance server for wo .PHONY: working-copy-acceptance-frontend-prod-start working-copy-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for working copy tests - ADDONS=@plone/volto-coresandbox:workingCopyFixture RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod + ADDONS=@plone/volto-coresandbox:workingCopyFixture PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod .PHONY: working-copy-acceptance-test working-copy-acceptance-test: ## Start Cypress in interactive mode for working copy tests @@ -274,7 +274,7 @@ guillotina-acceptance-backend-start: ## Start backend acceptance server for Guil .PHONY: guillotina-acceptance-frontend-prod-start guillotina-acceptance-frontend-prod-start: ## Start acceptance frontend in production mode for Guillotina tests - ADDONS=volto-guillotina RAZZLE_API_PATH=http://127.0.0.1:8081/db/web RAZZLE_LEGACY_TRAVERSE=true pnpm build && pnpm start:prod + ADDONS=volto-guillotina PLONE_API_PATH=http://127.0.0.1:8081/db/web RAZZLE_LEGACY_TRAVERSE=true pnpm build && pnpm start:prod .PHONY: guillotina-acceptance-test guillotina-acceptance-test: ## Start Cypress in interactive mode for Guillotina tests diff --git a/packages/volto/babel.js b/packages/volto/babel.cjs similarity index 92% rename from packages/volto/babel.js rename to packages/volto/babel.cjs index 2a34da5172..00f3aac14f 100644 --- a/packages/volto/babel.js +++ b/packages/volto/babel.cjs @@ -26,7 +26,6 @@ module.exports = function (api) { messagesDir: './build/messages/', }, ], - '@loadable/babel-plugin', // Required by the @loadable plugin ]; return { diff --git a/packages/volto/babel.config.cjs b/packages/volto/babel.config.cjs new file mode 100644 index 0000000000..6b77e55f2a --- /dev/null +++ b/packages/volto/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require('./babel.cjs'); diff --git a/packages/volto/babel.config.js b/packages/volto/babel.config.js deleted file mode 100644 index 38355d6c91..0000000000 --- a/packages/volto/babel.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./babel'); diff --git a/packages/volto/cypress.config.js b/packages/volto/cypress.config.js index 4da7c4e853..0445e067b6 100644 --- a/packages/volto/cypress.config.js +++ b/packages/volto/cypress.config.js @@ -1,6 +1,6 @@ -const { defineConfig } = require('cypress'); +import { defineConfig } from 'cypress'; -module.exports = defineConfig({ +export default defineConfig({ viewportWidth: 1280, chromeWebSecurity: false, projectId: 'hvviu4', diff --git a/packages/volto/index.html b/packages/volto/index.html new file mode 100644 index 0000000000..8bc8e4470e --- /dev/null +++ b/packages/volto/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
+ + + + diff --git a/packages/volto/package.json b/packages/volto/package.json index 64d9b1c45b..c49b5b4b53 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -32,13 +32,14 @@ "package": "@plone/volto-slate" } }, + "type": "module", "main": "src/index.js", "types": "types/index.d.ts", "scripts": { "analyze": "BUNDLE_ANALYZE=true razzle build", "start": "make build-deps && razzle start", "start:coresandbox": "make build-deps && ADDONS=coresandbox razzle start", - "build": "make build-deps && razzle build --noninteractive", + "build": "make build-deps && pnpm run build:client && pnpm run build:server", "build:types": "tsc --project tsconfig.declarations.json", "test": "razzle test --maxWorkers=50%", "test:ci": "CI=true NODE_ICU_DATA=node_modules/full-icu razzle test", @@ -62,7 +63,14 @@ "release-major-alpha": "release-it major --preRelease=alpha", "release-alpha": "release-it --preRelease=alpha", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "dev": "vite", + "dev:server": "node server.js", + "dev:server:debug": "NODE_INSPECT_RESUME_ON_START=1 node inspect src/vite-server.js", + "build:client": "vite build --ssrManifest --outDir dist/client", + "build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server", + "start:prod": "NODE_ENV=production node server.js", + "start:prod:debug": "NODE_INSPECT_RESUME_ON_START=1 NODE_ENV=production node --inspect-brk server.js" }, "bundlewatch": { "files": [ @@ -177,8 +185,6 @@ "node": "^20 || ^22" }, "dependencies": { - "@loadable/component": "5.14.1", - "@loadable/server": "5.14.0", "@plone/registry": "workspace:*", "@plone/scripts": "workspace:*", "@plone/volto-slate": "workspace:*", @@ -188,7 +194,7 @@ "debug": "4.3.2", "decorate-component-with-props": "1.2.1", "dependency-graph": "0.10.0", - "detect-browser": "5.1.0", + "detect-browser": "5.3.0", "diff": "3.5.0", "express": "4.19.2", "filesize": "6", @@ -268,7 +274,8 @@ "universal-cookie-express": "4.0.3", "url": "^0.11.3", "use-deep-compare-effect": "1.8.1", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "vite-plugin-node-polyfills": "0.17.0" }, "devDependencies": { "@babel/core": "^7.0.0", @@ -281,13 +288,12 @@ "@babel/plugin-syntax-export-namespace-from": "7.8.3", "@babel/runtime": "7.20.6", "@babel/types": "7.20.5", + "@builder.io/react-hydration-overlay": "^0.1.0", "@dnd-kit/core": "6.0.8", "@dnd-kit/sortable": "7.0.2", "@dnd-kit/utilities": "3.2.2", "@fiverr/afterbuild-webpack-plugin": "^1.0.0", "@jest/globals": "^29.7.0", - "@loadable/babel-plugin": "5.13.2", - "@loadable/webpack-plugin": "5.15.2", "@plone/types": "workspace:*", "@plone/volto-coresandbox": "workspace:*", "@sinonjs/fake-timers": "^6.0.1", @@ -304,6 +310,7 @@ "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.0", "@testing-library/react-hooks": "8.0.1", + "@types/express": "^4.17.21", "@types/history": "^4.7.11", "@types/jest": "^29.5.8", "@types/loadable__component": "^5.13.9", @@ -315,6 +322,7 @@ "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "10.4.8", "axe-core": "4.4.2", "babel-loader": "9.1.0", @@ -325,11 +333,13 @@ "babel-preset-razzle": "4.2.18", "bundlewatch": "0.3.3", "circular-dependency-plugin": "5.2.2", + "compression": "^1.7.4", "css-loader": "5.2.7", "cypress": "13.13.2", "cypress-axe": "1.5.0", "cypress-file-upload": "5.0.8", "deep-freeze": "0.0.1", + "esbuild-plugin-react-virtualized": "^1.0.4", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "^7.0.1", @@ -342,6 +352,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "full-icu": "1.4.0", + "get-port": "^7.0.0", "html-webpack-plugin": "5.5.0", "identity-obj-proxy": "3.0.0", "jest": "26.6.3", @@ -370,6 +381,7 @@ "react-is": "^18.2.0", "release-it": "^17.1.1", "semver": "^7.5.4", + "sirv": "^2.0.4", "start-server-and-test": "1.14.0", "storybook": "^8.0.4", "style-loader": "3.3.1", @@ -377,6 +389,7 @@ "stylelint-config-idiomatic-order": "10.0.0", "stylelint-prettier": "5.0.0", "svg-loader": "0.0.2", + "svgo": "^2.8.0", "svgo-loader": "3.0.3", "terser-webpack-plugin": "5.3.6", "tmp": "0.2.1", @@ -384,6 +397,7 @@ "ts-loader": "9.4.4", "typescript": "^5.6.3", "use-trace-update": "1.3.2", + "vite": "^5.4.5", "wait-on": "6.0.0", "webpack": "5.90.1", "webpack-bundle-analyzer": "4.10.1", diff --git a/packages/volto/razzle.config.js b/packages/volto/razzle.config.js index 5ab88cb132..49202e7d2e 100644 --- a/packages/volto/razzle.config.js +++ b/packages/volto/razzle.config.js @@ -303,23 +303,23 @@ const defaultModify = ({ config.resolve.alias = { ...registry.getAddonCustomizationPaths(), ...registry.getAddonsFromEnvVarCustomizationPaths(), - ...registry.getProjectCustomizationPaths(), + ...registry.getProjectCustomizationPaths(), // NOT supported anymore, will be removed in Volto 19 ...config.resolve.alias, '../../theme.config$': `${projectRootPath}/theme/theme.config`, 'volto-themes': `${registry.voltoPath}/theme/themes`, - 'load-volto-addons': addonsLoaderPath, + 'load-volto-addons': addonsLoaderPath, // Change to @plone/registry/load-addons-loader ...registry.getResolveAliases(), '@plone/volto': `${registry.voltoPath}/src`, // to be able to reference path uncustomized by webpack - '@plone/volto-original': `${registry.voltoPath}/src`, + '@plone/volto-original': `${registry.voltoPath}/src`, // Not used anymore, will be removed in Volto 19 // be able to reference current package from customized package - '@package': `${projectRootPath}/src`, - '@root': `${projectRootPath}/src`, + '@package': `${projectRootPath}/src`, // Not used anymore, will be removed in Volto 19 + '@root': `${projectRootPath}/src`, // Not used anymore, will be removed in Volto 19 // we're incorporating redux-connect - 'redux-connect': `${registry.voltoPath}/src/helpers/AsyncConnect`, + 'redux-connect': `${registry.voltoPath}/src/helpers/AsyncConnect`, // Deprecated // avoids including lodash multiple times. // semantic-ui-react uses lodash-es, everything else uses lodash - 'lodash-es': path.dirname(require.resolve('lodash')), + 'lodash-es': path.dirname(require.resolve('lodash')), // Not used anymore }; const [addonsThemeLoaderVariablesPath, addonsThemeLoaderMainPath] = diff --git a/packages/volto/server.js b/packages/volto/server.js new file mode 100644 index 0000000000..876e45320e --- /dev/null +++ b/packages/volto/server.js @@ -0,0 +1,155 @@ +import fs from 'node:fs/promises'; +import express from 'express'; +import getPort, { portNumbers } from 'get-port'; +import dns from 'dns'; +import cookiesMiddleware from 'universal-cookie-express'; +import { JSDOM } from 'jsdom'; + +const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD; + +// If DNS returns both ipv4 and ipv6 addresses, prefer ipv4 + +export async function createServer( + root = process.cwd(), + isProd = process.env.NODE_ENV === 'production', + hmrPort, +) { + dns.setDefaultResultOrder('ipv4first'); + const app = express(); + + const prodIndexHtml = isProd + ? await fs.readFile('./dist/client/index.html', 'utf-8') + : ''; + + /** + * @type {import('vite').ViteDevServer} + */ + let vite; + vite = await ( + await import('vite') + ).createServer({ + root, + logLevel: isTest ? 'error' : 'info', + server: { + middlewareMode: true, + watch: { + // During tests we edit the files too fast and sometimes chokidar + // misses change events, so enforce polling for consistency + usePolling: true, + interval: 100, + }, + hmr: { + port: hmrPort, + }, + }, + appType: 'custom', + }); + if (!isProd) { + // use vite's connect instance as middleware + app.use(vite.middlewares); + } else { + const sirv = (await import('sirv')).default; + app.use((await import('compression')).default()); + app.use('/', sirv('./dist/client', { extensions: [] })); + } + + app.use(cookiesMiddleware()); + + // Load the current config for the Express server to consume it + // const currentConfig = (await vite.ssrLoadModule('/src/config')).currentConfig; + // console.log(currentConfig); + + const entry = await (async () => { + if (!isProd) { + return vite.ssrLoadModule('/src/entry-server.tsx'); + } else { + return import('./dist/server/entry-server.js'); + } + })(); + + // Loads the Express server middleware from the settings. + const middleware = ( + entry.getConfig().settings?.serverConfig?.expressMiddleware || [] + ).filter((m) => m); + if (middleware.length) app.use('/', middleware); + + app.use('*', async (req, res) => { + try { + const url = req.originalUrl; + + if (url.includes('.')) { + console.warn(`${url} is not valid router path`); + res.status(404); + res.end(`${url} is not valid router path`); + return; + } + + // Extract the head from vite's index transformation hook (while in dev) + let viteHead = !isProd + ? await vite.transformIndexHtml( + url, + ``, + ) + : prodIndexHtml; + + viteHead = viteHead.substring( + viteHead.indexOf('') + 6, + viteHead.indexOf(''), + ); + + // Parse the HTML string with jsdom + const dom = new JSDOM(viteHead); + const document = dom.window.document; + + // Extract script elements + const scripts = Array.from(document.querySelectorAll('script')).map( + (script) => ({ + type: script.getAttribute('type'), + crossorigin: script.getAttribute('crossorigin'), + src: script.getAttribute('src'), + }), + ); + + // Extract link elements + const links = Array.from(document.querySelectorAll('link')).map( + (link) => ({ + rel: link.getAttribute('rel'), + crossorigin: link.getAttribute('crossorigin'), + href: link.getAttribute('href'), + }), + ); + + // Combine the results into a single object + const headElements = { + scripts, + links, + }; + + console.log('Rendering: ', url, '...'); + entry.render({ + req, + res, + url, + head: isProd ? headElements : '', + }); + } catch (e) { + !isProd && vite.ssrFixStacktrace(e); + console.log(e.stack); + res.status(500).end(e.stack); + } + }); + + return { app, vite }; +} + +if (!isTest) { + createServer().then(async ({ app }) => + app.listen( + await getPort({ port: portNumbers(3000, 3100) }), + '0.0.0.0', + () => { + console.log('Client Server: http://localhost:3000'); + }, + ), + ); +} diff --git a/packages/volto/src/client.js b/packages/volto/src/client.js deleted file mode 100644 index 6cc5103add..0000000000 --- a/packages/volto/src/client.js +++ /dev/null @@ -1,7 +0,0 @@ -import client from './start-client'; - -client(); - -if (module.hot) { - module.hot.accept(); -} diff --git a/packages/volto/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx b/packages/volto/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx index 485440d26b..7523c33205 100644 --- a/packages/volto/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +++ b/packages/volto/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx @@ -16,7 +16,7 @@ import { URLUtils, } from '@plone/volto/helpers/Url/Url'; -import doesNodeContainClick from 'semantic-ui-react/dist/commonjs/lib/doesNodeContainClick'; +import doesNodeContainClick from '@plone/volto/helpers/Utils/doesNodeContainClick/doesNodeContainClick'; import { Input, Form, Button } from 'semantic-ui-react'; import { defineMessages, injectIntl } from 'react-intl'; diff --git a/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx b/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx index d75055f375..9655937acd 100644 --- a/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx +++ b/packages/volto/src/components/manage/BlockChooser/BlockChooserButton.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import doesNodeContainClick from 'semantic-ui-react/dist/commonjs/lib/doesNodeContainClick'; +import doesNodeContainClick from '@plone/volto/helpers/Utils/doesNodeContainClick/doesNodeContainClick'; import addSVG from '@plone/volto/icons/circle-plus.svg'; import { blockHasValue } from '@plone/volto/helpers/Blocks/Blocks'; import Icon from '@plone/volto/components/theme/Icon/Icon'; diff --git a/packages/volto/src/components/manage/Blocks/Block/BlocksForm.jsx b/packages/volto/src/components/manage/Blocks/Block/BlocksForm.jsx index a8fd1e7c05..271044a976 100644 --- a/packages/volto/src/components/manage/Blocks/Block/BlocksForm.jsx +++ b/packages/volto/src/components/manage/Blocks/Block/BlocksForm.jsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { lazy, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import cloneDeep from 'lodash/cloneDeep'; import map from 'lodash/map'; import EditBlock from './Edit'; -import DragDropList from '@plone/volto/components/manage/DragDropList/DragDropList'; import { getBlocks, getBlocksFieldname, @@ -29,7 +28,10 @@ import { useDispatch } from 'react-redux'; import config from '@plone/volto/registry'; import { createPortal } from 'react-dom'; -import Order from './Order/Order'; +const Order = lazy(() => import('./Order/Order')); +const DragDropList = lazy( + () => import('@plone/volto/components/manage/DragDropList/DragDropList'), +); const BlocksForm = (props) => { const { diff --git a/packages/volto/src/components/manage/Blocks/Block/Order/Order.jsx b/packages/volto/src/components/manage/Blocks/Block/Order/Order.jsx index bb8a2cedc6..8fb9f10214 100644 --- a/packages/volto/src/components/manage/Blocks/Block/Order/Order.jsx +++ b/packages/volto/src/components/manage/Blocks/Block/Order/Order.jsx @@ -6,7 +6,22 @@ import min from 'lodash/min'; import { flattenTree, getProjection, removeChildrenOf } from './utilities'; import SortableItem from './SortableItem'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, + DragOverlay, + MeasuringStrategy, + defaultDropAnimation, +} from '@dnd-kit/core'; +import { + SortableContext, + arrayMove, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; export function Order({ items = [], @@ -15,9 +30,6 @@ export function Order({ onSelectBlock, indentationWidth = 25, removable, - dndKitCore, - dndKitSortable, - dndKitUtilities, errors, }) { const [activeId, setActiveId] = useState(null); @@ -25,20 +37,6 @@ export function Order({ const [offsetLeft, setOffsetLeft] = useState(0); const [currentPosition, setCurrentPosition] = useState(null); - const { - DndContext, - closestCenter, - PointerSensor, - useSensor, - useSensors, - DragOverlay, - MeasuringStrategy, - defaultDropAnimation, - } = dndKitCore; - const { SortableContext, arrayMove, verticalListSortingStrategy } = - dndKitSortable; - const { CSS } = dndKitUtilities; - const measuring = { droppable: { strategy: MeasuringStrategy.Always, @@ -363,8 +361,4 @@ export function Order({ } } -export default injectLazyLibs([ - 'dndKitCore', - 'dndKitSortable', - 'dndKitUtilities', -])(Order); +export default Order; diff --git a/packages/volto/src/components/manage/Blocks/Block/Order/SortableItem.jsx b/packages/volto/src/components/manage/Blocks/Block/Order/SortableItem.jsx index bfa328b1e9..853d0c219b 100644 --- a/packages/volto/src/components/manage/Blocks/Block/Order/SortableItem.jsx +++ b/packages/volto/src/components/manage/Blocks/Block/Order/SortableItem.jsx @@ -1,21 +1,14 @@ import React from 'react'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import { Item } from './Item'; const animateLayoutChanges = ({ isSorting, wasDragging }) => isSorting || wasDragging ? false : true; -export function SortableItem({ - id, - depth, - dndKitSortable, - dndKitUtilities, - ...props -}) { - const { useSortable } = dndKitSortable; - const { CSS } = dndKitUtilities; +export function SortableItem({ id, depth, ...props }) { const { attributes, isDragging, @@ -53,6 +46,4 @@ export function SortableItem({ ); } -export default injectLazyLibs(['dndKitSortable', 'dndKitUtilities'])( - SortableItem, -); +export default SortableItem; diff --git a/packages/volto/src/components/manage/Blocks/Description/Edit.jsx b/packages/volto/src/components/manage/Blocks/Description/Edit.jsx index 5899e4e1e0..ff2db2ecfe 100644 --- a/packages/volto/src/components/manage/Blocks/Description/Edit.jsx +++ b/packages/volto/src/components/manage/Blocks/Description/Edit.jsx @@ -152,7 +152,7 @@ export const DescriptionBlockEdit = (props) => { ); }, []); - if (typeof window.__SERVER__ !== 'undefined') { + if (import.meta.env.SSR) { return
; } diff --git a/packages/volto/src/components/manage/Blocks/HTML/Edit.jsx b/packages/volto/src/components/manage/Blocks/HTML/Edit.jsx index 7427dde32b..3dd33e99b7 100644 --- a/packages/volto/src/components/manage/Blocks/HTML/Edit.jsx +++ b/packages/volto/src/components/manage/Blocks/HTML/Edit.jsx @@ -4,21 +4,24 @@ */ import { compose } from 'redux'; -import React, { Component } from 'react'; +import React, { lazy, Component } from 'react'; import PropTypes from 'prop-types'; import { Button, Popup } from 'semantic-ui-react'; import { defineMessages, injectIntl } from 'react-intl'; -import loadable from '@loadable/component'; import isEqual from 'lodash/isEqual'; +import prettierStandalone from 'prettier/standalone'; +import prettierParserHtml from 'prettier/parser-html'; +import prismCore from 'prismjs/components/prism-core'; + import Icon from '@plone/volto/components/theme/Icon/Icon'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + import showSVG from '@plone/volto/icons/show.svg'; import clearSVG from '@plone/volto/icons/clear.svg'; import codeSVG from '@plone/volto/icons/code.svg'; import indentSVG from '@plone/volto/icons/indent.svg'; -const Editor = loadable(() => import('react-simple-code-editor')); +const Editor = lazy(() => import('react-simple-code-editor')); const messages = defineMessages({ source: { @@ -152,9 +155,9 @@ class Edit extends Component { async onPreview() { try { const code = ( - await this.props.prettierStandalone.format(this.getValue(), { + await prettierStandalone.format(this.getValue(), { parser: 'html', - plugins: [this.props.prettierParserHtml], + plugins: [prettierParserHtml], }) ).trim(); this.setState( @@ -177,9 +180,9 @@ class Edit extends Component { onPrettify = async () => { try { const code = ( - await this.props.prettierStandalone.format(this.getValue(), { + await prettierStandalone.format(this.getValue(), { parser: 'html', - plugins: [this.props.prettierParserHtml], + plugins: [prettierParserHtml], }) ).trim(); this.onChangeCode(code); @@ -316,14 +319,9 @@ class Edit extends Component { placeholder={placeholder} onValueChange={(code) => this.onChangeCode(code)} highlight={ - this.props.prismCore?.highlight && - this.props.prismCore?.languages?.html + prismCore?.highlight && prismCore?.languages?.html ? (code) => - this.props.prismCore.highlight( - code, - this.props.prismCore.languages.html, - 'html', - ) + prismCore.highlight(code, prismCore.languages.html, 'html') : () => {} } padding={8} @@ -361,8 +359,4 @@ const withPrismMarkup = (WrappedComponent) => (props) => { return loaded ? : null; }; -export default compose( - injectLazyLibs(['prettierStandalone', 'prettierParserHtml', 'prismCore']), - withPrismMarkup, - injectIntl, -)(Edit); +export default compose(withPrismMarkup, injectIntl)(Edit); diff --git a/packages/volto/src/components/manage/Blocks/Listing/ImageGallery.jsx b/packages/volto/src/components/manage/Blocks/Listing/ImageGallery.jsx index ca18bf100c..348efa50d1 100644 --- a/packages/volto/src/components/manage/Blocks/Listing/ImageGallery.jsx +++ b/packages/volto/src/components/manage/Blocks/Listing/ImageGallery.jsx @@ -1,6 +1,5 @@ -import React from 'react'; +import React, { lazy } from 'react'; import PropTypes from 'prop-types'; -import loadable from '@loadable/component'; import 'react-image-gallery/styles/css/image-gallery.css'; import { Button } from 'semantic-ui-react'; import Icon from '@plone/volto/components/theme/Icon/Icon'; @@ -14,7 +13,7 @@ import galleryPauseSVG from '@plone/volto/icons/pause.svg'; import galleryFullScreenSVG from '@plone/volto/icons/fullscreen.svg'; import galleryBackDownSVG from '@plone/volto/icons/back-down.svg'; -const ImageGallery = loadable(() => import('react-image-gallery')); +const ImageGallery = lazy(() => import('react-image-gallery')); const renderLeftNav = (onClick, disabled) => { return ( diff --git a/packages/volto/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx b/packages/volto/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx index b62f3cb15e..07d42f14cd 100644 --- a/packages/volto/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +++ b/packages/volto/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx @@ -1,7 +1,6 @@ -import React, { useState } from 'react'; +import React, { lazy, useState } from 'react'; import { Header } from 'semantic-ui-react'; import { defineMessages, injectIntl } from 'react-intl'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import { compose } from 'redux'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import { toBackendLang } from '@plone/volto/helpers/Utils/Utils'; @@ -14,6 +13,12 @@ import clearSVG from '@plone/volto/icons/clear.svg'; import 'react-dates/initialize'; import 'react-dates/lib/css/_datepicker.css'; +import moment from 'moment'; + +const DateRangePicker = lazy(() => + import('react-dates').then((mod) => ({ default: mod.DateRangePicker })), +); + const messages = defineMessages({ startDate: { id: 'Start Date', @@ -61,9 +66,7 @@ const NextIcon = () => ( const CloseIcon = () => ; const DateRangeFacet = (props) => { - const { facet, isEditMode, onChange, value, reactDates, intl, lang } = props; - const moment = props.moment.default; - const { DateRangePicker } = reactDates; + const { facet, isEditMode, onChange, value, intl, lang } = props; const [focused, setFocused] = useState(null); return ( @@ -119,7 +122,6 @@ DateRangeFacet.valueToQuery = ({ value, facet }) => { }; export default compose( - injectLazyLibs(['reactDates', 'moment']), connect((state) => ({ lang: state.intl.locale, })), diff --git a/packages/volto/src/components/manage/Blocks/Search/components/SelectFacet.jsx b/packages/volto/src/components/manage/Blocks/Search/components/SelectFacet.jsx index f3eb6c2f08..225ad68df2 100644 --- a/packages/volto/src/components/manage/Blocks/Search/components/SelectFacet.jsx +++ b/packages/volto/src/components/manage/Blocks/Search/components/SelectFacet.jsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; +import React, { lazy } from 'react'; import { Option, DropdownIndicator, @@ -12,10 +11,10 @@ import { selectFacetValueToQuery, } from './base'; +const Select = lazy(() => import('react-select')); + const SelectFacet = (props) => { - const { facet, choices, reactSelect, isMulti, onChange, value, isEditMode } = - props; - const Select = reactSelect.default; + const { facet, choices, isMulti, onChange, value, isEditMode } = props; const v = Array.isArray(value) && value.length === 0 ? null : value; return ( @@ -50,4 +49,4 @@ SelectFacet.schemaEnhancer = selectFacetSchemaEnhancer; SelectFacet.stateToValue = selectFacetStateToValue; SelectFacet.valueToQuery = selectFacetValueToQuery; -export default injectLazyLibs('reactSelect')(SelectFacet); +export default SelectFacet; diff --git a/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx b/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx index dec213bacf..69c457a7dc 100644 --- a/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx +++ b/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx @@ -1,13 +1,12 @@ +import { lazy } from 'react'; import { Button } from 'semantic-ui-react'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; import cx from 'classnames'; -import { compose } from 'redux'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import { Option, DropdownIndicator, } from '@plone/volto/components/manage/Widgets/SelectStyling'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import { selectTheme, sortOnSelectStyles } from './SelectStyling'; import upSVG from '@plone/volto/icons/sort-up.svg'; @@ -36,20 +35,20 @@ const messages = defineMessages({ }, }); +const Select = lazy(() => import('react-select')); + const SortOn = (props) => { const { data = {}, - reactSelect, sortOn = null, sortOrder = null, setSortOn, setSortOrder, isEditMode, querystring = {}, - intl, } = props; + const intl = useIntl(); const { sortable_indexes } = querystring; - const Select = reactSelect.default; const defaultSortOn = data?.query?.sort_on || ''; const activeSortOn = sortOn || defaultSortOn; @@ -146,4 +145,4 @@ const SortOn = (props) => { ); }; -export default compose(injectIntl, injectLazyLibs(['reactSelect']))(SortOn); +export default SortOn; diff --git a/packages/volto/src/components/manage/Blocks/Search/components/ViewSwitcher.jsx b/packages/volto/src/components/manage/Blocks/Search/components/ViewSwitcher.jsx index 436f3c5d18..85a126edb2 100644 --- a/packages/volto/src/components/manage/Blocks/Search/components/ViewSwitcher.jsx +++ b/packages/volto/src/components/manage/Blocks/Search/components/ViewSwitcher.jsx @@ -1,11 +1,9 @@ -import React from 'react'; -import { compose } from 'redux'; -import { defineMessages, injectIntl } from 'react-intl'; +import React, { lazy } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import { Option, DropdownIndicator, } from '@plone/volto/components/manage/Widgets/SelectStyling'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import { selectTheme, sortOnSelectStyles } from './SelectStyling'; @@ -22,9 +20,11 @@ const messages = defineMessages({ }, }); +const Select = lazy(() => import('react-select')); + function ViewSwitcher(props) { - const { data, intl, reactSelect, selectedView, setSelectedView } = props; - const Select = reactSelect.default; + const { data, selectedView, setSelectedView } = props; + const intl = useIntl(); return (