From 6c37a6a62687078c462406d85fdffa1631e7cf2a Mon Sep 17 00:00:00 2001 From: daff Date: Fri, 4 Oct 2024 14:45:19 +0300 Subject: [PATCH 01/84] feat: experimental Editor functionality --- .gitignore | 5 + .prettierignore | 6 +- README.md | 32 +- package-lock.json | 347 ++++++++- package.json | 16 +- playground/.gitignore | 28 + playground/next.config.mjs | 4 + playground/package-lock.json | 478 ++++++++++++ playground/package.json | 23 + playground/src/app/content.json | 335 ++++++++ playground/src/app/favicon.ico | Bin 0 -> 1732 bytes playground/src/app/layout.tsx | 17 + playground/src/app/page.scss | 5 + playground/src/app/page.tsx | 36 + playground/src/app/pc/page.tsx | 13 + playground/src/constants.ts | 4 + playground/src/styles/globals.scss | 6 + playground/tsconfig.json | 32 + src/blocks/Banner/Banner.tsx | 5 +- src/blocks/Banner/index.tsx | 25 + src/blocks/CardLayout/CardLayout.scss | 1 + src/blocks/CardLayout/CardLayout.tsx | 48 +- src/blocks/CardLayout/index.ts | 170 ++++ src/blocks/Companies/Companies.tsx | 17 +- src/blocks/Companies/index.ts | 23 + src/blocks/ContentLayout/ContentLayout.tsx | 68 +- src/blocks/ContentLayout/index.ts | 21 + .../ExtendedFeatures/ExtendedFeatures.tsx | 123 +-- src/blocks/ExtendedFeatures/index.ts | 22 + src/blocks/ExtendedFeatures/schema.ts | 1 + src/blocks/FilterBlock/FilterBlock.tsx | 62 +- src/blocks/FilterBlock/index.ts | 19 + src/blocks/Form/index.ts | 20 + src/blocks/Header/dynamic-form.ts | 732 ++++++++++++++++++ src/blocks/Header/index.ts | 16 + src/blocks/HeaderSlider/HeaderSlider.tsx | 19 +- src/blocks/HeaderSlider/index.ts | 44 ++ src/blocks/Icons/Icons.tsx | 64 +- src/blocks/Icons/index.ts | 22 + src/blocks/Info/index.ts | 43 + src/blocks/Map/index.ts | 19 + src/blocks/Media/index.ts | 23 + .../PromoFeaturesBlock/PromoFeaturesBlock.tsx | 78 +- src/blocks/PromoFeaturesBlock/index.ts | 22 + src/blocks/Questions/Questions.tsx | 90 ++- src/blocks/Questions/index.ts | 51 ++ src/blocks/Share/Share.tsx | 51 +- src/blocks/Share/index.ts | 20 + src/blocks/Slider/Slider.tsx | 88 ++- src/blocks/Slider/__tests__/Slider.test.tsx | 27 +- src/blocks/Slider/dynamic-form.ts | 226 ++++++ src/blocks/Slider/index.ts | 16 + src/blocks/Table/index.ts | 16 + src/blocks/Table/schema.ts | 1 + src/blocks/Tabs/Tabs.tsx | 74 +- src/blocks/Tabs/index.ts | 31 + .../TestEditorBlock/TestEditorBlock.tsx | 13 + src/blocks/TestEditorBlock/form.ts | 114 +++ src/blocks/TestEditorBlock/index.ts | 16 + src/common/hooks/usePostMessage.tsx | 134 ++++ src/common/types/actions/codes.ts | 27 + src/common/types/actions/index.ts | 31 + src/common/types/actions/initial.ts | 29 + src/common/types/actions/insert.ts | 24 + src/common/types/actions/other.ts | 21 + src/common/types/actions/overlay.ts | 16 + src/common/types/actions/reorder.ts | 27 + src/common/types/actions/select.ts | 20 + src/common/types/common.ts | 17 + src/common/types/forms.ts | 110 +++ src/common/types/index.ts | 4 + src/common/types/messages.ts | 22 + src/components/BlockBase/BlockBase.scss | 8 +- src/components/Image/dynamic-form.ts | 87 +++ .../editor/ChildrenWrap/ChildrenWrap.scss | 8 + .../editor/ChildrenWrap/ChildrenWrap.tsx | 27 + src/components/editor/ItemWrap/ItemWrap.scss | 12 + src/components/editor/ItemWrap/ItemWrap.tsx | 27 + src/constructor-items.ts | 88 +++ .../PageConstructor/PageConstructor.tsx | 65 +- src/containers/PageConstructor/Provider.tsx | 4 + .../ConstructorBlock/ConstructorBlock.scss | 4 + .../ConstructorBlock/ConstructorBlock.tsx | 16 +- .../hooks/useEditorBlockMouseEvents.tsx | 130 ++++ .../ConstructorBlocks/ConstructorBlocks.tsx | 14 +- .../ConstructorItem/ConstructorItem.tsx | 4 +- .../ConstructorLoadable.tsx | 2 +- .../ConstructorRow/ConstructorRow.tsx | 7 +- src/context/blockIdContext/blockIdContext.ts | 4 +- src/context/editorContext/editorContext.ts | 18 + src/context/editorContext/editorProvider.tsx | 27 + .../editorContext/hooks/useEditorStore.ts | 17 + src/context/editorContext/index.ts | 4 + src/context/editorContext/store.ts | 68 ++ .../hooks/useMessageObserver.tsx | 23 + .../hooks/useMessageSender.tsx | 10 + .../hooks/useMessagesStore.tsx | 17 + src/context/messagesContext/index.ts | 5 + .../messagesContext/messagesContext.tsx | 21 + .../messagesContext/messagesProvider.tsx | 38 + src/context/messagesContext/store.ts | 44 ++ .../components/BigOverlay/BigOverlay.scss | 33 + .../components/BigOverlay/BigOverlay.tsx | 69 ++ .../components/BlockConfig/BlockConfig.scss | 28 + .../components/BlockConfig/BlockConfig.tsx | 53 ++ .../components/BlocksList/BlocksList.scss | 42 + .../components/BlocksList/BlocksList.tsx | 43 + .../components/DynamicForm/DynamicForm.scss | 8 + .../components/DynamicForm/DynamicForm.tsx | 219 ++++++ .../DynamicForm/FieldBase/FieldBase.scss | 72 ++ .../DynamicForm/FieldBase/FieldBase.tsx | 73 ++ .../DynamicForm/Fields/Array/Array.scss | 51 ++ .../DynamicForm/Fields/Array/Array.tsx | 158 ++++ .../Fields/Array/ItemButton/ItemButton.tsx | 77 ++ .../DynamicForm/Fields/Boolean/Boolean.tsx | 26 + .../DynamicForm/Fields/Number/Number.tsx | 30 + .../DynamicForm/Fields/Object/Object.scss | 10 + .../DynamicForm/Fields/Object/Object.tsx | 38 + .../DynamicForm/Fields/OneOf/OneOf.scss | 14 + .../DynamicForm/Fields/OneOf/OneOf.tsx | 78 ++ .../DynamicForm/Fields/Select/Select.tsx | 42 + .../DynamicForm/Fields/Text/Text.tsx | 26 + .../DynamicForm/Fields/TextArea/TextArea.tsx | 26 + src/editor-v2/components/DynamicForm/utils.ts | 23 + .../components/GlobalConfig/GlobalConfig.scss | 17 + .../components/GlobalConfig/GlobalConfig.tsx | 29 + .../components/MiddleScreen/MiddleScreen.scss | 52 ++ .../components/MiddleScreen/MiddleScreen.tsx | 52 ++ src/editor-v2/components/Overlay/Overlay.scss | 68 ++ src/editor-v2/components/Overlay/Overlay.tsx | 133 ++++ src/editor-v2/components/Sidebar/Sidebar.scss | 86 ++ src/editor-v2/components/Sidebar/Sidebar.tsx | 110 +++ src/editor-v2/components/Source/Source.scss | 18 + src/editor-v2/components/Source/Source.tsx | 53 ++ .../components/SourceCode/SourceCode.scss | 26 + .../components/SourceCode/SourceCode.tsx | 63 ++ src/editor-v2/components/TopBar/TopBar.scss | 34 + src/editor-v2/components/TopBar/TopBar.tsx | 25 + src/editor-v2/components/Tree/Tree.scss | 38 + src/editor-v2/components/Tree/Tree.tsx | 54 ++ .../components/ViewSwitches/ViewSwitches.scss | 12 + .../components/ViewSwitches/ViewSwitches.tsx | 50 ++ src/editor-v2/containers/Editor/Editor.scss | 57 ++ src/editor-v2/containers/Editor/Editor.tsx | 107 +++ .../Editor/hooks/useAdminInitialize.tsx | 39 + .../containers/__stories__/Editor.stories.tsx | 29 + .../containers/__stories__/data.json | 335 ++++++++ src/editor-v2/containers/__stories__/utils.ts | 22 + .../contentConfig/contentConfigContext.tsx | 15 + .../contentConfig/contentConfigProvider.tsx | 34 + .../hooks/useContentConfigStore.tsx | 15 + src/editor-v2/context/contentConfig/index.ts | 4 + src/editor-v2/context/contentConfig/store.ts | 202 +++++ .../context/editorContext/editorContext.tsx | 16 + .../context/editorContext/editorProvider.tsx | 20 + .../editorContext/hooks/useEditorStore.tsx | 17 + src/editor-v2/context/editorContext/index.ts | 4 + src/editor-v2/context/editorContext/store.ts | 101 +++ .../iframeContext/hooks/useIframeStore.tsx | 15 + .../context/iframeContext/iframeContext.tsx | 18 + .../context/iframeContext/iframeProvider.tsx | 36 + src/editor-v2/context/iframeContext/index.ts | 4 + src/editor-v2/context/iframeContext/store.ts | 34 + .../hooks/useMessageObserver.tsx | 23 + .../hooks/useMessageSender.tsx | 10 + .../hooks/useMessagesStore.tsx | 17 + .../context/messagesContext/index.ts | 5 + .../messagesContext/messagesContext.tsx | 21 + .../messagesContext/messagesProvider.tsx | 46 ++ .../context/messagesContext/store.ts | 44 ++ src/editor-v2/icons/Tablet.tsx | 23 + src/editor-v2/index.ts | 2 + src/editor-v2/styles/mixins.scss | 12 + src/editor-v2/styles/root.scss | 9 + src/editor-v2/styles/variables.scss | 5 + src/editor-v2/utils/code.ts | 12 + src/editor-v2/utils/index.ts | 168 ++++ src/editor-v2/utils/store.ts | 24 + .../data/templates/test-editor-block.json | 18 + src/grid/Grid/Grid.scss | 8 +- src/hooks/useAnalytics.ts | 2 +- src/hooks/useEditorInitialize.ts | 117 +++ src/models/constructor-items/blocks.ts | 1 + .../NavigationItem/NavigationItem.tsx | 4 +- .../NavigationButton/NavigationButton.tsx | 4 +- src/navigation/containers/Layout/Layout.scss | 3 +- src/schema/constants.ts | 2 + src/schema/index.ts | 17 +- src/sub-blocks/BackgroundCard/dynamic-form.ts | 81 ++ src/sub-blocks/BackgroundCard/index.tsx | 24 + src/sub-blocks/BannerCard/BannerCard.tsx | 4 +- src/sub-blocks/BannerCard/index.tsx | 25 + src/sub-blocks/BasicCard/index.tsx | 21 + src/sub-blocks/Content/index.tsx | 21 + src/sub-blocks/Content/schema.ts | 1 + src/sub-blocks/Divider/index.tsx | 18 + src/sub-blocks/ImageCard/ImageCard.tsx | 14 +- src/sub-blocks/ImageCard/index.tsx | 21 + src/sub-blocks/LayoutItem/LayoutItem.tsx | 2 +- src/sub-blocks/LayoutItem/form.ts | 16 + src/sub-blocks/LayoutItem/index.tsx | 23 + src/sub-blocks/LayoutItem/schema.ts | 2 + src/sub-blocks/MediaCard/index.tsx | 23 + src/sub-blocks/PriceCard/index.tsx | 34 + .../PriceDetailed/PriceDetailed.tsx | 2 +- src/sub-blocks/PriceDetailed/index.tsx | 38 + src/sub-blocks/Quote/index.tsx | 24 + src/utils/editor.ts | 20 + src/utils/form-generator.ts | 145 ++++ src/utils/store.ts | 22 + styles/mixins.scss | 18 +- 211 files changed, 9216 insertions(+), 472 deletions(-) create mode 100644 playground/.gitignore create mode 100644 playground/next.config.mjs create mode 100644 playground/package-lock.json create mode 100644 playground/package.json create mode 100644 playground/src/app/content.json create mode 100644 playground/src/app/favicon.ico create mode 100644 playground/src/app/layout.tsx create mode 100644 playground/src/app/page.scss create mode 100644 playground/src/app/page.tsx create mode 100644 playground/src/app/pc/page.tsx create mode 100644 playground/src/constants.ts create mode 100644 playground/src/styles/globals.scss create mode 100644 playground/tsconfig.json create mode 100644 src/blocks/Banner/index.tsx create mode 100644 src/blocks/CardLayout/index.ts create mode 100644 src/blocks/Companies/index.ts create mode 100644 src/blocks/ContentLayout/index.ts create mode 100644 src/blocks/ExtendedFeatures/index.ts create mode 100644 src/blocks/FilterBlock/index.ts create mode 100644 src/blocks/Form/index.ts create mode 100644 src/blocks/Header/dynamic-form.ts create mode 100644 src/blocks/Header/index.ts create mode 100644 src/blocks/HeaderSlider/index.ts create mode 100644 src/blocks/Icons/index.ts create mode 100644 src/blocks/Info/index.ts create mode 100644 src/blocks/Map/index.ts create mode 100644 src/blocks/Media/index.ts create mode 100644 src/blocks/PromoFeaturesBlock/index.ts create mode 100644 src/blocks/Questions/index.ts create mode 100644 src/blocks/Share/index.ts create mode 100644 src/blocks/Slider/dynamic-form.ts create mode 100644 src/blocks/Slider/index.ts create mode 100644 src/blocks/Table/index.ts create mode 100644 src/blocks/Tabs/index.ts create mode 100644 src/blocks/TestEditorBlock/TestEditorBlock.tsx create mode 100644 src/blocks/TestEditorBlock/form.ts create mode 100644 src/blocks/TestEditorBlock/index.ts create mode 100644 src/common/hooks/usePostMessage.tsx create mode 100644 src/common/types/actions/codes.ts create mode 100644 src/common/types/actions/index.ts create mode 100644 src/common/types/actions/initial.ts create mode 100644 src/common/types/actions/insert.ts create mode 100644 src/common/types/actions/other.ts create mode 100644 src/common/types/actions/overlay.ts create mode 100644 src/common/types/actions/reorder.ts create mode 100644 src/common/types/actions/select.ts create mode 100644 src/common/types/common.ts create mode 100644 src/common/types/forms.ts create mode 100644 src/common/types/index.ts create mode 100644 src/common/types/messages.ts create mode 100644 src/components/Image/dynamic-form.ts create mode 100644 src/components/editor/ChildrenWrap/ChildrenWrap.scss create mode 100644 src/components/editor/ChildrenWrap/ChildrenWrap.tsx create mode 100644 src/components/editor/ItemWrap/ItemWrap.scss create mode 100644 src/components/editor/ItemWrap/ItemWrap.tsx create mode 100644 src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx create mode 100644 src/context/editorContext/editorContext.ts create mode 100644 src/context/editorContext/editorProvider.tsx create mode 100644 src/context/editorContext/hooks/useEditorStore.ts create mode 100644 src/context/editorContext/index.ts create mode 100644 src/context/editorContext/store.ts create mode 100644 src/context/messagesContext/hooks/useMessageObserver.tsx create mode 100644 src/context/messagesContext/hooks/useMessageSender.tsx create mode 100644 src/context/messagesContext/hooks/useMessagesStore.tsx create mode 100644 src/context/messagesContext/index.ts create mode 100644 src/context/messagesContext/messagesContext.tsx create mode 100644 src/context/messagesContext/messagesProvider.tsx create mode 100644 src/context/messagesContext/store.ts create mode 100644 src/editor-v2/components/BigOverlay/BigOverlay.scss create mode 100644 src/editor-v2/components/BigOverlay/BigOverlay.tsx create mode 100644 src/editor-v2/components/BlockConfig/BlockConfig.scss create mode 100644 src/editor-v2/components/BlockConfig/BlockConfig.tsx create mode 100644 src/editor-v2/components/BlocksList/BlocksList.scss create mode 100644 src/editor-v2/components/BlocksList/BlocksList.tsx create mode 100644 src/editor-v2/components/DynamicForm/DynamicForm.scss create mode 100644 src/editor-v2/components/DynamicForm/DynamicForm.tsx create mode 100644 src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss create mode 100644 src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Array/Array.scss create mode 100644 src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Object/Object.scss create mode 100644 src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss create mode 100644 src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx create mode 100644 src/editor-v2/components/DynamicForm/utils.ts create mode 100644 src/editor-v2/components/GlobalConfig/GlobalConfig.scss create mode 100644 src/editor-v2/components/GlobalConfig/GlobalConfig.tsx create mode 100644 src/editor-v2/components/MiddleScreen/MiddleScreen.scss create mode 100644 src/editor-v2/components/MiddleScreen/MiddleScreen.tsx create mode 100644 src/editor-v2/components/Overlay/Overlay.scss create mode 100644 src/editor-v2/components/Overlay/Overlay.tsx create mode 100644 src/editor-v2/components/Sidebar/Sidebar.scss create mode 100644 src/editor-v2/components/Sidebar/Sidebar.tsx create mode 100644 src/editor-v2/components/Source/Source.scss create mode 100644 src/editor-v2/components/Source/Source.tsx create mode 100644 src/editor-v2/components/SourceCode/SourceCode.scss create mode 100644 src/editor-v2/components/SourceCode/SourceCode.tsx create mode 100644 src/editor-v2/components/TopBar/TopBar.scss create mode 100644 src/editor-v2/components/TopBar/TopBar.tsx create mode 100644 src/editor-v2/components/Tree/Tree.scss create mode 100644 src/editor-v2/components/Tree/Tree.tsx create mode 100644 src/editor-v2/components/ViewSwitches/ViewSwitches.scss create mode 100644 src/editor-v2/components/ViewSwitches/ViewSwitches.tsx create mode 100644 src/editor-v2/containers/Editor/Editor.scss create mode 100644 src/editor-v2/containers/Editor/Editor.tsx create mode 100644 src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx create mode 100644 src/editor-v2/containers/__stories__/Editor.stories.tsx create mode 100644 src/editor-v2/containers/__stories__/data.json create mode 100644 src/editor-v2/containers/__stories__/utils.ts create mode 100644 src/editor-v2/context/contentConfig/contentConfigContext.tsx create mode 100644 src/editor-v2/context/contentConfig/contentConfigProvider.tsx create mode 100644 src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx create mode 100644 src/editor-v2/context/contentConfig/index.ts create mode 100644 src/editor-v2/context/contentConfig/store.ts create mode 100644 src/editor-v2/context/editorContext/editorContext.tsx create mode 100644 src/editor-v2/context/editorContext/editorProvider.tsx create mode 100644 src/editor-v2/context/editorContext/hooks/useEditorStore.tsx create mode 100644 src/editor-v2/context/editorContext/index.ts create mode 100644 src/editor-v2/context/editorContext/store.ts create mode 100644 src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx create mode 100644 src/editor-v2/context/iframeContext/iframeContext.tsx create mode 100644 src/editor-v2/context/iframeContext/iframeProvider.tsx create mode 100644 src/editor-v2/context/iframeContext/index.ts create mode 100644 src/editor-v2/context/iframeContext/store.ts create mode 100644 src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx create mode 100644 src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx create mode 100644 src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx create mode 100644 src/editor-v2/context/messagesContext/index.ts create mode 100644 src/editor-v2/context/messagesContext/messagesContext.tsx create mode 100644 src/editor-v2/context/messagesContext/messagesProvider.tsx create mode 100644 src/editor-v2/context/messagesContext/store.ts create mode 100644 src/editor-v2/icons/Tablet.tsx create mode 100644 src/editor-v2/index.ts create mode 100644 src/editor-v2/styles/mixins.scss create mode 100644 src/editor-v2/styles/root.scss create mode 100644 src/editor-v2/styles/variables.scss create mode 100644 src/editor-v2/utils/code.ts create mode 100644 src/editor-v2/utils/index.ts create mode 100644 src/editor-v2/utils/store.ts create mode 100644 src/editor/data/templates/test-editor-block.json create mode 100644 src/hooks/useEditorInitialize.ts create mode 100644 src/sub-blocks/BackgroundCard/dynamic-form.ts create mode 100644 src/sub-blocks/BackgroundCard/index.tsx create mode 100644 src/sub-blocks/BannerCard/index.tsx create mode 100644 src/sub-blocks/BasicCard/index.tsx create mode 100644 src/sub-blocks/Content/index.tsx create mode 100644 src/sub-blocks/Divider/index.tsx create mode 100644 src/sub-blocks/ImageCard/index.tsx create mode 100644 src/sub-blocks/LayoutItem/form.ts create mode 100644 src/sub-blocks/LayoutItem/index.tsx create mode 100644 src/sub-blocks/MediaCard/index.tsx create mode 100644 src/sub-blocks/PriceCard/index.tsx create mode 100644 src/sub-blocks/PriceDetailed/index.tsx create mode 100644 src/sub-blocks/Quote/index.tsx create mode 100644 src/utils/editor.ts create mode 100644 src/utils/form-generator.ts create mode 100644 src/utils/store.ts diff --git a/.gitignore b/.gitignore index ada2d4bd1..d8ccc7731 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ node_modules playwright-report* /blob-report/ .cache* + +# Playground +/playground/node_modules +/playground/.next +/playground/.env diff --git a/.prettierignore b/.prettierignore index a84998f2d..2d6035968 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,7 +15,11 @@ CONTRIBUTING.md /widget /schema + /playwright/playwright/.cache/ # npm files -package.json \ No newline at end of file +package.json + +# Playground +/playground/.next diff --git a/README.md b/README.md index 1f9485904..bf619e326 100644 --- a/README.md +++ b/README.md @@ -526,7 +526,7 @@ If you want to release a new version in previous major after commit it to the ma 7. Check your changes in CHANGELOG.md and approve robot's PR. 8. Squash and merge PR. You can see release process on [the Actions tab](https://github.com/gravity-ui/page-constructor/actions). -## Page constructor editor +## Page Constructor Editor v1 Editor provides user interface for page content management with realtime preview. @@ -546,6 +546,36 @@ export const MyAppEditor = ({initialContent, onChange, transformContent}: MyAppE ); ``` +## Page Constructor Editor v2 + +Editor provides user interface for page content management with realtime preview. + +Based on Iframe postMessage communication. + +How to use: + +```tsx +import {Editor} from '@gravity-ui/page-constructor/editor-v2'; + +export const MyAppEditor = ({initialContent, onUpdate, disableUrlField}: MyAppEditorProps) => ( + +); +``` + +### How to develop + +```shell +npm run deps:install +npm run dev:playground +``` + +Directory `/playground` contains NextJS service with integrated PC and Editor for development purposes. + ## Tests Comprehensive documentation is available at the provided [link](./test-utils/docs/README.md). diff --git a/package-lock.json b/package-lock.json index 3906af152..c4f89aa32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "ajv-keywords": "^5.1.0", "final-form": "^4.20.9", "github-buttons": "2.23.0", + "immutable": "^4.3.7", "js-yaml-source-map": "^0.2.2", "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", + "react-resizable-panels": "^2.1.3", "react-slick": "^0.29.0", "react-transition-group": "^4.4.2", "react-waypoint": "^10.1.0", @@ -32,7 +34,8 @@ "swiper": "^6.8.4", "typograf": "^7.4.1", "utility-types": "^3.10.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "zustand": "^4.5.2" }, "devDependencies": { "@babel/core": "^7.22.8", @@ -102,6 +105,7 @@ "lint-staged": "^11.2.6", "monaco-editor-webpack-plugin": "^7.1.0", "move-file-cli": "^3.0.0", + "next": "^14.2.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", "postcss-loader": "^4.3.0", @@ -4916,6 +4920,156 @@ "react": ">=16" } }, + "node_modules/@next/env": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.24.tgz", + "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==", + "dev": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.24.tgz", + "integrity": "sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.24.tgz", + "integrity": "sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.24.tgz", + "integrity": "sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.24.tgz", + "integrity": "sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.24.tgz", + "integrity": "sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.24.tgz", + "integrity": "sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.24.tgz", + "integrity": "sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.24.tgz", + "integrity": "sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.24.tgz", + "integrity": "sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -6399,6 +6553,22 @@ "@svgr/core": "*" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.11.3", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.3.tgz", @@ -8852,6 +9022,18 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -9333,6 +9515,12 @@ "node": ">=8" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -14284,8 +14472,7 @@ "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -18005,12 +18192,90 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/next": { + "version": "14.2.24", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.24.tgz", + "integrity": "sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==", + "dev": true, + "dependencies": { + "@next/env": "14.2.24", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.24", + "@next/swc-darwin-x64": "14.2.24", + "@next/swc-linux-arm64-gnu": "14.2.24", + "@next/swc-linux-arm64-musl": "14.2.24", + "@next/swc-linux-x64-gnu": "14.2.24", + "@next/swc-linux-x64-musl": "14.2.24", + "@next/swc-win32-arm64-msvc": "14.2.24", + "@next/swc-win32-ia32-msvc": "14.2.24", + "@next/swc-win32-x64-msvc": "14.2.24" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "node_modules/next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==", "dev": true }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -20537,6 +20802,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable-panels": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", + "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-select": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", @@ -22480,6 +22754,15 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", @@ -22926,6 +23209,29 @@ "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dev": true, + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/stylelint": { "version": "15.11.0", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", @@ -24408,6 +24714,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -25778,6 +26092,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 951dc7604..b8fa888c4 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,11 @@ "require": "./build/cjs/editor/index.js", "import": "./build/esm/editor/index.js" }, + "./editor-v2": { + "types": "./build/esm/editor-v2/index.d.ts", + "require": "./build/cjs/editor-v2/index.js", + "import": "./build/esm/editor-v2/index.js" + }, "./server": { "types": "./server/index.d.ts", "require": "./server/index.js", @@ -61,7 +66,9 @@ "*.scss" ], "scripts": { - "deps:install": "npm ci", + "deps:install:playground": "cd playground && npm ci", + "deps:install:package": "npm ci", + "deps:install": "run-p deps:install:playground deps:install:package", "deps:truncate": "npm prune --production", "lint:fix": "run-s lint:js:fix lint:styles:fix lint:prettier:fix typecheck", "lint:js": "eslint '**/*.{js,jsx,ts,tsx}' --max-warnings=0", @@ -73,6 +80,7 @@ "lint": "run-p lint:js lint:styles lint:prettier typecheck", "typecheck": "tsc --noEmit", "dev": "npm run storybook:start", + "dev:website": "next dev playground", "storybook:start": "storybook dev -p 7009", "storybook:build": "storybook build -c .storybook -o storybook-static", "start": "node dist", @@ -105,12 +113,14 @@ "ajv-keywords": "^5.1.0", "final-form": "^4.20.9", "github-buttons": "2.23.0", + "immutable": "^4.3.7", "js-yaml-source-map": "^0.2.2", "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", + "react-resizable-panels": "^2.1.3", "react-slick": "^0.29.0", "react-transition-group": "^4.4.2", "react-waypoint": "^10.1.0", @@ -119,7 +129,8 @@ "swiper": "^6.8.4", "typograf": "^7.4.1", "utility-types": "^3.10.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "zustand": "^4.5.2" }, "peerDependencies": { "@diplodoc/transform": "^4.10.4", @@ -194,6 +205,7 @@ "lint-staged": "^11.2.6", "monaco-editor-webpack-plugin": "^7.1.0", "move-file-cli": "^3.0.0", + "next": "^14.2.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", "postcss-loader": "^4.3.0", diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 000000000..583394ff2 --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,28 @@ +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/playground/next.config.mjs b/playground/next.config.mjs new file mode 100644 index 000000000..4678774e6 --- /dev/null +++ b/playground/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/playground/package-lock.json b/playground/package-lock.json new file mode 100644 index 000000000..afdffac32 --- /dev/null +++ b/playground/package-lock.json @@ -0,0 +1,478 @@ +{ + "name": "page-constructor-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "page-constructor-example", + "version": "1.0.0", + "dependencies": { + "bem-cn-lite": "^4.1.0", + "next": "14.2.3", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } + }, + "node_modules/@next/env": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", + "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", + "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", + "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/bem-cn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bem-cn/-/bem-cn-3.0.1.tgz", + "integrity": "sha512-kWC76a09vSk6cJXDYsH1erjxdBf856HTxl0IHOvYItSmBC6wQCsRCf9bmKR0hmeUDcUP5XPMr8MNXDgKbKJi0A==" + }, + "node_modules/bem-cn-lite": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bem-cn-lite/-/bem-cn-lite-4.1.0.tgz", + "integrity": "sha512-0IEVRYK2MQKQO00P3sY3hNv7vH8P+Z8mR46qFcaiwsQAWp0MuMWpLSuUUhZEjKD2HzTGXMqMsFysWEeeJa1drQ==", + "dependencies": { + "bem-cn": "^3.0.1" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001618", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz", + "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", + "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "dependencies": { + "@next/env": "14.2.3", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.3", + "@next/swc-darwin-x64": "14.2.3", + "@next/swc-linux-arm64-gnu": "14.2.3", + "@next/swc-linux-arm64-musl": "14.2.3", + "@next/swc-linux-x64-gnu": "14.2.3", + "@next/swc-linux-x64-musl": "14.2.3", + "@next/swc-win32-arm64-msvc": "14.2.3", + "@next/swc-win32-ia32-msvc": "14.2.3", + "@next/swc-win32-x64-msvc": "14.2.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 000000000..2ce85b873 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,23 @@ +{ + "name": "page-constructor-playground", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "bem-cn-lite": "^4.1.0", + "next": "14.2.3", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} diff --git a/playground/src/app/content.json b/playground/src/app/content.json new file mode 100644 index 000000000..396e3394e --- /dev/null +++ b/playground/src/app/content.json @@ -0,0 +1,335 @@ +{ + "blocks": [ + { + "type": "header-block", + "title": "Yandex Open Source", + "description": "

Мы в Яндексе верим, что вклад в опенсорс — это вклад в технологическую эволюцию: без открытости, совместной работы и поддержки развитие IT‑индустрии сильно затруднено. Уже много лет мы используем в своих продуктах сторонние открытые технологии, а также делимся собственными и активно вовлекаем в их развитие разработчиков по всему миру.

", + "width": "s", + "verticalOffset": "l", + "offset": "default", + "resetPaddings": true, + "background": { + "image": { + "mobile": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover-m.png", + "desktop": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover.png" + }, + "color": "#EFF2F8", + "fullWidth": false, + "fullWidthMedia": true + } + }, + { + "type": "extended-features-block", + "title": { + "text": "Почему мы выкладываем наши технологии в открытый доступ?" + }, + "items": [ + { + "title": "Ответственность", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-01.svg", + "text": "

Мы верим, что вкладываться в развитие опенсорс‑технологий — это ответственность каждого технологического лидера на рынке. Без опенсорс‑решений не появились бы многие продукты и сервисы не только Яндекса, но и других крупных компаний, и мы хотим отдавать обратно, делиться теми нашими решениями, которые, как мы считаем, принесут реальную пользу.

" + }, + { + "title": "Польза для сообщества", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-02.svg", + "text": "

Технологии, которые мы разрабатываем, ежедневно помогают нам эффективно решать огромное количество самых разных задач в наших сервисах. Мы знаем, что разработчики вне Яндекса часто сталкиваются с теми же самыми задачами — и верим, что наши технологии могут быть полезны и им.

" + }, + { + "title": "Качество сервисов", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-03.svg", + "text": "

Для нас важно разрабатывать и использовать только качественные технологические решения. В особенности это касается опенсорса: зная, что наши решения увидят и будут использовать другие, мы уделяем их качеству особое внимание. А уже в открытом доступе у технологии больше шансов развиваться и улучшаться — в том числе, при участии сообщества разработчиков.

" + }, + { + "title": "Бизнес‑потенциал", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-04.svg", + "text": "

Мы верим, что при условии роста популярности наших решений и спроса на них со стороны сообщества, то, что мы выкладываем в опенсорс, может далее стать для нас бизнесом. То, что мы выкладываем в опенсорс, можно использовать и во внешних коммерческих проектах.

" + }, + { + "title": "Поиск талантов", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-05.svg", + "text": "

Мы ценим каждого, кто вкладывается в сторонние опенсорс‑решения или делится с миром своими. Контрибьюторы в наши продукты нам особенно важны: среди них мы ищем и находим тех, кто сможет развивать технологии уже будучи частью команды Яндекса.

" + } + ] + }, + { + "type": "card-layout-block", + "animated": false, + "title": "Краткая история опенсорса в Яндексе", + "description": "

С начала истории развития опенсорса в Яндексе мы успели выложить в открытый доступ десятки собственных проектов, использовать в разработке наших продуктов внешние технологии, а также внесли существенный вклад в их развитие.

", + "colSizes": { + "all": 12, + "lg": 3, + "md": 4, + "sm": 6 + }, + "anchor": { + "url": "history", + "text": "history" + }, + "children": [ + { + "type": "layout-item", + "content": { + "title": "2010", + "text": "

Методология веб‑разработки БЭМ (Блок‑Элемент‑Модификатор) выходит в оперсорс

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-01.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2012", + "text": "

Запуск Яндекс Браузера на базе Blink (Chromium)

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-02.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2013", + "text": "

Яндекс начинает контрибьютить в ядро Linux

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-03.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2016", + "text": "

Выход в опенсорс ClickHouse

\\n

Выход в опенсорс Hermione (с 2024 года — Testplane)

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2016.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2017", + "text": "

Выход в опенсорс CatBoost
\\nЯндекс начинает контрибьютить в PostgreSQL

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-05.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2018", + "text": "

Выход в опенсорс Одиссея

\\n

Яндекс — топ‑контрибьютор в WAL‑G

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-06.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2019", + "text": "

В Яндексе появляется команда разработки СУБД с открытым исходным кодом

\\n

Яндекс — спонсор разработки PostgreSQL

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-07.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2020", + "text": "

Выход в опенсорс Testsuite

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2020.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2022", + "text": "

Яндекс — один из основных спонсоров разработки PostgreSQL

\\n

Выход в опенсорс YDB, userver, YaLM 100B, DivKit, Yatagan

\\n

Старт программы «Код для всех»

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-09.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2023", + "text": "

Выход в опенсорс YTsaurus, Gravity UI, AppMetrica, Diplodoc, DataLens и счётчика Метрики

\\n

Старт Программы грантов Yandex Open Source

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2023.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2024", + "text": "

Первый Yandex Open Source Jam

\\n

Выход в опенсорс YaFSDP

" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2024.png" + } + } + ] + }, + { + "type": "card-layout-block", + "title": "Наши проекты", + "description": "

В Яндексе мы разрабатываем и развиваем технологические решения самых разных сфер применения, размеров и сложности. Поэтому и в открытый доступ попадают самые разные проекты — главное, чтобы они приносили пользу не только нам, но и другим.

", + "colSizes": { + "all": 12, + "lg": 3, + "md": 4, + "sm": 6 + }, + "anchor": { + "url": "projects", + "text": "projects" + }, + "children": [ + { + "type": "background-card", + "title": "YDB", + "text": "

Отказоустойчивая распределённая SQL база данных

", + "backgroundColor": "#2399FF", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-ydb.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "background-card", + "title": "YTsaurus", + "text": "

Платформа для хранения и обработки больших данных

", + "backgroundColor": "#FFB23E", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-yt.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "GravityUI", + "text": "

Библиотеки для создания интерфейсов

", + "backgroundColor": "#262626", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-gui.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "background-card", + "title": "DivKit", + "text": "

Фреймворк для server‑driven интерфейсов

", + "backgroundColor": "#F1F1F1", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dk.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "Diplodoc", + "text": "

Платформа для написания документации в концепции Docs as Code

", + "backgroundColor": "#79F985", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dd.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "userver", + "text": "

Фреймворк для создания высоконагруженных приложений

", + "backgroundColor": "#FF9D73", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-u.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "DataLens", + "text": "

BI-платформа для анализа и визуализации данных

", + "backgroundColor": "#FF7132", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dl.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "basic-card", + "title": "И это ещё не всё", + "text": "

Узнать про наши опенсорс‑проекты больше вы можете на другой странице

", + "border": "none", + "buttons": [ + { + "text": "Все проекты", + "primary": true, + "theme": "monochrome", + "size": "promo", + "url": "/projects" + } + ] + } + ] + }, + { + "largeMedia": true, + "mediaOnly": false, + "size": "l", + "type": "media-block", + "direction": "content-media", + "anchor": { + "url": "1", + "text": "Фильм" + }, + "title": "

Смотрите фильм с YaC 22

", + "description": "

Руководители опенсорс‑проектов рассказывают про историю и культуру открытого кода в Яндексе.

", + "media": { + "youtube": "https://www.youtube.com/watch?v=G7G286S8ntc", + "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/video-cover.png" + }, + "disableShadow": true + }, + { + "type": "content-layout-block", + "size": "l", + "centered": true, + "textContent": { + "title": "", + "text": "", + "additionalInfo": "", + "buttons": [ + { + "text": "Сделано на GravityUI", + "theme": "monochrome", + "img": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/open-source-gravity-ui-button.svg", + "url": "https://gravity-ui.com/" + } + ] + } + } + ] +} diff --git a/playground/src/app/favicon.ico b/playground/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..74cc5d2cdbf7a60dc986d8bcdf7a7080882dcc19 GIT binary patch literal 1732 zcmV;#20QtQP)5>=%wn{M|7ezpf!Ke|u1Y*L)2P0}k6zV0Cfarq>7^v~V zAP;CH+6WP13}QkGFDRmIK}2kWUDz&cVcTvmyPfXr?D0P{+tO~QbcO!O$?lw)bN=sq z|Mf8BC_WO!aIFC3#f+hfa|9Soi+3D|(Oi(`8tL(bF2|3=NOIYijjgyeprqtY^yRt% zg}DfU+OO-z2Qe)Y#O(kuPn`B_V;q}s2f`Wl+~^L!iT?vYoLV*HI{F*H_((9R8%yoj zkg_NIo+(x1tXxPyRpdIcclL{64CHe1g(Zb}!0$!XgnSe_9p>Dno<5wo+KFG=x)IfM z+(Je$!EP$|`BcNxFq-mb10~pa?^Mk2I{`HXC~4v^ZS6pYhQW~RXE@ZHW3idrb0}qvZfIK>#I|(>50AvAx z{5>rIkTsLUNWTN&Xc~=IH7xS_;WgYqLdDT=6zk8QG4~wmm~063E!oOTTq?d@R*8zK zv~fW{;1PoWxCn@10FJcvp#Y%ErgHQtG*lo?qnFr<)96)kxFw30&o>P2O{SP8Oko;P zG34zwtfOY(bW3@mMx&2FXlU;0!-2Xu>ikEu;|!t*-K*N3)3XU&{x=^Ji%)XIv-A%w#WOtoxqNKoy zy+?~m>=!UzB~cs%MgS-+v}hEbPUAwWKBjU99rob+`9uK$&@APc83CiHDKBu78k?-o zSwOO@aFQ^Lu%Yl~iuJY_9}_^$t9dBPbDN>pEeKoL2=3Lu&2Tl~lU2KAKFMyD3nL1^ z%=#_!-@0mVdESZV=6Fnx?};kddb-67U30nc17?yiNi-ALlNUPUHyw$_-06-{ zmA|vfh3Y4~KxZEGb{E!u69!6qEHh|y>V5@ni6m0?;S&>17|fn+i1u0@oJk}vEq9?H zZ#WBXC&RwYd3fvb0xRCT+<4_+1gEaVQT=wW3CPL=PSnS`?9nqGlo(5&&7MCV?nHNj zTUm_qdm-6x#~SzG`vo%{_;E)*o~j__oo;ARH#XEp@WaI}YA& zXvZ~s0Gu^CIHVJFwsk}2yOJqyQ<=adHJZAteTdoDpxFT?{JznUvr=p zzl7r0{B94bU+)M1hr%vY1cZIK5|&UKYq8f){iB(RDI2zAM?;}1__? zM5TNF(3kxNM};pRGmFWVqH#(|R<+YIRgMtLs~hm-+D<&SfT6@7x-a>G<`NSCYo3T* z4I%7pZ^9_DzrFiLt?Ayf!`PEH^k!13l8J;g{a1;NifiD_ek;tV-kT2B zMm`ZH>VtP-*SX8s+4k3H${|t5EPl;hBe6)(pFg#RF+K?5k&+20V|1!S%A#D()PCWZ z#T2H0PPR-U!1Xz->PJo&3dz`fu^uOTBcm#(h^&cZ+Ooj_*By@Ss*!w}xDANs`#nWg zV_{hJ{8UE=_FfEQxvLzN#f7LW%m*VE7OhxYLdWT73`edv;ivA_(PDl;eNcBKwhufF z3+__tjM9cwCqR_D^00g6EL=AM2 z5y!loT5On2>pJ8pW`X1QQcf9rBDZ@v%@*r=?pZ+#mT=KUvEkQqv#2ridVZ3w=ZAB> ap8p42@17$8jUv) { + return ( + + {children} + + ); +} diff --git a/playground/src/app/page.scss b/playground/src/app/page.scss new file mode 100644 index 000000000..213d4b128 --- /dev/null +++ b/playground/src/app/page.scss @@ -0,0 +1,5 @@ +$block: '.home'; + +#{$block} { + height: 100vh; +} diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx new file mode 100644 index 000000000..e641a8a5f --- /dev/null +++ b/playground/src/app/page.tsx @@ -0,0 +1,36 @@ +'use client'; +import {ThemeProvider} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import React, {useEffect, useState} from 'react'; + +import {Editor} from '../../../src/editor-v2'; + +import content from './content.json'; +import './page.scss'; + +const b = block('home'); + +export default function Home() { + const [initialUrl, setInitialUrl] = useState(''); + + useEffect(() => { + if (typeof window !== 'undefined') { + setInitialUrl(window.location.origin + '/pc'); + } + }, []); + + return ( + +
+ {initialUrl && ( + {}} + /> + )} +
+
+ ); +} diff --git a/playground/src/app/pc/page.tsx b/playground/src/app/pc/page.tsx new file mode 100644 index 000000000..e208d76c6 --- /dev/null +++ b/playground/src/app/pc/page.tsx @@ -0,0 +1,13 @@ +'use client'; +import React from 'react'; + +import {PageConstructor, PageConstructorProvider} from '../../../../src'; +import content from '../content.json'; + +export default function Home() { + return ( + + + + ); +} diff --git a/playground/src/constants.ts b/playground/src/constants.ts new file mode 100644 index 000000000..428aee7cb --- /dev/null +++ b/playground/src/constants.ts @@ -0,0 +1,4 @@ +import {Theme} from '@gravity-ui/uikit'; + +export const DEFAULT_THEME: Theme = 'light'; +export const DEFAULT_BODY_CLASSNAME = `g-root g-root_theme_${DEFAULT_THEME}`; diff --git a/playground/src/styles/globals.scss b/playground/src/styles/globals.scss new file mode 100644 index 000000000..9fb6a01e7 --- /dev/null +++ b/playground/src/styles/globals.scss @@ -0,0 +1,6 @@ +@import '../../../styles/styles.scss'; + +html, +body { + margin: 0; +} diff --git a/playground/tsconfig.json b/playground/tsconfig.json new file mode 100644 index 000000000..5912c1974 --- /dev/null +++ b/playground/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "@gravity-ui/tsconfig/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "outDir": "build/esm", + "module": "esnext", + "jsx": "preserve", + "baseUrl": ".", + "target": "ES2020", + "resolveJsonModule": true, + "importHelpers": true, + "lib": ["dom", "dom.iterable", "esnext"], + "noEmit": true, + "incremental": true, + "isolatedModules": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "src/typings/*.d.ts", + ".next/types/**/*.ts", + "../src", + "../styles" + ], + "exclude": ["node_modules"] +} diff --git a/src/blocks/Banner/Banner.tsx b/src/blocks/Banner/Banner.tsx index 89a7906fc..53b3a1316 100644 --- a/src/blocks/Banner/Banner.tsx +++ b/src/blocks/Banner/Banner.tsx @@ -1,4 +1,5 @@ import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; +import {Grid} from '../../grid'; import {BannerBlockProps} from '../../models'; import {BannerCard} from '../../sub-blocks'; import {block} from '../../utils'; @@ -12,7 +13,9 @@ export const BannerBlock = (props: BannerBlockProps) => { return ( - + + + ); }; diff --git a/src/blocks/Banner/index.tsx b/src/blocks/Banner/index.tsx new file mode 100644 index 000000000..928df0660 --- /dev/null +++ b/src/blocks/Banner/index.tsx @@ -0,0 +1,25 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import BannerBlock from './Banner'; +import {BannerCardProps} from './schema'; + +const BannerBlockConfig: BlockData = { + component: BannerBlock, + schema: { + name: 'Banner Block', + inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>), + default: { + color: 'rgba(54, 151, 241, 0.4)', + title: 'Banner Block', + subtitle: 'Some sort of description.', + button: { + text: 'Read more', + }, + }, + }, +}; + +export default BannerBlockConfig; diff --git a/src/blocks/CardLayout/CardLayout.scss b/src/blocks/CardLayout/CardLayout.scss index c4a484533..9c178834c 100644 --- a/src/blocks/CardLayout/CardLayout.scss +++ b/src/blocks/CardLayout/CardLayout.scss @@ -26,6 +26,7 @@ $largeBorderRadius: 32px; width: 100%; height: 100%; border-radius: $largeBorderRadius; + pointer-events: none; img { object-fit: cover; diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx index bc8fa3856..135ebb16c 100644 --- a/src/blocks/CardLayout/CardLayout.tsx +++ b/src/blocks/CardLayout/CardLayout.tsx @@ -2,9 +2,14 @@ import * as React from 'react'; import isEmpty from 'lodash/isEmpty'; import {AnimateBlock, BackgroundImage, Title} from '../../components'; +import ChildrenWrap from '../../components/editor/ChildrenWrap/ChildrenWrap'; +import ItemWrap from '../../components/editor/ItemWrap/ItemWrap'; import {useTheme} from '../../context/theme'; -import {Col, GridColumnSizesType, Row} from '../../grid'; -import {CardLayoutBlockProps as CardLayoutBlockParams, ClassNameProps} from '../../models'; +import {Col, Grid, GridColumnSizesType, Row} from '../../grid'; +import { + CardLayoutBlockProps as CardLayoutBlockParams, + ClassNameProps, +} from '../../models'; import {block, getThemedValue} from '../../utils'; import './CardLayout.scss'; @@ -35,23 +40,28 @@ const CardLayout: React.FC = ({ const {border, ...backgroundImageProps} = getThemedValue(background || {}, theme); return ( - {(title || description) && ( - - )} - <div - className={b('content', { - 'with-background': !isEmpty(background), - })} - > - <BackgroundImage className={b('image', {border})} {...backgroundImageProps} /> - <Row> - {React.Children.map(children, (child, index) => ( - <Col key={index} sizes={colSizes} className={b('item')}> - {child} - </Col> - ))} - </Row> - </div> + <Grid> + {(title || description) && ( + <Title title={title} subtitle={description} className={titleClassName} /> + )} + <div + className={b('content', { + 'with-background': !isEmpty(background), + })} + > + <BackgroundImage className={b('image', {border})} {...backgroundImageProps} /> + + <ChildrenWrap> + <Row> + {React.Children.map(children, (child, index) => ( + <Col key={index} sizes={colSizes} className={b('item')}> + <ItemWrap index={index}>{child}</ItemWrap> + </Col> + ))} + </Row> + </ChildrenWrap> + </div> + </Grid> </AnimateBlock> ); }; diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts new file mode 100644 index 000000000..b600e79dd --- /dev/null +++ b/src/blocks/CardLayout/index.ts @@ -0,0 +1,170 @@ +import {ConfigInput} from '../../common/types'; +import {BlockData} from '../../constructor-items'; +import {sliderSizesArray, textSize} from '../../schema/validators/common'; + +import CardLayout from './CardLayout'; + +const textSizeEnum = textSize.map((size) => ({value: size, content: size})); + +export const blockConfig: ConfigInput[] = [ + { + type: 'oneOf', + name: 'title', + title: 'Title Object', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'text', + name: '', + title: 'Title', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: [ + { + type: 'text', + name: 'text', + title: 'Title', + }, + { + type: 'select', + name: 'textSize', + title: 'Text Size', + enum: textSizeEnum, + view: 'select', + mode: 'single', + }, + { + type: 'text', + name: 'url', + title: 'Url', + }, + { + type: 'text', + name: 'urlTitle', + title: 'Url', + }, + { + type: 'boolean', + name: 'resetMargin', + title: 'Reset Margin', + }, + ], + }, + ], + }, + { + type: 'textarea', + name: 'description', + title: 'Description', + }, + { + type: 'boolean', + name: 'dots', + title: 'With dots', + }, + { + type: 'boolean', + name: 'arrows', + title: 'With Arrows', + }, + { + type: 'boolean', + name: 'randomOrder', + title: 'Random Order', + }, + { + type: 'number', + name: 'autoplay', + title: 'Autoplay', + }, + { + type: 'object', + name: 'disclaimer', + title: 'Disclaimer', + properties: [ + { + type: 'text', + name: 'text', + title: 'Text', + }, + { + type: 'select', + name: 'size', + title: 'Size', + enum: textSizeEnum, + view: 'select', + mode: 'single', + }, + ], + }, + { + type: 'oneOf', + name: 'slidesToShow', + title: 'Slides to Show', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'number', + name: '', + title: 'Slides', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: sliderSizesArray.reduce((acc, size) => { + acc.push({type: 'number', name: size, title: size}); + return acc; + }, [] as Array<ConfigInput>), + }, + ], + }, + { + type: 'array', + name: 'array', + title: 'Array Properties', + buttonText: 'Add new', + arrayType: 'object', + properties: [ + { + type: 'text', + name: 'text', + title: 'Property', + }, + { + type: 'boolean', + name: 'boolean', + title: 'Property', + }, + ], + }, +]; + +const CardLayoutBlockConfig: BlockData = { + component: CardLayout, + schema: { + name: 'Card Layout Block', + inputs: blockConfig, + // inputs: generateFromAJV(CardLayoutProps as unknown as JSONSchemaType<{}>), + default: { + type: 'card-layout-block', + children: [], + title: 'Card Layout Block', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default CardLayoutBlockConfig; diff --git a/src/blocks/Companies/Companies.tsx b/src/blocks/Companies/Companies.tsx index d16d78a05..c200e4528 100644 --- a/src/blocks/Companies/Companies.tsx +++ b/src/blocks/Companies/Companies.tsx @@ -1,6 +1,7 @@ import {Image, Title} from '../../components'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import {useTheme} from '../../context/theme'; +import {Grid} from '../../grid'; import {CompaniesBlockProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -14,12 +15,18 @@ export const CompaniesBlock = ({title, description, images, animated}: Companies return ( <AnimateBlock className={b()} offset={150} animate={animated}> - <div className={b('content')}> - <Title title={title} subtitle={description} colSizes={{all: 12, sm: 12}}> -
- + +
+ +
+ +
-
+
); }; diff --git a/src/blocks/Companies/index.ts b/src/blocks/Companies/index.ts new file mode 100644 index 000000000..dab19df95 --- /dev/null +++ b/src/blocks/Companies/index.ts @@ -0,0 +1,23 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import CompaniesBlock from './Companies'; +import {CompaniesBlock as CompaniesBlockSchema} from './schema'; + +const CompaniesBlockConfig: BlockData = { + component: CompaniesBlock, + schema: { + name: 'Companies Block', + inputs: generateFromAJV( + CompaniesBlockSchema['companies-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + title: 'Companies Block', + description: 'Here is the list', + }, + }, +}; + +export default CompaniesBlockConfig; diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx index 7706d229a..ba26121d9 100644 --- a/src/blocks/ContentLayout/ContentLayout.tsx +++ b/src/blocks/ContentLayout/ContentLayout.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import {BackgroundImage, FileLink} from '../../components'; import {MobileContext} from '../../context/mobileContext'; import {useTheme} from '../../context/theme'; -import {Col} from '../../grid'; +import {Col, Grid} from '../../grid'; import {ContentLayoutBlockProps, ContentSize, ContentTextSize} from '../../models'; import {Content} from '../../sub-blocks'; import {block, getThemedValue} from '../../utils'; @@ -51,39 +51,41 @@ export const ContentLayoutBlock = (props: ContentLayoutBlockProps) => { const themedBackground = getThemedValue(background, globalTheme); return ( -
- - {fileContent && ( - - {fileContent.map((file) => ( - +
+ + {fileContent && ( + + {fileContent.map((file) => ( + + ))} + + )} + {background && ( +
+ - ))} - - )} - {background && ( -
- -
- )} -
+
+ )} +
+ ); }; export default ContentLayoutBlock; diff --git a/src/blocks/ContentLayout/index.ts b/src/blocks/ContentLayout/index.ts new file mode 100644 index 000000000..59dac4d75 --- /dev/null +++ b/src/blocks/ContentLayout/index.ts @@ -0,0 +1,21 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import ContentLayoutBlock from './ContentLayout'; +import {ContentLayoutBlock as ContentLayoutBlockSchema} from './schema'; + +const ContentLayoutBlockConfig = { + component: ContentLayoutBlock, + schema: { + name: 'Content Layout Block', + inputs: generateFromAJV( + ContentLayoutBlockSchema['content-layout-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + title: 'Content Layout Block', + }, + }, +}; + +export default ContentLayoutBlockConfig; diff --git a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx index 0d034fa00..35c5569af 100644 --- a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx +++ b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx @@ -4,7 +4,7 @@ import {AnimateBlock, HTML, Title} from '../../components/'; import Image from '../../components/Image/Image'; import {getMediaImage} from '../../components/Media/Image/utils'; import {useTheme} from '../../context/theme'; -import {Col, Row} from '../../grid'; +import {Col, Grid, Row} from '../../grid'; import {ExtendedFeaturesProps} from '../../models'; import {Content} from '../../sub-blocks'; import {block, getThemedValue} from '../../utils'; @@ -31,69 +31,72 @@ export const ExtendedFeaturesBlock = ({ return ( - - <div className={b('items')}> - <Row> - {items.map( - ({ - title: itemTitle, - text, - list, - link, - links, - label, - icon, - buttons, - additionalInfo, - }) => { - const itemLinks = links || []; + <Grid> + <Title title={title} subtitle={description} className={b('header')} /> + <div className={b('items')}> + <Row> + {items && + items.map( + ({ + title: itemTitle, + text, + list, + link, + links, + label, + icon, + buttons, + additionalInfo, + }) => { + const itemLinks = links || []; - const iconThemed = icon && getThemedValue(icon, theme); - const iconData = iconThemed && getMediaImage(iconThemed); + const iconThemed = icon && getThemedValue(icon, theme); + const iconData = iconThemed && getMediaImage(iconThemed); - if (link) { - itemLinks.push(link); - } + if (link) { + itemLinks.push(link); + } - return ( - <Col className={b('item')} key={text || itemTitle} sizes={colSizes}> - {iconData && ( - <div className={b('icon-wrap')} aria-hidden> - <Image {...iconData} className={b('icon')} /> + return ( + <Col className={b('item')} key={text || itemTitle} sizes={colSizes}> + {iconData && ( + <div className={b('icon-wrap')} aria-hidden> + <Image {...iconData} className={b('icon')} /> + </div> + )} + <div className={b('container')}> + {itemTitle && + React.createElement( + itemTitleHeadingTag, + { + className: b('item-title'), + }, + <React.Fragment> + <HTML>{itemTitle}</HTML> + {label && ( + <span className={b('item-label')}> + {label} + </span> + )} + </React.Fragment>, + )} + <Content + text={text} + links={itemLinks} + size="s" + list={list} + colSizes={{all: 12, md: 12}} + buttons={buttons} + additionalInfo={additionalInfo} + /> </div> - )} - <div className={b('container')}> - {itemTitle && - React.createElement( - itemTitleHeadingTag, - { - className: b('item-title'), - }, - <React.Fragment> - <HTML>{itemTitle}</HTML> - {label && ( - <span className={b('item-label')}> - {label} - </span> - )} - </React.Fragment>, - )} - <Content - text={text} - links={itemLinks} - size="s" - list={list} - colSizes={{all: 12, md: 12}} - buttons={buttons} - additionalInfo={additionalInfo} - /> - </div> - </Col> - ); - }, - )} - </Row> - </div> + </Col> + ); + }, + )} + </Row> + </div> + </Grid> </AnimateBlock> ); }; diff --git a/src/blocks/ExtendedFeatures/index.ts b/src/blocks/ExtendedFeatures/index.ts new file mode 100644 index 000000000..e6b0a209d --- /dev/null +++ b/src/blocks/ExtendedFeatures/index.ts @@ -0,0 +1,22 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import ExtendedFeaturesBlock from './ExtendedFeatures'; +import {ExtendedFeaturesBlock as ExtendedFeaturesBlockSchema} from './schema'; + +const ExtendedFeaturesBlockConfig = { + component: ExtendedFeaturesBlock, + schema: { + name: 'Extended Features Block', + inputs: generateFromAJV( + ExtendedFeaturesBlockSchema['extended-features-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + title: 'Extended Features Block', + items: [{}], + }, + }, +}; + +export default ExtendedFeaturesBlockConfig; diff --git a/src/blocks/ExtendedFeatures/schema.ts b/src/blocks/ExtendedFeatures/schema.ts index a7705dfc1..eae9ea2e5 100644 --- a/src/blocks/ExtendedFeatures/schema.ts +++ b/src/blocks/ExtendedFeatures/schema.ts @@ -20,6 +20,7 @@ export const ExtendedFeaturesItem = { text: { type: 'string', contentType: 'yfm', + inputType: 'textarea', }, label: { type: 'string', diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 2f6439223..870dc7d5f 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -4,7 +4,7 @@ import {CardLayoutBlock} from '..'; import {AnimateBlock, Title} from '../../components'; import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs'; import {ConstructorItem} from '../../containers/PageConstructor/components/ConstructorItem'; -import {Col, Row} from '../../grid'; +import {Col, Grid, Row} from '../../grid'; import {FilterBlockProps, FilterItem} from '../../models'; import {block, getBlockKey} from '../../utils'; @@ -19,7 +19,7 @@ const FilterBlock: React.FC<FilterBlockProps> = ({ tags, tagButtonSize, allTag, - items, + items = [], colSizes, centered, animated, @@ -53,35 +53,37 @@ const FilterBlock: React.FC<FilterBlockProps> = ({ return ( <AnimateBlock className={b()} animate={animated}> - {title && ( - <Title - className={b('title', {centered: centered})} - title={title} - subtitle={description} - /> - )} - {tabButtons.length && ( - <Row> - <Col> - <ButtonTabs - className={b('tabs', {centered: centered})} - items={tabButtons} - activeTab={selectedTag} - onSelectTab={setSelectedTag} - tabSize={tagButtonSize} - /> - </Col> - </Row> - )} - <Row className={b('block-container')}> - <CardLayoutBlock title="" colSizes={colSizes} className={b('cards-container')}> - {cards.map((card, index) => { - const key = getBlockKey(card, index); + <Grid> + {title && ( + <Title + className={b('title', {centered: centered})} + title={title} + subtitle={description} + /> + )} + {tabButtons.length && ( + <Row> + <Col> + <ButtonTabs + className={b('tabs', {centered: centered})} + items={tabButtons} + activeTab={selectedTag} + onSelectTab={setSelectedTag} + tabSize={tagButtonSize} + /> + </Col> + </Row> + )} + <Row className={b('block-container')}> + <CardLayoutBlock title="" colSizes={colSizes} className={b('cards-container')}> + {cards.map((card, index) => { + const key = getBlockKey(card, index); - return <ConstructorItem data={card} blockKey={key} key={key} />; - })} - </CardLayoutBlock> - </Row> + return <ConstructorItem data={card} blockKey={index} key={key} />; + })} + </CardLayoutBlock> + </Row> + </Grid> </AnimateBlock> ); }; diff --git a/src/blocks/FilterBlock/index.ts b/src/blocks/FilterBlock/index.ts new file mode 100644 index 000000000..15a4a56b1 --- /dev/null +++ b/src/blocks/FilterBlock/index.ts @@ -0,0 +1,19 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import FilterBlock from './FilterBlock'; +import {FilterProps} from './schema'; + +const FilterBlockConfig = { + component: FilterBlock, + schema: { + name: 'Filter Block', + inputs: generateFromAJV(FilterProps as unknown as JSONSchemaType<{}>), + default: { + title: 'Filter Block', + }, + }, +}; + +export default FilterBlockConfig; diff --git a/src/blocks/Form/index.ts b/src/blocks/Form/index.ts new file mode 100644 index 000000000..b80b5c371 --- /dev/null +++ b/src/blocks/Form/index.ts @@ -0,0 +1,20 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import FormBlock from './Form'; +import {FormBlock as FormBlockSchema} from './schema'; + +const FormBlockConfig = { + component: FormBlock, + schema: { + name: 'Form Block', + inputs: generateFromAJV(FormBlockSchema['form-block'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Form Block', + formData: {}, + }, + }, +}; + +export default FormBlockConfig; diff --git a/src/blocks/Header/dynamic-form.ts b/src/blocks/Header/dynamic-form.ts new file mode 100644 index 000000000..d3f174aba --- /dev/null +++ b/src/blocks/Header/dynamic-form.ts @@ -0,0 +1,732 @@ +import {BlockConfig, ConfigInput} from '../../common/types'; +import {imageInputs} from '../../components/Image/dynamic-form'; +import {Theme} from '../../models'; + +const mediaInputs: ConfigInput[] = [ + { + type: 'oneOf', + name: '', + key: 'mediaType', + title: 'Media Type', + options: [ + { + title: 'Empty', + value: 'emptyMedia', + properties: [ + { + type: 'text', + name: 'color', + title: 'Color', + }, + ], + }, + { + title: 'Image', + value: 'image', + properties: [ + { + type: 'oneOf', + name: 'image', + key: 'imagesCount', + title: 'Images Count', + options: [ + {title: 'Single Image', value: 'singleImage', properties: imageInputs}, + { + title: 'Multiple Images', + value: 'multipleImages', + properties: [ + { + type: 'array', + name: '', + arrayType: 'object', + title: 'Images', + properties: imageInputs, + buttonText: 'Add Image', + }, + ], + }, + ], + }, + ], + }, + { + title: 'Youtube', + value: 'youtube', + properties: [ + { + type: 'text', + name: 'youtube', + title: 'Youtube', + }, + { + type: 'text', + name: 'previewImg', + title: 'Preview Image', + }, + ], + }, + { + title: 'Datalens', + value: 'datalens', + properties: [], + }, + { + title: 'Iframe', + value: 'iframe', + properties: [], + }, + { + title: 'Video', + value: 'video', + properties: [], + }, + ], + }, + { + type: 'boolean', + name: 'disableImageSliderForArrayInput', + title: 'Disable Image Slider For Array Input', + }, + { + type: 'boolean', + name: 'parallax', + title: 'Parallax', + }, + { + type: 'number', + name: 'height', + title: 'Height', + }, + { + type: 'boolean', + name: 'fullscreen', + title: 'Fullscreen', + }, + { + type: 'number', + name: 'ratio', + title: 'Ratio', + }, + { + type: 'boolean', + name: 'margins', + title: 'Margins', + }, +]; + +const headerBackgroundInputs: ConfigInput[] = [ + { + type: 'boolean', + name: 'fullWidthMedia', + title: 'Full width Media', + }, + { + type: 'boolean', + name: 'fullWidth', + title: 'Full width', + }, +]; + +const backgroundProperty = (properties: ConfigInput[]): ConfigInput => ({ + type: 'oneOf', + name: 'background', + title: 'Background', + options: [ + { + value: 'noThemes', + title: 'No Themes', + properties: properties, + }, + { + value: 'withThemes', + title: 'With Themes', + properties: Object.values(Theme).map((theme) => ({ + type: 'object', + name: theme, + title: theme, + properties: properties, + })), + }, + ], +}); + +export const blockConfig: BlockConfig = { + name: 'Header Block', + inputs: [ + { + type: 'text', + name: 'title', + title: 'Title', + }, + { + type: 'text', + name: 'overtitle', + title: 'Overtitle', + }, + { + type: 'textarea', + name: 'description', + title: 'Description', + }, + backgroundProperty([...mediaInputs, ...headerBackgroundInputs]), + /* { + type: 'text', + name: 'when', + title: 'when', + }, + { + type: 'object', + name: 'anchor', + title: 'anchor', + properties: [ + { + type: 'text', + name: 'text', + title: 'text', + }, + { + type: 'text', + name: 'url', + title: 'url', + }, + { + type: 'text', + name: 'urlTitle', + title: 'urlTitle', + }, + ], + }, + { + type: 'select', + name: 'visible', + title: 'visible', + enum: [ + { + content: 'sm', + value: 'sm', + }, + { + content: 'md', + value: 'md', + }, + { + content: 'lg', + value: 'lg', + }, + { + content: 'xl', + value: 'xl', + }, + { + content: 'all', + value: 'all', + }, + ], + }, + { + type: 'text', + name: 'context', + title: 'context', + }, + { + type: 'object', + name: 'indent', + title: 'indent', + properties: [], + }, + { + type: 'select', + name: 'width', + title: 'width', + enum: [ + { + content: 's', + value: 's', + }, + { + content: 'm', + value: 'm', + }, + { + content: 'l', + value: 'l', + }, + ], + }, + { + type: 'array', + name: 'buttons', + title: 'buttons', + properties: [ + { + type: 'text', + name: 'when', + title: 'when', + }, + { + type: 'text', + name: 'text', + title: 'text', + }, + { + type: 'text', + name: 'url', + title: 'url', + }, + { + type: 'text', + name: 'urlTitle', + title: 'urlTitle', + }, + { + type: 'select', + name: 'size', + title: 'size', + enum: [ + { + content: 'xs', + value: 'xs', + }, + { + content: 'ns', + value: 'ns', + }, + { + content: 's', + value: 's', + }, + { + content: 'n', + value: 'n', + }, + { + content: 'm', + value: 'm', + }, + { + content: 'l', + value: 'l', + }, + { + content: 'xl', + value: 'xl', + }, + { + content: 'head', + value: 'head', + }, + { + content: 'promo', + value: 'promo', + }, + ], + }, + { + type: 'select', + name: 'theme', + title: 'theme', + enum: [ + { + content: 'normal', + value: 'normal', + }, + { + content: 'action', + value: 'action', + }, + { + content: 'outlined', + value: 'outlined', + }, + { + content: 'outlined-info', + value: 'outlined-info', + }, + { + content: 'outlined-danger', + value: 'outlined-danger', + }, + { + content: 'raised', + value: 'raised', + }, + { + content: 'flat', + value: 'flat', + }, + { + content: 'flat-info', + value: 'flat-info', + }, + { + content: 'flat-danger', + value: 'flat-danger', + }, + { + content: 'flat-secondary', + value: 'flat-secondary', + }, + { + content: 'clear', + value: 'clear', + }, + { + content: 'normal-contrast', + value: 'normal-contrast', + }, + { + content: 'outlined-contrast', + value: 'outlined-contrast', + }, + { + content: 'flat-contrast', + value: 'flat-contrast', + }, + { + content: 'link', + value: 'link', + }, + { + content: 'pseudo', + value: 'pseudo', + }, + { + content: 'pseudo-special', + value: 'pseudo-special', + }, + { + content: 'websearch', + value: 'websearch', + }, + { + content: 'normal-dark', + value: 'normal-dark', + }, + { + content: 'normal-special', + value: 'normal-special', + }, + { + content: 'accent', + value: 'accent', + }, + { + content: 'dark-grey', + value: 'dark-grey', + }, + { + content: 'app-store', + value: 'app-store', + }, + { + content: 'google-play', + value: 'google-play', + }, + { + content: 'scale', + value: 'scale', + }, + { + content: 'github', + value: 'github', + }, + { + content: 'monochrome', + value: 'monochrome', + }, + ], + }, + { + type: 'oneOf', + name: 'img', + title: 'img', + options: [ + { + value: 'url', + title: 'url', + properties: [], + }, + { + value: 'options', + title: 'options', + properties: [], + }, + ], + }, + { + type: 'oneOf', + name: 'analyticsEvents', + title: 'analyticsEvents', + options: [ + { + value: 'single', + title: 'single', + properties: [ + { + type: 'text', + name: 'additionalProperties', + title: 'additionalProperties', + }, + ], + }, + { + value: 'list', + title: 'list', + properties: [ + { + type: 'object', + name: 'items', + title: 'items', + properties: [ + { + type: 'text', + name: 'name', + title: 'name', + }, + { + type: 'text', + name: 'type', + title: 'type', + }, + { + type: 'object', + name: 'counters', + title: 'counters', + properties: [], + }, + { + type: 'text', + name: 'context', + title: 'context', + }, + ], + }, + ], + }, + ], + }, + { + type: 'select', + name: 'target', + title: 'target', + enum: [ + { + content: '_self', + value: '_self', + }, + { + content: '_blank', + value: '_blank', + }, + { + content: '_parent', + value: '_parent', + }, + { + content: '_top', + value: '_top', + }, + ], + }, + { + type: 'select', + name: 'width', + title: 'width', + enum: [ + { + content: 'auto', + value: 'auto', + }, + { + content: 'max', + value: 'max', + }, + ], + }, + ], + }, + { + type: 'select', + name: 'offset', + title: 'offset', + enum: [ + { + content: 'default', + value: 'default', + }, + { + content: 'large', + value: 'large', + }, + ], + }, + { + type: 'oneOf', + name: 'image', + title: 'image', + options: [ + { + value: 'no theme', + title: 'no theme', + properties: [], + }, + { + value: 'themes', + title: 'themes', + properties: [], + }, + ], + }, + { + type: 'oneOf', + name: 'video', + title: 'video', + options: [ + { + value: 'no theme', + title: 'no theme', + properties: [], + }, + { + value: 'themes', + title: 'themes', + properties: [], + }, + ], + }, + { + type: 'select', + name: 'mediaView', + title: 'mediaView', + enum: [ + { + content: 'fit', + value: 'fit', + }, + { + content: 'full', + value: 'full', + }, + ], + }, + { + type: 'object', + name: 'backLink', + title: 'backLink', + properties: [ + { + type: 'text', + name: 'url', + title: 'url', + }, + { + type: 'text', + name: 'title', + title: 'title', + }, + ], + }, + { + type: 'select', + name: 'imageSize', + title: 'imageSize', + enum: [ + { + content: 's', + value: 's', + }, + { + content: 'm', + value: 'm', + }, + ], + }, + { + type: 'select', + name: 'verticalOffset', + title: 'verticalOffset', + enum: [ + { + content: '0', + value: '0', + }, + { + content: 's', + value: 's', + }, + { + content: 'm', + value: 'm', + }, + { + content: 'l', + value: 'l', + }, + { + content: 'xl', + value: 'xl', + }, + ], + }, + + { + type: 'select', + name: 'theme', + title: 'theme', + enum: [ + { + content: 'default', + value: 'default', + }, + { + content: 'dark', + value: 'dark', + }, + ], + }, + { + type: 'object', + name: 'breadcrumbs', + title: 'breadcrumbs', + properties: [ + { + type: 'array', + name: 'items', + title: 'items', + properties: [ + { + type: 'text', + name: 'url', + title: 'url', + }, + { + type: 'text', + name: 'text', + title: 'text', + }, + ], + }, + { + type: 'select', + name: 'theme', + title: 'theme', + enum: [ + { + content: 'light', + value: 'light', + }, + { + content: 'dark', + value: 'dark', + }, + ], + }, + ], + }, + { + type: 'text', + name: 'status', + title: 'status', + },*/ + ], +}; diff --git a/src/blocks/Header/index.ts b/src/blocks/Header/index.ts new file mode 100644 index 000000000..1f432122e --- /dev/null +++ b/src/blocks/Header/index.ts @@ -0,0 +1,16 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import HeaderBlock from './Header'; +import {HeaderBlock as HeaderBlockSchema} from './schema'; + +const HeaderBlockConfig = { + component: HeaderBlock, + schema: { + name: 'Header Block', + inputs: generateFromAJV(HeaderBlockSchema['header-block'] as unknown as JSONSchemaType<{}>), + }, +}; + +export default HeaderBlockConfig; diff --git a/src/blocks/HeaderSlider/HeaderSlider.tsx b/src/blocks/HeaderSlider/HeaderSlider.tsx index 02e0c11d2..c621ae7e4 100644 --- a/src/blocks/HeaderSlider/HeaderSlider.tsx +++ b/src/blocks/HeaderSlider/HeaderSlider.tsx @@ -25,15 +25,16 @@ export const HeaderSliderBlock = ({items, arrows, ...props}: HeaderSliderBlockPr blockClassName={b()} arrowSize={20} > - {items.map((item, index) => ( - <div - key={index} - className={b('item')} - data-qa={`header-slider-item-${index + 1}`} - > - <Header {...item} className={b('item-content')} /> - </div> - ))} + {items && + items.map((item, index) => ( + <div + key={index} + className={b('item')} + data-qa={`header-slider-item-${index + 1}`} + > + <Header {...item} className={b('item-content')} /> + </div> + ))} </SliderBlock> </div> ); diff --git a/src/blocks/HeaderSlider/index.ts b/src/blocks/HeaderSlider/index.ts new file mode 100644 index 000000000..482887543 --- /dev/null +++ b/src/blocks/HeaderSlider/index.ts @@ -0,0 +1,44 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import HeaderSliderBlock from './HeaderSlider'; +import {HeaderSliderBlock as HeaderSliderBlockSchema} from './schema'; + +const HeaderSliderBlockConfig = { + component: HeaderSliderBlock, + schema: { + name: 'Header Slider Block', + inputs: generateFromAJV( + HeaderSliderBlockSchema['header-slider-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + type: 'header-slider-block', + items: [ + { + title: 'Header Slide 1', + overtitle: 'Header Slider Block presents', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + { + image: 'https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/header-bg-video_light.png', + mediaView: 'fit', + title: 'Header Slide 2', + overtitle: 'Header Slider Block presents', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + width: 'm', + buttons: [ + { + text: 'Button', + theme: 'action', + }, + ], + }, + ], + }, + }, +}; + +export default HeaderSliderBlockConfig; diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index e6ce8c667..93935febb 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import {Image, Title} from '../../components'; import {LocationContext} from '../../context/locationContext'; +import {Grid} from '../../grid'; import {useTheme} from '../../context/theme'; import {useAnalytics} from '../../hooks'; import {IconsBlockItemProps, IconsBlockProps} from '../../models'; @@ -32,36 +33,39 @@ const Icons = ({title, description, size = 's', colSizes = {all: 12}, items}: Ic return ( <div className={b({size})}> - {(title || description) && ( - <Title - className={b('header')} - title={title} - subtitle={description} - colSizes={colSizes} - /> - )} - {items.map((item) => { - const themedSrc = getThemedValue(item.src, theme); - const itemContent = getItemContent({...item, src: themedSrc}); - const {url, text} = item; - return url ? ( - <a - className={b('item')} - key={url} - href={url} - aria-label={text} - title={text} - {...getLinkProps(url, hostname)} - onClick={() => onClick(item)} - > - {itemContent} - </a> - ) : ( - <div className={b('item')} key={text}> - {itemContent} - </div> - ); - })} + <Grid> + {(title || description) && ( + <Title + className={b('header')} + title={title} + subtitle={description} + colSizes={colSizes} + /> + )} + {items && + items.map((item) => { + const themedSrc = getThemedValue(item.src, theme); + const itemContent = getItemContent({...item, src: themedSrc}); + const {url, text} = item; + return url ? ( + <a + className={b('item')} + key={url} + href={url} + aria-label={text} + title={text} + {...getLinkProps(url, hostname)} + onClick={() => onClick(item)} + > + {itemContent} + </a> + ) : ( + <div className={b('item')} key={text}> + {itemContent} + </div> + ); + })} + </Grid> </div> ); }; diff --git a/src/blocks/Icons/index.ts b/src/blocks/Icons/index.ts new file mode 100644 index 000000000..21251e103 --- /dev/null +++ b/src/blocks/Icons/index.ts @@ -0,0 +1,22 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import IconsBlock from './Icons'; +import {IconsProps} from './schema'; + +const IconsBlockConfig = { + component: IconsBlock, + schema: { + name: 'Icons Block', + inputs: generateFromAJV(IconsProps as unknown as JSONSchemaType<{}>), + default: { + type: 'icons-block', + title: 'Icons Block', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default IconsBlockConfig; diff --git a/src/blocks/Info/index.ts b/src/blocks/Info/index.ts new file mode 100644 index 000000000..1bd367f98 --- /dev/null +++ b/src/blocks/Info/index.ts @@ -0,0 +1,43 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import InfoBlock from './Info'; +import {InfoBlock as InfoBlockSchema} from './schema'; + +const InfoBlockConfig = { + component: InfoBlock, + schema: { + name: 'Info Block', + inputs: generateFromAJV(InfoBlockSchema['info-block'] as unknown as JSONSchemaType<{}>), + default: { + type: 'info-block', + title: 'Info Block', + backgroundColor: '#1c1c1c', + sectionsTitle: 'Other links', + links: [ + { + text: 'Link 1', + }, + { + text: 'Link 2', + }, + { + text: 'Link 3', + }, + ], + buttons: [ + { + text: 'Read more', + theme: 'outlined-contrast', + }, + { + text: 'Go back', + theme: 'action', + }, + ], + }, + }, +}; + +export default InfoBlockConfig; diff --git a/src/blocks/Map/index.ts b/src/blocks/Map/index.ts new file mode 100644 index 000000000..88c48ce77 --- /dev/null +++ b/src/blocks/Map/index.ts @@ -0,0 +1,19 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import MapBlock from './Map'; +import {MapBlock as MapBlockSchema} from './schema'; + +const MapBlockConfig = { + component: MapBlock, + schema: { + name: 'Map Block', + inputs: generateFromAJV(MapBlockSchema['map-block'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Map Block', + }, + }, +}; + +export default MapBlockConfig; diff --git a/src/blocks/Media/index.ts b/src/blocks/Media/index.ts new file mode 100644 index 000000000..b3f113ad6 --- /dev/null +++ b/src/blocks/Media/index.ts @@ -0,0 +1,23 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import MediaBlock from './Media'; +import {MediaBlock as MediaBlockSchema} from './schema'; + +const MediaBlockConfig = { + component: MediaBlock, + schema: { + name: 'Media Block', + inputs: generateFromAJV(MediaBlockSchema['media-block'] as unknown as JSONSchemaType<{}>), + default: { + type: 'media-block', + title: 'Media Block', + additionalInfo: 'Additional info', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default MediaBlockConfig; diff --git a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx index 4c34e6a95..ac77be32f 100644 --- a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx +++ b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx @@ -5,6 +5,7 @@ import Media from '../../components/Media/Media'; import Title from '../../components/Title/Title'; import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {BREAKPOINTS} from '../../constants'; +import {Grid} from '../../grid'; import {useTheme} from '../../context/theme'; import {PromoFeaturesProps} from '../../models'; import {block, getThemedValue} from '../../utils'; @@ -26,43 +27,50 @@ const PromoFeaturesBlock = (props: PromoFeaturesProps) => { const globalTheme = useTheme(); return ( - <AnimateBlock className={b({[backgroundTheme]: true})} animate={animated}> - <FullWidthBackground className={b('background', {[backgroundTheme]: true})} /> - <Title title={title} subtitle={description} className={b('header')} /> - <BalancedMasonry - breakpointCols={breakpointColumns} - className={b('card-container')} - columnClassName={b('card-container-column')} - > - {items.map(({title: cardTitle, text, media, theme: cardTheme}, index) => { - const blockModifier = backgroundTheme === 'default' ? 'default' : 'light'; - const themeMod = cardTheme || blockModifier || ''; - const themedMedia = getThemedValue(media, globalTheme); - const allProps = mergeVideoMicrodata(themedMedia, { - name: cardTitle, - description: text, - }); + <Grid> + <AnimateBlock className={b({[backgroundTheme]: true})} animate={animated}> + <FullWidthBackground className={b('background', {[backgroundTheme]: true})} /> + <Title title={title} subtitle={description} className={b('header')} /> + <BalancedMasonry + breakpointCols={breakpointColumns} + className={b('card-container')} + columnClassName={b('card-container-column')} + > + {items && + items.map(({title: cardTitle, text, media, theme: cardTheme}, index) => { + const blockModifier = + backgroundTheme === 'default' ? 'default' : 'light'; + const themeMod = cardTheme || blockModifier || ''; + const themedMedia = getThemedValue(media, globalTheme); + const allProps = mergeVideoMicrodata(themedMedia, { + name: cardTitle, + description: text, + }); - return ( - <div - key={index} - className={b('card', { - 'no-media': !media, - [themeMod]: Boolean(themeMod), - })} - > - <div className={b('card-info')}> - <h3 className={b('card-title')}>{cardTitle}</h3> - <div className={b('card-text')}> - <YFMWrapper content={text} modifiers={{constructor: true}} /> + return ( + <div + key={index} + className={b('card', { + 'no-media': !media, + [themeMod]: Boolean(themeMod), + })} + > + <div className={b('card-info')}> + <h3 className={b('card-title')}>{cardTitle}</h3> + <div className={b('card-text')}> + <YFMWrapper + content={text} + modifiers={{constructor: true}} + /> + </div> + </div> + {media && <Media className={b('card-media')} {...allProps} />} </div> - </div> - {media && <Media className={b('card-media')} {...allProps} />} - </div> - ); - })} - </BalancedMasonry> - </AnimateBlock> + ); + })} + </BalancedMasonry> + </AnimateBlock> + </Grid> ); }; diff --git a/src/blocks/PromoFeaturesBlock/index.ts b/src/blocks/PromoFeaturesBlock/index.ts new file mode 100644 index 000000000..2c3eac449 --- /dev/null +++ b/src/blocks/PromoFeaturesBlock/index.ts @@ -0,0 +1,22 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import PromoFeaturesBlock from './PromoFeaturesBlock'; +import {PromoFeaturesBlock as PromoFeaturesBlockSchema} from './schema'; + +const PromoFeaturesBlockConfig = { + component: PromoFeaturesBlock, + schema: { + name: 'Promo Features Block', + inputs: generateFromAJV( + PromoFeaturesBlockSchema['promo-features-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + title: 'Promo Features Block', + items: [{}], + }, + }, +}; + +export default PromoFeaturesBlockConfig; diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx index dc70729e7..0f4b5840c 100644 --- a/src/blocks/Questions/Questions.tsx +++ b/src/blocks/Questions/Questions.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {Col, Row} from '../../grid'; +import {Col, Grid, Row} from '../../grid'; import {QuestionsProps} from '../../models'; import {Content} from '../../sub-blocks'; import {block} from '../../utils'; @@ -29,48 +29,54 @@ const QuestionsBlock = (props: QuestionsProps) => { }; return ( - <div - className={b()} - itemScope - itemType={FaqMicrodataValues.PageType} - itemID={FaqMicrodataValues.PageId} - > - <Row> - <Col sizes={{all: 12, md: 4}}> - <div className={b('title')}> - <Content - title={title} - text={text} - additionalInfo={additionalInfo} - links={links} - list={list} - buttons={buttons} - colSizes={{all: 12, md: 12}} - /> - </div> - </Col> - <Col sizes={{all: 12, md: 8}} role={'list'}> - {items.map( - ({title: itemTitle, text: itemText, link, listStyle = 'dash'}, index) => { - const isOpened = opened.includes(index); - const onClick = () => toggleItem(index); + <Grid> + <div + className={b()} + itemScope + itemType={FaqMicrodataValues.PageType} + itemID={FaqMicrodataValues.PageId} + > + <Row> + <Col sizes={{all: 12, md: 4}}> + <div className={b('title')}> + <Content + title={title} + text={text} + additionalInfo={additionalInfo} + links={links} + list={list} + buttons={buttons} + colSizes={{all: 12, md: 12}} + /> + </div> + </Col> + <Col sizes={{all: 12, md: 8}} role={'list'}> + {items && + items.map( + ( + {title: itemTitle, text: itemText, link, listStyle = 'dash'}, + index, + ) => { + const isOpened = opened.includes(index); + const onClick = () => toggleItem(index); - return ( - <QuestionBlockItem - key={itemTitle} - title={itemTitle} - text={itemText} - link={link} - listStyle={listStyle} - isOpened={isOpened} - onClick={onClick} - /> - ); - }, - )} - </Col> - </Row> - </div> + return ( + <QuestionBlockItem + key={itemTitle} + title={itemTitle} + text={itemText} + link={link} + listStyle={listStyle} + isOpened={isOpened} + onClick={onClick} + /> + ); + }, + )} + </Col> + </Row> + </div> + </Grid> ); }; diff --git a/src/blocks/Questions/index.ts b/src/blocks/Questions/index.ts new file mode 100644 index 000000000..43253bf7e --- /dev/null +++ b/src/blocks/Questions/index.ts @@ -0,0 +1,51 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import QuestionsBlock from './Questions'; +import {QuestionsBlock as QuestionsBlockSchema} from './schema'; + +const QuestionsBlockConfig = { + component: QuestionsBlock, + schema: { + name: 'Questions Block', + inputs: generateFromAJV( + QuestionsBlockSchema['questions-block'] as unknown as JSONSchemaType<{}>, + ), + default: { + type: 'questions-block', + title: 'Questions Block', + text: 'Here you can find answers.', + links: [ + { + text: 'Report for 2024', + url: 'file.doc', + }, + ], + buttons: [ + { + text: 'Get more', + theme: 'outlined-info', + }, + ], + list: [ + { + title: 'Report for 2024', + text: 'Some advice here', + }, + ], + items: [ + { + title: 'Question 1', + text: 'Answer for question 1', + }, + { + title: 'Question 2', + text: 'Answer for question 2', + }, + ], + }, + }, +}; + +export default QuestionsBlockConfig; diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx index f3450836b..8a911261b 100644 --- a/src/blocks/Share/Share.tsx +++ b/src/blocks/Share/Share.tsx @@ -40,31 +40,34 @@ const Share = ({items, title}: ShareBlockProps) => { <div className={b()}> <h5 className={b('title')}>{title || i18n('constructor-share')}</h5> <div className={b('items')}> - {items.map((type) => { - const url = getAbsolutePath(hostname, pathname); - const socialUrl = getShareLink(url, type); - const icon = icons[type]; - const urlTitle = i18n(`${type}-title`); - const buttonLabel = i18n(`${type}-label`); + {items && + items.map((type) => { + const url = getAbsolutePath(hostname, pathname); + const socialUrl = getShareLink(url, type); + const icon = icons[type]; + const urlTitle = i18n(`${type}-title`); + const buttonLabel = i18n(`${type}-label`); - return ( - <Button - key={type} - view="flat" - size="l" - target="_blank" - href={socialUrl} - className={b('item', {type: type.toLowerCase()})} - onClick={handleButtonClick} - title={urlTitle} - extraProps={{ - 'aria-label': buttonLabel, - }} - > - {icon && <Icon data={icon} size={24} className={b('icon', {type})} />} - </Button> - ); - })} + return ( + <Button + key={type} + view="flat" + size="l" + target="_blank" + href={socialUrl} + className={b('item', {type: type.toLowerCase()})} + onClick={handleButtonClick} + title={urlTitle} + extraProps={{ + 'aria-label': buttonLabel, + }} + > + {icon && ( + <Icon data={icon} size={24} className={b('icon', {type})} /> + )} + </Button> + ); + })} </div> </div> ); diff --git a/src/blocks/Share/index.ts b/src/blocks/Share/index.ts new file mode 100644 index 000000000..e44d6b623 --- /dev/null +++ b/src/blocks/Share/index.ts @@ -0,0 +1,20 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import ShareBlock from './Share'; +import {ShareBlock as ShareBlockSchema} from './schema'; + +const ShareBlockConfig = { + component: ShareBlock, + schema: { + name: 'Share Block', + inputs: generateFromAJV(ShareBlockSchema['share-block'] as unknown as JSONSchemaType<{}>), + default: { + items: ['vk', 'telegram', 'facebook'], + title: 'Share Block', + }, + }, +}; + +export default ShareBlockConfig; diff --git a/src/blocks/Slider/Slider.tsx b/src/blocks/Slider/Slider.tsx index eaadacfac..9fb3ec23d 100644 --- a/src/blocks/Slider/Slider.tsx +++ b/src/blocks/Slider/Slider.tsx @@ -10,10 +10,13 @@ import Anchor from '../../components/Anchor/Anchor'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import OutsideClick from '../../components/OutsideClick/OutsideClick'; import Title from '../../components/Title/Title'; +import ChildrenWrap from '../../components/editor/ChildrenWrap/ChildrenWrap'; +import ItemWrap from '../../components/editor/ItemWrap/ItemWrap'; import {BREAKPOINTS} from '../../constants'; import {MobileContext} from '../../context/mobileContext'; import {SSRContext} from '../../context/ssrContext'; import {StylesContext} from '../../context/stylesContext/StylesContext'; +import {Grid} from '../../grid'; import useFocus from '../../hooks/useFocus'; import { ClassNameProps, @@ -97,14 +100,16 @@ export const SliderBlock = (props: React.PropsWithChildren<SliderProps>) => { const isAutoplayEnabled = autoplaySpeed !== undefined && autoplaySpeed > 0; const isUserInteractionRef = React.useRef(false); - const [slidesToShow] = React.useState<SliderBreakpointParams>( - getSlidesToShowWithDefaults({ - contentLength: childrenCount, - breakpoints: props.slidesToShow, - mobileFullscreen: Boolean( - props.type && Object.values(SliderType).includes(props.type as SliderType), - ), - }), + const slidesToShow = React.useMemo<SliderBreakpointParams>( + () => + getSlidesToShowWithDefaults({ + contentLength: childrenCount, + breakpoints: props.slidesToShow, + mobileFullscreen: Boolean( + props.type && Object.values(SliderType).includes(props.type as SliderType), + ), + }), + [childrenCount, props.slidesToShow, props.type], ); const slidesToShowCount = getSlidesToShowCount(slidesToShow); @@ -423,40 +428,49 @@ export const SliderBlock = (props: React.PropsWithChildren<SliderProps>) => { }; return ( - <OutsideClick onOutsideClick={isMobile ? unsetFocus : noop}> - <SlickSlider {...settings}>{disclosedChildren}</SlickSlider> - <div className={b('footer')}> - {renderDisclaimer()} - {renderNavigation()} - </div> - </OutsideClick> + <ChildrenWrap> + <OutsideClick onOutsideClick={isMobile ? unsetFocus : noop}> + <SlickSlider {...settings}> + {React.Children.map(disclosedChildren, (child, index) => ( + <ItemWrap index={index}>{child}</ItemWrap> + ))} + </SlickSlider> + + <div className={b('footer')}> + {renderDisclaimer()} + {renderNavigation()} + </div> + </OutsideClick> + </ChildrenWrap> ); }; return ( <StylesContext.Provider value={{...childStyles, setStyles: setChildStyles}}> - <div - className={b( - { - 'align-left': childrenCount < slidesCountByBreakpoint, - 'one-slide': childrenCount === 1, - 'only-arrows': !title?.text && !description && arrows, - mobile: isMobile, - type, - }, - blockClassName, - )} - > - {anchorId && <Anchor id={anchorId} />} - <Title - title={title} - subtitle={description} - className={b('header', {'no-description': !description})} - /> - <AnimateBlock className={b('animate-slides')} animate={animated}> - {renderSlider()} - </AnimateBlock> - </div> + <Grid> + <div + className={b( + { + 'align-left': childrenCount < slidesCountByBreakpoint, + 'one-slide': childrenCount === 1, + 'only-arrows': !title?.text && !description && arrows, + mobile: isMobile, + type, + }, + blockClassName, + )} + > + {anchorId && <Anchor id={anchorId} />} + <Title + title={title} + subtitle={description} + className={b('header', {'no-description': !description})} + /> + <AnimateBlock className={b('animate-slides')} animate={animated}> + {renderSlider()} + </AnimateBlock> + </div> + </Grid> </StylesContext.Provider> ); }; diff --git a/src/blocks/Slider/__tests__/Slider.test.tsx b/src/blocks/Slider/__tests__/Slider.test.tsx index 7b8d2f8a6..defcfb704 100644 --- a/src/blocks/Slider/__tests__/Slider.test.tsx +++ b/src/blocks/Slider/__tests__/Slider.test.tsx @@ -1,5 +1,6 @@ import {queryHelpers, render} from '@testing-library/react'; +import {PageConstructorProvider} from '../../../containers/PageConstructor'; import {BasicCard} from '../../../sub-blocks'; import Slider from '../Slider'; @@ -14,18 +15,20 @@ const slidesToShowValues = [3, 2, 1]; describe('Slider', () => { test.each(slidesToShowValues)('Has correct slider labels', async (slidesToShow) => { const {container} = render( - <Slider title={{text: SLIDER_TITLE, url: EXAMPLE_URL}} slidesToShow={slidesToShow}> - {Array(CARDS_COUNT) - .fill(null) - .map((_, index) => ( - <BasicCard - url={EXAMPLE_URL} - title={CARD_TITLE} - text={CARD_TEXT} - key={index} - /> - ))} - </Slider>, + <PageConstructorProvider> + <Slider title={{text: SLIDER_TITLE, url: EXAMPLE_URL}} slidesToShow={slidesToShow}> + {Array(CARDS_COUNT) + .fill(null) + .map((_, index) => ( + <BasicCard + url={EXAMPLE_URL} + title={CARD_TITLE} + text={CARD_TEXT} + key={index} + /> + ))} + </Slider> + </PageConstructorProvider>, ); // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access diff --git a/src/blocks/Slider/dynamic-form.ts b/src/blocks/Slider/dynamic-form.ts new file mode 100644 index 000000000..ea7468997 --- /dev/null +++ b/src/blocks/Slider/dynamic-form.ts @@ -0,0 +1,226 @@ +import {BlockConfig, ConfigInput} from '../../common/types'; +import {sliderSizesArray, textSize} from '../../schema/validators/common'; + +const textSizeEnum = textSize.map((size) => ({value: size, content: size})); + +export const blockConfig: BlockConfig = { + name: 'Slider Block', + inputs: [ + { + type: 'oneOf', + name: 'title', + title: 'Title Object', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'text', + name: '', + title: 'Title', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: [ + { + type: 'text', + name: 'text', + title: 'Title', + }, + { + type: 'select', + name: 'textSize', + title: 'Text Size', + enum: textSizeEnum, + view: 'select', + mode: 'single', + }, + { + type: 'text', + name: 'url', + title: 'Url', + }, + { + type: 'text', + name: 'urlTitle', + title: 'Url', + }, + { + type: 'boolean', + name: 'resetMargin', + title: 'Reset Margin', + }, + ], + }, + ], + }, + { + type: 'textarea', + name: 'description', + title: 'Description', + }, + { + type: 'boolean', + name: 'dots', + title: 'With dots', + }, + { + type: 'boolean', + name: 'arrows', + title: 'With Arrows', + }, + { + type: 'boolean', + name: 'randomOrder', + title: 'Random Order', + }, + { + type: 'number', + name: 'autoplay', + title: 'Autoplay', + }, + { + type: 'object', + name: 'disclaimer', + title: 'Disclaimer', + properties: [ + { + type: 'text', + name: 'text', + title: 'Text', + }, + { + type: 'select', + name: 'size', + title: 'Size', + enum: textSizeEnum, + view: 'select', + mode: 'single', + }, + ], + }, + { + type: 'oneOf', + name: 'slidesToShow', + title: 'Slides to Show', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'number', + name: '', + title: 'Slides', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: sliderSizesArray.reduce((acc, size) => { + acc.push({type: 'number', name: size, title: size}); + return acc; + }, [] as Array<ConfigInput>), + }, + ], + }, + { + type: 'array', + name: 'array', + title: 'Array Properties', + buttonText: 'Add new', + arrayType: 'object', + properties: [ + { + type: 'text', + name: 'text', + title: 'Property', + }, + { + type: 'boolean', + name: 'boolean', + title: 'Property', + }, + ], + }, + ], +}; + +export const exampleConfig: BlockConfig = { + name: 'Slider Block', + inputs: [ + { + type: 'text', + name: 'title', + title: 'Text Property', + }, + { + type: 'boolean', + name: '<ID>', + title: 'Boolean Property', + }, + { + type: 'number', + name: '<ID>', + title: 'Number Property', + }, + { + type: 'textarea', + name: '<ID>', + title: 'TextArea Property', + showIf: `block.description === 'Test'`, + }, + { + type: 'select', + name: 'select', + title: 'Select Property', + enum: [ + {content: 'Option 1', value: 'option-1'}, + {content: 'Option 2', value: 'option-2'}, + ], + mode: 'single', + view: 'select', + }, + { + type: 'object', + name: 'object_data', + title: 'Object Property', + properties: [ + { + type: 'text', + name: 'text', + title: 'Property', + }, + { + type: 'boolean', + name: 'boolean', + title: 'Property', + }, + ], + }, + { + type: 'array', + name: 'array', + title: 'Array Properties', + buttonText: 'Add new', + arrayType: 'object', + properties: [ + { + type: 'text', + name: 'text', + title: 'Property', + }, + { + type: 'boolean', + name: 'boolean', + title: 'Property', + }, + ], + }, + ], +}; diff --git a/src/blocks/Slider/index.ts b/src/blocks/Slider/index.ts new file mode 100644 index 000000000..106ad3afe --- /dev/null +++ b/src/blocks/Slider/index.ts @@ -0,0 +1,16 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import SliderBlock from './Slider'; +import {SliderBlock as SliderBlockSchema} from './schema'; + +const SliderBlockConfig = { + component: SliderBlock, + schema: { + name: 'Slider Block', + inputs: generateFromAJV(SliderBlockSchema['slider-block'] as unknown as JSONSchemaType<{}>), + }, +}; + +export default SliderBlockConfig; diff --git a/src/blocks/Table/index.ts b/src/blocks/Table/index.ts new file mode 100644 index 000000000..a7f587811 --- /dev/null +++ b/src/blocks/Table/index.ts @@ -0,0 +1,16 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import TableBlock from './Table'; +import {TableBlock as TableBlockSchema} from './schema'; + +const TableBlockConfig = { + component: TableBlock, + schema: { + name: 'Table Block', + inputs: generateFromAJV(TableBlockSchema['table-block'] as unknown as JSONSchemaType<{}>), + }, +}; + +export default TableBlockConfig; diff --git a/src/blocks/Table/schema.ts b/src/blocks/Table/schema.ts index a6b1ecd2e..30fc6765e 100644 --- a/src/blocks/Table/schema.ts +++ b/src/blocks/Table/schema.ts @@ -11,6 +11,7 @@ export const TableBlock = { contentType: 'text', }, table: { + type: 'object', additionalProperties: false, required: ['content'], properties: { diff --git a/src/blocks/Tabs/Tabs.tsx b/src/blocks/Tabs/Tabs.tsx index 889b6215d..347b273d4 100644 --- a/src/blocks/Tabs/Tabs.tsx +++ b/src/blocks/Tabs/Tabs.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs'; import Title from '../../components/Title/Title'; -import {Col, GridJustifyContent, Row} from '../../grid'; +import {Col, Grid, GridJustifyContent, Row} from '../../grid'; import {TabsBlockProps} from '../../models'; import {block} from '../../utils'; import './Tabs.scss'; @@ -53,42 +53,44 @@ export const TabsBlock = ({ ); return ( - <AnimateBlock className={b()} onScroll={() => setPlay(true)} animate={animated}> - <Title - title={title} - subtitle={description} - className={b('title', {centered: centered})} - /> - <Row justifyContent={centered ? GridJustifyContent.Center : undefined}> - <Col sizes={tabsColSizes}> - <ButtonTabs - items={tabs} - onSelectTab={onSelectTab} - activeTab={activeTab} - className={b('tabs', {centered: centered})} - getTabElementId={getTabElementId} - getTabContentElementId={getTabContentElementId} - /> - </Col> - </Row> - {items.map((tabData) => { - const {tabName} = tabData; + <Grid> + <AnimateBlock className={b()} onScroll={() => setPlay(true)} animate={animated}> + <Title + title={title} + subtitle={description} + className={b('title', {centered: centered})} + /> + <Row justifyContent={centered ? GridJustifyContent.Center : undefined}> + <Col sizes={tabsColSizes}> + <ButtonTabs + items={tabs} + onSelectTab={onSelectTab} + activeTab={activeTab} + className={b('tabs', {centered: centered})} + getTabElementId={getTabElementId} + getTabContentElementId={getTabContentElementId} + /> + </Col> + </Row> + {items.map((tabData) => { + const {tabName} = tabData; - return ( - <TabContent - key={tabName} - tabData={tabData} - isActive={tabName === activeTab} - isReverse={isReverse} - contentSize={contentSize} - centered={centered} - play={play} - getTabElementId={getTabElementId} - getTabContentElementId={getTabContentElementId} - /> - ); - })} - </AnimateBlock> + return ( + <TabContent + key={tabName} + tabData={tabData} + isActive={tabName === activeTab} + isReverse={isReverse} + contentSize={contentSize} + centered={centered} + play={play} + getTabElementId={getTabElementId} + getTabContentElementId={getTabContentElementId} + /> + ); + })} + </AnimateBlock> + </Grid> ); }; diff --git a/src/blocks/Tabs/index.ts b/src/blocks/Tabs/index.ts new file mode 100644 index 000000000..4d7be63d1 --- /dev/null +++ b/src/blocks/Tabs/index.ts @@ -0,0 +1,31 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import TabsBlock from './Tabs'; +import {TabsBlock as TabsBlockSchema} from './schema'; + +const TabsBlockConfig = { + component: TabsBlock, + schema: { + name: 'Tabs Block', + inputs: generateFromAJV(TabsBlockSchema['tabs-block'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Tabs Block', + items: [ + { + tabName: 'First Tab', + text: 'First Tab Content', + title: 'First Tab Title', + }, + { + text: 'Second Tab Content', + title: 'Second Tab Title', + tabName: 'Second Tab', + }, + ], + }, + }, +}; + +export default TabsBlockConfig; diff --git a/src/blocks/TestEditorBlock/TestEditorBlock.tsx b/src/blocks/TestEditorBlock/TestEditorBlock.tsx new file mode 100644 index 000000000..7888c5cc1 --- /dev/null +++ b/src/blocks/TestEditorBlock/TestEditorBlock.tsx @@ -0,0 +1,13 @@ +import React, {PropsWithChildren} from 'react'; + +import {block} from '../../utils'; + +const b = block('test-editor-block'); + +export interface TestEditorBlockProps extends PropsWithChildren {} + +export const TestEditorBlock = (props: TestEditorBlockProps) => { + return <div className={b()}>{JSON.stringify(props, null, 2)}</div>; +}; + +export default TestEditorBlock; diff --git a/src/blocks/TestEditorBlock/form.ts b/src/blocks/TestEditorBlock/form.ts new file mode 100644 index 000000000..838eacb06 --- /dev/null +++ b/src/blocks/TestEditorBlock/form.ts @@ -0,0 +1,114 @@ +import { + ArrayObjectInput, + ArrayTextInput, + BooleanInput, + NumberInput, + ObjectInput, + OneOfInput, + SelectMultipleInput, + SelectSingleInput, + TextAreaInput, + TextInput, +} from '../../common/types'; + +const textInput: TextInput = { + type: 'text', + name: 'text', + title: 'Text Input', +}; + +const textAreaInput: TextAreaInput = { + type: 'textarea', + name: 'textarea', + title: 'TextArea Input', +}; + +const booleanInput: BooleanInput = { + type: 'boolean', + name: 'boolean', + title: 'Boolean Input', +}; + +const numberInput: NumberInput = { + type: 'number', + name: 'number', + title: 'Number Input', +}; + +const selectInput: SelectSingleInput = { + type: 'select', + name: 'selectSingle', + title: 'Select Single Input', + mode: 'single', + view: 'select', + enum: [ + {value: 'id_1', content: 'Option 1'}, + {value: 'id_2', content: 'Option 2'}, + ], +}; + +const radioButtonsViewSingleInput: SelectSingleInput = { + ...selectInput, + name: 'radioButtons', + title: 'Radio Button Input', + + view: 'radiobutton', +}; + +// @ts-ignore +const selectMultipleModeInput: SelectMultipleInput = { + ...selectInput, + name: 'selectMultiple', + title: 'Select Multiple Input', + + mode: 'multiple', +}; + +const objectInput: ObjectInput = { + type: 'object', + name: 'object', + title: 'Object Input', + properties: [textInput, textAreaInput, selectInput], +}; + +const arrayTextInput: ArrayTextInput = { + type: 'array', + name: 'arrayText', + title: 'Array Text Input', + buttonText: 'Add Array Item', + arrayType: 'text', +}; + +const arrayObjectInput: ArrayObjectInput = { + type: 'array', + name: 'arrayObject', + title: 'Array Object Input', + buttonText: 'Add Array Item', + arrayType: 'object', + properties: [textInput, textAreaInput, selectInput], +}; + +const oneOfInput: OneOfInput = { + type: 'oneOf', + name: 'oneOf', + key: 'oneOfKey', + title: 'Array Text Input', + options: [ + {value: 'text', title: 'Text', properties: [textInput]}, + {value: 'textarea', title: 'TextArea', properties: [textAreaInput]}, + ], +}; + +export default [ + textInput, + textAreaInput, + booleanInput, + numberInput, + selectInput, + radioButtonsViewSingleInput, + selectMultipleModeInput, + objectInput, + arrayTextInput, + arrayObjectInput, + oneOfInput, +]; diff --git a/src/blocks/TestEditorBlock/index.ts b/src/blocks/TestEditorBlock/index.ts new file mode 100644 index 000000000..b999fdf7e --- /dev/null +++ b/src/blocks/TestEditorBlock/index.ts @@ -0,0 +1,16 @@ +import TestEditorBlock from './TestEditorBlock'; +import testEditorBlockInputs from './form'; + +const TestEditorBlockConfig = { + component: TestEditorBlock, + schema: { + name: 'Test Editor Block', + inputs: testEditorBlockInputs, + }, +}; + +export const TestEditorBlockSchema = { + ['test-editor-block']: {}, +}; + +export default TestEditorBlockConfig; diff --git a/src/common/hooks/usePostMessage.tsx b/src/common/hooks/usePostMessage.tsx new file mode 100644 index 000000000..8b3d03259 --- /dev/null +++ b/src/common/hooks/usePostMessage.tsx @@ -0,0 +1,134 @@ +import {useCallback, useEffect} from 'react'; + +import { + Action, + Meta, + MetaSource, + PostMessageArgs, + SendOptions, + Subscriber, + WithStoreReducer, +} from '../../common/types'; + +interface UsePostMessageProps { + subscribers: Subscriber[]; + storesWithReducer: WithStoreReducer[]; + targetIframeElement?: HTMLIFrameElement; + urlOrigin?: string; +} + +// TODO: enable/disable via env variables +const DEBUG_MODE = false; + +export const usePostMessage = (props: UsePostMessageProps) => { + const {subscribers, storesWithReducer, targetIframeElement, urlOrigin} = props; + + const metaSource: MetaSource = targetIframeElement ? 'editor' : 'pc'; + const receiveMetaSource: MetaSource = targetIframeElement ? 'pc' : 'editor'; + + const notifySubscribers = useCallback( + (action: Action, source: MetaSource) => { + const meta: Meta = {source}; + + const {type, payload} = action; + + if (!type) { + return; + } + + if (subscribers) { + const foundedSubscribers = subscribers.filter( + (subscriber) => subscriber.action === type, + ); + + for (const storeWithReducer of storesWithReducer) { + storeWithReducer.reducer(action, meta); + } + + if (foundedSubscribers.length) { + for (const subscriber of foundedSubscribers) { + subscriber.handler(payload, meta); + } + } + } + }, + [storesWithReducer, subscribers], + ); + + const sendPostMessage = useCallback( + (args: PostMessageArgs) => { + if (targetIframeElement && targetIframeElement.contentWindow) { + if (targetIframeElement.contentWindow) { + targetIframeElement.contentWindow.postMessage(args, '*'); + } else { + // eslint-disable-next-line no-console + console.error('No Iframe element in Editor'); + } + } else { + window.parent.postMessage(args, '*'); + } + }, + [targetIframeElement], + ); + + const sendMessage = useCallback( + (action: Action, {debug = DEBUG_MODE, direction = 'both'}: SendOptions = {}) => { + if (debug) { + if (metaSource === 'editor') { + // eslint-disable-next-line no-console + console.log(`🔵 Editor ➡️ ${action.type}`, action.payload); + } + + if (metaSource === 'pc') { + // eslint-disable-next-line no-console + console.log(`🟢 Website ➡️ ${action.type}`, action.payload); + } + } + + if (direction === 'both') { + sendPostMessage({action, debug}); + notifySubscribers(action, metaSource); + } else if (direction === metaSource) { + notifySubscribers(action, metaSource); + } else { + sendPostMessage({action, debug}); + } + }, + [metaSource, notifySubscribers, sendPostMessage], + ); + + useEffect(() => { + const onMessage = (e: MessageEvent) => { + const {action, debug}: PostMessageArgs = e.data; + + if (urlOrigin && e.origin !== urlOrigin) { + return; + } + + if (action && action.type) { + if (debug) { + if (metaSource === 'editor') { + // eslint-disable-next-line no-console + console.log(`🔵 ➡️ Editor ${action.type}`, action.payload); + } + + if (metaSource === 'pc') { + // eslint-disable-next-line no-console + console.log(`🟢 ➡️ Website ${action.type}`, action.payload); + } + } + notifySubscribers(action, receiveMetaSource); + } + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; + }, [metaSource, notifySubscribers, receiveMetaSource, targetIframeElement, urlOrigin]); + + return { + sendMessage, + }; +}; diff --git a/src/common/types/actions/codes.ts b/src/common/types/actions/codes.ts new file mode 100644 index 000000000..3ec1a5935 --- /dev/null +++ b/src/common/types/actions/codes.ts @@ -0,0 +1,27 @@ +export enum ActionTypes { + // Other Events + UpdateConfigs = 'UPDATE_CONFIGS', + SetHeight = 'SET_HEIGHT', + + // Initial Events + IframeReady = 'IFRAME_READY', + EditorReady = 'EDITOR_READY', + BlocksConfigs = 'BLOCKS_CONFIGS', + + // Insert Events + InsertBlock = 'INSERT_BLOCK', + InsertModeEnable = 'INSERT_MODE_ENABLE', + InsertModeDisable = 'INSERT_MODE_DISABLE', + + // Reorder Events + ReorderBlocks = 'REORDER_BLOCKS', + ReorderModeEnable = 'REORDER_MODE_ENABLE', + ReorderModeDisable = 'REORDER_MODE_DISABLE', + + // Overlay Mode + OverlayModeOnMove = 'OVERLAY_MODE_ON_MOVE', + + // Select Events + SelectBlock = 'SELECT_BLOCK', + UpdateSelectedBlockRect = 'UPDATE_SELECTED_BLOCK_RECT', +} diff --git a/src/common/types/actions/index.ts b/src/common/types/actions/index.ts new file mode 100644 index 000000000..2c7da200a --- /dev/null +++ b/src/common/types/actions/index.ts @@ -0,0 +1,31 @@ +import {InitialActions} from './initial'; +import {InsertActions} from './insert'; +import {OtherActions} from './other'; +import {OverlayActions} from './overlay'; +import {ReorderActions} from './reorder'; +import {SelectActions} from './select'; + +export type BaseAction<T extends string = string> = { + type: T; +}; + +export interface UnknownAction extends BaseAction { + // Allows any extra properties to be defined in an action. + [extraProps: string]: unknown; +} + +export type Action = + | InitialActions + | InsertActions + | OverlayActions + | ReorderActions + | SelectActions + | OtherActions; + +export * from './codes'; +export * from './initial'; +export * from './insert'; +export * from './other'; +export * from './overlay'; +export * from './reorder'; +export * from './select'; diff --git a/src/common/types/actions/initial.ts b/src/common/types/actions/initial.ts new file mode 100644 index 000000000..b313c3a11 --- /dev/null +++ b/src/common/types/actions/initial.ts @@ -0,0 +1,29 @@ +import {ConfigInput} from '../../../common/types'; +import {ItemConfig} from '../index'; + +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface IframeReadyAction extends UnknownAction { + type: ActionTypes.IframeReady; + payload: { + height: number; + }; +} + +export interface EditorReadyAction extends UnknownAction { + type: ActionTypes.EditorReady; + payload: unknown; +} + +export interface BlocksConfigsAction extends UnknownAction { + type: ActionTypes.BlocksConfigs; + payload: { + blocks: ItemConfig[]; + subBlocks: ItemConfig[]; + global: ConfigInput[]; + }; +} + +export type InitialActions = BlocksConfigsAction | IframeReadyAction | EditorReadyAction; diff --git a/src/common/types/actions/insert.ts b/src/common/types/actions/insert.ts new file mode 100644 index 000000000..de88f33aa --- /dev/null +++ b/src/common/types/actions/insert.ts @@ -0,0 +1,24 @@ +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface InsertBlockAction extends UnknownAction { + type: ActionTypes.InsertBlock; + payload: { + path: number[]; + }; +} + +export interface InsertModeEnableAction extends UnknownAction { + type: ActionTypes.InsertModeEnable; + payload: { + blockType: string; + }; +} + +export interface InsertModeDisableAction extends UnknownAction { + type: ActionTypes.InsertModeDisable; + payload: undefined; +} + +export type InsertActions = InsertBlockAction | InsertModeEnableAction | InsertModeDisableAction; diff --git a/src/common/types/actions/other.ts b/src/common/types/actions/other.ts new file mode 100644 index 000000000..1f10e9217 --- /dev/null +++ b/src/common/types/actions/other.ts @@ -0,0 +1,21 @@ +import {PageContent} from '../../../models'; + +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface UpdateConfigsAction extends UnknownAction { + type: ActionTypes.UpdateConfigs; + payload: { + content: PageContent; + }; +} + +export interface SetHeightAction extends UnknownAction { + type: ActionTypes.SetHeight; + payload: { + height: number; + }; +} + +export type OtherActions = UpdateConfigsAction | SetHeightAction; diff --git a/src/common/types/actions/overlay.ts b/src/common/types/actions/overlay.ts new file mode 100644 index 000000000..dd5340dd2 --- /dev/null +++ b/src/common/types/actions/overlay.ts @@ -0,0 +1,16 @@ +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface OverlayModeOnMoveAction extends UnknownAction { + type: ActionTypes.OverlayModeOnMove; + payload: { + block?: { + rect: DOMRect; + cursorPosition: 'top' | 'bottom' | 'left' | 'right'; + }; + cursor: {x: number; y: number}; + }; +} + +export type OverlayActions = OverlayModeOnMoveAction; diff --git a/src/common/types/actions/reorder.ts b/src/common/types/actions/reorder.ts new file mode 100644 index 000000000..e49cc2c04 --- /dev/null +++ b/src/common/types/actions/reorder.ts @@ -0,0 +1,27 @@ +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface ReorderModeEnableAction extends UnknownAction { + type: ActionTypes.ReorderModeEnable; + payload: { + path: number[]; + }; +} + +export interface ReorderModeDisableAction extends UnknownAction { + type: ActionTypes.ReorderModeDisable; + payload: undefined; +} + +export interface ReorderBlocksAction extends UnknownAction { + type: ActionTypes.ReorderBlocks; + payload: { + path: number[]; + }; +} + +export type ReorderActions = + | ReorderBlocksAction + | ReorderModeEnableAction + | ReorderModeDisableAction; diff --git a/src/common/types/actions/select.ts b/src/common/types/actions/select.ts new file mode 100644 index 000000000..cf451ab84 --- /dev/null +++ b/src/common/types/actions/select.ts @@ -0,0 +1,20 @@ +import {ActionTypes} from './codes'; + +import {UnknownAction} from './index'; + +export interface SelectBlockAction extends UnknownAction { + type: ActionTypes.SelectBlock; + payload: { + path: number[]; + rect: DOMRect; + }; +} + +export interface UpdateSelectedBlockRectAction extends UnknownAction { + type: ActionTypes.UpdateSelectedBlockRect; + payload: { + rect: DOMRect; + }; +} + +export type SelectActions = SelectBlockAction | UpdateSelectedBlockRectAction; diff --git a/src/common/types/common.ts b/src/common/types/common.ts new file mode 100644 index 000000000..ce344e43f --- /dev/null +++ b/src/common/types/common.ts @@ -0,0 +1,17 @@ +import {Action} from './actions'; +import {BlockConfig} from './forms'; +import {Meta} from './messages'; + +export interface ItemConfig { + type: string; + schema: BlockConfig; +} + +export interface WithStoreReducer { + reducer: (action: Action, meta: Meta) => void; +} + +export type Subscriber<A extends Action = Action> = { + action: A['type']; + handler: (payload: A['payload'], meta: Meta) => void; +}; diff --git a/src/common/types/forms.ts b/src/common/types/forms.ts new file mode 100644 index 000000000..8ade3e18a --- /dev/null +++ b/src/common/types/forms.ts @@ -0,0 +1,110 @@ +export interface BlockConfig { + name: string; + inputs: Array<ConfigInput>; + default?: object; +} + +export interface TextInput { + type: 'text'; + name: string; + title: string; +} + +export interface BooleanInput { + type: 'boolean'; + name: string; + title: string; +} + +export interface NumberInput { + type: 'number'; + name: string; + title: string; +} + +export interface TextAreaInput { + type: 'textarea'; + name: string; + title: string; +} + +export interface SelectBaseInput { + type: 'select'; + name: string; + title: string; + view: 'select' | 'radiobutton'; + mode: 'single' | 'multiple'; + enum: Array<{content: string; value: string}>; +} + +export interface SelectSingleInput extends SelectBaseInput { + type: 'select'; + name: string; + title: string; + view: 'select' | 'radiobutton'; + mode: 'single'; + enum: Array<{content: string; value: string}>; +} + +export interface SelectMultipleInput extends SelectBaseInput { + type: 'select'; + name: string; + title: string; + view: 'select'; + mode: 'multiple'; + enum: Array<{content: string; value: string}>; +} + +export interface ObjectInput { + type: 'object'; + name: string; + title: string; + properties: Array<ConfigInput>; +} + +export interface ArrayBaseInput { + type: 'array'; + arrayType: 'object' | 'text'; + name: string; + title: string; + buttonText: string; +} + +export interface ArrayTextInput extends ArrayBaseInput { + arrayType: 'text'; +} + +export interface ArrayObjectInput extends ArrayBaseInput { + arrayType: 'object'; + properties: Array<ConfigInput>; +} + +export interface OneOfInput { + type: 'oneOf'; + name: string; + key?: string; + title: string; + options: { + value: string; + title: string; + properties: Array<ConfigInput>; + }[]; +} + +export interface GeneralProps { + showIf?: string; +} + +export type ConfigInput = ( + | TextInput + | BooleanInput + | NumberInput + | TextAreaInput + | SelectSingleInput + | SelectMultipleInput + | ObjectInput + | ArrayTextInput + | ArrayObjectInput + | OneOfInput +) & + GeneralProps; diff --git a/src/common/types/index.ts b/src/common/types/index.ts new file mode 100644 index 000000000..31c2745fd --- /dev/null +++ b/src/common/types/index.ts @@ -0,0 +1,4 @@ +export * from './actions'; +export * from './common'; +export * from './forms'; +export * from './messages'; diff --git a/src/common/types/messages.ts b/src/common/types/messages.ts new file mode 100644 index 000000000..b2a905b8e --- /dev/null +++ b/src/common/types/messages.ts @@ -0,0 +1,22 @@ +import {Action} from './actions'; + +export interface SendOptions { + direction?: MetaSource | 'both'; + debug?: boolean; +} + +export interface PostMessageArgs { + action: Action; + debug: boolean; +} + +export type SubscriptionFunc = <A extends Action>( + type: A['type'], + payloadCallback: (payload: A['payload'], meta: Meta) => void, +) => void; + +export type MetaSource = 'pc' | 'editor'; + +export interface Meta { + source: MetaSource; +} diff --git a/src/components/BlockBase/BlockBase.scss b/src/components/BlockBase/BlockBase.scss index 01221dfc3..88c2fc656 100644 --- a/src/components/BlockBase/BlockBase.scss +++ b/src/components/BlockBase/BlockBase.scss @@ -14,12 +14,12 @@ $block: '.#{$ns}block-base'; @include add-specificity(&) { @media only screen and (max-width: map-get($gridBreakpoints, 'sm')) { - margin-top: $indentM; + padding-top: $indentM; padding-bottom: $indentM; - &:first-child { - margin-top: var(--pc-first-block-mobile-indent, #{$indentXL}); - } + //&:first-child { + // padding-top: var(--pc-first-block-mobile-indent, #{$indentXL}); + //} } } diff --git a/src/components/Image/dynamic-form.ts b/src/components/Image/dynamic-form.ts new file mode 100644 index 000000000..f5f8de747 --- /dev/null +++ b/src/components/Image/dynamic-form.ts @@ -0,0 +1,87 @@ +import _ from 'lodash'; + +import {ConfigInput} from '../../common/types'; + +const devices = ['desktop', 'tablet', 'mobile']; + +const imageBaseInputs: ConfigInput[] = [ + { + type: 'text', + name: 'alt', + title: 'Alternative', + }, + { + type: 'boolean', + name: 'disableCompress', + title: 'Disable Compress', + }, +]; + +const imageStyleInputs: ConfigInput[] = [ + { + type: 'text', + name: 'style.backgroundColor', + title: 'Background Color', + }, + { + type: 'text', + name: 'style.height', + title: 'Height', + }, + { + type: 'text', + name: 'style.width', + title: 'Width', + }, + { + type: 'text', + name: 'style.color', + title: 'Color', + }, +]; + +const devicesInputs: ConfigInput[] = devices.map((device) => ({ + type: 'text', + title: _.capitalize(device), + name: `${device}`, +})); + +export const imageInputs: ConfigInput[] = [ + { + type: 'oneOf', + name: '', + key: 'imageType', + title: 'Image Type', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'text', + name: '', // image props + title: 'Image URL', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: [ + { + type: 'text', + name: 'src', + title: 'Source', + }, + ...imageStyleInputs, + ...imageBaseInputs, + ], + }, + { + title: 'Device Based', + value: 'deviseBased', + properties: [...devicesInputs, ...imageBaseInputs], + }, + ], + }, +]; diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.scss b/src/components/editor/ChildrenWrap/ChildrenWrap.scss new file mode 100644 index 000000000..c583e86a8 --- /dev/null +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.scss @@ -0,0 +1,8 @@ +@import '../../../../styles/mixins.scss'; +@import '../../../../styles/variables.scss'; + +$block: '.#{$ns}children-wrap'; + +#{$block} { + min-height: 50px; +} diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx new file mode 100644 index 000000000..1eb77dca3 --- /dev/null +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -0,0 +1,27 @@ +import React, {PropsWithChildren, ReactNode, useContext} from 'react'; + +import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; +import {BlockIdContext} from '../../../context/blockIdContext'; +import {block} from '../../../utils'; + +import './ChildrenWrap.scss'; + +const b = block('children-wrap'); + +export interface ChildrenWrapProps extends PropsWithChildren { + checkChildren?: ReactNode; +} + +const ChildrenWrap = (props: ChildrenWrapProps) => { + const {children} = props; + const parentBlockId = useContext(BlockIdContext); + const {onMouseUp, onMouseMove} = useEditorBlockMouseEvents([parentBlockId, 0]); + + return ( + <div className={b()} onMouseMove={onMouseMove} onMouseUp={onMouseUp}> + {children} + </div> + ); +}; + +export default ChildrenWrap; diff --git a/src/components/editor/ItemWrap/ItemWrap.scss b/src/components/editor/ItemWrap/ItemWrap.scss new file mode 100644 index 000000000..40bbfad31 --- /dev/null +++ b/src/components/editor/ItemWrap/ItemWrap.scss @@ -0,0 +1,12 @@ +@import '../../../../styles/mixins.scss'; +@import '../../../../styles/variables.scss'; + +$block: '.#{$ns}item-wrap'; + +#{$block} { + height: 100%; +} + +.pc-page-constructor_with-editor #{$block} { + cursor: pointer; +} diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx new file mode 100644 index 000000000..4c7842002 --- /dev/null +++ b/src/components/editor/ItemWrap/ItemWrap.tsx @@ -0,0 +1,27 @@ +import React, {PropsWithChildren, useContext} from 'react'; + +import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; +import {BlockIdContext} from '../../../context/blockIdContext'; +import {block} from '../../../utils'; + +import './ItemWrap.scss'; + +const b = block('item-wrap'); + +export interface ItemWrapProps extends PropsWithChildren { + index: number; +} + +const ItemWrap = (props: ItemWrapProps) => { + const {children, index} = props; + const parentBlockId = useContext(BlockIdContext); + const adminBlockMouseEvents = useEditorBlockMouseEvents([parentBlockId, index]); + + return ( + <div className={b()} {...adminBlockMouseEvents}> + {children} + </div> + ); +}; + +export default ItemWrap; diff --git a/src/constructor-items.ts b/src/constructor-items.ts index 6416046ad..d11730fda 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -1,3 +1,5 @@ +import React from 'react'; + import { BannerBlock, CardLayoutBlock, @@ -20,6 +22,28 @@ import { TableBlock, TabsBlock, } from './blocks'; +import BannerBlockConfig from './blocks/Banner'; +import CardLayoutBlockConfig from './blocks/CardLayout'; +import CompaniesBlockConfig from './blocks/Companies'; +import ContentLayoutBlockConfig from './blocks/ContentLayout'; +import ExtendedFeaturesBlockConfig from './blocks/ExtendedFeatures'; +import FilterBlockConfig from './blocks/FilterBlock'; +import FormBlockConfig from './blocks/Form'; +import HeaderBlockConfig from './blocks/Header'; +import HeaderSliderBlockConfig from './blocks/HeaderSlider'; +import IconsBlockConfig from './blocks/Icons'; +import InfoBlockConfig from './blocks/Info'; +import MapBlockConfig from './blocks/Map'; +import MediaBlockConfig from './blocks/Media'; +import PromoFeaturesBlockConfig from './blocks/PromoFeaturesBlock'; +import QuestionsBlockConfig from './blocks/Questions'; +import ShareBlockConfig from './blocks/Share'; +import SliderBlockConfig from './blocks/Slider'; +import TableBlockConfig from './blocks/Table'; +import TabsBlockConfig from './blocks/Tabs'; +import TestEditorBlockConfig from './blocks/TestEditorBlock'; +import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock'; +import {ConfigInput} from './common/types'; import {SliderNewBlock} from './blocks/unstable'; import {BlockType, NavigationItemType, SubBlockType} from './models'; import { @@ -42,7 +66,21 @@ import { PriceDetailed, Quote, } from './sub-blocks'; +import BackgroundCardConfig from './sub-blocks/BackgroundCard'; +import BannerCardConfig from './sub-blocks/BannerCard'; +import BasicCardConfig from './sub-blocks/BasicCard'; +import ContentConfig from './sub-blocks/Content'; +import DividerConfig from './sub-blocks/Divider'; +import ImageCardConfig from './sub-blocks/ImageCard'; +import LayoutItemConfig from './sub-blocks/LayoutItem/form'; +import MediaCardConfig from './sub-blocks/MediaCard'; +import PriceCardConfig from './sub-blocks/PriceCard'; +import PriceDetailedConfig from './sub-blocks/PriceDetailed'; +import QuoteConfig from './sub-blocks/Quote'; +/** + * @deprecated use blockDataMap + **/ export const blockMap = { [BlockType.SliderBlock]: SliderBlock, [BlockType.ExtendedFeaturesBlock]: ExtendedFeaturesBlock, @@ -66,8 +104,12 @@ export const blockMap = { [BlockType.FormBlock]: FormBlock, // unstable [BlockType.SliderNewBlock]: SliderNewBlock, + [BlockType.TestEditorBlock]: TestEditorBlock, }; +/** + * @deprecated use blockDataMap + **/ export const subBlockMap = { [SubBlockType.Divider]: Divider, [SubBlockType.PriceDetailed]: PriceDetailed, @@ -89,3 +131,49 @@ export const navItemMap = { [NavigationItemType.Link]: NavigationLink, [NavigationItemType.GithubButton]: GithubButton, }; + +export interface BlockData { + // TODO: remove any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: React.ComponentType<any>; + schema: { + name: string; + inputs: ConfigInput[]; + default?: object; + }; +} + +export const blockDataMap: Record<string, BlockData> = { + [BlockType.ExtendedFeaturesBlock]: ExtendedFeaturesBlockConfig, + [BlockType.PromoFeaturesBlock]: PromoFeaturesBlockConfig, + [BlockType.QuestionsBlock]: QuestionsBlockConfig, + [BlockType.BannerBlock]: BannerBlockConfig, + [BlockType.CompaniesBlock]: CompaniesBlockConfig, + [BlockType.MediaBlock]: MediaBlockConfig, + [BlockType.InfoBlock]: InfoBlockConfig, + [BlockType.TableBlock]: TableBlockConfig, + [BlockType.TabsBlock]: TabsBlockConfig, + [BlockType.HeaderBlock]: HeaderBlockConfig, + [BlockType.IconsBlock]: IconsBlockConfig, + [BlockType.HeaderSliderBlock]: HeaderSliderBlockConfig, + [BlockType.CardLayoutBlock]: CardLayoutBlockConfig, + [BlockType.ContentLayoutBlock]: ContentLayoutBlockConfig, + [BlockType.ShareBlock]: ShareBlockConfig, + [BlockType.MapBlock]: MapBlockConfig, + [BlockType.FilterBlock]: FilterBlockConfig, + [BlockType.FormBlock]: FormBlockConfig, + [BlockType.TestEditorBlock]: TestEditorBlockConfig, + [BlockType.SliderBlock]: SliderBlockConfig, + + [SubBlockType.Divider]: DividerConfig, + [SubBlockType.PriceDetailed]: PriceDetailedConfig, + [SubBlockType.MediaCard]: MediaCardConfig, + [SubBlockType.BannerCard]: BannerCardConfig, + [SubBlockType.LayoutItem]: LayoutItemConfig, + [SubBlockType.BackgroundCard]: BackgroundCardConfig, + [SubBlockType.BasicCard]: BasicCardConfig, + [SubBlockType.Content]: ContentConfig, + [SubBlockType.Quote]: QuoteConfig, + [SubBlockType.PriceCard]: PriceCardConfig, + [SubBlockType.ImageCard]: ImageCardConfig, +}; diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index ccb8a8a24..801c6e554 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -1,18 +1,19 @@ -import * as React from 'react'; +import {ReactNode, useContext, useMemo, useState} from 'react'; -import '@diplodoc/transform/dist/js/yfm.js'; +import '@diplodoc/transform/dist/js/yfm'; +import {ThemeProvider} from '@gravity-ui/uikit'; import BackgroundMedia from '../../components/BackgroundMedia/BackgroundMedia'; import BrandFooter from '../../components/BrandFooter/BrandFooter'; import RootCn from '../../components/RootCn'; import {blockMap, navItemMap, subBlockMap} from '../../constructor-items'; import {AnimateContext} from '../../context/animateContext'; +import {useEditorStore} from '../../context/editorContext'; import {InnerContext} from '../../context/innerContext'; import {ProjectSettingsContext} from '../../context/projectSettingsContext'; import {useTheme} from '../../context/theme'; -import {Grid} from '../../grid'; +import useEditorInitialize from '../../hooks/useEditorInitialize'; import { - BlockType, BlockTypes, CustomConfig, CustomItems, @@ -24,17 +25,9 @@ import { SubBlockTypes, } from '../../models'; import Layout from '../../navigation/containers/Layout/Layout'; -import { - block as cnBlock, - getCustomItems, - getCustomTypes, - getHeaderBlock, - getOrderedBlocks, - getThemedValue, -} from '../../utils'; - -import {ConstructorBlocks} from './components/ConstructorBlocks'; -import {ConstructorHeader} from './components/ConstructorItem'; +import {block as cnBlock, getCustomItems, getCustomTypes, getThemedValue} from '../../utils'; + +import {ConstructorBlocks} from './components'; import {ConstructorRow} from './components/ConstructorRow'; import './PageConstructor.scss'; @@ -49,7 +42,7 @@ export interface PageConstructorProps { content?: PageContent; shouldRenderBlock?: ShouldRenderBlock; custom?: CustomConfig; - renderMenu?: () => React.ReactNode; + renderMenu?: () => ReactNode; navigation?: NavigationData; isBranded?: boolean; microdata?: { @@ -68,7 +61,13 @@ export const Constructor = (props: PageConstructorProps) => { microdata, } = props; - const {context} = React.useMemo( + const [content, setContent] = useState<PageContent>({blocks, background}); + const theme = useTheme(); + + const {initialized} = useEditorStore(); + useEditorInitialize({content, setContent}); + + const {context} = useMemo( () => ({ context: { blockTypes: [...BlockTypes, ...getCustomTypes(['blocks', 'headers'], custom)], @@ -98,31 +97,23 @@ export const Constructor = (props: PageConstructorProps) => { [custom, shouldRenderBlock, microdata], ); - const theme = useTheme(); - - const header = getHeaderBlock(blocks, context.headerBlockTypes); - const restBlocks = getOrderedBlocks(blocks, context.headerBlockTypes); - const themedBackground = getThemedValue(background, theme); + const restBlocks = content.blocks; + const themedBackground = getThemedValue(content.background, theme); return ( <InnerContext.Provider value={context}> - <RootCn className={b()}> + <RootCn className={b('', {['with-editor']: initialized})}> <div className={b('wrapper')}> {themedBackground && ( <BackgroundMedia {...themedBackground} className={b('background')} /> )} <Layout navigation={navigation}> {renderMenu && renderMenu()} - {header && ( - <ConstructorHeader data={header} blockKey={BlockType.HeaderBlock} /> + {restBlocks && ( + <ConstructorRow> + <ConstructorBlocks items={restBlocks} /> + </ConstructorRow> )} - <Grid> - {restBlocks && ( - <ConstructorRow> - <ConstructorBlocks items={restBlocks} /> - </ConstructorRow> - )} - </Grid> </Layout> {isBranded && <BrandFooter />} </div> @@ -132,12 +123,14 @@ export const Constructor = (props: PageConstructorProps) => { }; export const PageConstructor = (props: PageConstructorProps) => { - const {isAnimationEnabled = true} = React.useContext(ProjectSettingsContext); + const {isAnimationEnabled = true} = useContext(ProjectSettingsContext); const {content: {animated = isAnimationEnabled} = {}, ...rest} = props; return ( - <AnimateContext.Provider value={{animated}}> - <Constructor content={props.content} {...rest} /> - </AnimateContext.Provider> + <ThemeProvider> + <AnimateContext.Provider value={{animated}}> + <Constructor content={props.content} {...rest} /> + </AnimateContext.Provider> + </ThemeProvider> ); }; diff --git a/src/containers/PageConstructor/Provider.tsx b/src/containers/PageConstructor/Provider.tsx index 3df2de962..0d99ddbe2 100644 --- a/src/containers/PageConstructor/Provider.tsx +++ b/src/containers/PageConstructor/Provider.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import {DEFAULT_THEME} from '../../components/constants'; import {AnalyticsContext, AnalyticsContextProps} from '../../context/analyticsContext'; +import {EditorProvider} from '../../context/editorContext'; import { DEFAULT_FORMS_CONTEXT_VALUE, FormsContext, @@ -11,6 +12,7 @@ import {ImageContext, ImageContextProps} from '../../context/imageContext'; import {LocaleContext, LocaleContextProps} from '../../context/localeContext'; import {LocationContext, LocationContextProps} from '../../context/locationContext'; import {MapsContext, MapsContextType, initialMapValue} from '../../context/mapsContext/mapsContext'; +import {PostMessageProvider} from '../../context/messagesContext'; import {MobileContext} from '../../context/mobileContext'; import { ProjectSettingsContext, @@ -62,6 +64,8 @@ export const PageConstructorProvider = ( <AnalyticsContext.Provider value={analytics} />, <FormsContext.Provider value={forms} />, <SSRContext.Provider value={{isServer: ssrConfig?.isServer}} />, + <EditorProvider />, + <PostMessageProvider />, ].reduceRight((prev, provider) => React.cloneElement(provider, {}, prev), children); /* eslint-enable react/jsx-key */ diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss index b6c7cbc86..3f49e0086 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss @@ -6,3 +6,7 @@ $block: '.#{$ns}constructor-block'; #{$block} { @include indents(&); } + +.pc-page-constructor_with-editor #{$block} { + cursor: pointer; +} diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index b41b5bc18..6f6697754 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -7,6 +7,8 @@ import {BlockDecoration} from '../../../../customization/BlockDecoration'; import {BlockDecorationProps, ConstructorBlock as ConstructorBlockType} from '../../../../models'; import {block} from '../../../../utils'; +import useEditorBlockMouseEvents from './hooks/useEditorBlockMouseEvents'; + import './ConstructorBlock.scss'; interface ConstructorBlockProps extends Pick<BlockDecorationProps, 'index'> { @@ -20,6 +22,8 @@ export const ConstructorBlock = ({ data, children, }: React.PropsWithChildren<ConstructorBlockProps>) => { + const adminBlockMouseEvents = useEditorBlockMouseEvents([index]); + const {type} = data; const blockBaseProps = React.useMemo( () => pick(data, ['anchor', 'visible', 'resetPaddings', 'indent']), @@ -27,10 +31,12 @@ export const ConstructorBlock = ({ ); return ( - <BlockDecoration type={type} index={index} {...blockBaseProps}> - <BlockBase className={b({type})} {...blockBaseProps}> - {children} - </BlockBase> - </BlockDecoration> + <div {...adminBlockMouseEvents}> + <BlockDecoration type={type} index={index} {...blockBaseProps}> + <BlockBase className={b({type})} {...blockBaseProps}> + {children} + </BlockBase> + </BlockDecoration> + </div> ); }; diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx new file mode 100644 index 000000000..b0c778771 --- /dev/null +++ b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx @@ -0,0 +1,130 @@ +import React, {useCallback, useContext} from 'react'; + +import {ActionTypes} from '../../../../../common/types/actions'; +import {EditorContext, useEditorStore} from '../../../../../context/editorContext'; +import {useMessageSender} from '../../../../../context/messagesContext/hooks/useMessageSender'; +import {getCursorPositionOverElement} from '../../../../../utils/editor'; + +const useEditorBlockMouseEvents = (arrayIndex: number[]) => { + const sendMessage = useMessageSender(); + const {manipulateOverlayMode} = useEditorStore(); + const {setActiveElement} = useContext(EditorContext); + + const onMouseUp = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (manipulateOverlayMode) { + const rect = event.currentTarget.getClientRects().item(0); + if (rect) { + const position = getCursorPositionOverElement(rect, event); + + let newLastIndex = arrayIndex[arrayIndex.length - 1]; + + if (position === 'right' || position === 'bottom') { + newLastIndex += 1; + } + + const newArrayIndex = [ + ...arrayIndex.slice(0, arrayIndex.length - 1), + newLastIndex, + ]; + + if (manipulateOverlayMode === 'insert') { + sendMessage({ + type: ActionTypes.InsertBlock, + payload: { + path: newArrayIndex, + }, + }); + } else if (manipulateOverlayMode === 'reorder') { + sendMessage({ + type: ActionTypes.ReorderBlocks, + payload: { + path: newArrayIndex, + }, + }); + } + } + } + }, + [arrayIndex, manipulateOverlayMode, sendMessage], + ); + + const onMouseMove = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (manipulateOverlayMode) { + const rect = event.currentTarget.getClientRects().item(0); + if (rect) { + const cursorPosition = getCursorPositionOverElement(rect, event); + sendMessage({ + type: ActionTypes.OverlayModeOnMove, + payload: { + block: {rect, cursorPosition}, + cursor: { + x: event.clientX, + y: event.clientY, + }, + }, + }); + } + } + }, + [manipulateOverlayMode, sendMessage], + ); + + const onMouseLeave = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + if (manipulateOverlayMode) { + const rect = e.currentTarget.getClientRects().item(0); + if (rect) { + sendMessage({ + type: ActionTypes.OverlayModeOnMove, + payload: { + cursor: { + x: e.clientX, + y: e.clientY, + }, + }, + }); + } + } else { + } + }, + [manipulateOverlayMode, sendMessage], + ); + + const onClick = useCallback( + (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation(); + if (!manipulateOverlayMode) { + const element = e.currentTarget; + const rect = element.getClientRects().item(0); + if (rect) { + setActiveElement(element); + + sendMessage({ + type: ActionTypes.SelectBlock, + payload: { + path: arrayIndex, + rect: rect, + }, + }); + } + } + }, + [arrayIndex, manipulateOverlayMode, sendMessage, setActiveElement], + ); + + return { + onClick, + onMouseMove, + onMouseUp, + onMouseLeave, + }; +}; + +export default useEditorBlockMouseEvents; diff --git a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx index 9903e2f88..2c9f43204 100644 --- a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx @@ -20,10 +20,14 @@ export interface ConstructorBlocksProps { } export const ConstructorBlocks: React.FC<ConstructorBlocksProps> = ({items}) => { - const {blockTypes, loadables, itemMap, shouldRenderBlock} = React.useContext(InnerContext); + const {blockTypes, subBlockTypes, loadables, itemMap, shouldRenderBlock} = + React.useContext(InnerContext); + + const allBlocks = [...blockTypes, ...subBlockTypes]; const renderer = ( parentId = '', + withoutConstructorBlockWrapper = false, item: ConstructorBlockType, index: number, ): React.ReactElement | null => { @@ -63,17 +67,17 @@ export const ConstructorBlocks: React.FC<ConstructorBlocksProps> = ({items}) => } else { let children; if ('children' in item && item.children) { - children = (item.children as SubBlock[]).map(renderer.bind(null, blockId)); + children = (item.children as SubBlock[]).map(renderer.bind(null, blockId, true)); } itemElement = ( - <ConstructorItem data={item} key={blockId} blockKey={blockId}> + <ConstructorItem data={item} key={blockId} blockKey={index}> {children} </ConstructorItem> ); } - return blockTypes.includes(item.type) ? ( + return allBlocks.includes(item.type) && !withoutConstructorBlockWrapper ? ( //TODO: replace ConstructorBlock (and delete it) with BlockBase when all // components relying on constructor inner structure like Slider or blog-constructor will be refactored <ConstructorBlock data={item} key={blockId} index={index}> @@ -84,5 +88,5 @@ export const ConstructorBlocks: React.FC<ConstructorBlocksProps> = ({items}) => ); }; - return <React.Fragment>{items.map(renderer.bind(null, ''))}</React.Fragment>; + return <React.Fragment>{items.map(renderer.bind(null, '', false))}</React.Fragment>; }; diff --git a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx index 182f20641..6524dbd10 100644 --- a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx +++ b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx @@ -7,7 +7,7 @@ import {BlockType, ConstructorBlock} from '../../../../models'; export interface ConstructorItemProps { data: ConstructorBlock; - blockKey: string; + blockKey: number; } export const ConstructorItem = ({ @@ -23,7 +23,7 @@ export const ConstructorItem = ({ >; return ( - <BlockIdContext.Provider value={blockKey}> + <BlockIdContext.Provider value={blockKey} key={blockKey}> <Component {...rest}>{children}</Component> </BlockIdContext.Provider> ); diff --git a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx index 4488dd6f2..8d29d4f04 100644 --- a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx +++ b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx @@ -20,7 +20,7 @@ export const ConstructorLoadable = (props: ConstructorLoadableProps) => { >; return ( - <BlockIdContext.Provider value={blockKey} key={blockKey}> + <BlockIdContext.Provider value={Number(blockKey)} key={blockKey}> <Loadable key={blockKey} block={block} diff --git a/src/containers/PageConstructor/components/ConstructorRow/ConstructorRow.tsx b/src/containers/PageConstructor/components/ConstructorRow/ConstructorRow.tsx index d3b7077ec..5173b89ad 100644 --- a/src/containers/PageConstructor/components/ConstructorRow/ConstructorRow.tsx +++ b/src/containers/PageConstructor/components/ConstructorRow/ConstructorRow.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import {Col, Row} from '../../../../grid'; import {block} from '../../../../utils'; import './ConstructorRow.scss'; @@ -8,8 +7,4 @@ import './ConstructorRow.scss'; const b = block('constructor-row'); export const ConstructorRow = ({children}: React.PropsWithChildren<{}>) => - children ? ( - <Row className={b()}> - <Col>{children}</Col> - </Row> - ) : null; + children ? <div className={b()}>{children}</div> : null; diff --git a/src/context/blockIdContext/blockIdContext.ts b/src/context/blockIdContext/blockIdContext.ts index 289ed964d..b621d734c 100644 --- a/src/context/blockIdContext/blockIdContext.ts +++ b/src/context/blockIdContext/blockIdContext.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -export type BlockIdContextProp = string; +export type BlockIdContextProp = number; -export const BlockIdContext = React.createContext<BlockIdContextProp>(''); +export const BlockIdContext = React.createContext<BlockIdContextProp>(NaN); diff --git a/src/context/editorContext/editorContext.ts b/src/context/editorContext/editorContext.ts new file mode 100644 index 000000000..b8ef739ef --- /dev/null +++ b/src/context/editorContext/editorContext.ts @@ -0,0 +1,18 @@ +/** + * Context for managing internal Editor states + * Same exist in Editor + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorStore} from './store'; + +export interface EditorContextProps { + state?: StoreApi<EditorStore>; + activeElement?: HTMLElement; + setActiveElement: (element: HTMLElement) => void; +} + +export const EditorContext = React.createContext<EditorContextProps>({setActiveElement: () => {}}); diff --git a/src/context/editorContext/editorProvider.tsx b/src/context/editorContext/editorProvider.tsx new file mode 100644 index 000000000..4812ee416 --- /dev/null +++ b/src/context/editorContext/editorProvider.tsx @@ -0,0 +1,27 @@ +import React, {PropsWithChildren, useRef, useState} from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorContext} from './editorContext'; +import {EditorStore, createEditorStore} from './store'; + +export const EditorProvider: React.FC<PropsWithChildren> = ({children}) => { + const storeRef = useRef<StoreApi<EditorStore>>(); + const [activeElement, setActiveElement] = useState<HTMLElement | undefined>(); + + if (!storeRef.current) { + storeRef.current = createEditorStore(); + } + + return ( + <EditorContext.Provider + value={{ + state: storeRef.current, + activeElement: activeElement, + setActiveElement: setActiveElement, + }} + > + {children} + </EditorContext.Provider> + ); +}; diff --git a/src/context/editorContext/hooks/useEditorStore.ts b/src/context/editorContext/hooks/useEditorStore.ts new file mode 100644 index 000000000..4f2a346cd --- /dev/null +++ b/src/context/editorContext/hooks/useEditorStore.ts @@ -0,0 +1,17 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {EditorContext} from '../editorContext'; + +export const useEditorStore = () => { + const {state} = useContext(EditorContext); + + if (!state) { + throw new Error('Missing EditorContext'); + } + + return useStore(state); +}; + +export default useEditorStore; diff --git a/src/context/editorContext/index.ts b/src/context/editorContext/index.ts new file mode 100644 index 000000000..c7b944eea --- /dev/null +++ b/src/context/editorContext/index.ts @@ -0,0 +1,4 @@ +export * from './editorContext'; +export * from './editorProvider'; +export * from './hooks/useEditorStore'; +export * from './store'; diff --git a/src/context/editorContext/store.ts b/src/context/editorContext/store.ts new file mode 100644 index 000000000..c8f65aabc --- /dev/null +++ b/src/context/editorContext/store.ts @@ -0,0 +1,68 @@ +import {ActionTypes, WithStoreReducer} from '../../common/types'; +import {initializeStore} from '../../utils/store'; + +export interface EditorState { + manipulateOverlayMode: 'insert' | 'reorder' | false; + isSelectActive: boolean; + initialized: boolean; +} + +export interface EditorMethods extends WithStoreReducer {} + +export type EditorStore = EditorState & EditorMethods; + +export const createEditorStore = initializeStore<EditorState, EditorMethods>( + { + manipulateOverlayMode: false, + isSelectActive: false, + initialized: false, + }, + (set, _get) => ({ + reducer: (action) => { + switch (action.type) { + case ActionTypes.EditorReady: { + set((state) => ({ + ...state, + initialized: true, + })); + break; + } + case ActionTypes.ReorderModeEnable: { + set((state) => ({ + ...state, + manipulateOverlayMode: 'reorder', + })); + break; + } + case ActionTypes.ReorderModeDisable: { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + })); + break; + } + case ActionTypes.InsertModeEnable: { + set((state) => ({ + ...state, + manipulateOverlayMode: 'insert', + })); + break; + } + case ActionTypes.InsertModeDisable: { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + })); + break; + } + case ActionTypes.SelectBlock: { + set((state) => ({ + ...state, + isSelectActive: true, + })); + break; + } + } + }, + }), +); diff --git a/src/context/messagesContext/hooks/useMessageObserver.tsx b/src/context/messagesContext/hooks/useMessageObserver.tsx new file mode 100644 index 000000000..296ca9dea --- /dev/null +++ b/src/context/messagesContext/hooks/useMessageObserver.tsx @@ -0,0 +1,23 @@ +import {DependencyList, useEffect} from 'react'; + +import {Action} from '../../../common/types'; + +import {useMessagesStore} from './useMessagesStore'; + +export const useMessageObserver = <A extends Action>( + type: A['type'], + callback: (payload: A['payload']) => void, + deps: DependencyList = [], +) => { + const {subscribe, unsubscribe} = useMessagesStore(); + + useEffect(() => { + subscribe(type, callback); + + return () => { + unsubscribe(type, callback); + }; + }, deps); // eslint-disable-line react-hooks/exhaustive-deps +}; + +export default useMessageObserver; diff --git a/src/context/messagesContext/hooks/useMessageSender.tsx b/src/context/messagesContext/hooks/useMessageSender.tsx new file mode 100644 index 000000000..57f25eef0 --- /dev/null +++ b/src/context/messagesContext/hooks/useMessageSender.tsx @@ -0,0 +1,10 @@ +import {useContext} from 'react'; + +import {PostMessageContext} from '../messagesContext'; + +export const useMessageSender = () => { + const {sendMessage} = useContext(PostMessageContext); + return sendMessage; +}; + +export default useMessageSender; diff --git a/src/context/messagesContext/hooks/useMessagesStore.tsx b/src/context/messagesContext/hooks/useMessagesStore.tsx new file mode 100644 index 000000000..1805cd32a --- /dev/null +++ b/src/context/messagesContext/hooks/useMessagesStore.tsx @@ -0,0 +1,17 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {PostMessageContext} from '../messagesContext'; + +export const useMessagesStore = () => { + const {state} = useContext(PostMessageContext); + + if (!state) { + throw new Error('Missing PostMessageContext'); + } + + return useStore(state); +}; + +export default useMessagesStore; diff --git a/src/context/messagesContext/index.ts b/src/context/messagesContext/index.ts new file mode 100644 index 000000000..eda561f12 --- /dev/null +++ b/src/context/messagesContext/index.ts @@ -0,0 +1,5 @@ +export * from './messagesContext'; +export * from './messagesProvider'; +export * from './hooks/useMessageSender'; +export * from './hooks/useMessageObserver'; +export * from './hooks/useMessagesStore'; diff --git a/src/context/messagesContext/messagesContext.tsx b/src/context/messagesContext/messagesContext.tsx new file mode 100644 index 000000000..502b6a2c8 --- /dev/null +++ b/src/context/messagesContext/messagesContext.tsx @@ -0,0 +1,21 @@ +/** + * Context for sending messages between Editor and App + * Same exist in Editor + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {Action, SendOptions} from '../../common/types'; + +import {MessagesStore} from './store'; + +export interface PostMessageContextProps { + state?: StoreApi<MessagesStore>; + sendMessage: (action: Action, options?: SendOptions) => void; +} + +export const PostMessageContext = React.createContext<PostMessageContextProps>({ + sendMessage: () => {}, +}); diff --git a/src/context/messagesContext/messagesProvider.tsx b/src/context/messagesContext/messagesProvider.tsx new file mode 100644 index 000000000..86515bd34 --- /dev/null +++ b/src/context/messagesContext/messagesProvider.tsx @@ -0,0 +1,38 @@ +import React, {PropsWithChildren, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {usePostMessage} from '../../common/hooks/usePostMessage'; +import {WithStoreReducer} from '../../common/types'; +import {useEditorStore} from '../editorContext'; + +import {PostMessageContext} from './messagesContext'; +import {MessagesStore, createMessagesStore} from './store'; + +export const PostMessageProvider = ({children}: PropsWithChildren) => { + const storeRef = useRef<StoreApi<MessagesStore>>(); + + if (!storeRef.current) { + storeRef.current = createMessagesStore(); + } + + const editorStore = useEditorStore(); + + const storesWithReducer: WithStoreReducer[] = [editorStore]; + + const {sendMessage} = usePostMessage({ + subscribers: storeRef.current?.getState().subscribers, + storesWithReducer, + }); + + return ( + <PostMessageContext.Provider + value={{ + state: storeRef.current, + sendMessage: sendMessage, + }} + > + {children} + </PostMessageContext.Provider> + ); +}; diff --git a/src/context/messagesContext/store.ts b/src/context/messagesContext/store.ts new file mode 100644 index 000000000..ccfa31228 --- /dev/null +++ b/src/context/messagesContext/store.ts @@ -0,0 +1,44 @@ +import {Subscriber, SubscriptionFunc} from '../../common/types'; +import {initializeStore} from '../../utils/store'; + +export interface MessagesState { + subscribers: Subscriber[]; +} + +export interface MessagesMethods { + subscribe: SubscriptionFunc; + unsubscribe: SubscriptionFunc; +} + +export type MessagesStore = MessagesState & MessagesMethods; + +export const createMessagesStore = initializeStore<MessagesState, MessagesMethods>( + { + subscribers: [], + }, + (set, _get) => ({ + subscribe: (type, payloadCallback) => { + set((state) => ({ + ...state, + subscribers: [...state.subscribers, {action: type, handler: payloadCallback}], + })); + + return () => { + set((state) => ({ + ...state, + subscribers: state.subscribers.filter( + ({handler, action}) => payloadCallback !== handler || type !== action, + ), + })); + }; + }, + unsubscribe: (type, payloadCallback) => { + set((state) => ({ + ...state, + subscribers: state.subscribers.filter( + ({handler, action}) => payloadCallback !== handler || type !== action, + ), + })); + }, + }), +); diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.scss b/src/editor-v2/components/BigOverlay/BigOverlay.scss new file mode 100644 index 000000000..e9d6abbf9 --- /dev/null +++ b/src/editor-v2/components/BigOverlay/BigOverlay.scss @@ -0,0 +1,33 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}big-overlay'; + +#{$block} { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + + $border: 3px var(--g-color-line-brand) solid; + + &__border { + pointer-events: none; + position: absolute; + width: 40px; + height: 40px; + margin-left: -20px; + margin-top: -20px; + background-color: var(--g-color-base-brand); + display: flex; + align-items: center; + justify-content: center; + border-radius: 10px; + //transition: top .1s ease, left .1s ease, height .1s ease, width .1s ease; + } +} diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx new file mode 100644 index 000000000..02fa4679a --- /dev/null +++ b/src/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -0,0 +1,69 @@ +import React, {useContext, useState} from 'react'; + +import {Stop} from '@gravity-ui/icons'; + +import { + ActionTypes, + InsertModeDisableAction, + OverlayModeOnMoveAction, + ReorderModeDisableAction, +} from '../../../common/types'; +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {IframeContext} from '../../context/iframeContext'; +import {useMessageObserver} from '../../context/messagesContext'; + +import './BigOverlay.scss'; + +const b = block('big-overlay'); + +interface BigOverlayProps extends ClassNameProps {} + +const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { + const {iframeElement} = useContext(IframeContext); + const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( + undefined, + ); + + useMessageObserver<OverlayModeOnMoveAction>( + ActionTypes.OverlayModeOnMove, + (payload, meta) => { + if (payload && payload.cursor) { + const {x, y} = payload.cursor; + const iframeRect = iframeElement?.getClientRects().item(0); + if (iframeRect) { + const newX = meta.source === 'editor' ? x : x + iframeRect.x; + const newY = meta.source === 'editor' ? y : y + iframeRect.y; + setMousePosition({x: newX, y: newY}); + } + } + }, + [iframeElement], + ); + + useMessageObserver<InsertModeDisableAction>(ActionTypes.InsertModeDisable, () => { + setMousePosition(undefined); + }); + + useMessageObserver<ReorderModeDisableAction>(ActionTypes.ReorderModeDisable, () => { + setMousePosition(undefined); + }); + + return ( + <div className={b(null, className)}> + {mousePosition ? ( + <div + className={b('border')} + style={{ + top: mousePosition.y, + left: mousePosition.x, + }} + > + <Stop height={20} width={20} /> + </div> + ) : null} + </div> + ); +}; + +export default BigOverlay; diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.scss b/src/editor-v2/components/BlockConfig/BlockConfig.scss new file mode 100644 index 000000000..6af7fb8a7 --- /dev/null +++ b/src/editor-v2/components/BlockConfig/BlockConfig.scss @@ -0,0 +1,28 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}block-config'; + +#{$block} { + padding: 12px; + + &__title { + margin-bottom: 16px; + margin-top: 8px; + @include text-subheader-3; + } + + &__empty { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + height: 100%; + width: 100%; + + @include text-body-2; + } +} diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.tsx b/src/editor-v2/components/BlockConfig/BlockConfig.tsx new file mode 100644 index 000000000..fcb71593d --- /dev/null +++ b/src/editor-v2/components/BlockConfig/BlockConfig.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import _ from 'lodash'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig'; +import {useEditorStore} from '../../context/editorContext'; +import {generateChildrenPathFromArray} from '../../utils'; +import DynamicForm, {DynamicFormValue} from '../DynamicForm/DynamicForm'; + +import './BlockConfig.scss'; + +const b = block('block-config'); + +interface BlockConfigProps extends ClassNameProps {} + +const BlockConfig: React.FC<BlockConfigProps> = ({className}) => { + const {selectedBlock} = useEditorStore(); + const {config, blocks, subBlocks, updateField} = useContentConfigStore(); + + const currentBlockPath = selectedBlock?.path + ? generateChildrenPathFromArray(selectedBlock?.path) + : '[]'; + + const currentConfig = _.get(config.blocks, currentBlockPath || ''); + const currentSchema = [...blocks, ...subBlocks].find(({type}) => type === currentConfig?.type); + + const onUpdate = (key: string, value: DynamicFormValue) => { + updateField('blocks' + currentBlockPath + '.' + key, value); + }; + + if (!currentConfig) { + return <div className={b('empty')}>Select block for start</div>; + } + + if (!currentSchema) { + return <div className={b('empty')}>Not supported: {currentConfig.type}</div>; + } + + return ( + <div className={b(null, className)}> + <div className={b('title')}>{currentSchema.schema.name}</div> + <DynamicForm + contentConfig={currentConfig} + blockConfig={currentSchema.schema.inputs} + onUpdate={onUpdate} + /> + </div> + ); +}; + +export default BlockConfig; diff --git a/src/editor-v2/components/BlocksList/BlocksList.scss b/src/editor-v2/components/BlocksList/BlocksList.scss new file mode 100644 index 000000000..e2b9c301e --- /dev/null +++ b/src/editor-v2/components/BlocksList/BlocksList.scss @@ -0,0 +1,42 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}blocks-list'; + +#{$block} { + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; + + &__card { + padding: 8px; + cursor: pointer; + user-select: none; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + &:active { + background-color: var(--g-color-base-generic-hover); + } + } + + &__title { + @include text-subheader-3; + margin-bottom: 10px; + } + + &__section { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + } +} diff --git a/src/editor-v2/components/BlocksList/BlocksList.tsx b/src/editor-v2/components/BlocksList/BlocksList.tsx new file mode 100644 index 000000000..f2af64211 --- /dev/null +++ b/src/editor-v2/components/BlocksList/BlocksList.tsx @@ -0,0 +1,43 @@ +import React, {PropsWithChildren, useCallback} from 'react'; + +import {Card} from '@gravity-ui/uikit'; + +import {ActionTypes, ItemConfig} from '../../../common/types'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig'; +import {useMessageSender} from '../../context/messagesContext'; + +import './BlocksList.scss'; + +const b = block('blocks-list'); + +export interface BlocksListProps { + blocks: ItemConfig[]; +} + +const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { + const {blocks} = useContentConfigStore(); + const sendMessage = useMessageSender(); + + const onMouseDown = useCallback( + (blockType: string) => { + sendMessage({type: ActionTypes.InsertModeEnable, payload: {blockType}}); + }, + [sendMessage], + ); + + return ( + <div className={b()}> + <div className={b('section')}> + <div className={b('title')}>Blocks</div> + {blocks.map(({type, schema: {name}}) => ( + <Card key={type} className={b('card')} onMouseDown={() => onMouseDown(type)}> + {name} + </Card> + ))} + </div> + </div> + ); +}; + +export default BlocksList; diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.scss b/src/editor-v2/components/DynamicForm/DynamicForm.scss new file mode 100644 index 000000000..4628d70ab --- /dev/null +++ b/src/editor-v2/components/DynamicForm/DynamicForm.scss @@ -0,0 +1,8 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; + +$block: '.#{$ns}dynamic-form'; + +#{$block} { + //padding-left: 16px; +} diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.tsx b/src/editor-v2/components/DynamicForm/DynamicForm.tsx new file mode 100644 index 000000000..8bef3e94d --- /dev/null +++ b/src/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -0,0 +1,219 @@ +import React, {useCallback} from 'react'; + +import _ from 'lodash'; + +import {ConfigInput} from '../../../common/types'; +import {ClassNameProps, PageContent} from '../../../models'; +import {block} from '../../../utils'; + +import ArrayDynamicField from './Fields/Array/Array'; +import BooleanDynamicField from './Fields/Boolean/Boolean'; +import NumberDynamicField from './Fields/Number/Number'; +import ObjectDynamicField from './Fields/Object/Object'; +import OneOfDynamicField from './Fields/OneOf/OneOf'; +import SelectDynamicField from './Fields/Select/Select'; +import TextDynamicField from './Fields/Text/Text'; +import TextAreaDynamicField from './Fields/TextArea/TextArea'; +import {getContent, getFullPath} from './utils'; + +import './DynamicForm.scss'; + +const b = block('dynamic-form'); + +export type DynamicFormValue = string | number | [] | object | boolean | PageContent | undefined; + +interface DynamicFormProps extends ClassNameProps { + blockConfig: Array<ConfigInput>; + contentConfig: PageContent; + onUpdate: (key: string, value: DynamicFormValue) => void; +} + +const DynamicForm: React.FC<DynamicFormProps> = (props) => { + const {blockConfig, onUpdate, contentConfig} = props; + const inputs = blockConfig; + + const getData = useCallback( + (variable: string) => { + if (variable.startsWith('block.')) { + const purePath = variable.replace('block.', ''); + return _.get(contentConfig, purePath); + } + + if ( + (variable.startsWith(`'`) && variable.endsWith(`'`)) || + (variable.startsWith(`"`) && variable.endsWith(`"`)) + ) { + // @ts-ignore TODO: replaceAll types + return variable.replaceAll(`'`, '').replaceAll(`"`, ''); + } + + return undefined; + }, + [contentConfig], + ); + + const decide = useCallback( + (showIf: string): boolean => { + const parts = showIf.split(' '); + + if (!(parts.length === 3)) { + // eslint-disable-next-line no-console + console.log('Something bad happened in showIf, ignored'); + return true; + } + + const [firstVariable, equals, secondVariable] = parts; + + const data1 = getData(firstVariable); + const data2 = getData(secondVariable); + + if (equals === '===') { + return data1 === data2; + } else { + return data1 !== data2; + } + }, + [getData], + ); + + const renderInput = useCallback( + (input: ConfigInput) => { + const fieldPath = input.name; + const fieldValue = getContent(contentConfig, input.name); + + if (input.showIf) { + const decision = decide(input.showIf); + + if (!decision) { + return <div>Hidden Field: {input.name}</div>; + } + } + + // Text, Select, Boolean and etc + const onSimpleDynamicFieldUpdate = (value: DynamicFormValue) => { + onUpdate(fieldPath, value); + }; + + // Array and Objects + const onComplexDynamicFieldUpdate = (key: string, value: DynamicFormValue) => { + onUpdate(getFullPath(fieldPath, key), value); + }; + + switch (input.type) { + case 'text': { + return ( + <TextDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + title={input.title} + value={fieldValue} + onUpdate={onSimpleDynamicFieldUpdate} + /> + ); + } + case 'boolean': { + return ( + <BooleanDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + title={input.title} + value={fieldValue} + onUpdate={onSimpleDynamicFieldUpdate} + /> + ); + } + case 'textarea': { + return ( + <TextAreaDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + title={input.title} + value={fieldValue} + onUpdate={onSimpleDynamicFieldUpdate} + /> + ); + } + case 'select': { + return ( + <SelectDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + input={input} + value={fieldValue} + onUpdate={onSimpleDynamicFieldUpdate} + /> + ); + } + case 'number': { + return ( + <NumberDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + title={input.title} + value={fieldValue} + onUpdate={onSimpleDynamicFieldUpdate} + /> + ); + } + case 'object': { + if (!input || !('properties' in input)) { + return null; + } + + return ( + <ObjectDynamicField + onRefresh={(value) => onUpdate(fieldPath, value)} + blockConfig={input.properties} + title={input.title} + value={fieldValue} + onUpdate={onComplexDynamicFieldUpdate} + /> + ); + } + case 'array': { + return ( + <ArrayDynamicField + blockConfig={input} + title={input.title} + values={fieldValue} + onUpdate={onComplexDynamicFieldUpdate} + /> + ); + } + case 'oneOf': { + if (!input || !('options' in input)) { + return null; + } + + return ( + <OneOfDynamicField + inputConfig={input} + contentConfig={contentConfig} + onUpdate={onComplexDynamicFieldUpdate} + /> + ); + } + default: { + return <div>Ignore {JSON.stringify(input)}</div>; + } + } + }, + [contentConfig, decide, onUpdate], + ); + + const sortedInputs = inputs.sort((x, y) => { + const nestingFieldTypes = ['object', 'array', 'oneOf']; + if (nestingFieldTypes.includes(x.type)) { + return 1; + } + if (nestingFieldTypes.includes(y.type)) { + return -1; + } + return 0; + }); + + return ( + <div className={b()}> + {sortedInputs.map((input, index) => ( + <React.Fragment key={index}>{renderInput(input)}</React.Fragment> + ))} + </div> + ); +}; + +export default DynamicForm; diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss new file mode 100644 index 000000000..c73969996 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -0,0 +1,72 @@ +@import '../../../../../styles/variables.scss'; +@import '../../../../../styles/mixins.scss'; +@import '../../../styles/root.scss'; +@import '../../../styles/variables.scss'; +@import '../../../styles/mixins.scss'; + +$block: '.#{$ns}field-base'; + +#{$block} { + $class: &; + + position: relative; + + &:hover { + & > #{$class}__top > #{$class}__button { + opacity: 1; + } + } + + &__top { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 8px; + } + + &__foldable { + display: flex; + align-items: center; + cursor: pointer; + } + + &__non-foldable { + &:before { + //content: '•'; + position: absolute; + right: calc(100% + 5px); + } + } + + &__button { + transition: opacity 0.3s ease; + opacity: 0; + } + + &__title { + @include text-body-2; + + &_size { + &_s { + @include text-body-1; + } + &_m { + @include text-body-2; + } + &_l { + @include text-body-3; + } + } + } + + margin-top: 20px; + + &:first-child { + margin-top: 0; + } + + &__arrow-toggle { + //position: absolute; + //right: calc(100%); + } +} diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx new file mode 100644 index 000000000..8b5860ace --- /dev/null +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -0,0 +1,73 @@ +import React, {PropsWithChildren, useState} from 'react'; + +import {ArrowRotateLeft} from '@gravity-ui/icons'; +import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit'; +import _ from 'lodash'; + +import {ClassNameProps} from '../../../../models'; +import {block} from '../../../../utils'; + +import './FieldBase.scss'; + +const b = block('field-base'); + +export interface FieldBaseParams { + title?: string; + textSize?: 's' | 'm' | 'l'; + onRefresh?: (value: undefined) => void; + expandable?: boolean; +} + +export interface FieldBaseProps extends ClassNameProps, PropsWithChildren, FieldBaseParams {} + +const FieldBase: React.FC<FieldBaseProps> = (props) => { + const {className, title, textSize, children, onRefresh, expandable = false} = props; + const [showChildren, setShowChildren] = useState(!expandable); + + const titleComponent = React.useMemo(() => { + if (title) { + const defaultTitle = ( + <div className={b('title', {size: textSize})}>{_.capitalize(title)}</div> + ); + + if (expandable) { + return ( + <div className={b('foldable')} onClick={() => setShowChildren(!showChildren)}> + <ArrowToggle + direction={showChildren ? 'bottom' : 'right'} + className={b('arrow-toggle')} + /> + {defaultTitle} + </div> + ); + } + + return <div className={b('non-foldable')}>{defaultTitle}</div>; + } + + return null; + }, [expandable, showChildren, textSize, title]); + + return ( + <div className={b(null, className)}> + {title && ( + <div className={b('top')}> + {titleComponent} + {onRefresh && ( + <Button + className={b('button')} + onClick={() => onRefresh(undefined)} + view={'flat'} + size={'xs'} + > + <Icon data={ArrowRotateLeft} size={14} /> + </Button> + )} + </div> + )} + {(!title || showChildren) && <div className={b('children')}>{children}</div>} + </div> + ); +}; + +export default FieldBase; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss b/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss new file mode 100644 index 000000000..973665582 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss @@ -0,0 +1,51 @@ +@import '../../../../../../styles/variables.scss'; +@import '../../../../../../styles/mixins.scss'; + +$block: '.#{$ns}array-dynamic-field'; + +#{$block} { + &__card { + padding: 12px; + margin-top: 12px; + + &:first-child { + margin-bottom: 0; + } + } + + &__row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + + margin-top: 10px; + + &:first-child { + margin-top: 0; + } + } + + &__card-head { + margin-bottom: 8px; + } + + &__row-title { + @include text-subheader-3; + } + + &__row-title, + &__row-field { + flex: 1; + } + + &__empty { + padding: 10px; + display: flex; + justify-content: center; + } + + &__add-button { + margin-top: 12px; + } +} diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx new file mode 100644 index 000000000..823665bbf --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -0,0 +1,158 @@ +import React, {useCallback} from 'react'; + +import {Plus} from '@gravity-ui/icons'; +import {Button, Card, Icon} from '@gravity-ui/uikit'; + +import {ArrayObjectInput, ArrayTextInput} from '../../../../../common/types'; +import {ClassNameProps, PageContent} from '../../../../../models'; +import {block} from '../../../../../utils'; +import {removeFromArray, swapArrayItems} from '../../../../utils'; +import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import FieldBase from '../../FieldBase/FieldBase'; +import Text from '../Text/Text'; + +import ItemButton from './ItemButton/ItemButton'; + +import './Array.scss'; + +const b = block('array-dynamic-field'); + +type ArrayInput = ArrayTextInput | ArrayObjectInput; + +interface ArrayFieldProps extends ClassNameProps { + title: string; + values: Array<DynamicFormValue>; + onUpdate: (key: string, value: DynamicFormValue) => void; + blockConfig: ArrayInput; +} + +const ArrayDynamicField: React.FC<ArrayFieldProps> = (props) => { + const {title, values, onUpdate, className, blockConfig} = props; + + const haveItems = values && Array.isArray(values) && values.length; + + const onAddItem = useCallback(() => { + if (blockConfig.arrayType === 'text') { + onUpdate('', haveItems ? [...values, ''] : ['']); + } else if (blockConfig.arrayType === 'object') { + onUpdate('', haveItems ? [...values, {}] : [{}]); + } + }, [blockConfig.arrayType, haveItems, onUpdate, values]); + + const onDeleteItem = useCallback( + (index: number) => { + if (Array.isArray(values)) { + const newArray = removeFromArray(values, index); + onUpdate('', newArray); + } + }, + [onUpdate, values], + ); + + const onReorderItem = useCallback( + (index: number, placement: 'up' | 'down') => { + if (Array.isArray(values)) { + const newArray = swapArrayItems( + values, + index, + placement === 'up' ? index - 1 : index + 1, + ); + onUpdate('', newArray); + } + }, + [onUpdate, values], + ); + + const renderInput = useCallback( + (value: DynamicFormValue, index: number) => { + const arrayItemButton = ( + <ItemButton + onRemove={() => onDeleteItem(index)} + onReorderUp={() => onReorderItem(index, 'up')} + onReorderDown={() => onReorderItem(index, 'down')} + disableReorderUp={index === 0} + disableReorderDown={Boolean(haveItems) && values.length === index + 1} + /> + ); + + switch (blockConfig.arrayType) { + case 'text': { + return ( + <div className={b('row')}> + <Text + className={b('row-field')} + value={String(value)} + onUpdate={(updateValue) => onUpdate(`[${index}]`, updateValue)} + onRefresh={(updatedValue) => onUpdate('', updatedValue)} + /> + {arrayItemButton} + </div> + ); + } + case 'object': { + if (!blockConfig.properties) { + return null; + } + return ( + <Card key={index} className={b('card')}> + <div className={`${b('row')} ${b('card-head')}`}> + <div className={b('row-title')}>#{index}</div> + {arrayItemButton} + </div> + <DynamicForm + contentConfig={value as PageContent} + blockConfig={blockConfig.properties} + onUpdate={(key, updateValue) => + onUpdate(`[${index}].${key}`, updateValue) + } + /> + </Card> + ); + } + default: { + return null; + } + } + }, + [blockConfig, haveItems, onDeleteItem, onReorderItem, onUpdate, values], + ); + + const renderInputs = useCallback(() => { + if (haveItems) { + const renderItems = values + .map(renderInput) + .filter(Boolean) as unknown as React.ReactNode[]; + return ( + <React.Fragment> + {renderItems} + <Button className={b('add-button')} onClick={onAddItem}> + <Icon data={Plus} /> + {blockConfig.buttonText} + </Button> + </React.Fragment> + ); + } else { + return ( + <div className={b('empty')}> + <Button className={b('add-button')} onClick={onAddItem}> + <Icon data={Plus} /> + Please, add new item + </Button> + </div> + ); + } + }, [blockConfig.buttonText, haveItems, onAddItem, renderInput, values]); + + return ( + <FieldBase + title={title} + className={b(null, className)} + onRefresh={(value) => onUpdate('', value)} + expandable + > + <Card className={b('card')}>{renderInputs()}</Card> + </FieldBase> + ); +}; + +export default ArrayDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx new file mode 100644 index 000000000..d566f49e5 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx @@ -0,0 +1,77 @@ +import React, {Fragment, useCallback, useRef, useState} from 'react'; + +import {ArrowDown, ArrowUp, EllipsisVertical, TrashBin} from '@gravity-ui/icons'; +import {Button, Icon, Menu, Popup} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../../../../models'; +import {block} from '../../../../../../utils'; + +const b = block('array-item-button'); + +interface ItemButtonProps extends ClassNameProps { + onRemove: () => void; + onReorderUp: () => void; + disableReorderUp?: boolean; + onReorderDown: () => void; + disableReorderDown?: boolean; +} + +const ItemButton: React.FC<ItemButtonProps> = (props) => { + const { + className, + onRemove, + onReorderUp, + onReorderDown, + disableReorderUp = false, + disableReorderDown = false, + } = props; + const buttonRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + + const onMenuItemClickWrapper = useCallback((callback: () => void) => { + return () => { + setIsOpen(false); + callback(); + }; + }, []); + + return ( + <Fragment> + <Button className={b(null, className)} ref={buttonRef} onClick={() => setIsOpen(true)}> + <Icon data={EllipsisVertical} /> + </Button> + <Popup + placement={'bottom-end'} + anchorRef={buttonRef} + open={isOpen} + onOutsideClick={() => setIsOpen(false)} + > + <Menu> + <Menu.Item + theme={'danger'} + onClick={onMenuItemClickWrapper(onRemove)} + iconStart={<Icon data={TrashBin} />} + > + Remove + </Menu.Item> + <Menu.Item + disabled={disableReorderUp} + onClick={onMenuItemClickWrapper(onReorderUp)} + iconStart={<Icon data={ArrowUp} />} + > + Reorder Up + </Menu.Item> + <Menu.Item + disabled={disableReorderDown} + onClick={onMenuItemClickWrapper(onReorderDown)} + iconStart={<Icon data={ArrowDown} />} + > + Reorder Down + </Menu.Item> + </Menu> + </Popup> + </Fragment> + ); +}; + +export default ItemButton; diff --git a/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx b/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx new file mode 100644 index 000000000..2ac37c4ef --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import {Switch} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../../../models'; +import {block} from '../../../../../utils'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +const b = block('boolean-dynamic-field'); + +interface BooleanProps extends ClassNameProps, FieldBaseParams { + value: string; + onUpdate: (value: boolean | undefined) => void; +} + +const BooleanDynamicField: React.FC<BooleanProps> = (props) => { + const {title, value, onUpdate, className} = props; + + return ( + <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> + <Switch checked={Boolean(value)} onUpdate={onUpdate} /> + </FieldBase> + ); +}; + +export default BooleanDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx b/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx new file mode 100644 index 000000000..2b01e6798 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import {TextInput} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../../../models'; +import {block} from '../../../../../utils'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +const b = block('number-dynamic-field'); + +interface NumberDynamicFieldProps extends ClassNameProps, FieldBaseParams { + value: string; + onUpdate: (value: number | undefined) => void; +} + +const NumberDynamicField: React.FC<NumberDynamicFieldProps> = (props) => { + const {title, value, onUpdate, className} = props; + + const onUpdateFunc = (updateValue: string) => { + onUpdate(Number(updateValue)); + }; + + return ( + <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> + <TextInput value={value || ''} onUpdate={onUpdateFunc} /> + </FieldBase> + ); +}; + +export default NumberDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss b/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss new file mode 100644 index 000000000..b3aa2610b --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss @@ -0,0 +1,10 @@ +@import '../../../../../../styles/variables.scss'; +@import '../../../../../../styles/mixins.scss'; + +$block: '.#{$ns}object-dynamic-field'; + +#{$block} { + &__card { + padding: 12px; + } +} diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx new file mode 100644 index 000000000..ff8bf177c --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import {Card} from '@gravity-ui/uikit'; + +import {ConfigInput} from '../../../../../common/types'; +import {ClassNameProps, PageContent} from '../../../../../models'; +import {block} from '../../../../../utils'; +import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +import './Object.scss'; + +const b = block('object-dynamic-field'); + +interface ObjectDynamicFieldProps extends ClassNameProps, FieldBaseParams { + value: PageContent; + onUpdate: (key: string, value: DynamicFormValue) => void; + blockConfig: Array<ConfigInput>; +} + +const ObjectDynamicField: React.FC<ObjectDynamicFieldProps> = (props) => { + const {title, value, onUpdate, className, blockConfig} = props; + + return ( + <FieldBase + title={title} + className={b(null, className)} + onRefresh={(updatedValue) => onUpdate('', updatedValue)} + expandable + > + <Card className={b('card')}> + <DynamicForm contentConfig={value} blockConfig={blockConfig} onUpdate={onUpdate} /> + </Card> + </FieldBase> + ); +}; + +export default ObjectDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss new file mode 100644 index 000000000..1e5ec1c1a --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss @@ -0,0 +1,14 @@ +@import '../../../../../../styles/variables.scss'; +@import '../../../../../../styles/mixins.scss'; + +$block: '.#{$ns}oneof-dynamic-field'; + +#{$block} { + &__card { + padding: 12px; + } + + &__radio { + margin-bottom: 12px; + } +} diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx new file mode 100644 index 000000000..4dafbe079 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -0,0 +1,78 @@ +import React, {useCallback, useMemo, useState} from 'react'; + +import {Card, RadioButton} from '@gravity-ui/uikit'; + +import {OneOfInput} from '../../../../../common/types'; +import {ClassNameProps, PageContent} from '../../../../../models'; +import {block} from '../../../../../utils'; +import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import FieldBase from '../../FieldBase/FieldBase'; + +import './OneOf.scss'; + +const b = block('oneof-dynamic-field'); + +interface OneOfDynamicFieldProps extends ClassNameProps { + contentConfig: PageContent; + onUpdate: (key: string, value: DynamicFormValue) => void; + inputConfig: OneOfInput; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const getOneOfContentConfig = (contentConfig: any, name: string) => { + if (name) { + return contentConfig ? contentConfig[name] : {}; + } + return contentConfig; +}; + +const OneOfDynamicField: React.FC<OneOfDynamicFieldProps> = (props) => { + const {contentConfig, onUpdate, className, inputConfig} = props; + + const defaultValue = inputConfig.options[0].value; + + const [oneOfMetaValue, setOneOfMetaValue] = useState(defaultValue); + + const oneOfContentConfig = getOneOfContentConfig(contentConfig, inputConfig.name); + const oneOfChosenOption = useMemo( + () => + inputConfig.options.find( + ({value: foundOneOfValue}) => foundOneOfValue === oneOfMetaValue, + ), + [inputConfig.options, oneOfMetaValue], + ); + + const onUpdateOneOf = useCallback((value: string) => { + setOneOfMetaValue(value); + }, []); + + return ( + <FieldBase + title={inputConfig.title} + className={b(null, className)} + onRefresh={(value) => onUpdate('', value)} + expandable + > + <Card className={b('card')}> + <RadioButton + className={b('radio')} + options={inputConfig.options.map((option) => ({ + content: option.title, + value: option.value, + }))} + value={oneOfMetaValue} + onUpdate={onUpdateOneOf} + /> + {oneOfChosenOption && ( + <DynamicForm + blockConfig={oneOfChosenOption.properties} + contentConfig={oneOfContentConfig} + onUpdate={onUpdate} + /> + )} + </Card> + </FieldBase> + ); +}; + +export default OneOfDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx new file mode 100644 index 000000000..06eff057a --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import {RadioButton, Select} from '@gravity-ui/uikit'; + +import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/types'; +import {ClassNameProps} from '../../../../../models'; +import {block} from '../../../../../utils'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +const b = block('select-field'); + +type SelectInput = SelectSingleInput | SelectMultipleInput; + +interface SelectDynamicFieldProps extends ClassNameProps, FieldBaseParams { + input: SelectInput; + value: string; + onUpdate: (value: string | undefined) => void; +} + +const SelectDynamicField: React.FC<SelectDynamicFieldProps> = (props) => { + const {input, value, onUpdate, className} = props; + + const inputView = input.view || 'radiobutton'; + + return ( + <FieldBase title={input.title} className={b(null, className)} onRefresh={onUpdate}> + {inputView === 'select' && ( + <Select + placeholder={'Value'} + value={value ? [value] : []} + onUpdate={([selectValue]) => onUpdate(selectValue)} + options={input.enum} + /> + )} + {inputView === 'radiobutton' && ( + <RadioButton options={input.enum} value={value} onUpdate={onUpdate} /> + )} + </FieldBase> + ); +}; + +export default SelectDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx b/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx new file mode 100644 index 000000000..b16101e86 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import {TextInput} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../../../models'; +import {block} from '../../../../../utils'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +const b = block('text-dynamic-field'); + +interface TextDynamicFieldProps extends ClassNameProps, FieldBaseParams { + value: string; + onUpdate: (value: string | undefined) => void; +} + +const TextDynamicField: React.FC<TextDynamicFieldProps> = (props) => { + const {title, value, onUpdate, className} = props; + + return ( + <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> + <TextInput value={value || ''} onUpdate={onUpdate} /> + </FieldBase> + ); +}; + +export default TextDynamicField; diff --git a/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx b/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx new file mode 100644 index 000000000..4ca783696 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import {TextArea} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../../../models'; +import {block} from '../../../../../utils'; +import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; + +const b = block('textarea-dynamic-field'); + +interface TextAreaDynamicFieldProps extends ClassNameProps, FieldBaseParams { + value: string; + onUpdate: (value: string | undefined) => void; +} + +const TextAreaDynamicField: React.FC<TextAreaDynamicFieldProps> = (props) => { + const {title, value, onUpdate, className} = props; + + return ( + <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> + <TextArea minRows={5} maxRows={20} value={value || ''} onUpdate={onUpdate} /> + </FieldBase> + ); +}; + +export default TextAreaDynamicField; diff --git a/src/editor-v2/components/DynamicForm/utils.ts b/src/editor-v2/components/DynamicForm/utils.ts new file mode 100644 index 000000000..b52c3381a --- /dev/null +++ b/src/editor-v2/components/DynamicForm/utils.ts @@ -0,0 +1,23 @@ +import _ from 'lodash'; + +import {PageContent} from '../../../models'; + +export const getFullPath = (path: string, name: string) => { + if (!path && !name) { + return ''; + } + + if (!path) { + return name; + } + + if (!name) { + return path; + } + + return path + '.' + name; +}; + +export const getContent = (contentConfig: PageContent, path: string) => { + return path ? _.get(contentConfig, path) : contentConfig; +}; diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.scss b/src/editor-v2/components/GlobalConfig/GlobalConfig.scss new file mode 100644 index 000000000..d54900eb4 --- /dev/null +++ b/src/editor-v2/components/GlobalConfig/GlobalConfig.scss @@ -0,0 +1,17 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}global-config'; + +#{$block} { + padding: 12px; + + &__title { + margin-bottom: 16px; + margin-top: 8px; + @include text-subheader-3; + } +} diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx new file mode 100644 index 000000000..e203439d2 --- /dev/null +++ b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig'; +import DynamicForm, {DynamicFormValue} from '../DynamicForm/DynamicForm'; + +import './GlobalConfig.scss'; + +const b = block('global-config'); + +interface GlobalConfigProps extends ClassNameProps {} + +const GlobalConfig: React.FC<GlobalConfigProps> = ({className}) => { + const {global, updateField, config} = useContentConfigStore(); + + const onUpdate = (key: string, value: DynamicFormValue) => { + updateField(key, value); + }; + + return ( + <div className={b(null, className)}> + <div className={b('title')}>Global Config</div> + <DynamicForm contentConfig={config} blockConfig={global} onUpdate={onUpdate} /> + </div> + ); +}; + +export default GlobalConfig; diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.scss b/src/editor-v2/components/MiddleScreen/MiddleScreen.scss new file mode 100644 index 000000000..bb24c8168 --- /dev/null +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.scss @@ -0,0 +1,52 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}middle-screen'; + +#{$block} { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + + &__topbar { + height: 50px; + } + + &__canvas { + height: 100%; + width: 100%; + overflow-y: scroll; + position: relative; + } + + &__overlay { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + } + + &__border { + position: absolute; + border: 3px var(--g-color-line-brand) solid; + border-radius: 10px; + } + + &__loading { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: var(--g-color-base-background); + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx new file mode 100644 index 000000000..eebe7469c --- /dev/null +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -0,0 +1,52 @@ +import React, {useContext} from 'react'; + +import {Loader} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {useEditorStore} from '../../context/editorContext'; +import {IframeContext, useIframeStore} from '../../context/iframeContext'; +import Overlay from '../Overlay/Overlay'; +import TopBar from '../TopBar/TopBar'; + +import './MiddleScreen.scss'; + +const b = block('middle-screen'); + +interface MiddleScreenProps extends ClassNameProps {} + +const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { + const {url, height} = useIframeStore(); + const {initialized} = useEditorStore(); + const {setIframeElement} = useContext(IframeContext); + + return ( + <div className={b(null, className)}> + <div className={b('topbar')}> + <TopBar /> + </div> + <div className={b('canvas', {hidden: !initialized})}> + <iframe + ref={(element) => { + if (element) { + setIframeElement(element); + } + }} + className={b('iframe')} + src={url} + height={`${height}px`} + width="100%" + frameBorder="0" + /> + <Overlay className={b('overlay')} /> + {!initialized && ( + <div className={b('loading')}> + <Loader size={'l'} /> + </div> + )} + </div> + </div> + ); +}; + +export default MiddleScreen; diff --git a/src/editor-v2/components/Overlay/Overlay.scss b/src/editor-v2/components/Overlay/Overlay.scss new file mode 100644 index 000000000..61e1b68a8 --- /dev/null +++ b/src/editor-v2/components/Overlay/Overlay.scss @@ -0,0 +1,68 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}overlay'; + +#{$block} { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + + $border: 5px var(--g-color-line-brand) solid; + $border-2: 3px var(--g-color-base-selection) solid; + + &__border { + position: absolute; + border: 3px var(--g-color-line-brand) solid; + box-sizing: border-box; + box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); + } + + &__line { + position: absolute; + border: $border-2; + + &_position { + &_top { + border-top: $border; + } + + &_bottom { + border-bottom: $border; + } + + &_left { + border-left: $border; + } + + &_right { + border-right: $border; + } + } + } + + &__actions { + pointer-events: auto; + position: absolute; + bottom: -40px; + left: 0; + + display: flex; + align-items: center; + background: transparent; + box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); + border-radius: 20px; + } + + &__action-button { + &_grip { + cursor: grab; + } + } +} diff --git a/src/editor-v2/components/Overlay/Overlay.tsx b/src/editor-v2/components/Overlay/Overlay.tsx new file mode 100644 index 000000000..ff231cc9f --- /dev/null +++ b/src/editor-v2/components/Overlay/Overlay.tsx @@ -0,0 +1,133 @@ +import React, {useCallback, useState} from 'react'; + +import {Copy, Grip, TrashBin} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; + +import { + ActionTypes, + InsertModeDisableAction, + OverlayModeOnMoveAction, + ReorderModeDisableAction, +} from '../../../common/types'; +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig'; +import {useEditorStore} from '../../context/editorContext'; +import {useIframeStore} from '../../context/iframeContext'; +import {useMessageObserver, useMessageSender} from '../../context/messagesContext'; + +import './Overlay.scss'; + +const b = block('overlay'); + +interface OverlayProps extends ClassNameProps {} + +interface InsertLineProps { + top: number; + left: number; + height: number; + width: number; + position: string; +} + +const Overlay: React.FC<OverlayProps> = ({className}) => { + const {selectedBlock} = useEditorStore(); + const [insertLineBox, setInsertLineBox] = useState<InsertLineProps | undefined>(undefined); + const {height} = useIframeStore(); + const {deleteBlock, duplicateBlock} = useContentConfigStore(); + const sendMessage = useMessageSender(); + + const margin = 0; + + useMessageObserver<OverlayModeOnMoveAction>(ActionTypes.OverlayModeOnMove, (payload) => { + if (payload && payload.block) { + const {rect, cursorPosition} = payload.block; + setInsertLineBox({ + left: rect.x, + top: rect.y, + height: rect.height, + width: rect.width, + position: cursorPosition, + }); + } + }); + + useMessageObserver<InsertModeDisableAction>(ActionTypes.InsertModeDisable, () => { + setInsertLineBox(undefined); + }); + + useMessageObserver<ReorderModeDisableAction>(ActionTypes.ReorderModeDisable, () => { + setInsertLineBox(undefined); + }); + + const onMouseDown = useCallback(() => { + if (selectedBlock) { + sendMessage({ + type: ActionTypes.ReorderModeEnable, + payload: {path: selectedBlock.path}, + }); + } + }, [selectedBlock, sendMessage]); + + return ( + <div className={b(null, className)} style={{height: `${height}px`}}> + {selectedBlock ? ( + <div + className={b('border')} + style={{ + top: selectedBlock.rect.top - margin, + left: selectedBlock.rect.left - margin, + width: selectedBlock.rect.width + margin * 2, + height: selectedBlock.rect.height + margin * 2, + }} + > + <div className={b('actions')}> + <div onMouseDown={onMouseDown}> + <Button + pin={'round-clear'} + className={`${b('action-button', {grip: true})}`} + size={'m'} + view={'action'} + > + <Icon data={Grip} size={18} /> + </Button> + </div> + <Button + pin={'clear-clear'} + className={b('action-button')} + size={'m'} + view={'action'} + onClick={() => selectedBlock && deleteBlock(selectedBlock.path)} + > + <Icon data={TrashBin} size={18} /> + </Button> + <Button + pin={'clear-round'} + className={b('action-button')} + size={'m'} + view={'action'} + onClick={() => selectedBlock && duplicateBlock(selectedBlock.path)} + > + <Icon data={Copy} size={18} /> + </Button> + </div> + </div> + ) : null} + {insertLineBox ? ( + <div + className={b('line', { + position: insertLineBox.position, + })} + style={{ + top: insertLineBox.top, + left: insertLineBox.left, + width: insertLineBox.width, + height: insertLineBox.height, + }} + /> + ) : null} + </div> + ); +}; + +export default Overlay; diff --git a/src/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss new file mode 100644 index 000000000..842996be6 --- /dev/null +++ b/src/editor-v2/components/Sidebar/Sidebar.scss @@ -0,0 +1,86 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}sidebar'; + +#{$block} { + $class: &; + + position: relative; + height: 100%; + width: 100%; + display: flex; + + &_position_left { + flex-direction: row; + } + + &_position_right { + flex-direction: row-reverse; + } + + &__buttons-wrapper { + border-right: 1px solid var(--g-color-line-generic); + } + + &__buttons { + display: inline-flex; + flex-direction: column; + padding: 8px; + gap: 8px; + + &:hover #{$class}__label-wrapper { + opacity: 1; + } + } + + &__body { + flex: 1; + border-right: 1px solid var(--g-color-line-generic); + overflow-y: auto; + } + + &__button-wrapper { + position: relative; + } + + &_position_right { + #{$class}__button-wrapper:hover { + #{$class}__label-wrapper { + right: 40px; + } + } + + #{$class}__label-wrapper { + right: 50px; + } + } + + &_position_left { + #{$class}__button-wrapper:hover { + #{$class}__label-wrapper { + left: 40px; + } + } + + #{$class}__label-wrapper { + left: 50px; + } + } + + &__label-wrapper { + pointer-events: none; + opacity: 0; + + background-color: var(--g-color-base-background); + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 100; + + transition: left 0.1s ease, right 0.1s ease, opacity 0.1s ease; + } +} diff --git a/src/editor-v2/components/Sidebar/Sidebar.tsx b/src/editor-v2/components/Sidebar/Sidebar.tsx new file mode 100644 index 000000000..09311f20e --- /dev/null +++ b/src/editor-v2/components/Sidebar/Sidebar.tsx @@ -0,0 +1,110 @@ +import React, {useMemo, useState} from 'react'; + +import {Code, FolderTree, Gear, Rectangles4, Square} from '@gravity-ui/icons'; +import {Button, Icon, Label} from '@gravity-ui/uikit'; +import {IconData} from '@gravity-ui/uikit/build/esm/components/Icon/Icon'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import BlockConfig from '../BlockConfig/BlockConfig'; +import BlocksList from '../BlocksList/BlocksList'; +import GlobalConfig from '../GlobalConfig/GlobalConfig'; +import SourceCode from '../SourceCode/SourceCode'; +import Tree from '../Tree/Tree'; + +import './Sidebar.scss'; + +const b = block('sidebar'); + +interface SidebarProps extends ClassNameProps { + position: 'left' | 'right'; + startMenu?: string; +} + +interface TabConfig { + id: string; + name: string; + icon: IconData; + component: React.ElementType; + position: 'left' | 'right' | 'both'; +} + +export const Sidebar = ({className, position, startMenu}: SidebarProps) => { + const [currentTab, setCurrentTab] = useState(startMenu || 'blocks-list'); + + const defaultTabs: TabConfig[] = useMemo( + () => [ + { + id: 'blocks-list', + name: 'Блоки', + icon: Rectangles4, + component: BlocksList, + position: 'left', + }, + { + id: 'global-config', + name: 'Глобальная конфигурация', + icon: Gear, + component: GlobalConfig, + position: 'left', + }, + { + id: 'tree', + name: 'Дерево', + icon: FolderTree, + component: Tree, + position: 'left', + }, + { + id: 'block-config', + name: 'Конфигурация блока', + icon: Square, + component: BlockConfig, + position: 'right', + }, + { + id: 'source-code', + name: 'Исходный код', + icon: Code, + component: SourceCode, + position: 'both', + }, + ], + [], + ); + + const filteredTabs = defaultTabs.filter( + ({position: tabPosition}) => tabPosition === 'both' || tabPosition === position, + ); + + const TabComponent = useMemo(() => { + const findTab = defaultTabs.find(({id}) => id === currentTab); + return findTab?.component; + }, [currentTab, defaultTabs]); + + return ( + <div className={`${b(null, className)} ${b('', {position})}`}> + <div className={b('buttons-wrapper')}> + <div className={b('buttons')}> + {filteredTabs.map(({id, icon, name}) => ( + <div className={b('button-wrapper')} key={id}> + <Button + view={currentTab === id ? 'action' : 'flat'} + size="m" + onClick={() => setCurrentTab(id)} + > + <Icon data={icon} size={18} /> + </Button> + <div className={b('label-wrapper')}> + <Label theme={'normal'} size={'m'}> + {name} + </Label> + </div> + </div> + ))} + </div> + </div> + <div className={b('body')}>{TabComponent && <TabComponent />}</div> + </div> + ); +}; diff --git a/src/editor-v2/components/Source/Source.scss b/src/editor-v2/components/Source/Source.scss new file mode 100644 index 000000000..d85ac31e6 --- /dev/null +++ b/src/editor-v2/components/Source/Source.scss @@ -0,0 +1,18 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}source'; + +#{$block} { + display: inline-flex; + align-items: center; + padding: 0 8px; + gap: 8px; + + &__text { + flex: 1; + } +} diff --git a/src/editor-v2/components/Source/Source.tsx b/src/editor-v2/components/Source/Source.tsx new file mode 100644 index 000000000..a9e065370 --- /dev/null +++ b/src/editor-v2/components/Source/Source.tsx @@ -0,0 +1,53 @@ +import React, {useCallback, useContext} from 'react'; + +import {ArrowRotateRight} from '@gravity-ui/icons'; +import {Button, Icon, TextInput} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {useEditorStore} from '../../context/editorContext'; +import {IframeContext, useIframeStore} from '../../context/iframeContext'; + +import './Source.scss'; + +const b = block('source'); + +interface SourceProps extends ClassNameProps {} + +const Source: React.FC<SourceProps> = ({className}) => { + const {url, setUrl} = useIframeStore(); + const {resetInitialize} = useEditorStore(); + const {disableUrlField} = useContext(IframeContext); + + const onUpdateUrl = useCallback( + (value: string) => { + setUrl(value); + resetInitialize(); + }, + [resetInitialize, setUrl], + ); + + const reloadIframe = () => { + setUrl(''); + setTimeout(() => { + setUrl(url); + }, 0); + }; + + return ( + <div className={b(null, className)}> + <Button view="flat" size="m" onClick={reloadIframe}> + <Icon data={ArrowRotateRight} size={18} /> + </Button> + <TextInput + disabled={disableUrlField} + className={b('text')} + size="s" + value={url} + onUpdate={onUpdateUrl} + /> + </div> + ); +}; + +export default Source; diff --git a/src/editor-v2/components/SourceCode/SourceCode.scss b/src/editor-v2/components/SourceCode/SourceCode.scss new file mode 100644 index 000000000..c63709db2 --- /dev/null +++ b/src/editor-v2/components/SourceCode/SourceCode.scss @@ -0,0 +1,26 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}source-code'; + +#{$block} { + padding: 10px; + white-space: pre; + + @include text-code-inline-1; + + display: flex; + flex-direction: column; + gap: 10px; + + &__code { + user-select: all; + } + + &__textarea textarea { + @include text-code-inline-1; + } +} diff --git a/src/editor-v2/components/SourceCode/SourceCode.tsx b/src/editor-v2/components/SourceCode/SourceCode.tsx new file mode 100644 index 000000000..02b52d162 --- /dev/null +++ b/src/editor-v2/components/SourceCode/SourceCode.tsx @@ -0,0 +1,63 @@ +import React, {useState} from 'react'; + +import {Button, Dialog, TextArea} from '@gravity-ui/uikit'; + +import {ClassNameProps, PageContent} from '../../../models'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig/hooks/useContentConfigStore'; + +import './SourceCode.scss'; + +const b = block('source-code'); + +interface SourceCodeProps extends ClassNameProps {} + +const SourceCode: React.FC<SourceCodeProps> = ({className}) => { + const {config, setConfig} = useContentConfigStore(); + const [isOpen, setIsOpen] = useState(false); + const [tempConfig, setTempConfig] = useState(''); + + const onUpdate = () => { + let object; + + try { + object = JSON.parse(tempConfig); + } catch { + // eslint-disable-next-line no-console + console.error('JSON.parse failed'); + } + + setConfig(object as PageContent); + setIsOpen(false); + }; + + return ( + <div className={b(null, className)}> + <Button onClick={() => setIsOpen(true)}>Update</Button> + <div className={b('code')}>{JSON.stringify(config, null, 2)}</div> + + <Dialog onClose={() => setIsOpen(false)} open={isOpen} size={'l'}> + <Dialog.Header caption="New configuration" /> + <Dialog.Body> + <TextArea + className={b('textarea')} + value={tempConfig} + onUpdate={setTempConfig} + rows={30} + /> + </Dialog.Body> + <Dialog.Footer + showError={false} + listenKeyEnter={true} + preset={'default'} + textButtonApply={'Apply'} + textButtonCancel={'Cancel'} + onClickButtonApply={onUpdate} + onClickButtonCancel={() => setIsOpen(false)} + /> + </Dialog> + </div> + ); +}; + +export default SourceCode; diff --git a/src/editor-v2/components/TopBar/TopBar.scss b/src/editor-v2/components/TopBar/TopBar.scss new file mode 100644 index 000000000..506b6ae87 --- /dev/null +++ b/src/editor-v2/components/TopBar/TopBar.scss @@ -0,0 +1,34 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}topbar'; + +#{$block} { + display: flex; + justify-content: start; + align-items: center; + width: 100%; + height: 40px; + + border-bottom: 1px solid var(--g-color-line-generic); + + &__switches { + display: flex; + align-items: center; + width: auto; + height: 100%; + padding: 0 12px; + gap: 12px; + + border-right: 1px solid var(--g-color-line-generic); + } + + &__source { + flex: 1; + width: 100%; + box-sizing: border-box; + } +} diff --git a/src/editor-v2/components/TopBar/TopBar.tsx b/src/editor-v2/components/TopBar/TopBar.tsx new file mode 100644 index 000000000..af8566fcf --- /dev/null +++ b/src/editor-v2/components/TopBar/TopBar.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import Source from '../Source/Source'; +import ViewSwitches from '../ViewSwitches/ViewSwitches'; + +import './TopBar.scss'; + +const b = block('topbar'); + +interface TopBarProps extends ClassNameProps {} + +const TopBar: React.FC<TopBarProps> = ({className}) => { + return ( + <div className={b(null, className)}> + <div className={b('switches')}> + <ViewSwitches /> + </div> + <Source className={b('source')} /> + </div> + ); +}; + +export default TopBar; diff --git a/src/editor-v2/components/Tree/Tree.scss b/src/editor-v2/components/Tree/Tree.scss new file mode 100644 index 000000000..628987a44 --- /dev/null +++ b/src/editor-v2/components/Tree/Tree.scss @@ -0,0 +1,38 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}tree'; + +#{$block} { + padding: 12px; + + &__item { + padding: 8px; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + @for $i from 1 through 9 { + &_deep_#{$i} { + position: relative; + margin-left: 16px * $i; + width: calc(100% - 16px * $i); + + &:before { + content: ''; + position: absolute; + right: calc(100% + 12px); + height: 100%; + width: 2px; + background-color: var(--g-color-line-generic-accent); + top: 0; + } + } + } + } +} diff --git a/src/editor-v2/components/Tree/Tree.tsx b/src/editor-v2/components/Tree/Tree.tsx new file mode 100644 index 000000000..e3c2dc5fe --- /dev/null +++ b/src/editor-v2/components/Tree/Tree.tsx @@ -0,0 +1,54 @@ +import React, {PropsWithChildren} from 'react'; + +import {Card} from '@gravity-ui/uikit'; + +import {ItemConfig} from '../../../common/types'; +import {block} from '../../../utils'; +import {useContentConfigStore} from '../../context/contentConfig'; + +import './Tree.scss'; + +const b = block('tree'); + +export interface TreeProps { + blocks: ItemConfig[]; +} + +type TreeItem = { + type: string; + children?: TreeItem[]; +}; + +const generateTree = (items: TreeItem[]): TreeItem[] => { + return items.map((item) => { + let children; + + if ('children' in item && item.children?.length) { + children = generateTree(item.children); + } + + return { + type: item.type, + children, + }; + }); +}; + +const Tree = (_p: PropsWithChildren<TreeProps>) => { + const {config} = useContentConfigStore(); + + const blockTree = generateTree(config.blocks); + + const renderTree = (items: TreeItem[], deepLevel = 0) => { + return items.map(({type, children}, index) => ( + <React.Fragment key={index}> + <Card className={b('item', {deep: deepLevel})}>{type}</Card> + {children && renderTree(children, deepLevel + 1)} + </React.Fragment> + )); + }; + + return <div className={b()}>{renderTree(blockTree)}</div>; +}; + +export default Tree; diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.scss b/src/editor-v2/components/ViewSwitches/ViewSwitches.scss new file mode 100644 index 000000000..ca2c5f7be --- /dev/null +++ b/src/editor-v2/components/ViewSwitches/ViewSwitches.scss @@ -0,0 +1,12 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}view-switches'; + +#{$block} { + display: inline-flex; + gap: 12px; +} diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx new file mode 100644 index 000000000..b96b73fe0 --- /dev/null +++ b/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import {Display, Eye, Smartphone, Square, SquareDashedText} from '@gravity-ui/icons'; +import {Icon, RadioButton, RadioButtonOption} from '@gravity-ui/uikit'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; + +import './ViewSwitches.scss'; + +const b = block('view-switches'); + +interface ViewSwitchesProps extends ClassNameProps {} + +const options1: RadioButtonOption[] = [ + {value: 'edit-mode', content: <Icon data={SquareDashedText} />}, + {value: 'preview-mode', content: <Icon data={Eye} />}, +]; + +export const options2: RadioButtonOption[] = [ + {value: 'desktop-mode', content: <Icon data={Display} />}, + {value: 'tablet-mode', content: <Icon data={Square} />}, + {value: 'phone-mode', content: <Icon data={Smartphone} />}, +]; + +// TODO: To be done +const ViewSwitches: React.FC<ViewSwitchesProps> = ({className}) => { + return ( + <div className={b(null, className)}> + <RadioButton + className={b('switch')} + name="group1" + defaultValue={options1[0].value} + options={options1} + size="m" + disabled + /> + <RadioButton + className={b('switch')} + name="group2" + defaultValue={options2[0].value} + options={options2} + size="m" + disabled + /> + </div> + ); +}; + +export default ViewSwitches; diff --git a/src/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss new file mode 100644 index 000000000..b6dea3b88 --- /dev/null +++ b/src/editor-v2/containers/Editor/Editor.scss @@ -0,0 +1,57 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}editor'; + +#{$block} { + margin: 0; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + height: 100%; + width: 100%; + position: relative; + + &__header { + height: $headerHeight; + width: 100%; + border-bottom: 1px solid var(--g-color-line-generic); + } + + &__body { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + width: 100%; + max-height: calc(100% - $headerHeight); + } + + &__canvas { + flex: 1; + } + + &__draggable { + width: 12px; + height: 100%; + cursor: col-resize; + background-color: var(--g-color-line-generic); + + display: flex; + align-items: center; + justify-content: center; + + color: var(--g-color-base-background); + } + + &__overlay { + position: absolute; + height: 100%; + width: 100%; + z-index: 100; + } +} diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx new file mode 100644 index 000000000..d0fb5b641 --- /dev/null +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -0,0 +1,107 @@ +import React, {useCallback} from 'react'; + +import {Grip} from '@gravity-ui/icons'; +import {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; + +import {ActionTypes} from '../../../common/types'; +import {PageContent} from '../../../models'; +import {block} from '../../../utils'; +import BigOverlay from '../../components/BigOverlay/BigOverlay'; +import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; +import {Sidebar} from '../../components/Sidebar/Sidebar'; +import {ContentConfigProvider} from '../../context/contentConfig'; +import {EditorProvider, useEditorStore} from '../../context/editorContext'; +import {IframeProvider} from '../../context/iframeContext'; +import {PostMessageProvider} from '../../context/messagesContext'; +import {useMessageSender} from '../../context/messagesContext/hooks/useMessageSender'; + +import useAdminInitialize from './hooks/useAdminInitialize'; + +import './Editor.scss'; + +const b = block('editor'); + +interface EditorViewProps { + content: PageContent; + onUpdate?: (pageContent: PageContent) => void; + initialUrl: string; + disableUrlField?: boolean; +} + +const EditorView = (_props: EditorViewProps) => { + const {manipulateOverlayMode} = useEditorStore(); + const sendMessage = useMessageSender(); + + useAdminInitialize(); + + // Disable insert mode on any MouseUp event + // Maybe should be attached to body + const onMouseUp = useCallback( + (e: React.MouseEvent) => { + if (manipulateOverlayMode === 'insert') { + e.preventDefault(); + sendMessage({type: ActionTypes.InsertModeDisable, payload: undefined}); + } + if (manipulateOverlayMode === 'reorder') { + e.preventDefault(); + sendMessage({type: ActionTypes.ReorderModeDisable, payload: undefined}); + } + }, + [manipulateOverlayMode, sendMessage], + ); + + const onMouseMove = useCallback( + (e: React.MouseEvent) => { + if (manipulateOverlayMode) { + sendMessage({ + type: ActionTypes.OverlayModeOnMove, + payload: {cursor: {x: e.clientX, y: e.clientY}}, + }); + } + }, + [manipulateOverlayMode, sendMessage], + ); + + return ( + <div className={b()} onMouseUp={onMouseUp} onMouseMove={onMouseMove}> + <div className={b('body')}> + <PanelGroup + className={b('panel')} + autoSaveId="page-constructor-editor" + direction="horizontal" + > + <Panel collapsible defaultSize={25} minSize={15}> + <Sidebar position={'left'} /> + </Panel> + <PanelResizeHandle className={b('draggable')}> + <Grip className={b('grip')} /> + </PanelResizeHandle> + <Panel minSize={20}> + <MiddleScreen /> + </Panel> + <PanelResizeHandle className={b('draggable')}> + <Grip className={b('grip')} /> + </PanelResizeHandle> + <Panel collapsible minSize={15} defaultSize={25}> + <Sidebar position={'right'} startMenu="block-config" /> + </Panel> + </PanelGroup> + </div> + <BigOverlay className={b('overlay')} /> + </div> + ); +}; + +export const Editor = (props: EditorViewProps) => { + return ( + <EditorProvider> + <IframeProvider initialUrl={props.initialUrl} disableUrlField={props.disableUrlField}> + <ContentConfigProvider onUpdate={props.onUpdate} content={props.content}> + <PostMessageProvider> + <EditorView {...props} /> + </PostMessageProvider> + </ContentConfigProvider> + </IframeProvider> + </EditorProvider> + ); +}; diff --git a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx new file mode 100644 index 000000000..9bc69e9f4 --- /dev/null +++ b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx @@ -0,0 +1,39 @@ +import {useContext, useEffect} from 'react'; + +import {ActionTypes, IframeReadyAction} from '../../../../common/types'; +import {ContentConfigContext, useContentConfigStore} from '../../../context/contentConfig'; +import {useEditorStore} from '../../../context/editorContext'; +import {useMessageObserver, useMessageSender} from '../../../context/messagesContext'; + +const useAdminInitialize = () => { + const {state: contentConfigState} = useContext(ContentConfigContext); + const {config} = useContentConfigStore(); + const {initialized} = useEditorStore(); + const sendMessage = useMessageSender(); + + useMessageObserver<IframeReadyAction>( + ActionTypes.IframeReady, + () => { + if (sendMessage && !initialized) { + sendMessage({type: ActionTypes.EditorReady, payload: undefined}); + sendMessage( + {type: ActionTypes.UpdateConfigs, payload: {content: config}}, + {direction: 'pc'}, + ); + } + }, + [sendMessage], + ); + + // Update configs for both instances on any changes + useEffect( + () => + contentConfigState && + contentConfigState.subscribe((state) => { + sendMessage({type: ActionTypes.UpdateConfigs, payload: {content: state.config}}); + }), + [sendMessage, contentConfigState], + ); +}; + +export default useAdminInitialize; diff --git a/src/editor-v2/containers/__stories__/Editor.stories.tsx b/src/editor-v2/containers/__stories__/Editor.stories.tsx new file mode 100644 index 000000000..3ade46fb6 --- /dev/null +++ b/src/editor-v2/containers/__stories__/Editor.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import {Meta, StoryFn} from '@storybook/react'; + +import {PageContent} from '../../../models'; +import {Editor} from '../Editor/Editor'; + +import data from './data.json'; + +export default { + title: 'Editor/Main 2.0', + component: Editor, +} as Meta; + +const DefaultTemplate: StoryFn = (args) => { + return ( + <div style={{height: '100vh', width: '100vw'}}> + <Editor + {...args} + content={data as unknown as PageContent} + initialUrl={'http://localhost:3000'} + /> + </div> + ); +}; + +export const Default = DefaultTemplate.bind({}); + +// Default.args = data.default; diff --git a/src/editor-v2/containers/__stories__/data.json b/src/editor-v2/containers/__stories__/data.json new file mode 100644 index 000000000..396e3394e --- /dev/null +++ b/src/editor-v2/containers/__stories__/data.json @@ -0,0 +1,335 @@ +{ + "blocks": [ + { + "type": "header-block", + "title": "Yandex Open Source", + "description": "<p>Мы в Яндексе верим, что вклад в опенсорс — это вклад в технологическую эволюцию: без открытости, совместной работы и поддержки развитие IT‑индустрии сильно затруднено. Уже много лет мы используем в своих продуктах сторонние открытые технологии, а также делимся собственными и активно вовлекаем в их развитие разработчиков по всему миру.</p>", + "width": "s", + "verticalOffset": "l", + "offset": "default", + "resetPaddings": true, + "background": { + "image": { + "mobile": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover-m.png", + "desktop": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover.png" + }, + "color": "#EFF2F8", + "fullWidth": false, + "fullWidthMedia": true + } + }, + { + "type": "extended-features-block", + "title": { + "text": "Почему мы выкладываем наши технологии в открытый доступ?" + }, + "items": [ + { + "title": "Ответственность", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-01.svg", + "text": "<p>Мы верим, что вкладываться в развитие опенсорс‑технологий — это ответственность каждого технологического лидера на рынке. Без опенсорс‑решений не появились бы многие продукты и сервисы не только Яндекса, но и других крупных компаний, и мы хотим отдавать обратно, делиться теми нашими решениями, которые, как мы считаем, принесут реальную пользу.</p>" + }, + { + "title": "Польза для сообщества", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-02.svg", + "text": "<p>Технологии, которые мы разрабатываем, ежедневно помогают нам эффективно решать огромное количество самых разных задач в наших сервисах. Мы знаем, что разработчики вне Яндекса часто сталкиваются с теми же самыми задачами — и верим, что наши технологии могут быть полезны и им.</p>" + }, + { + "title": "Качество сервисов", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-03.svg", + "text": "<p>Для нас важно разрабатывать и использовать только качественные технологические решения. В особенности это касается опенсорса: зная, что наши решения увидят и будут использовать другие, мы уделяем их качеству особое внимание. А уже в открытом доступе у технологии больше шансов развиваться и улучшаться — в том числе, при участии сообщества разработчиков.</p>" + }, + { + "title": "Бизнес‑потенциал", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-04.svg", + "text": "<p>Мы верим, что при условии роста популярности наших решений и спроса на них со стороны сообщества, то, что мы выкладываем в опенсорс, может далее стать для нас бизнесом. То, что мы выкладываем в опенсорс, можно использовать и во внешних коммерческих проектах.</p>" + }, + { + "title": "Поиск талантов", + "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-05.svg", + "text": "<p>Мы ценим каждого, кто вкладывается в сторонние опенсорс‑решения или делится с миром своими. Контрибьюторы в наши продукты нам особенно важны: среди них мы ищем и находим тех, кто сможет развивать технологии уже будучи частью команды Яндекса.</p>" + } + ] + }, + { + "type": "card-layout-block", + "animated": false, + "title": "Краткая история опенсорса в Яндексе", + "description": "<p>С начала истории развития опенсорса в Яндексе мы успели выложить в открытый доступ десятки собственных проектов, использовать в разработке наших продуктов внешние технологии, а также внесли существенный вклад в их развитие.</p>", + "colSizes": { + "all": 12, + "lg": 3, + "md": 4, + "sm": 6 + }, + "anchor": { + "url": "history", + "text": "history" + }, + "children": [ + { + "type": "layout-item", + "content": { + "title": "2010", + "text": "<p>Методология веб‑разработки БЭМ (Блок‑Элемент‑Модификатор) выходит в оперсорс</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-01.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2012", + "text": "<p>Запуск Яндекс Браузера на базе Blink (Chromium)</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-02.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2013", + "text": "<p>Яндекс начинает контрибьютить в ядро Linux</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-03.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2016", + "text": "<p>Выход в опенсорс ClickHouse</p>\\n<p>Выход в опенсорс Hermione (с 2024 года — Testplane)</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2016.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2017", + "text": "<p>Выход в опенсорс CatBoost<br />\\nЯндекс начинает контрибьютить в PostgreSQL</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-05.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2018", + "text": "<p>Выход в опенсорс Одиссея</p>\\n<p>Яндекс — топ‑контрибьютор в WAL‑G</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-06.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2019", + "text": "<p>В Яндексе появляется команда разработки СУБД с открытым исходным кодом</p>\\n<p>Яндекс — спонсор разработки PostgreSQL</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-07.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2020", + "text": "<p>Выход в опенсорс Testsuite</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2020.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2022", + "text": "<p>Яндекс — один из основных спонсоров разработки PostgreSQL</p>\\n<p>Выход в опенсорс YDB, userver, YaLM 100B, DivKit, Yatagan</p>\\n<p>Старт программы «Код для всех»</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-09.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2023", + "text": "<p>Выход в опенсорс YTsaurus, Gravity UI, AppMetrica, Diplodoc, DataLens и счётчика Метрики</p>\\n<p>Старт Программы грантов Yandex Open Source</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2023.png" + } + }, + { + "type": "layout-item", + "content": { + "title": "2024", + "text": "<p>Первый Yandex Open Source Jam</p>\\n<p>Выход в опенсорс YaFSDP</p>" + }, + "media": { + "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2024.png" + } + } + ] + }, + { + "type": "card-layout-block", + "title": "Наши проекты", + "description": "<p>В Яндексе мы разрабатываем и развиваем технологические решения самых разных сфер применения, размеров и сложности. Поэтому и в открытый доступ попадают самые разные проекты — главное, чтобы они приносили пользу не только нам, но и другим.</p>", + "colSizes": { + "all": 12, + "lg": 3, + "md": 4, + "sm": 6 + }, + "anchor": { + "url": "projects", + "text": "projects" + }, + "children": [ + { + "type": "background-card", + "title": "YDB", + "text": "<p>Отказоустойчивая распределённая SQL база данных</p>", + "backgroundColor": "#2399FF", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-ydb.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "background-card", + "title": "YTsaurus", + "text": "<p>Платформа для хранения и обработки больших данных</p>", + "backgroundColor": "#FFB23E", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-yt.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "GravityUI", + "text": "<p>Библиотеки для создания интерфейсов</p>", + "backgroundColor": "#262626", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-gui.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "background-card", + "title": "DivKit", + "text": "<p>Фреймворк для server‑driven интерфейсов</p>", + "backgroundColor": "#F1F1F1", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dk.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "Diplodoc", + "text": "<p>Платформа для написания документации в концепции Docs as Code</p>", + "backgroundColor": "#79F985", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dd.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "userver", + "text": "<p>Фреймворк для создания высоконагруженных приложений</p>", + "backgroundColor": "#FF9D73", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-u.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "light" + }, + { + "type": "background-card", + "title": "DataLens", + "text": "<p>BI-платформа для анализа и визуализации данных</p>", + "backgroundColor": "#FF7132", + "background": { + "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dl.png", + "alt": "card-background" + }, + "paddingBottom": "m", + "theme": "dark" + }, + { + "type": "basic-card", + "title": "И это ещё не всё", + "text": "<p>Узнать про наши опенсорс‑проекты больше вы можете на другой странице</p>", + "border": "none", + "buttons": [ + { + "text": "Все проекты", + "primary": true, + "theme": "monochrome", + "size": "promo", + "url": "/projects" + } + ] + } + ] + }, + { + "largeMedia": true, + "mediaOnly": false, + "size": "l", + "type": "media-block", + "direction": "content-media", + "anchor": { + "url": "1", + "text": "Фильм" + }, + "title": "<p>Смотрите фильм с YaC 22</p>", + "description": "<p>Руководители опенсорс‑проектов рассказывают про историю и культуру открытого кода в Яндексе.</p>", + "media": { + "youtube": "https://www.youtube.com/watch?v=G7G286S8ntc", + "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/video-cover.png" + }, + "disableShadow": true + }, + { + "type": "content-layout-block", + "size": "l", + "centered": true, + "textContent": { + "title": "", + "text": "", + "additionalInfo": "", + "buttons": [ + { + "text": "Сделано на GravityUI", + "theme": "monochrome", + "img": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/open-source-gravity-ui-button.svg", + "url": "https://gravity-ui.com/" + } + ] + } + } + ] +} diff --git a/src/editor-v2/containers/__stories__/utils.ts b/src/editor-v2/containers/__stories__/utils.ts new file mode 100644 index 000000000..7d9be4882 --- /dev/null +++ b/src/editor-v2/containers/__stories__/utils.ts @@ -0,0 +1,22 @@ +import isEqual from 'lodash/isEqual'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const memoizeLast = (fn: (...args: any[]) => any) => { + let cacheKey: Parameters<typeof fn>; + let cacheResult: ReturnType<typeof fn>; + + return (...params: Parameters<typeof fn>) => { + if (!isEqual(params, cacheKey)) { + try { + const result = fn(...params); + + cacheResult = result; + cacheKey = params; + } catch { + return cacheResult; + } + } + + return cacheResult; + }; +}; diff --git a/src/editor-v2/context/contentConfig/contentConfigContext.tsx b/src/editor-v2/context/contentConfig/contentConfigContext.tsx new file mode 100644 index 000000000..9ae9e2791 --- /dev/null +++ b/src/editor-v2/context/contentConfig/contentConfigContext.tsx @@ -0,0 +1,15 @@ +/** + * Context for managing PC configuration (aka Yaml Configs) + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {ContentConfigStore} from './store'; + +export interface ContentConfigContextProps { + state?: StoreApi<ContentConfigStore>; +} + +export const ContentConfigContext = React.createContext<ContentConfigContextProps>({}); diff --git a/src/editor-v2/context/contentConfig/contentConfigProvider.tsx b/src/editor-v2/context/contentConfig/contentConfigProvider.tsx new file mode 100644 index 000000000..18a7f6276 --- /dev/null +++ b/src/editor-v2/context/contentConfig/contentConfigProvider.tsx @@ -0,0 +1,34 @@ +import React, {PropsWithChildren, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {PageContent} from '../../../models'; + +import {ContentConfigContext} from './contentConfigContext'; +import {ContentConfigStore, createContentConfigStore} from './store'; + +interface ContentConfigProviderProps extends PropsWithChildren { + content: PageContent; + onUpdate?: (pageContent: PageContent) => void; +} + +export const ContentConfigProvider = ({ + content, + onUpdate, + children, +}: ContentConfigProviderProps) => { + const storeRef = useRef<StoreApi<ContentConfigStore>>(); + + if (!storeRef.current) { + storeRef.current = createContentConfigStore({config: content}); + if (onUpdate) { + storeRef.current?.subscribe((state) => onUpdate(state.config)); + } + } + + return ( + <ContentConfigContext.Provider value={{state: storeRef.current}}> + {children} + </ContentConfigContext.Provider> + ); +}; diff --git a/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx b/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx new file mode 100644 index 000000000..40d57fd1a --- /dev/null +++ b/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx @@ -0,0 +1,15 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {ContentConfigContext} from '../contentConfigContext'; + +export const useContentConfigStore = () => { + const {state} = useContext(ContentConfigContext); + if (!state) { + throw new Error('Missing ContentConfigContext'); + } + return useStore(state); +}; + +export default useContentConfigStore; diff --git a/src/editor-v2/context/contentConfig/index.ts b/src/editor-v2/context/contentConfig/index.ts new file mode 100644 index 000000000..4772ca299 --- /dev/null +++ b/src/editor-v2/context/contentConfig/index.ts @@ -0,0 +1,4 @@ +export * from './contentConfigContext'; +export * from './contentConfigProvider'; +export * from './hooks/useContentConfigStore'; +export * from './store'; diff --git a/src/editor-v2/context/contentConfig/store.ts b/src/editor-v2/context/contentConfig/store.ts new file mode 100644 index 000000000..819362418 --- /dev/null +++ b/src/editor-v2/context/contentConfig/store.ts @@ -0,0 +1,202 @@ +import _ from 'lodash'; + +import {ActionTypes, ConfigInput, ItemConfig, WithStoreReducer} from '../../../common/types'; +import {ConstructorBlock, PageContent} from '../../../models'; +import {DynamicFormValue} from '../../components/DynamicForm/DynamicForm'; +import { + duplicateArrayItem, + generateChildrenPathFromArray, + getDestinationShiftBeforeReorder, + insert, + isItemsNeighbours, + modifyObjectByPath, + removeFromArray, + reorderArrayItems, +} from '../../utils'; +import {initializeStore} from '../../utils/store'; + +export interface ContentConfigState { + config: PageContent; + blocks: Array<ItemConfig>; + subBlocks: Array<ItemConfig>; + global: Array<ConfigInput>; + + preInsertBlockType: string | null; + preReorderBlockPath: number[] | null; +} + +export interface ContentConfigMethods extends WithStoreReducer { + setConfig: (config: PageContent) => void; + setBlocks: (blocks: ItemConfig[]) => void; + setSubBlocks: (subBlocks: ItemConfig[]) => void; + insertBlock: (position: number[], blockType: string) => void; + deleteBlock: (path: number[]) => void; + duplicateBlock: (path: number[]) => void; + reorderBlock: (path: number[], destination: number[]) => void; + updateField: (path: string, value: DynamicFormValue) => void; +} + +export type ContentConfigStore = ContentConfigState & ContentConfigMethods; + +export const createContentConfigStore = initializeStore<ContentConfigState, ContentConfigMethods>( + { + config: {blocks: []}, + blocks: [], + subBlocks: [], + global: [], + preInsertBlockType: null, + preReorderBlockPath: null, + }, + (set, get) => ({ + setConfig: (config) => set((state) => ({...state, config})), + setBlocks: (blocks) => set((state) => ({...state, blocks})), + setSubBlocks: (subBlocks) => set((state) => ({...state, subBlocks})), + insertBlock: (arrayPath, blockType) => { + const blocksConfig = get().config.blocks; + const blocksData = get().blocks; + + const foundBlock = blocksData.find(({type}) => type === blockType); + const defaultValue = + foundBlock && foundBlock.schema.default + ? {...foundBlock.schema.default, type: blockType} + : {type: blockType}; + + const newBlocksConfig = modifyObjectByPath( + blocksConfig, + arrayPath, + (parentBlocks, index) => + insert(parentBlocks, index, defaultValue as ConstructorBlock), + ); + + set((state) => ({ + ...state, + config: {...state.config, blocks: newBlocksConfig}, + })); + }, + deleteBlock: (arrayPath) => { + const blocksConfig = get().config.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); + + set((state) => ({ + ...state, + config: {...state.config, blocks: newBlocksConfig}, + })); + }, + duplicateBlock: (arrayPath) => { + const blocksConfig = get().config.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem); + + set((state) => ({ + ...state, + config: {...state.config, blocks: newBlocksConfig}, + })); + }, + updateField: (path, value) => { + set((state) => { + const newConfig = _.set(state.config, path, value); + return { + ...state, + config: newConfig, + }; + }); + }, + reorderBlock: (arrayPath, destination) => { + let newBlocksConfig: ConstructorBlock[]; + const blocksConfig = get().config.blocks; + // Copy + const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath)); + + if (isItemsNeighbours(arrayPath, destination)) { + newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => { + return reorderArrayItems( + parentBlocks, + arrayPath[arrayPath.length - 1], + destination[destination.length - 1], + ); + }); + } else { + const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); + + // Delete + const blocksConfigWithoutBlock = modifyObjectByPath( + blocksConfig, + arrayPath, + removeFromArray, + ); + // Paste + newBlocksConfig = modifyObjectByPath( + blocksConfigWithoutBlock, + arrayDest, + (parentBlocks, index) => insert(parentBlocks, index, copiedBlock), + ); + } + + set((state) => ({ + ...state, + config: {...state.config, blocks: newBlocksConfig}, + })); + }, + reducer: function (action) { + switch (action.type) { + // Insert Actions + case ActionTypes.InsertModeEnable: { + set((state) => ({ + ...state, + preInsertBlockType: action.payload.blockType, + })); + break; + } + case ActionTypes.InsertModeDisable: { + set((state) => ({ + ...state, + preInsertBlockType: null, + })); + break; + } + + case ActionTypes.InsertBlock: { + const {preInsertBlockType} = get(); + if (preInsertBlockType) { + const {path} = action.payload; + this.insertBlock(path, preInsertBlockType); + } + break; + } + // Reorder Actions + case ActionTypes.ReorderModeEnable: { + set((state) => ({ + ...state, + preReorderBlockPath: action.payload.path, + })); + break; + } + case ActionTypes.ReorderModeDisable: { + set((state) => ({ + ...state, + preReorderBlockPath: null, + })); + break; + } + case ActionTypes.ReorderBlocks: { + const {preReorderBlockPath} = get(); + if (preReorderBlockPath) { + const {path: destinationPath} = action.payload; + this.reorderBlock(preReorderBlockPath, destinationPath); + } + break; + } + case ActionTypes.BlocksConfigs: { + set((state) => ({ + ...state, + blocks: action.payload.blocks, + subBlocks: action.payload.subBlocks, + global: action.payload.global, + })); + break; + } + } + }, + }), +); diff --git a/src/editor-v2/context/editorContext/editorContext.tsx b/src/editor-v2/context/editorContext/editorContext.tsx new file mode 100644 index 000000000..23a9cce22 --- /dev/null +++ b/src/editor-v2/context/editorContext/editorContext.tsx @@ -0,0 +1,16 @@ +/** + * Context for managing internal Editor states + * Same exist in PC + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorStore} from './store'; + +export interface EditorContextProps { + state?: StoreApi<EditorStore>; +} + +export const EditorContext = React.createContext<EditorContextProps>({}); diff --git a/src/editor-v2/context/editorContext/editorProvider.tsx b/src/editor-v2/context/editorContext/editorProvider.tsx new file mode 100644 index 000000000..64dcb447b --- /dev/null +++ b/src/editor-v2/context/editorContext/editorProvider.tsx @@ -0,0 +1,20 @@ +import React, {PropsWithChildren, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorContext} from './editorContext'; +import {EditorStore, createEditorStore} from './store'; + +export const EditorProvider = ({children}: PropsWithChildren) => { + const storeRef = useRef<StoreApi<EditorStore>>(); + + if (!storeRef.current) { + storeRef.current = createEditorStore(); + } + + return ( + <EditorContext.Provider value={{state: storeRef.current}}> + {children} + </EditorContext.Provider> + ); +}; diff --git a/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx b/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx new file mode 100644 index 000000000..4f2a346cd --- /dev/null +++ b/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx @@ -0,0 +1,17 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {EditorContext} from '../editorContext'; + +export const useEditorStore = () => { + const {state} = useContext(EditorContext); + + if (!state) { + throw new Error('Missing EditorContext'); + } + + return useStore(state); +}; + +export default useEditorStore; diff --git a/src/editor-v2/context/editorContext/index.ts b/src/editor-v2/context/editorContext/index.ts new file mode 100644 index 000000000..c7b944eea --- /dev/null +++ b/src/editor-v2/context/editorContext/index.ts @@ -0,0 +1,4 @@ +export * from './editorContext'; +export * from './editorProvider'; +export * from './hooks/useEditorStore'; +export * from './store'; diff --git a/src/editor-v2/context/editorContext/store.ts b/src/editor-v2/context/editorContext/store.ts new file mode 100644 index 000000000..9c6085f75 --- /dev/null +++ b/src/editor-v2/context/editorContext/store.ts @@ -0,0 +1,101 @@ +import {Action, ActionTypes, WithStoreReducer} from '../../../common/types'; +import {initializeStore} from '../../utils/store'; + +export interface EditorState { + manipulateOverlayMode: 'insert' | 'reorder' | false; + selectedBlock?: { + path: number[]; + rect: DOMRect; + }; + initialized: boolean; +} + +export interface EditorMethods extends WithStoreReducer { + resetInitialize: () => void; +} + +export type EditorStore = EditorState & EditorMethods; + +export const createEditorStore = initializeStore<EditorState, EditorMethods>( + { + manipulateOverlayMode: false, + initialized: false, + }, + (set, get) => ({ + resetInitialize: () => { + set((state) => ({ + ...state, + initialized: false, + })); + }, + reducer: (action: Action) => { + switch (action.type) { + case ActionTypes.IframeReady: { + set((state) => ({ + ...state, + initialized: true, + })); + break; + } + case ActionTypes.ReorderModeEnable: { + set((state) => ({ + ...state, + manipulateOverlayMode: 'reorder', + })); + break; + } + case ActionTypes.ReorderModeDisable: { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + })); + break; + } + case ActionTypes.InsertModeEnable: { + set((state) => ({ + ...state, + manipulateOverlayMode: 'insert', + })); + break; + } + case ActionTypes.InsertModeDisable: { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + })); + break; + } + case ActionTypes.SelectBlock: { + set((state) => ({ + ...state, + selectedBlock: { + path: action.payload.path, + rect: action.payload.rect, + }, + })); + break; + } + case ActionTypes.UpdateSelectedBlockRect: { + const {selectedBlock} = get(); + if (selectedBlock) { + set((state) => ({ + ...state, + selectedBlock: { + ...selectedBlock, + rect: action.payload.rect, + }, + })); + } + break; + } + case ActionTypes.ReorderBlocks: { + set((state) => ({ + ...state, + selectedBlock: undefined, + })); + break; + } + } + }, + }), +); diff --git a/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx b/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx new file mode 100644 index 000000000..87369816c --- /dev/null +++ b/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx @@ -0,0 +1,15 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {IframeContext} from '../iframeContext'; + +export const useIframeStore = () => { + const {state} = useContext(IframeContext); + if (!state) { + throw new Error('Missing IframeContext'); + } + return useStore(state); +}; + +export default useIframeStore; diff --git a/src/editor-v2/context/iframeContext/iframeContext.tsx b/src/editor-v2/context/iframeContext/iframeContext.tsx new file mode 100644 index 000000000..5404b61a7 --- /dev/null +++ b/src/editor-v2/context/iframeContext/iframeContext.tsx @@ -0,0 +1,18 @@ +/** + * Context for iframe window + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {IframeStore} from './store'; + +export interface IframeContextProps { + state?: StoreApi<IframeStore>; + iframeElement?: HTMLIFrameElement; + setIframeElement: (element: HTMLIFrameElement) => void; + disableUrlField?: boolean; +} + +export const IframeContext = React.createContext<IframeContextProps>({setIframeElement: () => {}}); diff --git a/src/editor-v2/context/iframeContext/iframeProvider.tsx b/src/editor-v2/context/iframeContext/iframeProvider.tsx new file mode 100644 index 000000000..a8b7f56fb --- /dev/null +++ b/src/editor-v2/context/iframeContext/iframeProvider.tsx @@ -0,0 +1,36 @@ +import React, {PropsWithChildren, useRef, useState} from 'react'; + +import {StoreApi} from 'zustand'; + +import {IframeContext} from './iframeContext'; +import {IframeStore, createIframeStore} from './store'; + +interface IframeProviderProps extends PropsWithChildren { + initialUrl?: string; + disableUrlField?: boolean; +} + +export const IframeProvider = (props: IframeProviderProps) => { + const {children, initialUrl, disableUrlField} = props; + const storeRef = useRef<StoreApi<IframeStore>>(); + const [iframeElement, setIframeElement] = useState<HTMLIFrameElement>(); + + const setIframeElementFunc = (element: HTMLIFrameElement) => setIframeElement(element); + + if (!storeRef.current) { + storeRef.current = createIframeStore({url: initialUrl}); + } + + return ( + <IframeContext.Provider + value={{ + state: storeRef.current, + iframeElement, + setIframeElement: setIframeElementFunc, + disableUrlField, + }} + > + {children} + </IframeContext.Provider> + ); +}; diff --git a/src/editor-v2/context/iframeContext/index.ts b/src/editor-v2/context/iframeContext/index.ts new file mode 100644 index 000000000..7d73463ac --- /dev/null +++ b/src/editor-v2/context/iframeContext/index.ts @@ -0,0 +1,4 @@ +export * from './iframeContext'; +export * from './iframeProvider'; +export * from './hooks/useIframeStore'; +export * from './store'; diff --git a/src/editor-v2/context/iframeContext/store.ts b/src/editor-v2/context/iframeContext/store.ts new file mode 100644 index 000000000..756ba17fd --- /dev/null +++ b/src/editor-v2/context/iframeContext/store.ts @@ -0,0 +1,34 @@ +import {ActionTypes, WithStoreReducer} from '../../../common/types'; +import {initializeStore} from '../../utils/store'; + +export interface IframeState { + url: string; + height?: number; +} + +export interface IframeMethods extends WithStoreReducer { + setUrl: (url: string) => void; +} + +export type IframeStore = IframeState & IframeMethods; + +export const createIframeStore = initializeStore<IframeState, IframeMethods>( + { + url: '', + height: 400, + }, + (set) => ({ + setUrl: (url) => set((state) => ({...state, url})), + reducer: (action) => { + switch (action.type) { + case ActionTypes.SetHeight: { + set((state) => ({ + ...state, + height: action.payload.height + 200, + })); + break; + } + } + }, + }), +); diff --git a/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx b/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx new file mode 100644 index 000000000..7414cad43 --- /dev/null +++ b/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx @@ -0,0 +1,23 @@ +import {DependencyList, useEffect} from 'react'; + +import {Action, Meta} from '../../../../common/types'; + +import useMessagesStore from './useMessagesStore'; + +export const useMessageObserver = <A extends Action>( + type: A['type'], + callback: (payload: A['payload'], meta: Meta) => void, + deps: DependencyList = [], +) => { + const {subscribe, unsubscribe} = useMessagesStore(); + + useEffect(() => { + subscribe(type, callback); + + return () => { + unsubscribe(type, callback); + }; + }, deps); // eslint-disable-line react-hooks/exhaustive-deps +}; + +export default useMessageObserver; diff --git a/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx b/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx new file mode 100644 index 000000000..57f25eef0 --- /dev/null +++ b/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx @@ -0,0 +1,10 @@ +import {useContext} from 'react'; + +import {PostMessageContext} from '../messagesContext'; + +export const useMessageSender = () => { + const {sendMessage} = useContext(PostMessageContext); + return sendMessage; +}; + +export default useMessageSender; diff --git a/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx b/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx new file mode 100644 index 000000000..1805cd32a --- /dev/null +++ b/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx @@ -0,0 +1,17 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {PostMessageContext} from '../messagesContext'; + +export const useMessagesStore = () => { + const {state} = useContext(PostMessageContext); + + if (!state) { + throw new Error('Missing PostMessageContext'); + } + + return useStore(state); +}; + +export default useMessagesStore; diff --git a/src/editor-v2/context/messagesContext/index.ts b/src/editor-v2/context/messagesContext/index.ts new file mode 100644 index 000000000..eda561f12 --- /dev/null +++ b/src/editor-v2/context/messagesContext/index.ts @@ -0,0 +1,5 @@ +export * from './messagesContext'; +export * from './messagesProvider'; +export * from './hooks/useMessageSender'; +export * from './hooks/useMessageObserver'; +export * from './hooks/useMessagesStore'; diff --git a/src/editor-v2/context/messagesContext/messagesContext.tsx b/src/editor-v2/context/messagesContext/messagesContext.tsx new file mode 100644 index 000000000..ba01f3552 --- /dev/null +++ b/src/editor-v2/context/messagesContext/messagesContext.tsx @@ -0,0 +1,21 @@ +/** + * Context for sending messages between Editor and App + * Same exist in PC + **/ + +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {Action, SendOptions} from '../../../common/types'; + +import {MessagesStore} from './store'; + +export interface PostMessageContextProps { + state?: StoreApi<MessagesStore>; + sendMessage: (action: Action, options?: SendOptions) => void; +} + +export const PostMessageContext = React.createContext<PostMessageContextProps>({ + sendMessage: () => {}, +}); diff --git a/src/editor-v2/context/messagesContext/messagesProvider.tsx b/src/editor-v2/context/messagesContext/messagesProvider.tsx new file mode 100644 index 000000000..3fe7d6066 --- /dev/null +++ b/src/editor-v2/context/messagesContext/messagesProvider.tsx @@ -0,0 +1,46 @@ +import React, {PropsWithChildren, useContext, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {usePostMessage} from '../../../common/hooks/usePostMessage'; +import {WithStoreReducer} from '../../../common/types'; +import {getUrlOrigin} from '../../utils'; +import {useContentConfigStore} from '../contentConfig/hooks/useContentConfigStore'; +import {useEditorStore} from '../editorContext/hooks/useEditorStore'; +import {IframeContext, useIframeStore} from '../iframeContext'; + +import {PostMessageContext} from './messagesContext'; +import {MessagesStore, createMessagesStore} from './store'; + +export const PostMessageProvider = ({children}: PropsWithChildren) => { + const storeRef = useRef<StoreApi<MessagesStore>>(); + const {iframeElement} = useContext(IframeContext); + + const contentConfigStore = useContentConfigStore(); + const editorStore = useEditorStore(); + const iframeStore = useIframeStore(); + + if (!storeRef.current) { + storeRef.current = createMessagesStore(); + } + + const storesWithReducer: WithStoreReducer[] = [contentConfigStore, editorStore, iframeStore]; + + const {sendMessage} = usePostMessage({ + subscribers: storeRef.current?.getState().subscribers, + storesWithReducer, + targetIframeElement: iframeElement, + urlOrigin: getUrlOrigin(iframeStore.url), + }); + + return ( + <PostMessageContext.Provider + value={{ + state: storeRef.current, + sendMessage: sendMessage, + }} + > + {children} + </PostMessageContext.Provider> + ); +}; diff --git a/src/editor-v2/context/messagesContext/store.ts b/src/editor-v2/context/messagesContext/store.ts new file mode 100644 index 000000000..3c76dfe9d --- /dev/null +++ b/src/editor-v2/context/messagesContext/store.ts @@ -0,0 +1,44 @@ +import {Subscriber, SubscriptionFunc} from '../../../common/types'; +import {initializeStore} from '../../utils/store'; + +export interface MessagesState { + subscribers: Subscriber[]; +} + +export interface MessagesMethods { + subscribe: SubscriptionFunc; + unsubscribe: SubscriptionFunc; +} + +export type MessagesStore = MessagesState & MessagesMethods; + +export const createMessagesStore = initializeStore<MessagesState, MessagesMethods>( + { + subscribers: [], + }, + (set, _get) => ({ + subscribe: (type, payloadCallback) => { + set((state) => ({ + ...state, + subscribers: [...state.subscribers, {action: type, handler: payloadCallback}], + })); + + return () => { + set((state) => ({ + ...state, + subscribers: state.subscribers.filter( + ({handler, action}) => payloadCallback !== handler || type !== action, + ), + })); + }; + }, + unsubscribe: (type, payloadCallback) => { + set((state) => ({ + ...state, + subscribers: state.subscribers.filter( + ({handler, action}) => payloadCallback !== handler || type !== action, + ), + })); + }, + }), +); diff --git a/src/editor-v2/icons/Tablet.tsx b/src/editor-v2/icons/Tablet.tsx new file mode 100644 index 000000000..9ece66900 --- /dev/null +++ b/src/editor-v2/icons/Tablet.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import {a11yHiddenSvgProps} from '../../utils/svg'; + +export const Tablet: React.FC<React.SVGProps<SVGSVGElement>> = (props) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="12" + height="14" + viewBox="0 0 12 14" + fill="none" + {...a11yHiddenSvgProps} + {...props} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M10.5 3L10.5 11C10.5 11.8284 9.82843 12.5 9 12.5L3 12.5C2.17157 12.5 1.5 11.8284 1.5 11L1.5 3C1.5 2.17157 2.17157 1.5 3 1.5L9 1.5C9.82843 1.5 10.5 2.17157 10.5 3ZM9 -1.31134e-07C10.6569 -5.87108e-08 12 1.34315 12 3L12 11C12 12.6569 10.6569 14 9 14L3 14C1.34315 14 4.00426e-07 12.6569 4.72849e-07 11L8.2254e-07 3C8.94964e-07 1.34315 1.34315 -4.65826e-07 3 -3.93403e-07L9 -1.31134e-07ZM3.75 9.5C3.33579 9.5 3 9.83579 3 10.25C3 10.6642 3.33579 11 3.75 11L8.25 11C8.66421 11 9 10.6642 9 10.25C9 9.83579 8.66421 9.5 8.25 9.5L3.75 9.5Z" + fill="currentColor" + fillOpacity="0.85" + /> + </svg> +); diff --git a/src/editor-v2/index.ts b/src/editor-v2/index.ts new file mode 100644 index 000000000..4697a13c9 --- /dev/null +++ b/src/editor-v2/index.ts @@ -0,0 +1,2 @@ +export {Editor} from './containers/Editor/Editor'; +export * from '../common/types'; diff --git a/src/editor-v2/styles/mixins.scss b/src/editor-v2/styles/mixins.scss new file mode 100644 index 000000000..6144f6425 --- /dev/null +++ b/src/editor-v2/styles/mixins.scss @@ -0,0 +1,12 @@ +@import './variables.scss'; + +@mixin control($hoverScale: 1.05) { + display: flex; + justify-content: center; + align-items: center; + transition: transform $editorTransitionTime; + + &:hover { + transform: scale($hoverScale); + } +} ; diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss new file mode 100644 index 000000000..d69df66c2 --- /dev/null +++ b/src/editor-v2/styles/root.scss @@ -0,0 +1,9 @@ +body { + --pc-editor-header-height: 48px; + --pc-editor-code-header-height: 36px; + --pc-editor-divider-width: 12px; + --pc-editor-left-column-width: calc(400px + var(--pc-editor-divider-width)); + --pc-editor-base-color: var(--g-color-base-brand); + --pc-editor-control-color: var(--g-color-base-brand); + --pc-editor-control-icon-color: var(--g-color-text-dark-primary); +} diff --git a/src/editor-v2/styles/variables.scss b/src/editor-v2/styles/variables.scss new file mode 100644 index 000000000..c776f820a --- /dev/null +++ b/src/editor-v2/styles/variables.scss @@ -0,0 +1,5 @@ +$editorTransitionTime: 0.2s; +$editorShadow: 0px 2px 8px rgba(0, 0, 0, 0.06), 0px 4px 24px rgba(0, 0, 0, 0.06); +$editorControlBorderRadius: 8px; + +$headerHeight: 0px; diff --git a/src/editor-v2/utils/code.ts b/src/editor-v2/utils/code.ts new file mode 100644 index 000000000..3b4663267 --- /dev/null +++ b/src/editor-v2/utils/code.ts @@ -0,0 +1,12 @@ +import yaml from 'js-yaml'; + +import {PageContent} from '../../models'; + +export function parseCode(code: string) { + const pageContent = yaml.load(code) as PageContent; + + return { + ...pageContent, + blocks: pageContent.blocks?.filter(Boolean), + }; +} diff --git a/src/editor-v2/utils/index.ts b/src/editor-v2/utils/index.ts new file mode 100644 index 000000000..dba199555 --- /dev/null +++ b/src/editor-v2/utils/index.ts @@ -0,0 +1,168 @@ +import _ from 'lodash'; + +import {ConstructorBlock} from '../../models'; + +export function insert<T>(arr: Array<T>, index: number, newItem: T) { + return [...arr.slice(0, index), newItem, ...arr.slice(index)]; +} + +export function removeFromArray<T>(array: Array<T>, index: number) { + return [...array.slice(0, index), ...array.slice(index + 1)]; +} + +export function swapArrayItems<T>(array: Array<T>, firstIndex: number, secondIndex: number) { + const results = array.slice(); + const firstItem = array[firstIndex]; + results[firstIndex] = array[secondIndex]; + results[secondIndex] = firstItem; + return results; +} + +export function reorderArrayItems<T>(array: Array<T>, index: number, destination: number) { + const min = Math.min(index, destination); + const max = Math.max(index, destination); + const firstOperationRemove = index < destination; + const result = []; + result.push(...array.slice(0, min)); + if (!firstOperationRemove) { + result.push(array[index]); + } + result.push(...array.slice(firstOperationRemove ? min + 1 : min, max)); + if (firstOperationRemove) { + result.push(array[index]); + } + result.push(...array.slice(firstOperationRemove ? max : max + 1, array.length)); + return result; +} + +export function duplicateArrayItem<T>(array: Array<T>, index: number) { + const duplicatedItem = _.cloneDeep(array[index]); + return [...array.slice(0, index), duplicatedItem, ...array.slice(index)]; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function insertByPath<T extends object>(object: T, path: string, value: any) { + if (!path) { + return value as T; + } + + return _.setWith(_.clone(object), path, value, _.clone); +} + +/* + * path: string; + * Example: + * 1. blocks[0] => {path: blocks, index: 0} + * 2. blocks[2].children[10] => {path: blocks[2].children, index: 10} + **/ +export function splitPathAndIndex(path: string) { + // Match blocks[3], blocks[0].children[12], blocks[0], blocks[999999] + const bracketsRegExp = /(.*)\[(\d+)]$/g; + const regexpMatches = [...path.matchAll(bracketsRegExp)]; + if (regexpMatches.length) { + return { + // blocks, blocks[0].children + path: regexpMatches[0][1], + // 3, 12, 0, 9999 + index: Number(regexpMatches[0][2]), + }; + } + + // eslint-disable-next-line no-console + console.error('Non correct path for splitting'); + return undefined; +} + +/* + * [0, 4, 3] => [0].children[4].children[3] + * */ +export function generateChildrenPathFromArray(indexes: number[]) { + if (!indexes.length) { + return ''; + } + + let resultPath = `[${indexes[0]}]`; + + if (indexes.length > 1) { + for (let i = 1; i < indexes.length; i++) { + resultPath += `.children[${indexes[i]}]`; + } + } + + return resultPath; +} + +export function modifyObjectByPath( + blocks: ConstructorBlock[], + arrayPath: number[], + modifyCallback: (parentBlocks: ConstructorBlock[], index: number) => ConstructorBlock[], +) { + // [1] + // [4].children[3] + const insertPath = generateChildrenPathFromArray(arrayPath); + // path: '' index: 1 + // path: '[4].children' index: 3 + const splitPath = splitPathAndIndex(insertPath); + + if (splitPath) { + const {path: parentPath, index} = splitPath; + // Get Array that lies on path + const parentArray = parentPath ? _.get(blocks, parentPath) : blocks; + + const value = Array.isArray(parentArray) ? parentArray : []; + // Modify Array + const newModifiedArray = modifyCallback(value, index); + + // Return it back + return insertByPath(blocks, parentPath, newModifiedArray); + } + + return blocks; +} + +export function isItemsNeighbours(arrayA: number[], arrayB: number[]) { + if (arrayA.length !== arrayB.length) { + return false; + } + + for (let i = 0; i < arrayA.length - 1; i++) { + if (arrayA[i] !== arrayB[i]) { + return false; + } + } + + return true; +} + +export function getDestinationShiftBeforeReorder(arrayInit: number[], arrayDest: number[]) { + if (arrayInit.length === arrayDest.length || arrayInit.length > arrayDest.length) { + return arrayDest; + } + + for (let i = 0; i < arrayInit.length; i++) { + if (arrayInit[i] < arrayDest[i]) { + return prepareShift(arrayInit, arrayDest); + } + } + + return arrayDest; +} + +export function prepareShift(arrayInit: number[], arrayDest: number[]) { + if (arrayInit.length === arrayDest.length || arrayInit.length > arrayDest.length) { + return arrayDest; + } + + return arrayDest.map((pathIndex, index) => + index === arrayInit.length - 1 ? pathIndex - 1 : pathIndex, + ); +} + +export const getUrlOrigin = (url: string) => { + try { + const urlObject = new URL(url); + return urlObject.origin; + } catch { + return undefined; + } +}; diff --git a/src/editor-v2/utils/store.ts b/src/editor-v2/utils/store.ts new file mode 100644 index 000000000..6806f7ca8 --- /dev/null +++ b/src/editor-v2/utils/store.ts @@ -0,0 +1,24 @@ +import {StoreApi, create} from 'zustand'; +import {devtools, subscribeWithSelector} from 'zustand/middleware'; + +export function initializeStore<State, Methods>( + initialState: State, + methods: ( + set: StoreApi<State & Methods>['setState'], + get: StoreApi<State & Methods>['getState'], + ) => Methods, +) { + return (overrideInitialState?: Partial<State>) => + create< + State & Methods, + [['zustand/subscribeWithSelector', never], ['zustand/devtools', never]] + >( + subscribeWithSelector( + devtools((set, get) => ({ + ...initialState, + ...overrideInitialState, + ...methods(set, get), + })), + ), + ); +} diff --git a/src/editor/data/templates/test-editor-block.json b/src/editor/data/templates/test-editor-block.json new file mode 100644 index 000000000..43c1534b1 --- /dev/null +++ b/src/editor/data/templates/test-editor-block.json @@ -0,0 +1,18 @@ +{ + "template": { + "type": "test-editor-block", + "title": "Lorem ipsum dolor sit amet", + "table": { + "content": [ + ["Lorem", "ipsum 1", "dolor 2", "sit 3"], + ["Lorem 1", "0", "0", "0"], + ["Lorem 2", "0", "0", "1"], + ["Lorem 3", "0", "0", "1"], + ["Lorem 4", "0", "1", "1"], + ["Lorem 5", "1", "1", "1"] + ], + "legend": ["ipsum 1", "ipsum 2"], + "justify": ["start", "center", "center", "center"] + } + } +} diff --git a/src/grid/Grid/Grid.scss b/src/grid/Grid/Grid.scss index 5c172d42f..6097bcc48 100644 --- a/src/grid/Grid/Grid.scss +++ b/src/grid/Grid/Grid.scss @@ -15,8 +15,8 @@ $block: '.#{$ns}Grid'; } .row { - margin-right: 0; - margin-left: 0; + //margin-right: 0; + //margin-left: 0; } #{$block} { @@ -26,7 +26,7 @@ $block: '.#{$ns}Grid'; } } - .row .row { + .row { margin: 0 -#{$gutter}; } @@ -58,7 +58,7 @@ $block: '.#{$ns}Grid'; } } - .row .row { + .row { margin: 0 -#{$gutterMobile}; } } diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts index 5eb1768d9..7212ef646 100644 --- a/src/hooks/useAnalytics.ts +++ b/src/hooks/useAnalytics.ts @@ -12,7 +12,7 @@ export const useAnalytics = (name = '', target?: string) => { name ? { name, - context, + context: String(context), type: PredefinedEventTypes.Default, target: target, } diff --git a/src/hooks/useEditorInitialize.ts b/src/hooks/useEditorInitialize.ts new file mode 100644 index 000000000..4f3cede1b --- /dev/null +++ b/src/hooks/useEditorInitialize.ts @@ -0,0 +1,117 @@ +import {useCallback, useContext, useEffect} from 'react'; + +import {JSONSchemaType} from 'ajv'; +import _ from 'lodash'; + +import {ActionTypes, ItemConfig, UpdateConfigsAction} from '../common/types'; +import {blockDataMap} from '../constructor-items'; +import {EditorContext, useEditorStore} from '../context/editorContext'; +import {useMessageObserver, useMessageSender} from '../context/messagesContext'; +import {PageContent} from '../models'; +import {defaultComponentsConfigurationSchema} from '../schema'; +import {generateFromAJV} from '../utils/form-generator'; + +interface UseEditorInitializeProps { + content: PageContent; + setContent: (content: PageContent) => void; +} + +const useEditorInitialize = ({setContent, content}: UseEditorInitializeProps) => { + const {manipulateOverlayMode, isSelectActive} = useEditorStore(); + const sendMessage = useMessageSender(); + const {activeElement} = useContext(EditorContext); + + const onResize = useCallback(() => { + const height = document.documentElement.getBoundingClientRect().height; + + sendMessage({ + type: ActionTypes.SetHeight, + payload: {height}, + }); + + if (isSelectActive && activeElement) { + const rect = activeElement.getClientRects().item(0); + + if (rect) { + sendMessage({ + type: ActionTypes.UpdateSelectedBlockRect, + payload: { + rect: rect, + }, + }); + } + } + }, [activeElement, isSelectActive, sendMessage]); + + useMessageObserver<UpdateConfigsAction>(ActionTypes.UpdateConfigs, ({content: newContent}) => { + setContent(newContent); + }); + + useEffect(() => { + onResize(); + }, [content, onResize]); + + // Fires only once + useEffect(() => { + const height = document.documentElement.getBoundingClientRect().height; + + sendMessage( + { + type: ActionTypes.IframeReady, + payload: {height}, + }, + {direction: 'editor'}, + ); + + sendMessage({ + type: ActionTypes.BlocksConfigs, + payload: { + blocks: Object.entries(blockDataMap).reduce((acc, [key, value]) => { + acc.push({type: key, schema: value.schema}); + return acc; + }, [] as ItemConfig[]), + subBlocks: [], + global: generateFromAJV( + defaultComponentsConfigurationSchema as unknown as JSONSchemaType<{}>, + ), + }, + }); + }, [sendMessage]); + + useEffect(() => { + const onMouseUp = (e: MouseEvent) => { + if (manipulateOverlayMode === 'insert') { + e.preventDefault(); + sendMessage({type: ActionTypes.InsertModeDisable, payload: undefined}); + } + if (manipulateOverlayMode === 'reorder') { + e.preventDefault(); + sendMessage({type: ActionTypes.ReorderModeDisable, payload: undefined}); + } + }; + + const onMouseMove = (event: MouseEvent) => { + if (manipulateOverlayMode) { + sendMessage({ + type: ActionTypes.OverlayModeOnMove, + payload: {cursor: {x: event.clientX, y: event.clientY}}, + }); + } + }; + + const throttleOnMouseMove = _.throttle(onMouseMove, 1); + const throttleOnMouseUp = _.throttle(onMouseUp, 1); + + document.addEventListener('mousemove', throttleOnMouseMove); + document.addEventListener('mouseup', throttleOnMouseUp); + window.addEventListener('resize', onResize); + + return () => { + document.removeEventListener('mousemove', throttleOnMouseMove); + document.removeEventListener('mouseup', throttleOnMouseUp); + window.removeEventListener('resize', onResize); + }; + }, [activeElement, manipulateOverlayMode, onResize, sendMessage]); +}; + +export default useEditorInitialize; diff --git a/src/models/constructor-items/blocks.ts b/src/models/constructor-items/blocks.ts index ceea101f2..6651206a7 100644 --- a/src/models/constructor-items/blocks.ts +++ b/src/models/constructor-items/blocks.ts @@ -63,6 +63,7 @@ export enum BlockType { FilterBlock = 'filter-block', FormBlock = 'form-block', // unstable + TestEditorBlock = 'test-editor-block', SliderNewBlock = 'slider-new-block', } diff --git a/src/navigation/components/NavigationItem/NavigationItem.tsx b/src/navigation/components/NavigationItem/NavigationItem.tsx index 161e2c8d5..d6dfec9f6 100644 --- a/src/navigation/components/NavigationItem/NavigationItem.tsx +++ b/src/navigation/components/NavigationItem/NavigationItem.tsx @@ -48,7 +48,9 @@ export const NavigationItem: React.FC<NavigationItemProps> = ({ }, [data, props, type, menuLayout]); return ( - <BlockIdContext.Provider value={ANALYTICS_ID}> + // TODO: fix any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + <BlockIdContext.Provider value={ANALYTICS_ID as any}> <li className={b({'menu-layout': menuLayout}, className)}> <Component {...componentProps} className={b('content', {type})} /> </li> diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx index 36f57fd44..3d891e080 100644 --- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx +++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx @@ -18,7 +18,9 @@ export const NavigationButton: React.FC<NavigationButtonProps> = (props) => { const {url, target, className} = props; const classes = b(null, className); return ( - <BlockIdContext.Provider value={ANALYTICS_ID}> + // TODO: fix any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + <BlockIdContext.Provider value={ANALYTICS_ID as any}> {target ? ( <Button className={classes} {...props} url={url} /> ) : ( diff --git a/src/navigation/containers/Layout/Layout.scss b/src/navigation/containers/Layout/Layout.scss index 7d2a45b72..1cc4f8956 100644 --- a/src/navigation/containers/Layout/Layout.scss +++ b/src/navigation/containers/Layout/Layout.scss @@ -6,7 +6,8 @@ $block: '.#{$ns}layout'; display: flex; flex-direction: column; - min-height: 100vh; + // TODO: It seems not okay + //min-height: 100vh; &__content { display: flex; diff --git a/src/schema/constants.ts b/src/schema/constants.ts index 0ce6a61bd..220e88fe9 100644 --- a/src/schema/constants.ts +++ b/src/schema/constants.ts @@ -1,3 +1,4 @@ +import {TestEditorBlockSchema} from '../blocks/TestEditorBlock'; import {BlockType} from '../models'; import { @@ -57,6 +58,7 @@ export const blockSchemas: Record<BlockType, object> = { ...ShareBlock, ...FilterBlock, ...FormBlock, + ...TestEditorBlockSchema, ...SliderNewBlock, }; diff --git a/src/schema/index.ts b/src/schema/index.ts index c7f90d8b9..42f16a0b7 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -33,6 +33,17 @@ export const getBlocksCases = (blocks: Schema) => { ); }; +export const defaultComponentsConfigurationSchema = { + type: 'object', + properties: { + ...AnimatableProps, + logo: withTheme(LogoProps), + header: NavigationHeaderProps, + menu: MenuProps, + background: withTheme(BackgroundProps), + }, +}; + export function generateDefaultSchema(config?: SchemaCustomConfig) { const {cards = {}, blocks = {}, extensions = {}} = config ?? {}; @@ -82,17 +93,13 @@ export function generateDefaultSchema(config?: SchemaCustomConfig) { additionalProperties: false, required: ['blocks'], properties: { - ...AnimatableProps, - logo: withTheme(LogoProps), - header: NavigationHeaderProps, + ...defaultComponentsConfigurationSchema.properties, blocks: { type: 'array', items: { $ref: '#/definitions/children', }, }, - menu: MenuProps, - background: withTheme(BackgroundProps), ...extensions, }, } as Schema; diff --git a/src/sub-blocks/BackgroundCard/dynamic-form.ts b/src/sub-blocks/BackgroundCard/dynamic-form.ts new file mode 100644 index 000000000..817ff35e2 --- /dev/null +++ b/src/sub-blocks/BackgroundCard/dynamic-form.ts @@ -0,0 +1,81 @@ +import {BlockConfig} from '../../common/types'; +import {contentThemes, textSize} from '../../schema/validators/common'; + +const textSizeEnum = textSize.map((size) => ({value: size, content: size})); +const contentThemesEnum = contentThemes.map((size) => ({value: size, content: size})); + +export const blockConfig: BlockConfig = { + name: 'Background Card', + inputs: [ + { + type: 'oneOf', + name: 'title', + title: 'Title Object', + options: [ + { + title: 'Simple', + value: 'simple', + properties: [ + { + type: 'text', + name: '', + title: 'Title', + }, + ], + }, + { + title: 'Complex', + value: 'complex', + properties: [ + { + type: 'text', + name: 'text', + title: 'Title', + }, + { + type: 'select', + name: 'textSize', + title: 'Text Size', + enum: textSizeEnum, + view: 'select', + mode: 'single', + }, + { + type: 'text', + name: 'url', + title: 'Url', + }, + { + type: 'text', + name: 'urlTitle', + title: 'Url', + }, + { + type: 'boolean', + name: 'resetMargin', + title: 'Reset Margin', + }, + ], + }, + ], + }, + { + type: 'textarea', + name: 'text', + title: 'Description', + }, + { + type: 'textarea', + name: 'additionalInfo', + title: 'Additional Info', + }, + { + type: 'select', + name: 'size', + title: 'Size', + enum: contentThemesEnum, + view: 'select', + mode: 'single', + }, + ], +}; diff --git a/src/sub-blocks/BackgroundCard/index.tsx b/src/sub-blocks/BackgroundCard/index.tsx new file mode 100644 index 000000000..17b5255d5 --- /dev/null +++ b/src/sub-blocks/BackgroundCard/index.tsx @@ -0,0 +1,24 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import BackgroundCard from './BackgroundCard'; +import {BackgroundCard as BackgroundCardSchema} from './schema'; + +const BackgroundCardConfig: BlockData = { + component: BackgroundCard, + schema: { + name: 'Background Card', + inputs: generateFromAJV( + BackgroundCardSchema['background-card'] as unknown as JSONSchemaType<{}>, + ), + default: { + title: 'Background Card', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + additionalInfo: 'Additional info', + }, + }, +}; + +export default BackgroundCardConfig; diff --git a/src/sub-blocks/BannerCard/BannerCard.tsx b/src/sub-blocks/BannerCard/BannerCard.tsx index c35f58345..702ac2973 100644 --- a/src/sub-blocks/BannerCard/BannerCard.tsx +++ b/src/sub-blocks/BannerCard/BannerCard.tsx @@ -11,7 +11,7 @@ export const BannerCard = (props: BannerCardProps) => { const { title, subtitle, - button: {url, text, target, theme: buttonTheme = 'raised'} = {}, + button, color, theme: textTheme = 'light', image, @@ -21,6 +21,8 @@ export const BannerCard = (props: BannerCardProps) => { const theme = useTheme(); const contentStyle: Record<string, string> = {}; + const {url, text, target, theme: buttonTheme = 'raised'} = button || {}; + if (color) { contentStyle.backgroundColor = getThemedValue(color, theme); } diff --git a/src/sub-blocks/BannerCard/index.tsx b/src/sub-blocks/BannerCard/index.tsx new file mode 100644 index 000000000..8976cea40 --- /dev/null +++ b/src/sub-blocks/BannerCard/index.tsx @@ -0,0 +1,25 @@ +import {JSONSchemaType} from 'ajv'; + +import {BannerCardProps} from '../../blocks/Banner/schema'; +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import BannerCard from './BannerCard'; + +const BannerCardConfig: BlockData = { + component: BannerCard, + schema: { + name: 'Banner Card', + inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>), + default: { + color: 'rgba(54, 151, 241, 0.4)', + title: 'Banner Card', + subtitle: 'Some sort of description.', + button: { + text: 'Read more', + }, + }, + }, +}; + +export default BannerCardConfig; diff --git a/src/sub-blocks/BasicCard/index.tsx b/src/sub-blocks/BasicCard/index.tsx new file mode 100644 index 000000000..23a4a85cf --- /dev/null +++ b/src/sub-blocks/BasicCard/index.tsx @@ -0,0 +1,21 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import BasicCard from './BasicCard'; +import {BasicCard as BasicCardSchema} from './schema'; + +const BasicCardConfig: BlockData = { + component: BasicCard, + schema: { + name: 'Basic Card', + inputs: generateFromAJV(BasicCardSchema['basic-card'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Basic Card', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default BasicCardConfig; diff --git a/src/sub-blocks/Content/index.tsx b/src/sub-blocks/Content/index.tsx new file mode 100644 index 000000000..55eedd9c7 --- /dev/null +++ b/src/sub-blocks/Content/index.tsx @@ -0,0 +1,21 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import Content from './Content'; +import {ContentBlock} from './schema'; + +const ContentConfig: BlockData = { + component: Content, + schema: { + name: 'Content', + inputs: generateFromAJV(ContentBlock['content'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Content', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default ContentConfig; diff --git a/src/sub-blocks/Content/schema.ts b/src/sub-blocks/Content/schema.ts index 84d29852d..0aa9623e9 100644 --- a/src/sub-blocks/Content/schema.ts +++ b/src/sub-blocks/Content/schema.ts @@ -69,6 +69,7 @@ export const ContentBase = { export const ContentBlock = { content: { + type: 'object', additionalProperties: false, properties: { ...ContentBase, diff --git a/src/sub-blocks/Divider/index.tsx b/src/sub-blocks/Divider/index.tsx new file mode 100644 index 000000000..8256c4fbc --- /dev/null +++ b/src/sub-blocks/Divider/index.tsx @@ -0,0 +1,18 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import Divider from './Divider'; +import {Divider as DividerSchema} from './schema'; + +const DividerConfig: BlockData = { + component: Divider, + schema: { + name: 'Divider', + inputs: generateFromAJV(DividerSchema['divider'] as unknown as JSONSchemaType<{}>), + default: {}, + }, +}; + +export default DividerConfig; diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx index 735105306..0c503362c 100644 --- a/src/sub-blocks/ImageCard/ImageCard.tsx +++ b/src/sub-blocks/ImageCard/ImageCard.tsx @@ -47,12 +47,14 @@ const ImageCard = (props: ImageCardProps) => { const cardContent = ( <React.Fragment> - <div className={b('image', {margins})}> - <Image - className={b('image_inner', {radius: enableImageBorderRadius})} - {...imageProps} - /> - </div> + {image && ( + <div className={b('image', {margins})}> + <Image + className={b('image_inner', {radius: enableImageBorderRadius})} + {...imageProps} + /> + </div> + )} {hasContent && ( <div className={b('content')}> <Content diff --git a/src/sub-blocks/ImageCard/index.tsx b/src/sub-blocks/ImageCard/index.tsx new file mode 100644 index 000000000..121e1a304 --- /dev/null +++ b/src/sub-blocks/ImageCard/index.tsx @@ -0,0 +1,21 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import ImageCard from './ImageCard'; +import {ImageCard as ImageCardSchema} from './schema'; + +const ImageCardConfig: BlockData = { + component: ImageCard, + schema: { + name: 'Image Card', + inputs: generateFromAJV(ImageCardSchema['image-card'] as unknown as JSONSchemaType<{}>), + default: { + title: 'Image Card', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, +}; + +export default ImageCardConfig; diff --git a/src/sub-blocks/LayoutItem/LayoutItem.tsx b/src/sub-blocks/LayoutItem/LayoutItem.tsx index c2577d354..fdc646636 100644 --- a/src/sub-blocks/LayoutItem/LayoutItem.tsx +++ b/src/sub-blocks/LayoutItem/LayoutItem.tsx @@ -16,7 +16,7 @@ import './LayoutItem.scss'; const b = block('layout-item'); const LayoutItem = ({ - content: {links, ...content}, + content: {links = [], ...content} = {}, contentMargin = 'm', metaInfo, media, diff --git a/src/sub-blocks/LayoutItem/form.ts b/src/sub-blocks/LayoutItem/form.ts new file mode 100644 index 000000000..301899c2a --- /dev/null +++ b/src/sub-blocks/LayoutItem/form.ts @@ -0,0 +1,16 @@ +import {JSONSchemaType} from 'ajv'; + +import {generateFromAJV} from '../../utils/form-generator'; + +import LayoutItem from './LayoutItem'; +import {LayoutItem as LayoutItemSchema} from './schema'; + +const LayoutItemConfig = { + component: LayoutItem, + schema: { + name: 'Layout Item', + inputs: generateFromAJV(LayoutItemSchema as unknown as JSONSchemaType<{}>), + }, +}; + +export default LayoutItemConfig; diff --git a/src/sub-blocks/LayoutItem/index.tsx b/src/sub-blocks/LayoutItem/index.tsx new file mode 100644 index 000000000..7113f1499 --- /dev/null +++ b/src/sub-blocks/LayoutItem/index.tsx @@ -0,0 +1,23 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import LayoutItem from './LayoutItem'; +import {LayoutItem as LayoutItemSchema} from './schema'; + +const LayoutItemConfig: BlockData = { + component: LayoutItem, + schema: { + name: 'Layout Item', + inputs: generateFromAJV(LayoutItemSchema as unknown as JSONSchemaType<{}>), + default: { + content: { + title: 'Layout Item', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, + }, +}; + +export default LayoutItemConfig; diff --git a/src/sub-blocks/LayoutItem/schema.ts b/src/sub-blocks/LayoutItem/schema.ts index 4ae79c4f2..5722b1539 100644 --- a/src/sub-blocks/LayoutItem/schema.ts +++ b/src/sub-blocks/LayoutItem/schema.ts @@ -1,5 +1,6 @@ import omit from 'lodash/omit'; +import {Media} from '../../blocks/Media/schema'; import metaInfo from '../../components/MetaInfo/schema'; import {BaseProps, CardLayoutProps, MediaProps} from '../../schema/validators/common'; import {AnalyticsEventSchema} from '../../schema/validators/event'; @@ -13,6 +14,7 @@ export const LayoutItem = { ...BaseProps, ...CardLayoutProps, media: MediaProps, + media: Media, content: omit(ContentBase, ['colSize', 'size', 'centered']), contentMargin: { type: 'string', diff --git a/src/sub-blocks/MediaCard/index.tsx b/src/sub-blocks/MediaCard/index.tsx new file mode 100644 index 000000000..22f6c9b7e --- /dev/null +++ b/src/sub-blocks/MediaCard/index.tsx @@ -0,0 +1,23 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import MediaCard from './MediaCard'; +import {MediaCardBlock as MediaCardSchema} from './schema'; + +const MediaCardConfig: BlockData = { + component: MediaCard, + schema: { + name: 'Media Card', + inputs: generateFromAJV(MediaCardSchema['media-card'] as unknown as JSONSchemaType<{}>), + default: { + content: { + title: 'Media Card', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + }, + }, +}; + +export default MediaCardConfig; diff --git a/src/sub-blocks/PriceCard/index.tsx b/src/sub-blocks/PriceCard/index.tsx new file mode 100644 index 000000000..604934389 --- /dev/null +++ b/src/sub-blocks/PriceCard/index.tsx @@ -0,0 +1,34 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import PriceCard from './PriceCard'; +import {PriceCardBlock as PriceCardSchema} from './schema'; + +const PriceCardConfig: BlockData = { + component: PriceCard, + schema: { + name: 'Price Card', + inputs: generateFromAJV(PriceCardSchema['price-card'] as unknown as JSONSchemaType<{}>), + default: { + type: 'price-card', + border: 'line', + title: 'Basic', + price: '100 $', + pricePeriod: 'month', + priceDetails: '+ 5% from check', + description: 'For any purposes', + buttons: [ + { + url: '/', + text: 'Read More', + width: 'max', + theme: 'action', + }, + ], + }, + }, +}; + +export default PriceCardConfig; diff --git a/src/sub-blocks/PriceDetailed/PriceDetailed.tsx b/src/sub-blocks/PriceDetailed/PriceDetailed.tsx index e1fb017cb..e94bb6c7f 100644 --- a/src/sub-blocks/PriceDetailed/PriceDetailed.tsx +++ b/src/sub-blocks/PriceDetailed/PriceDetailed.tsx @@ -18,7 +18,7 @@ import SeparatePriceDetailed from './SeparatePriceDetailed/SeparatePriceDetailed const PriceDetailed = (props: PriceDetailedProps) => { const { priceType = PriceDetailsType.SETTINGS, - items, + items = [], numberGroupItems = 1, description, details, diff --git a/src/sub-blocks/PriceDetailed/index.tsx b/src/sub-blocks/PriceDetailed/index.tsx new file mode 100644 index 000000000..f031b87d9 --- /dev/null +++ b/src/sub-blocks/PriceDetailed/index.tsx @@ -0,0 +1,38 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import PriceDetailed from './PriceDetailed'; +import {PriceDetailedBlock as PriceDetailedSchema} from './schema'; + +/** @deprecated */ +const PriceDetailedConfig: BlockData = { + component: PriceDetailed, + schema: { + name: 'Price Detailed', + inputs: generateFromAJV( + PriceDetailedSchema['price-detailed'] as unknown as JSONSchemaType<{}>, + ), + default: { + priceType: 'marked-list', + items: [ + { + title: '100$', + description: 'Basic edition', + detailedTitle: 'per year', + items: [ + { + text: 'First item', + }, + { + text: 'Second item', + }, + ], + }, + ], + }, + }, +}; + +export default PriceDetailedConfig; diff --git a/src/sub-blocks/Quote/index.tsx b/src/sub-blocks/Quote/index.tsx new file mode 100644 index 000000000..47f45c716 --- /dev/null +++ b/src/sub-blocks/Quote/index.tsx @@ -0,0 +1,24 @@ +import {JSONSchemaType} from 'ajv'; + +import {BlockData} from '../../constructor-items'; +import {generateFromAJV} from '../../utils/form-generator'; + +import Quote from './Quote'; +import {Quote as QuoteSchema} from './schema'; + +const QuoteConfig: BlockData = { + component: Quote, + schema: { + name: 'Quote', + inputs: generateFromAJV(QuoteSchema['quote'] as unknown as JSONSchemaType<{}>), + default: { + text: 'A good decision is based on knowledge and not on numbers.', + author: { + firstName: ' Plato', + description: 'Greek philosopher', + }, + }, + }, +}; + +export default QuoteConfig; diff --git a/src/utils/editor.ts b/src/utils/editor.ts new file mode 100644 index 000000000..58cd311ce --- /dev/null +++ b/src/utils/editor.ts @@ -0,0 +1,20 @@ +import {MouseEvent} from 'react'; + +export const getCursorPositionOverElement = (elementRect: DOMRect, mouseEvent: MouseEvent) => { + const cursorPositionY = elementRect.height - (mouseEvent.clientY - elementRect.y); + const cursorPositionX = elementRect.width - (mouseEvent.clientX - elementRect.x); + const cursorRatioY = elementRect.height / 2 / cursorPositionY; + const cursorRatioX = elementRect.width / 2 / cursorPositionX; + + if (cursorRatioY > cursorRatioX) { + if (cursorRatioY >= 1) { + return 'bottom'; + } else { + return 'left'; + } + } else if (cursorRatioX >= 1) { + return 'right'; + } else { + return 'top'; + } +}; diff --git a/src/utils/form-generator.ts b/src/utils/form-generator.ts new file mode 100644 index 000000000..9c8af8ca0 --- /dev/null +++ b/src/utils/form-generator.ts @@ -0,0 +1,145 @@ +import {JSONSchemaType} from 'ajv'; + +import { + ArrayBaseInput, + BooleanInput, + ConfigInput, + NumberInput, + ObjectInput, + SelectBaseInput, + TextAreaInput, + TextInput, +} from '../common/types'; + +export const generateFromAJV = (schema: JSONSchemaType<{}>): ConfigInput[] => { + if (schema && schema.properties) { + const obj = Object.entries(schema.properties).map(([key, value]) => { + const innerSchema = value as JSONSchemaType<{}>; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + return generateSingleEntity(key, innerSchema); + }); + + return obj.filter(Boolean) as ConfigInput[]; + } + return []; +}; + +export const generateSingleEntity = (key: string, schema: JSONSchemaType<{}>) => { + const type = schema.type; + + if (!type && schema.enum) { + return { + type: 'select', + view: 'select', + name: key, + title: key, + enum: schema.enum.map((enumValue: string) => ({ + content: enumValue, + value: enumValue, + })), + } as SelectBaseInput; + } + + if (schema.oneOf) { + return { + type: 'oneOf', + name: key, + title: key, + options: schema.oneOf.map((item: JSONSchemaType<{}>) => { + let properties; + if (item.properties) { + properties = generateFromAJV(item); + } else { + properties = [ + generateSingleEntity('', { + ...item, + name: '', + title: item.optionName, + }), + ]; + } + + return { + value: item.optionName, + title: item.optionName, + properties: properties, + }; + }), + }; + } + + switch (type) { + case 'string': { + if (schema.inputType === 'textarea') { + return { + type: 'textarea', + name: key, + title: key, + } as TextAreaInput; + } + if (schema.enum) { + return { + type: 'select', + view: 'select', + name: key, + title: key, + enum: schema.enum.map((enumValue: string) => ({ + content: enumValue, + value: enumValue, + })), + } as SelectBaseInput; + } + return { + type: 'text', + name: key, + title: key, + } as TextInput; + } + case 'number': { + return { + type: 'number', + name: key, + title: key, + } as NumberInput; + } + case 'object': { + return { + type: 'object', + name: key, + title: key, + properties: generateFromAJV(schema), + } as ObjectInput; + } + case 'boolean': { + return { + type: 'boolean', + name: key, + title: key, + properties: generateFromAJV(schema), + } as BooleanInput; + } + case 'array': { + if (schema.items.type === 'string') { + return { + type: 'array', + name: key, + title: key, + properties: generateFromAJV(schema.items), + buttonText: 'Add', + arrayType: 'text', + } as ArrayBaseInput; + } + + return { + type: 'array', + name: key, + title: key, + properties: generateFromAJV(schema.items), + buttonText: 'Add', + arrayType: 'object', + } as ArrayBaseInput; + } + } + + return undefined; +}; diff --git a/src/utils/store.ts b/src/utils/store.ts new file mode 100644 index 000000000..9bf940a34 --- /dev/null +++ b/src/utils/store.ts @@ -0,0 +1,22 @@ +import {StoreApi, create} from 'zustand'; +import {devtools} from 'zustand/middleware'; + +export function initializeStore<State, Methods>( + initialState: State, + methods: ( + set: StoreApi<State & Methods>['setState'], + get: StoreApi<State & Methods>['getState'], + ) => Methods, +) { + return (overrideInitialState?: Partial<State>) => + create< + State & Methods, + [['zustand/devtools', never], ['zustand/persist', State & Methods]] + >( + devtools((set, get) => ({ + ...initialState, + ...overrideInitialState, + ...methods(set, get), + })), + ); +} diff --git a/styles/mixins.scss b/styles/mixins.scss index 855eef6c0..43bc986db 100644 --- a/styles/mixins.scss +++ b/styles/mixins.scss @@ -177,12 +177,12 @@ @mixin block { @include add-specificity(&) { - margin-top: $indentL; - padding: 0 0 $indentL; + //margin-top: $indentL; + padding: $indentL 0; &:first-child { // @deprecated - margin-top: var(--pc-first-block-indent, #{$indentXXL}); + //margin-top: var(--pc-first-block-indent, #{$indentXXL}); } } } @@ -581,27 +581,27 @@ unpredictable css rules order in build */ @include add-specificity(&) { &_indentTop { &_0 { - margin-top: 0; + padding-top: 0; } &_xs { - margin-top: $indentXS; + padding-top: $indentXS; } &_s { - margin-top: $indentSM; + padding-top: $indentSM; } &_m { - margin-top: $indentM; + padding-top: $indentM; } &_l { - margin-top: $indentL; + padding-top: $indentL; } &_xl { - margin-top: $indentXL; + padding-top: $indentXL; } } From 87a885de809534edf55ee0e8b921eaa8df8f9fc7 Mon Sep 17 00:00:00 2001 From: daff <daff@yandex-team.ru> Date: Sat, 12 Oct 2024 15:47:28 +0300 Subject: [PATCH 02/84] feat: add yaml support for editor v2 --- .../components/SourceCode/SourceCode.scss | 24 ++++++-- .../components/SourceCode/SourceCode.tsx | 60 +++++++++++++++++-- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/editor-v2/components/SourceCode/SourceCode.scss b/src/editor-v2/components/SourceCode/SourceCode.scss index c63709db2..205c407b9 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.scss +++ b/src/editor-v2/components/SourceCode/SourceCode.scss @@ -7,20 +7,32 @@ $block: '.#{$ns}source-code'; #{$block} { - padding: 10px; - white-space: pre; - - @include text-code-inline-1; - + height: 100%; + box-sizing: border-box; display: flex; flex-direction: column; gap: 10px; &__code { - user-select: all; + white-space: pre; + @include text-code-inline-1; + padding: 0 10px 10px; + overflow: auto; + } + + &__head { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + padding: 10px 10px 0; } &__textarea textarea { @include text-code-inline-1; } + + &__alert { + margin-bottom: 8px; + } } diff --git a/src/editor-v2/components/SourceCode/SourceCode.tsx b/src/editor-v2/components/SourceCode/SourceCode.tsx index 02b52d162..e31c84072 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.tsx +++ b/src/editor-v2/components/SourceCode/SourceCode.tsx @@ -1,6 +1,16 @@ import React, {useState} from 'react'; -import {Button, Dialog, TextArea} from '@gravity-ui/uikit'; +import {ArrowDownToSquare, Copy, CopyCheck} from '@gravity-ui/icons'; +import { + Alert, + Button, + CopyToClipboard, + Dialog, + Icon, + RadioButton, + TextArea, +} from '@gravity-ui/uikit'; +import yaml from 'js-yaml'; import {ClassNameProps, PageContent} from '../../../models'; import {block} from '../../../utils'; @@ -16,34 +26,72 @@ const SourceCode: React.FC<SourceCodeProps> = ({className}) => { const {config, setConfig} = useContentConfigStore(); const [isOpen, setIsOpen] = useState(false); const [tempConfig, setTempConfig] = useState(''); + const [format, setFormat] = useState<'yaml' | 'json'>('yaml'); const onUpdate = () => { let object; try { - object = JSON.parse(tempConfig); + if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) { + object = JSON.parse(tempConfig); + } else { + object = yaml.load(tempConfig); + } } catch { // eslint-disable-next-line no-console console.error('JSON.parse failed'); } - setConfig(object as PageContent); + if (object) { + setConfig(object as PageContent); + } + setIsOpen(false); }; + const text = format === 'yaml' ? yaml.dump(config) : JSON.stringify(config, null, 2); + return ( <div className={b(null, className)}> - <Button onClick={() => setIsOpen(true)}>Update</Button> - <div className={b('code')}>{JSON.stringify(config, null, 2)}</div> + <div className={b('head')}> + <RadioButton value={format} onUpdate={setFormat}> + <RadioButton.Option value={'yaml'} content={'YAML'} /> + <RadioButton.Option value={'json'} content={'JSON'} /> + </RadioButton> + <CopyToClipboard text={text} timeout={1000}> + {(status) => ( + <Button> + {status === 'pending' ? ( + <Icon data={Copy} /> + ) : ( + <Icon data={CopyCheck} /> + )} + Copy + </Button> + )} + </CopyToClipboard> + <Button onClick={() => setIsOpen(true)}> + <Icon data={ArrowDownToSquare} /> + Import + </Button> + </div> + + <div className={b('code')}>{text}</div> <Dialog onClose={() => setIsOpen(false)} open={isOpen} size={'l'}> <Dialog.Header caption="New configuration" /> <Dialog.Body> + <Alert + className={b('alert')} + theme={'info'} + title={'You can use YAML or JSON'} + message={'The editor will automatically understand which format is needed.'} + ></Alert> <TextArea className={b('textarea')} value={tempConfig} onUpdate={setTempConfig} - rows={30} + rows={25} /> </Dialog.Body> <Dialog.Footer From 79f29036f126d6c42b15bad2b83861f708dd08e7 Mon Sep 17 00:00:00 2001 From: daff <daff@yandex-team.ru> Date: Tue, 15 Oct 2024 16:57:05 +0300 Subject: [PATCH 03/84] feat: add zoom support for editor v2 --- .../components/BigOverlay/BigOverlay.tsx | 11 +-- .../components/MiddleScreen/MiddleScreen.scss | 9 ++- .../components/MiddleScreen/MiddleScreen.tsx | 54 +++++++++------ src/editor-v2/components/TopBar/TopBar.tsx | 22 +++++- .../components/ViewSwitches/ViewSwitches.scss | 9 +++ .../components/ViewSwitches/ViewSwitches.tsx | 67 ++++++++++--------- src/editor-v2/constants.ts | 1 + src/editor-v2/context/iframeContext/store.ts | 36 +++++++++- 8 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 src/editor-v2/constants.ts diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx index 02fa4679a..345e18ad5 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.tsx +++ b/src/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -10,7 +10,7 @@ import { } from '../../../common/types'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {IframeContext} from '../../context/iframeContext'; +import {IframeContext, useIframeStore} from '../../context/iframeContext'; import {useMessageObserver} from '../../context/messagesContext'; import './BigOverlay.scss'; @@ -20,6 +20,7 @@ const b = block('big-overlay'); interface BigOverlayProps extends ClassNameProps {} const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { + const {zoom} = useIframeStore(); const {iframeElement} = useContext(IframeContext); const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( undefined, @@ -32,13 +33,15 @@ const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { const {x, y} = payload.cursor; const iframeRect = iframeElement?.getClientRects().item(0); if (iframeRect) { - const newX = meta.source === 'editor' ? x : x + iframeRect.x; - const newY = meta.source === 'editor' ? y : y + iframeRect.y; + const zoomedX = (x * zoom) / 100; + const zoomedY = (y * zoom) / 100; + const newX = meta.source === 'editor' ? x : zoomedX + iframeRect.x; + const newY = meta.source === 'editor' ? y : zoomedY + iframeRect.y; setMousePosition({x: newX, y: newY}); } } }, - [iframeElement], + [iframeElement, zoom], ); useMessageObserver<InsertModeDisableAction>(ActionTypes.InsertModeDisable, () => { diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.scss b/src/editor-v2/components/MiddleScreen/MiddleScreen.scss index bb24c8168..16c869fa1 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.scss +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.scss @@ -13,12 +13,15 @@ $block: '.#{$ns}middle-screen'; flex-direction: column; &__topbar { - height: 50px; + height: 40px; } - &__canvas { + &__wrapper { height: 100%; - width: 100%; + } + + &__canvas { + transform-origin: left top; overflow-y: scroll; position: relative; } diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx index eebe7469c..e48ed564a 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -16,34 +16,48 @@ const b = block('middle-screen'); interface MiddleScreenProps extends ClassNameProps {} const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { - const {url, height} = useIframeStore(); + const {url, height, zoom, decreaseZoom, increaseZoom, setZoom} = useIframeStore(); const {initialized} = useEditorStore(); const {setIframeElement} = useContext(IframeContext); return ( <div className={b(null, className)}> <div className={b('topbar')}> - <TopBar /> + <TopBar + onZoomUpdate={setZoom} + zoom={zoom} + onDecreaseZoom={decreaseZoom} + onIncreaseZoom={increaseZoom} + /> </div> - <div className={b('canvas', {hidden: !initialized})}> - <iframe - ref={(element) => { - if (element) { - setIframeElement(element); - } + <div className={b('wrapper')}> + <div + className={b('canvas', {hidden: !initialized})} + style={{ + transform: `scale(${zoom}%)`, + height: `${(100 / zoom) * 100}%`, + width: `${(100 / zoom) * 100}%`, }} - className={b('iframe')} - src={url} - height={`${height}px`} - width="100%" - frameBorder="0" - /> - <Overlay className={b('overlay')} /> - {!initialized && ( - <div className={b('loading')}> - <Loader size={'l'} /> - </div> - )} + > + <iframe + ref={(element) => { + if (element) { + setIframeElement(element); + } + }} + className={b('iframe')} + src={url} + height={`${height}px`} + width="100%" + frameBorder="0" + /> + <Overlay className={b('overlay')} /> + {!initialized && ( + <div className={b('loading')}> + <Loader size={'l'} /> + </div> + )} + </div> </div> </div> ); diff --git a/src/editor-v2/components/TopBar/TopBar.tsx b/src/editor-v2/components/TopBar/TopBar.tsx index af8566fcf..bf9c0b365 100644 --- a/src/editor-v2/components/TopBar/TopBar.tsx +++ b/src/editor-v2/components/TopBar/TopBar.tsx @@ -9,13 +9,29 @@ import './TopBar.scss'; const b = block('topbar'); -interface TopBarProps extends ClassNameProps {} +interface TopBarProps extends ClassNameProps { + onZoomUpdate: (zoom: number) => void; + onDecreaseZoom: () => void; + onIncreaseZoom: () => void; + zoom: number; +} -const TopBar: React.FC<TopBarProps> = ({className}) => { +const TopBar: React.FC<TopBarProps> = ({ + zoom, + onDecreaseZoom, + onIncreaseZoom, + onZoomUpdate, + className, +}) => { return ( <div className={b(null, className)}> <div className={b('switches')}> - <ViewSwitches /> + <ViewSwitches + onZoomUpdate={onZoomUpdate} + onDecreaseZoom={onDecreaseZoom} + onIncreaseZoom={onIncreaseZoom} + zoom={zoom} + /> </div> <Source className={b('source')} /> </div> diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.scss b/src/editor-v2/components/ViewSwitches/ViewSwitches.scss index ca2c5f7be..45751b598 100644 --- a/src/editor-v2/components/ViewSwitches/ViewSwitches.scss +++ b/src/editor-v2/components/ViewSwitches/ViewSwitches.scss @@ -9,4 +9,13 @@ $block: '.#{$ns}view-switches'; #{$block} { display: inline-flex; gap: 12px; + + &__zoom { + display: flex; + gap: 4px; + } + + &__zoom-input { + width: 40px; + } } diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx index b96b73fe0..cb9cd6578 100644 --- a/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx +++ b/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx @@ -1,48 +1,49 @@ import React from 'react'; -import {Display, Eye, Smartphone, Square, SquareDashedText} from '@gravity-ui/icons'; -import {Icon, RadioButton, RadioButtonOption} from '@gravity-ui/uikit'; +import {Minus, Plus} from '@gravity-ui/icons'; +import {Button, Icon, Select} from '@gravity-ui/uikit'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; +import {ZOOM_STEPS} from '../../constants'; import './ViewSwitches.scss'; const b = block('view-switches'); -interface ViewSwitchesProps extends ClassNameProps {} - -const options1: RadioButtonOption[] = [ - {value: 'edit-mode', content: <Icon data={SquareDashedText} />}, - {value: 'preview-mode', content: <Icon data={Eye} />}, -]; - -export const options2: RadioButtonOption[] = [ - {value: 'desktop-mode', content: <Icon data={Display} />}, - {value: 'tablet-mode', content: <Icon data={Square} />}, - {value: 'phone-mode', content: <Icon data={Smartphone} />}, -]; - -// TODO: To be done -const ViewSwitches: React.FC<ViewSwitchesProps> = ({className}) => { +interface ViewSwitchesProps extends ClassNameProps { + onZoomUpdate: (zoom: number) => void; + onDecreaseZoom: () => void; + onIncreaseZoom: () => void; + zoom: number; +} + +const ViewSwitches: React.FC<ViewSwitchesProps> = ({ + zoom, + onIncreaseZoom, + onDecreaseZoom, + onZoomUpdate, + className, +}) => { return ( <div className={b(null, className)}> - <RadioButton - className={b('switch')} - name="group1" - defaultValue={options1[0].value} - options={options1} - size="m" - disabled - /> - <RadioButton - className={b('switch')} - name="group2" - defaultValue={options2[0].value} - options={options2} - size="m" - disabled - /> + <div className={b('zoom')}> + <Button view={'flat'} onClick={onDecreaseZoom}> + <Icon data={Minus} /> + </Button> + <Select + multiple={false} + value={[String(zoom)]} + options={ZOOM_STEPS.map((step) => ({ + value: String(step), + content: `${String(step)}%`, + }))} + onUpdate={(value) => onZoomUpdate(Number(value))} + /> + <Button view={'flat'} onClick={onIncreaseZoom}> + <Icon data={Plus} /> + </Button> + </div> </div> ); }; diff --git a/src/editor-v2/constants.ts b/src/editor-v2/constants.ts new file mode 100644 index 000000000..22c3e53f2 --- /dev/null +++ b/src/editor-v2/constants.ts @@ -0,0 +1 @@ +export const ZOOM_STEPS = [25, 33, 50, 75, 100, 125, 150, 200, 250, 300]; diff --git a/src/editor-v2/context/iframeContext/store.ts b/src/editor-v2/context/iframeContext/store.ts index 756ba17fd..c5902b902 100644 --- a/src/editor-v2/context/iframeContext/store.ts +++ b/src/editor-v2/context/iframeContext/store.ts @@ -1,13 +1,19 @@ import {ActionTypes, WithStoreReducer} from '../../../common/types'; +import {ZOOM_STEPS} from '../../constants'; import {initializeStore} from '../../utils/store'; export interface IframeState { url: string; height?: number; + // In percents + zoom: number; } export interface IframeMethods extends WithStoreReducer { setUrl: (url: string) => void; + setZoom: (zoom: number) => void; + increaseZoom: () => void; + decreaseZoom: () => void; } export type IframeStore = IframeState & IframeMethods; @@ -16,8 +22,36 @@ export const createIframeStore = initializeStore<IframeState, IframeMethods>( { url: '', height: 400, + zoom: 100, }, - (set) => ({ + (set, get) => ({ + setZoom: function (zoom) { + if (zoom > 0) { + set((state) => ({...state, zoom})); + } + }, + increaseZoom: function () { + const currentZoom = get().zoom; + + for (const step of ZOOM_STEPS) { + if (currentZoom < step) { + get().setZoom(step); + break; + } + } + }, + decreaseZoom: function () { + const currentZoom = get().zoom; + const reverseSteps = ZOOM_STEPS.slice().reverse(); + + for (const step of reverseSteps) { + if (currentZoom > step) { + get().setZoom(step); + break; + } + } + }, + setUrl: (url) => set((state) => ({...state, url})), reducer: (action) => { switch (action.type) { From 6e64e34416270139e505657ee19b2e9abc9d95eb Mon Sep 17 00:00:00 2001 From: daff <daff@yandex-team.ru> Date: Sat, 2 Nov 2024 14:26:31 +0300 Subject: [PATCH 04/84] feat: add groups, block buttons and panel open button --- src/blocks/Banner/index.tsx | 1 + src/blocks/CardLayout/index.ts | 26 +++++- src/blocks/Companies/index.ts | 1 + src/blocks/ContentLayout/index.ts | 66 ++++++++++++++- src/blocks/ExtendedFeatures/index.ts | 64 +++++++++++++- src/blocks/FilterBlock/FilterBlock.tsx | 1 - src/blocks/FilterBlock/index.ts | 83 ++++++++++++++++++- src/blocks/Form/index.ts | 1 + src/blocks/Header/index.ts | 19 +++++ src/blocks/HeaderSlider/index.ts | 1 + src/blocks/Icons/Icons.scss | 1 + src/blocks/Icons/Icons.tsx | 8 +- src/blocks/Icons/index.ts | 44 ++++++++++ src/blocks/Info/index.ts | 1 + src/blocks/Map/index.ts | 1 + src/blocks/Media/index.ts | 44 +++++++++- src/blocks/PromoFeaturesBlock/index.ts | 32 ++++++- src/blocks/Questions/index.ts | 1 + src/blocks/Share/index.ts | 1 + src/blocks/Slider/index.ts | 31 +++++++ src/blocks/Table/index.ts | 17 ++++ src/blocks/Tabs/index.ts | 1 + src/common/types/forms.ts | 1 + src/constructor-items.ts | 10 +-- .../components/BlocksList/BlocksList.scss | 31 ++++++- .../components/BlocksList/BlocksList.tsx | 62 ++++++++++++-- src/editor-v2/components/Panels/Panels.scss | 40 +++++++++ src/editor-v2/components/Panels/Panels.tsx | 79 ++++++++++++++++++ src/editor-v2/components/Tree/Tree.scss | 11 +++ src/editor-v2/components/Tree/Tree.tsx | 21 ++++- src/editor-v2/containers/Editor/Editor.scss | 13 --- src/editor-v2/containers/Editor/Editor.tsx | 30 ++----- src/editor-v2/context/contentConfig/store.ts | 7 ++ src/sub-blocks/BackgroundCard/index.tsx | 2 + src/sub-blocks/BannerCard/index.tsx | 1 + src/sub-blocks/BasicCard/index.tsx | 1 + src/sub-blocks/Content/index.tsx | 1 + src/sub-blocks/ImageCard/index.tsx | 1 + src/sub-blocks/LayoutItem/form.ts | 16 ---- src/sub-blocks/LayoutItem/index.tsx | 9 +- src/sub-blocks/MediaCard/index.tsx | 2 + src/sub-blocks/PriceCard/index.tsx | 1 + src/sub-blocks/PriceDetailed/index.tsx | 1 + src/sub-blocks/Quote/index.tsx | 1 + 44 files changed, 697 insertions(+), 89 deletions(-) create mode 100644 src/editor-v2/components/Panels/Panels.scss create mode 100644 src/editor-v2/components/Panels/Panels.tsx delete mode 100644 src/sub-blocks/LayoutItem/form.ts diff --git a/src/blocks/Banner/index.tsx b/src/blocks/Banner/index.tsx index 928df0660..58c9e84ac 100644 --- a/src/blocks/Banner/index.tsx +++ b/src/blocks/Banner/index.tsx @@ -10,6 +10,7 @@ const BannerBlockConfig: BlockData = { component: BannerBlock, schema: { name: 'Banner Block', + group: 'block', inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>), default: { color: 'rgba(54, 151, 241, 0.4)', diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts index b600e79dd..2056f833c 100644 --- a/src/blocks/CardLayout/index.ts +++ b/src/blocks/CardLayout/index.ts @@ -1,8 +1,12 @@ +import {JSONSchemaType} from 'ajv'; + import {ConfigInput} from '../../common/types'; import {BlockData} from '../../constructor-items'; import {sliderSizesArray, textSize} from '../../schema/validators/common'; +import {generateFromAJV} from '../../utils/form-generator'; import CardLayout from './CardLayout'; +import {CardLayoutProps} from './schema'; const textSizeEnum = textSize.map((size) => ({value: size, content: size})); @@ -155,11 +159,27 @@ const CardLayoutBlockConfig: BlockData = { component: CardLayout, schema: { name: 'Card Layout Block', - inputs: blockConfig, - // inputs: generateFromAJV(CardLayoutProps as unknown as JSONSchemaType<{}>), + group: 'card-containers', + inputs: generateFromAJV(CardLayoutProps as unknown as JSONSchemaType<{}>), default: { type: 'card-layout-block', - children: [], + children: [ + { + type: 'background-card', + title: 'Tell a story and build a narrative', + text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.', + }, + { + type: 'background-card', + title: 'Tell a story and build a narrative', + text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.', + }, + { + type: 'background-card', + title: 'Tell a story and build a narrative', + text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.', + }, + ], title: 'Card Layout Block', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', diff --git a/src/blocks/Companies/index.ts b/src/blocks/Companies/index.ts index dab19df95..a1ee333c8 100644 --- a/src/blocks/Companies/index.ts +++ b/src/blocks/Companies/index.ts @@ -10,6 +10,7 @@ const CompaniesBlockConfig: BlockData = { component: CompaniesBlock, schema: { name: 'Companies Block', + group: 'block', inputs: generateFromAJV( CompaniesBlockSchema['companies-block'] as unknown as JSONSchemaType<{}>, ), diff --git a/src/blocks/ContentLayout/index.ts b/src/blocks/ContentLayout/index.ts index 59dac4d75..50ce74ad0 100644 --- a/src/blocks/ContentLayout/index.ts +++ b/src/blocks/ContentLayout/index.ts @@ -9,11 +9,75 @@ const ContentLayoutBlockConfig = { component: ContentLayoutBlock, schema: { name: 'Content Layout Block', + group: 'block', inputs: generateFromAJV( ContentLayoutBlockSchema['content-layout-block'] as unknown as JSONSchemaType<{}>, ), default: { - title: 'Content Layout Block', + textContent: { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + additionalInfo: + 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + buttons: [ + { + text: 'Button', + theme: 'action', + url: 'https://example.com', + }, + { + text: 'Button', + theme: 'outlined', + url: 'https://example.com', + }, + ], + links: [ + { + url: 'https://example.com', + text: 'Link', + theme: 'normal', + arrow: true, + }, + ], + fileContent: [ + { + href: 'https://example.xls', + text: 'File xls', + }, + { + href: 'https://example.fig', + text: 'File PNG, JPG, and SVG format', + }, + { + href: 'https://example.pdf', + text: 'Pdf file', + }, + { + href: 'https://example.zip', + text: 'Archive file', + }, + { + href: 'https://example.doc', + text: 'Microsoft Word document', + }, + { + href: 'https://example.ppt', + text: 'PPT file', + }, + ], + list: [ + { + title: 'Lorem ipsum', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum ipsum', + }, + ], }, }, }; diff --git a/src/blocks/ExtendedFeatures/index.ts b/src/blocks/ExtendedFeatures/index.ts index e6b0a209d..9876764de 100644 --- a/src/blocks/ExtendedFeatures/index.ts +++ b/src/blocks/ExtendedFeatures/index.ts @@ -9,12 +9,72 @@ const ExtendedFeaturesBlockConfig = { component: ExtendedFeaturesBlock, schema: { name: 'Extended Features Block', + group: 'block', inputs: generateFromAJV( ExtendedFeaturesBlockSchema['extended-features-block'] as unknown as JSONSchemaType<{}>, ), default: { - title: 'Extended Features Block', - items: [{}], + type: 'extended-features-block', + title: { + text: 'Lorem ipsum dolor sit amet', + textSize: 'm', + }, + description: + 'Three cards in a row on the desktop, two cards in a row on a tablet, one card in a row on a mobile phone.', + items: [ + { + title: 'Sed do eiusmod tempor incididunt', + text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.', + additionalInfo: + 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + }, + { + title: 'Sed do eiusmod tempor', + text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.', + buttons: [ + { + text: 'Button', + theme: 'action', + url: 'https://example.com', + }, + { + text: 'Button', + theme: 'outlined', + url: 'https://example.com', + }, + ], + }, + { + title: 'Sed do eiusmod tempor incididunt', + text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.', + links: [ + { + text: 'Go', + url: '#', + arrow: true, + theme: 'normal', + }, + ], + }, + { + title: 'Sed do eiusmod tempor incididunt', + text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.', + list: [ + { + title: 'Lorem ipsum', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum ipsum', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + ], + }, + ], }, }, }; diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx index 870dc7d5f..6563b8a0a 100644 --- a/src/blocks/FilterBlock/FilterBlock.tsx +++ b/src/blocks/FilterBlock/FilterBlock.tsx @@ -78,7 +78,6 @@ const FilterBlock: React.FC<FilterBlockProps> = ({ <CardLayoutBlock title="" colSizes={colSizes} className={b('cards-container')}> {cards.map((card, index) => { const key = getBlockKey(card, index); - return <ConstructorItem data={card} blockKey={index} key={key} />; })} </CardLayoutBlock> diff --git a/src/blocks/FilterBlock/index.ts b/src/blocks/FilterBlock/index.ts index 15a4a56b1..f969c403b 100644 --- a/src/blocks/FilterBlock/index.ts +++ b/src/blocks/FilterBlock/index.ts @@ -9,9 +9,90 @@ const FilterBlockConfig = { component: FilterBlock, schema: { name: 'Filter Block', + group: 'block', inputs: generateFromAJV(FilterProps as unknown as JSONSchemaType<{}>), default: { - title: 'Filter Block', + allTag: true, + description: + 'Three cards in a row on the desktop, two cards in a row on a tablet, one card in a row on a mobile phone.', + items: [ + { + card: { + content: { + title: 'Layout Item 1', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['one'], + }, + { + card: { + content: { + title: 'Layout Item 2', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['two'], + }, + { + card: { + content: { + title: 'Layout Item 3', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['three'], + }, + { + card: { + content: { + title: 'Layout Item 4', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['one'], + }, + { + card: { + content: { + title: 'Layout Item 5', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['two'], + }, + { + card: { + content: { + title: 'Layout Item 6', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + type: 'layout-item', + }, + tags: ['three'], + }, + ], + tags: [ + { + id: 'one', + label: 'First very long label', + }, + { + id: 'two', + label: 'Second very long label', + }, + { + id: 'three', + label: 'Third very long label', + }, + ], + title: 'Card Layout', + type: 'filter-block', }, }, }; diff --git a/src/blocks/Form/index.ts b/src/blocks/Form/index.ts index b80b5c371..b28fc2c66 100644 --- a/src/blocks/Form/index.ts +++ b/src/blocks/Form/index.ts @@ -9,6 +9,7 @@ const FormBlockConfig = { component: FormBlock, schema: { name: 'Form Block', + group: 'block', inputs: generateFromAJV(FormBlockSchema['form-block'] as unknown as JSONSchemaType<{}>), default: { title: 'Form Block', diff --git a/src/blocks/Header/index.ts b/src/blocks/Header/index.ts index 1f432122e..7b9e77099 100644 --- a/src/blocks/Header/index.ts +++ b/src/blocks/Header/index.ts @@ -9,7 +9,26 @@ const HeaderBlockConfig = { component: HeaderBlock, schema: { name: 'Header Block', + group: 'block', inputs: generateFromAJV(HeaderBlockSchema['header-block'] as unknown as JSONSchemaType<{}>), + default: { + type: 'header-block', + title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', + description: + 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + buttons: [ + { + text: 'Button\r', + theme: 'action', + url: 'https://example.com', + }, + { + text: 'Button', + theme: 'outlined', + url: 'https://example.com', + }, + ], + }, }, }; diff --git a/src/blocks/HeaderSlider/index.ts b/src/blocks/HeaderSlider/index.ts index 482887543..6660668d5 100644 --- a/src/blocks/HeaderSlider/index.ts +++ b/src/blocks/HeaderSlider/index.ts @@ -9,6 +9,7 @@ const HeaderSliderBlockConfig = { component: HeaderSliderBlock, schema: { name: 'Header Slider Block', + group: 'card-containers', inputs: generateFromAJV( HeaderSliderBlockSchema['header-slider-block'] as unknown as JSONSchemaType<{}>, ), diff --git a/src/blocks/Icons/Icons.scss b/src/blocks/Icons/Icons.scss index d098159d6..bd0cc7d3e 100644 --- a/src/blocks/Icons/Icons.scss +++ b/src/blocks/Icons/Icons.scss @@ -48,6 +48,7 @@ $block: '.#{$ns}icons-block'; @include reset-link-style(); margin: 0 $indentXXXS $indentSM; } + a#{$block}__item { @include focusable(); border-radius: var(--g-focus-border-radius); diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index 93935febb..c7338587e 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -32,8 +32,8 @@ const Icons = ({title, description, size = 's', colSizes = {all: 12}, items}: Ic ); return ( - <div className={b({size})}> - <Grid> + <Grid> + <div className={b({size})}> {(title || description) && ( <Title className={b('header')} @@ -65,8 +65,8 @@ const Icons = ({title, description, size = 's', colSizes = {all: 12}, items}: Ic </div> ); })} - </Grid> - </div> + </div> + </Grid> ); }; diff --git a/src/blocks/Icons/index.ts b/src/blocks/Icons/index.ts index 21251e103..6a1c39406 100644 --- a/src/blocks/Icons/index.ts +++ b/src/blocks/Icons/index.ts @@ -9,12 +9,56 @@ const IconsBlockConfig = { component: IconsBlock, schema: { name: 'Icons Block', + group: 'block', inputs: generateFromAJV(IconsProps as unknown as JSONSchemaType<{}>), default: { type: 'icons-block', title: 'Icons Block', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + size: 'm', + items: [ + { + url: '/security/standards/software-registry', + text: 'Государственные реестры РФ', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-software-registry.svg', + }, + { + url: '/security/standards/gdpr', + text: 'Общий регламент защиты данных (GDPR)', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-gdpr.svg', + }, + { + url: '/security/standards/cloud-security-alliance', + text: 'Cloud Security Alliance', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-csa.svg', + }, + { + url: '/security/standards/iso-standards', + text: 'Международная организация по стандартизации (ISO)', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-iso.svg', + }, + { + url: '/security/standards/152-fz', + text: '№152-ФЗ «О персональных данных»', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-152-fz.svg', + }, + { + url: '/security/standards/gost-p-57580', + text: 'ГОСТ Р 57580', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-gost.svg', + }, + { + url: '/security/standards/pci', + text: 'Payment Card Industry Data Security Standard', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-pci.svg', + }, + { + url: '/docs/security/standard/all', + text: 'Стандарт по защите облачной инфраструктуры', + src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/icons/security_yc.svg', + }, + ], }, }, }; diff --git a/src/blocks/Info/index.ts b/src/blocks/Info/index.ts index 1bd367f98..841e85d0a 100644 --- a/src/blocks/Info/index.ts +++ b/src/blocks/Info/index.ts @@ -9,6 +9,7 @@ const InfoBlockConfig = { component: InfoBlock, schema: { name: 'Info Block', + group: 'block', inputs: generateFromAJV(InfoBlockSchema['info-block'] as unknown as JSONSchemaType<{}>), default: { type: 'info-block', diff --git a/src/blocks/Map/index.ts b/src/blocks/Map/index.ts index 88c48ce77..f34a5041c 100644 --- a/src/blocks/Map/index.ts +++ b/src/blocks/Map/index.ts @@ -9,6 +9,7 @@ const MapBlockConfig = { component: MapBlock, schema: { name: 'Map Block', + group: 'block', inputs: generateFromAJV(MapBlockSchema['map-block'] as unknown as JSONSchemaType<{}>), default: { title: 'Map Block', diff --git a/src/blocks/Media/index.ts b/src/blocks/Media/index.ts index b3f113ad6..f9c0abb94 100644 --- a/src/blocks/Media/index.ts +++ b/src/blocks/Media/index.ts @@ -9,13 +9,49 @@ const MediaBlockConfig = { component: MediaBlock, schema: { name: 'Media Block', + group: 'block', inputs: generateFromAJV(MediaBlockSchema['media-block'] as unknown as JSONSchemaType<{}>), default: { - type: 'media-block', - title: 'Media Block', - additionalInfo: 'Additional info', + title: 'Lorem ipsum dolor sit', description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + additionalInfo: + 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.', + links: [ + { + url: '#', + text: 'Learn more', + theme: 'normal', + arrow: true, + }, + ], + buttons: [ + { + text: 'Button', + theme: 'action', + url: 'https://example.com', + }, + { + text: 'Button', + theme: 'outlined', + url: '#', + }, + ], + list: [ + { + title: 'Lorem ipsum', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum ipsum', + }, + ], + media: { + image: 'https://storage.yandexcloud.net/cloud-www-assets/constructor/main/new/media-01-01.jpg', + }, }, }, }; diff --git a/src/blocks/PromoFeaturesBlock/index.ts b/src/blocks/PromoFeaturesBlock/index.ts index 2c3eac449..07a01b7b2 100644 --- a/src/blocks/PromoFeaturesBlock/index.ts +++ b/src/blocks/PromoFeaturesBlock/index.ts @@ -9,12 +9,42 @@ const PromoFeaturesBlockConfig = { component: PromoFeaturesBlock, schema: { name: 'Promo Features Block', + group: 'block', inputs: generateFromAJV( PromoFeaturesBlockSchema['promo-features-block'] as unknown as JSONSchemaType<{}>, ), default: { title: 'Promo Features Block', - items: [{}], + theme: 'default', + items: [ + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + theme: 'accent', + }, + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + theme: 'accent-light', + }, + { + title: 'Lorem ipsum dolor sit amet', + text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + theme: 'primary', + }, + ], }, }, }; diff --git a/src/blocks/Questions/index.ts b/src/blocks/Questions/index.ts index 43253bf7e..8a8c30adc 100644 --- a/src/blocks/Questions/index.ts +++ b/src/blocks/Questions/index.ts @@ -9,6 +9,7 @@ const QuestionsBlockConfig = { component: QuestionsBlock, schema: { name: 'Questions Block', + group: 'block', inputs: generateFromAJV( QuestionsBlockSchema['questions-block'] as unknown as JSONSchemaType<{}>, ), diff --git a/src/blocks/Share/index.ts b/src/blocks/Share/index.ts index e44d6b623..4b27ce689 100644 --- a/src/blocks/Share/index.ts +++ b/src/blocks/Share/index.ts @@ -9,6 +9,7 @@ const ShareBlockConfig = { component: ShareBlock, schema: { name: 'Share Block', + group: 'block', inputs: generateFromAJV(ShareBlockSchema['share-block'] as unknown as JSONSchemaType<{}>), default: { items: ['vk', 'telegram', 'facebook'], diff --git a/src/blocks/Slider/index.ts b/src/blocks/Slider/index.ts index 106ad3afe..a809a55f7 100644 --- a/src/blocks/Slider/index.ts +++ b/src/blocks/Slider/index.ts @@ -9,7 +9,38 @@ const SliderBlockConfig = { component: SliderBlock, schema: { name: 'Slider Block', + group: 'card-containers', inputs: generateFromAJV(SliderBlockSchema['slider-block'] as unknown as JSONSchemaType<{}>), + default: { + dots: true, + type: 'slider-block', + title: 'Slider Block with Quote Cards', + description: 'You can insert any card inside block', + slidesToShow: 1, + arrows: true, + children: [ + { + type: 'quote', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + url: 'https://example.com', + author: { + firstName: 'Lorem', + secondName: 'ipsum', + description: 'Lorem ipsum', + }, + }, + { + type: 'quote', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + url: 'https://example.com', + author: { + firstName: 'Lorem', + secondName: 'ipsum', + description: 'Lorem ipsum', + }, + }, + ], + }, }, }; diff --git a/src/blocks/Table/index.ts b/src/blocks/Table/index.ts index a7f587811..8ccf96f3d 100644 --- a/src/blocks/Table/index.ts +++ b/src/blocks/Table/index.ts @@ -9,7 +9,24 @@ const TableBlockConfig = { component: TableBlock, schema: { name: 'Table Block', + group: 'block', inputs: generateFromAJV(TableBlockSchema['table-block'] as unknown as JSONSchemaType<{}>), + default: { + type: 'table-block', + title: 'Lorem ipsum dolor sit amet', + table: { + content: [ + ['Lorem', 'ipsum 1', 'dolor 2', 'sit 3'], + ['Lorem 1', '0', '0', '0'], + ['Lorem 2', '0', '0', '1'], + ['Lorem 3', '0', '0', '1'], + ['Lorem 4', '0', '1', '1'], + ['Lorem 5', '1', '1', '1'], + ], + legend: ['ipsum 1', 'ipsum 2'], + justify: ['start', 'center', 'center', 'center'], + }, + }, }, }; diff --git a/src/blocks/Tabs/index.ts b/src/blocks/Tabs/index.ts index 4d7be63d1..d0e63357d 100644 --- a/src/blocks/Tabs/index.ts +++ b/src/blocks/Tabs/index.ts @@ -9,6 +9,7 @@ const TabsBlockConfig = { component: TabsBlock, schema: { name: 'Tabs Block', + group: 'block', inputs: generateFromAJV(TabsBlockSchema['tabs-block'] as unknown as JSONSchemaType<{}>), default: { title: 'Tabs Block', diff --git a/src/common/types/forms.ts b/src/common/types/forms.ts index 8ade3e18a..285bf89bb 100644 --- a/src/common/types/forms.ts +++ b/src/common/types/forms.ts @@ -1,6 +1,7 @@ export interface BlockConfig { name: string; inputs: Array<ConfigInput>; + group?: string; default?: object; } diff --git a/src/constructor-items.ts b/src/constructor-items.ts index d11730fda..a37cd8c99 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -43,7 +43,7 @@ import TableBlockConfig from './blocks/Table'; import TabsBlockConfig from './blocks/Tabs'; import TestEditorBlockConfig from './blocks/TestEditorBlock'; import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock'; -import {ConfigInput} from './common/types'; +import {BlockConfig} from './common/types'; import {SliderNewBlock} from './blocks/unstable'; import {BlockType, NavigationItemType, SubBlockType} from './models'; import { @@ -72,7 +72,7 @@ import BasicCardConfig from './sub-blocks/BasicCard'; import ContentConfig from './sub-blocks/Content'; import DividerConfig from './sub-blocks/Divider'; import ImageCardConfig from './sub-blocks/ImageCard'; -import LayoutItemConfig from './sub-blocks/LayoutItem/form'; +import LayoutItemConfig from './sub-blocks/LayoutItem'; import MediaCardConfig from './sub-blocks/MediaCard'; import PriceCardConfig from './sub-blocks/PriceCard'; import PriceDetailedConfig from './sub-blocks/PriceDetailed'; @@ -136,11 +136,7 @@ export interface BlockData { // TODO: remove any // eslint-disable-next-line @typescript-eslint/no-explicit-any component: React.ComponentType<any>; - schema: { - name: string; - inputs: ConfigInput[]; - default?: object; - }; + schema: BlockConfig; } export const blockDataMap: Record<string, BlockData> = { diff --git a/src/editor-v2/components/BlocksList/BlocksList.scss b/src/editor-v2/components/BlocksList/BlocksList.scss index e2b9c301e..e335c07ca 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.scss +++ b/src/editor-v2/components/BlocksList/BlocksList.scss @@ -14,22 +14,51 @@ $block: '.#{$ns}blocks-list'; &__card { padding: 8px; - cursor: pointer; + cursor: grab; user-select: none; margin-bottom: 8px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; + &:last-child { margin-bottom: 0; } &:active { background-color: var(--g-color-base-generic-hover); + cursor: grabbing; } } + &__name { + flex: 1; + } + &__title { @include text-subheader-3; margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: flex-start; + } + + &__subtitle { + @include text-subheader-2; + margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: flex-start; + } + + &__group { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; + } } &__section { diff --git a/src/editor-v2/components/BlocksList/BlocksList.tsx b/src/editor-v2/components/BlocksList/BlocksList.tsx index f2af64211..9f9f08d30 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.tsx +++ b/src/editor-v2/components/BlocksList/BlocksList.tsx @@ -1,10 +1,13 @@ import React, {PropsWithChildren, useCallback} from 'react'; -import {Card} from '@gravity-ui/uikit'; +import {Grip, Plus} from '@gravity-ui/icons'; +import {Button, Card, Icon} from '@gravity-ui/uikit'; +import _ from 'lodash'; import {ActionTypes, ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; import {useContentConfigStore} from '../../context/contentConfig'; +import {useEditorStore} from '../../context/editorContext'; import {useMessageSender} from '../../context/messagesContext'; import './BlocksList.scss'; @@ -15,8 +18,13 @@ export interface BlocksListProps { blocks: ItemConfig[]; } +interface BlockGroups { + [key: string]: ItemConfig[]; +} + const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { - const {blocks} = useContentConfigStore(); + const {blocks, insertBlock} = useContentConfigStore(); + const {selectedBlock} = useEditorStore(); const sendMessage = useMessageSender(); const onMouseDown = useCallback( @@ -26,14 +34,56 @@ const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { [sendMessage], ); + const onAddClick = useCallback( + (type: string) => { + const path = selectedBlock ? selectedBlock.path : [0]; + return insertBlock(path, type); + }, + [insertBlock, selectedBlock], + ); + + const groups = blocks.reduce<BlockGroups>((acc, currentBlock) => { + const group = currentBlock.schema.group; + if (group) { + if (!acc[group]) { + /* eslint-disable no-param-reassign */ + acc[group] = []; + } + acc[group].push(currentBlock); + } else { + if (!acc['other']) { + /* eslint-disable no-param-reassign */ + acc['other'] = []; + } + acc['other'].push(currentBlock); + } + + return acc; + }, {}); + return ( <div className={b()}> <div className={b('section')}> <div className={b('title')}>Blocks</div> - {blocks.map(({type, schema: {name}}) => ( - <Card key={type} className={b('card')} onMouseDown={() => onMouseDown(type)}> - {name} - </Card> + {Object.entries(groups).map(([key, groupBlocks]) => ( + <div className={b('group')} key={key}> + <div className={b('subtitle')}>{_.capitalize(key)}</div> + <div> + {groupBlocks.map(({type, schema: {name}}) => ( + <Card + key={type} + className={b('card')} + onMouseDown={() => onMouseDown(type)} + > + <Icon data={Grip} /> + <div className={b('name')}>{name}</div> + <Button onClick={() => onAddClick(type)}> + <Icon data={Plus} /> + </Button> + </Card> + ))} + </div> + </div> ))} </div> </div> diff --git a/src/editor-v2/components/Panels/Panels.scss b/src/editor-v2/components/Panels/Panels.scss new file mode 100644 index 000000000..e22ef9764 --- /dev/null +++ b/src/editor-v2/components/Panels/Panels.scss @@ -0,0 +1,40 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}panels'; + +#{$block} { + &__button-wrap { + --pc-editor-panels-button-gap: 8px; + position: absolute; + z-index: 1000; + + &_left { + bottom: var(--pc-editor-panels-button-gap); + left: calc(100% + var(--pc-editor-panels-button-gap)); + } + + &_right { + bottom: var(--pc-editor-panels-button-gap); + right: calc(100% + var(--pc-editor-panels-button-gap)); + } + } + + &__draggable { + position: relative; + + width: 12px; + height: 100%; + cursor: col-resize; + background-color: var(--g-color-line-generic); + + display: flex; + align-items: center; + justify-content: center; + + color: var(--g-color-base-background); + } +} diff --git a/src/editor-v2/components/Panels/Panels.tsx b/src/editor-v2/components/Panels/Panels.tsx new file mode 100644 index 000000000..8505f7209 --- /dev/null +++ b/src/editor-v2/components/Panels/Panels.tsx @@ -0,0 +1,79 @@ +import React, {ReactElement, useRef} from 'react'; + +import {ArrowLeftFromLine, ArrowRightFromLine, Grip} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; +import {ImperativePanelHandle, Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; + +import {block} from '../../../utils'; + +import './Panels.scss'; + +const b = block('panels'); + +interface PanelsProps { + left: ReactElement; + middle: ReactElement; + right: ReactElement; +} + +export const Panels = (props: PanelsProps) => { + const {left, right, middle} = props; + const leftPanel = useRef<ImperativePanelHandle>(null); + const rightPanel = useRef<ImperativePanelHandle>(null); + + const expandPanel = (reference: React.RefObject<ImperativePanelHandle>) => { + const panel = reference.current; + if (panel) { + panel.expand(); + } + }; + + const isCollapsed = { + left: leftPanel.current?.isCollapsed() || false, + right: rightPanel.current?.isCollapsed() || false, + }; + + return ( + <PanelGroup + className={b('panel')} + autoSaveId="page-constructor-editor" + direction="horizontal" + > + <Panel ref={leftPanel} collapsible defaultSize={25} minSize={15}> + {left} + </Panel> + <PanelResizeHandle className={b('draggable')}> + <Grip className={b('grip')} /> + {isCollapsed.left && ( + <div className={b('button-wrap', {left: true})}> + <Button + className={b('button')} + view="action" + onClick={() => expandPanel(leftPanel)} + > + <Icon data={ArrowRightFromLine} /> + </Button> + </div> + )} + </PanelResizeHandle> + <Panel minSize={20}>{middle}</Panel> + <PanelResizeHandle className={b('draggable')}> + <Grip className={b('grip')} /> + {isCollapsed.right && ( + <div className={b('button-wrap', {right: true})}> + <Button + className={b('button')} + view="action" + onClick={() => expandPanel(rightPanel)} + > + <Icon data={ArrowLeftFromLine} /> + </Button> + </div> + )} + </PanelResizeHandle> + <Panel ref={rightPanel} collapsible minSize={15} defaultSize={25}> + {right} + </Panel> + </PanelGroup> + ); +}; diff --git a/src/editor-v2/components/Tree/Tree.scss b/src/editor-v2/components/Tree/Tree.scss index 628987a44..1c197a67f 100644 --- a/src/editor-v2/components/Tree/Tree.scss +++ b/src/editor-v2/components/Tree/Tree.scss @@ -9,6 +9,17 @@ $block: '.#{$ns}tree'; #{$block} { padding: 12px; + &__head { + margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: space-between; + } + + &__title { + @include text-subheader-3; + } + &__item { padding: 8px; margin-bottom: 8px; diff --git a/src/editor-v2/components/Tree/Tree.tsx b/src/editor-v2/components/Tree/Tree.tsx index e3c2dc5fe..68a38be01 100644 --- a/src/editor-v2/components/Tree/Tree.tsx +++ b/src/editor-v2/components/Tree/Tree.tsx @@ -1,6 +1,7 @@ import React, {PropsWithChildren} from 'react'; -import {Card} from '@gravity-ui/uikit'; +import {TrashBin} from '@gravity-ui/icons'; +import {Button, Card, Icon} from '@gravity-ui/uikit'; import {ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; @@ -35,7 +36,7 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { }; const Tree = (_p: PropsWithChildren<TreeProps>) => { - const {config} = useContentConfigStore(); + const {config, resetBlocks} = useContentConfigStore(); const blockTree = generateTree(config.blocks); @@ -48,7 +49,21 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { )); }; - return <div className={b()}>{renderTree(blockTree)}</div>; + return ( + <div className={b()}> + <div className={b('head')}> + <div className={b('title')}>Tree</div> + <div className={b('actions')}> + <Button view="outlined-danger" onClick={() => resetBlocks()}> + <Icon data={TrashBin} /> + Clear all + </Button> + </div> + </div> + + <div className={b('cards')}>{renderTree(blockTree)}</div> + </div> + ); }; export default Tree; diff --git a/src/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss index b6dea3b88..18030f4fc 100644 --- a/src/editor-v2/containers/Editor/Editor.scss +++ b/src/editor-v2/containers/Editor/Editor.scss @@ -35,19 +35,6 @@ $block: '.#{$ns}editor'; flex: 1; } - &__draggable { - width: 12px; - height: 100%; - cursor: col-resize; - background-color: var(--g-color-line-generic); - - display: flex; - align-items: center; - justify-content: center; - - color: var(--g-color-base-background); - } - &__overlay { position: absolute; height: 100%; diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index d0fb5b641..406d75884 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -1,13 +1,11 @@ import React, {useCallback} from 'react'; -import {Grip} from '@gravity-ui/icons'; -import {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; - import {ActionTypes} from '../../../common/types'; import {PageContent} from '../../../models'; import {block} from '../../../utils'; import BigOverlay from '../../components/BigOverlay/BigOverlay'; import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; +import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; import {ContentConfigProvider} from '../../context/contentConfig'; import {EditorProvider, useEditorStore} from '../../context/editorContext'; @@ -65,27 +63,11 @@ const EditorView = (_props: EditorViewProps) => { return ( <div className={b()} onMouseUp={onMouseUp} onMouseMove={onMouseMove}> <div className={b('body')}> - <PanelGroup - className={b('panel')} - autoSaveId="page-constructor-editor" - direction="horizontal" - > - <Panel collapsible defaultSize={25} minSize={15}> - <Sidebar position={'left'} /> - </Panel> - <PanelResizeHandle className={b('draggable')}> - <Grip className={b('grip')} /> - </PanelResizeHandle> - <Panel minSize={20}> - <MiddleScreen /> - </Panel> - <PanelResizeHandle className={b('draggable')}> - <Grip className={b('grip')} /> - </PanelResizeHandle> - <Panel collapsible minSize={15} defaultSize={25}> - <Sidebar position={'right'} startMenu="block-config" /> - </Panel> - </PanelGroup> + <Panels + left={<Sidebar position={'left'} />} + right={<Sidebar position={'right'} startMenu="block-config" />} + middle={<MiddleScreen />} + /> </div> <BigOverlay className={b('overlay')} /> </div> diff --git a/src/editor-v2/context/contentConfig/store.ts b/src/editor-v2/context/contentConfig/store.ts index 819362418..8443cbcb2 100644 --- a/src/editor-v2/context/contentConfig/store.ts +++ b/src/editor-v2/context/contentConfig/store.ts @@ -34,6 +34,7 @@ export interface ContentConfigMethods extends WithStoreReducer { duplicateBlock: (path: number[]) => void; reorderBlock: (path: number[], destination: number[]) => void; updateField: (path: string, value: DynamicFormValue) => void; + resetBlocks: () => void; } export type ContentConfigStore = ContentConfigState & ContentConfigMethods; @@ -138,6 +139,12 @@ export const createContentConfigStore = initializeStore<ContentConfigState, Cont config: {...state.config, blocks: newBlocksConfig}, })); }, + resetBlocks: () => { + set((state) => ({ + ...state, + config: {...state.config, blocks: []}, + })); + }, reducer: function (action) { switch (action.type) { // Insert Actions diff --git a/src/sub-blocks/BackgroundCard/index.tsx b/src/sub-blocks/BackgroundCard/index.tsx index 17b5255d5..abdfa7f0c 100644 --- a/src/sub-blocks/BackgroundCard/index.tsx +++ b/src/sub-blocks/BackgroundCard/index.tsx @@ -13,10 +13,12 @@ const BackgroundCardConfig: BlockData = { inputs: generateFromAJV( BackgroundCardSchema['background-card'] as unknown as JSONSchemaType<{}>, ), + group: 'cards', default: { title: 'Background Card', text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', additionalInfo: 'Additional info', + backgroundColor: '#F0F0F0', }, }, }; diff --git a/src/sub-blocks/BannerCard/index.tsx b/src/sub-blocks/BannerCard/index.tsx index 8976cea40..e311d3e5e 100644 --- a/src/sub-blocks/BannerCard/index.tsx +++ b/src/sub-blocks/BannerCard/index.tsx @@ -10,6 +10,7 @@ const BannerCardConfig: BlockData = { component: BannerCard, schema: { name: 'Banner Card', + group: 'cards', inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>), default: { color: 'rgba(54, 151, 241, 0.4)', diff --git a/src/sub-blocks/BasicCard/index.tsx b/src/sub-blocks/BasicCard/index.tsx index 23a4a85cf..546df5946 100644 --- a/src/sub-blocks/BasicCard/index.tsx +++ b/src/sub-blocks/BasicCard/index.tsx @@ -10,6 +10,7 @@ const BasicCardConfig: BlockData = { component: BasicCard, schema: { name: 'Basic Card', + group: 'cards', inputs: generateFromAJV(BasicCardSchema['basic-card'] as unknown as JSONSchemaType<{}>), default: { title: 'Basic Card', diff --git a/src/sub-blocks/Content/index.tsx b/src/sub-blocks/Content/index.tsx index 55eedd9c7..48a055979 100644 --- a/src/sub-blocks/Content/index.tsx +++ b/src/sub-blocks/Content/index.tsx @@ -10,6 +10,7 @@ const ContentConfig: BlockData = { component: Content, schema: { name: 'Content', + group: 'cards', inputs: generateFromAJV(ContentBlock['content'] as unknown as JSONSchemaType<{}>), default: { title: 'Content', diff --git a/src/sub-blocks/ImageCard/index.tsx b/src/sub-blocks/ImageCard/index.tsx index 121e1a304..a4d09b1b8 100644 --- a/src/sub-blocks/ImageCard/index.tsx +++ b/src/sub-blocks/ImageCard/index.tsx @@ -10,6 +10,7 @@ const ImageCardConfig: BlockData = { component: ImageCard, schema: { name: 'Image Card', + group: 'cards', inputs: generateFromAJV(ImageCardSchema['image-card'] as unknown as JSONSchemaType<{}>), default: { title: 'Image Card', diff --git a/src/sub-blocks/LayoutItem/form.ts b/src/sub-blocks/LayoutItem/form.ts deleted file mode 100644 index 301899c2a..000000000 --- a/src/sub-blocks/LayoutItem/form.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {JSONSchemaType} from 'ajv'; - -import {generateFromAJV} from '../../utils/form-generator'; - -import LayoutItem from './LayoutItem'; -import {LayoutItem as LayoutItemSchema} from './schema'; - -const LayoutItemConfig = { - component: LayoutItem, - schema: { - name: 'Layout Item', - inputs: generateFromAJV(LayoutItemSchema as unknown as JSONSchemaType<{}>), - }, -}; - -export default LayoutItemConfig; diff --git a/src/sub-blocks/LayoutItem/index.tsx b/src/sub-blocks/LayoutItem/index.tsx index 7113f1499..e55f88d11 100644 --- a/src/sub-blocks/LayoutItem/index.tsx +++ b/src/sub-blocks/LayoutItem/index.tsx @@ -10,11 +10,16 @@ const LayoutItemConfig: BlockData = { component: LayoutItem, schema: { name: 'Layout Item', + group: 'cards', inputs: generateFromAJV(LayoutItemSchema as unknown as JSONSchemaType<{}>), default: { + type: 'layout-item', content: { - title: 'Layout Item', - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + title: 'Lorem ipsum', + text: 'Dolor sit amet', + }, + media: { + image: 'https://storage.yandexcloud.net/yc-www-community-images/event_ecaf1ef1-bc3a-40fa-adef-827b0959e6c3.jpg', }, }, }, diff --git a/src/sub-blocks/MediaCard/index.tsx b/src/sub-blocks/MediaCard/index.tsx index 22f6c9b7e..e13c9bad1 100644 --- a/src/sub-blocks/MediaCard/index.tsx +++ b/src/sub-blocks/MediaCard/index.tsx @@ -10,12 +10,14 @@ const MediaCardConfig: BlockData = { component: MediaCard, schema: { name: 'Media Card', + group: 'cards', inputs: generateFromAJV(MediaCardSchema['media-card'] as unknown as JSONSchemaType<{}>), default: { content: { title: 'Media Card', text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', }, + image: 'https://storage.yandexcloud.net/yc-www-community-images/event_ecaf1ef1-bc3a-40fa-adef-827b0959e6c3.jpg', }, }, }; diff --git a/src/sub-blocks/PriceCard/index.tsx b/src/sub-blocks/PriceCard/index.tsx index 604934389..e23bf5cdf 100644 --- a/src/sub-blocks/PriceCard/index.tsx +++ b/src/sub-blocks/PriceCard/index.tsx @@ -10,6 +10,7 @@ const PriceCardConfig: BlockData = { component: PriceCard, schema: { name: 'Price Card', + group: 'cards', inputs: generateFromAJV(PriceCardSchema['price-card'] as unknown as JSONSchemaType<{}>), default: { type: 'price-card', diff --git a/src/sub-blocks/PriceDetailed/index.tsx b/src/sub-blocks/PriceDetailed/index.tsx index f031b87d9..5907f8655 100644 --- a/src/sub-blocks/PriceDetailed/index.tsx +++ b/src/sub-blocks/PriceDetailed/index.tsx @@ -11,6 +11,7 @@ const PriceDetailedConfig: BlockData = { component: PriceDetailed, schema: { name: 'Price Detailed', + group: 'cards', inputs: generateFromAJV( PriceDetailedSchema['price-detailed'] as unknown as JSONSchemaType<{}>, ), diff --git a/src/sub-blocks/Quote/index.tsx b/src/sub-blocks/Quote/index.tsx index 47f45c716..756458ff3 100644 --- a/src/sub-blocks/Quote/index.tsx +++ b/src/sub-blocks/Quote/index.tsx @@ -10,6 +10,7 @@ const QuoteConfig: BlockData = { component: Quote, schema: { name: 'Quote', + group: 'cards', inputs: generateFromAJV(QuoteSchema['quote'] as unknown as JSONSchemaType<{}>), default: { text: 'A good decision is based on knowledge and not on numbers.', From e52b8fd6a909f4c2fa1fc1df2106591fa231b875 Mon Sep 17 00:00:00 2001 From: daff <daff@yandex-team.ru> Date: Mon, 3 Feb 2025 16:07:17 +0300 Subject: [PATCH 05/84] feat: add PostMessage API and Synchronized store --- package-lock.json | 244 ++++++++++++- package.json | 2 + playground/src/app/page.tsx | 8 +- src/common/hooks/usePostMessage.tsx | 134 ------- src/common/postMessage.ts | 42 +++ src/common/store.ts | 263 ++++++++++++++ src/common/types/actions.ts | 22 ++ src/common/types/actions/codes.ts | 27 -- src/common/types/actions/index.ts | 31 -- src/common/types/actions/initial.ts | 29 -- src/common/types/actions/insert.ts | 24 -- src/common/types/actions/other.ts | 21 -- src/common/types/actions/overlay.ts | 16 - src/common/types/actions/reorder.ts | 27 -- src/common/types/actions/select.ts | 20 -- src/common/types/common.ts | 11 - src/common/types/forms.ts | 4 + src/common/types/messages.ts | 28 +- .../editor/ChildrenWrap/ChildrenWrap.tsx | 12 +- src/components/editor/ItemWrap/ItemWrap.tsx | 12 +- .../PageConstructor/PageConstructor.tsx | 10 +- src/containers/PageConstructor/Provider.tsx | 6 +- .../ConstructorBlock/ConstructorBlock.tsx | 14 +- .../hooks/useEditorBlockMouseEvents.tsx | 141 +++----- src/context/editorContext/editorContext.ts | 18 - src/context/editorContext/editorProvider.tsx | 27 -- .../editorContext/hooks/useEditorStore.ts | 17 - src/context/editorContext/index.ts | 4 - src/context/editorContext/store.ts | 68 ---- .../PCEditorStoreContext.tsx | 13 + .../PCEditorStoreProvider.tsx | 47 +++ .../hooks/usePCEditorStore.ts | 10 + src/context/editorStoreContext/index.ts | 3 + .../hooks/useMessageObserver.tsx | 23 -- .../hooks/useMessageSender.tsx | 10 - .../hooks/useMessagesStore.tsx | 17 - src/context/messagesContext/index.ts | 5 - .../messagesContext/messagesContext.tsx | 21 -- .../messagesContext/messagesProvider.tsx | 38 -- src/context/messagesContext/store.ts | 44 --- .../components/BigOverlay/BigOverlay.scss | 1 + .../components/BigOverlay/BigOverlay.tsx | 85 +++-- .../components/BlockConfig/BlockConfig.tsx | 15 +- .../components/BlocksList/BlocksList.tsx | 16 +- .../components/DynamicForm/DynamicForm.tsx | 4 +- .../DynamicForm/Fields/Array/Array.tsx | 4 +- .../DynamicForm/Fields/Object/Object.tsx | 4 +- .../DynamicForm/Fields/OneOf/OneOf.tsx | 4 +- .../components/GlobalConfig/GlobalConfig.tsx | 9 +- .../components/MiddleScreen/MiddleScreen.tsx | 34 +- src/editor-v2/components/Overlay/Overlay.scss | 7 +- src/editor-v2/components/Overlay/Overlay.tsx | 86 +++-- src/editor-v2/components/Source/Source.tsx | 9 +- .../components/SourceCode/SourceCode.tsx | 8 +- .../components/StoreViewer/StoreViewer.scss | 39 ++ .../components/StoreViewer/StoreViewer.tsx | 40 +++ src/editor-v2/components/Tree/Tree.tsx | 6 +- src/editor-v2/containers/Editor/Editor.scss | 7 + src/editor-v2/containers/Editor/Editor.tsx | 51 +-- .../Editor/hooks/useAdminInitialize.tsx | 79 +++-- .../containers/__stories__/Editor.stories.tsx | 9 +- .../containers/__stories__/data.json | 335 ------------------ .../contentConfig/contentConfigContext.tsx | 15 - .../contentConfig/contentConfigProvider.tsx | 34 -- .../hooks/useContentConfigStore.tsx | 15 - src/editor-v2/context/contentConfig/index.ts | 4 - src/editor-v2/context/contentConfig/store.ts | 209 ----------- .../context/editorContext/editorContext.tsx | 16 - .../context/editorContext/editorProvider.tsx | 20 -- .../editorContext/hooks/useEditorStore.tsx | 17 - src/editor-v2/context/editorContext/index.ts | 4 - src/editor-v2/context/editorContext/store.ts | 101 ------ .../editorStore/MainEditorStoreContext.tsx | 13 + .../editorStore/MainEditorStoreProvider.tsx | 53 +++ .../editorStore/hooks/useMainEditorStore.ts | 10 + src/editor-v2/context/editorStore/index.ts | 3 + .../iframeContext/hooks/useIframeStore.tsx | 15 - .../context/iframeContext/iframeContext.tsx | 13 +- .../context/iframeContext/iframeProvider.tsx | 16 +- src/editor-v2/context/iframeContext/index.ts | 2 - src/editor-v2/context/iframeContext/store.ts | 68 ---- .../hooks/useMessageObserver.tsx | 23 -- .../hooks/useMessageSender.tsx | 10 - .../hooks/useMessagesStore.tsx | 17 - .../context/messagesContext/index.ts | 5 - .../messagesContext/messagesContext.tsx | 21 -- .../messagesContext/messagesProvider.tsx | 46 --- .../context/messagesContext/store.ts | 44 --- src/editor-v2/hooks/usePostMessageEvents.ts | 26 ++ src/hooks/useEditorInitialize.ts | 124 +++---- src/hooks/usePostMessageAPI.ts | 42 +++ src/index.ts | 1 + src/utils/store.ts | 29 +- 93 files changed, 1271 insertions(+), 2110 deletions(-) delete mode 100644 src/common/hooks/usePostMessage.tsx create mode 100644 src/common/postMessage.ts create mode 100644 src/common/store.ts create mode 100644 src/common/types/actions.ts delete mode 100644 src/common/types/actions/codes.ts delete mode 100644 src/common/types/actions/index.ts delete mode 100644 src/common/types/actions/initial.ts delete mode 100644 src/common/types/actions/insert.ts delete mode 100644 src/common/types/actions/other.ts delete mode 100644 src/common/types/actions/overlay.ts delete mode 100644 src/common/types/actions/reorder.ts delete mode 100644 src/common/types/actions/select.ts delete mode 100644 src/context/editorContext/editorContext.ts delete mode 100644 src/context/editorContext/editorProvider.tsx delete mode 100644 src/context/editorContext/hooks/useEditorStore.ts delete mode 100644 src/context/editorContext/index.ts delete mode 100644 src/context/editorContext/store.ts create mode 100644 src/context/editorStoreContext/PCEditorStoreContext.tsx create mode 100644 src/context/editorStoreContext/PCEditorStoreProvider.tsx create mode 100644 src/context/editorStoreContext/hooks/usePCEditorStore.ts create mode 100644 src/context/editorStoreContext/index.ts delete mode 100644 src/context/messagesContext/hooks/useMessageObserver.tsx delete mode 100644 src/context/messagesContext/hooks/useMessageSender.tsx delete mode 100644 src/context/messagesContext/hooks/useMessagesStore.tsx delete mode 100644 src/context/messagesContext/index.ts delete mode 100644 src/context/messagesContext/messagesContext.tsx delete mode 100644 src/context/messagesContext/messagesProvider.tsx delete mode 100644 src/context/messagesContext/store.ts create mode 100644 src/editor-v2/components/StoreViewer/StoreViewer.scss create mode 100644 src/editor-v2/components/StoreViewer/StoreViewer.tsx delete mode 100644 src/editor-v2/containers/__stories__/data.json delete mode 100644 src/editor-v2/context/contentConfig/contentConfigContext.tsx delete mode 100644 src/editor-v2/context/contentConfig/contentConfigProvider.tsx delete mode 100644 src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx delete mode 100644 src/editor-v2/context/contentConfig/index.ts delete mode 100644 src/editor-v2/context/contentConfig/store.ts delete mode 100644 src/editor-v2/context/editorContext/editorContext.tsx delete mode 100644 src/editor-v2/context/editorContext/editorProvider.tsx delete mode 100644 src/editor-v2/context/editorContext/hooks/useEditorStore.tsx delete mode 100644 src/editor-v2/context/editorContext/index.ts delete mode 100644 src/editor-v2/context/editorContext/store.ts create mode 100644 src/editor-v2/context/editorStore/MainEditorStoreContext.tsx create mode 100644 src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx create mode 100644 src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts create mode 100644 src/editor-v2/context/editorStore/index.ts delete mode 100644 src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx delete mode 100644 src/editor-v2/context/iframeContext/store.ts delete mode 100644 src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx delete mode 100644 src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx delete mode 100644 src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx delete mode 100644 src/editor-v2/context/messagesContext/index.ts delete mode 100644 src/editor-v2/context/messagesContext/messagesContext.tsx delete mode 100644 src/editor-v2/context/messagesContext/messagesProvider.tsx delete mode 100644 src/editor-v2/context/messagesContext/store.ts create mode 100644 src/editor-v2/hooks/usePostMessageEvents.ts create mode 100644 src/hooks/usePostMessageAPI.ts diff --git a/package-lock.json b/package-lock.json index c4f89aa32..6242fdff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@react-spring/web": "^9.7.3", "ajv": "^8.12.0", "ajv-keywords": "^5.1.0", + "deep-object-diff": "^1.1.9", "final-form": "^4.20.9", "github-buttons": "2.23.0", "immutable": "^4.3.7", @@ -23,6 +24,7 @@ "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", + "react-json-view": "^1.21.3", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", "react-resizable-panels": "^2.1.3", @@ -1934,11 +1936,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -8420,6 +8422,11 @@ "node": ">=0.10.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -8825,6 +8832,11 @@ "dev": true, "optional": true }, + "node_modules/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9877,6 +9889,14 @@ "typescript": ">=5" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -10493,6 +10513,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -12284,6 +12309,33 @@ "bser": "2.1.1" } }, + "node_modules/fbemitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", + "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", + "dependencies": { + "fbjs": "^3.0.0" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -12483,6 +12535,18 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/flux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz", + "integrity": "sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==", + "dependencies": { + "fbemitter": "^3.0.0", + "fbjs": "^3.0.1" + }, + "peerDependencies": { + "react": "^15.0.2 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -16613,11 +16677,21 @@ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -18297,6 +18371,25 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -20418,6 +20511,14 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -20476,6 +20577,11 @@ "node": ">=6" } }, + "node_modules/pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -20603,6 +20709,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==", + "dependencies": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, "node_modules/react-beautiful-dnd": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", @@ -20728,11 +20845,25 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-json-view": { + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", + "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", + "dependencies": { + "flux": "^4.0.1", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^8.3.2" + }, + "peerDependencies": { + "react": "^17.0.0 || ^16.3.0 || ^15.5.4", + "react-dom": "^17.0.0 || ^16.3.0 || ^15.5.4" + } + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "node_modules/react-monaco-editor": { "version": "0.53.0", @@ -20854,6 +20985,22 @@ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-textarea-autosize": { + "version": "8.5.7", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.7.tgz", + "integrity": "sha512-2MqJ3p0Jh69yt9ktFIaZmORHXw4c4bxSIhCeWiFwmJ9EYKgLmuNII3e9c9b2UO+ijl4StnpZdqpxNIhTdHvqtQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -21149,9 +21296,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -22376,6 +22523,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -24075,6 +24227,11 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-newlines": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", @@ -24358,6 +24515,31 @@ "node": ">= 4" } }, + "node_modules/ua-parser-js": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -24691,11 +24873,23 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "dev": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, @@ -24705,6 +24899,22 @@ } } }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", @@ -25532,6 +25742,11 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/webpack": { "version": "5.98.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", @@ -25801,6 +26016,15 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index b8fa888c4..92a405b20 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "@react-spring/web": "^9.7.3", "ajv": "^8.12.0", "ajv-keywords": "^5.1.0", + "deep-object-diff": "^1.1.9", "final-form": "^4.20.9", "github-buttons": "2.23.0", "immutable": "^4.3.7", @@ -118,6 +119,7 @@ "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", + "react-json-view": "^1.21.3", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", "react-resizable-panels": "^2.1.3", diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index e641a8a5f..061338f96 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -5,7 +5,6 @@ import React, {useEffect, useState} from 'react'; import {Editor} from '../../../src/editor-v2'; -import content from './content.json'; import './page.scss'; const b = block('home'); @@ -23,12 +22,7 @@ export default function Home() { <ThemeProvider theme={'light'}> <div className={b()}> {initialUrl && ( - <Editor - initialUrl={initialUrl} - content={content} - disableUrlField={false} - onUpdate={() => {}} - /> + <Editor initialUrl={initialUrl} disableUrlField={false} onUpdate={() => {}} /> )} </div> </ThemeProvider> diff --git a/src/common/hooks/usePostMessage.tsx b/src/common/hooks/usePostMessage.tsx deleted file mode 100644 index 8b3d03259..000000000 --- a/src/common/hooks/usePostMessage.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import {useCallback, useEffect} from 'react'; - -import { - Action, - Meta, - MetaSource, - PostMessageArgs, - SendOptions, - Subscriber, - WithStoreReducer, -} from '../../common/types'; - -interface UsePostMessageProps { - subscribers: Subscriber[]; - storesWithReducer: WithStoreReducer[]; - targetIframeElement?: HTMLIFrameElement; - urlOrigin?: string; -} - -// TODO: enable/disable via env variables -const DEBUG_MODE = false; - -export const usePostMessage = (props: UsePostMessageProps) => { - const {subscribers, storesWithReducer, targetIframeElement, urlOrigin} = props; - - const metaSource: MetaSource = targetIframeElement ? 'editor' : 'pc'; - const receiveMetaSource: MetaSource = targetIframeElement ? 'pc' : 'editor'; - - const notifySubscribers = useCallback( - (action: Action, source: MetaSource) => { - const meta: Meta = {source}; - - const {type, payload} = action; - - if (!type) { - return; - } - - if (subscribers) { - const foundedSubscribers = subscribers.filter( - (subscriber) => subscriber.action === type, - ); - - for (const storeWithReducer of storesWithReducer) { - storeWithReducer.reducer(action, meta); - } - - if (foundedSubscribers.length) { - for (const subscriber of foundedSubscribers) { - subscriber.handler(payload, meta); - } - } - } - }, - [storesWithReducer, subscribers], - ); - - const sendPostMessage = useCallback( - (args: PostMessageArgs) => { - if (targetIframeElement && targetIframeElement.contentWindow) { - if (targetIframeElement.contentWindow) { - targetIframeElement.contentWindow.postMessage(args, '*'); - } else { - // eslint-disable-next-line no-console - console.error('No Iframe element in Editor'); - } - } else { - window.parent.postMessage(args, '*'); - } - }, - [targetIframeElement], - ); - - const sendMessage = useCallback( - (action: Action, {debug = DEBUG_MODE, direction = 'both'}: SendOptions = {}) => { - if (debug) { - if (metaSource === 'editor') { - // eslint-disable-next-line no-console - console.log(`🔵 Editor ➡️ ${action.type}`, action.payload); - } - - if (metaSource === 'pc') { - // eslint-disable-next-line no-console - console.log(`🟢 Website ➡️ ${action.type}`, action.payload); - } - } - - if (direction === 'both') { - sendPostMessage({action, debug}); - notifySubscribers(action, metaSource); - } else if (direction === metaSource) { - notifySubscribers(action, metaSource); - } else { - sendPostMessage({action, debug}); - } - }, - [metaSource, notifySubscribers, sendPostMessage], - ); - - useEffect(() => { - const onMessage = (e: MessageEvent) => { - const {action, debug}: PostMessageArgs = e.data; - - if (urlOrigin && e.origin !== urlOrigin) { - return; - } - - if (action && action.type) { - if (debug) { - if (metaSource === 'editor') { - // eslint-disable-next-line no-console - console.log(`🔵 ➡️ Editor ${action.type}`, action.payload); - } - - if (metaSource === 'pc') { - // eslint-disable-next-line no-console - console.log(`🟢 ➡️ Website ${action.type}`, action.payload); - } - } - notifySubscribers(action, receiveMetaSource); - } - }; - - window.addEventListener('message', onMessage); - - return () => { - window.removeEventListener('message', onMessage); - }; - }, [metaSource, notifySubscribers, receiveMetaSource, targetIframeElement, urlOrigin]); - - return { - sendMessage, - }; -}; diff --git a/src/common/postMessage.ts b/src/common/postMessage.ts new file mode 100644 index 000000000..47695356f --- /dev/null +++ b/src/common/postMessage.ts @@ -0,0 +1,42 @@ +import {useEffect} from 'react'; + +import {ActionMessageTypes, EventMessageTypes, PostMessageAPIMessage} from './types'; + +export function requestActionPostMessage<K extends keyof ActionMessageTypes>( + action: K, + data: ActionMessageTypes[K], + destinationElement: Window, +) { + const message = {action, data} as PostMessageAPIMessage<K>; + destinationElement.postMessage(message); +} + +export function listenPostMessageEvents<K extends keyof EventMessageTypes>( + action: K, + callback: (data: EventMessageTypes[K]) => void, +) { + const onMessage = (e: MessageEvent) => { + const message = e.data as PostMessageAPIMessage<K>; + + if ('action' in message && message.action === action) { + return callback(message.data); + } + + return undefined; + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; +} + +export function usePostMessageAPIListener<K extends keyof EventMessageTypes>( + action: K, + callback: (data: EventMessageTypes[K]) => void, +) { + useEffect(() => { + return listenPostMessageEvents(action, callback); + }, [action, callback]); +} diff --git a/src/common/store.ts b/src/common/store.ts new file mode 100644 index 000000000..37a246003 --- /dev/null +++ b/src/common/store.ts @@ -0,0 +1,263 @@ +import _ from 'lodash'; + +// TODO: remove imports from editor-v2 +import {ZOOM_STEPS} from '../editor-v2/constants'; +import { + duplicateArrayItem, + generateChildrenPathFromArray, + getDestinationShiftBeforeReorder, + insert, + isItemsNeighbours, + modifyObjectByPath, + removeFromArray, + reorderArrayItems, +} from '../editor-v2/utils'; +import {ConstructorBlock, PageContent} from '../models'; +import {initializeStore} from '../utils/store'; + +import {ConfigInput, DynamicFormValue, ItemConfig} from './types'; + +export interface EditorState { + height?: number; + zoom: number; + + manipulateOverlayMode: 'insert' | 'reorder' | false; + selectedBlock?: number[]; + initialized: boolean; + + content: PageContent; + blocks: Array<ItemConfig>; + subBlocks: Array<ItemConfig>; + global: Array<ConfigInput>; + + preInsertBlockType: string | null; + preReorderBlockPath: number[] | null; +} + +export interface EditorMethods { + initialize(): void; + setSelectedBlock(path: number[]): void; + setHeight(height: number): void; + setZoom(zoom: number): void; + increaseZoom(): void; + decreaseZoom(): void; + setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; + setContent(data: PageContent): void; + insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; + enableInsertMode(blockType: string): void; + enableReorderMode(path: number[]): void; + disableMode(): void; + updateField(path: string, value: DynamicFormValue): void; + deleteBlock(path: number[]): void; + duplicateBlock(path: number[]): void; + reorderBlock(path: number[], destination: number[], position?: 'prepend' | 'append'): void; + resetInitialize(): void; + resetBlocks(): void; +} + +export type EditorStore = EditorState & EditorMethods; + +// TODO: split methods and state +export const createEditorStore = initializeStore<EditorState, EditorMethods>( + { + height: 100, + zoom: 100, + manipulateOverlayMode: false, + selectedBlock: undefined, + initialized: false, + content: {blocks: []}, + blocks: [], + subBlocks: [], + global: [], + preInsertBlockType: null, + preReorderBlockPath: null, + }, + (set, get) => ({ + setHeight(height: number) { + // We have to add 200-500px, because of bottom padding or margin of last element + // which is not taken into calculation of final height + const newHeight = height + 500; + set((state) => ({...state, height: newHeight})); + }, + setZoom(zoom) { + if (zoom > 0) { + set((state) => ({...state, zoom})); + } + }, + increaseZoom() { + const currentZoom = get().zoom; + + for (const step of ZOOM_STEPS) { + if (currentZoom < step) { + get().setZoom(step); + break; + } + } + }, + decreaseZoom() { + const currentZoom = get().zoom; + const reverseSteps = ZOOM_STEPS.slice().reverse(); + + for (const step of reverseSteps) { + if (currentZoom > step) { + get().setZoom(step); + break; + } + } + }, + setConfig(data) { + set((state) => ({...state, ...data})); + }, + insertBlock: (arrayPath, blockType, position = 'append') => { + if (position === 'append') { + // TODO: fix + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign + arrayPath[arrayPath.length - 1] = arrayPath[arrayPath.length - 1] + 1; + } + + const blocksConfig = get().content.blocks; + const blocksData = get().blocks; + + const foundBlock = blocksData.find(({type}) => type === blockType); + const defaultValue = + foundBlock && foundBlock.schema.default + ? {...foundBlock.schema.default, type: blockType} + : {type: blockType}; + + const newBlocksConfig = modifyObjectByPath( + blocksConfig, + arrayPath, + (parentBlocks, index) => + insert(parentBlocks, index, defaultValue as ConstructorBlock), + ); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + enableInsertMode(blockType: string) { + set((state) => ({ + ...state, + manipulateOverlayMode: 'insert', + preInsertBlockType: blockType, + })); + }, + disableMode() { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + preInsertBlockType: undefined, + preReorderBlockPath: undefined, + })); + }, + enableReorderMode(path) { + set((state) => ({ + ...state, + manipulateOverlayMode: 'reorder', + preReorderBlockPath: path, + })); + }, + setContent(content) { + set((state) => ({ + ...state, + content: content, + })); + }, + initialize() { + set((state) => ({ + ...state, + initialized: true, + })); + }, + setSelectedBlock(path) { + set((state) => ({ + ...state, + selectedBlock: path, + })); + }, + updateField(path, value) { + set((state) => { + const newConfig = _.set(state.content, path, value); + return { + ...state, + content: newConfig, + }; + }); + }, + deleteBlock: (arrayPath) => { + const blocksConfig = get().content.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + duplicateBlock: (arrayPath) => { + const blocksConfig = get().content.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + reorderBlock: (arrayPath, destination, position = 'append') => { + if (position === 'append') { + // TODO: fix + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign + destination[destination.length - 1] = destination[destination.length - 1] + 1; + } + + let newBlocksConfig: ConstructorBlock[]; + const blocksConfig = get().content.blocks; + // Copy + const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath)); + + if (isItemsNeighbours(arrayPath, destination)) { + newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => { + return reorderArrayItems( + parentBlocks, + arrayPath[arrayPath.length - 1], + destination[destination.length - 1], + ); + }); + } else { + const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); + + // Delete + const blocksConfigWithoutBlock = modifyObjectByPath( + blocksConfig, + arrayPath, + removeFromArray, + ); + // Paste + newBlocksConfig = modifyObjectByPath( + blocksConfigWithoutBlock, + arrayDest, + (parentBlocks, index) => insert(parentBlocks, index, copiedBlock), + ); + } + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + resetInitialize: () => { + set((state) => ({ + ...state, + initialized: false, + })); + }, + resetBlocks: () => { + set((state) => ({ + ...state, + content: {...state.content, blocks: []}, + })); + }, + }), +); diff --git a/src/common/types/actions.ts b/src/common/types/actions.ts new file mode 100644 index 000000000..64c7465d5 --- /dev/null +++ b/src/common/types/actions.ts @@ -0,0 +1,22 @@ +import {PageContent} from '../../models'; +import {EditorState} from '../store'; + +export type MessageTypes = EventMessageTypes & ActionMessageTypes; + +export type EventMessageTypes = { + ON_INIT: {height: number}; + ON_RESIZE: {height: number}; + ON_MOUSE_UP: {path?: number[]; rect?: DOMRect; position?: string}; + ON_MOUSE_MOVE: {x: number; y: number}; + ON_HOVER_BLOCK: {rect?: DOMRect; position?: string}; + ON_CLICK_BLOCK: {path: number[]; rect: DOMRect}; + ON_RESIZE_BLOCK: {rect: DOMRect}; + ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; + ON_INITIAL_CONTENT: PageContent; +}; + +export type ActionMessageTypes = { + GET_SUPPORTED_BLOCKS: {}; + GET_CURRENT_CONTENT: {}; + SET_CONTENT: PageContent; +}; diff --git a/src/common/types/actions/codes.ts b/src/common/types/actions/codes.ts deleted file mode 100644 index 3ec1a5935..000000000 --- a/src/common/types/actions/codes.ts +++ /dev/null @@ -1,27 +0,0 @@ -export enum ActionTypes { - // Other Events - UpdateConfigs = 'UPDATE_CONFIGS', - SetHeight = 'SET_HEIGHT', - - // Initial Events - IframeReady = 'IFRAME_READY', - EditorReady = 'EDITOR_READY', - BlocksConfigs = 'BLOCKS_CONFIGS', - - // Insert Events - InsertBlock = 'INSERT_BLOCK', - InsertModeEnable = 'INSERT_MODE_ENABLE', - InsertModeDisable = 'INSERT_MODE_DISABLE', - - // Reorder Events - ReorderBlocks = 'REORDER_BLOCKS', - ReorderModeEnable = 'REORDER_MODE_ENABLE', - ReorderModeDisable = 'REORDER_MODE_DISABLE', - - // Overlay Mode - OverlayModeOnMove = 'OVERLAY_MODE_ON_MOVE', - - // Select Events - SelectBlock = 'SELECT_BLOCK', - UpdateSelectedBlockRect = 'UPDATE_SELECTED_BLOCK_RECT', -} diff --git a/src/common/types/actions/index.ts b/src/common/types/actions/index.ts deleted file mode 100644 index 2c7da200a..000000000 --- a/src/common/types/actions/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {InitialActions} from './initial'; -import {InsertActions} from './insert'; -import {OtherActions} from './other'; -import {OverlayActions} from './overlay'; -import {ReorderActions} from './reorder'; -import {SelectActions} from './select'; - -export type BaseAction<T extends string = string> = { - type: T; -}; - -export interface UnknownAction extends BaseAction { - // Allows any extra properties to be defined in an action. - [extraProps: string]: unknown; -} - -export type Action = - | InitialActions - | InsertActions - | OverlayActions - | ReorderActions - | SelectActions - | OtherActions; - -export * from './codes'; -export * from './initial'; -export * from './insert'; -export * from './other'; -export * from './overlay'; -export * from './reorder'; -export * from './select'; diff --git a/src/common/types/actions/initial.ts b/src/common/types/actions/initial.ts deleted file mode 100644 index b313c3a11..000000000 --- a/src/common/types/actions/initial.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {ConfigInput} from '../../../common/types'; -import {ItemConfig} from '../index'; - -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface IframeReadyAction extends UnknownAction { - type: ActionTypes.IframeReady; - payload: { - height: number; - }; -} - -export interface EditorReadyAction extends UnknownAction { - type: ActionTypes.EditorReady; - payload: unknown; -} - -export interface BlocksConfigsAction extends UnknownAction { - type: ActionTypes.BlocksConfigs; - payload: { - blocks: ItemConfig[]; - subBlocks: ItemConfig[]; - global: ConfigInput[]; - }; -} - -export type InitialActions = BlocksConfigsAction | IframeReadyAction | EditorReadyAction; diff --git a/src/common/types/actions/insert.ts b/src/common/types/actions/insert.ts deleted file mode 100644 index de88f33aa..000000000 --- a/src/common/types/actions/insert.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface InsertBlockAction extends UnknownAction { - type: ActionTypes.InsertBlock; - payload: { - path: number[]; - }; -} - -export interface InsertModeEnableAction extends UnknownAction { - type: ActionTypes.InsertModeEnable; - payload: { - blockType: string; - }; -} - -export interface InsertModeDisableAction extends UnknownAction { - type: ActionTypes.InsertModeDisable; - payload: undefined; -} - -export type InsertActions = InsertBlockAction | InsertModeEnableAction | InsertModeDisableAction; diff --git a/src/common/types/actions/other.ts b/src/common/types/actions/other.ts deleted file mode 100644 index 1f10e9217..000000000 --- a/src/common/types/actions/other.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {PageContent} from '../../../models'; - -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface UpdateConfigsAction extends UnknownAction { - type: ActionTypes.UpdateConfigs; - payload: { - content: PageContent; - }; -} - -export interface SetHeightAction extends UnknownAction { - type: ActionTypes.SetHeight; - payload: { - height: number; - }; -} - -export type OtherActions = UpdateConfigsAction | SetHeightAction; diff --git a/src/common/types/actions/overlay.ts b/src/common/types/actions/overlay.ts deleted file mode 100644 index dd5340dd2..000000000 --- a/src/common/types/actions/overlay.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface OverlayModeOnMoveAction extends UnknownAction { - type: ActionTypes.OverlayModeOnMove; - payload: { - block?: { - rect: DOMRect; - cursorPosition: 'top' | 'bottom' | 'left' | 'right'; - }; - cursor: {x: number; y: number}; - }; -} - -export type OverlayActions = OverlayModeOnMoveAction; diff --git a/src/common/types/actions/reorder.ts b/src/common/types/actions/reorder.ts deleted file mode 100644 index e49cc2c04..000000000 --- a/src/common/types/actions/reorder.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface ReorderModeEnableAction extends UnknownAction { - type: ActionTypes.ReorderModeEnable; - payload: { - path: number[]; - }; -} - -export interface ReorderModeDisableAction extends UnknownAction { - type: ActionTypes.ReorderModeDisable; - payload: undefined; -} - -export interface ReorderBlocksAction extends UnknownAction { - type: ActionTypes.ReorderBlocks; - payload: { - path: number[]; - }; -} - -export type ReorderActions = - | ReorderBlocksAction - | ReorderModeEnableAction - | ReorderModeDisableAction; diff --git a/src/common/types/actions/select.ts b/src/common/types/actions/select.ts deleted file mode 100644 index cf451ab84..000000000 --- a/src/common/types/actions/select.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {ActionTypes} from './codes'; - -import {UnknownAction} from './index'; - -export interface SelectBlockAction extends UnknownAction { - type: ActionTypes.SelectBlock; - payload: { - path: number[]; - rect: DOMRect; - }; -} - -export interface UpdateSelectedBlockRectAction extends UnknownAction { - type: ActionTypes.UpdateSelectedBlockRect; - payload: { - rect: DOMRect; - }; -} - -export type SelectActions = SelectBlockAction | UpdateSelectedBlockRectAction; diff --git a/src/common/types/common.ts b/src/common/types/common.ts index ce344e43f..2732c46ed 100644 --- a/src/common/types/common.ts +++ b/src/common/types/common.ts @@ -1,17 +1,6 @@ -import {Action} from './actions'; import {BlockConfig} from './forms'; -import {Meta} from './messages'; export interface ItemConfig { type: string; schema: BlockConfig; } - -export interface WithStoreReducer { - reducer: (action: Action, meta: Meta) => void; -} - -export type Subscriber<A extends Action = Action> = { - action: A['type']; - handler: (payload: A['payload'], meta: Meta) => void; -}; diff --git a/src/common/types/forms.ts b/src/common/types/forms.ts index 285bf89bb..6597b12e5 100644 --- a/src/common/types/forms.ts +++ b/src/common/types/forms.ts @@ -1,3 +1,7 @@ +import {PageContent} from '../../models'; + +export type DynamicFormValue = string | number | [] | object | boolean | PageContent | undefined; + export interface BlockConfig { name: string; inputs: Array<ConfigInput>; diff --git a/src/common/types/messages.ts b/src/common/types/messages.ts index b2a905b8e..aa9ec2a62 100644 --- a/src/common/types/messages.ts +++ b/src/common/types/messages.ts @@ -1,22 +1,12 @@ -import {Action} from './actions'; +import {EditorState} from '../store'; -export interface SendOptions { - direction?: MetaSource | 'both'; - debug?: boolean; -} +import {MessageTypes} from './actions'; -export interface PostMessageArgs { - action: Action; - debug: boolean; -} +export type PostMessageAPIMessage<K extends keyof MessageTypes> = { + action: K; + data: MessageTypes[K]; +}; -export type SubscriptionFunc = <A extends Action>( - type: A['type'], - payloadCallback: (payload: A['payload'], meta: Meta) => void, -) => void; - -export type MetaSource = 'pc' | 'editor'; - -export interface Meta { - source: MetaSource; -} +export type StoreSyncMessage = { + state: EditorState; +}; diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx index 1eb77dca3..78287b18f 100644 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, ReactNode, useContext} from 'react'; +import React, {PropsWithChildren, ReactNode, useCallback, useContext, useState} from 'react'; import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; import {BlockIdContext} from '../../../context/blockIdContext'; @@ -14,11 +14,17 @@ export interface ChildrenWrapProps extends PropsWithChildren { const ChildrenWrap = (props: ChildrenWrapProps) => { const {children} = props; + const [element, setElement] = useState<HTMLElement | undefined>(); + const blockRef = useCallback((node: HTMLElement | null) => { + if (node !== null) { + setElement(node); + } + }, []); const parentBlockId = useContext(BlockIdContext); - const {onMouseUp, onMouseMove} = useEditorBlockMouseEvents([parentBlockId, 0]); + const {onMouseUp, onMouseMove} = useEditorBlockMouseEvents([parentBlockId, 0], element); return ( - <div className={b()} onMouseMove={onMouseMove} onMouseUp={onMouseUp}> + <div ref={blockRef} className={b()} onMouseMove={onMouseMove} onMouseUp={onMouseUp}> {children} </div> ); diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx index 4c7842002..cf8fbad92 100644 --- a/src/components/editor/ItemWrap/ItemWrap.tsx +++ b/src/components/editor/ItemWrap/ItemWrap.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, useContext} from 'react'; +import React, {PropsWithChildren, useCallback, useContext, useState} from 'react'; import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; import {BlockIdContext} from '../../../context/blockIdContext'; @@ -13,12 +13,18 @@ export interface ItemWrapProps extends PropsWithChildren { } const ItemWrap = (props: ItemWrapProps) => { + const [element, setElement] = useState<HTMLElement | undefined>(); + const blockRef = useCallback((node: HTMLElement | null) => { + if (node !== null) { + setElement(node); + } + }, []); const {children, index} = props; const parentBlockId = useContext(BlockIdContext); - const adminBlockMouseEvents = useEditorBlockMouseEvents([parentBlockId, index]); + const adminBlockMouseEvents = useEditorBlockMouseEvents([parentBlockId, index], element); return ( - <div className={b()} {...adminBlockMouseEvents}> + <div ref={blockRef} className={b()} {...adminBlockMouseEvents}> {children} </div> ); diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 801c6e554..c3e49b23f 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -8,11 +8,11 @@ import BrandFooter from '../../components/BrandFooter/BrandFooter'; import RootCn from '../../components/RootCn'; import {blockMap, navItemMap, subBlockMap} from '../../constructor-items'; import {AnimateContext} from '../../context/animateContext'; -import {useEditorStore} from '../../context/editorContext'; +import {usePCEditorStore} from '../../context/editorStoreContext'; import {InnerContext} from '../../context/innerContext'; import {ProjectSettingsContext} from '../../context/projectSettingsContext'; import {useTheme} from '../../context/theme'; -import useEditorInitialize from '../../hooks/useEditorInitialize'; +import {useInitializeEditorEvents} from '../../hooks/useEditorInitialize'; import { BlockTypes, CustomConfig, @@ -64,8 +64,10 @@ export const Constructor = (props: PageConstructorProps) => { const [content, setContent] = useState<PageContent>({blocks, background}); const theme = useTheme(); - const {initialized} = useEditorStore(); - useEditorInitialize({content, setContent}); + const store = usePCEditorStore(); + const {initialized} = store; + + useInitializeEditorEvents({initialContent: {blocks, background}, setContent}); const {context} = useMemo( () => ({ diff --git a/src/containers/PageConstructor/Provider.tsx b/src/containers/PageConstructor/Provider.tsx index 0d99ddbe2..389c852d2 100644 --- a/src/containers/PageConstructor/Provider.tsx +++ b/src/containers/PageConstructor/Provider.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import {DEFAULT_THEME} from '../../components/constants'; import {AnalyticsContext, AnalyticsContextProps} from '../../context/analyticsContext'; -import {EditorProvider} from '../../context/editorContext'; +import {PCEditorStoreProvider} from '../../context/editorStoreContext'; import { DEFAULT_FORMS_CONTEXT_VALUE, FormsContext, @@ -12,7 +12,6 @@ import {ImageContext, ImageContextProps} from '../../context/imageContext'; import {LocaleContext, LocaleContextProps} from '../../context/localeContext'; import {LocationContext, LocationContextProps} from '../../context/locationContext'; import {MapsContext, MapsContextType, initialMapValue} from '../../context/mapsContext/mapsContext'; -import {PostMessageProvider} from '../../context/messagesContext'; import {MobileContext} from '../../context/mobileContext'; import { ProjectSettingsContext, @@ -64,8 +63,7 @@ export const PageConstructorProvider = ( <AnalyticsContext.Provider value={analytics} />, <FormsContext.Provider value={forms} />, <SSRContext.Provider value={{isServer: ssrConfig?.isServer}} />, - <EditorProvider />, - <PostMessageProvider />, + <PCEditorStoreProvider />, ].reduceRight((prev, provider) => React.cloneElement(provider, {}, prev), children); /* eslint-enable react/jsx-key */ diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index 6f6697754..cd9c6b485 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import pick from 'lodash/pick'; @@ -22,16 +22,22 @@ export const ConstructorBlock = ({ data, children, }: React.PropsWithChildren<ConstructorBlockProps>) => { - const adminBlockMouseEvents = useEditorBlockMouseEvents([index]); + const [element, setElement] = useState<HTMLElement | undefined>(); + const blockRef = useCallback((node: HTMLElement | null) => { + if (node !== null) { + setElement(node); + } + }, []); + const adminBlockMouseEvents = useEditorBlockMouseEvents([index], element); const {type} = data; - const blockBaseProps = React.useMemo( + const blockBaseProps = useMemo( () => pick(data, ['anchor', 'visible', 'resetPaddings', 'indent']), [data], ); return ( - <div {...adminBlockMouseEvents}> + <div ref={blockRef} {...adminBlockMouseEvents}> <BlockDecoration type={type} index={index} {...blockBaseProps}> <BlockBase className={b({type})} {...blockBaseProps}> {children} diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx index b0c778771..b33985716 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx @@ -1,124 +1,81 @@ -import React, {useCallback, useContext} from 'react'; +import React, {useCallback, useEffect} from 'react'; -import {ActionTypes} from '../../../../../common/types/actions'; -import {EditorContext, useEditorStore} from '../../../../../context/editorContext'; -import {useMessageSender} from '../../../../../context/messagesContext/hooks/useMessageSender'; +import _ from 'lodash'; + +import {usePCEditorStore} from '../../../../../context/editorStoreContext'; +import {sendEventPostMessage} from '../../../../../hooks/usePostMessageAPI'; import {getCursorPositionOverElement} from '../../../../../utils/editor'; -const useEditorBlockMouseEvents = (arrayIndex: number[]) => { - const sendMessage = useMessageSender(); - const {manipulateOverlayMode} = useEditorStore(); - const {setActiveElement} = useContext(EditorContext); +const useEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { + const {selectedBlock} = usePCEditorStore(); const onMouseUp = useCallback( - (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - if (manipulateOverlayMode) { - const rect = event.currentTarget.getClientRects().item(0); + (e: React.MouseEvent) => { + e.stopPropagation(); + if (element) { + const rect = element.getClientRects().item(0); if (rect) { - const position = getCursorPositionOverElement(rect, event); - - let newLastIndex = arrayIndex[arrayIndex.length - 1]; - - if (position === 'right' || position === 'bottom') { - newLastIndex += 1; - } - - const newArrayIndex = [ - ...arrayIndex.slice(0, arrayIndex.length - 1), - newLastIndex, - ]; - - if (manipulateOverlayMode === 'insert') { - sendMessage({ - type: ActionTypes.InsertBlock, - payload: { - path: newArrayIndex, - }, - }); - } else if (manipulateOverlayMode === 'reorder') { - sendMessage({ - type: ActionTypes.ReorderBlocks, - payload: { - path: newArrayIndex, - }, - }); - } + const position = getCursorPositionOverElement(rect, e); + sendEventPostMessage('ON_MOUSE_UP', {path: arrayIndex, rect, position}); } } }, - [arrayIndex, manipulateOverlayMode, sendMessage], + [arrayIndex, element], ); const onMouseMove = useCallback( - (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - if (manipulateOverlayMode) { - const rect = event.currentTarget.getClientRects().item(0); + (e: React.MouseEvent) => { + e.stopPropagation(); + if (element) { + const rect = element.getClientRects().item(0); if (rect) { - const cursorPosition = getCursorPositionOverElement(rect, event); - sendMessage({ - type: ActionTypes.OverlayModeOnMove, - payload: { - block: {rect, cursorPosition}, - cursor: { - x: event.clientX, - y: event.clientY, - }, - }, - }); + const position = getCursorPositionOverElement(rect, e); + sendEventPostMessage('ON_HOVER_BLOCK', {rect, position}); } } }, - [manipulateOverlayMode, sendMessage], + [element], ); - const onMouseLeave = useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - if (manipulateOverlayMode) { - const rect = e.currentTarget.getClientRects().item(0); - if (rect) { - sendMessage({ - type: ActionTypes.OverlayModeOnMove, - payload: { - cursor: { - x: e.clientX, - y: e.clientY, - }, - }, - }); - } - } else { + const onMouseLeave = useCallback(() => { + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_HOVER_BLOCK', {}); } - }, - [manipulateOverlayMode, sendMessage], - ); + } + }, [element]); const onClick = useCallback( - (e: React.MouseEvent<HTMLDivElement>) => { + (e: React.MouseEvent) => { e.stopPropagation(); - if (!manipulateOverlayMode) { - const element = e.currentTarget; + if (element) { const rect = element.getClientRects().item(0); if (rect) { - setActiveElement(element); - - sendMessage({ - type: ActionTypes.SelectBlock, - payload: { - path: arrayIndex, - rect: rect, - }, - }); + sendEventPostMessage('ON_CLICK_BLOCK', {rect, path: arrayIndex}); } } }, - [arrayIndex, manipulateOverlayMode, sendMessage, setActiveElement], + [arrayIndex, element], ); + const onResize = useCallback(() => { + if (element && _.isEqual(selectedBlock, arrayIndex)) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_RESIZE_BLOCK', {rect}); + } + } + }, [arrayIndex, element, selectedBlock]); + + useEffect(() => { + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }, [element, onResize]); + return { onClick, onMouseMove, diff --git a/src/context/editorContext/editorContext.ts b/src/context/editorContext/editorContext.ts deleted file mode 100644 index b8ef739ef..000000000 --- a/src/context/editorContext/editorContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Context for managing internal Editor states - * Same exist in Editor - **/ - -import React from 'react'; - -import {StoreApi} from 'zustand'; - -import {EditorStore} from './store'; - -export interface EditorContextProps { - state?: StoreApi<EditorStore>; - activeElement?: HTMLElement; - setActiveElement: (element: HTMLElement) => void; -} - -export const EditorContext = React.createContext<EditorContextProps>({setActiveElement: () => {}}); diff --git a/src/context/editorContext/editorProvider.tsx b/src/context/editorContext/editorProvider.tsx deleted file mode 100644 index 4812ee416..000000000 --- a/src/context/editorContext/editorProvider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, {PropsWithChildren, useRef, useState} from 'react'; - -import {StoreApi} from 'zustand'; - -import {EditorContext} from './editorContext'; -import {EditorStore, createEditorStore} from './store'; - -export const EditorProvider: React.FC<PropsWithChildren> = ({children}) => { - const storeRef = useRef<StoreApi<EditorStore>>(); - const [activeElement, setActiveElement] = useState<HTMLElement | undefined>(); - - if (!storeRef.current) { - storeRef.current = createEditorStore(); - } - - return ( - <EditorContext.Provider - value={{ - state: storeRef.current, - activeElement: activeElement, - setActiveElement: setActiveElement, - }} - > - {children} - </EditorContext.Provider> - ); -}; diff --git a/src/context/editorContext/hooks/useEditorStore.ts b/src/context/editorContext/hooks/useEditorStore.ts deleted file mode 100644 index 4f2a346cd..000000000 --- a/src/context/editorContext/hooks/useEditorStore.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {EditorContext} from '../editorContext'; - -export const useEditorStore = () => { - const {state} = useContext(EditorContext); - - if (!state) { - throw new Error('Missing EditorContext'); - } - - return useStore(state); -}; - -export default useEditorStore; diff --git a/src/context/editorContext/index.ts b/src/context/editorContext/index.ts deleted file mode 100644 index c7b944eea..000000000 --- a/src/context/editorContext/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './editorContext'; -export * from './editorProvider'; -export * from './hooks/useEditorStore'; -export * from './store'; diff --git a/src/context/editorContext/store.ts b/src/context/editorContext/store.ts deleted file mode 100644 index c8f65aabc..000000000 --- a/src/context/editorContext/store.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {ActionTypes, WithStoreReducer} from '../../common/types'; -import {initializeStore} from '../../utils/store'; - -export interface EditorState { - manipulateOverlayMode: 'insert' | 'reorder' | false; - isSelectActive: boolean; - initialized: boolean; -} - -export interface EditorMethods extends WithStoreReducer {} - -export type EditorStore = EditorState & EditorMethods; - -export const createEditorStore = initializeStore<EditorState, EditorMethods>( - { - manipulateOverlayMode: false, - isSelectActive: false, - initialized: false, - }, - (set, _get) => ({ - reducer: (action) => { - switch (action.type) { - case ActionTypes.EditorReady: { - set((state) => ({ - ...state, - initialized: true, - })); - break; - } - case ActionTypes.ReorderModeEnable: { - set((state) => ({ - ...state, - manipulateOverlayMode: 'reorder', - })); - break; - } - case ActionTypes.ReorderModeDisable: { - set((state) => ({ - ...state, - manipulateOverlayMode: false, - })); - break; - } - case ActionTypes.InsertModeEnable: { - set((state) => ({ - ...state, - manipulateOverlayMode: 'insert', - })); - break; - } - case ActionTypes.InsertModeDisable: { - set((state) => ({ - ...state, - manipulateOverlayMode: false, - })); - break; - } - case ActionTypes.SelectBlock: { - set((state) => ({ - ...state, - isSelectActive: true, - })); - break; - } - } - }, - }), -); diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx new file mode 100644 index 000000000..7eebc64a7 --- /dev/null +++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorStore, createEditorStore} from '../../common/store'; + +export interface PCEditorStoreContextProps { + state: StoreApi<EditorStore>; +} + +export const PCEditorStoreContext = React.createContext<PCEditorStoreContextProps>({ + state: createEditorStore(), +}); diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx new file mode 100644 index 000000000..9a305ca55 --- /dev/null +++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx @@ -0,0 +1,47 @@ +import React, {PropsWithChildren, useCallback, useEffect, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorStore, createEditorStore} from '../../common/store'; +import {StoreSyncMessage} from '../../common/types'; + +import {PCEditorStoreContext} from './PCEditorStoreContext'; + +interface PCEditorStoreProviderProps extends PropsWithChildren {} + +export const PCEditorStoreProvider = ({children}: PCEditorStoreProviderProps) => { + const storeRef = useRef<StoreApi<EditorStore>>(); + + const syncStore = useCallback((message: StoreSyncMessage) => { + if (storeRef.current && message.state) { + storeRef.current.setState(message.state); + } + }, []); + + useEffect(() => { + const onMessage = (e: MessageEvent) => { + const message = e.data as StoreSyncMessage; + syncStore(message); + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; + }, [syncStore]); + + if (!storeRef.current) { + storeRef.current = createEditorStore(); + } + + return ( + <PCEditorStoreContext.Provider + value={{ + state: storeRef.current, + }} + > + {children} + </PCEditorStoreContext.Provider> + ); +}; diff --git a/src/context/editorStoreContext/hooks/usePCEditorStore.ts b/src/context/editorStoreContext/hooks/usePCEditorStore.ts new file mode 100644 index 000000000..31e9226aa --- /dev/null +++ b/src/context/editorStoreContext/hooks/usePCEditorStore.ts @@ -0,0 +1,10 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {PCEditorStoreContext} from '../PCEditorStoreContext'; + +export const usePCEditorStore = () => { + const {state} = useContext(PCEditorStoreContext); + return useStore(state); +}; diff --git a/src/context/editorStoreContext/index.ts b/src/context/editorStoreContext/index.ts new file mode 100644 index 000000000..7fa6e621a --- /dev/null +++ b/src/context/editorStoreContext/index.ts @@ -0,0 +1,3 @@ +export * from './hooks/usePCEditorStore'; +export * from './PCEditorStoreContext'; +export * from './PCEditorStoreProvider'; diff --git a/src/context/messagesContext/hooks/useMessageObserver.tsx b/src/context/messagesContext/hooks/useMessageObserver.tsx deleted file mode 100644 index 296ca9dea..000000000 --- a/src/context/messagesContext/hooks/useMessageObserver.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import {DependencyList, useEffect} from 'react'; - -import {Action} from '../../../common/types'; - -import {useMessagesStore} from './useMessagesStore'; - -export const useMessageObserver = <A extends Action>( - type: A['type'], - callback: (payload: A['payload']) => void, - deps: DependencyList = [], -) => { - const {subscribe, unsubscribe} = useMessagesStore(); - - useEffect(() => { - subscribe(type, callback); - - return () => { - unsubscribe(type, callback); - }; - }, deps); // eslint-disable-line react-hooks/exhaustive-deps -}; - -export default useMessageObserver; diff --git a/src/context/messagesContext/hooks/useMessageSender.tsx b/src/context/messagesContext/hooks/useMessageSender.tsx deleted file mode 100644 index 57f25eef0..000000000 --- a/src/context/messagesContext/hooks/useMessageSender.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import {useContext} from 'react'; - -import {PostMessageContext} from '../messagesContext'; - -export const useMessageSender = () => { - const {sendMessage} = useContext(PostMessageContext); - return sendMessage; -}; - -export default useMessageSender; diff --git a/src/context/messagesContext/hooks/useMessagesStore.tsx b/src/context/messagesContext/hooks/useMessagesStore.tsx deleted file mode 100644 index 1805cd32a..000000000 --- a/src/context/messagesContext/hooks/useMessagesStore.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {PostMessageContext} from '../messagesContext'; - -export const useMessagesStore = () => { - const {state} = useContext(PostMessageContext); - - if (!state) { - throw new Error('Missing PostMessageContext'); - } - - return useStore(state); -}; - -export default useMessagesStore; diff --git a/src/context/messagesContext/index.ts b/src/context/messagesContext/index.ts deleted file mode 100644 index eda561f12..000000000 --- a/src/context/messagesContext/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './messagesContext'; -export * from './messagesProvider'; -export * from './hooks/useMessageSender'; -export * from './hooks/useMessageObserver'; -export * from './hooks/useMessagesStore'; diff --git a/src/context/messagesContext/messagesContext.tsx b/src/context/messagesContext/messagesContext.tsx deleted file mode 100644 index 502b6a2c8..000000000 --- a/src/context/messagesContext/messagesContext.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Context for sending messages between Editor and App - * Same exist in Editor - **/ - -import React from 'react'; - -import {StoreApi} from 'zustand'; - -import {Action, SendOptions} from '../../common/types'; - -import {MessagesStore} from './store'; - -export interface PostMessageContextProps { - state?: StoreApi<MessagesStore>; - sendMessage: (action: Action, options?: SendOptions) => void; -} - -export const PostMessageContext = React.createContext<PostMessageContextProps>({ - sendMessage: () => {}, -}); diff --git a/src/context/messagesContext/messagesProvider.tsx b/src/context/messagesContext/messagesProvider.tsx deleted file mode 100644 index 86515bd34..000000000 --- a/src/context/messagesContext/messagesProvider.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, {PropsWithChildren, useRef} from 'react'; - -import {StoreApi} from 'zustand'; - -import {usePostMessage} from '../../common/hooks/usePostMessage'; -import {WithStoreReducer} from '../../common/types'; -import {useEditorStore} from '../editorContext'; - -import {PostMessageContext} from './messagesContext'; -import {MessagesStore, createMessagesStore} from './store'; - -export const PostMessageProvider = ({children}: PropsWithChildren) => { - const storeRef = useRef<StoreApi<MessagesStore>>(); - - if (!storeRef.current) { - storeRef.current = createMessagesStore(); - } - - const editorStore = useEditorStore(); - - const storesWithReducer: WithStoreReducer[] = [editorStore]; - - const {sendMessage} = usePostMessage({ - subscribers: storeRef.current?.getState().subscribers, - storesWithReducer, - }); - - return ( - <PostMessageContext.Provider - value={{ - state: storeRef.current, - sendMessage: sendMessage, - }} - > - {children} - </PostMessageContext.Provider> - ); -}; diff --git a/src/context/messagesContext/store.ts b/src/context/messagesContext/store.ts deleted file mode 100644 index ccfa31228..000000000 --- a/src/context/messagesContext/store.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Subscriber, SubscriptionFunc} from '../../common/types'; -import {initializeStore} from '../../utils/store'; - -export interface MessagesState { - subscribers: Subscriber[]; -} - -export interface MessagesMethods { - subscribe: SubscriptionFunc; - unsubscribe: SubscriptionFunc; -} - -export type MessagesStore = MessagesState & MessagesMethods; - -export const createMessagesStore = initializeStore<MessagesState, MessagesMethods>( - { - subscribers: [], - }, - (set, _get) => ({ - subscribe: (type, payloadCallback) => { - set((state) => ({ - ...state, - subscribers: [...state.subscribers, {action: type, handler: payloadCallback}], - })); - - return () => { - set((state) => ({ - ...state, - subscribers: state.subscribers.filter( - ({handler, action}) => payloadCallback !== handler || type !== action, - ), - })); - }; - }, - unsubscribe: (type, payloadCallback) => { - set((state) => ({ - ...state, - subscribers: state.subscribers.filter( - ({handler, action}) => payloadCallback !== handler || type !== action, - ), - })); - }, - }), -); diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.scss b/src/editor-v2/components/BigOverlay/BigOverlay.scss index e9d6abbf9..b1f87ba97 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.scss +++ b/src/editor-v2/components/BigOverlay/BigOverlay.scss @@ -13,6 +13,7 @@ $block: '.#{$ns}big-overlay'; width: 100%; height: 100%; pointer-events: none; + overflow: hidden; $border: 3px var(--g-color-line-brand) solid; diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx index 345e18ad5..140191685 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.tsx +++ b/src/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -1,17 +1,12 @@ -import React, {useContext, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {Stop} from '@gravity-ui/icons'; -import { - ActionTypes, - InsertModeDisableAction, - OverlayModeOnMoveAction, - ReorderModeDisableAction, -} from '../../../common/types'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {IframeContext, useIframeStore} from '../../context/iframeContext'; -import {useMessageObserver} from '../../context/messagesContext'; +import {useMainEditorStore} from '../../context/editorStore'; +import {IframeContext} from '../../context/iframeContext'; import './BigOverlay.scss'; @@ -20,46 +15,64 @@ const b = block('big-overlay'); interface BigOverlayProps extends ClassNameProps {} const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { - const {zoom} = useIframeStore(); + const {zoom, manipulateOverlayMode} = useMainEditorStore(); const {iframeElement} = useContext(IframeContext); const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( undefined, ); + const [source, setSource] = useState<'main' | 'iframe'>('main'); - useMessageObserver<OverlayModeOnMoveAction>( - ActionTypes.OverlayModeOnMove, - (payload, meta) => { - if (payload && payload.cursor) { - const {x, y} = payload.cursor; - const iframeRect = iframeElement?.getClientRects().item(0); - if (iframeRect) { - const zoomedX = (x * zoom) / 100; - const zoomedY = (y * zoom) / 100; - const newX = meta.source === 'editor' ? x : zoomedX + iframeRect.x; - const newY = meta.source === 'editor' ? y : zoomedY + iframeRect.y; - setMousePosition({x: newX, y: newY}); - } - } - }, - [iframeElement, zoom], - ); - - useMessageObserver<InsertModeDisableAction>(ActionTypes.InsertModeDisable, () => { + const onMouseUp = useCallback(() => { setMousePosition(undefined); - }); + }, []); - useMessageObserver<ReorderModeDisableAction>(ActionTypes.ReorderModeDisable, () => { - setMousePosition(undefined); - }); + const onIframeMouseEvent = useCallback((position: {x: number; y: number}) => { + setMousePosition(position); + setSource('iframe'); + }, []); + + usePostMessageAPIListener('ON_MOUSE_UP', onMouseUp); + usePostMessageAPIListener('ON_MOUSE_MOVE', onIframeMouseEvent); + + useEffect(() => { + const onEditorMouseEvent = (event: MouseEvent) => { + setMousePosition({x: event.clientX, y: event.clientY}); + setSource('main'); + }; + + document.addEventListener('mousemove', onEditorMouseEvent); + document.addEventListener('mousedown', onEditorMouseEvent); + + return () => { + document.removeEventListener('mousemove', onEditorMouseEvent); + document.removeEventListener('mousedown', onEditorMouseEvent); + }; + }, []); + + const realPositions = useMemo(() => { + if (mousePosition) { + const {x, y} = mousePosition; + const iframeRect = iframeElement?.getClientRects().item(0); + if (iframeRect) { + const zoomedX = (x * zoom) / 100; + const zoomedY = (y * zoom) / 100; + const newX = source === 'main' ? x : zoomedX + iframeRect.x; + const newY = source === 'main' ? y : zoomedY + iframeRect.y; + return {x: newX, y: newY}; + } + } + + return undefined; + }, [mousePosition, source, iframeElement, zoom]); return ( <div className={b(null, className)}> - {mousePosition ? ( + {realPositions && manipulateOverlayMode ? ( <div className={b('border')} style={{ - top: mousePosition.y, - left: mousePosition.x, + top: realPositions.y, + left: realPositions.x, }} > <Stop height={20} width={20} /> diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.tsx b/src/editor-v2/components/BlockConfig/BlockConfig.tsx index fcb71593d..cc2b93a28 100644 --- a/src/editor-v2/components/BlockConfig/BlockConfig.tsx +++ b/src/editor-v2/components/BlockConfig/BlockConfig.tsx @@ -2,12 +2,12 @@ import React from 'react'; import _ from 'lodash'; +import {DynamicFormValue} from '../../../common/types'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig'; -import {useEditorStore} from '../../context/editorContext'; +import {useMainEditorStore} from '../../context/editorStore'; import {generateChildrenPathFromArray} from '../../utils'; -import DynamicForm, {DynamicFormValue} from '../DynamicForm/DynamicForm'; +import DynamicForm from '../DynamicForm/DynamicForm'; import './BlockConfig.scss'; @@ -16,14 +16,11 @@ const b = block('block-config'); interface BlockConfigProps extends ClassNameProps {} const BlockConfig: React.FC<BlockConfigProps> = ({className}) => { - const {selectedBlock} = useEditorStore(); - const {config, blocks, subBlocks, updateField} = useContentConfigStore(); + const {selectedBlock, content, blocks, subBlocks, updateField} = useMainEditorStore(); - const currentBlockPath = selectedBlock?.path - ? generateChildrenPathFromArray(selectedBlock?.path) - : '[]'; + const currentBlockPath = selectedBlock ? generateChildrenPathFromArray(selectedBlock) : '[]'; - const currentConfig = _.get(config.blocks, currentBlockPath || ''); + const currentConfig = _.get(content.blocks, currentBlockPath || ''); const currentSchema = [...blocks, ...subBlocks].find(({type}) => type === currentConfig?.type); const onUpdate = (key: string, value: DynamicFormValue) => { diff --git a/src/editor-v2/components/BlocksList/BlocksList.tsx b/src/editor-v2/components/BlocksList/BlocksList.tsx index 9f9f08d30..a6f6dcce5 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.tsx +++ b/src/editor-v2/components/BlocksList/BlocksList.tsx @@ -4,11 +4,9 @@ import {Grip, Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; import _ from 'lodash'; -import {ActionTypes, ItemConfig} from '../../../common/types'; +import {ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig'; -import {useEditorStore} from '../../context/editorContext'; -import {useMessageSender} from '../../context/messagesContext'; +import {useMainEditorStore} from '../../context/editorStore'; import './BlocksList.scss'; @@ -23,20 +21,18 @@ interface BlockGroups { } const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { - const {blocks, insertBlock} = useContentConfigStore(); - const {selectedBlock} = useEditorStore(); - const sendMessage = useMessageSender(); + const {blocks, selectedBlock, insertBlock, enableInsertMode} = useMainEditorStore(); const onMouseDown = useCallback( (blockType: string) => { - sendMessage({type: ActionTypes.InsertModeEnable, payload: {blockType}}); + enableInsertMode(blockType); }, - [sendMessage], + [enableInsertMode], ); const onAddClick = useCallback( (type: string) => { - const path = selectedBlock ? selectedBlock.path : [0]; + const path = selectedBlock || [0]; return insertBlock(path, type); }, [insertBlock, selectedBlock], diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.tsx b/src/editor-v2/components/DynamicForm/DynamicForm.tsx index 8bef3e94d..722aa8411 100644 --- a/src/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/src/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -2,7 +2,7 @@ import React, {useCallback} from 'react'; import _ from 'lodash'; -import {ConfigInput} from '../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../common/types'; import {ClassNameProps, PageContent} from '../../../models'; import {block} from '../../../utils'; @@ -20,8 +20,6 @@ import './DynamicForm.scss'; const b = block('dynamic-form'); -export type DynamicFormValue = string | number | [] | object | boolean | PageContent | undefined; - interface DynamicFormProps extends ClassNameProps { blockConfig: Array<ConfigInput>; contentConfig: PageContent; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 823665bbf..185de27d5 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -3,11 +3,11 @@ import React, {useCallback} from 'react'; import {Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; -import {ArrayObjectInput, ArrayTextInput} from '../../../../../common/types'; +import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; import {ClassNameProps, PageContent} from '../../../../../models'; import {block} from '../../../../../utils'; import {removeFromArray, swapArrayItems} from '../../../../utils'; -import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; import Text from '../Text/Text'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index ff8bf177c..1d012eb2d 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -2,10 +2,10 @@ import React from 'react'; import {Card} from '@gravity-ui/uikit'; -import {ConfigInput} from '../../../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; import {ClassNameProps, PageContent} from '../../../../../models'; import {block} from '../../../../../utils'; -import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; import './Object.scss'; diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 4dafbe079..5db8644b9 100644 --- a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -2,10 +2,10 @@ import React, {useCallback, useMemo, useState} from 'react'; import {Card, RadioButton} from '@gravity-ui/uikit'; -import {OneOfInput} from '../../../../../common/types'; +import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; import {ClassNameProps, PageContent} from '../../../../../models'; import {block} from '../../../../../utils'; -import DynamicForm, {DynamicFormValue} from '../../DynamicForm'; +import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; import './OneOf.scss'; diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx index e203439d2..3fb4e5500 100644 --- a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx +++ b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import {DynamicFormValue} from '../../../common/types'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig'; -import DynamicForm, {DynamicFormValue} from '../DynamicForm/DynamicForm'; +import {useMainEditorStore} from '../../context/editorStore'; +import DynamicForm from '../DynamicForm/DynamicForm'; import './GlobalConfig.scss'; @@ -12,7 +13,7 @@ const b = block('global-config'); interface GlobalConfigProps extends ClassNameProps {} const GlobalConfig: React.FC<GlobalConfigProps> = ({className}) => { - const {global, updateField, config} = useContentConfigStore(); + const {global, content, updateField} = useMainEditorStore(); const onUpdate = (key: string, value: DynamicFormValue) => { updateField(key, value); @@ -21,7 +22,7 @@ const GlobalConfig: React.FC<GlobalConfigProps> = ({className}) => { return ( <div className={b(null, className)}> <div className={b('title')}>Global Config</div> - <DynamicForm contentConfig={config} blockConfig={global} onUpdate={onUpdate} /> + <DynamicForm contentConfig={content} blockConfig={global} onUpdate={onUpdate} /> </div> ); }; diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx index e48ed564a..6bd08ebda 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -1,11 +1,12 @@ -import React, {useContext} from 'react'; +import React, {useCallback, useContext, useState} from 'react'; import {Loader} from '@gravity-ui/uikit'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useEditorStore} from '../../context/editorContext'; -import {IframeContext, useIframeStore} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../context/editorStore'; +import {IframeContext} from '../../context/iframeContext'; import Overlay from '../Overlay/Overlay'; import TopBar from '../TopBar/TopBar'; @@ -16,9 +17,24 @@ const b = block('middle-screen'); interface MiddleScreenProps extends ClassNameProps {} const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { - const {url, height, zoom, decreaseZoom, increaseZoom, setZoom} = useIframeStore(); - const {initialized} = useEditorStore(); - const {setIframeElement} = useContext(IframeContext); + const {zoom, initialized, setZoom, decreaseZoom, increaseZoom} = useMainEditorStore(); + const {url, setIframeElement} = useContext(IframeContext); + const [height, setHeight] = useState(0); + + const onResize = useCallback( + (newHeight: number) => { + setHeight(newHeight + 500); + }, + [setHeight], + ); + + usePostMessageAPIListener('ON_RESIZE', ({height: newHeight}) => { + onResize(newHeight); + }); + + usePostMessageAPIListener('ON_INIT', ({height: newHeight}) => { + onResize(newHeight); + }); return ( <div className={b(null, className)}> @@ -40,11 +56,7 @@ const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { }} > <iframe - ref={(element) => { - if (element) { - setIframeElement(element); - } - }} + ref={(instance) => instance && setIframeElement(instance)} className={b('iframe')} src={url} height={`${height}px`} diff --git a/src/editor-v2/components/Overlay/Overlay.scss b/src/editor-v2/components/Overlay/Overlay.scss index 61e1b68a8..7d946ec71 100644 --- a/src/editor-v2/components/Overlay/Overlay.scss +++ b/src/editor-v2/components/Overlay/Overlay.scss @@ -22,11 +22,16 @@ $block: '.#{$ns}overlay'; border: 3px var(--g-color-line-brand) solid; box-sizing: border-box; box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); + + &_hover { + opacity: 0.5; + } } &__line { position: absolute; - border: $border-2; + //border: $border-2; + box-sizing: border-box; &_position { &_top { diff --git a/src/editor-v2/components/Overlay/Overlay.tsx b/src/editor-v2/components/Overlay/Overlay.tsx index ff231cc9f..51722ef3a 100644 --- a/src/editor-v2/components/Overlay/Overlay.tsx +++ b/src/editor-v2/components/Overlay/Overlay.tsx @@ -3,18 +3,10 @@ import React, {useCallback, useState} from 'react'; import {Copy, Grip, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; -import { - ActionTypes, - InsertModeDisableAction, - OverlayModeOnMoveAction, - ReorderModeDisableAction, -} from '../../../common/types'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig'; -import {useEditorStore} from '../../context/editorContext'; -import {useIframeStore} from '../../context/iframeContext'; -import {useMessageObserver, useMessageSender} from '../../context/messagesContext'; +import {useMainEditorStore} from '../../context/editorStore'; import './Overlay.scss'; @@ -31,54 +23,59 @@ interface InsertLineProps { } const Overlay: React.FC<OverlayProps> = ({className}) => { - const {selectedBlock} = useEditorStore(); + const { + height, + selectedBlock, + setSelectedBlock, + deleteBlock, + duplicateBlock, + manipulateOverlayMode, + enableReorderMode, + } = useMainEditorStore(); const [insertLineBox, setInsertLineBox] = useState<InsertLineProps | undefined>(undefined); - const {height} = useIframeStore(); - const {deleteBlock, duplicateBlock} = useContentConfigStore(); - const sendMessage = useMessageSender(); + const [hoverBorders, setHoverBorders] = useState<DOMRect | null>(null); + const [blockBorders, setBlockBorders] = useState<DOMRect | null>(null); const margin = 0; - useMessageObserver<OverlayModeOnMoveAction>(ActionTypes.OverlayModeOnMove, (payload) => { - if (payload && payload.block) { - const {rect, cursorPosition} = payload.block; + usePostMessageAPIListener('ON_HOVER_BLOCK', ({rect, position}) => { + setHoverBorders(rect || null); + if (rect && position) { setInsertLineBox({ left: rect.x, top: rect.y, height: rect.height, width: rect.width, - position: cursorPosition, + position: position, }); } }); - useMessageObserver<InsertModeDisableAction>(ActionTypes.InsertModeDisable, () => { - setInsertLineBox(undefined); + usePostMessageAPIListener('ON_CLICK_BLOCK', ({rect, path}) => { + setSelectedBlock(path); + setBlockBorders(rect || null); }); - useMessageObserver<ReorderModeDisableAction>(ActionTypes.ReorderModeDisable, () => { - setInsertLineBox(undefined); + usePostMessageAPIListener('ON_RESIZE_BLOCK', ({rect}) => { + setBlockBorders(rect || null); }); const onMouseDown = useCallback(() => { if (selectedBlock) { - sendMessage({ - type: ActionTypes.ReorderModeEnable, - payload: {path: selectedBlock.path}, - }); + enableReorderMode(selectedBlock); } - }, [selectedBlock, sendMessage]); + }, [enableReorderMode, selectedBlock]); return ( <div className={b(null, className)} style={{height: `${height}px`}}> - {selectedBlock ? ( + {blockBorders ? ( <div className={b('border')} style={{ - top: selectedBlock.rect.top - margin, - left: selectedBlock.rect.left - margin, - width: selectedBlock.rect.width + margin * 2, - height: selectedBlock.rect.height + margin * 2, + top: blockBorders.top - margin, + left: blockBorders.left - margin, + width: blockBorders.width + margin * 2, + height: blockBorders.height + margin * 2, }} > <div className={b('actions')}> @@ -97,7 +94,7 @@ const Overlay: React.FC<OverlayProps> = ({className}) => { className={b('action-button')} size={'m'} view={'action'} - onClick={() => selectedBlock && deleteBlock(selectedBlock.path)} + onClick={() => selectedBlock && deleteBlock(selectedBlock)} > <Icon data={TrashBin} size={18} /> </Button> @@ -106,23 +103,34 @@ const Overlay: React.FC<OverlayProps> = ({className}) => { className={b('action-button')} size={'m'} view={'action'} - onClick={() => selectedBlock && duplicateBlock(selectedBlock.path)} + onClick={() => selectedBlock && duplicateBlock(selectedBlock)} > <Icon data={Copy} size={18} /> </Button> </div> </div> ) : null} - {insertLineBox ? ( + {hoverBorders ? ( + <div + className={b('border', {hover: true})} + style={{ + top: hoverBorders.top - margin, + left: hoverBorders.left - margin, + width: hoverBorders.width + margin * 2, + height: hoverBorders.height + margin * 2, + }} + ></div> + ) : null} + {manipulateOverlayMode && hoverBorders && insertLineBox ? ( <div className={b('line', { position: insertLineBox.position, })} style={{ - top: insertLineBox.top, - left: insertLineBox.left, - width: insertLineBox.width, - height: insertLineBox.height, + top: hoverBorders.top, + left: hoverBorders.left, + width: hoverBorders.width, + height: hoverBorders.height, }} /> ) : null} diff --git a/src/editor-v2/components/Source/Source.tsx b/src/editor-v2/components/Source/Source.tsx index a9e065370..5e56661de 100644 --- a/src/editor-v2/components/Source/Source.tsx +++ b/src/editor-v2/components/Source/Source.tsx @@ -5,8 +5,8 @@ import {Button, Icon, TextInput} from '@gravity-ui/uikit'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useEditorStore} from '../../context/editorContext'; -import {IframeContext, useIframeStore} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../context/editorStore'; +import {IframeContext} from '../../context/iframeContext'; import './Source.scss'; @@ -15,9 +15,8 @@ const b = block('source'); interface SourceProps extends ClassNameProps {} const Source: React.FC<SourceProps> = ({className}) => { - const {url, setUrl} = useIframeStore(); - const {resetInitialize} = useEditorStore(); - const {disableUrlField} = useContext(IframeContext); + const {resetInitialize} = useMainEditorStore(); + const {disableUrlField, url, setUrl} = useContext(IframeContext); const onUpdateUrl = useCallback( (value: string) => { diff --git a/src/editor-v2/components/SourceCode/SourceCode.tsx b/src/editor-v2/components/SourceCode/SourceCode.tsx index e31c84072..94e84a2c3 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.tsx +++ b/src/editor-v2/components/SourceCode/SourceCode.tsx @@ -14,7 +14,7 @@ import yaml from 'js-yaml'; import {ClassNameProps, PageContent} from '../../../models'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig/hooks/useContentConfigStore'; +import {useMainEditorStore} from '../../context/editorStore'; import './SourceCode.scss'; @@ -23,7 +23,7 @@ const b = block('source-code'); interface SourceCodeProps extends ClassNameProps {} const SourceCode: React.FC<SourceCodeProps> = ({className}) => { - const {config, setConfig} = useContentConfigStore(); + const {content, setContent} = useMainEditorStore(); const [isOpen, setIsOpen] = useState(false); const [tempConfig, setTempConfig] = useState(''); const [format, setFormat] = useState<'yaml' | 'json'>('yaml'); @@ -43,13 +43,13 @@ const SourceCode: React.FC<SourceCodeProps> = ({className}) => { } if (object) { - setConfig(object as PageContent); + setContent(object as PageContent); } setIsOpen(false); }; - const text = format === 'yaml' ? yaml.dump(config) : JSON.stringify(config, null, 2); + const text = format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); return ( <div className={b(null, className)}> diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.scss b/src/editor-v2/components/StoreViewer/StoreViewer.scss new file mode 100644 index 000000000..93a126da9 --- /dev/null +++ b/src/editor-v2/components/StoreViewer/StoreViewer.scss @@ -0,0 +1,39 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; + +$block: '.#{$ns}store-viewer'; + +#{$block} { + background-color: var(--g-color-base-float-heavy); + border-radius: 12px; + border: 1px solid var(--g-color-line-generic); + overflow: hidden; + opacity: 0.3; + + &:hover { + opacity: 1; + } + + &_open { + opacity: 1; + + #{$block}__box { + display: block; + } + } + + &__head { + padding: 12px; + } + + &__box { + max-height: 80vh; + overflow: scroll; + display: none; + min-width: 400px; + max-width: 80vw; + padding: 12px; + background-color: var(--g-color-base-float); + white-space: pre; + } +} diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.tsx b/src/editor-v2/components/StoreViewer/StoreViewer.tsx new file mode 100644 index 000000000..c45f091e3 --- /dev/null +++ b/src/editor-v2/components/StoreViewer/StoreViewer.tsx @@ -0,0 +1,40 @@ +import React, {useState} from 'react'; + +import {Code} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; +import ReactJson from 'react-json-view'; + +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {removeFn} from '../../../utils/store'; + +import './StoreViewer.scss'; + +const b = block('store-viewer'); + +interface StoreViewerProps extends ClassNameProps { + store: object; +} + +const StoreViewer: React.FC<StoreViewerProps> = ({store, className}) => { + const [open, setOpen] = useState(false); + return ( + <div className={b({open}, className)}> + <div className={b('head')}> + <Button view="action" onClick={() => setOpen(!open)}> + <Icon data={Code} /> + </Button> + </div> + <div className={b('box')}> + <ReactJson + style={{backgroundColor: 'transparent'}} + src={removeFn(store)} + collapsed + theme="bright:inverted" + /> + </div> + </div> + ); +}; + +export default StoreViewer; diff --git a/src/editor-v2/components/Tree/Tree.tsx b/src/editor-v2/components/Tree/Tree.tsx index 68a38be01..a8874e8c2 100644 --- a/src/editor-v2/components/Tree/Tree.tsx +++ b/src/editor-v2/components/Tree/Tree.tsx @@ -5,7 +5,7 @@ import {Button, Card, Icon} from '@gravity-ui/uikit'; import {ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; -import {useContentConfigStore} from '../../context/contentConfig'; +import {useMainEditorStore} from '../../context/editorStore'; import './Tree.scss'; @@ -36,9 +36,9 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { }; const Tree = (_p: PropsWithChildren<TreeProps>) => { - const {config, resetBlocks} = useContentConfigStore(); + const {content, resetBlocks} = useMainEditorStore(); - const blockTree = generateTree(config.blocks); + const blockTree = generateTree(content.blocks); const renderTree = (items: TreeItem[], deepLevel = 0) => { return items.map(({type, children}, index) => ( diff --git a/src/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss index 18030f4fc..58ed10c8e 100644 --- a/src/editor-v2/containers/Editor/Editor.scss +++ b/src/editor-v2/containers/Editor/Editor.scss @@ -41,4 +41,11 @@ $block: '.#{$ns}editor'; width: 100%; z-index: 100; } + + &__debug-store { + position: fixed; + right: 8px; + bottom: 8px; + z-index: 1000; + } } diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index 406d75884..e7fd9b9ac 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -1,17 +1,14 @@ import React, {useCallback} from 'react'; -import {ActionTypes} from '../../../common/types'; import {PageContent} from '../../../models'; import {block} from '../../../utils'; import BigOverlay from '../../components/BigOverlay/BigOverlay'; import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; -import {ContentConfigProvider} from '../../context/contentConfig'; -import {EditorProvider, useEditorStore} from '../../context/editorContext'; +import StoreViewer from '../../components/StoreViewer/StoreViewer'; +import {MainEditorStoreProvider, useMainEditorStore} from '../../context/editorStore'; import {IframeProvider} from '../../context/iframeContext'; -import {PostMessageProvider} from '../../context/messagesContext'; -import {useMessageSender} from '../../context/messagesContext/hooks/useMessageSender'; import useAdminInitialize from './hooks/useAdminInitialize'; @@ -20,48 +17,31 @@ import './Editor.scss'; const b = block('editor'); interface EditorViewProps { - content: PageContent; onUpdate?: (pageContent: PageContent) => void; initialUrl: string; disableUrlField?: boolean; } const EditorView = (_props: EditorViewProps) => { - const {manipulateOverlayMode} = useEditorStore(); - const sendMessage = useMessageSender(); + const store = useMainEditorStore(); + const {manipulateOverlayMode, disableMode} = store; useAdminInitialize(); // Disable insert mode on any MouseUp event // Maybe should be attached to body const onMouseUp = useCallback( - (e: React.MouseEvent) => { - if (manipulateOverlayMode === 'insert') { - e.preventDefault(); - sendMessage({type: ActionTypes.InsertModeDisable, payload: undefined}); - } - if (manipulateOverlayMode === 'reorder') { - e.preventDefault(); - sendMessage({type: ActionTypes.ReorderModeDisable, payload: undefined}); - } - }, - [manipulateOverlayMode, sendMessage], - ); - - const onMouseMove = useCallback( (e: React.MouseEvent) => { if (manipulateOverlayMode) { - sendMessage({ - type: ActionTypes.OverlayModeOnMove, - payload: {cursor: {x: e.clientX, y: e.clientY}}, - }); + e.preventDefault(); + disableMode(); } }, - [manipulateOverlayMode, sendMessage], + [disableMode, manipulateOverlayMode], ); return ( - <div className={b()} onMouseUp={onMouseUp} onMouseMove={onMouseMove}> + <div className={b()} onMouseUp={onMouseUp}> <div className={b('body')}> <Panels left={<Sidebar position={'left'} />} @@ -70,20 +50,17 @@ const EditorView = (_props: EditorViewProps) => { /> </div> <BigOverlay className={b('overlay')} /> + <StoreViewer className={b('debug-store')} store={store} /> </div> ); }; export const Editor = (props: EditorViewProps) => { return ( - <EditorProvider> - <IframeProvider initialUrl={props.initialUrl} disableUrlField={props.disableUrlField}> - <ContentConfigProvider onUpdate={props.onUpdate} content={props.content}> - <PostMessageProvider> - <EditorView {...props} /> - </PostMessageProvider> - </ContentConfigProvider> - </IframeProvider> - </EditorProvider> + <IframeProvider initialUrl={props.initialUrl} disableUrlField={props.disableUrlField}> + <MainEditorStoreProvider> + <EditorView {...props} /> + </MainEditorStoreProvider> + </IframeProvider> ); }; diff --git a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx index 9bc69e9f4..bdf1e929d 100644 --- a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx +++ b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx @@ -1,39 +1,54 @@ -import {useContext, useEffect} from 'react'; - -import {ActionTypes, IframeReadyAction} from '../../../../common/types'; -import {ContentConfigContext, useContentConfigStore} from '../../../context/contentConfig'; -import {useEditorStore} from '../../../context/editorContext'; -import {useMessageObserver, useMessageSender} from '../../../context/messagesContext'; +import {usePostMessageAPIListener} from '../../../../common/postMessage'; +import {useMainEditorStore} from '../../../context/editorStore'; +import {usePostMessageEvents} from '../../../hooks/usePostMessageEvents'; const useAdminInitialize = () => { - const {state: contentConfigState} = useContext(ContentConfigContext); - const {config} = useContentConfigStore(); - const {initialized} = useEditorStore(); - const sendMessage = useMessageSender(); + const {requestPostMessage} = usePostMessageEvents(); + const { + initialize, + setConfig, + setContent, + manipulateOverlayMode, + disableMode, + insertBlock, + reorderBlock, + setSelectedBlock, + preInsertBlockType, + preReorderBlockPath, + } = useMainEditorStore(); + + usePostMessageAPIListener('ON_INIT', () => { + initialize(); + requestPostMessage('GET_SUPPORTED_BLOCKS', {}); + requestPostMessage('GET_CURRENT_CONTENT', {}); + }); + + usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { + setContent(data); + }); - useMessageObserver<IframeReadyAction>( - ActionTypes.IframeReady, - () => { - if (sendMessage && !initialized) { - sendMessage({type: ActionTypes.EditorReady, payload: undefined}); - sendMessage( - {type: ActionTypes.UpdateConfigs, payload: {content: config}}, - {direction: 'pc'}, - ); - } - }, - [sendMessage], - ); + usePostMessageAPIListener('ON_SUPPORTED_BLOCKS', (data) => { + setConfig(data); + }); - // Update configs for both instances on any changes - useEffect( - () => - contentConfigState && - contentConfigState.subscribe((state) => { - sendMessage({type: ActionTypes.UpdateConfigs, payload: {content: state.config}}); - }), - [sendMessage, contentConfigState], - ); + usePostMessageAPIListener('ON_MOUSE_UP', ({path, position}) => { + if (manipulateOverlayMode === 'insert' && path && position && preInsertBlockType) { + insertBlock( + path, + preInsertBlockType, + ['left', 'top'].includes(position) ? 'prepend' : 'append', + ); + } + if (manipulateOverlayMode === 'reorder' && path && position && preReorderBlockPath) { + reorderBlock( + preReorderBlockPath, + path, + ['left', 'top'].includes(position) ? 'prepend' : 'append', + ); + setSelectedBlock(path); + } + disableMode(); + }); }; export default useAdminInitialize; diff --git a/src/editor-v2/containers/__stories__/Editor.stories.tsx b/src/editor-v2/containers/__stories__/Editor.stories.tsx index 3ade46fb6..9d5ee6959 100644 --- a/src/editor-v2/containers/__stories__/Editor.stories.tsx +++ b/src/editor-v2/containers/__stories__/Editor.stories.tsx @@ -2,11 +2,8 @@ import React from 'react'; import {Meta, StoryFn} from '@storybook/react'; -import {PageContent} from '../../../models'; import {Editor} from '../Editor/Editor'; -import data from './data.json'; - export default { title: 'Editor/Main 2.0', component: Editor, @@ -15,11 +12,7 @@ export default { const DefaultTemplate: StoryFn = (args) => { return ( <div style={{height: '100vh', width: '100vw'}}> - <Editor - {...args} - content={data as unknown as PageContent} - initialUrl={'http://localhost:3000'} - /> + <Editor {...args} initialUrl={'http://localhost:3000'} /> </div> ); }; diff --git a/src/editor-v2/containers/__stories__/data.json b/src/editor-v2/containers/__stories__/data.json deleted file mode 100644 index 396e3394e..000000000 --- a/src/editor-v2/containers/__stories__/data.json +++ /dev/null @@ -1,335 +0,0 @@ -{ - "blocks": [ - { - "type": "header-block", - "title": "Yandex Open Source", - "description": "<p>Мы в Яндексе верим, что вклад в опенсорс — это вклад в технологическую эволюцию: без открытости, совместной работы и поддержки развитие IT‑индустрии сильно затруднено. Уже много лет мы используем в своих продуктах сторонние открытые технологии, а также делимся собственными и активно вовлекаем в их развитие разработчиков по всему миру.</p>", - "width": "s", - "verticalOffset": "l", - "offset": "default", - "resetPaddings": true, - "background": { - "image": { - "mobile": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover-m.png", - "desktop": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover.png" - }, - "color": "#EFF2F8", - "fullWidth": false, - "fullWidthMedia": true - } - }, - { - "type": "extended-features-block", - "title": { - "text": "Почему мы выкладываем наши технологии в открытый доступ?" - }, - "items": [ - { - "title": "Ответственность", - "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-01.svg", - "text": "<p>Мы верим, что вкладываться в развитие опенсорс‑технологий — это ответственность каждого технологического лидера на рынке. Без опенсорс‑решений не появились бы многие продукты и сервисы не только Яндекса, но и других крупных компаний, и мы хотим отдавать обратно, делиться теми нашими решениями, которые, как мы считаем, принесут реальную пользу.</p>" - }, - { - "title": "Польза для сообщества", - "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-02.svg", - "text": "<p>Технологии, которые мы разрабатываем, ежедневно помогают нам эффективно решать огромное количество самых разных задач в наших сервисах. Мы знаем, что разработчики вне Яндекса часто сталкиваются с теми же самыми задачами — и верим, что наши технологии могут быть полезны и им.</p>" - }, - { - "title": "Качество сервисов", - "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-03.svg", - "text": "<p>Для нас важно разрабатывать и использовать только качественные технологические решения. В особенности это касается опенсорса: зная, что наши решения увидят и будут использовать другие, мы уделяем их качеству особое внимание. А уже в открытом доступе у технологии больше шансов развиваться и улучшаться — в том числе, при участии сообщества разработчиков.</p>" - }, - { - "title": "Бизнес‑потенциал", - "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-04.svg", - "text": "<p>Мы верим, что при условии роста популярности наших решений и спроса на них со стороны сообщества, то, что мы выкладываем в опенсорс, может далее стать для нас бизнесом. То, что мы выкладываем в опенсорс, можно использовать и во внешних коммерческих проектах.</p>" - }, - { - "title": "Поиск талантов", - "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-05.svg", - "text": "<p>Мы ценим каждого, кто вкладывается в сторонние опенсорс‑решения или делится с миром своими. Контрибьюторы в наши продукты нам особенно важны: среди них мы ищем и находим тех, кто сможет развивать технологии уже будучи частью команды Яндекса.</p>" - } - ] - }, - { - "type": "card-layout-block", - "animated": false, - "title": "Краткая история опенсорса в Яндексе", - "description": "<p>С начала истории развития опенсорса в Яндексе мы успели выложить в открытый доступ десятки собственных проектов, использовать в разработке наших продуктов внешние технологии, а также внесли существенный вклад в их развитие.</p>", - "colSizes": { - "all": 12, - "lg": 3, - "md": 4, - "sm": 6 - }, - "anchor": { - "url": "history", - "text": "history" - }, - "children": [ - { - "type": "layout-item", - "content": { - "title": "2010", - "text": "<p>Методология веб‑разработки БЭМ (Блок‑Элемент‑Модификатор) выходит в оперсорс</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-01.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2012", - "text": "<p>Запуск Яндекс Браузера на базе Blink (Chromium)</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-02.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2013", - "text": "<p>Яндекс начинает контрибьютить в ядро Linux</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-03.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2016", - "text": "<p>Выход в опенсорс ClickHouse</p>\\n<p>Выход в опенсорс Hermione (с 2024 года — Testplane)</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2016.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2017", - "text": "<p>Выход в опенсорс CatBoost<br />\\nЯндекс начинает контрибьютить в PostgreSQL</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-05.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2018", - "text": "<p>Выход в опенсорс Одиссея</p>\\n<p>Яндекс — топ‑контрибьютор в WAL‑G</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-06.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2019", - "text": "<p>В Яндексе появляется команда разработки СУБД с открытым исходным кодом</p>\\n<p>Яндекс — спонсор разработки PostgreSQL</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-07.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2020", - "text": "<p>Выход в опенсорс Testsuite</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2020.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2022", - "text": "<p>Яндекс — один из основных спонсоров разработки PostgreSQL</p>\\n<p>Выход в опенсорс YDB, userver, YaLM 100B, DivKit, Yatagan</p>\\n<p>Старт программы «Код для всех»</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-09.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2023", - "text": "<p>Выход в опенсорс YTsaurus, Gravity UI, AppMetrica, Diplodoc, DataLens и счётчика Метрики</p>\\n<p>Старт Программы грантов Yandex Open Source</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2023.png" - } - }, - { - "type": "layout-item", - "content": { - "title": "2024", - "text": "<p>Первый Yandex Open Source Jam</p>\\n<p>Выход в опенсорс YaFSDP</p>" - }, - "media": { - "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2024.png" - } - } - ] - }, - { - "type": "card-layout-block", - "title": "Наши проекты", - "description": "<p>В Яндексе мы разрабатываем и развиваем технологические решения самых разных сфер применения, размеров и сложности. Поэтому и в открытый доступ попадают самые разные проекты — главное, чтобы они приносили пользу не только нам, но и другим.</p>", - "colSizes": { - "all": 12, - "lg": 3, - "md": 4, - "sm": 6 - }, - "anchor": { - "url": "projects", - "text": "projects" - }, - "children": [ - { - "type": "background-card", - "title": "YDB", - "text": "<p>Отказоустойчивая распределённая SQL база данных</p>", - "backgroundColor": "#2399FF", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-ydb.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "dark" - }, - { - "type": "background-card", - "title": "YTsaurus", - "text": "<p>Платформа для хранения и обработки больших данных</p>", - "backgroundColor": "#FFB23E", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-yt.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "light" - }, - { - "type": "background-card", - "title": "GravityUI", - "text": "<p>Библиотеки для создания интерфейсов</p>", - "backgroundColor": "#262626", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-gui.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "dark" - }, - { - "type": "background-card", - "title": "DivKit", - "text": "<p>Фреймворк для server‑driven интерфейсов</p>", - "backgroundColor": "#F1F1F1", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dk.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "light" - }, - { - "type": "background-card", - "title": "Diplodoc", - "text": "<p>Платформа для написания документации в концепции Docs as Code</p>", - "backgroundColor": "#79F985", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dd.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "light" - }, - { - "type": "background-card", - "title": "userver", - "text": "<p>Фреймворк для создания высоконагруженных приложений</p>", - "backgroundColor": "#FF9D73", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-u.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "light" - }, - { - "type": "background-card", - "title": "DataLens", - "text": "<p>BI-платформа для анализа и визуализации данных</p>", - "backgroundColor": "#FF7132", - "background": { - "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dl.png", - "alt": "card-background" - }, - "paddingBottom": "m", - "theme": "dark" - }, - { - "type": "basic-card", - "title": "И это ещё не всё", - "text": "<p>Узнать про наши опенсорс‑проекты больше вы можете на другой странице</p>", - "border": "none", - "buttons": [ - { - "text": "Все проекты", - "primary": true, - "theme": "monochrome", - "size": "promo", - "url": "/projects" - } - ] - } - ] - }, - { - "largeMedia": true, - "mediaOnly": false, - "size": "l", - "type": "media-block", - "direction": "content-media", - "anchor": { - "url": "1", - "text": "Фильм" - }, - "title": "<p>Смотрите фильм с YaC 22</p>", - "description": "<p>Руководители опенсорс‑проектов рассказывают про историю и культуру открытого кода в Яндексе.</p>", - "media": { - "youtube": "https://www.youtube.com/watch?v=G7G286S8ntc", - "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/video-cover.png" - }, - "disableShadow": true - }, - { - "type": "content-layout-block", - "size": "l", - "centered": true, - "textContent": { - "title": "", - "text": "", - "additionalInfo": "", - "buttons": [ - { - "text": "Сделано на GravityUI", - "theme": "monochrome", - "img": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/open-source-gravity-ui-button.svg", - "url": "https://gravity-ui.com/" - } - ] - } - } - ] -} diff --git a/src/editor-v2/context/contentConfig/contentConfigContext.tsx b/src/editor-v2/context/contentConfig/contentConfigContext.tsx deleted file mode 100644 index 9ae9e2791..000000000 --- a/src/editor-v2/context/contentConfig/contentConfigContext.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Context for managing PC configuration (aka Yaml Configs) - **/ - -import React from 'react'; - -import {StoreApi} from 'zustand'; - -import {ContentConfigStore} from './store'; - -export interface ContentConfigContextProps { - state?: StoreApi<ContentConfigStore>; -} - -export const ContentConfigContext = React.createContext<ContentConfigContextProps>({}); diff --git a/src/editor-v2/context/contentConfig/contentConfigProvider.tsx b/src/editor-v2/context/contentConfig/contentConfigProvider.tsx deleted file mode 100644 index 18a7f6276..000000000 --- a/src/editor-v2/context/contentConfig/contentConfigProvider.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, {PropsWithChildren, useRef} from 'react'; - -import {StoreApi} from 'zustand'; - -import {PageContent} from '../../../models'; - -import {ContentConfigContext} from './contentConfigContext'; -import {ContentConfigStore, createContentConfigStore} from './store'; - -interface ContentConfigProviderProps extends PropsWithChildren { - content: PageContent; - onUpdate?: (pageContent: PageContent) => void; -} - -export const ContentConfigProvider = ({ - content, - onUpdate, - children, -}: ContentConfigProviderProps) => { - const storeRef = useRef<StoreApi<ContentConfigStore>>(); - - if (!storeRef.current) { - storeRef.current = createContentConfigStore({config: content}); - if (onUpdate) { - storeRef.current?.subscribe((state) => onUpdate(state.config)); - } - } - - return ( - <ContentConfigContext.Provider value={{state: storeRef.current}}> - {children} - </ContentConfigContext.Provider> - ); -}; diff --git a/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx b/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx deleted file mode 100644 index 40d57fd1a..000000000 --- a/src/editor-v2/context/contentConfig/hooks/useContentConfigStore.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {ContentConfigContext} from '../contentConfigContext'; - -export const useContentConfigStore = () => { - const {state} = useContext(ContentConfigContext); - if (!state) { - throw new Error('Missing ContentConfigContext'); - } - return useStore(state); -}; - -export default useContentConfigStore; diff --git a/src/editor-v2/context/contentConfig/index.ts b/src/editor-v2/context/contentConfig/index.ts deleted file mode 100644 index 4772ca299..000000000 --- a/src/editor-v2/context/contentConfig/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './contentConfigContext'; -export * from './contentConfigProvider'; -export * from './hooks/useContentConfigStore'; -export * from './store'; diff --git a/src/editor-v2/context/contentConfig/store.ts b/src/editor-v2/context/contentConfig/store.ts deleted file mode 100644 index 8443cbcb2..000000000 --- a/src/editor-v2/context/contentConfig/store.ts +++ /dev/null @@ -1,209 +0,0 @@ -import _ from 'lodash'; - -import {ActionTypes, ConfigInput, ItemConfig, WithStoreReducer} from '../../../common/types'; -import {ConstructorBlock, PageContent} from '../../../models'; -import {DynamicFormValue} from '../../components/DynamicForm/DynamicForm'; -import { - duplicateArrayItem, - generateChildrenPathFromArray, - getDestinationShiftBeforeReorder, - insert, - isItemsNeighbours, - modifyObjectByPath, - removeFromArray, - reorderArrayItems, -} from '../../utils'; -import {initializeStore} from '../../utils/store'; - -export interface ContentConfigState { - config: PageContent; - blocks: Array<ItemConfig>; - subBlocks: Array<ItemConfig>; - global: Array<ConfigInput>; - - preInsertBlockType: string | null; - preReorderBlockPath: number[] | null; -} - -export interface ContentConfigMethods extends WithStoreReducer { - setConfig: (config: PageContent) => void; - setBlocks: (blocks: ItemConfig[]) => void; - setSubBlocks: (subBlocks: ItemConfig[]) => void; - insertBlock: (position: number[], blockType: string) => void; - deleteBlock: (path: number[]) => void; - duplicateBlock: (path: number[]) => void; - reorderBlock: (path: number[], destination: number[]) => void; - updateField: (path: string, value: DynamicFormValue) => void; - resetBlocks: () => void; -} - -export type ContentConfigStore = ContentConfigState & ContentConfigMethods; - -export const createContentConfigStore = initializeStore<ContentConfigState, ContentConfigMethods>( - { - config: {blocks: []}, - blocks: [], - subBlocks: [], - global: [], - preInsertBlockType: null, - preReorderBlockPath: null, - }, - (set, get) => ({ - setConfig: (config) => set((state) => ({...state, config})), - setBlocks: (blocks) => set((state) => ({...state, blocks})), - setSubBlocks: (subBlocks) => set((state) => ({...state, subBlocks})), - insertBlock: (arrayPath, blockType) => { - const blocksConfig = get().config.blocks; - const blocksData = get().blocks; - - const foundBlock = blocksData.find(({type}) => type === blockType); - const defaultValue = - foundBlock && foundBlock.schema.default - ? {...foundBlock.schema.default, type: blockType} - : {type: blockType}; - - const newBlocksConfig = modifyObjectByPath( - blocksConfig, - arrayPath, - (parentBlocks, index) => - insert(parentBlocks, index, defaultValue as ConstructorBlock), - ); - - set((state) => ({ - ...state, - config: {...state.config, blocks: newBlocksConfig}, - })); - }, - deleteBlock: (arrayPath) => { - const blocksConfig = get().config.blocks; - - const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); - - set((state) => ({ - ...state, - config: {...state.config, blocks: newBlocksConfig}, - })); - }, - duplicateBlock: (arrayPath) => { - const blocksConfig = get().config.blocks; - - const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem); - - set((state) => ({ - ...state, - config: {...state.config, blocks: newBlocksConfig}, - })); - }, - updateField: (path, value) => { - set((state) => { - const newConfig = _.set(state.config, path, value); - return { - ...state, - config: newConfig, - }; - }); - }, - reorderBlock: (arrayPath, destination) => { - let newBlocksConfig: ConstructorBlock[]; - const blocksConfig = get().config.blocks; - // Copy - const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath)); - - if (isItemsNeighbours(arrayPath, destination)) { - newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => { - return reorderArrayItems( - parentBlocks, - arrayPath[arrayPath.length - 1], - destination[destination.length - 1], - ); - }); - } else { - const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); - - // Delete - const blocksConfigWithoutBlock = modifyObjectByPath( - blocksConfig, - arrayPath, - removeFromArray, - ); - // Paste - newBlocksConfig = modifyObjectByPath( - blocksConfigWithoutBlock, - arrayDest, - (parentBlocks, index) => insert(parentBlocks, index, copiedBlock), - ); - } - - set((state) => ({ - ...state, - config: {...state.config, blocks: newBlocksConfig}, - })); - }, - resetBlocks: () => { - set((state) => ({ - ...state, - config: {...state.config, blocks: []}, - })); - }, - reducer: function (action) { - switch (action.type) { - // Insert Actions - case ActionTypes.InsertModeEnable: { - set((state) => ({ - ...state, - preInsertBlockType: action.payload.blockType, - })); - break; - } - case ActionTypes.InsertModeDisable: { - set((state) => ({ - ...state, - preInsertBlockType: null, - })); - break; - } - - case ActionTypes.InsertBlock: { - const {preInsertBlockType} = get(); - if (preInsertBlockType) { - const {path} = action.payload; - this.insertBlock(path, preInsertBlockType); - } - break; - } - // Reorder Actions - case ActionTypes.ReorderModeEnable: { - set((state) => ({ - ...state, - preReorderBlockPath: action.payload.path, - })); - break; - } - case ActionTypes.ReorderModeDisable: { - set((state) => ({ - ...state, - preReorderBlockPath: null, - })); - break; - } - case ActionTypes.ReorderBlocks: { - const {preReorderBlockPath} = get(); - if (preReorderBlockPath) { - const {path: destinationPath} = action.payload; - this.reorderBlock(preReorderBlockPath, destinationPath); - } - break; - } - case ActionTypes.BlocksConfigs: { - set((state) => ({ - ...state, - blocks: action.payload.blocks, - subBlocks: action.payload.subBlocks, - global: action.payload.global, - })); - break; - } - } - }, - }), -); diff --git a/src/editor-v2/context/editorContext/editorContext.tsx b/src/editor-v2/context/editorContext/editorContext.tsx deleted file mode 100644 index 23a9cce22..000000000 --- a/src/editor-v2/context/editorContext/editorContext.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Context for managing internal Editor states - * Same exist in PC - **/ - -import React from 'react'; - -import {StoreApi} from 'zustand'; - -import {EditorStore} from './store'; - -export interface EditorContextProps { - state?: StoreApi<EditorStore>; -} - -export const EditorContext = React.createContext<EditorContextProps>({}); diff --git a/src/editor-v2/context/editorContext/editorProvider.tsx b/src/editor-v2/context/editorContext/editorProvider.tsx deleted file mode 100644 index 64dcb447b..000000000 --- a/src/editor-v2/context/editorContext/editorProvider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, {PropsWithChildren, useRef} from 'react'; - -import {StoreApi} from 'zustand'; - -import {EditorContext} from './editorContext'; -import {EditorStore, createEditorStore} from './store'; - -export const EditorProvider = ({children}: PropsWithChildren) => { - const storeRef = useRef<StoreApi<EditorStore>>(); - - if (!storeRef.current) { - storeRef.current = createEditorStore(); - } - - return ( - <EditorContext.Provider value={{state: storeRef.current}}> - {children} - </EditorContext.Provider> - ); -}; diff --git a/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx b/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx deleted file mode 100644 index 4f2a346cd..000000000 --- a/src/editor-v2/context/editorContext/hooks/useEditorStore.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {EditorContext} from '../editorContext'; - -export const useEditorStore = () => { - const {state} = useContext(EditorContext); - - if (!state) { - throw new Error('Missing EditorContext'); - } - - return useStore(state); -}; - -export default useEditorStore; diff --git a/src/editor-v2/context/editorContext/index.ts b/src/editor-v2/context/editorContext/index.ts deleted file mode 100644 index c7b944eea..000000000 --- a/src/editor-v2/context/editorContext/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './editorContext'; -export * from './editorProvider'; -export * from './hooks/useEditorStore'; -export * from './store'; diff --git a/src/editor-v2/context/editorContext/store.ts b/src/editor-v2/context/editorContext/store.ts deleted file mode 100644 index 9c6085f75..000000000 --- a/src/editor-v2/context/editorContext/store.ts +++ /dev/null @@ -1,101 +0,0 @@ -import {Action, ActionTypes, WithStoreReducer} from '../../../common/types'; -import {initializeStore} from '../../utils/store'; - -export interface EditorState { - manipulateOverlayMode: 'insert' | 'reorder' | false; - selectedBlock?: { - path: number[]; - rect: DOMRect; - }; - initialized: boolean; -} - -export interface EditorMethods extends WithStoreReducer { - resetInitialize: () => void; -} - -export type EditorStore = EditorState & EditorMethods; - -export const createEditorStore = initializeStore<EditorState, EditorMethods>( - { - manipulateOverlayMode: false, - initialized: false, - }, - (set, get) => ({ - resetInitialize: () => { - set((state) => ({ - ...state, - initialized: false, - })); - }, - reducer: (action: Action) => { - switch (action.type) { - case ActionTypes.IframeReady: { - set((state) => ({ - ...state, - initialized: true, - })); - break; - } - case ActionTypes.ReorderModeEnable: { - set((state) => ({ - ...state, - manipulateOverlayMode: 'reorder', - })); - break; - } - case ActionTypes.ReorderModeDisable: { - set((state) => ({ - ...state, - manipulateOverlayMode: false, - })); - break; - } - case ActionTypes.InsertModeEnable: { - set((state) => ({ - ...state, - manipulateOverlayMode: 'insert', - })); - break; - } - case ActionTypes.InsertModeDisable: { - set((state) => ({ - ...state, - manipulateOverlayMode: false, - })); - break; - } - case ActionTypes.SelectBlock: { - set((state) => ({ - ...state, - selectedBlock: { - path: action.payload.path, - rect: action.payload.rect, - }, - })); - break; - } - case ActionTypes.UpdateSelectedBlockRect: { - const {selectedBlock} = get(); - if (selectedBlock) { - set((state) => ({ - ...state, - selectedBlock: { - ...selectedBlock, - rect: action.payload.rect, - }, - })); - } - break; - } - case ActionTypes.ReorderBlocks: { - set((state) => ({ - ...state, - selectedBlock: undefined, - })); - break; - } - } - }, - }), -); diff --git a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx new file mode 100644 index 000000000..b14610b74 --- /dev/null +++ b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorStore, createEditorStore} from '../../../common/store'; + +export interface MainEditorStoreContextProps { + state: StoreApi<EditorStore>; +} + +export const MainEditorStoreContext = React.createContext<MainEditorStoreContextProps>({ + state: createEditorStore(), +}); diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx new file mode 100644 index 000000000..68b8e9908 --- /dev/null +++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -0,0 +1,53 @@ +import React, {PropsWithChildren, useCallback, useContext, useEffect, useRef} from 'react'; + +import {StoreApi} from 'zustand'; + +import {EditorState, EditorStore, createEditorStore} from '../../../common/store'; +import {StoreSyncMessage} from '../../../common/types'; +import {removeFn} from '../../../utils/store'; +import {IframeContext} from '../iframeContext'; + +import {MainEditorStoreContext} from './MainEditorStoreContext'; + +interface MainEditorProviderProps extends PropsWithChildren {} + +export const MainEditorStoreProvider = ({children}: MainEditorProviderProps) => { + const {iframeElement} = useContext(IframeContext); + const storeRef = useRef<StoreApi<EditorStore>>(); + + const sendPostMessage = useCallback( + (data: EditorState) => { + const message: StoreSyncMessage = { + state: data, + }; + + if (iframeElement && iframeElement.contentWindow) { + iframeElement.contentWindow.postMessage(message, '*'); + } else { + // eslint-disable-next-line no-console + console.error('No Iframe element in Editor'); + } + }, + [iframeElement], + ); + + if (!storeRef.current) { + storeRef.current = createEditorStore(); + } + + useEffect(() => { + storeRef.current?.subscribe((state) => { + sendPostMessage(removeFn(state)); + }); + }, [sendPostMessage]); + + return ( + <MainEditorStoreContext.Provider + value={{ + state: storeRef.current, + }} + > + {children} + </MainEditorStoreContext.Provider> + ); +}; diff --git a/src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts b/src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts new file mode 100644 index 000000000..7cd400005 --- /dev/null +++ b/src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts @@ -0,0 +1,10 @@ +import {useContext} from 'react'; + +import {useStore} from 'zustand'; + +import {MainEditorStoreContext} from '../MainEditorStoreContext'; + +export const useMainEditorStore = () => { + const {state} = useContext(MainEditorStoreContext); + return useStore(state); +}; diff --git a/src/editor-v2/context/editorStore/index.ts b/src/editor-v2/context/editorStore/index.ts new file mode 100644 index 000000000..ced0a0fcd --- /dev/null +++ b/src/editor-v2/context/editorStore/index.ts @@ -0,0 +1,3 @@ +export * from './hooks/useMainEditorStore'; +export * from './MainEditorStoreContext'; +export * from './MainEditorStoreProvider'; diff --git a/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx b/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx deleted file mode 100644 index 87369816c..000000000 --- a/src/editor-v2/context/iframeContext/hooks/useIframeStore.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {IframeContext} from '../iframeContext'; - -export const useIframeStore = () => { - const {state} = useContext(IframeContext); - if (!state) { - throw new Error('Missing IframeContext'); - } - return useStore(state); -}; - -export default useIframeStore; diff --git a/src/editor-v2/context/iframeContext/iframeContext.tsx b/src/editor-v2/context/iframeContext/iframeContext.tsx index 5404b61a7..95c498bef 100644 --- a/src/editor-v2/context/iframeContext/iframeContext.tsx +++ b/src/editor-v2/context/iframeContext/iframeContext.tsx @@ -4,15 +4,16 @@ import React from 'react'; -import {StoreApi} from 'zustand'; - -import {IframeStore} from './store'; - export interface IframeContextProps { - state?: StoreApi<IframeStore>; iframeElement?: HTMLIFrameElement; setIframeElement: (element: HTMLIFrameElement) => void; + url: string; + setUrl: (url: string) => void; disableUrlField?: boolean; } -export const IframeContext = React.createContext<IframeContextProps>({setIframeElement: () => {}}); +export const IframeContext = React.createContext<IframeContextProps>({ + setIframeElement: () => {}, + setUrl: () => {}, + url: '', +}); diff --git a/src/editor-v2/context/iframeContext/iframeProvider.tsx b/src/editor-v2/context/iframeContext/iframeProvider.tsx index a8b7f56fb..f6261ac55 100644 --- a/src/editor-v2/context/iframeContext/iframeProvider.tsx +++ b/src/editor-v2/context/iframeContext/iframeProvider.tsx @@ -1,9 +1,6 @@ -import React, {PropsWithChildren, useRef, useState} from 'react'; - -import {StoreApi} from 'zustand'; +import React, {PropsWithChildren, useState} from 'react'; import {IframeContext} from './iframeContext'; -import {IframeStore, createIframeStore} from './store'; interface IframeProviderProps extends PropsWithChildren { initialUrl?: string; @@ -11,20 +8,17 @@ interface IframeProviderProps extends PropsWithChildren { } export const IframeProvider = (props: IframeProviderProps) => { - const {children, initialUrl, disableUrlField} = props; - const storeRef = useRef<StoreApi<IframeStore>>(); + const {children, initialUrl = '', disableUrlField} = props; const [iframeElement, setIframeElement] = useState<HTMLIFrameElement>(); + const [url, setUrl] = useState(initialUrl); const setIframeElementFunc = (element: HTMLIFrameElement) => setIframeElement(element); - if (!storeRef.current) { - storeRef.current = createIframeStore({url: initialUrl}); - } - return ( <IframeContext.Provider value={{ - state: storeRef.current, + url, + setUrl, iframeElement, setIframeElement: setIframeElementFunc, disableUrlField, diff --git a/src/editor-v2/context/iframeContext/index.ts b/src/editor-v2/context/iframeContext/index.ts index 7d73463ac..7da30b4e2 100644 --- a/src/editor-v2/context/iframeContext/index.ts +++ b/src/editor-v2/context/iframeContext/index.ts @@ -1,4 +1,2 @@ export * from './iframeContext'; export * from './iframeProvider'; -export * from './hooks/useIframeStore'; -export * from './store'; diff --git a/src/editor-v2/context/iframeContext/store.ts b/src/editor-v2/context/iframeContext/store.ts deleted file mode 100644 index c5902b902..000000000 --- a/src/editor-v2/context/iframeContext/store.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {ActionTypes, WithStoreReducer} from '../../../common/types'; -import {ZOOM_STEPS} from '../../constants'; -import {initializeStore} from '../../utils/store'; - -export interface IframeState { - url: string; - height?: number; - // In percents - zoom: number; -} - -export interface IframeMethods extends WithStoreReducer { - setUrl: (url: string) => void; - setZoom: (zoom: number) => void; - increaseZoom: () => void; - decreaseZoom: () => void; -} - -export type IframeStore = IframeState & IframeMethods; - -export const createIframeStore = initializeStore<IframeState, IframeMethods>( - { - url: '', - height: 400, - zoom: 100, - }, - (set, get) => ({ - setZoom: function (zoom) { - if (zoom > 0) { - set((state) => ({...state, zoom})); - } - }, - increaseZoom: function () { - const currentZoom = get().zoom; - - for (const step of ZOOM_STEPS) { - if (currentZoom < step) { - get().setZoom(step); - break; - } - } - }, - decreaseZoom: function () { - const currentZoom = get().zoom; - const reverseSteps = ZOOM_STEPS.slice().reverse(); - - for (const step of reverseSteps) { - if (currentZoom > step) { - get().setZoom(step); - break; - } - } - }, - - setUrl: (url) => set((state) => ({...state, url})), - reducer: (action) => { - switch (action.type) { - case ActionTypes.SetHeight: { - set((state) => ({ - ...state, - height: action.payload.height + 200, - })); - break; - } - } - }, - }), -); diff --git a/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx b/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx deleted file mode 100644 index 7414cad43..000000000 --- a/src/editor-v2/context/messagesContext/hooks/useMessageObserver.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import {DependencyList, useEffect} from 'react'; - -import {Action, Meta} from '../../../../common/types'; - -import useMessagesStore from './useMessagesStore'; - -export const useMessageObserver = <A extends Action>( - type: A['type'], - callback: (payload: A['payload'], meta: Meta) => void, - deps: DependencyList = [], -) => { - const {subscribe, unsubscribe} = useMessagesStore(); - - useEffect(() => { - subscribe(type, callback); - - return () => { - unsubscribe(type, callback); - }; - }, deps); // eslint-disable-line react-hooks/exhaustive-deps -}; - -export default useMessageObserver; diff --git a/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx b/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx deleted file mode 100644 index 57f25eef0..000000000 --- a/src/editor-v2/context/messagesContext/hooks/useMessageSender.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import {useContext} from 'react'; - -import {PostMessageContext} from '../messagesContext'; - -export const useMessageSender = () => { - const {sendMessage} = useContext(PostMessageContext); - return sendMessage; -}; - -export default useMessageSender; diff --git a/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx b/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx deleted file mode 100644 index 1805cd32a..000000000 --- a/src/editor-v2/context/messagesContext/hooks/useMessagesStore.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import {useContext} from 'react'; - -import {useStore} from 'zustand'; - -import {PostMessageContext} from '../messagesContext'; - -export const useMessagesStore = () => { - const {state} = useContext(PostMessageContext); - - if (!state) { - throw new Error('Missing PostMessageContext'); - } - - return useStore(state); -}; - -export default useMessagesStore; diff --git a/src/editor-v2/context/messagesContext/index.ts b/src/editor-v2/context/messagesContext/index.ts deleted file mode 100644 index eda561f12..000000000 --- a/src/editor-v2/context/messagesContext/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './messagesContext'; -export * from './messagesProvider'; -export * from './hooks/useMessageSender'; -export * from './hooks/useMessageObserver'; -export * from './hooks/useMessagesStore'; diff --git a/src/editor-v2/context/messagesContext/messagesContext.tsx b/src/editor-v2/context/messagesContext/messagesContext.tsx deleted file mode 100644 index ba01f3552..000000000 --- a/src/editor-v2/context/messagesContext/messagesContext.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Context for sending messages between Editor and App - * Same exist in PC - **/ - -import React from 'react'; - -import {StoreApi} from 'zustand'; - -import {Action, SendOptions} from '../../../common/types'; - -import {MessagesStore} from './store'; - -export interface PostMessageContextProps { - state?: StoreApi<MessagesStore>; - sendMessage: (action: Action, options?: SendOptions) => void; -} - -export const PostMessageContext = React.createContext<PostMessageContextProps>({ - sendMessage: () => {}, -}); diff --git a/src/editor-v2/context/messagesContext/messagesProvider.tsx b/src/editor-v2/context/messagesContext/messagesProvider.tsx deleted file mode 100644 index 3fe7d6066..000000000 --- a/src/editor-v2/context/messagesContext/messagesProvider.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, {PropsWithChildren, useContext, useRef} from 'react'; - -import {StoreApi} from 'zustand'; - -import {usePostMessage} from '../../../common/hooks/usePostMessage'; -import {WithStoreReducer} from '../../../common/types'; -import {getUrlOrigin} from '../../utils'; -import {useContentConfigStore} from '../contentConfig/hooks/useContentConfigStore'; -import {useEditorStore} from '../editorContext/hooks/useEditorStore'; -import {IframeContext, useIframeStore} from '../iframeContext'; - -import {PostMessageContext} from './messagesContext'; -import {MessagesStore, createMessagesStore} from './store'; - -export const PostMessageProvider = ({children}: PropsWithChildren) => { - const storeRef = useRef<StoreApi<MessagesStore>>(); - const {iframeElement} = useContext(IframeContext); - - const contentConfigStore = useContentConfigStore(); - const editorStore = useEditorStore(); - const iframeStore = useIframeStore(); - - if (!storeRef.current) { - storeRef.current = createMessagesStore(); - } - - const storesWithReducer: WithStoreReducer[] = [contentConfigStore, editorStore, iframeStore]; - - const {sendMessage} = usePostMessage({ - subscribers: storeRef.current?.getState().subscribers, - storesWithReducer, - targetIframeElement: iframeElement, - urlOrigin: getUrlOrigin(iframeStore.url), - }); - - return ( - <PostMessageContext.Provider - value={{ - state: storeRef.current, - sendMessage: sendMessage, - }} - > - {children} - </PostMessageContext.Provider> - ); -}; diff --git a/src/editor-v2/context/messagesContext/store.ts b/src/editor-v2/context/messagesContext/store.ts deleted file mode 100644 index 3c76dfe9d..000000000 --- a/src/editor-v2/context/messagesContext/store.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Subscriber, SubscriptionFunc} from '../../../common/types'; -import {initializeStore} from '../../utils/store'; - -export interface MessagesState { - subscribers: Subscriber[]; -} - -export interface MessagesMethods { - subscribe: SubscriptionFunc; - unsubscribe: SubscriptionFunc; -} - -export type MessagesStore = MessagesState & MessagesMethods; - -export const createMessagesStore = initializeStore<MessagesState, MessagesMethods>( - { - subscribers: [], - }, - (set, _get) => ({ - subscribe: (type, payloadCallback) => { - set((state) => ({ - ...state, - subscribers: [...state.subscribers, {action: type, handler: payloadCallback}], - })); - - return () => { - set((state) => ({ - ...state, - subscribers: state.subscribers.filter( - ({handler, action}) => payloadCallback !== handler || type !== action, - ), - })); - }; - }, - unsubscribe: (type, payloadCallback) => { - set((state) => ({ - ...state, - subscribers: state.subscribers.filter( - ({handler, action}) => payloadCallback !== handler || type !== action, - ), - })); - }, - }), -); diff --git a/src/editor-v2/hooks/usePostMessageEvents.ts b/src/editor-v2/hooks/usePostMessageEvents.ts new file mode 100644 index 000000000..3bb5473ff --- /dev/null +++ b/src/editor-v2/hooks/usePostMessageEvents.ts @@ -0,0 +1,26 @@ +import {useContext} from 'react'; + +import {requestActionPostMessage} from '../../common/postMessage'; +import {ActionMessageTypes} from '../../common/types/actions'; +import {IframeContext} from '../context/iframeContext'; + +interface UsePostMessageRequestReturn { + requestPostMessage: <K extends keyof ActionMessageTypes>( + action: K, + data: ActionMessageTypes[K], + ) => void; +} + +export function usePostMessageEvents(): UsePostMessageRequestReturn { + const {iframeElement} = useContext(IframeContext); + + return { + requestPostMessage: (action, data) => { + if (iframeElement && iframeElement.contentWindow) { + return requestActionPostMessage(action, data, iframeElement.contentWindow); + } + + return undefined; + }, + }; +} diff --git a/src/hooks/useEditorInitialize.ts b/src/hooks/useEditorInitialize.ts index 4f3cede1b..8d721821c 100644 --- a/src/hooks/useEditorInitialize.ts +++ b/src/hooks/useEditorInitialize.ts @@ -1,106 +1,71 @@ -import {useCallback, useContext, useEffect} from 'react'; +import {useCallback, useEffect} from 'react'; import {JSONSchemaType} from 'ajv'; import _ from 'lodash'; -import {ActionTypes, ItemConfig, UpdateConfigsAction} from '../common/types'; +import {ItemConfig} from '../common/types'; import {blockDataMap} from '../constructor-items'; -import {EditorContext, useEditorStore} from '../context/editorContext'; -import {useMessageObserver, useMessageSender} from '../context/messagesContext'; +import {usePCEditorStore} from '../context/editorStoreContext'; import {PageContent} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; import {generateFromAJV} from '../utils/form-generator'; +import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; + interface UseEditorInitializeProps { - content: PageContent; + initialContent: PageContent; setContent: (content: PageContent) => void; } -const useEditorInitialize = ({setContent, content}: UseEditorInitializeProps) => { - const {manipulateOverlayMode, isSelectActive} = useEditorStore(); - const sendMessage = useMessageSender(); - const {activeElement} = useContext(EditorContext); +export const useInitializeEditorEvents = ({ + initialContent, + setContent, +}: UseEditorInitializeProps) => { + const {manipulateOverlayMode, disableMode, initialized, content} = usePCEditorStore(); - const onResize = useCallback(() => { - const height = document.documentElement.getBoundingClientRect().height; - - sendMessage({ - type: ActionTypes.SetHeight, - payload: {height}, - }); - - if (isSelectActive && activeElement) { - const rect = activeElement.getClientRects().item(0); - - if (rect) { - sendMessage({ - type: ActionTypes.UpdateSelectedBlockRect, - payload: { - rect: rect, - }, - }); - } + useEffect(() => { + if (initialized) { + setContent(content); } - }, [activeElement, isSelectActive, sendMessage]); + }, [content, initialized, setContent]); - useMessageObserver<UpdateConfigsAction>(ActionTypes.UpdateConfigs, ({content: newContent}) => { - setContent(newContent); + useInternalPostMessageAPIListener('GET_CURRENT_CONTENT', () => { + sendEventPostMessage('ON_INITIAL_CONTENT', initialContent); }); - useEffect(() => { - onResize(); - }, [content, onResize]); + useInternalPostMessageAPIListener('GET_SUPPORTED_BLOCKS', () => { + sendEventPostMessage('ON_SUPPORTED_BLOCKS', { + blocks: Object.entries(blockDataMap).reduce((acc, [key, value]) => { + acc.push({type: key, schema: value.schema}); + return acc; + }, [] as ItemConfig[]), + subBlocks: [], + global: generateFromAJV( + defaultComponentsConfigurationSchema as unknown as JSONSchemaType<{}>, + ), + }); + }); - // Fires only once - useEffect(() => { + const onResize = useCallback(() => { const height = document.documentElement.getBoundingClientRect().height; + sendEventPostMessage('ON_RESIZE', {height}); + }, []); - sendMessage( - { - type: ActionTypes.IframeReady, - payload: {height}, - }, - {direction: 'editor'}, - ); - - sendMessage({ - type: ActionTypes.BlocksConfigs, - payload: { - blocks: Object.entries(blockDataMap).reduce((acc, [key, value]) => { - acc.push({type: key, schema: value.schema}); - return acc; - }, [] as ItemConfig[]), - subBlocks: [], - global: generateFromAJV( - defaultComponentsConfigurationSchema as unknown as JSONSchemaType<{}>, - ), - }, - }); - }, [sendMessage]); + new ResizeObserver(onResize).observe(document.body); useEffect(() => { - const onMouseUp = (e: MouseEvent) => { - if (manipulateOverlayMode === 'insert') { - e.preventDefault(); - sendMessage({type: ActionTypes.InsertModeDisable, payload: undefined}); - } - if (manipulateOverlayMode === 'reorder') { - e.preventDefault(); - sendMessage({type: ActionTypes.ReorderModeDisable, payload: undefined}); - } + const onMouseUp = () => { + sendEventPostMessage('ON_MOUSE_UP', {}); }; const onMouseMove = (event: MouseEvent) => { - if (manipulateOverlayMode) { - sendMessage({ - type: ActionTypes.OverlayModeOnMove, - payload: {cursor: {x: event.clientX, y: event.clientY}}, - }); - } + event.preventDefault(); + event.stopPropagation(); + sendEventPostMessage('ON_MOUSE_MOVE', {x: event.clientX, y: event.clientY}); }; - const throttleOnMouseMove = _.throttle(onMouseMove, 1); - const throttleOnMouseUp = _.throttle(onMouseUp, 1); + const throttleOnMouseMove = _.throttle(onMouseMove, 10); + const throttleOnMouseUp = _.throttle(onMouseUp, 10); document.addEventListener('mousemove', throttleOnMouseMove); document.addEventListener('mouseup', throttleOnMouseUp); @@ -111,7 +76,10 @@ const useEditorInitialize = ({setContent, content}: UseEditorInitializeProps) => document.removeEventListener('mouseup', throttleOnMouseUp); window.removeEventListener('resize', onResize); }; - }, [activeElement, manipulateOverlayMode, onResize, sendMessage]); -}; + }, [disableMode, manipulateOverlayMode, onResize]); -export default useEditorInitialize; + useEffect(() => { + const height = document.documentElement.getBoundingClientRect().height; + sendEventPostMessage('ON_INIT', {height}); + }, []); +}; diff --git a/src/hooks/usePostMessageAPI.ts b/src/hooks/usePostMessageAPI.ts new file mode 100644 index 000000000..6cf8c4cda --- /dev/null +++ b/src/hooks/usePostMessageAPI.ts @@ -0,0 +1,42 @@ +import {useEffect} from 'react'; + +import {PostMessageAPIMessage} from '../common/types'; +import {ActionMessageTypes, EventMessageTypes} from '../common/types/actions'; + +export function sendEventPostMessage<K extends keyof EventMessageTypes>( + action: K, + data: EventMessageTypes[K], +) { + const message = {action, data} as PostMessageAPIMessage<K>; + window.parent.postMessage(message); +} + +export function listenPostMessageActions<K extends keyof ActionMessageTypes>( + action: K, + callback: (data: ActionMessageTypes[K]) => void, +) { + const onMessage = (e: MessageEvent) => { + const message = e.data as PostMessageAPIMessage<K>; + + if ('action' in message && message.action === action) { + return callback(message.data); + } + + return undefined; + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; +} + +export function useInternalPostMessageAPIListener<K extends keyof ActionMessageTypes>( + action: K, + callback: (data: ActionMessageTypes[K]) => void, +) { + useEffect(() => { + return listenPostMessageActions(action, callback); + }, [action, callback]); +} diff --git a/src/index.ts b/src/index.ts index 10becb250..05106270f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,5 +12,6 @@ export * from './utils'; export * from './schema'; export * from './hooks'; export * from './navigation'; +export * from './common/postMessage'; export {BREAKPOINTS} from './constants'; diff --git a/src/utils/store.ts b/src/utils/store.ts index 9bf940a34..a50614c5d 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,22 +1,35 @@ import {StoreApi, create} from 'zustand'; -import {devtools} from 'zustand/middleware'; +import {devtools, subscribeWithSelector} from 'zustand/middleware'; export function initializeStore<State, Methods>( initialState: State, methods: ( set: StoreApi<State & Methods>['setState'], get: StoreApi<State & Methods>['getState'], + onStateUpdated?: () => void, ) => Methods, ) { - return (overrideInitialState?: Partial<State>) => + return (overrideInitialState?: Partial<State>, onStateUpdated?: () => void) => create< State & Methods, - [['zustand/devtools', never], ['zustand/persist', State & Methods]] + [ + ['zustand/subscribeWithSelector', State & Methods], + ['zustand/devtools', never], + ['zustand/persist', State & Methods], + ] >( - devtools((set, get) => ({ - ...initialState, - ...overrideInitialState, - ...methods(set, get), - })), + subscribeWithSelector( + devtools((set, get) => { + return { + ...initialState, + ...overrideInitialState, + ...methods(set, get, onStateUpdated), + }; + }), + ), ); } + +export const removeFn = (object: object) => { + return JSON.parse(JSON.stringify(object)); +}; From 23e81781ab8fce2887d038a01c1d932eed6b0bb7 Mon Sep 17 00:00:00 2001 From: DaffPunks <sawavas2@gmail.com> Date: Mon, 3 Feb 2025 17:26:54 +0300 Subject: [PATCH 06/84] feat: add PostMessage API and Synchronized store --- playground/package-lock.json | 680 ++++++++++++++---- playground/package.json | 10 +- src/blocks/CardLayout/CardLayout.tsx | 5 +- .../ExtendedFeatures/ExtendedFeatures.tsx | 97 +-- src/blocks/Icons/Icons.tsx | 2 +- .../PromoFeaturesBlock/PromoFeaturesBlock.tsx | 2 +- .../editor/ChildrenWrap/ChildrenWrap.tsx | 1 + src/constructor-items.ts | 2 +- .../DynamicForm/FieldBase/FieldBase.tsx | 1 + .../components/MiddleScreen/MiddleScreen.tsx | 1 + src/editor-v2/components/Overlay/Overlay.tsx | 1 + src/editor-v2/containers/Editor/Editor.tsx | 1 + .../editorStore/MainEditorStoreProvider.tsx | 3 - .../components/CodeEditor/CodeEditor.tsx | 106 ++- src/editor/data/index.ts | 45 +- src/sub-blocks/LayoutItem/schema.ts | 3 +- 16 files changed, 682 insertions(+), 278 deletions(-) diff --git a/playground/package-lock.json b/playground/package-lock.json index afdffac32..4352df272 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1,34 +1,385 @@ { - "name": "page-constructor-example", + "name": "page-constructor-playground", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "page-constructor-example", + "name": "page-constructor-playground", "version": "1.0.0", "dependencies": { "bem-cn-lite": "^4.1.0", - "next": "14.2.3", - "react": "^18", - "react-dom": "^18" + "next": "15.1.2", + "react": "^19", + "react-dom": "^19" }, "devDependencies": { "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", + "@types/react": "^19", + "@types/react-dom": "^19", "typescript": "^5" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@next/env": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.2.tgz", + "integrity": "sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==" }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.2.tgz", + "integrity": "sha512-b9TN7q+j5/7+rGLhFAVZiKJGIASuo8tWvInGfAd8wsULjB1uNGRCj1z1WZwwPWzVQbIKWFYqc+9L7W09qwt52w==", "cpu": [ "arm64" ], @@ -41,9 +392,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.2.tgz", + "integrity": "sha512-caR62jNDUCU+qobStO6YJ05p9E+LR0EoXh1EEmyU69cYydsAy7drMcOlUlRtQihM6K6QfvNwJuLhsHcCzNpqtA==", "cpu": [ "x64" ], @@ -56,9 +407,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.2.tgz", + "integrity": "sha512-fHHXBusURjBmN6VBUtu6/5s7cCeEkuGAb/ZZiGHBLVBXMBy4D5QpM8P33Or8JD1nlOjm/ZT9sEE5HouQ0F+hUA==", "cpu": [ "arm64" ], @@ -71,9 +422,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.2.tgz", + "integrity": "sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==", "cpu": [ "arm64" ], @@ -86,9 +437,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.2.tgz", + "integrity": "sha512-tINV7WmcTUf4oM/eN3Yuu/f8jQ5C6AkueZPKeALs/qfdfX57eNv4Ij7rt0SA6iZ8+fMobVfcFVv664Op0caCCg==", "cpu": [ "x64" ], @@ -101,9 +452,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.2.tgz", + "integrity": "sha512-jf2IseC4WRsGkzeUw/cK3wci9pxR53GlLAt30+y+B+2qAQxMw6WAC3QrANIKxkcoPU3JFh/10uFfmoMDF9JXKg==", "cpu": [ "x64" ], @@ -116,9 +467,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.2.tgz", + "integrity": "sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==", "cpu": [ "arm64" ], @@ -130,25 +481,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.2.tgz", + "integrity": "sha512-D3cNA8NoT3aWISWmo7HF5Eyko/0OdOO+VagkoJuiTk7pyX3P/b+n8XA/MYvyR+xSVcbKn68B1rY9fgqjNISqzQ==", "cpu": [ "x64" ], @@ -166,12 +502,11 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@types/node": { @@ -183,29 +518,22 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true - }, "node_modules/@types/react": { - "version": "18.3.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", - "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "version": "19.0.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", + "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", "dev": true, "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", + "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", "dev": true, - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/bem-cn": { @@ -256,33 +584,68 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "engines": { + "node": ">=8" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -301,40 +664,41 @@ } }, "node_modules/next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.2.tgz", + "integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==", "dependencies": { - "@next/env": "14.2.3", - "@swc/helpers": "0.5.5", + "@next/env": "15.1.2", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=18.17.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.3", - "@next/swc-darwin-x64": "14.2.3", - "@next/swc-linux-arm64-gnu": "14.2.3", - "@next/swc-linux-arm64-musl": "14.2.3", - "@next/swc-linux-x64-gnu": "14.2.3", - "@next/swc-linux-x64-musl": "14.2.3", - "@next/swc-win32-arm64-msvc": "14.2.3", - "@next/swc-win32-ia32-msvc": "14.2.3", - "@next/swc-win32-x64-msvc": "14.2.3" + "@next/swc-darwin-arm64": "15.1.2", + "@next/swc-darwin-x64": "15.1.2", + "@next/swc-linux-arm64-gnu": "15.1.2", + "@next/swc-linux-arm64-musl": "15.1.2", + "@next/swc-linux-x64-gnu": "15.1.2", + "@next/swc-linux-x64-musl": "15.1.2", + "@next/swc-win32-arm64-msvc": "15.1.2", + "@next/swc-win32-x64-msvc": "15.1.2", + "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -344,6 +708,9 @@ "@playwright/test": { "optional": true }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } @@ -382,34 +749,87 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + }, + "node_modules/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "optional": true, "dependencies": { - "loose-envify": "^1.1.0" + "is-arrayish": "^0.3.1" } }, "node_modules/source-map-js": { @@ -429,9 +849,9 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "dependencies": { "client-only": "0.0.1" }, @@ -439,7 +859,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -451,9 +871,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/typescript": { "version": "5.4.5", diff --git a/playground/package.json b/playground/package.json index 2ce85b873..76783faa5 100644 --- a/playground/package.json +++ b/playground/package.json @@ -10,14 +10,14 @@ }, "dependencies": { "bem-cn-lite": "^4.1.0", - "next": "14.2.3", - "react": "^18", - "react-dom": "^18" + "next": "15.1.2", + "react": "^19", + "react-dom": "^19" }, "devDependencies": { "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", + "@types/react": "^19", + "@types/react-dom": "^19", "typescript": "^5" } } diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx index 135ebb16c..e523278de 100644 --- a/src/blocks/CardLayout/CardLayout.tsx +++ b/src/blocks/CardLayout/CardLayout.tsx @@ -6,10 +6,7 @@ import ChildrenWrap from '../../components/editor/ChildrenWrap/ChildrenWrap'; import ItemWrap from '../../components/editor/ItemWrap/ItemWrap'; import {useTheme} from '../../context/theme'; import {Col, Grid, GridColumnSizesType, Row} from '../../grid'; -import { - CardLayoutBlockProps as CardLayoutBlockParams, - ClassNameProps, -} from '../../models'; +import {CardLayoutBlockProps as CardLayoutBlockParams, ClassNameProps} from '../../models'; import {block, getThemedValue} from '../../utils'; import './CardLayout.scss'; diff --git a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx index 35c5569af..094d8b2de 100644 --- a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx +++ b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx @@ -37,17 +37,20 @@ export const ExtendedFeaturesBlock = ({ <Row> {items && items.map( - ({ - title: itemTitle, - text, - list, - link, - links, - label, - icon, - buttons, - additionalInfo, - }) => { + ( + { + title: itemTitle, + text, + list, + link, + links, + label, + icon, + buttons, + additionalInfo, + }, + index, + ) => { const itemLinks = links || []; const iconThemed = icon && getThemedValue(icon, theme); @@ -57,43 +60,43 @@ export const ExtendedFeaturesBlock = ({ itemLinks.push(link); } - return ( - <Col className={b('item')} key={text || itemTitle} sizes={colSizes}> - {iconData && ( - <div className={b('icon-wrap')} aria-hidden> - <Image {...iconData} className={b('icon')} /> + return ( + <Col className={b('item')} key={index} sizes={colSizes}> + {iconData && ( + <div className={b('icon-wrap')} aria-hidden> + <Image {...iconData} className={b('icon')} /> + </div> + )} + <div className={b('container')}> + {itemTitle && + React.createElement( + itemTitleHeadingTag, + { + className: b('item-title'), + }, + <React.Fragment> + <HTML>{itemTitle}</HTML> + {label && ( + <span className={b('item-label')}> + {label} + </span> + )} + </React.Fragment>, + )} + <Content + text={text} + links={itemLinks} + size="s" + list={list} + colSizes={{all: 12, md: 12}} + buttons={buttons} + additionalInfo={additionalInfo} + /> </div> - )} - <div className={b('container')}> - {itemTitle && - React.createElement( - itemTitleHeadingTag, - { - className: b('item-title'), - }, - <React.Fragment> - <HTML>{itemTitle}</HTML> - {label && ( - <span className={b('item-label')}> - {label} - </span> - )} - </React.Fragment>, - )} - <Content - text={text} - links={itemLinks} - size="s" - list={list} - colSizes={{all: 12, md: 12}} - buttons={buttons} - additionalInfo={additionalInfo} - /> - </div> - </Col> - ); - }, - )} + </Col> + ); + }, + )} </Row> </div> </Grid> diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx index c7338587e..8d966d4aa 100644 --- a/src/blocks/Icons/Icons.tsx +++ b/src/blocks/Icons/Icons.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import {Image, Title} from '../../components'; import {LocationContext} from '../../context/locationContext'; -import {Grid} from '../../grid'; import {useTheme} from '../../context/theme'; +import {Grid} from '../../grid'; import {useAnalytics} from '../../hooks'; import {IconsBlockItemProps, IconsBlockProps} from '../../models'; import {block, getLinkProps, getThemedValue} from '../../utils'; diff --git a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx index ac77be32f..227a9d331 100644 --- a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx +++ b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx @@ -5,8 +5,8 @@ import Media from '../../components/Media/Media'; import Title from '../../components/Title/Title'; import YFMWrapper from '../../components/YFMWrapper/YFMWrapper'; import {BREAKPOINTS} from '../../constants'; -import {Grid} from '../../grid'; import {useTheme} from '../../context/theme'; +import {Grid} from '../../grid'; import {PromoFeaturesProps} from '../../models'; import {block, getThemedValue} from '../../utils'; import {mergeVideoMicrodata} from '../../utils/microdata'; diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx index 78287b18f..f6129ae0c 100644 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -24,6 +24,7 @@ const ChildrenWrap = (props: ChildrenWrapProps) => { const {onMouseUp, onMouseMove} = useEditorBlockMouseEvents([parentBlockId, 0], element); return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions <div ref={blockRef} className={b()} onMouseMove={onMouseMove} onMouseUp={onMouseUp}> {children} </div> diff --git a/src/constructor-items.ts b/src/constructor-items.ts index a37cd8c99..d2ce36c7c 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -43,8 +43,8 @@ import TableBlockConfig from './blocks/Table'; import TabsBlockConfig from './blocks/Tabs'; import TestEditorBlockConfig from './blocks/TestEditorBlock'; import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock'; -import {BlockConfig} from './common/types'; import {SliderNewBlock} from './blocks/unstable'; +import {BlockConfig} from './common/types'; import {BlockType, NavigationItemType, SubBlockType} from './models'; import { GithubButton, diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx index 8b5860ace..ce1bf2587 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -32,6 +32,7 @@ const FieldBase: React.FC<FieldBaseProps> = (props) => { if (expandable) { return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions <div className={b('foldable')} onClick={() => setShowChildren(!showChildren)}> <ArrowToggle direction={showChildren ? 'bottom' : 'right'} diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx index 6bd08ebda..e0a1bac09 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -62,6 +62,7 @@ const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { height={`${height}px`} width="100%" frameBorder="0" + title="Page Constructor Iframe" /> <Overlay className={b('overlay')} /> {!initialized && ( diff --git a/src/editor-v2/components/Overlay/Overlay.tsx b/src/editor-v2/components/Overlay/Overlay.tsx index 51722ef3a..f42078f90 100644 --- a/src/editor-v2/components/Overlay/Overlay.tsx +++ b/src/editor-v2/components/Overlay/Overlay.tsx @@ -79,6 +79,7 @@ const Overlay: React.FC<OverlayProps> = ({className}) => { }} > <div className={b('actions')}> + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} <div onMouseDown={onMouseDown}> <Button pin={'round-clear'} diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index e7fd9b9ac..85eb3c3bd 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -41,6 +41,7 @@ const EditorView = (_props: EditorViewProps) => { ); return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions <div className={b()} onMouseUp={onMouseUp}> <div className={b('body')}> <Panels diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx index 68b8e9908..c91e56a4c 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -23,9 +23,6 @@ export const MainEditorStoreProvider = ({children}: MainEditorProviderProps) => if (iframeElement && iframeElement.contentWindow) { iframeElement.contentWindow.postMessage(message, '*'); - } else { - // eslint-disable-next-line no-console - console.error('No Iframe element in Editor'); } }, [iframeElement], diff --git a/src/editor/components/CodeEditor/CodeEditor.tsx b/src/editor/components/CodeEditor/CodeEditor.tsx index de0978a5c..168bdce52 100644 --- a/src/editor/components/CodeEditor/CodeEditor.tsx +++ b/src/editor/components/CodeEditor/CodeEditor.tsx @@ -1,5 +1,4 @@ -import * as React from 'react'; - +import {useCallback, useContext, useState} from 'react'; import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import debounce from 'lodash/debounce'; @@ -17,8 +16,6 @@ import './CodeEditor.scss'; const b = block('code-editor'); -const ON_CHANGE_DEBOUNCE_TIMEOUT = 300; - interface CodeEditorProps { code: string; fullscreenModeOn: boolean; @@ -28,60 +25,57 @@ interface CodeEditorProps { message?: CodeEditorMessageProps; } -export const CodeEditor = React.memo( - ({onChange, validator, fullscreenModeOn, onFullscreenModeOnUpdate, code}: CodeEditorProps) => { - const [message, setMessage] = React.useState(() => validator(code)); - const {theme = Theme.Light} = React.useContext(EditorContext); +export const CodeEditor = ({ + onChange, + validator, + fullscreenModeOn, + onFullscreenModeOnUpdate, + code, +}: CodeEditorProps) => { + const [message, setMessage] = useState(() => validator(code)); + const {theme = Theme.Light} = useContext(EditorContext); - // eslint-disable-next-line react-hooks/exhaustive-deps - const onChangeWithValidation = React.useCallback( - debounce((newCode: string) => { - const validationResult = validator(newCode); + // eslint-disable-next-line react-hooks/exhaustive-deps + const onChangeWithValidation = useCallback( + debounce((newCode: string) => { + const validationResult = validator(newCode); - setMessage(validationResult); - onChange(parseCode(newCode)); - }, ON_CHANGE_DEBOUNCE_TIMEOUT), - [onChange, validator], - ); + setMessage(validationResult); + onChange(parseCode(newCode)); + }, 200), + [onChange, validator], + ); - return ( - <div className={b({fullscreen: fullscreenModeOn})}> - <div className={b('header')}> - <Button - view="flat-secondary" - onClick={() => onFullscreenModeOnUpdate(!fullscreenModeOn)} - > - <Icon - data={ - fullscreenModeOn ? ChevronsCollapseUpRight : ChevronsExpandUpRight - } - size={16} - /> - </Button> - </div> - <div className={b('code')}> - <MonacoEditor - key={String(fullscreenModeOn)} - defaultValue={code} - value={code} - language="yaml" - options={options} - onChange={onChangeWithValidation} - theme={theme === Theme.Dark ? 'vs-dark' : 'vs'} + return ( + <div className={b({fullscreen: fullscreenModeOn})}> + <div className={b('header')}> + <Button + view="flat-secondary" + onClick={() => onFullscreenModeOnUpdate(!fullscreenModeOn)} + > + <Icon + data={fullscreenModeOn ? ChevronsCollapseUpRight : ChevronsExpandUpRight} + size={16} /> - </div> - <div className={b('footer')}> - {message && ( - <div className={b('message-container')}> - <div className={b('message', {status: message.status})}> - {message.text} - </div> - </div> - )} - </div> + </Button> </div> - ); - }, -); - -CodeEditor.displayName = 'CodeEditor'; + <div className={b('code')}> + <MonacoEditor + key={String(fullscreenModeOn)} + value={code} + language="yaml" + options={options} + onChange={onChangeWithValidation} + theme={theme === Theme.Dark ? 'vs-dark' : 'vs'} + /> + </div> + <div className={b('footer')}> + {message && ( + <div className={b('message-container')}> + <div className={b('message', {status: message.status})}>{message.text}</div> + </div> + )} + </div> + </div> + ); +}; diff --git a/src/editor/data/index.ts b/src/editor/data/index.ts index ff14d8594..690274125 100644 --- a/src/editor/data/index.ts +++ b/src/editor/data/index.ts @@ -2,7 +2,6 @@ import {Block, BlockType} from '../../models'; import {formatBlockName} from '../utils'; import DefaultPreview from './previews/default-preview'; -import HeaderBlock from './previews/header-block'; export type PreviewComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>; @@ -15,16 +14,12 @@ export interface EdiorBlockData { }; } -const getBlockTemplate = (blockType: BlockType): Promise<Omit<EdiorBlockData, 'preview'>> => - import(`./templates/${blockType}.json`).then((data) => data.default); +const getBlockTemplate = (blockType: BlockType) => + require(`./templates/${blockType}.json`) as Omit<EdiorBlockData, 'preview'>; -const getBlockPreview = (blockType: BlockType): PreviewComponent => { +const getBlockPreview = (blockType: BlockType) => { try { - if (blockType === BlockType.HeaderBlock) { - return HeaderBlock; - } - - return DefaultPreview; + return require(`./previews/${blockType}.tsx`).default as PreviewComponent; } catch (err) { /*eslint-disable no-console */ console.warn(`Preview image for ${blockType} not found`); @@ -32,26 +27,20 @@ const getBlockPreview = (blockType: BlockType): PreviewComponent => { } }; -type EditorBlocksData = Partial<Record<BlockType, EdiorBlockData>>; - -async function getEditorBlocksData(): Promise<EditorBlocksData> { - const EdiorBlockData: EditorBlocksData = {}; - - for (const blockType of Object.values(BlockType)) { - const template = await getBlockTemplate(blockType as BlockType); +const EditorBlocksData = Object.values(BlockType).reduce((previewData, blockType) => { + const template = getBlockTemplate(blockType); + const preview = getBlockPreview(blockType); - const preview = getBlockPreview(blockType); + template.meta = template.meta || {}; + template.meta.title = template.meta.title || formatBlockName(blockType); - template.meta = template.meta || {}; - template.meta.title = template.meta.title || formatBlockName(blockType); + /* eslint-disable no-param-reassign */ + previewData[blockType] = { + ...template, + preview, + } as EdiorBlockData; - EdiorBlockData[blockType] = { - ...template, - preview, - }; - } - - return EdiorBlockData; -} + return previewData; +}, {} as Record<BlockType, EdiorBlockData>); -export {EditorBlocksData, getEditorBlocksData}; +export default EditorBlocksData; diff --git a/src/sub-blocks/LayoutItem/schema.ts b/src/sub-blocks/LayoutItem/schema.ts index 5722b1539..9f47d54e1 100644 --- a/src/sub-blocks/LayoutItem/schema.ts +++ b/src/sub-blocks/LayoutItem/schema.ts @@ -2,7 +2,7 @@ import omit from 'lodash/omit'; import {Media} from '../../blocks/Media/schema'; import metaInfo from '../../components/MetaInfo/schema'; -import {BaseProps, CardLayoutProps, MediaProps} from '../../schema/validators/common'; +import {BaseProps, CardLayoutProps} from '../../schema/validators/common'; import {AnalyticsEventSchema} from '../../schema/validators/event'; import {ContentBase} from '../../sub-blocks/Content/schema'; @@ -13,7 +13,6 @@ export const LayoutItem = { properties: { ...BaseProps, ...CardLayoutProps, - media: MediaProps, media: Media, content: omit(ContentBase, ['colSize', 'size', 'centered']), contentMargin: { From 84967f8dd96a9479819f83056d9aafeaa31b2e6b Mon Sep 17 00:00:00 2001 From: DaffPunks <sawavas2@gmail.com> Date: Thu, 13 Feb 2025 15:37:14 +0300 Subject: [PATCH 07/84] fix: rework state, fix listeners --- src/common/postMessage.ts | 4 ++- src/common/store.ts | 31 ++++++++++--------- src/common/types/actions.ts | 3 +- .../PCEditorStoreContext.tsx | 6 ++-- .../PCEditorStoreProvider.tsx | 6 ++-- .../Editor/hooks/useAdminInitialize.tsx | 14 ++++++--- src/editor-v2/hooks/usePostMessageEvents.ts | 2 +- src/hooks/useEditorInitialize.ts | 6 ++-- src/utils/store.ts | 10 +++--- 9 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/common/postMessage.ts b/src/common/postMessage.ts index 47695356f..65481b329 100644 --- a/src/common/postMessage.ts +++ b/src/common/postMessage.ts @@ -35,8 +35,10 @@ export function listenPostMessageEvents<K extends keyof EventMessageTypes>( export function usePostMessageAPIListener<K extends keyof EventMessageTypes>( action: K, callback: (data: EventMessageTypes[K]) => void, + deps: unknown[] = [], ) { useEffect(() => { return listenPostMessageEvents(action, callback); - }, [action, callback]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps]); } diff --git a/src/common/store.ts b/src/common/store.ts index 37a246003..7e2110121 100644 --- a/src/common/store.ts +++ b/src/common/store.ts @@ -57,21 +57,24 @@ export interface EditorMethods { export type EditorStore = EditorState & EditorMethods; -// TODO: split methods and state +const initialStore: EditorState = { + height: 100, + zoom: 100, + manipulateOverlayMode: false, + selectedBlock: undefined, + initialized: false, + content: {blocks: []}, + blocks: [], + subBlocks: [], + global: [], + preInsertBlockType: null, + preReorderBlockPath: null, +}; + +export const createPCEditorStore = initializeStore<EditorState>(initialStore, () => ({})); + export const createEditorStore = initializeStore<EditorState, EditorMethods>( - { - height: 100, - zoom: 100, - manipulateOverlayMode: false, - selectedBlock: undefined, - initialized: false, - content: {blocks: []}, - blocks: [], - subBlocks: [], - global: [], - preInsertBlockType: null, - preReorderBlockPath: null, - }, + initialStore, (set, get) => ({ setHeight(height: number) { // We have to add 200-500px, because of bottom padding or margin of last element diff --git a/src/common/types/actions.ts b/src/common/types/actions.ts index 64c7465d5..ffb86cb76 100644 --- a/src/common/types/actions.ts +++ b/src/common/types/actions.ts @@ -17,6 +17,5 @@ export type EventMessageTypes = { export type ActionMessageTypes = { GET_SUPPORTED_BLOCKS: {}; - GET_CURRENT_CONTENT: {}; - SET_CONTENT: PageContent; + GET_INITIAL_CONTENT: {}; }; diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx index 7eebc64a7..acceef310 100644 --- a/src/context/editorStoreContext/PCEditorStoreContext.tsx +++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx @@ -2,12 +2,12 @@ import React from 'react'; import {StoreApi} from 'zustand'; -import {EditorStore, createEditorStore} from '../../common/store'; +import {EditorState, createPCEditorStore} from '../../common/store'; export interface PCEditorStoreContextProps { - state: StoreApi<EditorStore>; + state: StoreApi<EditorState>; } export const PCEditorStoreContext = React.createContext<PCEditorStoreContextProps>({ - state: createEditorStore(), + state: createPCEditorStore(), }); diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx index 9a305ca55..22938c507 100644 --- a/src/context/editorStoreContext/PCEditorStoreProvider.tsx +++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx @@ -2,7 +2,7 @@ import React, {PropsWithChildren, useCallback, useEffect, useRef} from 'react'; import {StoreApi} from 'zustand'; -import {EditorStore, createEditorStore} from '../../common/store'; +import {EditorState, createPCEditorStore} from '../../common/store'; import {StoreSyncMessage} from '../../common/types'; import {PCEditorStoreContext} from './PCEditorStoreContext'; @@ -10,7 +10,7 @@ import {PCEditorStoreContext} from './PCEditorStoreContext'; interface PCEditorStoreProviderProps extends PropsWithChildren {} export const PCEditorStoreProvider = ({children}: PCEditorStoreProviderProps) => { - const storeRef = useRef<StoreApi<EditorStore>>(); + const storeRef = useRef<StoreApi<EditorState>>(); const syncStore = useCallback((message: StoreSyncMessage) => { if (storeRef.current && message.state) { @@ -32,7 +32,7 @@ export const PCEditorStoreProvider = ({children}: PCEditorStoreProviderProps) => }, [syncStore]); if (!storeRef.current) { - storeRef.current = createEditorStore(); + storeRef.current = createPCEditorStore(); } return ( diff --git a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx index bdf1e929d..88bb3e815 100644 --- a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx +++ b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx @@ -17,11 +17,15 @@ const useAdminInitialize = () => { preReorderBlockPath, } = useMainEditorStore(); - usePostMessageAPIListener('ON_INIT', () => { - initialize(); - requestPostMessage('GET_SUPPORTED_BLOCKS', {}); - requestPostMessage('GET_CURRENT_CONTENT', {}); - }); + usePostMessageAPIListener( + 'ON_INIT', + () => { + initialize(); + requestPostMessage('GET_SUPPORTED_BLOCKS', {}); + requestPostMessage('GET_INITIAL_CONTENT', {}); + }, + [requestPostMessage], + ); usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { setContent(data); diff --git a/src/editor-v2/hooks/usePostMessageEvents.ts b/src/editor-v2/hooks/usePostMessageEvents.ts index 3bb5473ff..1b73e0cd8 100644 --- a/src/editor-v2/hooks/usePostMessageEvents.ts +++ b/src/editor-v2/hooks/usePostMessageEvents.ts @@ -1,7 +1,7 @@ import {useContext} from 'react'; import {requestActionPostMessage} from '../../common/postMessage'; -import {ActionMessageTypes} from '../../common/types/actions'; +import {ActionMessageTypes} from '../../common/types'; import {IframeContext} from '../context/iframeContext'; interface UsePostMessageRequestReturn { diff --git a/src/hooks/useEditorInitialize.ts b/src/hooks/useEditorInitialize.ts index 8d721821c..f47e4dde1 100644 --- a/src/hooks/useEditorInitialize.ts +++ b/src/hooks/useEditorInitialize.ts @@ -21,7 +21,7 @@ export const useInitializeEditorEvents = ({ initialContent, setContent, }: UseEditorInitializeProps) => { - const {manipulateOverlayMode, disableMode, initialized, content} = usePCEditorStore(); + const {manipulateOverlayMode, initialized, content} = usePCEditorStore(); useEffect(() => { if (initialized) { @@ -29,7 +29,7 @@ export const useInitializeEditorEvents = ({ } }, [content, initialized, setContent]); - useInternalPostMessageAPIListener('GET_CURRENT_CONTENT', () => { + useInternalPostMessageAPIListener('GET_INITIAL_CONTENT', () => { sendEventPostMessage('ON_INITIAL_CONTENT', initialContent); }); @@ -76,7 +76,7 @@ export const useInitializeEditorEvents = ({ document.removeEventListener('mouseup', throttleOnMouseUp); window.removeEventListener('resize', onResize); }; - }, [disableMode, manipulateOverlayMode, onResize]); + }, [manipulateOverlayMode, onResize]); useEffect(() => { const height = document.documentElement.getBoundingClientRect().height; diff --git a/src/utils/store.ts b/src/utils/store.ts index a50614c5d..666e53573 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,16 +1,15 @@ import {StoreApi, create} from 'zustand'; import {devtools, subscribeWithSelector} from 'zustand/middleware'; -export function initializeStore<State, Methods>( +export function initializeStore<State extends {}, Methods extends {} = {}>( initialState: State, methods: ( set: StoreApi<State & Methods>['setState'], get: StoreApi<State & Methods>['getState'], - onStateUpdated?: () => void, ) => Methods, ) { - return (overrideInitialState?: Partial<State>, onStateUpdated?: () => void) => - create< + return (overrideInitialState?: Partial<State>) => { + return create< State & Methods, [ ['zustand/subscribeWithSelector', State & Methods], @@ -23,11 +22,12 @@ export function initializeStore<State, Methods>( return { ...initialState, ...overrideInitialState, - ...methods(set, get, onStateUpdated), + ...methods(set, get), }; }), ), ); + }; } export const removeFn = (object: object) => { From 4321843d3cd3fde1f13afe348236a18d9886fd6f Mon Sep 17 00:00:00 2001 From: DaffPunks <sawavas2@gmail.com> Date: Fri, 21 Feb 2025 16:03:36 +0300 Subject: [PATCH 08/84] fix: refactor common, store and hooks --- package-lock.json | 295 ------------- package.json | 2 - playground/src/app/pc-2/content.json | 402 ++++++++++++++++++ playground/src/app/pc-2/page.tsx | 15 + playground/src/app/pc-2/styles.scss | 116 +++++ playground/src/app/{ => pc}/content.json | 0 playground/src/app/pc/page.tsx | 3 +- src/common/store.ts | 235 +--------- src/{utils/store.ts => common/utils.ts} | 0 .../editor/ChildrenWrap/ChildrenWrap.tsx | 4 +- src/components/editor/ItemWrap/ItemWrap.tsx | 4 +- src/constructor-items.ts | 9 +- .../PageConstructor/PageConstructor.tsx | 6 +- .../ConstructorBlock/ConstructorBlock.tsx | 5 +- src/context/editorStoreContext/index.ts | 1 - .../components/BigOverlay/BigOverlay.tsx | 2 +- .../components/BlockConfig/BlockConfig.tsx | 2 +- .../components/BlocksList/BlocksList.tsx | 2 +- .../components/GlobalConfig/GlobalConfig.tsx | 2 +- .../components/MiddleScreen/MiddleScreen.tsx | 2 +- src/editor-v2/components/Overlay/Overlay.tsx | 2 +- src/editor-v2/components/Source/Source.tsx | 2 +- .../components/SourceCode/SourceCode.tsx | 2 +- .../components/StoreViewer/StoreViewer.tsx | 2 +- src/editor-v2/components/Tree/Tree.tsx | 2 +- src/editor-v2/containers/Editor/Editor.tsx | 8 +- .../Editor/hooks/useAdminInitialize.tsx | 58 --- .../editorStore/MainEditorStoreContext.tsx | 2 +- .../editorStore/MainEditorStoreProvider.tsx | 5 +- src/editor-v2/context/editorStore/index.ts | 1 - .../{iframeContext.tsx => IframeContext.tsx} | 0 ...{iframeProvider.tsx => IframeProvider.tsx} | 2 +- src/editor-v2/context/iframeContext/index.ts | 4 +- .../hooks/useMainEditorInitialize.ts | 63 +++ .../hooks/useMainEditorStore.ts | 2 +- src/editor-v2/store.ts | 233 ++++++++++ src/editor/data/index.ts | 45 +- .../usePCEditorBlockMouseEvents.ts} | 11 +- ...lize.ts => usePCEditorInitializeEvents.ts} | 4 +- .../hooks/usePCEditorStore.ts | 2 +- 40 files changed, 906 insertions(+), 651 deletions(-) create mode 100644 playground/src/app/pc-2/content.json create mode 100644 playground/src/app/pc-2/page.tsx create mode 100644 playground/src/app/pc-2/styles.scss rename playground/src/app/{ => pc}/content.json (100%) rename src/{utils/store.ts => common/utils.ts} (100%) delete mode 100644 src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx rename src/editor-v2/context/iframeContext/{iframeContext.tsx => IframeContext.tsx} (100%) rename src/editor-v2/context/iframeContext/{iframeProvider.tsx => IframeProvider.tsx} (94%) create mode 100644 src/editor-v2/hooks/useMainEditorInitialize.ts rename src/editor-v2/{context/editorStore => }/hooks/useMainEditorStore.ts (75%) create mode 100644 src/editor-v2/store.ts rename src/{containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx => hooks/usePCEditorBlockMouseEvents.ts} (86%) rename src/hooks/{useEditorInitialize.ts => usePCEditorInitializeEvents.ts} (96%) rename src/{context/editorStoreContext => }/hooks/usePCEditorStore.ts (74%) diff --git a/package-lock.json b/package-lock.json index 6242fdff5..010389716 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,7 +107,6 @@ "lint-staged": "^11.2.6", "monaco-editor-webpack-plugin": "^7.1.0", "move-file-cli": "^3.0.0", - "next": "^14.2.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", "postcss-loader": "^4.3.0", @@ -4922,156 +4921,6 @@ "react": ">=16" } }, - "node_modules/@next/env": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.24.tgz", - "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==", - "dev": true - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.24.tgz", - "integrity": "sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.24.tgz", - "integrity": "sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.24.tgz", - "integrity": "sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.24.tgz", - "integrity": "sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.24.tgz", - "integrity": "sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.24.tgz", - "integrity": "sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.24.tgz", - "integrity": "sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.24.tgz", - "integrity": "sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.24.tgz", - "integrity": "sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -6555,22 +6404,6 @@ "@svgr/core": "*" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true - }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "dev": true, - "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, "node_modules/@tanstack/react-virtual": { "version": "3.11.3", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.3.tgz", @@ -9034,18 +8867,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dev": true, - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -9527,12 +9348,6 @@ "node": ">=8" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "dev": true - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -18266,90 +18081,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/next": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.24.tgz", - "integrity": "sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==", - "dev": true, - "dependencies": { - "@next/env": "14.2.24", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.24", - "@next/swc-darwin-x64": "14.2.24", - "@next/swc-linux-arm64-gnu": "14.2.24", - "@next/swc-linux-arm64-musl": "14.2.24", - "@next/swc-linux-x64-gnu": "14.2.24", - "@next/swc-linux-x64-musl": "14.2.24", - "@next/swc-win32-arm64-msvc": "14.2.24", - "@next/swc-win32-ia32-msvc": "14.2.24", - "@next/swc-win32-x64-msvc": "14.2.24" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, "node_modules/next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==", "dev": true }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -22906,15 +22643,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/streamx": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", @@ -23361,29 +23089,6 @@ "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "dev": true, - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, "node_modules/stylelint": { "version": "15.11.0", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", diff --git a/package.json b/package.json index 92a405b20..7794af67a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "lint": "run-p lint:js lint:styles lint:prettier typecheck", "typecheck": "tsc --noEmit", "dev": "npm run storybook:start", - "dev:website": "next dev playground", "storybook:start": "storybook dev -p 7009", "storybook:build": "storybook build -c .storybook -o storybook-static", "start": "node dist", @@ -207,7 +206,6 @@ "lint-staged": "^11.2.6", "monaco-editor-webpack-plugin": "^7.1.0", "move-file-cli": "^3.0.0", - "next": "^14.2.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", "postcss-loader": "^4.3.0", diff --git a/playground/src/app/pc-2/content.json b/playground/src/app/pc-2/content.json new file mode 100644 index 000000000..568c1b8d2 --- /dev/null +++ b/playground/src/app/pc-2/content.json @@ -0,0 +1,402 @@ +{ + "meta": { + "title": "YDB — распределённая SQL база данных с открытым исходным кодом", + "description": "YDB — это открытая распределённая SQL база данных, которая сочетает высокую доступность и масштабируемость со строгой согласованностью и ACID транзакциями.", + "sharing": { + "image": "https://storage.yandexcloud.net/ydb-site-assets/share-ydb-eng.png" + } + }, + "blocks": [ + { + "type": "header-block", + "title": "YDB", + "description": "YDB — это распределённая отказоустойчивая Distributed SQL база данных с открытым исходным кодом, которая сочетает в себе высокую доступность и масштабируемость со строгой согласованностью и транзакциями ACID. Она поддерживает одновременное выполнение транзакционных (OLTP), аналитических (OLAP) и потоковых нагрузок.", + "width": "s", + "verticalOffset": "m", + "imageSize": "m", + "background": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/cover.png", + "disableCompress": true, + "alt": "Фон YDB" + }, + "color": "#e2eaff", + "fullWidth": false + }, + "buttons": [ + { + "text": "Быстрый старт", + "theme": "accent", + "url": "/../docs/ru/quickstart" + }, + { + "text": "Документация", + "theme": "outlined", + "url": "/../docs/ru/" + } + ] + }, + { + "type": "card-layout-block", + "title": "Что я могу делать с YDB?", + "animated": false, + "colSizes": { + "all": 12, + "sm": 6, + "md": 4 + }, + "anchor": { + "url": "whatcanido", + "text": "Что я могу делать с YDB?" + }, + "description": "", + "children": [ + { + "type": "background-card", + "backgroundColor": "#CCE7FF", + "title": "Транзакционные нагрузки (OLTP)", + "text": "Вы можете использовать YDB для хранения состояния вашего приложения, независимо от объема данных или частоты их изменения. Обрабатывать петабайты при миллионах транзакций в секунду — не проблема.", + "controlPosition": "footer", + "buttons": [ + { + "text": "Узнать больше", + "theme": "outlined", + "url": "/docs/ru/concepts/" + } + ] + }, + { + "type": "background-card", + "backgroundColor": "rgba(107,132,153,.12)", + "title": "Аналитические нагрузки (OLAP)", + "text": "Вы можете создавать аналитические отчёты на основе хранимых в YDB данных с производительностью, сопоставимой со специализированными аналитическими СУБД. При этом никаких компромиссов по согласованности и доступности не потребуется.", + "controlPosition": "footer", + "buttons": [ + { + "text": "Узнать больше", + "theme": "outlined", + "url": "/docs/ru/concepts/datamodel/table#column-oriented-tables" + } + ] + }, + { + "type": "background-card", + "backgroundColor": "rgba(107,132,153,.12)", + "title": "Потоковые нагрузки", + "text": "Вы можете использовать функциональность YDB-топиков для надёжной отправки данных между вашими приложениями или отслеживания изменений в таблицах YDB. Можно выбрать как семантику доставки сообщений ровно один раз (exactly once), так и не менее одного раза (at least once).", + "controlPosition": "footer", + "buttons": [ + { + "text": "Узнать больше", + "theme": "outlined", + "url": "/docs/ru/concepts/topic" + } + ] + } + ] + }, + { + "type": "extended-features-block", + "title": "Почему YDB?", + "animated": false, + "colSizes": { + "all": 12, + "sm": 6, + "md": 4 + }, + "items": [ + { + "title": "Эластичность и масштабируемость", + "text": "Добавляйте или удаляйте узлы на лету, чтобы легко масштабировать кластер по мере необходимости. YDB имеет отдельные слои вычисления и хранения, что позволяет независимо добавлять дисковую ёмкость или вычислительные ресурсы в зависимости от того, чего не хватает при текущей нагрузке.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_01.svg" + }, + { + "title": "Отказоустойчивость", + "text": "YDB спроектирована для работы в трёх зонах доступности и обеспечивает работоспособность даже в случае выхода из строя одной из них. Она автоматически восстанавливается после сбоя диска, сервера или датацентра с минимальной задержкой для приложений.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_03.svg" + }, + { + "title": "Простота в использовании", + "text": "Работа с кластером YDB ощущается как работа с одноузловой СУБД с безграничными ресурсами благодаря строгой согласованности, ACID-транзакциям, высокопроизводительным запросам, возможности загрузки больших объёмов данных, а также поддержке знакомого диалекта SQL и JSON API.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_02.svg" + }, + { + "title": "Универсальность", + "text": "Благодаря поддержке различных видов нагрузок в одной системе YDB может заменить несколько систем хранения и обработки данных или всю корпоративную экосистему данных в компании.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_06.svg" + }, + { + "title": "Открытый исходный код", + "text": "[Исходный код YDB](https://github.com/ydb-platform/ydb) опубликован под лицензией Apache 2.0, накладывающей минимум ограничений на использование. Таким образом, нет рисков, связанных с привязкой к конкретному поставщику или провайдеру облачных услуг.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_05.svg" + }, + { + "title": "Совместимость с любым окружением", + "text": "YDB можно развернуть в [Kubernetes](https://github.com/ydb-platform/ydb-kubernetes-operator), в любом облачном окружении или в корпоративных ЦОД. Либо можно использовать YDB как [управляемый сервис в Yandex Cloud](https://cloud.yandex.ru/services/ydb). Также возможны локальные эксперименты на любом компьютере.", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_04.svg" + } + ] + }, + { + "type": "banner-block", + "animated": false, + "title": "Оценка производительности PostgreSQL vs Distributed DBMS", + "subtitle": "TPC-C является наиболее известным набором тестов производительности для OLTP. Мы подготовили исследование производительности, сравнивающее PostgreSQL, YDB и CockroachDB в отказоустойчивых конфигурациях.", + "image": "https://storage.yandexcloud.net/ydb-site-assets/banner-block-pg.png", + "color": "#CCE7FF", + "button": { + "text": "Читать дальше", + "theme": "raised", + "url": "https://habr.com/ru/companies/ydb/articles/801587/" + } + }, + { + "type": "card-layout-block", + "title": "Кто использует YDB?", + "animated": false, + "colSizes": { + "all": 12, + "sm": 6, + "md": 4 + }, + "anchor": { + "url": "whouses", + "text": "Кто использует YDB?" + }, + "description": "", + "children": [ + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/metrika.png", + "disableCompress": true + } + }, + "content": { + "title": "Метрика", + "text": "[Метрика](https://metrika.yandex.ru/) - одна из крупнейших в мире платформ мобильной и веб-аналитики. Она полагается на YDB для создания пользовательских сессий на лету.\n\nПереход на YDB позволил Метрике расширить объём хранимых данных и бесконечно наращивать обрабатываемую нагрузку. Теперь одна из баз данных Метрики в YDB содержит более 400 ТБ данных и выдерживает нагрузку более 1 000 000 RPS." + }, + "fullScreen": false, + "border": true + }, + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/yandex-cloud-new.png", + "disableCompress": true + } + }, + "content": { + "title": "Yandex Cloud", + "text": "YDB отвечает за слой хранения для сетевых дисков [Yandex Cloud](https://cloud.yandex.ru), используется в качестве СУБД для хранения данных и метаданных облачной инфраструктуры и сервисов платформы, а также в качестве базы данных для облачного Control Plane.\n\nИнфраструктурным и платформенным сервисам Yandex Cloud необходима высокая доступность и масштабируемость, поэтому платформа выбрала YDB в качестве ключевого компонента." + }, + "fullScreen": false, + "border": true + }, + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/praktikum.jpg", + "disableCompress": true + } + }, + "content": { + "title": "Практикум", + "text": "[Практикум](https://practicum.yandex.ru/) - это онлайн-ориентированная образовательная платформа. Она использует YDB в качестве гибкого хранилища состояний для своих микросервисов.\n\nНативная поддержка многоарендности в YDB позволяет им избежать создания и управления выделенными базами данных для каждого компонента сервиса." + }, + "fullScreen": false, + "border": true + }, + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/market.png", + "disableCompress": true + } + }, + "content": { + "title": "Яндекс Маркет", + "text": "[Яндекс Маркет](https://market.yandex.ru) - один из крупнейших сервисов электронной коммерции в СНГ. Многие ключевые функции сервиса, такие как корзина, скидки, и оформление заказа, используют YDB для хранения своего состояния.\n\nВыбор YDB в качестве базы данных позволил Яндекс Маркету выдержать стократное увеличение нагрузки на корзину при соблюдении строгих гарантий времени отклика." + }, + "fullScreen": false, + "border": true + }, + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/jaeger.png", + "disableCompress": true + } + }, + "content": { + "title": "Auto.ru", + "text": "[Auto.ru](https://auto.ru) снизила потребление CPU для трассировочной базы данных [Jaeger](https://www.jaegertracing.io/) в три раза после перехода на YDB, что позволило записывать 500 000 трассировок в секунду без семплирования.\n\nУспешная реализация YDB в качестве хранилища трассировок доказала применимость и ключевые свойства, такие как масштабируемость, отказоустойчивость и строгая согласованность. В результате Auto.ru выбрала YDB в качестве реляционной базы данных для некоторых своих микросервисов." + }, + "fullScreen": false, + "border": true + }, + { + "type": "layout-item", + "media": { + "image": { + "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/alice-new-speakers.png", + "disableCompress": "true\"" + } + }, + "content": { + "title": "Алиса", + "text": "[Алиса](https://yandex.ru/alice) - это голосовой помощник и экосистема умного дома. После перехода на YDB команда Алисы решила проблемы синхронизации между дата‑центрами, простоев при переключении основных серверов, снизила нагрузку на команду DevOps, увеличила объём хранимых данных до сотен терабайт и нагрузку до сотен тысяч запросов в секунду.\n\nПереход на YDB позволил отказаться от ручного шардинга данных и добиться строгой согласованности в кросс-датацентровом кластере. Сейчас команда Алисы использует YDB как реляционную базу данных и как базу для хранения логов и трейсов." + }, + "fullScreen": false, + "border": true + } + ] + }, + { + "type": "slider-block", + "animated": false, + "anchor": { + "url": "scenarios", + "text": "scenarios" + }, + "title": { + "text": "В каких типовых сценариях стоит использовать YDB?", + "textSize": "m" + }, + "children": [ + { + "type": "basic-card", + "title": "Работа с внезапным ростом нагрузки", + "text": "Эластичность YDB позволяет быстро изменять количество ресурсов, выделенных базе данных, чтобы обеспечить необходимую пропускную способность в соответствии с нагрузкой. Вы можете легко увеличить или уменьшить количество вычислительных ресурсов в зависимости от предстоящих нагрузок, например, на «Чёрную пятницу» или для маркетинговых кампаний." + }, + { + "type": "basic-card", + "title": "Хранение Observability данных", + "text": "[Колоночные таблицы YDB](/docs/ru/concepts/column-table) отлично подходят для хранения логов и метрик с простым доступом к ним через SQL интерфейс. Также, низкое потребление вычислительных ресурсов и масштабируемость YDB делают запись [Jaeger трейсов](https://www.jaegertracing.io/) выгодной по себестоимости и лёгкой в использовании." + }, + { + "type": "basic-card", + "title": "Документоориентированная СУБД", + "text": "Не смотря на строгую систему типов данных YDB, поддержка [типа данных JSON и связанных с ним функций](/docs/ru/yql/reference/builtins/json), расширяет возможности YDB в роли системы хранения неструктурированных документов." + }, + { + "type": "basic-card", + "title": "Кеш с SQL-интерфейсом", + "text": "Быстрый отклик и масштабируемость пропускной способности позволяют использовать YDB одновременно в качестве онлайн-базы данных и предварительно посчитанного кеша. Возможности SQL значительно повышают удобство использования и позволяют проводить оперативную аналитику данных в кеше. Например, сайты туроператоров и агрегаторов путешествий могут использовать базу данных для кеширования результатов поиска авиабилетов или туров, а также для пересчёта цен и проверки сезонной доступности." + }, + { + "type": "basic-card", + "title": "Централизованная система учёта запасов", + "text": "YDB обеспечивает строгую целостность транзакций. Это позволяет предоставлять консистентные данные о запасах на всех складах и торговых объектах, и делает YDB подходящим решением для электронной коммерции, приложений складской или транспортной логистики." + }, + { + "type": "basic-card", + "title": "Система хранения данных для Internet of Things (IoT) экосистемы", + "text": "Поддержка автоматического шардирования YDB позволяет обрабатывать потоки данных от большого количества устройств — профиль нагрузки, который встречается в проектах интернета вещей." + } + ] + }, + { + "type": "media-block", + "animated": false, + "direction": "content-media", + "title": "Можете вкратце объяснить, что такое YDB, в видеоформате?", + "description": "Конечно, смотрите →", + "anchor": { + "url": "video", + "text": "Video" + }, + "largeMedia": true, + "media": { + "youtube": "https://youtu.be/QVmhR__wAJg" + } + }, + { + "type": "banner-block", + "animated": false, + "title": "Учебный курс по YDB от участника сообщества пользователей", + "subtitle": "_Сторонняя разработка - Contribution_\n\nНачальный [учебный курс по YDB](https://stepik.org/course/186264/promo), разработанный одним из наших пользователей - Владом Бурмистровым, позволяет ознакомиться с ключевыми свойствами, возможностями и архитектурой YDB, а также научиться основным приемам по работе с YDB.", + "color": "#CCE7FF", + "image": "https://storage.yandexcloud.net/ydb-site-assets/careers/img_4.png", + "button": { + "text": "Описание и программа курса", + "theme": "raised", + "url": "https://stepik.org/course/186264/promo" + } + }, + { + "type": "tabs-block", + "title": { + "text": "Как начать?" + }, + "anchor": { + "url": "howtostart", + "text": "Как начать?" + }, + "items": [ + { + "tabName": "Docker", + "title": "Docker", + "text": " Создайте рабочий каталог и запустите локальный контейнер YDB из этого каталога:\n```bash mkdir ydb-local && cd ydb-local \ndocker run -d --rm --name ydb-local -h localhost \\\n--platform linux/amd64 \\\n-p 2135:2135 -p 2136:2136 -p 8765:8765 \\\n-v $(pwd)/ydb_certs:/ydb_certs -v $(pwd)/ydb_data:/ydb_data \\\n-e GRPC_TLS_PORT=2135 -e GRPC_PORT=2136 -e MON_PORT=8765 \\\nydbplatform/local-ydb:latest\n```\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. " + }, + { + "tabName": "Minikube", + "title": "Minikube", + "text": " Установите Kubernetes CLI [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl) и менеджер пакетов [Helm 3](https://helm.sh/docs/intro/install/).\nУстановите и запустите [Minikube](https://kubernetes.io/ru/docs/tasks/tools/install-minikube/).\nСклонируйте репозиторий с [YDB Kubernetes Operator](https://github.com/ydb-platform/ydb-kubernetes-operator).\n```\ngit clone https://github.com/ydb-platform/ydb-kubernetes-operator && cd ydb-kubernetes-operator\n```\nУстановите контроллер YDB на кластер.\n```\nhelm upgrade --install ydb-operator deploy/ydb-operator --set metrics.enabled=false\n```\nПримените манифест для создания кластера YDB.\n```\nkubectl apply -f samples/minikube/storage.yaml\n```\nДождитесь, пока `kubectl get storages.ydb.tech` не станет `Ready`.\nПримените манифест для создания базы данных.\n```\nkubectl apply -f samples/minikube/database.yaml\n```\nДождитесь, пока `kubectl get databases.ydb.tech` не станет `Ready`.\n\nПосле обработки манифеста будет создан объект StatefulSet, описывающий набор динамических узлов. Созданная база данных будет доступна изнутри кластера Kubernetes по DNS имени `database-minikube-sample` на порту 2135.\n\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. " + }, + { + "tabName": "Установка вручную", + "title": "Установка вручную (только для Linux x86_64)", + "text": " Создайте рабочий каталог, запустите скрипт установки из него, а затем запустите одноузловую базу данных YDB с включенным режимом работы в оперативной памяти:\n``` mkdir ydb-local && cd ydb-local\ncurl https://install.ydb.tech | bash\n./start.sh ram\n```\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. " + } + ] + }, + { + "type": "icons-block", + "size": "s", + "title": "Как оставаться на связи?", + "items": [ + { + "url": "https://github.com/ydb-platform/ydb", + "text": "GitHub", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/git.svg" + }, + { + "url": "https://t.me/ydb_ru", + "text": "Telegram", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/telegram.svg" + }, + { + "url": "https://habr.com/ru/companies/ydb/articles/", + "text": "Habr", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/habr.svg" + }, + { + "url": "https://blog.ydb.tech", + "text": "Medium", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/medium.svg" + }, + { + "url": "https://twitter.com/YDBPlatform", + "text": "Twitter", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/twitter.svg" + }, + { + "url": "https://www.linkedin.com/company/ydb-platform/", + "text": "LinkedIn", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/linkedin.svg" + }, + { + "url": "https://www.youtube.com/c/YDBPlatform", + "text": "YouTube", + "src": "https://storage.yandexcloud.net/ydb-site-assets/community/youtube.svg" + } + ] + } + ] +} diff --git a/playground/src/app/pc-2/page.tsx b/playground/src/app/pc-2/page.tsx new file mode 100644 index 000000000..5b54e6796 --- /dev/null +++ b/playground/src/app/pc-2/page.tsx @@ -0,0 +1,15 @@ +'use client'; +import React from 'react'; + +import {PageConstructor, PageConstructorProvider} from '../../../../src'; + +import content from './content.json'; +import './styles.scss'; + +export default function Home() { + return ( + <PageConstructorProvider projectSettings={{disableCompress: true}}> + <PageConstructor content={content} /> + </PageConstructorProvider> + ); +} diff --git a/playground/src/app/pc-2/styles.scss b/playground/src/app/pc-2/styles.scss new file mode 100644 index 000000000..158d94216 --- /dev/null +++ b/playground/src/app/pc-2/styles.scss @@ -0,0 +1,116 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap'); + +.g-root_theme_light { + --g-border-radius-xs: 0px; + --g-border-radius-s: 0px; + --g-border-radius-m: 0px; + --g-border-radius-l: 0px; + --g-border-radius-xl: 0px; + + --g-font-family-sans: 'Inter', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + --g-font-family-monospace: 'Roboto Mono', 'Menlo', 'Monaco', 'Consolas', 'Ubuntu Mono', + 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', 'Courier', monospace; + + --g-color-private-brand-50: rgba(203, 255, 92, 0.1); + --g-color-private-brand-100: rgba(203, 255, 92, 0.15); + --g-color-private-brand-150: rgba(203, 255, 92, 0.2); + --g-color-private-brand-200: rgba(203, 255, 92, 0.3); + --g-color-private-brand-250: rgba(203, 255, 92, 0.4); + --g-color-private-brand-300: rgba(203, 255, 92, 0.5); + --g-color-private-brand-350: rgba(203, 255, 92, 0.6); + --g-color-private-brand-400: rgba(203, 255, 92, 0.7); + --g-color-private-brand-450: rgba(203, 255, 92, 0.8); + --g-color-private-brand-500: rgba(203, 255, 92, 0.9); + --g-color-private-brand-550-solid: rgb(203, 255, 92); + --g-color-private-brand-1000-solid: rgb(59, 63, 43); + --g-color-private-brand-950-solid: rgb(68, 74, 46); + --g-color-private-brand-900-solid: rgb(85, 97, 51); + --g-color-private-brand-850-solid: rgb(102, 119, 57); + --g-color-private-brand-800-solid: rgb(119, 142, 63); + --g-color-private-brand-750-solid: rgb(135, 165, 69); + --g-color-private-brand-700-solid: rgb(152, 187, 75); + --g-color-private-brand-650-solid: rgb(169, 210, 80); + --g-color-private-brand-600-solid: rgb(186, 232, 86); + --g-color-private-brand-500-solid: rgb(208, 255, 108); + --g-color-private-brand-450-solid: rgb(213, 255, 125); + --g-color-private-brand-400-solid: rgb(219, 255, 141); + --g-color-private-brand-350-solid: rgb(224, 255, 157); + --g-color-private-brand-300-solid: rgb(229, 255, 174); + --g-color-private-brand-250-solid: rgb(234, 255, 190); + --g-color-private-brand-200-solid: rgb(239, 255, 206); + --g-color-private-brand-150-solid: rgb(245, 255, 222); + --g-color-private-brand-100-solid: rgb(247, 255, 231); + --g-color-private-brand-50-solid: rgb(250, 255, 239); + + --g-color-base-brand: rgb(203, 255, 92); + --g-color-base-background: rgb(255, 255, 255); + --g-color-base-brand-hover: var(--g-color-private-brand-600-solid); + --g-color-base-selection: var(--g-color-private-brand-200); + --g-color-base-selection-hover: var(--g-color-private-brand-300); + --g-color-line-brand: var(--g-color-private-brand-600-solid); + --g-color-text-brand: var(--g-color-private-brand-700-solid); + --g-color-text-brand-heavy: var(--g-color-private-brand-700-solid); + --g-color-text-brand-contrast: rgba(0, 0, 0, 0.85); + --g-color-text-link: var(--g-color-private-brand-600-solid); + --g-color-text-link-hover: var(--g-color-private-brand-800-solid); + --g-color-text-link-visited: var(--g-color-private-purple-550-solid); + --g-color-text-link-visited-hover: var(--g-color-private-purple-800-solid); +} + +.g-root_theme_dark { + --g-border-radius-xs: 0px; + --g-border-radius-s: 0px; + --g-border-radius-m: 0px; + --g-border-radius-l: 0px; + --g-border-radius-xl: 0px; + + --g-font-family-sans: 'Inter', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + --g-font-family-monospace: 'Roboto Mono', 'Menlo', 'Monaco', 'Consolas', 'Ubuntu Mono', + 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', 'Courier', monospace; + + --g-color-private-brand-50: rgba(203, 255, 92, 0.1); + --g-color-private-brand-100: rgba(203, 255, 92, 0.15); + --g-color-private-brand-150: rgba(203, 255, 92, 0.2); + --g-color-private-brand-200: rgba(203, 255, 92, 0.3); + --g-color-private-brand-250: rgba(203, 255, 92, 0.4); + --g-color-private-brand-300: rgba(203, 255, 92, 0.5); + --g-color-private-brand-350: rgba(203, 255, 92, 0.6); + --g-color-private-brand-400: rgba(203, 255, 92, 0.7); + --g-color-private-brand-450: rgba(203, 255, 92, 0.8); + --g-color-private-brand-500: rgba(203, 255, 92, 0.9); + --g-color-private-brand-550-solid: rgb(203, 255, 92); + --g-color-private-brand-1000-solid: rgb(247, 255, 231); + --g-color-private-brand-950-solid: rgb(245, 255, 222); + --g-color-private-brand-900-solid: rgb(239, 255, 206); + --g-color-private-brand-850-solid: rgb(234, 255, 190); + --g-color-private-brand-800-solid: rgb(229, 255, 174); + --g-color-private-brand-750-solid: rgb(224, 255, 157); + --g-color-private-brand-700-solid: rgb(219, 255, 141); + --g-color-private-brand-650-solid: rgb(213, 255, 125); + --g-color-private-brand-600-solid: rgb(208, 255, 108); + --g-color-private-brand-500-solid: rgb(186, 232, 86); + --g-color-private-brand-450-solid: rgb(169, 210, 80); + --g-color-private-brand-400-solid: rgb(152, 187, 75); + --g-color-private-brand-350-solid: rgb(135, 165, 69); + --g-color-private-brand-300-solid: rgb(119, 142, 63); + --g-color-private-brand-250-solid: rgb(102, 119, 57); + --g-color-private-brand-200-solid: rgb(85, 97, 51); + --g-color-private-brand-150-solid: rgb(68, 74, 46); + --g-color-private-brand-100-solid: rgb(59, 63, 43); + --g-color-private-brand-50-solid: rgb(51, 52, 40); + + --g-color-base-brand: rgb(203, 255, 92); + --g-color-base-background: rgb(34, 29, 34); + --g-color-base-brand-hover: var(--g-color-private-brand-650-solid); + --g-color-base-selection: var(--g-color-private-brand-150); + --g-color-base-selection-hover: var(--g-color-private-brand-200); + --g-color-line-brand: var(--g-color-private-brand-600-solid); + --g-color-text-brand: var(--g-color-private-brand-600-solid); + --g-color-text-brand-heavy: var(--g-color-private-brand-700-solid); + --g-color-text-brand-contrast: rgba(0, 0, 0, 0.9); + --g-color-text-link: var(--g-color-private-brand-550-solid); + --g-color-text-link-hover: var(--g-color-private-brand-700-solid); + --g-color-text-link-visited: var(--g-color-private-purple-700-solid); + --g-color-text-link-visited-hover: var(--g-color-private-purple-850-solid); +} diff --git a/playground/src/app/content.json b/playground/src/app/pc/content.json similarity index 100% rename from playground/src/app/content.json rename to playground/src/app/pc/content.json diff --git a/playground/src/app/pc/page.tsx b/playground/src/app/pc/page.tsx index e208d76c6..88c94a9f2 100644 --- a/playground/src/app/pc/page.tsx +++ b/playground/src/app/pc/page.tsx @@ -2,7 +2,8 @@ import React from 'react'; import {PageConstructor, PageConstructorProvider} from '../../../../src'; -import content from '../content.json'; + +import content from './content.json'; export default function Home() { return ( diff --git a/src/common/store.ts b/src/common/store.ts index 7e2110121..41db0f670 100644 --- a/src/common/store.ts +++ b/src/common/store.ts @@ -1,21 +1,9 @@ import _ from 'lodash'; -// TODO: remove imports from editor-v2 -import {ZOOM_STEPS} from '../editor-v2/constants'; -import { - duplicateArrayItem, - generateChildrenPathFromArray, - getDestinationShiftBeforeReorder, - insert, - isItemsNeighbours, - modifyObjectByPath, - removeFromArray, - reorderArrayItems, -} from '../editor-v2/utils'; -import {ConstructorBlock, PageContent} from '../models'; -import {initializeStore} from '../utils/store'; +import {PageContent} from '../models'; -import {ConfigInput, DynamicFormValue, ItemConfig} from './types'; +import {ConfigInput, ItemConfig} from './types'; +import {initializeStore} from './utils'; export interface EditorState { height?: number; @@ -34,30 +22,7 @@ export interface EditorState { preReorderBlockPath: number[] | null; } -export interface EditorMethods { - initialize(): void; - setSelectedBlock(path: number[]): void; - setHeight(height: number): void; - setZoom(zoom: number): void; - increaseZoom(): void; - decreaseZoom(): void; - setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; - setContent(data: PageContent): void; - insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; - enableInsertMode(blockType: string): void; - enableReorderMode(path: number[]): void; - disableMode(): void; - updateField(path: string, value: DynamicFormValue): void; - deleteBlock(path: number[]): void; - duplicateBlock(path: number[]): void; - reorderBlock(path: number[], destination: number[], position?: 'prepend' | 'append'): void; - resetInitialize(): void; - resetBlocks(): void; -} - -export type EditorStore = EditorState & EditorMethods; - -const initialStore: EditorState = { +export const initialStore: EditorState = { height: 100, zoom: 100, manipulateOverlayMode: false, @@ -72,195 +37,3 @@ const initialStore: EditorState = { }; export const createPCEditorStore = initializeStore<EditorState>(initialStore, () => ({})); - -export const createEditorStore = initializeStore<EditorState, EditorMethods>( - initialStore, - (set, get) => ({ - setHeight(height: number) { - // We have to add 200-500px, because of bottom padding or margin of last element - // which is not taken into calculation of final height - const newHeight = height + 500; - set((state) => ({...state, height: newHeight})); - }, - setZoom(zoom) { - if (zoom > 0) { - set((state) => ({...state, zoom})); - } - }, - increaseZoom() { - const currentZoom = get().zoom; - - for (const step of ZOOM_STEPS) { - if (currentZoom < step) { - get().setZoom(step); - break; - } - } - }, - decreaseZoom() { - const currentZoom = get().zoom; - const reverseSteps = ZOOM_STEPS.slice().reverse(); - - for (const step of reverseSteps) { - if (currentZoom > step) { - get().setZoom(step); - break; - } - } - }, - setConfig(data) { - set((state) => ({...state, ...data})); - }, - insertBlock: (arrayPath, blockType, position = 'append') => { - if (position === 'append') { - // TODO: fix - // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign - arrayPath[arrayPath.length - 1] = arrayPath[arrayPath.length - 1] + 1; - } - - const blocksConfig = get().content.blocks; - const blocksData = get().blocks; - - const foundBlock = blocksData.find(({type}) => type === blockType); - const defaultValue = - foundBlock && foundBlock.schema.default - ? {...foundBlock.schema.default, type: blockType} - : {type: blockType}; - - const newBlocksConfig = modifyObjectByPath( - blocksConfig, - arrayPath, - (parentBlocks, index) => - insert(parentBlocks, index, defaultValue as ConstructorBlock), - ); - - set((state) => ({ - ...state, - content: {...state.content, blocks: newBlocksConfig}, - })); - }, - enableInsertMode(blockType: string) { - set((state) => ({ - ...state, - manipulateOverlayMode: 'insert', - preInsertBlockType: blockType, - })); - }, - disableMode() { - set((state) => ({ - ...state, - manipulateOverlayMode: false, - preInsertBlockType: undefined, - preReorderBlockPath: undefined, - })); - }, - enableReorderMode(path) { - set((state) => ({ - ...state, - manipulateOverlayMode: 'reorder', - preReorderBlockPath: path, - })); - }, - setContent(content) { - set((state) => ({ - ...state, - content: content, - })); - }, - initialize() { - set((state) => ({ - ...state, - initialized: true, - })); - }, - setSelectedBlock(path) { - set((state) => ({ - ...state, - selectedBlock: path, - })); - }, - updateField(path, value) { - set((state) => { - const newConfig = _.set(state.content, path, value); - return { - ...state, - content: newConfig, - }; - }); - }, - deleteBlock: (arrayPath) => { - const blocksConfig = get().content.blocks; - - const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); - - set((state) => ({ - ...state, - content: {...state.content, blocks: newBlocksConfig}, - })); - }, - duplicateBlock: (arrayPath) => { - const blocksConfig = get().content.blocks; - - const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem); - - set((state) => ({ - ...state, - content: {...state.content, blocks: newBlocksConfig}, - })); - }, - reorderBlock: (arrayPath, destination, position = 'append') => { - if (position === 'append') { - // TODO: fix - // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign - destination[destination.length - 1] = destination[destination.length - 1] + 1; - } - - let newBlocksConfig: ConstructorBlock[]; - const blocksConfig = get().content.blocks; - // Copy - const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath)); - - if (isItemsNeighbours(arrayPath, destination)) { - newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => { - return reorderArrayItems( - parentBlocks, - arrayPath[arrayPath.length - 1], - destination[destination.length - 1], - ); - }); - } else { - const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); - - // Delete - const blocksConfigWithoutBlock = modifyObjectByPath( - blocksConfig, - arrayPath, - removeFromArray, - ); - // Paste - newBlocksConfig = modifyObjectByPath( - blocksConfigWithoutBlock, - arrayDest, - (parentBlocks, index) => insert(parentBlocks, index, copiedBlock), - ); - } - - set((state) => ({ - ...state, - content: {...state.content, blocks: newBlocksConfig}, - })); - }, - resetInitialize: () => { - set((state) => ({ - ...state, - initialized: false, - })); - }, - resetBlocks: () => { - set((state) => ({ - ...state, - content: {...state.content, blocks: []}, - })); - }, - }), -); diff --git a/src/utils/store.ts b/src/common/utils.ts similarity index 100% rename from src/utils/store.ts rename to src/common/utils.ts diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx index f6129ae0c..7ddbce1d1 100644 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -1,7 +1,7 @@ import React, {PropsWithChildren, ReactNode, useCallback, useContext, useState} from 'react'; -import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; import {BlockIdContext} from '../../../context/blockIdContext'; +import usePCEditorBlockMouseEvents from '../../../hooks/usePCEditorBlockMouseEvents'; import {block} from '../../../utils'; import './ChildrenWrap.scss'; @@ -21,7 +21,7 @@ const ChildrenWrap = (props: ChildrenWrapProps) => { } }, []); const parentBlockId = useContext(BlockIdContext); - const {onMouseUp, onMouseMove} = useEditorBlockMouseEvents([parentBlockId, 0], element); + const {onMouseUp, onMouseMove} = usePCEditorBlockMouseEvents([parentBlockId, 0], element); return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx index cf8fbad92..c002f418d 100644 --- a/src/components/editor/ItemWrap/ItemWrap.tsx +++ b/src/components/editor/ItemWrap/ItemWrap.tsx @@ -1,7 +1,7 @@ import React, {PropsWithChildren, useCallback, useContext, useState} from 'react'; -import useEditorBlockMouseEvents from '../../../containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents'; import {BlockIdContext} from '../../../context/blockIdContext'; +import usePCEditorBlockMouseEvents from '../../../hooks/usePCEditorBlockMouseEvents'; import {block} from '../../../utils'; import './ItemWrap.scss'; @@ -21,7 +21,7 @@ const ItemWrap = (props: ItemWrapProps) => { }, []); const {children, index} = props; const parentBlockId = useContext(BlockIdContext); - const adminBlockMouseEvents = useEditorBlockMouseEvents([parentBlockId, index], element); + const adminBlockMouseEvents = usePCEditorBlockMouseEvents([parentBlockId, index], element); return ( <div ref={blockRef} className={b()} {...adminBlockMouseEvents}> diff --git a/src/constructor-items.ts b/src/constructor-items.ts index d2ce36c7c..9f8bc18a2 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -46,12 +46,7 @@ import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock'; import {SliderNewBlock} from './blocks/unstable'; import {BlockConfig} from './common/types'; import {BlockType, NavigationItemType, SubBlockType} from './models'; -import { - GithubButton, - NavigationButton, - NavigationDropdown, - NavigationLink, -} from './navigation/components/NavigationItem'; +import {GithubButton, NavigationButton, NavigationDropdown, NavigationLink} from './navigation'; import SocialIcon from './navigation/components/SocialIcon/SocialIcon'; import { BackgroundCard, @@ -79,6 +74,7 @@ import PriceDetailedConfig from './sub-blocks/PriceDetailed'; import QuoteConfig from './sub-blocks/Quote'; /** + * TODO: remove it * @deprecated use blockDataMap **/ export const blockMap = { @@ -108,6 +104,7 @@ export const blockMap = { }; /** + * TODO: remove it * @deprecated use blockDataMap **/ export const subBlockMap = { diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index c3e49b23f..e0442e2da 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -8,11 +8,11 @@ import BrandFooter from '../../components/BrandFooter/BrandFooter'; import RootCn from '../../components/RootCn'; import {blockMap, navItemMap, subBlockMap} from '../../constructor-items'; import {AnimateContext} from '../../context/animateContext'; -import {usePCEditorStore} from '../../context/editorStoreContext'; import {InnerContext} from '../../context/innerContext'; import {ProjectSettingsContext} from '../../context/projectSettingsContext'; import {useTheme} from '../../context/theme'; -import {useInitializeEditorEvents} from '../../hooks/useEditorInitialize'; +import {usePCEditorInitializeEvents} from '../../hooks/usePCEditorInitializeEvents'; +import {usePCEditorStore} from '../../hooks/usePCEditorStore'; import { BlockTypes, CustomConfig, @@ -67,7 +67,7 @@ export const Constructor = (props: PageConstructorProps) => { const store = usePCEditorStore(); const {initialized} = store; - useInitializeEditorEvents({initialContent: {blocks, background}, setContent}); + usePCEditorInitializeEvents({initialContent: {blocks, background}, setContent}); const {context} = useMemo( () => ({ diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index cd9c6b485..9d593cf90 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -4,11 +4,10 @@ import pick from 'lodash/pick'; import BlockBase from '../../../../components/BlockBase/BlockBase'; import {BlockDecoration} from '../../../../customization/BlockDecoration'; +import usePCEditorBlockMouseEvents from '../../../../hooks/usePCEditorBlockMouseEvents'; import {BlockDecorationProps, ConstructorBlock as ConstructorBlockType} from '../../../../models'; import {block} from '../../../../utils'; -import useEditorBlockMouseEvents from './hooks/useEditorBlockMouseEvents'; - import './ConstructorBlock.scss'; interface ConstructorBlockProps extends Pick<BlockDecorationProps, 'index'> { @@ -28,7 +27,7 @@ export const ConstructorBlock = ({ setElement(node); } }, []); - const adminBlockMouseEvents = useEditorBlockMouseEvents([index], element); + const adminBlockMouseEvents = usePCEditorBlockMouseEvents([index], element); const {type} = data; const blockBaseProps = useMemo( diff --git a/src/context/editorStoreContext/index.ts b/src/context/editorStoreContext/index.ts index 7fa6e621a..2cb38452e 100644 --- a/src/context/editorStoreContext/index.ts +++ b/src/context/editorStoreContext/index.ts @@ -1,3 +1,2 @@ -export * from './hooks/usePCEditorStore'; export * from './PCEditorStoreContext'; export * from './PCEditorStoreProvider'; diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx index 140191685..8a33f9bea 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.tsx +++ b/src/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -5,8 +5,8 @@ import {Stop} from '@gravity-ui/icons'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; import {IframeContext} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './BigOverlay.scss'; diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.tsx b/src/editor-v2/components/BlockConfig/BlockConfig.tsx index cc2b93a28..852739b70 100644 --- a/src/editor-v2/components/BlockConfig/BlockConfig.tsx +++ b/src/editor-v2/components/BlockConfig/BlockConfig.tsx @@ -5,7 +5,7 @@ import _ from 'lodash'; import {DynamicFormValue} from '../../../common/types'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {generateChildrenPathFromArray} from '../../utils'; import DynamicForm from '../DynamicForm/DynamicForm'; diff --git a/src/editor-v2/components/BlocksList/BlocksList.tsx b/src/editor-v2/components/BlocksList/BlocksList.tsx index a6f6dcce5..e608b8c1f 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.tsx +++ b/src/editor-v2/components/BlocksList/BlocksList.tsx @@ -6,7 +6,7 @@ import _ from 'lodash'; import {ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './BlocksList.scss'; diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx index 3fb4e5500..57594bca3 100644 --- a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx +++ b/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {DynamicFormValue} from '../../../common/types'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import DynamicForm from '../DynamicForm/DynamicForm'; import './GlobalConfig.scss'; diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx index e0a1bac09..efa1499b6 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -5,8 +5,8 @@ import {Loader} from '@gravity-ui/uikit'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; import {IframeContext} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import Overlay from '../Overlay/Overlay'; import TopBar from '../TopBar/TopBar'; diff --git a/src/editor-v2/components/Overlay/Overlay.tsx b/src/editor-v2/components/Overlay/Overlay.tsx index f42078f90..b900ed4b8 100644 --- a/src/editor-v2/components/Overlay/Overlay.tsx +++ b/src/editor-v2/components/Overlay/Overlay.tsx @@ -6,7 +6,7 @@ import {Button, Icon} from '@gravity-ui/uikit'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './Overlay.scss'; diff --git a/src/editor-v2/components/Source/Source.tsx b/src/editor-v2/components/Source/Source.tsx index 5e56661de..9c3ed18f6 100644 --- a/src/editor-v2/components/Source/Source.tsx +++ b/src/editor-v2/components/Source/Source.tsx @@ -5,8 +5,8 @@ import {Button, Icon, TextInput} from '@gravity-ui/uikit'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; import {IframeContext} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './Source.scss'; diff --git a/src/editor-v2/components/SourceCode/SourceCode.tsx b/src/editor-v2/components/SourceCode/SourceCode.tsx index 94e84a2c3..42d357089 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.tsx +++ b/src/editor-v2/components/SourceCode/SourceCode.tsx @@ -14,7 +14,7 @@ import yaml from 'js-yaml'; import {ClassNameProps, PageContent} from '../../../models'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './SourceCode.scss'; diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.tsx b/src/editor-v2/components/StoreViewer/StoreViewer.tsx index c45f091e3..b0bd87d9d 100644 --- a/src/editor-v2/components/StoreViewer/StoreViewer.tsx +++ b/src/editor-v2/components/StoreViewer/StoreViewer.tsx @@ -4,9 +4,9 @@ import {Code} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import ReactJson from 'react-json-view'; +import {removeFn} from '../../../common/utils'; import {ClassNameProps} from '../../../models'; import {block} from '../../../utils'; -import {removeFn} from '../../../utils/store'; import './StoreViewer.scss'; diff --git a/src/editor-v2/components/Tree/Tree.tsx b/src/editor-v2/components/Tree/Tree.tsx index a8874e8c2..26b6d8506 100644 --- a/src/editor-v2/components/Tree/Tree.tsx +++ b/src/editor-v2/components/Tree/Tree.tsx @@ -5,7 +5,7 @@ import {Button, Card, Icon} from '@gravity-ui/uikit'; import {ItemConfig} from '../../../common/types'; import {block} from '../../../utils'; -import {useMainEditorStore} from '../../context/editorStore'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './Tree.scss'; diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index 85eb3c3bd..0ec1cf321 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -7,10 +7,10 @@ import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; import StoreViewer from '../../components/StoreViewer/StoreViewer'; -import {MainEditorStoreProvider, useMainEditorStore} from '../../context/editorStore'; +import {MainEditorStoreProvider} from '../../context/editorStore'; import {IframeProvider} from '../../context/iframeContext'; - -import useAdminInitialize from './hooks/useAdminInitialize'; +import useMainEditorInitialize from '../../hooks/useMainEditorInitialize'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import './Editor.scss'; @@ -26,7 +26,7 @@ const EditorView = (_props: EditorViewProps) => { const store = useMainEditorStore(); const {manipulateOverlayMode, disableMode} = store; - useAdminInitialize(); + useMainEditorInitialize(); // Disable insert mode on any MouseUp event // Maybe should be attached to body diff --git a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx b/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx deleted file mode 100644 index 88bb3e815..000000000 --- a/src/editor-v2/containers/Editor/hooks/useAdminInitialize.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import {usePostMessageAPIListener} from '../../../../common/postMessage'; -import {useMainEditorStore} from '../../../context/editorStore'; -import {usePostMessageEvents} from '../../../hooks/usePostMessageEvents'; - -const useAdminInitialize = () => { - const {requestPostMessage} = usePostMessageEvents(); - const { - initialize, - setConfig, - setContent, - manipulateOverlayMode, - disableMode, - insertBlock, - reorderBlock, - setSelectedBlock, - preInsertBlockType, - preReorderBlockPath, - } = useMainEditorStore(); - - usePostMessageAPIListener( - 'ON_INIT', - () => { - initialize(); - requestPostMessage('GET_SUPPORTED_BLOCKS', {}); - requestPostMessage('GET_INITIAL_CONTENT', {}); - }, - [requestPostMessage], - ); - - usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { - setContent(data); - }); - - usePostMessageAPIListener('ON_SUPPORTED_BLOCKS', (data) => { - setConfig(data); - }); - - usePostMessageAPIListener('ON_MOUSE_UP', ({path, position}) => { - if (manipulateOverlayMode === 'insert' && path && position && preInsertBlockType) { - insertBlock( - path, - preInsertBlockType, - ['left', 'top'].includes(position) ? 'prepend' : 'append', - ); - } - if (manipulateOverlayMode === 'reorder' && path && position && preReorderBlockPath) { - reorderBlock( - preReorderBlockPath, - path, - ['left', 'top'].includes(position) ? 'prepend' : 'append', - ); - setSelectedBlock(path); - } - disableMode(); - }); -}; - -export default useAdminInitialize; diff --git a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx index b14610b74..07a9eb77e 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx +++ b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {StoreApi} from 'zustand'; -import {EditorStore, createEditorStore} from '../../../common/store'; +import {EditorStore, createEditorStore} from '../../store'; export interface MainEditorStoreContextProps { state: StoreApi<EditorStore>; diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx index c91e56a4c..43bafff89 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -2,9 +2,10 @@ import React, {PropsWithChildren, useCallback, useContext, useEffect, useRef} fr import {StoreApi} from 'zustand'; -import {EditorState, EditorStore, createEditorStore} from '../../../common/store'; +import {EditorState} from '../../../common/store'; import {StoreSyncMessage} from '../../../common/types'; -import {removeFn} from '../../../utils/store'; +import {removeFn} from '../../../common/utils'; +import {EditorStore, createEditorStore} from '../../store'; import {IframeContext} from '../iframeContext'; import {MainEditorStoreContext} from './MainEditorStoreContext'; diff --git a/src/editor-v2/context/editorStore/index.ts b/src/editor-v2/context/editorStore/index.ts index ced0a0fcd..7804dc9a1 100644 --- a/src/editor-v2/context/editorStore/index.ts +++ b/src/editor-v2/context/editorStore/index.ts @@ -1,3 +1,2 @@ -export * from './hooks/useMainEditorStore'; export * from './MainEditorStoreContext'; export * from './MainEditorStoreProvider'; diff --git a/src/editor-v2/context/iframeContext/iframeContext.tsx b/src/editor-v2/context/iframeContext/IframeContext.tsx similarity index 100% rename from src/editor-v2/context/iframeContext/iframeContext.tsx rename to src/editor-v2/context/iframeContext/IframeContext.tsx diff --git a/src/editor-v2/context/iframeContext/iframeProvider.tsx b/src/editor-v2/context/iframeContext/IframeProvider.tsx similarity index 94% rename from src/editor-v2/context/iframeContext/iframeProvider.tsx rename to src/editor-v2/context/iframeContext/IframeProvider.tsx index f6261ac55..d6b3454c0 100644 --- a/src/editor-v2/context/iframeContext/iframeProvider.tsx +++ b/src/editor-v2/context/iframeContext/IframeProvider.tsx @@ -1,6 +1,6 @@ import React, {PropsWithChildren, useState} from 'react'; -import {IframeContext} from './iframeContext'; +import {IframeContext} from './IframeContext'; interface IframeProviderProps extends PropsWithChildren { initialUrl?: string; diff --git a/src/editor-v2/context/iframeContext/index.ts b/src/editor-v2/context/iframeContext/index.ts index 7da30b4e2..dc2795f0f 100644 --- a/src/editor-v2/context/iframeContext/index.ts +++ b/src/editor-v2/context/iframeContext/index.ts @@ -1,2 +1,2 @@ -export * from './iframeContext'; -export * from './iframeProvider'; +export * from './IframeContext'; +export * from './IframeProvider'; diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts new file mode 100644 index 000000000..b1ba59560 --- /dev/null +++ b/src/editor-v2/hooks/useMainEditorInitialize.ts @@ -0,0 +1,63 @@ +import {usePostMessageAPIListener} from '../../common/postMessage'; + +import {useMainEditorStore} from './useMainEditorStore'; +import {usePostMessageEvents} from './usePostMessageEvents'; + +const useMainEditorInitialize = () => { + const {requestPostMessage} = usePostMessageEvents(); + const { + initialize, + setConfig, + setContent, + manipulateOverlayMode, + disableMode, + insertBlock, + reorderBlock, + setSelectedBlock, + preInsertBlockType, + preReorderBlockPath, + } = useMainEditorStore(); + + usePostMessageAPIListener( + 'ON_INIT', + () => { + initialize(); + requestPostMessage('GET_SUPPORTED_BLOCKS', {}); + requestPostMessage('GET_INITIAL_CONTENT', {}); + }, + [requestPostMessage], + ); + + usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { + setContent(data); + }); + + usePostMessageAPIListener('ON_SUPPORTED_BLOCKS', (data) => { + setConfig(data); + }); + + usePostMessageAPIListener( + 'ON_MOUSE_UP', + ({path, position}) => { + if (manipulateOverlayMode === 'insert' && path && position && preInsertBlockType) { + insertBlock( + path, + preInsertBlockType, + ['left', 'top'].includes(position) ? 'prepend' : 'append', + ); + } + if (manipulateOverlayMode === 'reorder' && path && position && preReorderBlockPath) { + reorderBlock( + preReorderBlockPath, + path, + ['left', 'top'].includes(position) ? 'prepend' : 'append', + ); + setSelectedBlock(path); + } + disableMode(); + }, + [preInsertBlockType, preReorderBlockPath], + ); +}; + +export default useMainEditorInitialize; diff --git a/src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts b/src/editor-v2/hooks/useMainEditorStore.ts similarity index 75% rename from src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts rename to src/editor-v2/hooks/useMainEditorStore.ts index 7cd400005..49c3496a9 100644 --- a/src/editor-v2/context/editorStore/hooks/useMainEditorStore.ts +++ b/src/editor-v2/hooks/useMainEditorStore.ts @@ -2,7 +2,7 @@ import {useContext} from 'react'; import {useStore} from 'zustand'; -import {MainEditorStoreContext} from '../MainEditorStoreContext'; +import {MainEditorStoreContext} from '../context/editorStore'; export const useMainEditorStore = () => { const {state} = useContext(MainEditorStoreContext); diff --git a/src/editor-v2/store.ts b/src/editor-v2/store.ts new file mode 100644 index 000000000..a75e1262b --- /dev/null +++ b/src/editor-v2/store.ts @@ -0,0 +1,233 @@ +import _ from 'lodash'; + +import {EditorState, initialStore} from '../common/store'; +import {DynamicFormValue} from '../common/types'; +import {initializeStore} from '../common/utils'; +import {ConstructorBlock, PageContent} from '../models'; + +import {ZOOM_STEPS} from './constants'; +import { + duplicateArrayItem, + generateChildrenPathFromArray, + getDestinationShiftBeforeReorder, + insert, + isItemsNeighbours, + modifyObjectByPath, + removeFromArray, + reorderArrayItems, +} from './utils'; + +export interface EditorMethods { + initialize(): void; + setSelectedBlock(path: number[]): void; + setHeight(height: number): void; + setZoom(zoom: number): void; + increaseZoom(): void; + decreaseZoom(): void; + setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; + setContent(data: PageContent): void; + insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; + enableInsertMode(blockType: string): void; + enableReorderMode(path: number[]): void; + disableMode(): void; + updateField(path: string, value: DynamicFormValue): void; + deleteBlock(path: number[]): void; + duplicateBlock(path: number[]): void; + reorderBlock(path: number[], destination: number[], position?: 'prepend' | 'append'): void; + resetInitialize(): void; + resetBlocks(): void; +} + +export type EditorStore = EditorState & EditorMethods; + +export const createEditorStore = initializeStore<EditorState, EditorMethods>( + initialStore, + (set, get) => ({ + setHeight(height: number) { + // We have to add 200-500px, because of bottom padding or margin of last element + // which is not taken into calculation of final height + const newHeight = height + 500; + set((state) => ({...state, height: newHeight})); + }, + setZoom(zoom) { + if (zoom > 0) { + set((state) => ({...state, zoom})); + } + }, + increaseZoom() { + const currentZoom = get().zoom; + + for (const step of ZOOM_STEPS) { + if (currentZoom < step) { + get().setZoom(step); + break; + } + } + }, + decreaseZoom() { + const currentZoom = get().zoom; + const reverseSteps = ZOOM_STEPS.slice().reverse(); + + for (const step of reverseSteps) { + if (currentZoom > step) { + get().setZoom(step); + break; + } + } + }, + setConfig(data) { + set((state) => ({...state, ...data})); + }, + insertBlock: (arrayPath, blockType, position = 'append') => { + if (position === 'append') { + // TODO: fix + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign + arrayPath[arrayPath.length - 1] = arrayPath[arrayPath.length - 1] + 1; + } + + const blocksConfig = get().content.blocks; + const blocksData = get().blocks; + + const foundBlock = blocksData.find(({type}) => type === blockType); + const defaultValue = + foundBlock && foundBlock.schema.default + ? {...foundBlock.schema.default, type: blockType} + : {type: blockType}; + + const newBlocksConfig = modifyObjectByPath( + blocksConfig, + arrayPath, + (parentBlocks, index) => + insert(parentBlocks, index, defaultValue as ConstructorBlock), + ); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + enableInsertMode(blockType: string) { + set((state) => ({ + ...state, + manipulateOverlayMode: 'insert', + preInsertBlockType: blockType, + })); + }, + disableMode() { + set((state) => ({ + ...state, + manipulateOverlayMode: false, + preInsertBlockType: undefined, + preReorderBlockPath: undefined, + })); + }, + enableReorderMode(path) { + set((state) => ({ + ...state, + manipulateOverlayMode: 'reorder', + preReorderBlockPath: path, + })); + }, + setContent(content) { + set((state) => ({ + ...state, + content: content, + })); + }, + initialize() { + set((state) => ({ + ...state, + initialized: true, + })); + }, + setSelectedBlock(path) { + set((state) => ({ + ...state, + selectedBlock: path, + })); + }, + updateField(path, value) { + set((state) => { + const newConfig = _.set(state.content, path, value); + return { + ...state, + content: newConfig, + }; + }); + }, + deleteBlock: (arrayPath) => { + const blocksConfig = get().content.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + duplicateBlock: (arrayPath) => { + const blocksConfig = get().content.blocks; + + const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem); + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + reorderBlock: (arrayPath, destination, position = 'append') => { + if (position === 'append') { + // TODO: fix + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign + destination[destination.length - 1] = destination[destination.length - 1] + 1; + } + + let newBlocksConfig: ConstructorBlock[]; + const blocksConfig = get().content.blocks; + // Copy + const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath)); + + if (isItemsNeighbours(arrayPath, destination)) { + newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => { + return reorderArrayItems( + parentBlocks, + arrayPath[arrayPath.length - 1], + destination[destination.length - 1], + ); + }); + } else { + const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); + + // Delete + const blocksConfigWithoutBlock = modifyObjectByPath( + blocksConfig, + arrayPath, + removeFromArray, + ); + // Paste + newBlocksConfig = modifyObjectByPath( + blocksConfigWithoutBlock, + arrayDest, + (parentBlocks, index) => insert(parentBlocks, index, copiedBlock), + ); + } + + set((state) => ({ + ...state, + content: {...state.content, blocks: newBlocksConfig}, + })); + }, + resetInitialize: () => { + set((state) => ({ + ...state, + initialized: false, + })); + }, + resetBlocks: () => { + set((state) => ({ + ...state, + content: {...state.content, blocks: []}, + })); + }, + }), +); diff --git a/src/editor/data/index.ts b/src/editor/data/index.ts index 690274125..ff14d8594 100644 --- a/src/editor/data/index.ts +++ b/src/editor/data/index.ts @@ -2,6 +2,7 @@ import {Block, BlockType} from '../../models'; import {formatBlockName} from '../utils'; import DefaultPreview from './previews/default-preview'; +import HeaderBlock from './previews/header-block'; export type PreviewComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>; @@ -14,12 +15,16 @@ export interface EdiorBlockData { }; } -const getBlockTemplate = (blockType: BlockType) => - require(`./templates/${blockType}.json`) as Omit<EdiorBlockData, 'preview'>; +const getBlockTemplate = (blockType: BlockType): Promise<Omit<EdiorBlockData, 'preview'>> => + import(`./templates/${blockType}.json`).then((data) => data.default); -const getBlockPreview = (blockType: BlockType) => { +const getBlockPreview = (blockType: BlockType): PreviewComponent => { try { - return require(`./previews/${blockType}.tsx`).default as PreviewComponent; + if (blockType === BlockType.HeaderBlock) { + return HeaderBlock; + } + + return DefaultPreview; } catch (err) { /*eslint-disable no-console */ console.warn(`Preview image for ${blockType} not found`); @@ -27,20 +32,26 @@ const getBlockPreview = (blockType: BlockType) => { } }; -const EditorBlocksData = Object.values(BlockType).reduce((previewData, blockType) => { - const template = getBlockTemplate(blockType); - const preview = getBlockPreview(blockType); +type EditorBlocksData = Partial<Record<BlockType, EdiorBlockData>>; + +async function getEditorBlocksData(): Promise<EditorBlocksData> { + const EdiorBlockData: EditorBlocksData = {}; + + for (const blockType of Object.values(BlockType)) { + const template = await getBlockTemplate(blockType as BlockType); - template.meta = template.meta || {}; - template.meta.title = template.meta.title || formatBlockName(blockType); + const preview = getBlockPreview(blockType); - /* eslint-disable no-param-reassign */ - previewData[blockType] = { - ...template, - preview, - } as EdiorBlockData; + template.meta = template.meta || {}; + template.meta.title = template.meta.title || formatBlockName(blockType); - return previewData; -}, {} as Record<BlockType, EdiorBlockData>); + EdiorBlockData[blockType] = { + ...template, + preview, + }; + } + + return EdiorBlockData; +} -export default EditorBlocksData; +export {EditorBlocksData, getEditorBlocksData}; diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/hooks/usePCEditorBlockMouseEvents.ts similarity index 86% rename from src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx rename to src/hooks/usePCEditorBlockMouseEvents.ts index b33985716..d908f584d 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -2,11 +2,12 @@ import React, {useCallback, useEffect} from 'react'; import _ from 'lodash'; -import {usePCEditorStore} from '../../../../../context/editorStoreContext'; -import {sendEventPostMessage} from '../../../../../hooks/usePostMessageAPI'; -import {getCursorPositionOverElement} from '../../../../../utils/editor'; +import {getCursorPositionOverElement} from '../utils/editor'; -const useEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { +import {usePCEditorStore} from './usePCEditorStore'; +import {sendEventPostMessage} from './usePostMessageAPI'; + +const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { const {selectedBlock} = usePCEditorStore(); const onMouseUp = useCallback( @@ -84,4 +85,4 @@ const useEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) }; }; -export default useEditorBlockMouseEvents; +export default usePCEditorBlockMouseEvents; diff --git a/src/hooks/useEditorInitialize.ts b/src/hooks/usePCEditorInitializeEvents.ts similarity index 96% rename from src/hooks/useEditorInitialize.ts rename to src/hooks/usePCEditorInitializeEvents.ts index f47e4dde1..fa4a61b81 100644 --- a/src/hooks/useEditorInitialize.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -5,11 +5,11 @@ import _ from 'lodash'; import {ItemConfig} from '../common/types'; import {blockDataMap} from '../constructor-items'; -import {usePCEditorStore} from '../context/editorStoreContext'; import {PageContent} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; import {generateFromAJV} from '../utils/form-generator'; +import {usePCEditorStore} from './usePCEditorStore'; import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; interface UseEditorInitializeProps { @@ -17,7 +17,7 @@ interface UseEditorInitializeProps { setContent: (content: PageContent) => void; } -export const useInitializeEditorEvents = ({ +export const usePCEditorInitializeEvents = ({ initialContent, setContent, }: UseEditorInitializeProps) => { diff --git a/src/context/editorStoreContext/hooks/usePCEditorStore.ts b/src/hooks/usePCEditorStore.ts similarity index 74% rename from src/context/editorStoreContext/hooks/usePCEditorStore.ts rename to src/hooks/usePCEditorStore.ts index 31e9226aa..3c9cdf851 100644 --- a/src/context/editorStoreContext/hooks/usePCEditorStore.ts +++ b/src/hooks/usePCEditorStore.ts @@ -2,7 +2,7 @@ import {useContext} from 'react'; import {useStore} from 'zustand'; -import {PCEditorStoreContext} from '../PCEditorStoreContext'; +import {PCEditorStoreContext} from '../context/editorStoreContext'; export const usePCEditorStore = () => { const {state} = useContext(PCEditorStoreContext); From 68d982d4c7dbf517300bc29366a009face590743 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 4 Mar 2025 18:23:56 +0300 Subject: [PATCH 09/84] refactor: distinct editor v2, add components config --- {src/common => common}/postMessage.ts | 1 - {src/common => common}/store.ts | 2 +- {src/common => common}/types/actions.ts | 0 {src/common => common}/types/common.ts | 0 {src/common => common}/types/forms.ts | 0 {src/common => common}/types/index.ts | 0 {src/common => common}/types/messages.ts | 0 {src/common => common}/utils.ts | 0 .../components/BigOverlay/BigOverlay.scss | 4 +- .../components/BigOverlay/BigOverlay.tsx | 12 +- .../components/DynamicForm/DynamicForm.scss | 8 ++ .../components/DynamicForm/DynamicForm.tsx | 18 ++- .../DynamicForm/FieldBase/FieldBase.scss | 2 - .../DynamicForm/FieldBase/FieldBase.tsx | 22 ++-- .../DynamicForm/Fields/Array/Array.scss | 4 +- .../DynamicForm/Fields/Array/Array.tsx | 19 ++- .../Fields/Array/ItemButton/ItemButton.tsx | 28 +++-- .../DynamicForm/Fields/Boolean/Boolean.tsx | 15 +-- .../DynamicForm/Fields/Number/Number.tsx | 15 +-- .../DynamicForm/Fields/Object/Object.scss | 6 +- .../DynamicForm/Fields/Object/Object.tsx | 22 ++-- .../DynamicForm/Fields/OneOf/OneOf.scss | 4 +- .../DynamicForm/Fields/OneOf/OneOf.tsx | 21 ++-- .../DynamicForm/Fields/Select/Select.tsx | 15 +-- .../DynamicForm/Fields/Text/Text.tsx | 15 +-- .../DynamicForm/Fields/TextArea/TextArea.tsx | 15 +-- .../components/DynamicForm/utils.ts | 2 +- .../components/MiddleScreen/MiddleScreen.scss | 9 +- .../components/MiddleScreen/MiddleScreen.tsx | 31 +++-- .../components/Overlay/Overlay.scss | 2 - .../components/Overlay/Overlay.tsx | 15 ++- .../components/Panels/Panels.scss | 12 +- .../components/Panels/Panels.tsx | 10 +- editor-v2/components/Sidebar/Sidebar.scss | 18 +++ editor-v2/components/Sidebar/Sidebar.tsx | 28 +++++ .../components/StoreViewer/StoreViewer.scss | 4 +- .../components/StoreViewer/StoreViewer.tsx | 13 +-- editor-v2/components/Tabs/Tabs.scss | 35 ++++++ editor-v2/components/Tabs/Tabs.tsx | 72 ++++++++++++ {src/editor-v2 => editor-v2}/constants.ts | 0 .../containers}/BlockConfig/BlockConfig.scss | 5 - .../containers}/BlockConfig/BlockConfig.tsx | 16 +-- .../containers}/BlocksList/BlocksList.scss | 2 - .../containers}/BlocksList/BlocksList.tsx | 9 +- .../containers/Editor/Editor.scss | 2 - .../containers/Editor/Editor.tsx | 36 ++++-- .../GlobalConfig/GlobalConfig.scss | 2 - .../containers}/GlobalConfig/GlobalConfig.tsx | 13 ++- .../containers}/Source/Source.scss | 12 +- .../containers}/Source/Source.tsx | 16 +-- .../containers}/SourceCode/SourceCode.scss | 2 - .../containers}/SourceCode/SourceCode.tsx | 15 +-- .../containers}/Tree/Tree.scss | 2 - .../containers}/Tree/Tree.tsx | 7 +- .../ViewSwitches/ViewSwitches.scss | 4 +- .../containers}/ViewSwitches/ViewSwitches.tsx | 34 ++---- .../containers/__stories__/Editor.stories.tsx | 3 +- .../containers/__stories__/utils.ts | 0 .../editorStore/MainEditorStoreContext.tsx | 1 - .../editorStore/MainEditorStoreProvider.tsx | 1 - .../context/editorStore/index.ts | 0 .../context/iframeContext/IframeContext.tsx | 0 .../context/iframeContext/IframeProvider.tsx | 7 +- .../context/iframeContext/index.ts | 0 editor-v2/hooks/useEditorTabs.tsx | 64 ++++++++++ .../hooks/useMainEditorInitialize.ts | 0 .../hooks/useMainEditorStore.ts | 1 - .../hooks/usePostMessageEvents.ts | 0 {src/editor-v2 => editor-v2}/index.ts | 0 {src/editor-v2 => editor-v2}/store.ts | 2 +- .../styles/mixins.scss | 1 + editor-v2/styles/root.scss | 2 + .../styles/variables.scss | 1 + editor-v2/utils/cn.ts | 5 + {src/editor-v2 => editor-v2}/utils/code.ts | 2 +- {src/editor-v2 => editor-v2}/utils/index.ts | 2 +- editor-v2/utils/store.ts | 0 playground/src/app/page.scss | 4 + playground/src/app/page.tsx | 23 +++- playground/src/app/pc-2/navigation.json | 48 ++++++++ playground/src/app/pc-2/page.tsx | 3 +- src/blocks/CardLayout/index.ts | 2 +- src/blocks/Header/dynamic-form.ts | 2 +- src/blocks/Slider/dynamic-form.ts | 2 +- src/blocks/TestEditorBlock/form.ts | 2 +- src/components/Image/dynamic-form.ts | 2 +- .../PCEditorStoreContext.tsx | 2 +- .../PCEditorStoreProvider.tsx | 4 +- .../DynamicForm/Fields/Object/Object.scss | 10 -- src/editor-v2/components/Sidebar/Sidebar.scss | 86 -------------- src/editor-v2/components/Sidebar/Sidebar.tsx | 110 ------------------ src/editor-v2/components/TopBar/TopBar.scss | 34 ------ src/editor-v2/components/TopBar/TopBar.tsx | 41 ------- src/editor-v2/icons/Tablet.tsx | 23 ---- src/editor-v2/styles/root.scss | 9 -- src/editor-v2/utils/store.ts | 24 ---- src/hooks/usePCEditorInitializeEvents.ts | 2 +- src/hooks/usePostMessageAPI.ts | 4 +- src/index.ts | 1 - 99 files changed, 570 insertions(+), 636 deletions(-) rename {src/common => common}/postMessage.ts (94%) rename {src/common => common}/store.ts (95%) rename {src/common => common}/types/actions.ts (100%) rename {src/common => common}/types/common.ts (100%) rename {src/common => common}/types/forms.ts (100%) rename {src/common => common}/types/index.ts (100%) rename {src/common => common}/types/messages.ts (100%) rename {src/common => common}/utils.ts (100%) rename {src/editor-v2 => editor-v2}/components/BigOverlay/BigOverlay.scss (89%) rename {src/editor-v2 => editor-v2}/components/BigOverlay/BigOverlay.tsx (91%) create mode 100644 editor-v2/components/DynamicForm/DynamicForm.scss rename {src/editor-v2 => editor-v2}/components/DynamicForm/DynamicForm.tsx (95%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/FieldBase/FieldBase.scss (92%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/FieldBase/FieldBase.tsx (85%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Array/Array.scss (88%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Array/Array.tsx (94%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx (82%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Boolean/Boolean.tsx (57%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Number/Number.tsx (61%) rename src/editor-v2/components/DynamicForm/DynamicForm.scss => editor-v2/components/DynamicForm/Fields/Object/Object.scss (55%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Object/Object.tsx (69%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/OneOf/OneOf.scss (59%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/OneOf/OneOf.tsx (86%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Select/Select.tsx (75%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/Text/Text.tsx (56%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/Fields/TextArea/TextArea.tsx (57%) rename {src/editor-v2 => editor-v2}/components/DynamicForm/utils.ts (89%) rename {src/editor-v2 => editor-v2}/components/MiddleScreen/MiddleScreen.scss (87%) rename {src/editor-v2 => editor-v2}/components/MiddleScreen/MiddleScreen.tsx (73%) rename {src/editor-v2 => editor-v2}/components/Overlay/Overlay.scss (94%) rename {src/editor-v2 => editor-v2}/components/Overlay/Overlay.tsx (95%) rename {src/editor-v2 => editor-v2}/components/Panels/Panels.scss (61%) rename {src/editor-v2 => editor-v2}/components/Panels/Panels.tsx (94%) create mode 100644 editor-v2/components/Sidebar/Sidebar.scss create mode 100644 editor-v2/components/Sidebar/Sidebar.tsx rename {src/editor-v2 => editor-v2}/components/StoreViewer/StoreViewer.scss (88%) rename {src/editor-v2 => editor-v2}/components/StoreViewer/StoreViewer.tsx (78%) create mode 100644 editor-v2/components/Tabs/Tabs.scss create mode 100644 editor-v2/components/Tabs/Tabs.tsx rename {src/editor-v2 => editor-v2}/constants.ts (100%) rename {src/editor-v2/components => editor-v2/containers}/BlockConfig/BlockConfig.scss (73%) rename {src/editor-v2/components => editor-v2/containers}/BlockConfig/BlockConfig.tsx (82%) rename {src/editor-v2/components => editor-v2/containers}/BlocksList/BlocksList.scss (93%) rename {src/editor-v2/components => editor-v2/containers}/BlocksList/BlocksList.tsx (92%) rename {src/editor-v2 => editor-v2}/containers/Editor/Editor.scss (91%) rename {src/editor-v2 => editor-v2}/containers/Editor/Editor.tsx (64%) rename {src/editor-v2/components => editor-v2/containers}/GlobalConfig/GlobalConfig.scss (76%) rename {src/editor-v2/components => editor-v2/containers}/GlobalConfig/GlobalConfig.tsx (67%) rename {src/editor-v2/components => editor-v2/containers}/Source/Source.scss (63%) rename {src/editor-v2/components => editor-v2/containers}/Source/Source.tsx (76%) rename {src/editor-v2/components => editor-v2/containers}/SourceCode/SourceCode.scss (88%) rename {src/editor-v2/components => editor-v2/containers}/SourceCode/SourceCode.tsx (93%) rename {src/editor-v2/components => editor-v2/containers}/Tree/Tree.scss (92%) rename {src/editor-v2/components => editor-v2/containers}/Tree/Tree.tsx (96%) rename {src/editor-v2/components => editor-v2/containers}/ViewSwitches/ViewSwitches.scss (77%) rename {src/editor-v2/components => editor-v2/containers}/ViewSwitches/ViewSwitches.tsx (52%) rename {src/editor-v2 => editor-v2}/containers/__stories__/Editor.stories.tsx (99%) rename {src/editor-v2 => editor-v2}/containers/__stories__/utils.ts (100%) rename {src/editor-v2 => editor-v2}/context/editorStore/MainEditorStoreContext.tsx (99%) rename {src/editor-v2 => editor-v2}/context/editorStore/MainEditorStoreProvider.tsx (99%) rename {src/editor-v2 => editor-v2}/context/editorStore/index.ts (100%) rename {src/editor-v2 => editor-v2}/context/iframeContext/IframeContext.tsx (100%) rename {src/editor-v2 => editor-v2}/context/iframeContext/IframeProvider.tsx (85%) rename {src/editor-v2 => editor-v2}/context/iframeContext/index.ts (100%) create mode 100644 editor-v2/hooks/useEditorTabs.tsx rename {src/editor-v2 => editor-v2}/hooks/useMainEditorInitialize.ts (100%) rename {src/editor-v2 => editor-v2}/hooks/useMainEditorStore.ts (99%) rename {src/editor-v2 => editor-v2}/hooks/usePostMessageEvents.ts (100%) rename {src/editor-v2 => editor-v2}/index.ts (100%) rename {src/editor-v2 => editor-v2}/store.ts (99%) rename {src/editor-v2 => editor-v2}/styles/mixins.scss (83%) create mode 100644 editor-v2/styles/root.scss rename {src/editor-v2 => editor-v2}/styles/variables.scss (90%) create mode 100644 editor-v2/utils/cn.ts rename {src/editor-v2 => editor-v2}/utils/code.ts (83%) rename {src/editor-v2 => editor-v2}/utils/index.ts (98%) create mode 100644 editor-v2/utils/store.ts create mode 100644 playground/src/app/pc-2/navigation.json delete mode 100644 src/editor-v2/components/DynamicForm/Fields/Object/Object.scss delete mode 100644 src/editor-v2/components/Sidebar/Sidebar.scss delete mode 100644 src/editor-v2/components/Sidebar/Sidebar.tsx delete mode 100644 src/editor-v2/components/TopBar/TopBar.scss delete mode 100644 src/editor-v2/components/TopBar/TopBar.tsx delete mode 100644 src/editor-v2/icons/Tablet.tsx delete mode 100644 src/editor-v2/styles/root.scss delete mode 100644 src/editor-v2/utils/store.ts diff --git a/src/common/postMessage.ts b/common/postMessage.ts similarity index 94% rename from src/common/postMessage.ts rename to common/postMessage.ts index 65481b329..d96c84b76 100644 --- a/src/common/postMessage.ts +++ b/common/postMessage.ts @@ -39,6 +39,5 @@ export function usePostMessageAPIListener<K extends keyof EventMessageTypes>( ) { useEffect(() => { return listenPostMessageEvents(action, callback); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [...deps]); } diff --git a/src/common/store.ts b/common/store.ts similarity index 95% rename from src/common/store.ts rename to common/store.ts index 41db0f670..a01179922 100644 --- a/src/common/store.ts +++ b/common/store.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {PageContent} from '../models'; +import {PageContent} from '../src/models'; import {ConfigInput, ItemConfig} from './types'; import {initializeStore} from './utils'; diff --git a/src/common/types/actions.ts b/common/types/actions.ts similarity index 100% rename from src/common/types/actions.ts rename to common/types/actions.ts diff --git a/src/common/types/common.ts b/common/types/common.ts similarity index 100% rename from src/common/types/common.ts rename to common/types/common.ts diff --git a/src/common/types/forms.ts b/common/types/forms.ts similarity index 100% rename from src/common/types/forms.ts rename to common/types/forms.ts diff --git a/src/common/types/index.ts b/common/types/index.ts similarity index 100% rename from src/common/types/index.ts rename to common/types/index.ts diff --git a/src/common/types/messages.ts b/common/types/messages.ts similarity index 100% rename from src/common/types/messages.ts rename to common/types/messages.ts diff --git a/src/common/utils.ts b/common/utils.ts similarity index 100% rename from src/common/utils.ts rename to common/utils.ts diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.scss b/editor-v2/components/BigOverlay/BigOverlay.scss similarity index 89% rename from src/editor-v2/components/BigOverlay/BigOverlay.scss rename to editor-v2/components/BigOverlay/BigOverlay.scss index b1f87ba97..1ae968b87 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.scss +++ b/editor-v2/components/BigOverlay/BigOverlay.scss @@ -1,5 +1,5 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; +@import '../../../styles/variables.scss'; +@import '../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/editor-v2/components/BigOverlay/BigOverlay.tsx similarity index 91% rename from src/editor-v2/components/BigOverlay/BigOverlay.tsx rename to editor-v2/components/BigOverlay/BigOverlay.tsx index 8a33f9bea..e88d78676 100644 --- a/src/editor-v2/components/BigOverlay/BigOverlay.tsx +++ b/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -1,20 +1,16 @@ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; - import {Stop} from '@gravity-ui/icons'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './BigOverlay.scss'; -const b = block('big-overlay'); - -interface BigOverlayProps extends ClassNameProps {} +const b = editorCn('big-overlay'); -const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { +const BigOverlay = ({className}: {className?: string}) => { const {zoom, manipulateOverlayMode} = useMainEditorStore(); const {iframeElement} = useContext(IframeContext); const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( diff --git a/editor-v2/components/DynamicForm/DynamicForm.scss b/editor-v2/components/DynamicForm/DynamicForm.scss new file mode 100644 index 000000000..d7bc2c688 --- /dev/null +++ b/editor-v2/components/DynamicForm/DynamicForm.scss @@ -0,0 +1,8 @@ +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}dynamic-form'; + +#{$block} { + //padding-left: 16px; +} diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.tsx b/editor-v2/components/DynamicForm/DynamicForm.tsx similarity index 95% rename from src/editor-v2/components/DynamicForm/DynamicForm.tsx rename to editor-v2/components/DynamicForm/DynamicForm.tsx index 722aa8411..2051840ed 100644 --- a/src/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -1,11 +1,11 @@ -import React, {useCallback} from 'react'; - import _ from 'lodash'; +import React, {useCallback} from 'react'; import {ConfigInput, DynamicFormValue} from '../../../common/types'; -import {ClassNameProps, PageContent} from '../../../models'; -import {block} from '../../../utils'; +import {PageContent} from '../../../src/models'; +import {editorCn} from '../../utils/cn'; +import './DynamicForm.scss'; import ArrayDynamicField from './Fields/Array/Array'; import BooleanDynamicField from './Fields/Boolean/Boolean'; import NumberDynamicField from './Fields/Number/Number'; @@ -16,18 +16,16 @@ import TextDynamicField from './Fields/Text/Text'; import TextAreaDynamicField from './Fields/TextArea/TextArea'; import {getContent, getFullPath} from './utils'; -import './DynamicForm.scss'; - -const b = block('dynamic-form'); +const b = editorCn('dynamic-form'); -interface DynamicFormProps extends ClassNameProps { +interface DynamicFormProps { blockConfig: Array<ConfigInput>; contentConfig: PageContent; onUpdate: (key: string, value: DynamicFormValue) => void; + className?: string; } -const DynamicForm: React.FC<DynamicFormProps> = (props) => { - const {blockConfig, onUpdate, contentConfig} = props; +const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) => { const inputs = blockConfig; const getData = useCallback( diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss similarity index 92% rename from src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss rename to editor-v2/components/DynamicForm/FieldBase/FieldBase.scss index c73969996..b029dd1fa 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss +++ b/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -1,5 +1,3 @@ -@import '../../../../../styles/variables.scss'; -@import '../../../../../styles/mixins.scss'; @import '../../../styles/root.scss'; @import '../../../styles/variables.scss'; @import '../../../styles/mixins.scss'; diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx similarity index 85% rename from src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx rename to editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx index ce1bf2587..2def6c0d0 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx +++ b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -1,15 +1,13 @@ -import React, {PropsWithChildren, useState} from 'react'; - import {ArrowRotateLeft} from '@gravity-ui/icons'; import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit'; import _ from 'lodash'; +import React, {PropsWithChildren, useState} from 'react'; -import {ClassNameProps} from '../../../../models'; -import {block} from '../../../../utils'; +import {editorCn} from '../../../utils/cn'; import './FieldBase.scss'; -const b = block('field-base'); +const b = editorCn('field-base'); export interface FieldBaseParams { title?: string; @@ -18,10 +16,18 @@ export interface FieldBaseParams { expandable?: boolean; } -export interface FieldBaseProps extends ClassNameProps, PropsWithChildren, FieldBaseParams {} +export interface FieldBaseProps extends PropsWithChildren, FieldBaseParams { + className?: string; +} -const FieldBase: React.FC<FieldBaseProps> = (props) => { - const {className, title, textSize, children, onRefresh, expandable = false} = props; +const FieldBase: React.FC<FieldBaseProps> = ({ + className, + title, + textSize, + children, + onRefresh, + expandable = false, +}) => { const [showChildren, setShowChildren] = useState(!expandable); const titleComponent = React.useMemo(() => { diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss b/editor-v2/components/DynamicForm/Fields/Array/Array.scss similarity index 88% rename from src/editor-v2/components/DynamicForm/Fields/Array/Array.scss rename to editor-v2/components/DynamicForm/Fields/Array/Array.scss index 973665582..8a060c7cc 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss +++ b/editor-v2/components/DynamicForm/Fields/Array/Array.scss @@ -1,5 +1,5 @@ -@import '../../../../../../styles/variables.scss'; -@import '../../../../../../styles/mixins.scss'; +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; $block: '.#{$ns}array-dynamic-field'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx similarity index 94% rename from src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx rename to editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 185de27d5..3342ab369 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -1,34 +1,31 @@ -import React, {useCallback} from 'react'; - import {Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; +import React, {useCallback} from 'react'; import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; -import {ClassNameProps, PageContent} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {PageContent} from '../../../../../src/models'; import {removeFromArray, swapArrayItems} from '../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; import Text from '../Text/Text'; -import ItemButton from './ItemButton/ItemButton'; - import './Array.scss'; +import ItemButton from './ItemButton/ItemButton'; -const b = block('array-dynamic-field'); +const b = editorCn('array-dynamic-field'); type ArrayInput = ArrayTextInput | ArrayObjectInput; -interface ArrayFieldProps extends ClassNameProps { +interface ArrayFieldProps { title: string; values: Array<DynamicFormValue>; onUpdate: (key: string, value: DynamicFormValue) => void; blockConfig: ArrayInput; + className?: string; } -const ArrayDynamicField: React.FC<ArrayFieldProps> = (props) => { - const {title, values, onUpdate, className, blockConfig} = props; - +const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: ArrayFieldProps) => { const haveItems = values && Array.isArray(values) && values.length; const onAddItem = useCallback(() => { diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx b/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx similarity index 82% rename from src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx rename to editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx index d566f49e5..05128a5b0 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx +++ b/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx @@ -1,30 +1,28 @@ -import React, {Fragment, useCallback, useRef, useState} from 'react'; - import {ArrowDown, ArrowUp, EllipsisVertical, TrashBin} from '@gravity-ui/icons'; import {Button, Icon, Menu, Popup} from '@gravity-ui/uikit'; +import React, {Fragment, useCallback, useRef, useState} from 'react'; -import {ClassNameProps} from '../../../../../../models'; -import {block} from '../../../../../../utils'; +import {editorCn} from '../../../../../utils/cn'; -const b = block('array-item-button'); +const b = editorCn('array-item-button'); -interface ItemButtonProps extends ClassNameProps { +interface ItemButtonProps { onRemove: () => void; onReorderUp: () => void; disableReorderUp?: boolean; onReorderDown: () => void; disableReorderDown?: boolean; + className?: string; } -const ItemButton: React.FC<ItemButtonProps> = (props) => { - const { - className, - onRemove, - onReorderUp, - onReorderDown, - disableReorderUp = false, - disableReorderDown = false, - } = props; +const ItemButton = ({ + className, + onRemove, + onReorderUp, + onReorderDown, + disableReorderUp = false, + disableReorderDown = false, +}: ItemButtonProps) => { const buttonRef = useRef(null); const [isOpen, setIsOpen] = useState(false); diff --git a/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx similarity index 57% rename from src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx rename to editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx index 2ac37c4ef..1ce0f5af2 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx +++ b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx @@ -1,21 +1,18 @@ -import React from 'react'; - import {Switch} from '@gravity-ui/uikit'; +import React from 'react'; -import {ClassNameProps} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = block('boolean-dynamic-field'); +const b = editorCn('boolean-dynamic-field'); -interface BooleanProps extends ClassNameProps, FieldBaseParams { +interface BooleanProps extends FieldBaseParams { value: string; onUpdate: (value: boolean | undefined) => void; + className?: string; } -const BooleanDynamicField: React.FC<BooleanProps> = (props) => { - const {title, value, onUpdate, className} = props; - +const BooleanDynamicField = ({title, value, onUpdate, className}: BooleanProps) => { return ( <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> <Switch checked={Boolean(value)} onUpdate={onUpdate} /> diff --git a/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx b/editor-v2/components/DynamicForm/Fields/Number/Number.tsx similarity index 61% rename from src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx rename to editor-v2/components/DynamicForm/Fields/Number/Number.tsx index 2b01e6798..659ea5c08 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx +++ b/editor-v2/components/DynamicForm/Fields/Number/Number.tsx @@ -1,21 +1,18 @@ -import React from 'react'; - import {TextInput} from '@gravity-ui/uikit'; +import React from 'react'; -import {ClassNameProps} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = block('number-dynamic-field'); +const b = editorCn('number-dynamic-field'); -interface NumberDynamicFieldProps extends ClassNameProps, FieldBaseParams { +interface NumberDynamicFieldProps extends FieldBaseParams { value: string; onUpdate: (value: number | undefined) => void; + className?: string; } -const NumberDynamicField: React.FC<NumberDynamicFieldProps> = (props) => { - const {title, value, onUpdate, className} = props; - +const NumberDynamicField = ({title, value, onUpdate, className}: NumberDynamicFieldProps) => { const onUpdateFunc = (updateValue: string) => { onUpdate(Number(updateValue)); }; diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.scss b/editor-v2/components/DynamicForm/Fields/Object/Object.scss similarity index 55% rename from src/editor-v2/components/DynamicForm/DynamicForm.scss rename to editor-v2/components/DynamicForm/Fields/Object/Object.scss index 4628d70ab..7092b9aff 100644 --- a/src/editor-v2/components/DynamicForm/DynamicForm.scss +++ b/editor-v2/components/DynamicForm/Fields/Object/Object.scss @@ -1,8 +1,10 @@ @import '../../../../styles/variables.scss'; @import '../../../../styles/mixins.scss'; -$block: '.#{$ns}dynamic-form'; +$block: '.#{$ns}object-dynamic-field'; #{$block} { - //padding-left: 16px; + &__card { + padding: 12px; + } } diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx similarity index 69% rename from src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx rename to editor-v2/components/DynamicForm/Fields/Object/Object.tsx index 1d012eb2d..b8b67f780 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -1,26 +1,30 @@ -import React from 'react'; - import {Card} from '@gravity-ui/uikit'; +import React from 'react'; import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; -import {ClassNameProps, PageContent} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {PageContent} from '../../../../../src/models'; +import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; import './Object.scss'; -const b = block('object-dynamic-field'); +const b = editorCn('object-dynamic-field'); -interface ObjectDynamicFieldProps extends ClassNameProps, FieldBaseParams { +interface ObjectDynamicFieldProps extends FieldBaseParams { value: PageContent; onUpdate: (key: string, value: DynamicFormValue) => void; blockConfig: Array<ConfigInput>; + className?: string; } -const ObjectDynamicField: React.FC<ObjectDynamicFieldProps> = (props) => { - const {title, value, onUpdate, className, blockConfig} = props; - +const ObjectDynamicField = ({ + title, + value, + onUpdate, + className, + blockConfig, +}: ObjectDynamicFieldProps) => { return ( <FieldBase title={title} diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss similarity index 59% rename from src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss rename to editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss index 1e5ec1c1a..b7660714b 100644 --- a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss @@ -1,5 +1,5 @@ -@import '../../../../../../styles/variables.scss'; -@import '../../../../../../styles/mixins.scss'; +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; $block: '.#{$ns}oneof-dynamic-field'; diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx similarity index 86% rename from src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx rename to editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 5db8644b9..eda4874f6 100644 --- a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -1,21 +1,21 @@ -import React, {useCallback, useMemo, useState} from 'react'; - import {Card, RadioButton} from '@gravity-ui/uikit'; +import React, {useCallback, useMemo, useState} from 'react'; import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; -import {ClassNameProps, PageContent} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {PageContent} from '../../../../../src/models'; +import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; import './OneOf.scss'; -const b = block('oneof-dynamic-field'); +const b = editorCn('oneof-dynamic-field'); -interface OneOfDynamicFieldProps extends ClassNameProps { +interface OneOfDynamicFieldProps { contentConfig: PageContent; onUpdate: (key: string, value: DynamicFormValue) => void; inputConfig: OneOfInput; + className?: string; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -26,9 +26,12 @@ const getOneOfContentConfig = (contentConfig: any, name: string) => { return contentConfig; }; -const OneOfDynamicField: React.FC<OneOfDynamicFieldProps> = (props) => { - const {contentConfig, onUpdate, className, inputConfig} = props; - +const OneOfDynamicField = ({ + contentConfig, + onUpdate, + className, + inputConfig, +}: OneOfDynamicFieldProps) => { const defaultValue = inputConfig.options[0].value; const [oneOfMetaValue, setOneOfMetaValue] = useState(defaultValue); diff --git a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx similarity index 75% rename from src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx rename to editor-v2/components/DynamicForm/Fields/Select/Select.tsx index 06eff057a..ae91fca9d 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -1,25 +1,22 @@ -import React from 'react'; - import {RadioButton, Select} from '@gravity-ui/uikit'; +import React from 'react'; import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/types'; -import {ClassNameProps} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = block('select-field'); +const b = editorCn('select-field'); type SelectInput = SelectSingleInput | SelectMultipleInput; -interface SelectDynamicFieldProps extends ClassNameProps, FieldBaseParams { +interface SelectDynamicFieldProps extends FieldBaseParams { input: SelectInput; value: string; onUpdate: (value: string | undefined) => void; + className?: string; } -const SelectDynamicField: React.FC<SelectDynamicFieldProps> = (props) => { - const {input, value, onUpdate, className} = props; - +const SelectDynamicField = ({input, value, onUpdate, className}: SelectDynamicFieldProps) => { const inputView = input.view || 'radiobutton'; return ( diff --git a/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx b/editor-v2/components/DynamicForm/Fields/Text/Text.tsx similarity index 56% rename from src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx rename to editor-v2/components/DynamicForm/Fields/Text/Text.tsx index b16101e86..96ad2ab90 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx +++ b/editor-v2/components/DynamicForm/Fields/Text/Text.tsx @@ -1,21 +1,18 @@ -import React from 'react'; - import {TextInput} from '@gravity-ui/uikit'; +import React from 'react'; -import {ClassNameProps} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = block('text-dynamic-field'); +const b = editorCn('text-dynamic-field'); -interface TextDynamicFieldProps extends ClassNameProps, FieldBaseParams { +interface TextDynamicFieldProps extends FieldBaseParams { value: string; onUpdate: (value: string | undefined) => void; + className?: string; } -const TextDynamicField: React.FC<TextDynamicFieldProps> = (props) => { - const {title, value, onUpdate, className} = props; - +const TextDynamicField = ({title, value, onUpdate, className}: TextDynamicFieldProps) => { return ( <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> <TextInput value={value || ''} onUpdate={onUpdate} /> diff --git a/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx b/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx similarity index 57% rename from src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx rename to editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx index 4ca783696..49d399c66 100644 --- a/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx +++ b/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx @@ -1,21 +1,18 @@ -import React from 'react'; - import {TextArea} from '@gravity-ui/uikit'; +import React from 'react'; -import {ClassNameProps} from '../../../../../models'; -import {block} from '../../../../../utils'; +import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = block('textarea-dynamic-field'); +const b = editorCn('textarea-dynamic-field'); -interface TextAreaDynamicFieldProps extends ClassNameProps, FieldBaseParams { +interface TextAreaDynamicFieldProps extends FieldBaseParams { value: string; onUpdate: (value: string | undefined) => void; + className?: string; } -const TextAreaDynamicField: React.FC<TextAreaDynamicFieldProps> = (props) => { - const {title, value, onUpdate, className} = props; - +const TextAreaDynamicField = ({title, value, onUpdate, className}: TextAreaDynamicFieldProps) => { return ( <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> <TextArea minRows={5} maxRows={20} value={value || ''} onUpdate={onUpdate} /> diff --git a/src/editor-v2/components/DynamicForm/utils.ts b/editor-v2/components/DynamicForm/utils.ts similarity index 89% rename from src/editor-v2/components/DynamicForm/utils.ts rename to editor-v2/components/DynamicForm/utils.ts index b52c3381a..8a83dc1cb 100644 --- a/src/editor-v2/components/DynamicForm/utils.ts +++ b/editor-v2/components/DynamicForm/utils.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {PageContent} from '../../../models'; +import {PageContent} from '../../../src/models'; export const getFullPath = (path: string, name: string) => { if (!path && !name) { diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.scss b/editor-v2/components/MiddleScreen/MiddleScreen.scss similarity index 87% rename from src/editor-v2/components/MiddleScreen/MiddleScreen.scss rename to editor-v2/components/MiddleScreen/MiddleScreen.scss index 16c869fa1..0b869dbdd 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.scss +++ b/editor-v2/components/MiddleScreen/MiddleScreen.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -13,11 +11,14 @@ $block: '.#{$ns}middle-screen'; flex-direction: column; &__topbar { - height: 40px; + flex: 0 0 auto; + // min-height: 40px; } &__wrapper { - height: 100%; + flex: 1 1 auto; + overflow-y: auto; + // height: 100%; } &__canvas { diff --git a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/editor-v2/components/MiddleScreen/MiddleScreen.tsx similarity index 73% rename from src/editor-v2/components/MiddleScreen/MiddleScreen.tsx rename to editor-v2/components/MiddleScreen/MiddleScreen.tsx index efa1499b6..5f7e0b8d1 100644 --- a/src/editor-v2/components/MiddleScreen/MiddleScreen.tsx +++ b/editor-v2/components/MiddleScreen/MiddleScreen.tsx @@ -1,23 +1,23 @@ -import React, {useCallback, useContext, useState} from 'react'; - import {Loader} from '@gravity-ui/uikit'; +import React, {ElementType, useCallback, useContext, useState} from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import Overlay from '../Overlay/Overlay'; -import TopBar from '../TopBar/TopBar'; import './MiddleScreen.scss'; -const b = block('middle-screen'); +const b = editorCn('middle-screen'); -interface MiddleScreenProps extends ClassNameProps {} +interface MiddleScreenProps { + className?: string; + CustomTop?: ElementType; +} -const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { - const {zoom, initialized, setZoom, decreaseZoom, increaseZoom} = useMainEditorStore(); +const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { + const {zoom, initialized} = useMainEditorStore(); const {url, setIframeElement} = useContext(IframeContext); const [height, setHeight] = useState(0); @@ -38,14 +38,11 @@ const MiddleScreen: React.FC<MiddleScreenProps> = ({className}) => { return ( <div className={b(null, className)}> - <div className={b('topbar')}> - <TopBar - onZoomUpdate={setZoom} - zoom={zoom} - onDecreaseZoom={decreaseZoom} - onIncreaseZoom={increaseZoom} - /> - </div> + {CustomTop ? ( + <div className={b('topbar')}> + <CustomTop /> + </div> + ) : null} <div className={b('wrapper')}> <div className={b('canvas', {hidden: !initialized})} diff --git a/src/editor-v2/components/Overlay/Overlay.scss b/editor-v2/components/Overlay/Overlay.scss similarity index 94% rename from src/editor-v2/components/Overlay/Overlay.scss rename to editor-v2/components/Overlay/Overlay.scss index 7d946ec71..bde825744 100644 --- a/src/editor-v2/components/Overlay/Overlay.scss +++ b/editor-v2/components/Overlay/Overlay.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/Overlay/Overlay.tsx b/editor-v2/components/Overlay/Overlay.tsx similarity index 95% rename from src/editor-v2/components/Overlay/Overlay.tsx rename to editor-v2/components/Overlay/Overlay.tsx index b900ed4b8..b9d74b7dc 100644 --- a/src/editor-v2/components/Overlay/Overlay.tsx +++ b/editor-v2/components/Overlay/Overlay.tsx @@ -1,19 +1,18 @@ -import React, {useCallback, useState} from 'react'; - import {Copy, Grip, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; +import React, {useCallback, useState} from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './Overlay.scss'; -const b = block('overlay'); - -interface OverlayProps extends ClassNameProps {} +const b = editorCn('overlay'); +interface OverlayProps { + className?: string; +} interface InsertLineProps { top: number; left: number; @@ -22,7 +21,7 @@ interface InsertLineProps { position: string; } -const Overlay: React.FC<OverlayProps> = ({className}) => { +const Overlay = ({className}: OverlayProps) => { const { height, selectedBlock, diff --git a/src/editor-v2/components/Panels/Panels.scss b/editor-v2/components/Panels/Panels.scss similarity index 61% rename from src/editor-v2/components/Panels/Panels.scss rename to editor-v2/components/Panels/Panels.scss index e22ef9764..e7865c13f 100644 --- a/src/editor-v2/components/Panels/Panels.scss +++ b/editor-v2/components/Panels/Panels.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -8,18 +6,18 @@ $block: '.#{$ns}panels'; #{$block} { &__button-wrap { - --pc-editor-panels-button-gap: 8px; + --pceditor-panels-button-gap: 8px; position: absolute; z-index: 1000; &_left { - bottom: var(--pc-editor-panels-button-gap); - left: calc(100% + var(--pc-editor-panels-button-gap)); + bottom: var(--pceditor-panels-button-gap); + left: calc(100% + var(--pceditor-panels-button-gap)); } &_right { - bottom: var(--pc-editor-panels-button-gap); - right: calc(100% + var(--pc-editor-panels-button-gap)); + bottom: var(--pceditor-panels-button-gap); + right: calc(100% + var(--pceditor-panels-button-gap)); } } diff --git a/src/editor-v2/components/Panels/Panels.tsx b/editor-v2/components/Panels/Panels.tsx similarity index 94% rename from src/editor-v2/components/Panels/Panels.tsx rename to editor-v2/components/Panels/Panels.tsx index 8505f7209..b0f2f6c87 100644 --- a/src/editor-v2/components/Panels/Panels.tsx +++ b/editor-v2/components/Panels/Panels.tsx @@ -1,14 +1,13 @@ -import React, {ReactElement, useRef} from 'react'; - import {ArrowLeftFromLine, ArrowRightFromLine, Grip} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; +import React, {ReactElement, useRef} from 'react'; import {ImperativePanelHandle, Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; -import {block} from '../../../utils'; +import {editorCn} from '../../utils/cn'; import './Panels.scss'; -const b = block('panels'); +const b = editorCn('panels'); interface PanelsProps { left: ReactElement; @@ -16,8 +15,7 @@ interface PanelsProps { right: ReactElement; } -export const Panels = (props: PanelsProps) => { - const {left, right, middle} = props; +export const Panels = ({left, right, middle}: PanelsProps) => { const leftPanel = useRef<ImperativePanelHandle>(null); const rightPanel = useRef<ImperativePanelHandle>(null); diff --git a/editor-v2/components/Sidebar/Sidebar.scss b/editor-v2/components/Sidebar/Sidebar.scss new file mode 100644 index 000000000..fca4b373d --- /dev/null +++ b/editor-v2/components/Sidebar/Sidebar.scss @@ -0,0 +1,18 @@ +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}sidebar'; + +#{$block} { + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + + &__block { + width: 100%; + border-bottom: 1px solid var(--g-color-line-generic); + } +} diff --git a/editor-v2/components/Sidebar/Sidebar.tsx b/editor-v2/components/Sidebar/Sidebar.tsx new file mode 100644 index 000000000..1ef6cfe7c --- /dev/null +++ b/editor-v2/components/Sidebar/Sidebar.tsx @@ -0,0 +1,28 @@ +import React, {ElementType} from 'react'; + +import {editorCn} from '../../utils/cn'; +import Tabs, {TabsItemProps} from '../Tabs/Tabs'; + +import './Sidebar.scss'; + +const b = editorCn('sidebar'); + +interface SidebarProps { + tabs: TabsItemProps[]; + defaultTab?: string; + top?: ElementType[]; + className?: string; +} + +export const Sidebar = ({className, tabs, top = []}: SidebarProps) => { + return ( + <div className={b(null, className)}> + {top.map((TopComponent, idx) => ( + <div key={idx} className={b('block')}> + <TopComponent /> + </div> + ))} + <Tabs items={tabs} /> + </div> + ); +}; diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.scss b/editor-v2/components/StoreViewer/StoreViewer.scss similarity index 88% rename from src/editor-v2/components/StoreViewer/StoreViewer.scss rename to editor-v2/components/StoreViewer/StoreViewer.scss index 93a126da9..6fd241894 100644 --- a/src/editor-v2/components/StoreViewer/StoreViewer.scss +++ b/editor-v2/components/StoreViewer/StoreViewer.scss @@ -1,5 +1,5 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; $block: '.#{$ns}store-viewer'; diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.tsx b/editor-v2/components/StoreViewer/StoreViewer.tsx similarity index 78% rename from src/editor-v2/components/StoreViewer/StoreViewer.tsx rename to editor-v2/components/StoreViewer/StoreViewer.tsx index b0bd87d9d..59afb58ee 100644 --- a/src/editor-v2/components/StoreViewer/StoreViewer.tsx +++ b/editor-v2/components/StoreViewer/StoreViewer.tsx @@ -1,22 +1,21 @@ -import React, {useState} from 'react'; - import {Code} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; +import React, {useState} from 'react'; import ReactJson from 'react-json-view'; import {removeFn} from '../../../common/utils'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; +import {editorCn} from '../../utils/cn'; import './StoreViewer.scss'; -const b = block('store-viewer'); +const b = editorCn('store-viewer'); -interface StoreViewerProps extends ClassNameProps { +interface StoreViewerProps { + className?: string; store: object; } -const StoreViewer: React.FC<StoreViewerProps> = ({store, className}) => { +const StoreViewer = ({store, className}: StoreViewerProps) => { const [open, setOpen] = useState(false); return ( <div className={b({open}, className)}> diff --git a/editor-v2/components/Tabs/Tabs.scss b/editor-v2/components/Tabs/Tabs.scss new file mode 100644 index 000000000..ecd849e29 --- /dev/null +++ b/editor-v2/components/Tabs/Tabs.scss @@ -0,0 +1,35 @@ +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}tabs'; + +#{$block} { + overflow: auto hidden; + width: 100%; + + &__item { + color: var(--g-color-private-black-300); + font-weight: 500; + letter-spacing: 0.5px; + cursor: pointer; + + &_active { + pointer-events: none; + color: var(--g-color-text-primary); + } + } + + &__tabs-wrapper { + width: 100%; + border-bottom: 1px solid var(--g-color-line-generic); + display: inline-flex; + flex-direction: row; + padding: 16px 4px; + } + + &__body { + flex: 1; + border-right: 1px solid var(--g-color-line-generic); + overflow-y: auto; + } +} diff --git a/editor-v2/components/Tabs/Tabs.tsx b/editor-v2/components/Tabs/Tabs.tsx new file mode 100644 index 000000000..7c7ee710e --- /dev/null +++ b/editor-v2/components/Tabs/Tabs.tsx @@ -0,0 +1,72 @@ +import {Button} from '@gravity-ui/uikit'; +import React, {ElementType, useCallback, useMemo, useState} from 'react'; + +import {editorCn} from '../../utils/cn'; + +import './Tabs.scss'; + +const b = editorCn('tabs'); + +export interface TabsItemProps { + id: string; + title: string; + component: ElementType; +} + +export interface TabsProps { + className?: string; + items: TabsItemProps[]; + defaultTab?: string | null; +} + +const Tabs = ({className, items, defaultTab}: TabsProps) => { + const [currentTab, setCurrentTab] = useState(defaultTab); + + const activeTabId: string | null = useMemo(() => { + if (currentTab) { + return currentTab; + } + + return items[0].id; + }, [currentTab, items]); + + const handleClick = useCallback( + (tabId: string) => () => { + setCurrentTab(tabId); + }, + [], + ); + + const TabComponent = useMemo(() => { + const findTab = items.find(({id}) => id === activeTabId); + return findTab?.component; + }, [activeTabId, items]); + + return ( + <div className={b(null, className)}> + <div className={b('tabs-wrapper')} role="tablist"> + {items.map(({id, title}) => { + const isActive = id === activeTabId; + + return ( + <Button + view="flat" + size="s" + className={b('item', {active: isActive})} + key={title} + extraProps={{ + role: 'tab', + }} + onClick={handleClick(id)} + > + {title} + </Button> + ); + })} + </div> + <div className={b('body')}>{TabComponent && <TabComponent />}</div> + </div> + ); +}; + +export default Tabs; diff --git a/src/editor-v2/constants.ts b/editor-v2/constants.ts similarity index 100% rename from src/editor-v2/constants.ts rename to editor-v2/constants.ts diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.scss b/editor-v2/containers/BlockConfig/BlockConfig.scss similarity index 73% rename from src/editor-v2/components/BlockConfig/BlockConfig.scss rename to editor-v2/containers/BlockConfig/BlockConfig.scss index 6af7fb8a7..0411ca8ad 100644 --- a/src/editor-v2/components/BlockConfig/BlockConfig.scss +++ b/editor-v2/containers/BlockConfig/BlockConfig.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -12,7 +10,6 @@ $block: '.#{$ns}block-config'; &__title { margin-bottom: 16px; margin-top: 8px; - @include text-subheader-3; } &__empty { @@ -22,7 +19,5 @@ $block: '.#{$ns}block-config'; text-align: center; height: 100%; width: 100%; - - @include text-body-2; } } diff --git a/src/editor-v2/components/BlockConfig/BlockConfig.tsx b/editor-v2/containers/BlockConfig/BlockConfig.tsx similarity index 82% rename from src/editor-v2/components/BlockConfig/BlockConfig.tsx rename to editor-v2/containers/BlockConfig/BlockConfig.tsx index 852739b70..1ccc59952 100644 --- a/src/editor-v2/components/BlockConfig/BlockConfig.tsx +++ b/editor-v2/containers/BlockConfig/BlockConfig.tsx @@ -1,21 +1,21 @@ -import React from 'react'; - import _ from 'lodash'; +import React from 'react'; import {DynamicFormValue} from '../../../common/types'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; +import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {generateChildrenPathFromArray} from '../../utils'; -import DynamicForm from '../DynamicForm/DynamicForm'; +import {editorCn} from '../../utils/cn'; import './BlockConfig.scss'; -const b = block('block-config'); +const b = editorCn('block-config'); -interface BlockConfigProps extends ClassNameProps {} +interface BlockConfigProps { + className?: string; +} -const BlockConfig: React.FC<BlockConfigProps> = ({className}) => { +const BlockConfig = ({className}: BlockConfigProps) => { const {selectedBlock, content, blocks, subBlocks, updateField} = useMainEditorStore(); const currentBlockPath = selectedBlock ? generateChildrenPathFromArray(selectedBlock) : '[]'; diff --git a/src/editor-v2/components/BlocksList/BlocksList.scss b/editor-v2/containers/BlocksList/BlocksList.scss similarity index 93% rename from src/editor-v2/components/BlocksList/BlocksList.scss rename to editor-v2/containers/BlocksList/BlocksList.scss index e335c07ca..c2a8cfe10 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.scss +++ b/editor-v2/containers/BlocksList/BlocksList.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/BlocksList/BlocksList.tsx b/editor-v2/containers/BlocksList/BlocksList.tsx similarity index 92% rename from src/editor-v2/components/BlocksList/BlocksList.tsx rename to editor-v2/containers/BlocksList/BlocksList.tsx index e608b8c1f..b2aa9b261 100644 --- a/src/editor-v2/components/BlocksList/BlocksList.tsx +++ b/editor-v2/containers/BlocksList/BlocksList.tsx @@ -1,16 +1,15 @@ -import React, {PropsWithChildren, useCallback} from 'react'; - import {Grip, Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; import _ from 'lodash'; +import React, {PropsWithChildren, useCallback} from 'react'; import {ItemConfig} from '../../../common/types'; -import {block} from '../../../utils'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './BlocksList.scss'; -const b = block('blocks-list'); +const b = editorCn('blocks-list'); export interface BlocksListProps { blocks: ItemConfig[]; @@ -60,10 +59,8 @@ const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { return ( <div className={b()}> <div className={b('section')}> - <div className={b('title')}>Blocks</div> {Object.entries(groups).map(([key, groupBlocks]) => ( <div className={b('group')} key={key}> - <div className={b('subtitle')}>{_.capitalize(key)}</div> <div> {groupBlocks.map(({type, schema: {name}}) => ( <Card diff --git a/src/editor-v2/containers/Editor/Editor.scss b/editor-v2/containers/Editor/Editor.scss similarity index 91% rename from src/editor-v2/containers/Editor/Editor.scss rename to editor-v2/containers/Editor/Editor.scss index 58ed10c8e..cdb4c1f6d 100644 --- a/src/editor-v2/containers/Editor/Editor.scss +++ b/editor-v2/containers/Editor/Editor.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx similarity index 64% rename from src/editor-v2/containers/Editor/Editor.tsx rename to editor-v2/containers/Editor/Editor.tsx index 0ec1cf321..83eeee221 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -1,7 +1,6 @@ -import React, {useCallback} from 'react'; +import React, {ElementType, useCallback} from 'react'; -import {PageContent} from '../../../models'; -import {block} from '../../../utils'; +import {PageContent} from '../../../src/models'; import BigOverlay from '../../components/BigOverlay/BigOverlay'; import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; import {Panels} from '../../components/Panels/Panels'; @@ -9,20 +8,34 @@ import {Sidebar} from '../../components/Sidebar/Sidebar'; import StoreViewer from '../../components/StoreViewer/StoreViewer'; import {MainEditorStoreProvider} from '../../context/editorStore'; import {IframeProvider} from '../../context/iframeContext'; +import {useEditorTabs} from '../../hooks/useEditorTabs'; import useMainEditorInitialize from '../../hooks/useMainEditorInitialize'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './Editor.scss'; -const b = block('editor'); +const b = editorCn('editor'); +interface SidebarTabComponent { + id: string; + title: string; + component: ElementType; +} interface EditorViewProps { onUpdate?: (pageContent: PageContent) => void; initialUrl: string; disableUrlField?: boolean; + componentsConfig?: { + middleTop?: ElementType; + leftTop?: ElementType[]; + rightTop?: ElementType[]; + leftTabs?: SidebarTabComponent[]; + rightTabs?: SidebarTabComponent[]; + }; } -const EditorView = (_props: EditorViewProps) => { +const EditorView = ({componentsConfig = {}}: EditorViewProps) => { const store = useMainEditorStore(); const {manipulateOverlayMode, disableMode} = store; @@ -39,15 +52,22 @@ const EditorView = (_props: EditorViewProps) => { }, [disableMode, manipulateOverlayMode], ); + const {left, right} = useEditorTabs(componentsConfig); return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions <div className={b()} onMouseUp={onMouseUp}> <div className={b('body')}> <Panels - left={<Sidebar position={'left'} />} - right={<Sidebar position={'right'} startMenu="block-config" />} - middle={<MiddleScreen />} + left={<Sidebar tabs={left} />} + right={ + <Sidebar + tabs={right} + top={componentsConfig.rightTop} + defaultTab="block-config" + /> + } + middle={<MiddleScreen CustomTop={componentsConfig.middleTop} />} /> </div> <BigOverlay className={b('overlay')} /> diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.scss b/editor-v2/containers/GlobalConfig/GlobalConfig.scss similarity index 76% rename from src/editor-v2/components/GlobalConfig/GlobalConfig.scss rename to editor-v2/containers/GlobalConfig/GlobalConfig.scss index d54900eb4..821017115 100644 --- a/src/editor-v2/components/GlobalConfig/GlobalConfig.scss +++ b/editor-v2/containers/GlobalConfig/GlobalConfig.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx similarity index 67% rename from src/editor-v2/components/GlobalConfig/GlobalConfig.tsx rename to editor-v2/containers/GlobalConfig/GlobalConfig.tsx index 57594bca3..0a3d2eb48 100644 --- a/src/editor-v2/components/GlobalConfig/GlobalConfig.tsx +++ b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx @@ -1,18 +1,19 @@ import React from 'react'; import {DynamicFormValue} from '../../../common/types'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; +import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; -import DynamicForm from '../DynamicForm/DynamicForm'; +import {editorCn} from '../../utils/cn'; import './GlobalConfig.scss'; -const b = block('global-config'); +const b = editorCn('global-config'); -interface GlobalConfigProps extends ClassNameProps {} +interface GlobalConfigProps { + className?: string; +} -const GlobalConfig: React.FC<GlobalConfigProps> = ({className}) => { +const GlobalConfig = ({className}: GlobalConfigProps) => { const {global, content, updateField} = useMainEditorStore(); const onUpdate = (key: string, value: DynamicFormValue) => { diff --git a/src/editor-v2/components/Source/Source.scss b/editor-v2/containers/Source/Source.scss similarity index 63% rename from src/editor-v2/components/Source/Source.scss rename to editor-v2/containers/Source/Source.scss index d85ac31e6..04512580e 100644 --- a/src/editor-v2/components/Source/Source.scss +++ b/editor-v2/containers/Source/Source.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -7,10 +5,16 @@ $block: '.#{$ns}source'; #{$block} { + width: 100%; display: inline-flex; + box-sizing: border-box; align-items: center; - padding: 0 8px; - gap: 8px; + flex-direction: row; + padding: 16px 12px; + + &__icon { + flex: 0 0 auto; + } &__text { flex: 1; diff --git a/src/editor-v2/components/Source/Source.tsx b/editor-v2/containers/Source/Source.tsx similarity index 76% rename from src/editor-v2/components/Source/Source.tsx rename to editor-v2/containers/Source/Source.tsx index 9c3ed18f6..176e819e2 100644 --- a/src/editor-v2/components/Source/Source.tsx +++ b/editor-v2/containers/Source/Source.tsx @@ -1,20 +1,16 @@ -import React, {useCallback, useContext} from 'react'; - import {ArrowRotateRight} from '@gravity-ui/icons'; import {Button, Icon, TextInput} from '@gravity-ui/uikit'; +import React, {useCallback, useContext} from 'react'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './Source.scss'; -const b = block('source'); - -interface SourceProps extends ClassNameProps {} +const b = editorCn('source'); -const Source: React.FC<SourceProps> = ({className}) => { +const Source = () => { const {resetInitialize} = useMainEditorStore(); const {disableUrlField, url, setUrl} = useContext(IframeContext); @@ -34,8 +30,8 @@ const Source: React.FC<SourceProps> = ({className}) => { }; return ( - <div className={b(null, className)}> - <Button view="flat" size="m" onClick={reloadIframe}> + <div className={b()}> + <Button className={b('icon')} view="flat" size="m" onClick={reloadIframe}> <Icon data={ArrowRotateRight} size={18} /> </Button> <TextInput diff --git a/src/editor-v2/components/SourceCode/SourceCode.scss b/editor-v2/containers/SourceCode/SourceCode.scss similarity index 88% rename from src/editor-v2/components/SourceCode/SourceCode.scss rename to editor-v2/containers/SourceCode/SourceCode.scss index 205c407b9..df1cda2af 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.scss +++ b/editor-v2/containers/SourceCode/SourceCode.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/SourceCode/SourceCode.tsx b/editor-v2/containers/SourceCode/SourceCode.tsx similarity index 93% rename from src/editor-v2/components/SourceCode/SourceCode.tsx rename to editor-v2/containers/SourceCode/SourceCode.tsx index 42d357089..98d707081 100644 --- a/src/editor-v2/components/SourceCode/SourceCode.tsx +++ b/editor-v2/containers/SourceCode/SourceCode.tsx @@ -1,5 +1,3 @@ -import React, {useState} from 'react'; - import {ArrowDownToSquare, Copy, CopyCheck} from '@gravity-ui/icons'; import { Alert, @@ -11,18 +9,21 @@ import { TextArea, } from '@gravity-ui/uikit'; import yaml from 'js-yaml'; +import React, {useState} from 'react'; -import {ClassNameProps, PageContent} from '../../../models'; -import {block} from '../../../utils'; +import {PageContent} from '../../../src/models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './SourceCode.scss'; -const b = block('source-code'); +const b = editorCn('source-code'); -interface SourceCodeProps extends ClassNameProps {} +interface SourceCodeProps { + className?: string; +} -const SourceCode: React.FC<SourceCodeProps> = ({className}) => { +const SourceCode = ({className}: SourceCodeProps) => { const {content, setContent} = useMainEditorStore(); const [isOpen, setIsOpen] = useState(false); const [tempConfig, setTempConfig] = useState(''); diff --git a/src/editor-v2/components/Tree/Tree.scss b/editor-v2/containers/Tree/Tree.scss similarity index 92% rename from src/editor-v2/components/Tree/Tree.scss rename to editor-v2/containers/Tree/Tree.scss index 1c197a67f..ca1b5f538 100644 --- a/src/editor-v2/components/Tree/Tree.scss +++ b/editor-v2/containers/Tree/Tree.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx similarity index 96% rename from src/editor-v2/components/Tree/Tree.tsx rename to editor-v2/containers/Tree/Tree.tsx index 26b6d8506..b78916110 100644 --- a/src/editor-v2/components/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -1,15 +1,14 @@ -import React, {PropsWithChildren} from 'react'; - import {TrashBin} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; +import React, {PropsWithChildren} from 'react'; import {ItemConfig} from '../../../common/types'; -import {block} from '../../../utils'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './Tree.scss'; -const b = block('tree'); +const b = editorCn('tree'); export interface TreeProps { blocks: ItemConfig[]; diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.scss b/editor-v2/containers/ViewSwitches/ViewSwitches.scss similarity index 77% rename from src/editor-v2/components/ViewSwitches/ViewSwitches.scss rename to editor-v2/containers/ViewSwitches/ViewSwitches.scss index 45751b598..7e0cee91b 100644 --- a/src/editor-v2/components/ViewSwitches/ViewSwitches.scss +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.scss @@ -1,5 +1,3 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -7,7 +5,9 @@ $block: '.#{$ns}view-switches'; #{$block} { + padding: 12px; display: inline-flex; + align-items: center; gap: 12px; &__zoom { diff --git a/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx similarity index 52% rename from src/editor-v2/components/ViewSwitches/ViewSwitches.tsx rename to editor-v2/containers/ViewSwitches/ViewSwitches.tsx index cb9cd6578..fd01be7e0 100644 --- a/src/editor-v2/components/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,34 +1,22 @@ -import React from 'react'; - import {Minus, Plus} from '@gravity-ui/icons'; import {Button, Icon, Select} from '@gravity-ui/uikit'; +import React from 'react'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; import {ZOOM_STEPS} from '../../constants'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {editorCn} from '../../utils/cn'; import './ViewSwitches.scss'; -const b = block('view-switches'); - -interface ViewSwitchesProps extends ClassNameProps { - onZoomUpdate: (zoom: number) => void; - onDecreaseZoom: () => void; - onIncreaseZoom: () => void; - zoom: number; -} +const b = editorCn('view-switches'); -const ViewSwitches: React.FC<ViewSwitchesProps> = ({ - zoom, - onIncreaseZoom, - onDecreaseZoom, - onZoomUpdate, - className, -}) => { +const ViewSwitches = () => { + const {zoom, setZoom, decreaseZoom, increaseZoom} = useMainEditorStore(); return ( - <div className={b(null, className)}> + <div className={b()}> + <div>TBD screen sizes</div> <div className={b('zoom')}> - <Button view={'flat'} onClick={onDecreaseZoom}> + <Button view={'flat'} onClick={decreaseZoom}> <Icon data={Minus} /> </Button> <Select @@ -38,9 +26,9 @@ const ViewSwitches: React.FC<ViewSwitchesProps> = ({ value: String(step), content: `${String(step)}%`, }))} - onUpdate={(value) => onZoomUpdate(Number(value))} + onUpdate={(value) => setZoom(Number(value))} /> - <Button view={'flat'} onClick={onIncreaseZoom}> + <Button view={'flat'} onClick={increaseZoom}> <Icon data={Plus} /> </Button> </div> diff --git a/src/editor-v2/containers/__stories__/Editor.stories.tsx b/editor-v2/containers/__stories__/Editor.stories.tsx similarity index 99% rename from src/editor-v2/containers/__stories__/Editor.stories.tsx rename to editor-v2/containers/__stories__/Editor.stories.tsx index 9d5ee6959..c118f076b 100644 --- a/src/editor-v2/containers/__stories__/Editor.stories.tsx +++ b/editor-v2/containers/__stories__/Editor.stories.tsx @@ -1,6 +1,5 @@ -import React from 'react'; - import {Meta, StoryFn} from '@storybook/react'; +import React from 'react'; import {Editor} from '../Editor/Editor'; diff --git a/src/editor-v2/containers/__stories__/utils.ts b/editor-v2/containers/__stories__/utils.ts similarity index 100% rename from src/editor-v2/containers/__stories__/utils.ts rename to editor-v2/containers/__stories__/utils.ts diff --git a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/editor-v2/context/editorStore/MainEditorStoreContext.tsx similarity index 99% rename from src/editor-v2/context/editorStore/MainEditorStoreContext.tsx rename to editor-v2/context/editorStore/MainEditorStoreContext.tsx index 07a9eb77e..7f48605d4 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx +++ b/editor-v2/context/editorStore/MainEditorStoreContext.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import {StoreApi} from 'zustand'; import {EditorStore, createEditorStore} from '../../store'; diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/editor-v2/context/editorStore/MainEditorStoreProvider.tsx similarity index 99% rename from src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx rename to editor-v2/context/editorStore/MainEditorStoreProvider.tsx index 43bafff89..5d3b57306 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -1,5 +1,4 @@ import React, {PropsWithChildren, useCallback, useContext, useEffect, useRef} from 'react'; - import {StoreApi} from 'zustand'; import {EditorState} from '../../../common/store'; diff --git a/src/editor-v2/context/editorStore/index.ts b/editor-v2/context/editorStore/index.ts similarity index 100% rename from src/editor-v2/context/editorStore/index.ts rename to editor-v2/context/editorStore/index.ts diff --git a/src/editor-v2/context/iframeContext/IframeContext.tsx b/editor-v2/context/iframeContext/IframeContext.tsx similarity index 100% rename from src/editor-v2/context/iframeContext/IframeContext.tsx rename to editor-v2/context/iframeContext/IframeContext.tsx diff --git a/src/editor-v2/context/iframeContext/IframeProvider.tsx b/editor-v2/context/iframeContext/IframeProvider.tsx similarity index 85% rename from src/editor-v2/context/iframeContext/IframeProvider.tsx rename to editor-v2/context/iframeContext/IframeProvider.tsx index d6b3454c0..c5a0295ce 100644 --- a/src/editor-v2/context/iframeContext/IframeProvider.tsx +++ b/editor-v2/context/iframeContext/IframeProvider.tsx @@ -7,8 +7,11 @@ interface IframeProviderProps extends PropsWithChildren { disableUrlField?: boolean; } -export const IframeProvider = (props: IframeProviderProps) => { - const {children, initialUrl = '', disableUrlField} = props; +export const IframeProvider = ({ + children, + initialUrl = '', + disableUrlField, +}: IframeProviderProps) => { const [iframeElement, setIframeElement] = useState<HTMLIFrameElement>(); const [url, setUrl] = useState(initialUrl); diff --git a/src/editor-v2/context/iframeContext/index.ts b/editor-v2/context/iframeContext/index.ts similarity index 100% rename from src/editor-v2/context/iframeContext/index.ts rename to editor-v2/context/iframeContext/index.ts diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx new file mode 100644 index 000000000..aaa6baf98 --- /dev/null +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -0,0 +1,64 @@ +import Tabs, {TabsItemProps} from '../components/Tabs/Tabs'; +import BlockConfig from '../containers/BlockConfig/BlockConfig'; +import BlocksList from '../containers/BlocksList/BlocksList'; +import GlobalConfig from '../containers/GlobalConfig/GlobalConfig'; +import SourceCode from '../containers/SourceCode/SourceCode'; +import Tree from '../containers/Tree/Tree'; + +export const useEditorTabs = ({ + leftTabs = [], + rightTabs = [], +}: { + leftTabs?: TabsItemProps[]; + rightTabs?: TabsItemProps[]; +}) => { + return { + left: [ + { + id: 'page', + title: 'Page', + component: () => ( + <Tabs + items={[ + { + id: 'blocks-list', + title: 'Blocks', + component: BlocksList, + }, + { + id: 'tree', + title: 'Tree', + component: Tree, + }, + { + id: 'source-code', + title: 'SourceCode', + component: SourceCode, + }, + ]} + /> + ), + }, + { + id: 'global-config', + title: 'GlobalConfig', + component: GlobalConfig, + }, + ...leftTabs, + ], + right: [ + { + id: 'block-config', + title: 'BlockConfig', + component: BlockConfig, + position: 'right', + }, + { + id: 'source-code', + title: 'SourceCode', + component: SourceCode, + }, + ...rightTabs, + ], + }; +}; diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/editor-v2/hooks/useMainEditorInitialize.ts similarity index 100% rename from src/editor-v2/hooks/useMainEditorInitialize.ts rename to editor-v2/hooks/useMainEditorInitialize.ts diff --git a/src/editor-v2/hooks/useMainEditorStore.ts b/editor-v2/hooks/useMainEditorStore.ts similarity index 99% rename from src/editor-v2/hooks/useMainEditorStore.ts rename to editor-v2/hooks/useMainEditorStore.ts index 49c3496a9..5ff0baf4f 100644 --- a/src/editor-v2/hooks/useMainEditorStore.ts +++ b/editor-v2/hooks/useMainEditorStore.ts @@ -1,5 +1,4 @@ import {useContext} from 'react'; - import {useStore} from 'zustand'; import {MainEditorStoreContext} from '../context/editorStore'; diff --git a/src/editor-v2/hooks/usePostMessageEvents.ts b/editor-v2/hooks/usePostMessageEvents.ts similarity index 100% rename from src/editor-v2/hooks/usePostMessageEvents.ts rename to editor-v2/hooks/usePostMessageEvents.ts diff --git a/src/editor-v2/index.ts b/editor-v2/index.ts similarity index 100% rename from src/editor-v2/index.ts rename to editor-v2/index.ts diff --git a/src/editor-v2/store.ts b/editor-v2/store.ts similarity index 99% rename from src/editor-v2/store.ts rename to editor-v2/store.ts index a75e1262b..18a882223 100644 --- a/src/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; import {EditorState, initialStore} from '../common/store'; import {DynamicFormValue} from '../common/types'; import {initializeStore} from '../common/utils'; -import {ConstructorBlock, PageContent} from '../models'; +import {ConstructorBlock, PageContent} from '../src/models'; import {ZOOM_STEPS} from './constants'; import { diff --git a/src/editor-v2/styles/mixins.scss b/editor-v2/styles/mixins.scss similarity index 83% rename from src/editor-v2/styles/mixins.scss rename to editor-v2/styles/mixins.scss index 6144f6425..63223ed64 100644 --- a/src/editor-v2/styles/mixins.scss +++ b/editor-v2/styles/mixins.scss @@ -1,3 +1,4 @@ +@import '@gravity-ui/uikit/styles/mixins.scss'; @import './variables.scss'; @mixin control($hoverScale: 1.05) { diff --git a/editor-v2/styles/root.scss b/editor-v2/styles/root.scss new file mode 100644 index 000000000..25fa51c5a --- /dev/null +++ b/editor-v2/styles/root.scss @@ -0,0 +1,2 @@ +body { +} diff --git a/src/editor-v2/styles/variables.scss b/editor-v2/styles/variables.scss similarity index 90% rename from src/editor-v2/styles/variables.scss rename to editor-v2/styles/variables.scss index c776f820a..e8cd79a73 100644 --- a/src/editor-v2/styles/variables.scss +++ b/editor-v2/styles/variables.scss @@ -1,3 +1,4 @@ +$ns: 'pceditor-'; $editorTransitionTime: 0.2s; $editorShadow: 0px 2px 8px rgba(0, 0, 0, 0.06), 0px 4px 24px rgba(0, 0, 0, 0.06); $editorControlBorderRadius: 8px; diff --git a/editor-v2/utils/cn.ts b/editor-v2/utils/cn.ts new file mode 100644 index 000000000..a43775d67 --- /dev/null +++ b/editor-v2/utils/cn.ts @@ -0,0 +1,5 @@ +import {withNaming} from '@bem-react/classname'; + +export const EDITOR_NAMESPACE = 'pceditor-'; + +export const editorCn = withNaming({n: EDITOR_NAMESPACE, e: '__', m: '_'}); diff --git a/src/editor-v2/utils/code.ts b/editor-v2/utils/code.ts similarity index 83% rename from src/editor-v2/utils/code.ts rename to editor-v2/utils/code.ts index 3b4663267..5fe329723 100644 --- a/src/editor-v2/utils/code.ts +++ b/editor-v2/utils/code.ts @@ -1,6 +1,6 @@ import yaml from 'js-yaml'; -import {PageContent} from '../../models'; +import {PageContent} from '../../src/models'; export function parseCode(code: string) { const pageContent = yaml.load(code) as PageContent; diff --git a/src/editor-v2/utils/index.ts b/editor-v2/utils/index.ts similarity index 98% rename from src/editor-v2/utils/index.ts rename to editor-v2/utils/index.ts index dba199555..8bb7e96fd 100644 --- a/src/editor-v2/utils/index.ts +++ b/editor-v2/utils/index.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {ConstructorBlock} from '../../models'; +import {ConstructorBlock} from '../../src/models'; export function insert<T>(arr: Array<T>, index: number, newItem: T) { return [...arr.slice(0, index), newItem, ...arr.slice(index)]; diff --git a/editor-v2/utils/store.ts b/editor-v2/utils/store.ts new file mode 100644 index 000000000..e69de29bb diff --git a/playground/src/app/page.scss b/playground/src/app/page.scss index 213d4b128..e4fa88e63 100644 --- a/playground/src/app/page.scss +++ b/playground/src/app/page.scss @@ -2,4 +2,8 @@ $block: '.home'; #{$block} { height: 100vh; + + &__test { + padding: 16px; + } } diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index 061338f96..b817c3893 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -3,12 +3,16 @@ import {ThemeProvider} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import React, {useEffect, useState} from 'react'; -import {Editor} from '../../../src/editor-v2'; +import {Editor} from '../../../editor-v2'; +import Source from '../../../editor-v2/containers/Source/Source'; +import ViewSwitches from '../../../editor-v2/containers/ViewSwitches/ViewSwitches'; import './page.scss'; const b = block('home'); +const Test = () => <div className={b('test')}>custom test</div>; + export default function Home() { const [initialUrl, setInitialUrl] = useState<string>(''); @@ -22,7 +26,22 @@ export default function Home() { <ThemeProvider theme={'light'}> <div className={b()}> {initialUrl && ( - <Editor initialUrl={initialUrl} disableUrlField={false} onUpdate={() => {}} /> + <Editor + componentsConfig={{ + middleTop: Test, + leftTabs: [ + { + id: 'test', + title: 'TEST', + component: Test, + }, + ], + rightTop: [Source, ViewSwitches], + }} + initialUrl={initialUrl} + disableUrlField={false} + onUpdate={() => {}} + /> )} </div> </ThemeProvider> diff --git a/playground/src/app/pc-2/navigation.json b/playground/src/app/pc-2/navigation.json new file mode 100644 index 000000000..8df0e9ccc --- /dev/null +++ b/playground/src/app/pc-2/navigation.json @@ -0,0 +1,48 @@ +{ + "logo": { + "text": "", + "icon": "https://storage.yandexcloud.net/ydb-site-assets/ydb_icon.svg" + }, + "header": { + "leftItems": [ + { + "type": "link", + "text": "Документация", + "url": "/../docs/ru", + "target": "_self" + }, + { + "type": "link", + "text": "Команда", + "url": "/careers/" + }, + { + "type": "link", + "text": "Студентам", + "url": "/students/" + }, + { + "type": "link", + "text": "Поддержка", + "url": "/support/" + }, + { + "type": "link", + "text": "Блог", + "url": "/blog/" + } + ], + "rightItems": [ + { + "type": "github-button", + "text": "Star", + "label": "Star ydb-platform/ydb on GitHub", + "url": "https://github.com/ydb-platform/ydb" + } + ] + }, + "meta": { + "title": "", + "description": "" + } +} diff --git a/playground/src/app/pc-2/page.tsx b/playground/src/app/pc-2/page.tsx index 5b54e6796..9765c3c46 100644 --- a/playground/src/app/pc-2/page.tsx +++ b/playground/src/app/pc-2/page.tsx @@ -4,12 +4,13 @@ import React from 'react'; import {PageConstructor, PageConstructorProvider} from '../../../../src'; import content from './content.json'; +import navigation from './navigation.json'; import './styles.scss'; export default function Home() { return ( <PageConstructorProvider projectSettings={{disableCompress: true}}> - <PageConstructor content={content} /> + <PageConstructor content={content} navigation={navigation} /> </PageConstructorProvider> ); } diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts index 2056f833c..13ce0f2db 100644 --- a/src/blocks/CardLayout/index.ts +++ b/src/blocks/CardLayout/index.ts @@ -1,6 +1,6 @@ import {JSONSchemaType} from 'ajv'; -import {ConfigInput} from '../../common/types'; +import {ConfigInput} from '../../../common/types'; import {BlockData} from '../../constructor-items'; import {sliderSizesArray, textSize} from '../../schema/validators/common'; import {generateFromAJV} from '../../utils/form-generator'; diff --git a/src/blocks/Header/dynamic-form.ts b/src/blocks/Header/dynamic-form.ts index d3f174aba..33c4b9b93 100644 --- a/src/blocks/Header/dynamic-form.ts +++ b/src/blocks/Header/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig, ConfigInput} from '../../common/types'; +import {BlockConfig, ConfigInput} from '../../../common/types'; import {imageInputs} from '../../components/Image/dynamic-form'; import {Theme} from '../../models'; diff --git a/src/blocks/Slider/dynamic-form.ts b/src/blocks/Slider/dynamic-form.ts index ea7468997..5bf5520fc 100644 --- a/src/blocks/Slider/dynamic-form.ts +++ b/src/blocks/Slider/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig, ConfigInput} from '../../common/types'; +import {BlockConfig, ConfigInput} from '../../../common/types'; import {sliderSizesArray, textSize} from '../../schema/validators/common'; const textSizeEnum = textSize.map((size) => ({value: size, content: size})); diff --git a/src/blocks/TestEditorBlock/form.ts b/src/blocks/TestEditorBlock/form.ts index 838eacb06..849cb6d30 100644 --- a/src/blocks/TestEditorBlock/form.ts +++ b/src/blocks/TestEditorBlock/form.ts @@ -9,7 +9,7 @@ import { SelectSingleInput, TextAreaInput, TextInput, -} from '../../common/types'; +} from '../../../common/types'; const textInput: TextInput = { type: 'text', diff --git a/src/components/Image/dynamic-form.ts b/src/components/Image/dynamic-form.ts index f5f8de747..23b3f44cf 100644 --- a/src/components/Image/dynamic-form.ts +++ b/src/components/Image/dynamic-form.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {ConfigInput} from '../../common/types'; +import {ConfigInput} from '../../../common/types'; const devices = ['desktop', 'tablet', 'mobile']; diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx index acceef310..e957bd6f9 100644 --- a/src/context/editorStoreContext/PCEditorStoreContext.tsx +++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {StoreApi} from 'zustand'; -import {EditorState, createPCEditorStore} from '../../common/store'; +import {EditorState, createPCEditorStore} from '../../../common/store'; export interface PCEditorStoreContextProps { state: StoreApi<EditorState>; diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx index 22938c507..a6f0add57 100644 --- a/src/context/editorStoreContext/PCEditorStoreProvider.tsx +++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx @@ -2,8 +2,8 @@ import React, {PropsWithChildren, useCallback, useEffect, useRef} from 'react'; import {StoreApi} from 'zustand'; -import {EditorState, createPCEditorStore} from '../../common/store'; -import {StoreSyncMessage} from '../../common/types'; +import {EditorState, createPCEditorStore} from '../../../common/store'; +import {StoreSyncMessage} from '../../../common/types'; import {PCEditorStoreContext} from './PCEditorStoreContext'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss b/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss deleted file mode 100644 index b3aa2610b..000000000 --- a/src/editor-v2/components/DynamicForm/Fields/Object/Object.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import '../../../../../../styles/variables.scss'; -@import '../../../../../../styles/mixins.scss'; - -$block: '.#{$ns}object-dynamic-field'; - -#{$block} { - &__card { - padding: 12px; - } -} diff --git a/src/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss deleted file mode 100644 index 842996be6..000000000 --- a/src/editor-v2/components/Sidebar/Sidebar.scss +++ /dev/null @@ -1,86 +0,0 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; -@import '../../styles/root.scss'; -@import '../../styles/variables.scss'; -@import '../../styles/mixins.scss'; - -$block: '.#{$ns}sidebar'; - -#{$block} { - $class: &; - - position: relative; - height: 100%; - width: 100%; - display: flex; - - &_position_left { - flex-direction: row; - } - - &_position_right { - flex-direction: row-reverse; - } - - &__buttons-wrapper { - border-right: 1px solid var(--g-color-line-generic); - } - - &__buttons { - display: inline-flex; - flex-direction: column; - padding: 8px; - gap: 8px; - - &:hover #{$class}__label-wrapper { - opacity: 1; - } - } - - &__body { - flex: 1; - border-right: 1px solid var(--g-color-line-generic); - overflow-y: auto; - } - - &__button-wrapper { - position: relative; - } - - &_position_right { - #{$class}__button-wrapper:hover { - #{$class}__label-wrapper { - right: 40px; - } - } - - #{$class}__label-wrapper { - right: 50px; - } - } - - &_position_left { - #{$class}__button-wrapper:hover { - #{$class}__label-wrapper { - left: 40px; - } - } - - #{$class}__label-wrapper { - left: 50px; - } - } - - &__label-wrapper { - pointer-events: none; - opacity: 0; - - background-color: var(--g-color-base-background); - position: absolute; - top: 50%; - transform: translateY(-50%); - z-index: 100; - - transition: left 0.1s ease, right 0.1s ease, opacity 0.1s ease; - } -} diff --git a/src/editor-v2/components/Sidebar/Sidebar.tsx b/src/editor-v2/components/Sidebar/Sidebar.tsx deleted file mode 100644 index 09311f20e..000000000 --- a/src/editor-v2/components/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, {useMemo, useState} from 'react'; - -import {Code, FolderTree, Gear, Rectangles4, Square} from '@gravity-ui/icons'; -import {Button, Icon, Label} from '@gravity-ui/uikit'; -import {IconData} from '@gravity-ui/uikit/build/esm/components/Icon/Icon'; - -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; -import BlockConfig from '../BlockConfig/BlockConfig'; -import BlocksList from '../BlocksList/BlocksList'; -import GlobalConfig from '../GlobalConfig/GlobalConfig'; -import SourceCode from '../SourceCode/SourceCode'; -import Tree from '../Tree/Tree'; - -import './Sidebar.scss'; - -const b = block('sidebar'); - -interface SidebarProps extends ClassNameProps { - position: 'left' | 'right'; - startMenu?: string; -} - -interface TabConfig { - id: string; - name: string; - icon: IconData; - component: React.ElementType; - position: 'left' | 'right' | 'both'; -} - -export const Sidebar = ({className, position, startMenu}: SidebarProps) => { - const [currentTab, setCurrentTab] = useState(startMenu || 'blocks-list'); - - const defaultTabs: TabConfig[] = useMemo( - () => [ - { - id: 'blocks-list', - name: 'Блоки', - icon: Rectangles4, - component: BlocksList, - position: 'left', - }, - { - id: 'global-config', - name: 'Глобальная конфигурация', - icon: Gear, - component: GlobalConfig, - position: 'left', - }, - { - id: 'tree', - name: 'Дерево', - icon: FolderTree, - component: Tree, - position: 'left', - }, - { - id: 'block-config', - name: 'Конфигурация блока', - icon: Square, - component: BlockConfig, - position: 'right', - }, - { - id: 'source-code', - name: 'Исходный код', - icon: Code, - component: SourceCode, - position: 'both', - }, - ], - [], - ); - - const filteredTabs = defaultTabs.filter( - ({position: tabPosition}) => tabPosition === 'both' || tabPosition === position, - ); - - const TabComponent = useMemo(() => { - const findTab = defaultTabs.find(({id}) => id === currentTab); - return findTab?.component; - }, [currentTab, defaultTabs]); - - return ( - <div className={`${b(null, className)} ${b('', {position})}`}> - <div className={b('buttons-wrapper')}> - <div className={b('buttons')}> - {filteredTabs.map(({id, icon, name}) => ( - <div className={b('button-wrapper')} key={id}> - <Button - view={currentTab === id ? 'action' : 'flat'} - size="m" - onClick={() => setCurrentTab(id)} - > - <Icon data={icon} size={18} /> - </Button> - <div className={b('label-wrapper')}> - <Label theme={'normal'} size={'m'}> - {name} - </Label> - </div> - </div> - ))} - </div> - </div> - <div className={b('body')}>{TabComponent && <TabComponent />}</div> - </div> - ); -}; diff --git a/src/editor-v2/components/TopBar/TopBar.scss b/src/editor-v2/components/TopBar/TopBar.scss deleted file mode 100644 index 506b6ae87..000000000 --- a/src/editor-v2/components/TopBar/TopBar.scss +++ /dev/null @@ -1,34 +0,0 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; -@import '../../styles/root.scss'; -@import '../../styles/variables.scss'; -@import '../../styles/mixins.scss'; - -$block: '.#{$ns}topbar'; - -#{$block} { - display: flex; - justify-content: start; - align-items: center; - width: 100%; - height: 40px; - - border-bottom: 1px solid var(--g-color-line-generic); - - &__switches { - display: flex; - align-items: center; - width: auto; - height: 100%; - padding: 0 12px; - gap: 12px; - - border-right: 1px solid var(--g-color-line-generic); - } - - &__source { - flex: 1; - width: 100%; - box-sizing: border-box; - } -} diff --git a/src/editor-v2/components/TopBar/TopBar.tsx b/src/editor-v2/components/TopBar/TopBar.tsx deleted file mode 100644 index bf9c0b365..000000000 --- a/src/editor-v2/components/TopBar/TopBar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; - -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; -import Source from '../Source/Source'; -import ViewSwitches from '../ViewSwitches/ViewSwitches'; - -import './TopBar.scss'; - -const b = block('topbar'); - -interface TopBarProps extends ClassNameProps { - onZoomUpdate: (zoom: number) => void; - onDecreaseZoom: () => void; - onIncreaseZoom: () => void; - zoom: number; -} - -const TopBar: React.FC<TopBarProps> = ({ - zoom, - onDecreaseZoom, - onIncreaseZoom, - onZoomUpdate, - className, -}) => { - return ( - <div className={b(null, className)}> - <div className={b('switches')}> - <ViewSwitches - onZoomUpdate={onZoomUpdate} - onDecreaseZoom={onDecreaseZoom} - onIncreaseZoom={onIncreaseZoom} - zoom={zoom} - /> - </div> - <Source className={b('source')} /> - </div> - ); -}; - -export default TopBar; diff --git a/src/editor-v2/icons/Tablet.tsx b/src/editor-v2/icons/Tablet.tsx deleted file mode 100644 index 9ece66900..000000000 --- a/src/editor-v2/icons/Tablet.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import {a11yHiddenSvgProps} from '../../utils/svg'; - -export const Tablet: React.FC<React.SVGProps<SVGSVGElement>> = (props) => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="12" - height="14" - viewBox="0 0 12 14" - fill="none" - {...a11yHiddenSvgProps} - {...props} - > - <path - fillRule="evenodd" - clipRule="evenodd" - d="M10.5 3L10.5 11C10.5 11.8284 9.82843 12.5 9 12.5L3 12.5C2.17157 12.5 1.5 11.8284 1.5 11L1.5 3C1.5 2.17157 2.17157 1.5 3 1.5L9 1.5C9.82843 1.5 10.5 2.17157 10.5 3ZM9 -1.31134e-07C10.6569 -5.87108e-08 12 1.34315 12 3L12 11C12 12.6569 10.6569 14 9 14L3 14C1.34315 14 4.00426e-07 12.6569 4.72849e-07 11L8.2254e-07 3C8.94964e-07 1.34315 1.34315 -4.65826e-07 3 -3.93403e-07L9 -1.31134e-07ZM3.75 9.5C3.33579 9.5 3 9.83579 3 10.25C3 10.6642 3.33579 11 3.75 11L8.25 11C8.66421 11 9 10.6642 9 10.25C9 9.83579 8.66421 9.5 8.25 9.5L3.75 9.5Z" - fill="currentColor" - fillOpacity="0.85" - /> - </svg> -); diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss deleted file mode 100644 index d69df66c2..000000000 --- a/src/editor-v2/styles/root.scss +++ /dev/null @@ -1,9 +0,0 @@ -body { - --pc-editor-header-height: 48px; - --pc-editor-code-header-height: 36px; - --pc-editor-divider-width: 12px; - --pc-editor-left-column-width: calc(400px + var(--pc-editor-divider-width)); - --pc-editor-base-color: var(--g-color-base-brand); - --pc-editor-control-color: var(--g-color-base-brand); - --pc-editor-control-icon-color: var(--g-color-text-dark-primary); -} diff --git a/src/editor-v2/utils/store.ts b/src/editor-v2/utils/store.ts deleted file mode 100644 index 6806f7ca8..000000000 --- a/src/editor-v2/utils/store.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {StoreApi, create} from 'zustand'; -import {devtools, subscribeWithSelector} from 'zustand/middleware'; - -export function initializeStore<State, Methods>( - initialState: State, - methods: ( - set: StoreApi<State & Methods>['setState'], - get: StoreApi<State & Methods>['getState'], - ) => Methods, -) { - return (overrideInitialState?: Partial<State>) => - create< - State & Methods, - [['zustand/subscribeWithSelector', never], ['zustand/devtools', never]] - >( - subscribeWithSelector( - devtools((set, get) => ({ - ...initialState, - ...overrideInitialState, - ...methods(set, get), - })), - ), - ); -} diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts index fa4a61b81..3053b2331 100644 --- a/src/hooks/usePCEditorInitializeEvents.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -3,7 +3,7 @@ import {useCallback, useEffect} from 'react'; import {JSONSchemaType} from 'ajv'; import _ from 'lodash'; -import {ItemConfig} from '../common/types'; +import {ItemConfig} from '../../common/types'; import {blockDataMap} from '../constructor-items'; import {PageContent} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; diff --git a/src/hooks/usePostMessageAPI.ts b/src/hooks/usePostMessageAPI.ts index 6cf8c4cda..9c1953c2a 100644 --- a/src/hooks/usePostMessageAPI.ts +++ b/src/hooks/usePostMessageAPI.ts @@ -1,7 +1,7 @@ import {useEffect} from 'react'; -import {PostMessageAPIMessage} from '../common/types'; -import {ActionMessageTypes, EventMessageTypes} from '../common/types/actions'; +import {PostMessageAPIMessage} from '../../common/types'; +import {ActionMessageTypes, EventMessageTypes} from '../../common/types/actions'; export function sendEventPostMessage<K extends keyof EventMessageTypes>( action: K, diff --git a/src/index.ts b/src/index.ts index 05106270f..10becb250 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,5 @@ export * from './utils'; export * from './schema'; export * from './hooks'; export * from './navigation'; -export * from './common/postMessage'; export {BREAKPOINTS} from './constants'; From 575a8f026a17d5920b2f32f4d1e99c7193cfc3b2 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Sun, 9 Mar 2025 23:47:48 +0300 Subject: [PATCH 10/84] fix: common imports --- common/types/actions.ts | 2 +- common/types/forms.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/types/actions.ts b/common/types/actions.ts index ffb86cb76..c0add108a 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -1,4 +1,4 @@ -import {PageContent} from '../../models'; +import {PageContent} from '../../src/models'; import {EditorState} from '../store'; export type MessageTypes = EventMessageTypes & ActionMessageTypes; diff --git a/common/types/forms.ts b/common/types/forms.ts index 6597b12e5..f4e21a757 100644 --- a/common/types/forms.ts +++ b/common/types/forms.ts @@ -1,4 +1,4 @@ -import {PageContent} from '../../models'; +import {PageContent} from '../../src/models'; export type DynamicFormValue = string | number | [] | object | boolean | PageContent | undefined; From ed50e8820280cb81abcf762402f803f57915d1cd Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Sun, 9 Mar 2025 23:49:32 +0300 Subject: [PATCH 11/84] fix: overlay css --- editor-v2/components/Overlay/Overlay.scss | 8 +- editor-v2/components/Overlay/Overlay.tsx | 55 ++++++----- editor-v2/components/Tabs/Tabs.scss | 2 +- .../containers/SourceCode/SourceCode.scss | 30 +++--- .../containers/SourceCode/SourceCode.tsx | 93 +------------------ .../containers/ViewSwitches/ViewSwitches.tsx | 2 +- editor-v2/hooks/useEditorTabs.tsx | 22 +++-- editor-v2/store.ts | 2 +- editor-v2/styles/root.scss | 7 +- 9 files changed, 77 insertions(+), 144 deletions(-) diff --git a/editor-v2/components/Overlay/Overlay.scss b/editor-v2/components/Overlay/Overlay.scss index bde825744..975ae27ca 100644 --- a/editor-v2/components/Overlay/Overlay.scss +++ b/editor-v2/components/Overlay/Overlay.scss @@ -18,6 +18,7 @@ $block: '.#{$ns}overlay'; &__border { position: absolute; border: 3px var(--g-color-line-brand) solid; + border-radius: 24px; box-sizing: border-box; box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); @@ -53,14 +54,13 @@ $block: '.#{$ns}overlay'; &__actions { pointer-events: auto; position: absolute; - bottom: -40px; - left: 0; + bottom: 0; + left: 50%; + transform: translate(-50%, 100%); display: flex; align-items: center; background: transparent; - box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); - border-radius: 20px; } &__action-button { diff --git a/editor-v2/components/Overlay/Overlay.tsx b/editor-v2/components/Overlay/Overlay.tsx index b9d74b7dc..c38643dfc 100644 --- a/editor-v2/components/Overlay/Overlay.tsx +++ b/editor-v2/components/Overlay/Overlay.tsx @@ -1,6 +1,6 @@ -import {Copy, Grip, TrashBin} from '@gravity-ui/icons'; +import {ChevronDown, ChevronUp, Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; -import React, {useCallback, useState} from 'react'; +import React, {useState} from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -29,7 +29,7 @@ const Overlay = ({className}: OverlayProps) => { deleteBlock, duplicateBlock, manipulateOverlayMode, - enableReorderMode, + reorderBlock, } = useMainEditorStore(); const [insertLineBox, setInsertLineBox] = useState<InsertLineProps | undefined>(undefined); const [hoverBorders, setHoverBorders] = useState<DOMRect | null>(null); @@ -59,11 +59,23 @@ const Overlay = ({className}: OverlayProps) => { setBlockBorders(rect || null); }); - const onMouseDown = useCallback(() => { - if (selectedBlock) { - enableReorderMode(selectedBlock); - } - }, [enableReorderMode, selectedBlock]); + const handleMoveUp = () => { + if (!selectedBlock) return; + const destination = [...selectedBlock]; + destination[destination.length - 1] = destination[destination.length - 1] - 1; + reorderBlock(selectedBlock, destination, 'prepend'); + setSelectedBlock(undefined); + setBlockBorders(null); + }; + + const handleMoveDown = () => { + if (!selectedBlock) return; + const destination = [...selectedBlock]; + destination[destination.length - 1] = destination[destination.length - 1] + 1; + reorderBlock(selectedBlock, destination, 'append'); + setSelectedBlock(undefined); + setBlockBorders(null); + }; return ( <div className={b(null, className)} style={{height: `${height}px`}}> @@ -78,34 +90,29 @@ const Overlay = ({className}: OverlayProps) => { }} > <div className={b('actions')}> - {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} - <div onMouseDown={onMouseDown}> - <Button - pin={'round-clear'} - className={`${b('action-button', {grip: true})}`} - size={'m'} - view={'action'} - > - <Icon data={Grip} size={18} /> - </Button> - </div> + <Button view="flat" size={'m'} onClick={handleMoveUp}> + <Icon data={ChevronUp} size={18} /> + </Button> <Button - pin={'clear-clear'} + pin={'round-clear'} className={b('action-button')} size={'m'} view={'action'} - onClick={() => selectedBlock && deleteBlock(selectedBlock)} + onClick={() => selectedBlock && duplicateBlock(selectedBlock)} > - <Icon data={TrashBin} size={18} /> + <Icon data={Copy} size={18} /> </Button> <Button pin={'clear-round'} className={b('action-button')} size={'m'} view={'action'} - onClick={() => selectedBlock && duplicateBlock(selectedBlock)} + onClick={() => selectedBlock && deleteBlock(selectedBlock)} > - <Icon data={Copy} size={18} /> + <Icon data={TrashBin} size={18} /> + </Button> + <Button view="flat" size={'m'} onClick={handleMoveDown}> + <Icon data={ChevronDown} size={18} /> </Button> </div> </div> diff --git a/editor-v2/components/Tabs/Tabs.scss b/editor-v2/components/Tabs/Tabs.scss index ecd849e29..c4f2e727d 100644 --- a/editor-v2/components/Tabs/Tabs.scss +++ b/editor-v2/components/Tabs/Tabs.scss @@ -4,7 +4,7 @@ $block: '.#{$ns}tabs'; #{$block} { - overflow: auto hidden; + overflow: auto; width: 100%; &__item { diff --git a/editor-v2/containers/SourceCode/SourceCode.scss b/editor-v2/containers/SourceCode/SourceCode.scss index df1cda2af..30f7272a7 100644 --- a/editor-v2/containers/SourceCode/SourceCode.scss +++ b/editor-v2/containers/SourceCode/SourceCode.scss @@ -6,31 +6,25 @@ $block: '.#{$ns}source-code'; #{$block} { height: 100%; + padding: 16px 12px; box-sizing: border-box; display: flex; flex-direction: column; - gap: 10px; + gap: 12px; + + &__title { + font-weight: 500; + font-size: 13px; + line-height: 18px; + color: var(--g-color-private-black-300); + } &__code { white-space: pre; @include text-code-inline-1; - padding: 0 10px 10px; + padding: 6px 2px 8px; overflow: auto; - } - - &__head { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 8px; - padding: 10px 10px 0; - } - - &__textarea textarea { - @include text-code-inline-1; - } - - &__alert { - margin-bottom: 8px; + border: 1px solid var(--g-color-line-generic); + border-radius: 6px; } } diff --git a/editor-v2/containers/SourceCode/SourceCode.tsx b/editor-v2/containers/SourceCode/SourceCode.tsx index 98d707081..864d2f310 100644 --- a/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/editor-v2/containers/SourceCode/SourceCode.tsx @@ -1,17 +1,6 @@ -import {ArrowDownToSquare, Copy, CopyCheck} from '@gravity-ui/icons'; -import { - Alert, - Button, - CopyToClipboard, - Dialog, - Icon, - RadioButton, - TextArea, -} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; -import React, {useState} from 'react'; +import React from 'react'; -import {PageContent} from '../../../src/models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; @@ -21,90 +10,18 @@ const b = editorCn('source-code'); interface SourceCodeProps { className?: string; + format: 'yaml' | 'json'; } -const SourceCode = ({className}: SourceCodeProps) => { - const {content, setContent} = useMainEditorStore(); - const [isOpen, setIsOpen] = useState(false); - const [tempConfig, setTempConfig] = useState(''); - const [format, setFormat] = useState<'yaml' | 'json'>('yaml'); - - const onUpdate = () => { - let object; - - try { - if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) { - object = JSON.parse(tempConfig); - } else { - object = yaml.load(tempConfig); - } - } catch { - // eslint-disable-next-line no-console - console.error('JSON.parse failed'); - } - - if (object) { - setContent(object as PageContent); - } - - setIsOpen(false); - }; +const SourceCode = ({className, format}: SourceCodeProps) => { + const {content} = useMainEditorStore(); const text = format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); return ( <div className={b(null, className)}> - <div className={b('head')}> - <RadioButton value={format} onUpdate={setFormat}> - <RadioButton.Option value={'yaml'} content={'YAML'} /> - <RadioButton.Option value={'json'} content={'JSON'} /> - </RadioButton> - <CopyToClipboard text={text} timeout={1000}> - {(status) => ( - <Button> - {status === 'pending' ? ( - <Icon data={Copy} /> - ) : ( - <Icon data={CopyCheck} /> - )} - Copy - </Button> - )} - </CopyToClipboard> - <Button onClick={() => setIsOpen(true)}> - <Icon data={ArrowDownToSquare} /> - Import - </Button> - </div> - + <div className={b('title')}>{format}</div> <div className={b('code')}>{text}</div> - - <Dialog onClose={() => setIsOpen(false)} open={isOpen} size={'l'}> - <Dialog.Header caption="New configuration" /> - <Dialog.Body> - <Alert - className={b('alert')} - theme={'info'} - title={'You can use YAML or JSON'} - message={'The editor will automatically understand which format is needed.'} - ></Alert> - <TextArea - className={b('textarea')} - value={tempConfig} - onUpdate={setTempConfig} - rows={25} - /> - </Dialog.Body> - <Dialog.Footer - showError={false} - listenKeyEnter={true} - preset={'default'} - textButtonApply={'Apply'} - textButtonCancel={'Cancel'} - onClickButtonApply={onUpdate} - onClickButtonCancel={() => setIsOpen(false)} - /> - </Dialog> </div> ); }; diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index fd01be7e0..f04ce59d8 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -14,7 +14,7 @@ const ViewSwitches = () => { const {zoom, setZoom, decreaseZoom, increaseZoom} = useMainEditorStore(); return ( <div className={b()}> - <div>TBD screen sizes</div> + <div>TBD</div> <div className={b('zoom')}> <Button view={'flat'} onClick={decreaseZoom}> <Icon data={Minus} /> diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index aaa6baf98..6dbc5b65b 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -31,9 +31,14 @@ export const useEditorTabs = ({ component: Tree, }, { - id: 'source-code', - title: 'SourceCode', - component: SourceCode, + id: 'source-code-yaml', + title: 'YAML', + component: () => <SourceCode format="yaml" />, + }, + { + id: 'source-code-json', + title: 'JSON', + component: () => <SourceCode format="json" />, }, ]} /> @@ -54,9 +59,14 @@ export const useEditorTabs = ({ position: 'right', }, { - id: 'source-code', - title: 'SourceCode', - component: SourceCode, + id: 'source-code-yaml', + title: 'YAML', + component: () => <SourceCode format="yaml" />, + }, + { + id: 'source-code-json', + title: 'JSON', + component: () => <SourceCode format="json" />, }, ...rightTabs, ], diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 18a882223..5e5ef9196 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -19,7 +19,7 @@ import { export interface EditorMethods { initialize(): void; - setSelectedBlock(path: number[]): void; + setSelectedBlock(path?: number[]): void; setHeight(height: number): void; setZoom(zoom: number): void; increaseZoom(): void; diff --git a/editor-v2/styles/root.scss b/editor-v2/styles/root.scss index 25fa51c5a..93d3ef75a 100644 --- a/editor-v2/styles/root.scss +++ b/editor-v2/styles/root.scss @@ -1,2 +1,7 @@ -body { +.g-root { + --g-color-base-brand: var(--g-color-text-primary); + --g-color-base-brand-hover: var(--g-color-text-complementary); + --g-color-text-brand-contrast: var(--g-color-text-light-primary); + --g-color-line-brand: var(--g-color-text-primary); + --g-color-text-brand: var(--g-color-private-brand-700-solid); } From c45ceaccfe522c4b569c42a67e14914ecc1b65ba Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 10 Mar 2025 00:16:29 +0300 Subject: [PATCH 12/84] feat: tree selected block style --- editor-v2/containers/Tree/Tree.scss | 4 + editor-v2/containers/Tree/Tree.tsx | 45 ++++++++-- editor-v2/hooks/useEditorTabs.tsx | 125 +++++++++++++++------------- playground/src/app/page.tsx | 24 +++--- 4 files changed, 119 insertions(+), 79 deletions(-) diff --git a/editor-v2/containers/Tree/Tree.scss b/editor-v2/containers/Tree/Tree.scss index ca1b5f538..d4b28e8da 100644 --- a/editor-v2/containers/Tree/Tree.scss +++ b/editor-v2/containers/Tree/Tree.scss @@ -22,6 +22,10 @@ $block: '.#{$ns}tree'; padding: 8px; margin-bottom: 8px; + &_selected { + border: 1.5px var(--g-color-line-brand) solid; + } + &:last-child { margin-bottom: 0; } diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index b78916110..8e192d068 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -1,9 +1,10 @@ import {TrashBin} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; -import React, {PropsWithChildren} from 'react'; +import React, {PropsWithChildren, useMemo} from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {generateChildrenPathFromArray} from '../../utils'; import {editorCn} from '../../utils/cn'; import './Tree.scss'; @@ -19,6 +20,11 @@ type TreeItem = { children?: TreeItem[]; }; +interface ItemOptions { + deepLevel?: number; + parentIndex?: number; +} + const generateTree = (items: TreeItem[]): TreeItem[] => { return items.map((item) => { let children; @@ -35,17 +41,38 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { }; const Tree = (_p: PropsWithChildren<TreeProps>) => { - const {content, resetBlocks} = useMainEditorStore(); + const {content, resetBlocks, selectedBlock} = useMainEditorStore(); + + const selectedBlockPath = useMemo(() => { + return generateChildrenPathFromArray(selectedBlock || []); + }, [selectedBlock]); const blockTree = generateTree(content.blocks); - const renderTree = (items: TreeItem[], deepLevel = 0) => { - return items.map(({type, children}, index) => ( - <React.Fragment key={index}> - <Card className={b('item', {deep: deepLevel})}>{type}</Card> - {children && renderTree(children, deepLevel + 1)} - </React.Fragment> - )); + const renderTree = (items: TreeItem[], {deepLevel = 0, parentIndex}: ItemOptions = {}) => { + return items.map(({type, children}, index) => { + let blockPathArray; + if (parentIndex) { + blockPathArray = [parentIndex, index]; + } else { + blockPathArray = [index]; + } + const blockPath = generateChildrenPathFromArray(blockPathArray); + return ( + <React.Fragment key={index}> + <Card + className={b('item', { + deep: deepLevel, + selected: blockPath === selectedBlockPath, + })} + > + {type} + </Card> + {children && + renderTree(children, {deepLevel: deepLevel + 1, parentIndex: index})} + </React.Fragment> + ); + }); }; return ( diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index 6dbc5b65b..ee861e99a 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -1,3 +1,5 @@ +import {useMemo} from 'react'; + import Tabs, {TabsItemProps} from '../components/Tabs/Tabs'; import BlockConfig from '../containers/BlockConfig/BlockConfig'; import BlocksList from '../containers/BlocksList/BlocksList'; @@ -12,63 +14,68 @@ export const useEditorTabs = ({ leftTabs?: TabsItemProps[]; rightTabs?: TabsItemProps[]; }) => { - return { - left: [ - { - id: 'page', - title: 'Page', - component: () => ( - <Tabs - items={[ - { - id: 'blocks-list', - title: 'Blocks', - component: BlocksList, - }, - { - id: 'tree', - title: 'Tree', - component: Tree, - }, - { - id: 'source-code-yaml', - title: 'YAML', - component: () => <SourceCode format="yaml" />, - }, - { - id: 'source-code-json', - title: 'JSON', - component: () => <SourceCode format="json" />, - }, - ]} - /> - ), - }, - { - id: 'global-config', - title: 'GlobalConfig', - component: GlobalConfig, - }, - ...leftTabs, - ], - right: [ - { - id: 'block-config', - title: 'BlockConfig', - component: BlockConfig, - position: 'right', - }, - { - id: 'source-code-yaml', - title: 'YAML', - component: () => <SourceCode format="yaml" />, - }, - { - id: 'source-code-json', - title: 'JSON', - component: () => <SourceCode format="json" />, - }, - ...rightTabs, - ], - }; + const tabs = useMemo( + () => ({ + left: [ + { + id: 'page', + title: 'Page', + component: () => ( + <Tabs + items={[ + { + id: 'blocks-list', + title: 'Blocks', + component: BlocksList, + }, + { + id: 'tree', + title: 'Tree', + component: Tree, + }, + { + id: 'source-code-yaml', + title: 'YAML', + component: () => <SourceCode format="yaml" />, + }, + { + id: 'source-code-json', + title: 'JSON', + component: () => <SourceCode format="json" />, + }, + ]} + /> + ), + }, + { + id: 'global-config', + title: 'GlobalConfig', + component: GlobalConfig, + }, + ...leftTabs, + ], + right: [ + { + id: 'block-config', + title: 'BlockConfig', + component: BlockConfig, + position: 'right', + }, + { + id: 'source-code-yaml', + title: 'YAML', + component: () => <SourceCode format="yaml" />, + }, + { + id: 'source-code-json', + title: 'JSON', + component: () => <SourceCode format="json" />, + }, + ...rightTabs, + ], + }), + [], + ); + + return tabs; }; diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index b817c3893..8886fbcdd 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -13,6 +13,18 @@ const b = block('home'); const Test = () => <div className={b('test')}>custom test</div>; +export const COMPONENTS_CONFIG = { + middleTop: Test, + leftTabs: [ + { + id: 'test', + title: 'TEST', + component: Test, + }, + ], + rightTop: [Source, ViewSwitches], +}; + export default function Home() { const [initialUrl, setInitialUrl] = useState<string>(''); @@ -27,17 +39,7 @@ export default function Home() { <div className={b()}> {initialUrl && ( <Editor - componentsConfig={{ - middleTop: Test, - leftTabs: [ - { - id: 'test', - title: 'TEST', - component: Test, - }, - ], - rightTop: [Source, ViewSwitches], - }} + componentsConfig={COMPONENTS_CONFIG} initialUrl={initialUrl} disableUrlField={false} onUpdate={() => {}} From c90949e422dfd66046491e72f6fb5a268da5c7cf Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 10 Mar 2025 15:39:40 +0300 Subject: [PATCH 13/84] feat: editor global config navigation --- common/store.ts | 4 ++-- common/types/actions.ts | 4 ++-- common/types/forms.ts | 4 ++-- editor-v2/components/DynamicForm/DynamicForm.tsx | 3 +-- .../components/DynamicForm/Fields/Array/Array.tsx | 3 +-- .../components/DynamicForm/Fields/Object/Object.tsx | 3 +-- .../components/DynamicForm/Fields/OneOf/OneOf.tsx | 3 +-- editor-v2/components/DynamicForm/utils.ts | 4 ++-- editor-v2/containers/Editor/Editor.tsx | 4 ++-- editor-v2/containers/GlobalConfig/GlobalConfig.tsx | 8 ++++++-- editor-v2/store.ts | 4 ++-- src/containers/PageConstructor/PageConstructor.tsx | 8 +++++--- src/hooks/usePCEditorInitializeEvents.ts | 6 +++--- src/models/constructor.ts | 12 +++++++++++- src/models/navigation.ts | 4 ++-- .../DesktopNavigation/DesktopNavigation.tsx | 4 ++-- src/navigation/components/Navigation/Navigation.tsx | 4 ++-- src/navigation/containers/Layout/Layout.tsx | 3 ++- src/navigation/models.ts | 2 +- src/utils/form-generator.ts | 2 +- src/utils/index.ts | 1 + src/utils/navigation.ts | 12 ++++++++++++ 22 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 src/utils/navigation.ts diff --git a/common/store.ts b/common/store.ts index a01179922..820ee2811 100644 --- a/common/store.ts +++ b/common/store.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {PageContent} from '../src/models'; +import {Content} from '../src/models'; import {ConfigInput, ItemConfig} from './types'; import {initializeStore} from './utils'; @@ -13,7 +13,7 @@ export interface EditorState { selectedBlock?: number[]; initialized: boolean; - content: PageContent; + content: Content; blocks: Array<ItemConfig>; subBlocks: Array<ItemConfig>; global: Array<ConfigInput>; diff --git a/common/types/actions.ts b/common/types/actions.ts index c0add108a..16114a740 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -1,4 +1,4 @@ -import {PageContent} from '../../src/models'; +import {Content} from '../../src/models'; import {EditorState} from '../store'; export type MessageTypes = EventMessageTypes & ActionMessageTypes; @@ -12,7 +12,7 @@ export type EventMessageTypes = { ON_CLICK_BLOCK: {path: number[]; rect: DOMRect}; ON_RESIZE_BLOCK: {rect: DOMRect}; ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; - ON_INITIAL_CONTENT: PageContent; + ON_INITIAL_CONTENT: Content; }; export type ActionMessageTypes = { diff --git a/common/types/forms.ts b/common/types/forms.ts index f4e21a757..d6377b356 100644 --- a/common/types/forms.ts +++ b/common/types/forms.ts @@ -1,6 +1,6 @@ -import {PageContent} from '../../src/models'; +import {Content} from '../../src/models'; -export type DynamicFormValue = string | number | [] | object | boolean | PageContent | undefined; +export type DynamicFormValue = string | number | [] | object | boolean | Content | undefined; export interface BlockConfig { name: string; diff --git a/editor-v2/components/DynamicForm/DynamicForm.tsx b/editor-v2/components/DynamicForm/DynamicForm.tsx index 2051840ed..3ed3cf233 100644 --- a/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -2,7 +2,6 @@ import _ from 'lodash'; import React, {useCallback} from 'react'; import {ConfigInput, DynamicFormValue} from '../../../common/types'; -import {PageContent} from '../../../src/models'; import {editorCn} from '../../utils/cn'; import './DynamicForm.scss'; @@ -20,7 +19,7 @@ const b = editorCn('dynamic-form'); interface DynamicFormProps { blockConfig: Array<ConfigInput>; - contentConfig: PageContent; + contentConfig?: DynamicFormValue; onUpdate: (key: string, value: DynamicFormValue) => void; className?: string; } diff --git a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 3342ab369..25dba84c7 100644 --- a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -3,7 +3,6 @@ import {Button, Card, Icon} from '@gravity-ui/uikit'; import React, {useCallback} from 'react'; import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; -import {PageContent} from '../../../../../src/models'; import {removeFromArray, swapArrayItems} from '../../../../utils'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; @@ -97,7 +96,7 @@ const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: Ar {arrayItemButton} </div> <DynamicForm - contentConfig={value as PageContent} + contentConfig={value} blockConfig={blockConfig.properties} onUpdate={(key, updateValue) => onUpdate(`[${index}].${key}`, updateValue) diff --git a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index b8b67f780..c5df74f10 100644 --- a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -2,7 +2,6 @@ import {Card} from '@gravity-ui/uikit'; import React from 'react'; import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; -import {PageContent} from '../../../../../src/models'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; @@ -12,7 +11,7 @@ import './Object.scss'; const b = editorCn('object-dynamic-field'); interface ObjectDynamicFieldProps extends FieldBaseParams { - value: PageContent; + value: DynamicFormValue; onUpdate: (key: string, value: DynamicFormValue) => void; blockConfig: Array<ConfigInput>; className?: string; diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index eda4874f6..48f34e68e 100644 --- a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -2,7 +2,6 @@ import {Card, RadioButton} from '@gravity-ui/uikit'; import React, {useCallback, useMemo, useState} from 'react'; import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; -import {PageContent} from '../../../../../src/models'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; @@ -12,7 +11,7 @@ import './OneOf.scss'; const b = editorCn('oneof-dynamic-field'); interface OneOfDynamicFieldProps { - contentConfig: PageContent; + contentConfig: DynamicFormValue; onUpdate: (key: string, value: DynamicFormValue) => void; inputConfig: OneOfInput; className?: string; diff --git a/editor-v2/components/DynamicForm/utils.ts b/editor-v2/components/DynamicForm/utils.ts index 8a83dc1cb..5ad3f80b7 100644 --- a/editor-v2/components/DynamicForm/utils.ts +++ b/editor-v2/components/DynamicForm/utils.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {PageContent} from '../../../src/models'; +import {DynamicFormValue} from '../../../common/types'; export const getFullPath = (path: string, name: string) => { if (!path && !name) { @@ -18,6 +18,6 @@ export const getFullPath = (path: string, name: string) => { return path + '.' + name; }; -export const getContent = (contentConfig: PageContent, path: string) => { +export const getContent = (contentConfig: DynamicFormValue, path: string) => { return path ? _.get(contentConfig, path) : contentConfig; }; diff --git a/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx index 83eeee221..7bc9611cc 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -1,6 +1,6 @@ import React, {ElementType, useCallback} from 'react'; -import {PageContent} from '../../../src/models'; +import {Content} from '../../../src/models'; import BigOverlay from '../../components/BigOverlay/BigOverlay'; import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; import {Panels} from '../../components/Panels/Panels'; @@ -23,7 +23,7 @@ interface SidebarTabComponent { component: ElementType; } interface EditorViewProps { - onUpdate?: (pageContent: PageContent) => void; + onUpdate?: (pageContent: Content) => void; initialUrl: string; disableUrlField?: boolean; componentsConfig?: { diff --git a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx index 0a3d2eb48..f77241a43 100644 --- a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx +++ b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx @@ -17,13 +17,17 @@ const GlobalConfig = ({className}: GlobalConfigProps) => { const {global, content, updateField} = useMainEditorStore(); const onUpdate = (key: string, value: DynamicFormValue) => { - updateField(key, value); + updateField('navigation.' + key, value); }; return ( <div className={b(null, className)}> <div className={b('title')}>Global Config</div> - <DynamicForm contentConfig={content} blockConfig={global} onUpdate={onUpdate} /> + <DynamicForm + contentConfig={content.navigation} + blockConfig={global} + onUpdate={onUpdate} + /> </div> ); }; diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 5e5ef9196..48c3aaf91 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; import {EditorState, initialStore} from '../common/store'; import {DynamicFormValue} from '../common/types'; import {initializeStore} from '../common/utils'; -import {ConstructorBlock, PageContent} from '../src/models'; +import {ConstructorBlock, Content} from '../src/models'; import {ZOOM_STEPS} from './constants'; import { @@ -25,7 +25,7 @@ export interface EditorMethods { increaseZoom(): void; decreaseZoom(): void; setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; - setContent(data: PageContent): void; + setContent(data: Content): void; insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; enableInsertMode(blockType: string): void; enableReorderMode(path: number[]): void; diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index e0442e2da..58412bec7 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -15,6 +15,7 @@ import {usePCEditorInitializeEvents} from '../../hooks/usePCEditorInitializeEven import {usePCEditorStore} from '../../hooks/usePCEditorStore'; import { BlockTypes, + Content, CustomConfig, CustomItems, HeaderBlockTypes, @@ -61,13 +62,14 @@ export const Constructor = (props: PageConstructorProps) => { microdata, } = props; - const [content, setContent] = useState<PageContent>({blocks, background}); + const [content, setContent] = useState<Content>({blocks, background, navigation}); + const theme = useTheme(); const store = usePCEditorStore(); const {initialized} = store; - usePCEditorInitializeEvents({initialContent: {blocks, background}, setContent}); + usePCEditorInitializeEvents({initialContent: {blocks, background, navigation}, setContent}); const {context} = useMemo( () => ({ @@ -109,7 +111,7 @@ export const Constructor = (props: PageConstructorProps) => { {themedBackground && ( <BackgroundMedia {...themedBackground} className={b('background')} /> )} - <Layout navigation={navigation}> + <Layout navigation={content.navigation}> {renderMenu && renderMenu()} {restBlocks && ( <ConstructorRow> diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts index 3053b2331..9dec24459 100644 --- a/src/hooks/usePCEditorInitializeEvents.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import {ItemConfig} from '../../common/types'; import {blockDataMap} from '../constructor-items'; -import {PageContent} from '../models'; +import {Content} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; import {generateFromAJV} from '../utils/form-generator'; @@ -13,8 +13,8 @@ import {usePCEditorStore} from './usePCEditorStore'; import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; interface UseEditorInitializeProps { - initialContent: PageContent; - setContent: (content: PageContent) => void; + initialContent: Content; + setContent: (content: Content) => void; } export const usePCEditorInitializeEvents = ({ diff --git a/src/models/constructor.ts b/src/models/constructor.ts index 786100727..ed7b0068c 100644 --- a/src/models/constructor.ts +++ b/src/models/constructor.ts @@ -1,6 +1,12 @@ import * as React from 'react'; -import {Animatable, BlockDecorationProps, ConstructorItem, ThemedMediaProps} from './'; +import { + Animatable, + BlockDecorationProps, + ConstructorItem, + NavigationData, + ThemedMediaProps, +} from './'; export interface PageData { content: PageContent; @@ -18,6 +24,10 @@ export interface PageContent extends Animatable { background?: ThemedMediaProps; } +export interface Content extends PageContent { + navigation?: NavigationData; +} + export interface InitConstrucorState { hasMenu: boolean; } diff --git a/src/models/navigation.ts b/src/models/navigation.ts index 0f1e8bc8b..977749861 100644 --- a/src/models/navigation.ts +++ b/src/models/navigation.ts @@ -120,8 +120,8 @@ export interface FooterData { } export interface NavigationData { - logo: ThemedNavigationLogoData; - header: HeaderData; + logo?: ThemedNavigationLogoData; + header?: HeaderData; footer?: FooterData; renderNavigation?: () => React.ReactNode; } diff --git a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx index 138594aa8..3ee691bc4 100644 --- a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx +++ b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import OverflowScroller from '../../../components/OverflowScroller/OverflowScroller'; -import {block} from '../../../utils'; +import {block, isLogoSet} from '../../../utils'; import {DesktopNavigationProps, ItemColumnName, NavigationLayout} from '../../models'; import Logo from '../Logo/Logo'; import {MobileMenuButton} from '../MobileMenuButton/MobileMenuButton'; @@ -22,7 +22,7 @@ export const DesktopNavigation: React.FC<DesktopNavigationProps> = ({ activeItemId, }) => ( <div className={b('wrapper')}> - {logo && ( + {isLogoSet(logo) && ( <div className={b('left')}> <Logo {...logo} className={b('logo')} /> </div> diff --git a/src/navigation/components/Navigation/Navigation.tsx b/src/navigation/components/Navigation/Navigation.tsx index a3161fcef..2481cdbca 100644 --- a/src/navigation/components/Navigation/Navigation.tsx +++ b/src/navigation/components/Navigation/Navigation.tsx @@ -25,13 +25,13 @@ export const Navigation: React.FC<NavigationComponentProps> = ({ mobilePortalContainer, }) => { const { - leftItems, + leftItems = [], rightItems, customMobileHeaderItems, iconSize = 20, withBorder = false, withBorderOnScroll = true, - } = data; + } = data || {}; const [isSidebarOpened, setIsSidebarOpened] = React.useState(false); const [showBorder] = useShowBorder(withBorder, withBorderOnScroll); diff --git a/src/navigation/containers/Layout/Layout.tsx b/src/navigation/containers/Layout/Layout.tsx index cc72bdf87..c05e184df 100644 --- a/src/navigation/containers/Layout/Layout.tsx +++ b/src/navigation/containers/Layout/Layout.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {NavigationData} from '../../../models'; -import {block} from '../../../utils'; +import {block, isHeaderSet, isLogoSet} from '../../../utils'; import Navigation from '../../components/Navigation/Navigation'; import './Layout.scss'; @@ -16,6 +16,7 @@ export interface LayoutProps { const Layout: React.FC<LayoutProps> = ({children, navigation}) => ( <div className={b()}> {navigation && + (isLogoSet(navigation.logo) || isHeaderSet(navigation.header)) && (navigation.renderNavigation ? ( navigation.renderNavigation() ) : ( diff --git a/src/navigation/models.ts b/src/navigation/models.ts index 010db51be..b6cc530bd 100644 --- a/src/navigation/models.ts +++ b/src/navigation/models.ts @@ -63,7 +63,7 @@ export interface ItemsWrapperProps ClassNameProps {} export interface DesktopNavigationProps extends MobileMenuButtonProps, ActiveItemProps { - logo: ThemedNavigationLogoData; + logo?: ThemedNavigationLogoData; leftItemsWithIconSize: NavigationItemModel[]; rightItemsWithIconSize?: NavigationItemModel[]; customMobileHeaderItems?: NavigationItemModel[]; diff --git a/src/utils/form-generator.ts b/src/utils/form-generator.ts index 9c8af8ca0..03d4ee6c7 100644 --- a/src/utils/form-generator.ts +++ b/src/utils/form-generator.ts @@ -9,7 +9,7 @@ import { SelectBaseInput, TextAreaInput, TextInput, -} from '../common/types'; +} from '../../common/types'; export const generateFromAJV = (schema: JSONSchemaType<{}>): ConfigInput[] => { if (schema && schema.properties) { diff --git a/src/utils/index.ts b/src/utils/index.ts index b25a11deb..e188c094a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,6 +5,7 @@ export * from './url'; export * from './cn'; export * from './url'; export * from './theme'; +export * from './navigation'; export type {HubspotEventData, HubspotEventHandlers, HubspotEventName} from './hubspot'; export {isHubspotEventData} from './hubspot'; diff --git a/src/utils/navigation.ts b/src/utils/navigation.ts new file mode 100644 index 000000000..080f95073 --- /dev/null +++ b/src/utils/navigation.ts @@ -0,0 +1,12 @@ +import _get from 'lodash/get'; +import _isEmpty from 'lodash/isEmpty'; + +import {HeaderData, ThemedNavigationLogoData} from '../models'; + +export function isLogoSet(logo?: ThemedNavigationLogoData): logo is ThemedNavigationLogoData { + return Boolean(_get(logo, 'icon') || _get(logo, 'light.icon')); +} + +export function isHeaderSet(header?: HeaderData): header is HeaderData { + return !_isEmpty(header?.leftItems); +} From 6b65bec8f1f5407e62958250dba3fd9747491c3c Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 10 Mar 2025 16:42:01 +0300 Subject: [PATCH 14/84] feat: editor tree tab --- editor-v2/containers/Tree/Tree.scss | 26 ++++++++------------------ editor-v2/containers/Tree/Tree.tsx | 22 +++++++++++++--------- editor-v2/hooks/useEditorTabs.tsx | 14 +++++++------- editor-v2/utils/index.ts | 9 +++++++++ 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/editor-v2/containers/Tree/Tree.scss b/editor-v2/containers/Tree/Tree.scss index d4b28e8da..40ba33064 100644 --- a/editor-v2/containers/Tree/Tree.scss +++ b/editor-v2/containers/Tree/Tree.scss @@ -14,8 +14,12 @@ $block: '.#{$ns}tree'; justify-content: space-between; } + &__type { + color: var(--g-color-text-dark-secondary); + } + &__title { - @include text-subheader-3; + @include overflow-ellipsis(); } &__item { @@ -29,23 +33,9 @@ $block: '.#{$ns}tree'; &:last-child { margin-bottom: 0; } + } - @for $i from 1 through 9 { - &_deep_#{$i} { - position: relative; - margin-left: 16px * $i; - width: calc(100% - 16px * $i); - - &:before { - content: ''; - position: absolute; - right: calc(100% + 12px); - height: 100%; - width: 2px; - background-color: var(--g-color-line-generic-accent); - top: 0; - } - } - } + &__children { + margin-top: 8px; } } diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index 8e192d068..3f7eb7341 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -4,7 +4,7 @@ import React, {PropsWithChildren, useMemo} from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; -import {generateChildrenPathFromArray} from '../../utils'; +import {generateChildrenPathFromArray, getItemTitle} from '../../utils'; import {editorCn} from '../../utils/cn'; import './Tree.scss'; @@ -18,6 +18,7 @@ export interface TreeProps { type TreeItem = { type: string; children?: TreeItem[]; + treeTitle?: string; }; interface ItemOptions { @@ -36,6 +37,7 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { return { type: item.type, children, + treeTitle: getItemTitle(item), }; }); }; @@ -49,9 +51,9 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { const blockTree = generateTree(content.blocks); - const renderTree = (items: TreeItem[], {deepLevel = 0, parentIndex}: ItemOptions = {}) => { - return items.map(({type, children}, index) => { - let blockPathArray; + const renderTree = (items: TreeItem[], {parentIndex}: ItemOptions = {}) => { + return items.map(({type, children, treeTitle}, index) => { + let blockPathArray: number[]; if (parentIndex) { blockPathArray = [parentIndex, index]; } else { @@ -62,14 +64,17 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { <React.Fragment key={index}> <Card className={b('item', { - deep: deepLevel, selected: blockPath === selectedBlockPath, })} > - {type} + <div className={b('type')}>{type}</div> + <div className={b('title')}>{treeTitle}</div> + {children && ( + <div className={b('children')}> + {renderTree(children, {parentIndex: index})} + </div> + )} </Card> - {children && - renderTree(children, {deepLevel: deepLevel + 1, parentIndex: index})} </React.Fragment> ); }); @@ -78,7 +83,6 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { return ( <div className={b()}> <div className={b('head')}> - <div className={b('title')}>Tree</div> <div className={b('actions')}> <Button view="outlined-danger" onClick={() => resetBlocks()}> <Icon data={TrashBin} /> diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index ee861e99a..2716fa93a 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -19,18 +19,18 @@ export const useEditorTabs = ({ left: [ { id: 'page', - title: 'Page', + title: 'PAGE', component: () => ( <Tabs items={[ { id: 'blocks-list', - title: 'Blocks', + title: 'BLOCKS', component: BlocksList, }, { - id: 'tree', - title: 'Tree', + id: 'layers', + title: 'LAYERS', component: Tree, }, { @@ -48,8 +48,8 @@ export const useEditorTabs = ({ ), }, { - id: 'global-config', - title: 'GlobalConfig', + id: 'navigation', + title: 'NAVIGATION', component: GlobalConfig, }, ...leftTabs, @@ -57,7 +57,7 @@ export const useEditorTabs = ({ right: [ { id: 'block-config', - title: 'BlockConfig', + title: 'INPUTS', component: BlockConfig, position: 'right', }, diff --git a/editor-v2/utils/index.ts b/editor-v2/utils/index.ts index 8bb7e96fd..9b416b484 100644 --- a/editor-v2/utils/index.ts +++ b/editor-v2/utils/index.ts @@ -166,3 +166,12 @@ export const getUrlOrigin = (url: string) => { return undefined; } }; + +export const getItemTitle = (item: object): string | undefined => { + return ( + _.get(item, 'title.text') || + _.get(item, 'title') || + _.get(item, 'textContent.title') || + _.get(item, 'content.title') + ); +}; From caf8a1c3786b49a508a622994a8351b35b494004 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 10 Mar 2025 19:05:07 +0300 Subject: [PATCH 15/84] fix: editor blocks previews --- common/types/forms.ts | 1 + .../containers/BlocksList/BlocksList.scss | 42 +++++++------- .../containers/BlocksList/BlocksList.tsx | 56 +++++++++---------- src/blocks/CardLayout/index.ts | 1 + src/blocks/Header/index.ts | 1 + src/blocks/Media/index.ts | 1 + src/blocks/Slider/index.ts | 1 + src/constructor-items.ts | 3 +- 8 files changed, 51 insertions(+), 55 deletions(-) diff --git a/common/types/forms.ts b/common/types/forms.ts index d6377b356..230d29f3b 100644 --- a/common/types/forms.ts +++ b/common/types/forms.ts @@ -7,6 +7,7 @@ export interface BlockConfig { inputs: Array<ConfigInput>; group?: string; default?: object; + previewImg?: string; } export interface TextInput { diff --git a/editor-v2/containers/BlocksList/BlocksList.scss b/editor-v2/containers/BlocksList/BlocksList.scss index c2a8cfe10..6204e9b46 100644 --- a/editor-v2/containers/BlocksList/BlocksList.scss +++ b/editor-v2/containers/BlocksList/BlocksList.scss @@ -17,14 +17,12 @@ $block: '.#{$ns}blocks-list'; margin-bottom: 8px; display: flex; + flex-direction: column; align-items: center; justify-content: space-between; + text-align: center; gap: 4px; - &:last-child { - margin-bottom: 0; - } - &:active { background-color: var(--g-color-base-generic-hover); cursor: grabbing; @@ -32,38 +30,36 @@ $block: '.#{$ns}blocks-list'; } &__name { - flex: 1; + justify-self: flex-end; + color: var(--g-color-text-dark-secondary); } &__title { - @include text-subheader-3; + color: var(--g-color-text-dark-secondary); margin-bottom: 10px; - display: flex; - align-items: center; - justify-content: flex-start; } - &__subtitle { - @include text-subheader-2; - margin-bottom: 10px; - display: flex; - align-items: center; - justify-content: flex-start; + &__icon { + color: var(--g-color-text-dark-complementary); } &__group { - margin-bottom: 16px; + padding-bottom: 16px; + margin-top: 16px; + border-bottom: 1px solid var(--g-color-line-generic); - &:last-child { - margin-bottom: 0; + &:first-child { + margin-top: 0; } - } - - &__section { - margin-bottom: 20px; &:last-child { - margin-bottom: 0; + border-bottom: none; + } + + &-items { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; } } } diff --git a/editor-v2/containers/BlocksList/BlocksList.tsx b/editor-v2/containers/BlocksList/BlocksList.tsx index b2aa9b261..fe6fad109 100644 --- a/editor-v2/containers/BlocksList/BlocksList.tsx +++ b/editor-v2/containers/BlocksList/BlocksList.tsx @@ -1,5 +1,5 @@ -import {Grip, Plus} from '@gravity-ui/icons'; -import {Button, Card, Icon} from '@gravity-ui/uikit'; +import {SquareBars} from '@gravity-ui/icons'; +import {Card, Icon} from '@gravity-ui/uikit'; import _ from 'lodash'; import React, {PropsWithChildren, useCallback} from 'react'; @@ -20,7 +20,7 @@ interface BlockGroups { } const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { - const {blocks, selectedBlock, insertBlock, enableInsertMode} = useMainEditorStore(); + const {blocks, enableInsertMode} = useMainEditorStore(); const onMouseDown = useCallback( (blockType: string) => { @@ -29,14 +29,6 @@ const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { [enableInsertMode], ); - const onAddClick = useCallback( - (type: string) => { - const path = selectedBlock || [0]; - return insertBlock(path, type); - }, - [insertBlock, selectedBlock], - ); - const groups = blocks.reduce<BlockGroups>((acc, currentBlock) => { const group = currentBlock.schema.group; if (group) { @@ -58,27 +50,29 @@ const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { return ( <div className={b()}> - <div className={b('section')}> - {Object.entries(groups).map(([key, groupBlocks]) => ( - <div className={b('group')} key={key}> - <div> - {groupBlocks.map(({type, schema: {name}}) => ( - <Card - key={type} - className={b('card')} - onMouseDown={() => onMouseDown(type)} - > - <Icon data={Grip} /> - <div className={b('name')}>{name}</div> - <Button onClick={() => onAddClick(type)}> - <Icon data={Plus} /> - </Button> - </Card> - ))} - </div> + {Object.entries(groups).map(([key, groupBlocks]) => ( + <div className={b('group')} key={key}> + <div className={b('title')}>{key}</div> + <div className={b('group-items')}> + {groupBlocks.map(({type, schema: {name, previewImg}}) => ( + <Card + key={type} + className={b('card')} + onMouseDown={() => onMouseDown(type)} + > + <div> + {previewImg ? ( + <img src={previewImg} alt="" /> + ) : ( + <Icon className={b('icon')} size={45} data={SquareBars} /> + )} + </div> + <div className={b('name')}>{name}</div> + </Card> + ))} </div> - ))} - </div> + </div> + ))} </div> ); }; diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts index 13ce0f2db..70f9abfca 100644 --- a/src/blocks/CardLayout/index.ts +++ b/src/blocks/CardLayout/index.ts @@ -184,6 +184,7 @@ const CardLayoutBlockConfig: BlockData = { description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', }, + previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/card-layout-block.svg', }, }; diff --git a/src/blocks/Header/index.ts b/src/blocks/Header/index.ts index 7b9e77099..b303ebedf 100644 --- a/src/blocks/Header/index.ts +++ b/src/blocks/Header/index.ts @@ -29,6 +29,7 @@ const HeaderBlockConfig = { }, ], }, + previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/header-block.svg', }, }; diff --git a/src/blocks/Media/index.ts b/src/blocks/Media/index.ts index f9c0abb94..2b74b1236 100644 --- a/src/blocks/Media/index.ts +++ b/src/blocks/Media/index.ts @@ -53,6 +53,7 @@ const MediaBlockConfig = { image: 'https://storage.yandexcloud.net/cloud-www-assets/constructor/main/new/media-01-01.jpg', }, }, + previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/media-block.svg', }, }; diff --git a/src/blocks/Slider/index.ts b/src/blocks/Slider/index.ts index a809a55f7..8dad37425 100644 --- a/src/blocks/Slider/index.ts +++ b/src/blocks/Slider/index.ts @@ -41,6 +41,7 @@ const SliderBlockConfig = { }, ], }, + previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/card-layout-block.svg', }, }; diff --git a/src/constructor-items.ts b/src/constructor-items.ts index 9f8bc18a2..37b1a3cba 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -1,5 +1,7 @@ import React from 'react'; +import {BlockConfig} from '../common/types'; + import { BannerBlock, CardLayoutBlock, @@ -44,7 +46,6 @@ import TabsBlockConfig from './blocks/Tabs'; import TestEditorBlockConfig from './blocks/TestEditorBlock'; import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock'; import {SliderNewBlock} from './blocks/unstable'; -import {BlockConfig} from './common/types'; import {BlockType, NavigationItemType, SubBlockType} from './models'; import {GithubButton, NavigationButton, NavigationDropdown, NavigationLink} from './navigation'; import SocialIcon from './navigation/components/SocialIcon/SocialIcon'; From 88fe2f0efbe4fe30d9f7410362fef123ee633e71 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 11 Mar 2025 15:37:35 +0300 Subject: [PATCH 16/84] feat: editor tree controls --- editor-v2/containers/Tree/Tree.scss | 40 +++++++++--- editor-v2/containers/Tree/Tree.tsx | 96 +++++++++++++++++++++-------- 2 files changed, 102 insertions(+), 34 deletions(-) diff --git a/editor-v2/containers/Tree/Tree.scss b/editor-v2/containers/Tree/Tree.scss index 40ba33064..666e4f0d2 100644 --- a/editor-v2/containers/Tree/Tree.scss +++ b/editor-v2/containers/Tree/Tree.scss @@ -14,14 +14,6 @@ $block: '.#{$ns}tree'; justify-content: space-between; } - &__type { - color: var(--g-color-text-dark-secondary); - } - - &__title { - @include overflow-ellipsis(); - } - &__item { padding: 8px; margin-bottom: 8px; @@ -35,6 +27,38 @@ $block: '.#{$ns}tree'; } } + &__main { + display: flex; + flex-direction: row; + align-items: center; + + &:hover { + #{$block}__buttons { + display: flex; + } + } + } + + &__text { + flex: 1 1 auto; + min-width: 1px; + } + + &__buttons { + flex: 0 0 auto; + flex-direction: row; + gap: 4px; + display: none; + } + + &__type { + color: var(--g-color-text-dark-secondary); + } + + &__title { + @include overflow-ellipsis(); + } + &__children { margin-top: 8px; } diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index 3f7eb7341..aa3c48f38 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -1,6 +1,6 @@ -import {TrashBin} from '@gravity-ui/icons'; +import {Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; -import React, {PropsWithChildren, useMemo} from 'react'; +import React, {PropsWithChildren, useCallback, useMemo} from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -21,9 +21,13 @@ type TreeItem = { treeTitle?: string; }; -interface ItemOptions { - deepLevel?: number; - parentIndex?: number; +interface ItemProps { + type: string; + treeTitle?: string; + path: number[]; + selected: boolean; + onCopy(path: number[]): void; + onDelete(path: number[]): void; } const generateTree = (items: TreeItem[]): TreeItem[] => { @@ -42,8 +46,50 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { }); }; +const Item = ({ + type, + children, + treeTitle, + path, + selected, + onCopy, + onDelete, +}: PropsWithChildren<ItemProps>) => { + const handleCopy = useCallback(() => { + onCopy(path); + }, [onCopy, path]); + + const handleDelete = useCallback(() => { + onDelete(path); + }, [onDelete, path]); + + return ( + <Card + className={b('item', { + selected, + })} + > + <div className={b('main')}> + <div className={b('text')}> + <div className={b('type')}>{type}</div> + <div className={b('title')}>{treeTitle}</div> + </div> + <div className={b('buttons')}> + <Button view="flat" size="xs" onClick={handleCopy}> + <Icon size={12} data={Copy} /> + </Button> + <Button view="flat" size="xs" onClick={handleDelete}> + <Icon size={12} data={TrashBin} /> + </Button> + </div> + </div> + {children} + </Card> + ); +}; + const Tree = (_p: PropsWithChildren<TreeProps>) => { - const {content, resetBlocks, selectedBlock} = useMainEditorStore(); + const {content, resetBlocks, selectedBlock, duplicateBlock, deleteBlock} = useMainEditorStore(); const selectedBlockPath = useMemo(() => { return generateChildrenPathFromArray(selectedBlock || []); @@ -51,31 +97,30 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { const blockTree = generateTree(content.blocks); - const renderTree = (items: TreeItem[], {parentIndex}: ItemOptions = {}) => { - return items.map(({type, children, treeTitle}, index) => { + const renderTree = (items: TreeItem[], parentPathArray?: number[]) => { + return items.map(({type, treeTitle, children}, index) => { let blockPathArray: number[]; - if (parentIndex) { - blockPathArray = [parentIndex, index]; + if (parentPathArray) { + blockPathArray = [...parentPathArray, index]; } else { blockPathArray = [index]; } const blockPath = generateChildrenPathFromArray(blockPathArray); + return ( - <React.Fragment key={index}> - <Card - className={b('item', { - selected: blockPath === selectedBlockPath, - })} - > - <div className={b('type')}>{type}</div> - <div className={b('title')}>{treeTitle}</div> - {children && ( - <div className={b('children')}> - {renderTree(children, {parentIndex: index})} - </div> - )} - </Card> - </React.Fragment> + <Item + type={type} + treeTitle={treeTitle} + onCopy={duplicateBlock} + onDelete={deleteBlock} + key={index} + path={blockPathArray} + selected={selectedBlockPath === blockPath} + > + {children && ( + <div className={b('children')}>{renderTree(children, blockPathArray)}</div> + )} + </Item> ); }); }; @@ -90,7 +135,6 @@ const Tree = (_p: PropsWithChildren<TreeProps>) => { </Button> </div> </div> - <div className={b('cards')}>{renderTree(blockTree)}</div> </div> ); From b2f934c9eaa6c500593b33e07638468a592c6bba Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 11 Mar 2025 18:19:58 +0300 Subject: [PATCH 17/84] feat: editor source yaml json controls --- .../containers/SourceCode/SourceCode.scss | 16 +++++++ .../containers/SourceCode/SourceCode.tsx | 45 +++++++++++++++++-- .../SourceCode/UpdateModal/UpdateModal.scss | 10 +++++ .../SourceCode/UpdateModal/UpdateModal.tsx | 38 ++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss create mode 100644 editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx diff --git a/editor-v2/containers/SourceCode/SourceCode.scss b/editor-v2/containers/SourceCode/SourceCode.scss index 30f7272a7..625207caf 100644 --- a/editor-v2/containers/SourceCode/SourceCode.scss +++ b/editor-v2/containers/SourceCode/SourceCode.scss @@ -26,5 +26,21 @@ $block: '.#{$ns}source-code'; overflow: auto; border: 1px solid var(--g-color-line-generic); border-radius: 6px; + position: relative; + + &:hover { + #{$block}__controls { + display: flex; + flex-direction: row; + gap: 8px; + } + } + } + + &__controls { + display: none; + position: absolute; + top: 10px; + right: 10px; } } diff --git a/editor-v2/containers/SourceCode/SourceCode.tsx b/editor-v2/containers/SourceCode/SourceCode.tsx index 864d2f310..b0b9ec368 100644 --- a/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/editor-v2/containers/SourceCode/SourceCode.tsx @@ -1,10 +1,14 @@ +import {ArrowDownToSquare} from '@gravity-ui/icons'; +import {Button, ClipboardButton, Icon} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; -import React from 'react'; +import React, {useMemo, useState} from 'react'; +import {Content} from '../../../src/models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; import './SourceCode.scss'; +import {UpdateModal} from './UpdateModal/UpdateModal'; const b = editorCn('source-code'); @@ -14,14 +18,47 @@ interface SourceCodeProps { } const SourceCode = ({className, format}: SourceCodeProps) => { - const {content} = useMainEditorStore(); + const {content, setContent} = useMainEditorStore(); + const [isOpen, setIsOpen] = useState(false); - const text = format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); + const handleUpdate = (tempConfig: string) => { + let object: Content | undefined; + + try { + if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) { + object = JSON.parse(tempConfig); + } else { + object = yaml.load(tempConfig) as Content; + } + } catch { + // eslint-disable-next-line no-console + console.error('JSON.parse failed'); + } + + if (object) { + setContent(object); + } + + setIsOpen(false); + }; + + const textContent = useMemo(() => { + return format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); + }, [format, content]); return ( <div className={b(null, className)}> <div className={b('title')}>{format}</div> - <div className={b('code')}>{text}</div> + <div className={b('code')}> + <div className={b('content')}>{textContent}</div> + <div className={b('controls')}> + <ClipboardButton view="flat" size="xs" text={textContent} /> + <Button view="flat" size="xs" onClick={() => setIsOpen(true)}> + <Icon size={14} data={ArrowDownToSquare} /> + </Button> + </div> + </div> + <UpdateModal onApply={handleUpdate} onClose={() => setIsOpen(false)} isOpen={isOpen} /> </div> ); }; diff --git a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss new file mode 100644 index 000000000..3a4980b95 --- /dev/null +++ b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss @@ -0,0 +1,10 @@ +@import '../../../styles/variables.scss'; +@import '../../../styles/mixins.scss'; + +$block: '.#{$ns}source-code-update-modal'; + +#{$block} { + &__alert { + margin-bottom: 8px; + } +} diff --git a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx new file mode 100644 index 000000000..4f5ba2cd1 --- /dev/null +++ b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx @@ -0,0 +1,38 @@ +import {Alert, Dialog, TextArea} from '@gravity-ui/uikit'; +import React, {useState} from 'react'; + +import {editorCn} from '../../../utils/cn'; + +import './UpdateModal.scss'; + +const b = editorCn('source-code-update-modal'); + +export const UpdateModal = ({onClose, onApply, isOpen}) => { + const [tempConfig, setTempConfig] = useState(''); + const handleApply = () => { + onApply(tempConfig); + }; + return ( + <Dialog onClose={onClose} open={isOpen} size={'l'} className={b()}> + <Dialog.Header caption="New configuration" /> + <Dialog.Body> + <Alert + theme={'info'} + title={'You can use YAML or JSON'} + message={'The editor will automatically understand which format is needed.'} + className={b('alert')} + ></Alert> + <TextArea value={tempConfig} onUpdate={setTempConfig} rows={25} /> + </Dialog.Body> + <Dialog.Footer + showError={false} + listenKeyEnter={true} + preset={'default'} + textButtonApply={'Apply'} + textButtonCancel={'Cancel'} + onClickButtonApply={handleApply} + onClickButtonCancel={onClose} + /> + </Dialog> + ); +}; From 2a1bb8937dc85337551e2d14e8bfc142e90ca1d3 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 11 Mar 2025 18:37:58 +0300 Subject: [PATCH 18/84] fix: editor right tabs lvl --- editor-v2/hooks/useEditorTabs.tsx | 38 +++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index 2716fa93a..0fa30e8ae 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -56,20 +56,30 @@ export const useEditorTabs = ({ ], right: [ { - id: 'block-config', - title: 'INPUTS', - component: BlockConfig, - position: 'right', - }, - { - id: 'source-code-yaml', - title: 'YAML', - component: () => <SourceCode format="yaml" />, - }, - { - id: 'source-code-json', - title: 'JSON', - component: () => <SourceCode format="json" />, + id: 'edit', + title: 'EDIT', + component: () => ( + <Tabs + items={[ + { + id: 'block-config', + title: 'INPUTS', + component: BlockConfig, + position: 'right', + }, + { + id: 'source-code-yaml', + title: 'YAML', + component: () => <SourceCode format="yaml" />, + }, + { + id: 'source-code-json', + title: 'JSON', + component: () => <SourceCode format="json" />, + }, + ]} + /> + ), }, ...rightTabs, ], From f0f42b1775fffc47d4e53b0cfd5aa55d231e6a44 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 12 Mar 2025 12:28:56 +0300 Subject: [PATCH 19/84] feat: editor blocks search input --- .../containers/BlocksList/BlocksList.scss | 12 +++- .../containers/BlocksList/BlocksList.tsx | 58 +++++++++++-------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/editor-v2/containers/BlocksList/BlocksList.scss b/editor-v2/containers/BlocksList/BlocksList.scss index 6204e9b46..b6b793ff3 100644 --- a/editor-v2/containers/BlocksList/BlocksList.scss +++ b/editor-v2/containers/BlocksList/BlocksList.scss @@ -5,7 +5,7 @@ $block: '.#{$ns}blocks-list'; #{$block} { - padding: 12px; + padding: 16px 12px; display: flex; flex-direction: column; gap: 8px; @@ -44,8 +44,8 @@ $block: '.#{$ns}blocks-list'; } &__group { - padding-bottom: 16px; - margin-top: 16px; + padding-bottom: 12px; + margin-top: 12px; border-bottom: 1px solid var(--g-color-line-generic); &:first-child { @@ -62,4 +62,10 @@ $block: '.#{$ns}blocks-list'; gap: 8px; } } + + &__search { + &-icon { + margin-left: 8px; + } + } } diff --git a/editor-v2/containers/BlocksList/BlocksList.tsx b/editor-v2/containers/BlocksList/BlocksList.tsx index fe6fad109..9461f3154 100644 --- a/editor-v2/containers/BlocksList/BlocksList.tsx +++ b/editor-v2/containers/BlocksList/BlocksList.tsx @@ -1,7 +1,6 @@ -import {SquareBars} from '@gravity-ui/icons'; -import {Card, Icon} from '@gravity-ui/uikit'; -import _ from 'lodash'; -import React, {PropsWithChildren, useCallback} from 'react'; +import {Magnifier, SquareBars} from '@gravity-ui/icons'; +import {Card, Icon, TextInput} from '@gravity-ui/uikit'; +import React, {useCallback, useMemo, useState} from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -11,16 +10,13 @@ import './BlocksList.scss'; const b = editorCn('blocks-list'); -export interface BlocksListProps { - blocks: ItemConfig[]; -} - interface BlockGroups { [key: string]: ItemConfig[]; } -const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { +const BlocksList = () => { const {blocks, enableInsertMode} = useMainEditorStore(); + const [search, setSearch] = useState(''); const onMouseDown = useCallback( (blockType: string) => { @@ -29,27 +25,41 @@ const BlocksList = (_p: PropsWithChildren<BlocksListProps>) => { [enableInsertMode], ); - const groups = blocks.reduce<BlockGroups>((acc, currentBlock) => { - const group = currentBlock.schema.group; - if (group) { - if (!acc[group]) { - /* eslint-disable no-param-reassign */ - acc[group] = []; + const groups = useMemo(() => { + return blocks.reduce<BlockGroups>((acc, currentBlock) => { + const group = currentBlock.schema.group; + if (search && currentBlock.type.indexOf(search) === -1) { + return acc; } - acc[group].push(currentBlock); - } else { - if (!acc['other']) { - /* eslint-disable no-param-reassign */ - acc['other'] = []; + if (group) { + if (!acc[group]) { + /* eslint-disable no-param-reassign */ + acc[group] = []; + } + acc[group].push(currentBlock); + } else { + if (!acc['other']) { + /* eslint-disable no-param-reassign */ + acc['other'] = []; + } + acc['other'].push(currentBlock); } - acc['other'].push(currentBlock); - } - return acc; - }, {}); + return acc; + }, {}); + }, [blocks, search]); return ( <div className={b()}> + <div className={b('search')}> + <TextInput + hasClear + placeholder="Search block" + onUpdate={setSearch} + value={search} + startContent={<Icon className={b('search-icon')} data={Magnifier} />} + /> + </div> {Object.entries(groups).map(([key, groupBlocks]) => ( <div className={b('group')} key={key}> <div className={b('title')}>{key}</div> From 6ada999aa831eebf208242ee9c92a8100af09270 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 12 Mar 2025 12:40:36 +0300 Subject: [PATCH 20/84] fix: editor containers imports --- .../{components => containers}/BigOverlay/BigOverlay.scss | 0 .../{components => containers}/BigOverlay/BigOverlay.tsx | 0 editor-v2/containers/Editor/Editor.tsx | 4 ++-- .../{components => containers}/MiddleScreen/MiddleScreen.scss | 0 .../{components => containers}/MiddleScreen/MiddleScreen.tsx | 0 editor-v2/{components => containers}/Overlay/Overlay.scss | 0 editor-v2/{components => containers}/Overlay/Overlay.tsx | 0 7 files changed, 2 insertions(+), 2 deletions(-) rename editor-v2/{components => containers}/BigOverlay/BigOverlay.scss (100%) rename editor-v2/{components => containers}/BigOverlay/BigOverlay.tsx (100%) rename editor-v2/{components => containers}/MiddleScreen/MiddleScreen.scss (100%) rename editor-v2/{components => containers}/MiddleScreen/MiddleScreen.tsx (100%) rename editor-v2/{components => containers}/Overlay/Overlay.scss (100%) rename editor-v2/{components => containers}/Overlay/Overlay.tsx (100%) diff --git a/editor-v2/components/BigOverlay/BigOverlay.scss b/editor-v2/containers/BigOverlay/BigOverlay.scss similarity index 100% rename from editor-v2/components/BigOverlay/BigOverlay.scss rename to editor-v2/containers/BigOverlay/BigOverlay.scss diff --git a/editor-v2/components/BigOverlay/BigOverlay.tsx b/editor-v2/containers/BigOverlay/BigOverlay.tsx similarity index 100% rename from editor-v2/components/BigOverlay/BigOverlay.tsx rename to editor-v2/containers/BigOverlay/BigOverlay.tsx diff --git a/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx index 7bc9611cc..675364810 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -1,11 +1,11 @@ import React, {ElementType, useCallback} from 'react'; import {Content} from '../../../src/models'; -import BigOverlay from '../../components/BigOverlay/BigOverlay'; -import MiddleScreen from '../../components/MiddleScreen/MiddleScreen'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; import StoreViewer from '../../components/StoreViewer/StoreViewer'; +import BigOverlay from '../../containers/BigOverlay/BigOverlay'; +import MiddleScreen from '../../containers/MiddleScreen/MiddleScreen'; import {MainEditorStoreProvider} from '../../context/editorStore'; import {IframeProvider} from '../../context/iframeContext'; import {useEditorTabs} from '../../hooks/useEditorTabs'; diff --git a/editor-v2/components/MiddleScreen/MiddleScreen.scss b/editor-v2/containers/MiddleScreen/MiddleScreen.scss similarity index 100% rename from editor-v2/components/MiddleScreen/MiddleScreen.scss rename to editor-v2/containers/MiddleScreen/MiddleScreen.scss diff --git a/editor-v2/components/MiddleScreen/MiddleScreen.tsx b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx similarity index 100% rename from editor-v2/components/MiddleScreen/MiddleScreen.tsx rename to editor-v2/containers/MiddleScreen/MiddleScreen.tsx diff --git a/editor-v2/components/Overlay/Overlay.scss b/editor-v2/containers/Overlay/Overlay.scss similarity index 100% rename from editor-v2/components/Overlay/Overlay.scss rename to editor-v2/containers/Overlay/Overlay.scss diff --git a/editor-v2/components/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx similarity index 100% rename from editor-v2/components/Overlay/Overlay.tsx rename to editor-v2/containers/Overlay/Overlay.tsx From b85781b33c55781dde7121458ca602d874b5564d Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 12 Mar 2025 16:56:42 +0300 Subject: [PATCH 21/84] fix: pc item path id array for editor --- .../editor/ChildrenWrap/ChildrenWrap.tsx | 20 +++++++------------ src/components/editor/ItemWrap/ItemWrap.tsx | 18 ++++------------- .../ConstructorBlock/ConstructorBlock.tsx | 12 +++-------- .../ConstructorItem/ConstructorItem.tsx | 3 ++- .../ConstructorLoadable.tsx | 3 ++- src/context/blockIdContext/blockIdContext.ts | 4 ++-- src/hooks/usePCEditorItemWrap.ts | 20 +++++++++++++++++++ 7 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 src/hooks/usePCEditorItemWrap.ts diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx index 7ddbce1d1..f4f212cc8 100644 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -1,7 +1,6 @@ -import React, {PropsWithChildren, ReactNode, useCallback, useContext, useState} from 'react'; +import React, {PropsWithChildren, ReactNode} from 'react'; -import {BlockIdContext} from '../../../context/blockIdContext'; -import usePCEditorBlockMouseEvents from '../../../hooks/usePCEditorBlockMouseEvents'; +import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap'; import {block} from '../../../utils'; import './ChildrenWrap.scss'; @@ -12,16 +11,11 @@ export interface ChildrenWrapProps extends PropsWithChildren { checkChildren?: ReactNode; } -const ChildrenWrap = (props: ChildrenWrapProps) => { - const {children} = props; - const [element, setElement] = useState<HTMLElement | undefined>(); - const blockRef = useCallback((node: HTMLElement | null) => { - if (node !== null) { - setElement(node); - } - }, []); - const parentBlockId = useContext(BlockIdContext); - const {onMouseUp, onMouseMove} = usePCEditorBlockMouseEvents([parentBlockId, 0], element); +const ChildrenWrap = ({children}: ChildrenWrapProps) => { + const { + blockRef, + adminBlockMouseEvents: {onMouseMove, onMouseUp}, + } = usePCEditorItemWrap(); return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx index c002f418d..90e98ca0b 100644 --- a/src/components/editor/ItemWrap/ItemWrap.tsx +++ b/src/components/editor/ItemWrap/ItemWrap.tsx @@ -1,7 +1,6 @@ -import React, {PropsWithChildren, useCallback, useContext, useState} from 'react'; +import React, {PropsWithChildren} from 'react'; -import {BlockIdContext} from '../../../context/blockIdContext'; -import usePCEditorBlockMouseEvents from '../../../hooks/usePCEditorBlockMouseEvents'; +import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap'; import {block} from '../../../utils'; import './ItemWrap.scss'; @@ -12,17 +11,8 @@ export interface ItemWrapProps extends PropsWithChildren { index: number; } -const ItemWrap = (props: ItemWrapProps) => { - const [element, setElement] = useState<HTMLElement | undefined>(); - const blockRef = useCallback((node: HTMLElement | null) => { - if (node !== null) { - setElement(node); - } - }, []); - const {children, index} = props; - const parentBlockId = useContext(BlockIdContext); - const adminBlockMouseEvents = usePCEditorBlockMouseEvents([parentBlockId, index], element); - +const ItemWrap = ({index, children}: ItemWrapProps) => { + const {blockRef, adminBlockMouseEvents} = usePCEditorItemWrap(index); return ( <div ref={blockRef} className={b()} {...adminBlockMouseEvents}> {children} diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index 9d593cf90..27f8a214a 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -1,10 +1,10 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import pick from 'lodash/pick'; import BlockBase from '../../../../components/BlockBase/BlockBase'; import {BlockDecoration} from '../../../../customization/BlockDecoration'; -import usePCEditorBlockMouseEvents from '../../../../hooks/usePCEditorBlockMouseEvents'; +import {usePCEditorItemWrap} from '../../../../hooks/usePCEditorItemWrap'; import {BlockDecorationProps, ConstructorBlock as ConstructorBlockType} from '../../../../models'; import {block} from '../../../../utils'; @@ -21,13 +21,7 @@ export const ConstructorBlock = ({ data, children, }: React.PropsWithChildren<ConstructorBlockProps>) => { - const [element, setElement] = useState<HTMLElement | undefined>(); - const blockRef = useCallback((node: HTMLElement | null) => { - if (node !== null) { - setElement(node); - } - }, []); - const adminBlockMouseEvents = usePCEditorBlockMouseEvents([index], element); + const {blockRef, adminBlockMouseEvents} = usePCEditorItemWrap(index); const {type} = data; const blockBaseProps = useMemo( diff --git a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx index 6524dbd10..7b80cda6d 100644 --- a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx +++ b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx @@ -16,6 +16,7 @@ export const ConstructorItem = ({ children, }: React.PropsWithChildren<ConstructorItemProps>) => { const {itemMap} = React.useContext(InnerContext); + const parentId = React.useContext(BlockIdContext); const {type, ...rest} = data; const Component = itemMap[type] as React.ComponentType< @@ -23,7 +24,7 @@ export const ConstructorItem = ({ >; return ( - <BlockIdContext.Provider value={blockKey} key={blockKey}> + <BlockIdContext.Provider value={[...parentId, blockKey]} key={blockKey}> <Component {...rest}>{children}</Component> </BlockIdContext.Provider> ); diff --git a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx index 8d29d4f04..e2daf4d30 100644 --- a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx +++ b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx @@ -18,9 +18,10 @@ export const ConstructorLoadable = (props: ConstructorLoadableProps) => { const Component = itemMap[type] as React.Component< React.ComponentProps<(typeof itemMap)[typeof type]> >; + const parentId = useContext(BlockIdContext); return ( - <BlockIdContext.Provider value={Number(blockKey)} key={blockKey}> + <BlockIdContext.Provider value={[...parentId, Number(blockKey)]} key={blockKey}> <Loadable key={blockKey} block={block} diff --git a/src/context/blockIdContext/blockIdContext.ts b/src/context/blockIdContext/blockIdContext.ts index b621d734c..bc82a8b72 100644 --- a/src/context/blockIdContext/blockIdContext.ts +++ b/src/context/blockIdContext/blockIdContext.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -export type BlockIdContextProp = number; +export type BlockIdContextProp = number[]; -export const BlockIdContext = React.createContext<BlockIdContextProp>(NaN); +export const BlockIdContext = React.createContext<BlockIdContextProp>([]); diff --git a/src/hooks/usePCEditorItemWrap.ts b/src/hooks/usePCEditorItemWrap.ts new file mode 100644 index 000000000..c05568445 --- /dev/null +++ b/src/hooks/usePCEditorItemWrap.ts @@ -0,0 +1,20 @@ +import {useCallback, useContext, useState} from 'react'; + +import {BlockIdContext} from '../context/blockIdContext'; + +import usePCEditorBlockMouseEvents from './usePCEditorBlockMouseEvents'; + +export function usePCEditorItemWrap(index = 0) { + const [element, setElement] = useState<HTMLElement | undefined>(); + + const blockRef = useCallback((node: HTMLElement | null) => { + if (node !== null) { + setElement(node); + } + }, []); + + const parentBlockId = useContext(BlockIdContext); + const adminBlockMouseEvents = usePCEditorBlockMouseEvents([...parentBlockId, index], element); + + return {adminBlockMouseEvents, blockRef}; +} From b34055912900948bb0cf7f7f5980e8c1efd0e256 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Fri, 14 Mar 2025 16:23:26 +0300 Subject: [PATCH 22/84] fix: prettier --- editor-v2/styles/mixins.scss | 2 +- editor-v2/styles/variables.scss | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/editor-v2/styles/mixins.scss b/editor-v2/styles/mixins.scss index 63223ed64..ae4284730 100644 --- a/editor-v2/styles/mixins.scss +++ b/editor-v2/styles/mixins.scss @@ -10,4 +10,4 @@ &:hover { transform: scale($hoverScale); } -} ; +} diff --git a/editor-v2/styles/variables.scss b/editor-v2/styles/variables.scss index e8cd79a73..d44cdee40 100644 --- a/editor-v2/styles/variables.scss +++ b/editor-v2/styles/variables.scss @@ -1,6 +1,8 @@ $ns: 'pceditor-'; $editorTransitionTime: 0.2s; -$editorShadow: 0px 2px 8px rgba(0, 0, 0, 0.06), 0px 4px 24px rgba(0, 0, 0, 0.06); +$editorShadow: + 0px 2px 8px rgba(0, 0, 0, 0.06), + 0px 4px 24px rgba(0, 0, 0, 0.06); $editorControlBorderRadius: 8px; $headerHeight: 0px; From 4e3ad034ef5b40fcd50dfe2930f309b1eac5af5a Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Fri, 14 Mar 2025 23:38:05 +0300 Subject: [PATCH 23/84] fix: update react imports for linter --- common/postMessage.ts | 4 +- .../components/DynamicForm/DynamicForm.tsx | 8 +- .../DynamicForm/FieldBase/FieldBase.tsx | 6 +- .../DynamicForm/Fields/Array/Array.tsx | 12 +- .../Fields/Array/ItemButton/ItemButton.tsx | 12 +- .../DynamicForm/Fields/Boolean/Boolean.tsx | 1 - .../DynamicForm/Fields/Number/Number.tsx | 1 - .../DynamicForm/Fields/Object/Object.tsx | 1 - .../DynamicForm/Fields/OneOf/OneOf.tsx | 12 +- .../DynamicForm/Fields/Select/Select.tsx | 5 +- .../DynamicForm/Fields/Text/Text.tsx | 1 - .../DynamicForm/Fields/TextArea/TextArea.tsx | 1 - editor-v2/components/Panels/Panels.tsx | 12 +- editor-v2/components/Sidebar/Sidebar.tsx | 4 +- .../components/StoreViewer/StoreViewer.tsx | 4 +- editor-v2/components/Tabs/Tabs.tsx | 12 +- .../containers/BigOverlay/BigOverlay.tsx | 16 +- .../containers/BlockConfig/BlockConfig.tsx | 1 - .../containers/BlocksList/BlocksList.tsx | 10 +- editor-v2/containers/Editor/Editor.tsx | 12 +- .../containers/GlobalConfig/GlobalConfig.tsx | 2 - .../containers/MiddleScreen/MiddleScreen.tsx | 10 +- editor-v2/containers/Overlay/Overlay.tsx | 10 +- editor-v2/containers/Source/Source.tsx | 6 +- .../containers/SourceCode/SourceCode.tsx | 6 +- .../SourceCode/UpdateModal/UpdateModal.tsx | 4 +- editor-v2/containers/Tree/Tree.tsx | 12 +- .../containers/ViewSwitches/ViewSwitches.tsx | 1 - .../containers/__stories__/Editor.stories.tsx | 1 - .../editorStore/MainEditorStoreContext.tsx | 2 +- .../editorStore/MainEditorStoreProvider.tsx | 12 +- .../context/iframeContext/IframeContext.tsx | 2 +- .../context/iframeContext/IframeProvider.tsx | 8 +- editor-v2/hooks/useEditorTabs.tsx | 5 +- editor-v2/hooks/useMainEditorStore.ts | 4 +- editor-v2/hooks/usePostMessageEvents.ts | 4 +- editor-v2/styles/root.scss | 5 + playground/src/app/page.tsx | 6 +- playground/src/app/pc-2/page.tsx | 5 +- playground/src/app/pc/page.tsx | 1 - .../TestEditorBlock/TestEditorBlock.tsx | 4 +- .../editor/ChildrenWrap/ChildrenWrap.tsx | 6 +- src/components/editor/ItemWrap/ItemWrap.tsx | 4 +- src/constructor-items.ts | 2 +- .../PageConstructor/PageConstructor.tsx | 10 +- .../ConstructorBlock/ConstructorBlock.tsx | 4 +- .../hooks/useEditorBlockMouseEvents.tsx | 87 ++++++++++ .../PCEditorStoreContext.tsx | 2 +- .../PCEditorStoreProvider.tsx | 10 +- .../components/BigOverlay/BigOverlay.tsx | 85 ++++++++++ .../DynamicForm/Fields/Array/Array.tsx | 158 ++++++++++++++++++ .../components/CodeEditor/CodeEditor.tsx | 8 +- src/hooks/usePCEditorBlockMouseEvents.ts | 14 +- src/hooks/usePCEditorInitializeEvents.ts | 10 +- src/hooks/usePCEditorItemWrap.ts | 8 +- src/hooks/usePCEditorStore.ts | 4 +- src/hooks/usePostMessageAPI.ts | 4 +- src/utils/editor.ts | 7 +- 58 files changed, 497 insertions(+), 171 deletions(-) create mode 100644 src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx create mode 100644 src/editor-v2/components/BigOverlay/BigOverlay.tsx create mode 100644 src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx diff --git a/common/postMessage.ts b/common/postMessage.ts index d96c84b76..8faadd6fd 100644 --- a/common/postMessage.ts +++ b/common/postMessage.ts @@ -1,4 +1,4 @@ -import {useEffect} from 'react'; +import * as React from 'react'; import {ActionMessageTypes, EventMessageTypes, PostMessageAPIMessage} from './types'; @@ -37,7 +37,7 @@ export function usePostMessageAPIListener<K extends keyof EventMessageTypes>( callback: (data: EventMessageTypes[K]) => void, deps: unknown[] = [], ) { - useEffect(() => { + React.useEffect(() => { return listenPostMessageEvents(action, callback); }, [...deps]); } diff --git a/editor-v2/components/DynamicForm/DynamicForm.tsx b/editor-v2/components/DynamicForm/DynamicForm.tsx index 3ed3cf233..9531c2181 100644 --- a/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -1,5 +1,5 @@ import _ from 'lodash'; -import React, {useCallback} from 'react'; +import * as React from 'react'; import {ConfigInput, DynamicFormValue} from '../../../common/types'; import {editorCn} from '../../utils/cn'; @@ -27,7 +27,7 @@ interface DynamicFormProps { const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) => { const inputs = blockConfig; - const getData = useCallback( + const getData = React.useCallback( (variable: string) => { if (variable.startsWith('block.')) { const purePath = variable.replace('block.', ''); @@ -47,7 +47,7 @@ const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) = [contentConfig], ); - const decide = useCallback( + const decide = React.useCallback( (showIf: string): boolean => { const parts = showIf.split(' '); @@ -71,7 +71,7 @@ const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) = [getData], ); - const renderInput = useCallback( + const renderInput = React.useCallback( (input: ConfigInput) => { const fieldPath = input.name; const fieldValue = getContent(contentConfig, input.name); diff --git a/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx index 2def6c0d0..3b4dc5a55 100644 --- a/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx +++ b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -1,7 +1,7 @@ import {ArrowRotateLeft} from '@gravity-ui/icons'; import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit'; import _ from 'lodash'; -import React, {PropsWithChildren, useState} from 'react'; +import * as React from 'react'; import {editorCn} from '../../../utils/cn'; @@ -16,7 +16,7 @@ export interface FieldBaseParams { expandable?: boolean; } -export interface FieldBaseProps extends PropsWithChildren, FieldBaseParams { +export interface FieldBaseProps extends React.PropsWithChildren, FieldBaseParams { className?: string; } @@ -28,7 +28,7 @@ const FieldBase: React.FC<FieldBaseProps> = ({ onRefresh, expandable = false, }) => { - const [showChildren, setShowChildren] = useState(!expandable); + const [showChildren, setShowChildren] = React.useState(!expandable); const titleComponent = React.useMemo(() => { if (title) { diff --git a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 25dba84c7..295225c73 100644 --- a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -1,6 +1,6 @@ import {Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; -import React, {useCallback} from 'react'; +import * as React from 'react'; import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; import {removeFromArray, swapArrayItems} from '../../../../utils'; @@ -27,7 +27,7 @@ interface ArrayFieldProps { const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: ArrayFieldProps) => { const haveItems = values && Array.isArray(values) && values.length; - const onAddItem = useCallback(() => { + const onAddItem = React.useCallback(() => { if (blockConfig.arrayType === 'text') { onUpdate('', haveItems ? [...values, ''] : ['']); } else if (blockConfig.arrayType === 'object') { @@ -35,7 +35,7 @@ const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: Ar } }, [blockConfig.arrayType, haveItems, onUpdate, values]); - const onDeleteItem = useCallback( + const onDeleteItem = React.useCallback( (index: number) => { if (Array.isArray(values)) { const newArray = removeFromArray(values, index); @@ -45,7 +45,7 @@ const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: Ar [onUpdate, values], ); - const onReorderItem = useCallback( + const onReorderItem = React.useCallback( (index: number, placement: 'up' | 'down') => { if (Array.isArray(values)) { const newArray = swapArrayItems( @@ -59,7 +59,7 @@ const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: Ar [onUpdate, values], ); - const renderInput = useCallback( + const renderInput = React.useCallback( (value: DynamicFormValue, index: number) => { const arrayItemButton = ( <ItemButton @@ -113,7 +113,7 @@ const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: Ar [blockConfig, haveItems, onDeleteItem, onReorderItem, onUpdate, values], ); - const renderInputs = useCallback(() => { + const renderInputs = React.useCallback(() => { if (haveItems) { const renderItems = values .map(renderInput) diff --git a/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx b/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx index 05128a5b0..95b22af94 100644 --- a/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx +++ b/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx @@ -1,6 +1,6 @@ import {ArrowDown, ArrowUp, EllipsisVertical, TrashBin} from '@gravity-ui/icons'; import {Button, Icon, Menu, Popup} from '@gravity-ui/uikit'; -import React, {Fragment, useCallback, useRef, useState} from 'react'; +import * as React from 'react'; import {editorCn} from '../../../../../utils/cn'; @@ -23,10 +23,10 @@ const ItemButton = ({ disableReorderUp = false, disableReorderDown = false, }: ItemButtonProps) => { - const buttonRef = useRef(null); - const [isOpen, setIsOpen] = useState(false); + const buttonRef = React.useRef(null); + const [isOpen, setIsOpen] = React.useState(false); - const onMenuItemClickWrapper = useCallback((callback: () => void) => { + const onMenuItemClickWrapper = React.useCallback((callback: () => void) => { return () => { setIsOpen(false); callback(); @@ -34,7 +34,7 @@ const ItemButton = ({ }, []); return ( - <Fragment> + <React.Fragment> <Button className={b(null, className)} ref={buttonRef} onClick={() => setIsOpen(true)}> <Icon data={EllipsisVertical} /> </Button> @@ -68,7 +68,7 @@ const ItemButton = ({ </Menu.Item> </Menu> </Popup> - </Fragment> + </React.Fragment> ); }; diff --git a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx index 1ce0f5af2..1bacfdc8f 100644 --- a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx +++ b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx @@ -1,5 +1,4 @@ import {Switch} from '@gravity-ui/uikit'; -import React from 'react'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/Number/Number.tsx b/editor-v2/components/DynamicForm/Fields/Number/Number.tsx index 659ea5c08..6a42d71d9 100644 --- a/editor-v2/components/DynamicForm/Fields/Number/Number.tsx +++ b/editor-v2/components/DynamicForm/Fields/Number/Number.tsx @@ -1,5 +1,4 @@ import {TextInput} from '@gravity-ui/uikit'; -import React from 'react'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index c5df74f10..72ad24941 100644 --- a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -1,5 +1,4 @@ import {Card} from '@gravity-ui/uikit'; -import React from 'react'; import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 48f34e68e..941f5e070 100644 --- a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -1,5 +1,5 @@ -import {Card, RadioButton} from '@gravity-ui/uikit'; -import React, {useCallback, useMemo, useState} from 'react'; +import {Card, SegmentedRadioGroup} from '@gravity-ui/uikit'; +import * as React from 'react'; import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; @@ -33,10 +33,10 @@ const OneOfDynamicField = ({ }: OneOfDynamicFieldProps) => { const defaultValue = inputConfig.options[0].value; - const [oneOfMetaValue, setOneOfMetaValue] = useState(defaultValue); + const [oneOfMetaValue, setOneOfMetaValue] = React.useState(defaultValue); const oneOfContentConfig = getOneOfContentConfig(contentConfig, inputConfig.name); - const oneOfChosenOption = useMemo( + const oneOfChosenOption = React.useMemo( () => inputConfig.options.find( ({value: foundOneOfValue}) => foundOneOfValue === oneOfMetaValue, @@ -44,7 +44,7 @@ const OneOfDynamicField = ({ [inputConfig.options, oneOfMetaValue], ); - const onUpdateOneOf = useCallback((value: string) => { + const onUpdateOneOf = React.useCallback((value: string) => { setOneOfMetaValue(value); }, []); @@ -56,7 +56,7 @@ const OneOfDynamicField = ({ expandable > <Card className={b('card')}> - <RadioButton + <SegmentedRadioGroup className={b('radio')} options={inputConfig.options.map((option) => ({ content: option.title, diff --git a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx index ae91fca9d..a61e4e28d 100644 --- a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -1,5 +1,4 @@ -import {RadioButton, Select} from '@gravity-ui/uikit'; -import React from 'react'; +import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; @@ -30,7 +29,7 @@ const SelectDynamicField = ({input, value, onUpdate, className}: SelectDynamicFi /> )} {inputView === 'radiobutton' && ( - <RadioButton options={input.enum} value={value} onUpdate={onUpdate} /> + <SegmentedRadioGroup options={input.enum} value={value} onUpdate={onUpdate} /> )} </FieldBase> ); diff --git a/editor-v2/components/DynamicForm/Fields/Text/Text.tsx b/editor-v2/components/DynamicForm/Fields/Text/Text.tsx index 96ad2ab90..ec16c5233 100644 --- a/editor-v2/components/DynamicForm/Fields/Text/Text.tsx +++ b/editor-v2/components/DynamicForm/Fields/Text/Text.tsx @@ -1,5 +1,4 @@ import {TextInput} from '@gravity-ui/uikit'; -import React from 'react'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx b/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx index 49d399c66..acd6b81eb 100644 --- a/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx +++ b/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx @@ -1,5 +1,4 @@ import {TextArea} from '@gravity-ui/uikit'; -import React from 'react'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/Panels/Panels.tsx b/editor-v2/components/Panels/Panels.tsx index b0f2f6c87..0c128a44b 100644 --- a/editor-v2/components/Panels/Panels.tsx +++ b/editor-v2/components/Panels/Panels.tsx @@ -1,6 +1,6 @@ import {ArrowLeftFromLine, ArrowRightFromLine, Grip} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; -import React, {ReactElement, useRef} from 'react'; +import * as React from 'react'; import {ImperativePanelHandle, Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; import {editorCn} from '../../utils/cn'; @@ -10,14 +10,14 @@ import './Panels.scss'; const b = editorCn('panels'); interface PanelsProps { - left: ReactElement; - middle: ReactElement; - right: ReactElement; + left: React.ReactElement; + middle: React.ReactElement; + right: React.ReactElement; } export const Panels = ({left, right, middle}: PanelsProps) => { - const leftPanel = useRef<ImperativePanelHandle>(null); - const rightPanel = useRef<ImperativePanelHandle>(null); + const leftPanel = React.useRef<ImperativePanelHandle>(null); + const rightPanel = React.useRef<ImperativePanelHandle>(null); const expandPanel = (reference: React.RefObject<ImperativePanelHandle>) => { const panel = reference.current; diff --git a/editor-v2/components/Sidebar/Sidebar.tsx b/editor-v2/components/Sidebar/Sidebar.tsx index 1ef6cfe7c..f77cd854c 100644 --- a/editor-v2/components/Sidebar/Sidebar.tsx +++ b/editor-v2/components/Sidebar/Sidebar.tsx @@ -1,4 +1,4 @@ -import React, {ElementType} from 'react'; +import * as React from 'react'; import {editorCn} from '../../utils/cn'; import Tabs, {TabsItemProps} from '../Tabs/Tabs'; @@ -10,7 +10,7 @@ const b = editorCn('sidebar'); interface SidebarProps { tabs: TabsItemProps[]; defaultTab?: string; - top?: ElementType[]; + top?: React.ElementType[]; className?: string; } diff --git a/editor-v2/components/StoreViewer/StoreViewer.tsx b/editor-v2/components/StoreViewer/StoreViewer.tsx index 59afb58ee..753842df2 100644 --- a/editor-v2/components/StoreViewer/StoreViewer.tsx +++ b/editor-v2/components/StoreViewer/StoreViewer.tsx @@ -1,6 +1,6 @@ import {Code} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; -import React, {useState} from 'react'; +import * as React from 'react'; import ReactJson from 'react-json-view'; import {removeFn} from '../../../common/utils'; @@ -16,7 +16,7 @@ interface StoreViewerProps { } const StoreViewer = ({store, className}: StoreViewerProps) => { - const [open, setOpen] = useState(false); + const [open, setOpen] = React.useState(false); return ( <div className={b({open}, className)}> <div className={b('head')}> diff --git a/editor-v2/components/Tabs/Tabs.tsx b/editor-v2/components/Tabs/Tabs.tsx index 7c7ee710e..7c8dbd6c5 100644 --- a/editor-v2/components/Tabs/Tabs.tsx +++ b/editor-v2/components/Tabs/Tabs.tsx @@ -1,5 +1,5 @@ import {Button} from '@gravity-ui/uikit'; -import React, {ElementType, useCallback, useMemo, useState} from 'react'; +import * as React from 'react'; import {editorCn} from '../../utils/cn'; @@ -10,7 +10,7 @@ const b = editorCn('tabs'); export interface TabsItemProps { id: string; title: string; - component: ElementType; + component: React.ElementType; } export interface TabsProps { @@ -20,9 +20,9 @@ export interface TabsProps { } const Tabs = ({className, items, defaultTab}: TabsProps) => { - const [currentTab, setCurrentTab] = useState(defaultTab); + const [currentTab, setCurrentTab] = React.useState(defaultTab); - const activeTabId: string | null = useMemo(() => { + const activeTabId: string | null = React.useMemo(() => { if (currentTab) { return currentTab; } @@ -30,14 +30,14 @@ const Tabs = ({className, items, defaultTab}: TabsProps) => { return items[0].id; }, [currentTab, items]); - const handleClick = useCallback( + const handleClick = React.useCallback( (tabId: string) => () => { setCurrentTab(tabId); }, [], ); - const TabComponent = useMemo(() => { + const TabComponent = React.useMemo(() => { const findTab = items.find(({id}) => id === activeTabId); return findTab?.component; }, [activeTabId, items]); diff --git a/editor-v2/containers/BigOverlay/BigOverlay.tsx b/editor-v2/containers/BigOverlay/BigOverlay.tsx index e88d78676..34d50fefc 100644 --- a/editor-v2/containers/BigOverlay/BigOverlay.tsx +++ b/editor-v2/containers/BigOverlay/BigOverlay.tsx @@ -1,5 +1,5 @@ import {Stop} from '@gravity-ui/icons'; -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; @@ -12,17 +12,17 @@ const b = editorCn('big-overlay'); const BigOverlay = ({className}: {className?: string}) => { const {zoom, manipulateOverlayMode} = useMainEditorStore(); - const {iframeElement} = useContext(IframeContext); - const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( + const {iframeElement} = React.useContext(IframeContext); + const [mousePosition, setMousePosition] = React.useState<{x: number; y: number} | undefined>( undefined, ); - const [source, setSource] = useState<'main' | 'iframe'>('main'); + const [source, setSource] = React.useState<'main' | 'iframe'>('main'); - const onMouseUp = useCallback(() => { + const onMouseUp = React.useCallback(() => { setMousePosition(undefined); }, []); - const onIframeMouseEvent = useCallback((position: {x: number; y: number}) => { + const onIframeMouseEvent = React.useCallback((position: {x: number; y: number}) => { setMousePosition(position); setSource('iframe'); }, []); @@ -30,7 +30,7 @@ const BigOverlay = ({className}: {className?: string}) => { usePostMessageAPIListener('ON_MOUSE_UP', onMouseUp); usePostMessageAPIListener('ON_MOUSE_MOVE', onIframeMouseEvent); - useEffect(() => { + React.useEffect(() => { const onEditorMouseEvent = (event: MouseEvent) => { setMousePosition({x: event.clientX, y: event.clientY}); setSource('main'); @@ -45,7 +45,7 @@ const BigOverlay = ({className}: {className?: string}) => { }; }, []); - const realPositions = useMemo(() => { + const realPositions = React.useMemo(() => { if (mousePosition) { const {x, y} = mousePosition; const iframeRect = iframeElement?.getClientRects().item(0); diff --git a/editor-v2/containers/BlockConfig/BlockConfig.tsx b/editor-v2/containers/BlockConfig/BlockConfig.tsx index 1ccc59952..e8f126356 100644 --- a/editor-v2/containers/BlockConfig/BlockConfig.tsx +++ b/editor-v2/containers/BlockConfig/BlockConfig.tsx @@ -1,5 +1,4 @@ import _ from 'lodash'; -import React from 'react'; import {DynamicFormValue} from '../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; diff --git a/editor-v2/containers/BlocksList/BlocksList.tsx b/editor-v2/containers/BlocksList/BlocksList.tsx index 9461f3154..b8652f238 100644 --- a/editor-v2/containers/BlocksList/BlocksList.tsx +++ b/editor-v2/containers/BlocksList/BlocksList.tsx @@ -1,6 +1,6 @@ import {Magnifier, SquareBars} from '@gravity-ui/icons'; import {Card, Icon, TextInput} from '@gravity-ui/uikit'; -import React, {useCallback, useMemo, useState} from 'react'; +import * as React from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -16,16 +16,16 @@ interface BlockGroups { const BlocksList = () => { const {blocks, enableInsertMode} = useMainEditorStore(); - const [search, setSearch] = useState(''); + const [search, setSearch] = React.useState(''); - const onMouseDown = useCallback( + const onMouseDown = React.useCallback( (blockType: string) => { enableInsertMode(blockType); }, [enableInsertMode], ); - const groups = useMemo(() => { + const groups = React.useMemo(() => { return blocks.reduce<BlockGroups>((acc, currentBlock) => { const group = currentBlock.schema.group; if (search && currentBlock.type.indexOf(search) === -1) { @@ -46,7 +46,7 @@ const BlocksList = () => { } return acc; - }, {}); + }, {} as BlockGroups); }, [blocks, search]); return ( diff --git a/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx index 675364810..c15150628 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -1,4 +1,4 @@ -import React, {ElementType, useCallback} from 'react'; +import * as React from 'react'; import {Content} from '../../../src/models'; import {Panels} from '../../components/Panels/Panels'; @@ -20,16 +20,16 @@ const b = editorCn('editor'); interface SidebarTabComponent { id: string; title: string; - component: ElementType; + component: React.ElementType; } interface EditorViewProps { onUpdate?: (pageContent: Content) => void; initialUrl: string; disableUrlField?: boolean; componentsConfig?: { - middleTop?: ElementType; - leftTop?: ElementType[]; - rightTop?: ElementType[]; + middleTop?: React.ElementType; + leftTop?: React.ElementType[]; + rightTop?: React.ElementType[]; leftTabs?: SidebarTabComponent[]; rightTabs?: SidebarTabComponent[]; }; @@ -43,7 +43,7 @@ const EditorView = ({componentsConfig = {}}: EditorViewProps) => { // Disable insert mode on any MouseUp event // Maybe should be attached to body - const onMouseUp = useCallback( + const onMouseUp = React.useCallback( (e: React.MouseEvent) => { if (manipulateOverlayMode) { e.preventDefault(); diff --git a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx index f77241a43..f36055530 100644 --- a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx +++ b/editor-v2/containers/GlobalConfig/GlobalConfig.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import {DynamicFormValue} from '../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 5f7e0b8d1..7ce7b974f 100644 --- a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -1,5 +1,5 @@ import {Loader} from '@gravity-ui/uikit'; -import React, {ElementType, useCallback, useContext, useState} from 'react'; +import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; @@ -13,15 +13,15 @@ const b = editorCn('middle-screen'); interface MiddleScreenProps { className?: string; - CustomTop?: ElementType; + CustomTop?: React.ElementType; } const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { const {zoom, initialized} = useMainEditorStore(); - const {url, setIframeElement} = useContext(IframeContext); - const [height, setHeight] = useState(0); + const {url, setIframeElement} = React.useContext(IframeContext); + const [height, setHeight] = React.useState(0); - const onResize = useCallback( + const onResize = React.useCallback( (newHeight: number) => { setHeight(newHeight + 500); }, diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index c38643dfc..759d9fbb0 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -1,6 +1,6 @@ import {ChevronDown, ChevronUp, Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; -import React, {useState} from 'react'; +import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -31,9 +31,11 @@ const Overlay = ({className}: OverlayProps) => { manipulateOverlayMode, reorderBlock, } = useMainEditorStore(); - const [insertLineBox, setInsertLineBox] = useState<InsertLineProps | undefined>(undefined); - const [hoverBorders, setHoverBorders] = useState<DOMRect | null>(null); - const [blockBorders, setBlockBorders] = useState<DOMRect | null>(null); + const [insertLineBox, setInsertLineBox] = React.useState<InsertLineProps | undefined>( + undefined, + ); + const [hoverBorders, setHoverBorders] = React.useState<DOMRect | null>(null); + const [blockBorders, setBlockBorders] = React.useState<DOMRect | null>(null); const margin = 0; diff --git a/editor-v2/containers/Source/Source.tsx b/editor-v2/containers/Source/Source.tsx index 176e819e2..ead0aa6c9 100644 --- a/editor-v2/containers/Source/Source.tsx +++ b/editor-v2/containers/Source/Source.tsx @@ -1,6 +1,6 @@ import {ArrowRotateRight} from '@gravity-ui/icons'; import {Button, Icon, TextInput} from '@gravity-ui/uikit'; -import React, {useCallback, useContext} from 'react'; +import * as React from 'react'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -12,9 +12,9 @@ const b = editorCn('source'); const Source = () => { const {resetInitialize} = useMainEditorStore(); - const {disableUrlField, url, setUrl} = useContext(IframeContext); + const {disableUrlField, url, setUrl} = React.useContext(IframeContext); - const onUpdateUrl = useCallback( + const onUpdateUrl = React.useCallback( (value: string) => { setUrl(value); resetInitialize(); diff --git a/editor-v2/containers/SourceCode/SourceCode.tsx b/editor-v2/containers/SourceCode/SourceCode.tsx index b0b9ec368..4e8b0de7f 100644 --- a/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/editor-v2/containers/SourceCode/SourceCode.tsx @@ -1,7 +1,7 @@ import {ArrowDownToSquare} from '@gravity-ui/icons'; import {Button, ClipboardButton, Icon} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; -import React, {useMemo, useState} from 'react'; +import * as React from 'react'; import {Content} from '../../../src/models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -19,7 +19,7 @@ interface SourceCodeProps { const SourceCode = ({className, format}: SourceCodeProps) => { const {content, setContent} = useMainEditorStore(); - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = React.useState(false); const handleUpdate = (tempConfig: string) => { let object: Content | undefined; @@ -42,7 +42,7 @@ const SourceCode = ({className, format}: SourceCodeProps) => { setIsOpen(false); }; - const textContent = useMemo(() => { + const textContent = React.useMemo(() => { return format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); }, [format, content]); diff --git a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx index 4f5ba2cd1..4a3224adb 100644 --- a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx +++ b/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx @@ -1,5 +1,5 @@ import {Alert, Dialog, TextArea} from '@gravity-ui/uikit'; -import React, {useState} from 'react'; +import * as React from 'react'; import {editorCn} from '../../../utils/cn'; @@ -8,7 +8,7 @@ import './UpdateModal.scss'; const b = editorCn('source-code-update-modal'); export const UpdateModal = ({onClose, onApply, isOpen}) => { - const [tempConfig, setTempConfig] = useState(''); + const [tempConfig, setTempConfig] = React.useState(''); const handleApply = () => { onApply(tempConfig); }; diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index aa3c48f38..4e698f7a1 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -1,6 +1,6 @@ import {Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; -import React, {PropsWithChildren, useCallback, useMemo} from 'react'; +import * as React from 'react'; import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -54,12 +54,12 @@ const Item = ({ selected, onCopy, onDelete, -}: PropsWithChildren<ItemProps>) => { - const handleCopy = useCallback(() => { +}: React.PropsWithChildren<ItemProps>) => { + const handleCopy = React.useCallback(() => { onCopy(path); }, [onCopy, path]); - const handleDelete = useCallback(() => { + const handleDelete = React.useCallback(() => { onDelete(path); }, [onDelete, path]); @@ -88,10 +88,10 @@ const Item = ({ ); }; -const Tree = (_p: PropsWithChildren<TreeProps>) => { +const Tree = (_p: React.PropsWithChildren<TreeProps>) => { const {content, resetBlocks, selectedBlock, duplicateBlock, deleteBlock} = useMainEditorStore(); - const selectedBlockPath = useMemo(() => { + const selectedBlockPath = React.useMemo(() => { return generateChildrenPathFromArray(selectedBlock || []); }, [selectedBlock]); diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index f04ce59d8..9ad12ac3e 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,6 +1,5 @@ import {Minus, Plus} from '@gravity-ui/icons'; import {Button, Icon, Select} from '@gravity-ui/uikit'; -import React from 'react'; import {ZOOM_STEPS} from '../../constants'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; diff --git a/editor-v2/containers/__stories__/Editor.stories.tsx b/editor-v2/containers/__stories__/Editor.stories.tsx index c118f076b..f06c3746b 100644 --- a/editor-v2/containers/__stories__/Editor.stories.tsx +++ b/editor-v2/containers/__stories__/Editor.stories.tsx @@ -1,5 +1,4 @@ import {Meta, StoryFn} from '@storybook/react'; -import React from 'react'; import {Editor} from '../Editor/Editor'; diff --git a/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/editor-v2/context/editorStore/MainEditorStoreContext.tsx index 7f48605d4..1332029b7 100644 --- a/editor-v2/context/editorStore/MainEditorStoreContext.tsx +++ b/editor-v2/context/editorStore/MainEditorStoreContext.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import {StoreApi} from 'zustand'; import {EditorStore, createEditorStore} from '../../store'; diff --git a/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/editor-v2/context/editorStore/MainEditorStoreProvider.tsx index 5d3b57306..e290b9880 100644 --- a/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, useCallback, useContext, useEffect, useRef} from 'react'; +import * as React from 'react'; import {StoreApi} from 'zustand'; import {EditorState} from '../../../common/store'; @@ -9,13 +9,13 @@ import {IframeContext} from '../iframeContext'; import {MainEditorStoreContext} from './MainEditorStoreContext'; -interface MainEditorProviderProps extends PropsWithChildren {} +interface MainEditorProviderProps extends React.PropsWithChildren {} export const MainEditorStoreProvider = ({children}: MainEditorProviderProps) => { - const {iframeElement} = useContext(IframeContext); - const storeRef = useRef<StoreApi<EditorStore>>(); + const {iframeElement} = React.useContext(IframeContext); + const storeRef = React.useRef<StoreApi<EditorStore>>(); - const sendPostMessage = useCallback( + const sendPostMessage = React.useCallback( (data: EditorState) => { const message: StoreSyncMessage = { state: data, @@ -32,7 +32,7 @@ export const MainEditorStoreProvider = ({children}: MainEditorProviderProps) => storeRef.current = createEditorStore(); } - useEffect(() => { + React.useEffect(() => { storeRef.current?.subscribe((state) => { sendPostMessage(removeFn(state)); }); diff --git a/editor-v2/context/iframeContext/IframeContext.tsx b/editor-v2/context/iframeContext/IframeContext.tsx index 95c498bef..c8962762f 100644 --- a/editor-v2/context/iframeContext/IframeContext.tsx +++ b/editor-v2/context/iframeContext/IframeContext.tsx @@ -2,7 +2,7 @@ * Context for iframe window **/ -import React from 'react'; +import * as React from 'react'; export interface IframeContextProps { iframeElement?: HTMLIFrameElement; diff --git a/editor-v2/context/iframeContext/IframeProvider.tsx b/editor-v2/context/iframeContext/IframeProvider.tsx index c5a0295ce..499850d21 100644 --- a/editor-v2/context/iframeContext/IframeProvider.tsx +++ b/editor-v2/context/iframeContext/IframeProvider.tsx @@ -1,8 +1,8 @@ -import React, {PropsWithChildren, useState} from 'react'; +import * as React from 'react'; import {IframeContext} from './IframeContext'; -interface IframeProviderProps extends PropsWithChildren { +interface IframeProviderProps extends React.PropsWithChildren { initialUrl?: string; disableUrlField?: boolean; } @@ -12,8 +12,8 @@ export const IframeProvider = ({ initialUrl = '', disableUrlField, }: IframeProviderProps) => { - const [iframeElement, setIframeElement] = useState<HTMLIFrameElement>(); - const [url, setUrl] = useState(initialUrl); + const [iframeElement, setIframeElement] = React.useState<HTMLIFrameElement>(); + const [url, setUrl] = React.useState(initialUrl); const setIframeElementFunc = (element: HTMLIFrameElement) => setIframeElement(element); diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index 0fa30e8ae..fb2199017 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -1,4 +1,4 @@ -import {useMemo} from 'react'; +import * as React from 'react'; import Tabs, {TabsItemProps} from '../components/Tabs/Tabs'; import BlockConfig from '../containers/BlockConfig/BlockConfig'; @@ -14,7 +14,7 @@ export const useEditorTabs = ({ leftTabs?: TabsItemProps[]; rightTabs?: TabsItemProps[]; }) => { - const tabs = useMemo( + const tabs = React.useMemo( () => ({ left: [ { @@ -65,7 +65,6 @@ export const useEditorTabs = ({ id: 'block-config', title: 'INPUTS', component: BlockConfig, - position: 'right', }, { id: 'source-code-yaml', diff --git a/editor-v2/hooks/useMainEditorStore.ts b/editor-v2/hooks/useMainEditorStore.ts index 5ff0baf4f..541ff6540 100644 --- a/editor-v2/hooks/useMainEditorStore.ts +++ b/editor-v2/hooks/useMainEditorStore.ts @@ -1,9 +1,9 @@ -import {useContext} from 'react'; +import * as React from 'react'; import {useStore} from 'zustand'; import {MainEditorStoreContext} from '../context/editorStore'; export const useMainEditorStore = () => { - const {state} = useContext(MainEditorStoreContext); + const {state} = React.useContext(MainEditorStoreContext); return useStore(state); }; diff --git a/editor-v2/hooks/usePostMessageEvents.ts b/editor-v2/hooks/usePostMessageEvents.ts index 1b73e0cd8..cc8000843 100644 --- a/editor-v2/hooks/usePostMessageEvents.ts +++ b/editor-v2/hooks/usePostMessageEvents.ts @@ -1,4 +1,4 @@ -import {useContext} from 'react'; +import * as React from 'react'; import {requestActionPostMessage} from '../../common/postMessage'; import {ActionMessageTypes} from '../../common/types'; @@ -12,7 +12,7 @@ interface UsePostMessageRequestReturn { } export function usePostMessageEvents(): UsePostMessageRequestReturn { - const {iframeElement} = useContext(IframeContext); + const {iframeElement} = React.useContext(IframeContext); return { requestPostMessage: (action, data) => { diff --git a/editor-v2/styles/root.scss b/editor-v2/styles/root.scss index 93d3ef75a..0e2334f3a 100644 --- a/editor-v2/styles/root.scss +++ b/editor-v2/styles/root.scss @@ -1,7 +1,12 @@ .g-root { + --g-color-base-brand: var(--g-color-private-black-550-solid); + --g-color-base-brand-hover: var(--g-color-private-black-600-solid); + --g-color-base-selection: var(--g-color-private-black-200); + --g-color-base-selection-hover: var(--g-color-private-black-300); --g-color-base-brand: var(--g-color-text-primary); --g-color-base-brand-hover: var(--g-color-text-complementary); --g-color-text-brand-contrast: var(--g-color-text-light-primary); --g-color-line-brand: var(--g-color-text-primary); --g-color-text-brand: var(--g-color-private-brand-700-solid); + --g-color-text-brand-heavy: var(--g-color-private-black-700-solid); } diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index 8886fbcdd..ef1feacef 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -1,7 +1,7 @@ 'use client'; import {ThemeProvider} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import React, {useEffect, useState} from 'react'; +import * as React from 'react'; import {Editor} from '../../../editor-v2'; import Source from '../../../editor-v2/containers/Source/Source'; @@ -26,9 +26,9 @@ export const COMPONENTS_CONFIG = { }; export default function Home() { - const [initialUrl, setInitialUrl] = useState<string>(''); + const [initialUrl, setInitialUrl] = React.useState<string>(''); - useEffect(() => { + React.useEffect(() => { if (typeof window !== 'undefined') { setInitialUrl(window.location.origin + '/pc'); } diff --git a/playground/src/app/pc-2/page.tsx b/playground/src/app/pc-2/page.tsx index 9765c3c46..319e2bcf8 100644 --- a/playground/src/app/pc-2/page.tsx +++ b/playground/src/app/pc-2/page.tsx @@ -1,7 +1,6 @@ 'use client'; -import React from 'react'; -import {PageConstructor, PageConstructorProvider} from '../../../../src'; +import {NavigationData, PageConstructor, PageConstructorProvider} from '../../../../src'; import content from './content.json'; import navigation from './navigation.json'; @@ -10,7 +9,7 @@ import './styles.scss'; export default function Home() { return ( <PageConstructorProvider projectSettings={{disableCompress: true}}> - <PageConstructor content={content} navigation={navigation} /> + <PageConstructor content={content} navigation={navigation as NavigationData} /> </PageConstructorProvider> ); } diff --git a/playground/src/app/pc/page.tsx b/playground/src/app/pc/page.tsx index 88c94a9f2..5a7dbab8e 100644 --- a/playground/src/app/pc/page.tsx +++ b/playground/src/app/pc/page.tsx @@ -1,5 +1,4 @@ 'use client'; -import React from 'react'; import {PageConstructor, PageConstructorProvider} from '../../../../src'; diff --git a/src/blocks/TestEditorBlock/TestEditorBlock.tsx b/src/blocks/TestEditorBlock/TestEditorBlock.tsx index 7888c5cc1..a9922bd37 100644 --- a/src/blocks/TestEditorBlock/TestEditorBlock.tsx +++ b/src/blocks/TestEditorBlock/TestEditorBlock.tsx @@ -1,10 +1,10 @@ -import React, {PropsWithChildren} from 'react'; +import * as React from 'react'; import {block} from '../../utils'; const b = block('test-editor-block'); -export interface TestEditorBlockProps extends PropsWithChildren {} +export interface TestEditorBlockProps extends React.PropsWithChildren {} export const TestEditorBlock = (props: TestEditorBlockProps) => { return <div className={b()}>{JSON.stringify(props, null, 2)}</div>; diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx index f4f212cc8..068ece91b 100644 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, ReactNode} from 'react'; +import * as React from 'react'; import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap'; import {block} from '../../../utils'; @@ -7,8 +7,8 @@ import './ChildrenWrap.scss'; const b = block('children-wrap'); -export interface ChildrenWrapProps extends PropsWithChildren { - checkChildren?: ReactNode; +export interface ChildrenWrapProps extends React.PropsWithChildren { + checkChildren?: React.ReactNode; } const ChildrenWrap = ({children}: ChildrenWrapProps) => { diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx index 90e98ca0b..cca5b199c 100644 --- a/src/components/editor/ItemWrap/ItemWrap.tsx +++ b/src/components/editor/ItemWrap/ItemWrap.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren} from 'react'; +import * as React from 'react'; import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap'; import {block} from '../../../utils'; @@ -7,7 +7,7 @@ import './ItemWrap.scss'; const b = block('item-wrap'); -export interface ItemWrapProps extends PropsWithChildren { +export interface ItemWrapProps extends React.PropsWithChildren { index: number; } diff --git a/src/constructor-items.ts b/src/constructor-items.ts index 37b1a3cba..00b12df99 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import {BlockConfig} from '../common/types'; diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 58412bec7..b0eb1fa58 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -1,4 +1,4 @@ -import {ReactNode, useContext, useMemo, useState} from 'react'; +import * as React from 'react'; import '@diplodoc/transform/dist/js/yfm'; import {ThemeProvider} from '@gravity-ui/uikit'; @@ -43,7 +43,7 @@ export interface PageConstructorProps { content?: PageContent; shouldRenderBlock?: ShouldRenderBlock; custom?: CustomConfig; - renderMenu?: () => ReactNode; + renderMenu?: () => React.ReactNode; navigation?: NavigationData; isBranded?: boolean; microdata?: { @@ -62,7 +62,7 @@ export const Constructor = (props: PageConstructorProps) => { microdata, } = props; - const [content, setContent] = useState<Content>({blocks, background, navigation}); + const [content, setContent] = React.useState<Content>({blocks, background, navigation}); const theme = useTheme(); @@ -71,7 +71,7 @@ export const Constructor = (props: PageConstructorProps) => { usePCEditorInitializeEvents({initialContent: {blocks, background, navigation}, setContent}); - const {context} = useMemo( + const {context} = React.useMemo( () => ({ context: { blockTypes: [...BlockTypes, ...getCustomTypes(['blocks', 'headers'], custom)], @@ -127,7 +127,7 @@ export const Constructor = (props: PageConstructorProps) => { }; export const PageConstructor = (props: PageConstructorProps) => { - const {isAnimationEnabled = true} = useContext(ProjectSettingsContext); + const {isAnimationEnabled = true} = React.useContext(ProjectSettingsContext); const {content: {animated = isAnimationEnabled} = {}, ...rest} = props; return ( diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx index 27f8a214a..469f8ab48 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import * as React from 'react'; import pick from 'lodash/pick'; @@ -24,7 +24,7 @@ export const ConstructorBlock = ({ const {blockRef, adminBlockMouseEvents} = usePCEditorItemWrap(index); const {type} = data; - const blockBaseProps = useMemo( + const blockBaseProps = React.useMemo( () => pick(data, ['anchor', 'visible', 'resetPaddings', 'indent']), [data], ); diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx new file mode 100644 index 000000000..5d7de0742 --- /dev/null +++ b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; + +import _ from 'lodash'; + +import {usePCEditorStore} from '../../../../../context/editorStoreContext'; +import {sendEventPostMessage} from '../../../../../hooks/usePostMessageAPI'; +import {getCursorPositionOverElement} from '../../../../../utils/editor'; + +const useEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { + const {selectedBlock} = usePCEditorStore(); + + const onMouseUp = React.useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + const position = getCursorPositionOverElement(rect, e); + sendEventPostMessage('ON_MOUSE_UP', {path: arrayIndex, rect, position}); + } + } + }, + [arrayIndex, element], + ); + + const onMouseMove = React.useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + const position = getCursorPositionOverElement(rect, e); + sendEventPostMessage('ON_HOVER_BLOCK', {rect, position}); + } + } + }, + [element], + ); + + const onMouseLeave = React.useCallback(() => { + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_HOVER_BLOCK', {}); + } + } + }, [element]); + + const onClick = React.useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_CLICK_BLOCK', {rect, path: arrayIndex}); + } + } + }, + [arrayIndex, element], + ); + + const onResize = React.useCallback(() => { + if (element && _.isEqual(selectedBlock, arrayIndex)) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_RESIZE_BLOCK', {rect}); + } + } + }, [arrayIndex, element, selectedBlock]); + + React.useEffect(() => { + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }, [element, onResize]); + + return { + onClick, + onMouseMove, + onMouseUp, + onMouseLeave, + }; +}; + +export default useEditorBlockMouseEvents; diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx index e957bd6f9..64e0b6051 100644 --- a/src/context/editorStoreContext/PCEditorStoreContext.tsx +++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import {StoreApi} from 'zustand'; diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx index a6f0add57..9de324e5a 100644 --- a/src/context/editorStoreContext/PCEditorStoreProvider.tsx +++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx @@ -1,4 +1,4 @@ -import React, {PropsWithChildren, useCallback, useEffect, useRef} from 'react'; +import * as React from 'react'; import {StoreApi} from 'zustand'; @@ -7,18 +7,18 @@ import {StoreSyncMessage} from '../../../common/types'; import {PCEditorStoreContext} from './PCEditorStoreContext'; -interface PCEditorStoreProviderProps extends PropsWithChildren {} +interface PCEditorStoreProviderProps extends React.PropsWithChildren {} export const PCEditorStoreProvider = ({children}: PCEditorStoreProviderProps) => { - const storeRef = useRef<StoreApi<EditorState>>(); + const storeRef = React.useRef<StoreApi<EditorState>>(); - const syncStore = useCallback((message: StoreSyncMessage) => { + const syncStore = React.useCallback((message: StoreSyncMessage) => { if (storeRef.current && message.state) { storeRef.current.setState(message.state); } }, []); - useEffect(() => { + React.useEffect(() => { const onMessage = (e: MessageEvent) => { const message = e.data as StoreSyncMessage; syncStore(message); diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx new file mode 100644 index 000000000..25eba68bc --- /dev/null +++ b/src/editor-v2/components/BigOverlay/BigOverlay.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; + +import {Stop} from '@gravity-ui/icons'; + +import {usePostMessageAPIListener} from '../../../common/postMessage'; +import {ClassNameProps} from '../../../models'; +import {block} from '../../../utils'; +import {IframeContext} from '../../context/iframeContext'; +import {useMainEditorStore} from '../../hooks/useMainEditorStore'; + +import './BigOverlay.scss'; + +const b = block('big-overlay'); + +interface BigOverlayProps extends ClassNameProps {} + +const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { + const {zoom, manipulateOverlayMode} = useMainEditorStore(); + const {iframeElement} = useContext(IframeContext); + const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( + undefined, + ); + const [source, setSource] = useState<'main' | 'iframe'>('main'); + + const onMouseUp = useCallback(() => { + setMousePosition(undefined); + }, []); + + const onIframeMouseEvent = useCallback((position: {x: number; y: number}) => { + setMousePosition(position); + setSource('iframe'); + }, []); + + usePostMessageAPIListener('ON_MOUSE_UP', onMouseUp); + usePostMessageAPIListener('ON_MOUSE_MOVE', onIframeMouseEvent); + + useEffect(() => { + const onEditorMouseEvent = (event: MouseEvent) => { + setMousePosition({x: event.clientX, y: event.clientY}); + setSource('main'); + }; + + document.addEventListener('mousemove', onEditorMouseEvent); + document.addEventListener('mousedown', onEditorMouseEvent); + + return () => { + document.removeEventListener('mousemove', onEditorMouseEvent); + document.removeEventListener('mousedown', onEditorMouseEvent); + }; + }, []); + + const realPositions = useMemo(() => { + if (mousePosition) { + const {x, y} = mousePosition; + const iframeRect = iframeElement?.getClientRects().item(0); + if (iframeRect) { + const zoomedX = (x * zoom) / 100; + const zoomedY = (y * zoom) / 100; + const newX = source === 'main' ? x : zoomedX + iframeRect.x; + const newY = source === 'main' ? y : zoomedY + iframeRect.y; + return {x: newX, y: newY}; + } + } + + return undefined; + }, [mousePosition, source, iframeElement, zoom]); + + return ( + <div className={b(null, className)}> + {realPositions && manipulateOverlayMode ? ( + <div + className={b('border')} + style={{ + top: realPositions.y, + left: realPositions.x, + }} + > + <Stop height={20} width={20} /> + </div> + ) : null} + </div> + ); +}; + +export default BigOverlay; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx new file mode 100644 index 000000000..994d82589 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; + +import {Plus} from '@gravity-ui/icons'; +import {Button, Card, Icon} from '@gravity-ui/uikit'; + +import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; +import {ClassNameProps, PageContent} from '../../../../../models'; +import {block} from '../../../../../utils'; +import {removeFromArray, swapArrayItems} from '../../../../utils'; +import DynamicForm from '../../DynamicForm'; +import FieldBase from '../../FieldBase/FieldBase'; +import Text from '../Text/Text'; + +import ItemButton from './ItemButton/ItemButton'; + +import './Array.scss'; + +const b = block('array-dynamic-field'); + +type ArrayInput = ArrayTextInput | ArrayObjectInput; + +interface ArrayFieldProps extends ClassNameProps { + title: string; + values: Array<DynamicFormValue>; + onUpdate: (key: string, value: DynamicFormValue) => void; + blockConfig: ArrayInput; +} + +const ArrayDynamicField: React.FC<ArrayFieldProps> = (props) => { + const {title, values, onUpdate, className, blockConfig} = props; + + const haveItems = values && Array.isArray(values) && values.length; + + const onAddItem = useCallback(() => { + if (blockConfig.arrayType === 'text') { + onUpdate('', haveItems ? [...values, ''] : ['']); + } else if (blockConfig.arrayType === 'object') { + onUpdate('', haveItems ? [...values, {}] : [{}]); + } + }, [blockConfig.arrayType, haveItems, onUpdate, values]); + + const onDeleteItem = useCallback( + (index: number) => { + if (Array.isArray(values)) { + const newArray = removeFromArray(values, index); + onUpdate('', newArray); + } + }, + [onUpdate, values], + ); + + const onReorderItem = useCallback( + (index: number, placement: 'up' | 'down') => { + if (Array.isArray(values)) { + const newArray = swapArrayItems( + values, + index, + placement === 'up' ? index - 1 : index + 1, + ); + onUpdate('', newArray); + } + }, + [onUpdate, values], + ); + + const renderInput = useCallback( + (value: DynamicFormValue, index: number) => { + const arrayItemButton = ( + <ItemButton + onRemove={() => onDeleteItem(index)} + onReorderUp={() => onReorderItem(index, 'up')} + onReorderDown={() => onReorderItem(index, 'down')} + disableReorderUp={index === 0} + disableReorderDown={Boolean(haveItems) && values.length === index + 1} + /> + ); + + switch (blockConfig.arrayType) { + case 'text': { + return ( + <div className={b('row')}> + <Text + className={b('row-field')} + value={String(value)} + onUpdate={(updateValue) => onUpdate(`[${index}]`, updateValue)} + onRefresh={(updatedValue) => onUpdate('', updatedValue)} + /> + {arrayItemButton} + </div> + ); + } + case 'object': { + if (!blockConfig.properties) { + return null; + } + return ( + <Card key={index} className={b('card')}> + <div className={`${b('row')} ${b('card-head')}`}> + <div className={b('row-title')}>#{index}</div> + {arrayItemButton} + </div> + <DynamicForm + contentConfig={value as PageContent} + blockConfig={blockConfig.properties} + onUpdate={(key, updateValue) => + onUpdate(`[${index}].${key}`, updateValue) + } + /> + </Card> + ); + } + default: { + return null; + } + } + }, + [blockConfig, haveItems, onDeleteItem, onReorderItem, onUpdate, values], + ); + + const renderInputs = useCallback(() => { + if (haveItems) { + const renderItems = values + .map(renderInput) + .filter(Boolean) as unknown as React.ReactNode[]; + return ( + <React.Fragment> + {renderItems} + <Button className={b('add-button')} onClick={onAddItem}> + <Icon data={Plus} /> + {blockConfig.buttonText} + </Button> + </React.Fragment> + ); + } else { + return ( + <div className={b('empty')}> + <Button className={b('add-button')} onClick={onAddItem}> + <Icon data={Plus} /> + Please, add new item + </Button> + </div> + ); + } + }, [blockConfig.buttonText, haveItems, onAddItem, renderInput, values]); + + return ( + <FieldBase + title={title} + className={b(null, className)} + onRefresh={(value) => onUpdate('', value)} + expandable + > + <Card className={b('card')}>{renderInputs()}</Card> + </FieldBase> + ); +}; + +export default ArrayDynamicField; diff --git a/src/editor/components/CodeEditor/CodeEditor.tsx b/src/editor/components/CodeEditor/CodeEditor.tsx index 168bdce52..d39f84822 100644 --- a/src/editor/components/CodeEditor/CodeEditor.tsx +++ b/src/editor/components/CodeEditor/CodeEditor.tsx @@ -1,4 +1,4 @@ -import {useCallback, useContext, useState} from 'react'; +import * as React from 'react'; import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import debounce from 'lodash/debounce'; @@ -32,11 +32,11 @@ export const CodeEditor = ({ onFullscreenModeOnUpdate, code, }: CodeEditorProps) => { - const [message, setMessage] = useState(() => validator(code)); - const {theme = Theme.Light} = useContext(EditorContext); + const [message, setMessage] = React.useState(() => validator(code)); + const {theme = Theme.Light} = React.useContext(EditorContext); // eslint-disable-next-line react-hooks/exhaustive-deps - const onChangeWithValidation = useCallback( + const onChangeWithValidation = React.useCallback( debounce((newCode: string) => { const validationResult = validator(newCode); diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts index d908f584d..942422371 100644 --- a/src/hooks/usePCEditorBlockMouseEvents.ts +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect} from 'react'; +import * as React from 'react'; import _ from 'lodash'; @@ -10,7 +10,7 @@ import {sendEventPostMessage} from './usePostMessageAPI'; const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { const {selectedBlock} = usePCEditorStore(); - const onMouseUp = useCallback( + const onMouseUp = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation(); if (element) { @@ -24,7 +24,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement [arrayIndex, element], ); - const onMouseMove = useCallback( + const onMouseMove = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation(); if (element) { @@ -38,7 +38,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement [element], ); - const onMouseLeave = useCallback(() => { + const onMouseLeave = React.useCallback(() => { if (element) { const rect = element.getClientRects().item(0); if (rect) { @@ -47,7 +47,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement } }, [element]); - const onClick = useCallback( + const onClick = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation(); if (element) { @@ -60,7 +60,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement [arrayIndex, element], ); - const onResize = useCallback(() => { + const onResize = React.useCallback(() => { if (element && _.isEqual(selectedBlock, arrayIndex)) { const rect = element.getClientRects().item(0); if (rect) { @@ -69,7 +69,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement } }, [arrayIndex, element, selectedBlock]); - useEffect(() => { + React.useEffect(() => { window.addEventListener('resize', onResize); return () => { diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts index 9dec24459..69ec82577 100644 --- a/src/hooks/usePCEditorInitializeEvents.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -1,4 +1,4 @@ -import {useCallback, useEffect} from 'react'; +import * as React from 'react'; import {JSONSchemaType} from 'ajv'; import _ from 'lodash'; @@ -23,7 +23,7 @@ export const usePCEditorInitializeEvents = ({ }: UseEditorInitializeProps) => { const {manipulateOverlayMode, initialized, content} = usePCEditorStore(); - useEffect(() => { + React.useEffect(() => { if (initialized) { setContent(content); } @@ -46,14 +46,14 @@ export const usePCEditorInitializeEvents = ({ }); }); - const onResize = useCallback(() => { + const onResize = React.useCallback(() => { const height = document.documentElement.getBoundingClientRect().height; sendEventPostMessage('ON_RESIZE', {height}); }, []); new ResizeObserver(onResize).observe(document.body); - useEffect(() => { + React.useEffect(() => { const onMouseUp = () => { sendEventPostMessage('ON_MOUSE_UP', {}); }; @@ -78,7 +78,7 @@ export const usePCEditorInitializeEvents = ({ }; }, [manipulateOverlayMode, onResize]); - useEffect(() => { + React.useEffect(() => { const height = document.documentElement.getBoundingClientRect().height; sendEventPostMessage('ON_INIT', {height}); }, []); diff --git a/src/hooks/usePCEditorItemWrap.ts b/src/hooks/usePCEditorItemWrap.ts index c05568445..6ab498a31 100644 --- a/src/hooks/usePCEditorItemWrap.ts +++ b/src/hooks/usePCEditorItemWrap.ts @@ -1,19 +1,19 @@ -import {useCallback, useContext, useState} from 'react'; +import * as React from 'react'; import {BlockIdContext} from '../context/blockIdContext'; import usePCEditorBlockMouseEvents from './usePCEditorBlockMouseEvents'; export function usePCEditorItemWrap(index = 0) { - const [element, setElement] = useState<HTMLElement | undefined>(); + const [element, setElement] = React.useState<HTMLElement | undefined>(); - const blockRef = useCallback((node: HTMLElement | null) => { + const blockRef = React.useCallback((node: HTMLElement | null) => { if (node !== null) { setElement(node); } }, []); - const parentBlockId = useContext(BlockIdContext); + const parentBlockId = React.useContext(BlockIdContext); const adminBlockMouseEvents = usePCEditorBlockMouseEvents([...parentBlockId, index], element); return {adminBlockMouseEvents, blockRef}; diff --git a/src/hooks/usePCEditorStore.ts b/src/hooks/usePCEditorStore.ts index 3c9cdf851..6e70148c9 100644 --- a/src/hooks/usePCEditorStore.ts +++ b/src/hooks/usePCEditorStore.ts @@ -1,10 +1,10 @@ -import {useContext} from 'react'; +import * as React from 'react'; import {useStore} from 'zustand'; import {PCEditorStoreContext} from '../context/editorStoreContext'; export const usePCEditorStore = () => { - const {state} = useContext(PCEditorStoreContext); + const {state} = React.useContext(PCEditorStoreContext); return useStore(state); }; diff --git a/src/hooks/usePostMessageAPI.ts b/src/hooks/usePostMessageAPI.ts index 9c1953c2a..7d5c33c69 100644 --- a/src/hooks/usePostMessageAPI.ts +++ b/src/hooks/usePostMessageAPI.ts @@ -1,4 +1,4 @@ -import {useEffect} from 'react'; +import * as React from 'react'; import {PostMessageAPIMessage} from '../../common/types'; import {ActionMessageTypes, EventMessageTypes} from '../../common/types/actions'; @@ -36,7 +36,7 @@ export function useInternalPostMessageAPIListener<K extends keyof ActionMessageT action: K, callback: (data: ActionMessageTypes[K]) => void, ) { - useEffect(() => { + React.useEffect(() => { return listenPostMessageActions(action, callback); }, [action, callback]); } diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 58cd311ce..b0cd861c5 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -1,6 +1,9 @@ -import {MouseEvent} from 'react'; +import * as React from 'react'; -export const getCursorPositionOverElement = (elementRect: DOMRect, mouseEvent: MouseEvent) => { +export const getCursorPositionOverElement = ( + elementRect: DOMRect, + mouseEvent: React.MouseEvent, +) => { const cursorPositionY = elementRect.height - (mouseEvent.clientY - elementRect.y); const cursorPositionX = elementRect.width - (mouseEvent.clientX - elementRect.x); const cursorRatioY = elementRect.height / 2 / cursorPositionY; From 51da75421d7da5b2c1350dfc61a17a60307b8ede Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Sat, 15 Mar 2025 10:22:27 +0300 Subject: [PATCH 24/84] fix: lint errors --- common/store.ts | 4 +- common/types/actions.ts | 4 +- common/types/forms.ts | 13 +- editor-v2/containers/Editor/Editor.tsx | 4 +- .../containers/SourceCode/SourceCode.tsx | 6 +- editor-v2/store.ts | 4 +- .../PageConstructor/PageConstructor.tsx | 8 +- .../hooks/useEditorBlockMouseEvents.tsx | 2 +- .../ConstructorLoadable.tsx | 2 +- .../components/BigOverlay/BigOverlay.tsx | 85 ---------- .../DynamicForm/Fields/Array/Array.tsx | 158 ------------------ src/hooks/usePCEditorInitializeEvents.ts | 6 +- src/models/constructor.ts | 2 +- .../components/Navigation/Navigation.tsx | 4 +- src/sub-blocks/BackgroundCard/dynamic-form.ts | 2 +- 15 files changed, 36 insertions(+), 268 deletions(-) delete mode 100644 src/editor-v2/components/BigOverlay/BigOverlay.tsx delete mode 100644 src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx diff --git a/common/store.ts b/common/store.ts index 820ee2811..48befec43 100644 --- a/common/store.ts +++ b/common/store.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {Content} from '../src/models'; +import {PageContentWithNavigation} from '../src/models'; import {ConfigInput, ItemConfig} from './types'; import {initializeStore} from './utils'; @@ -13,7 +13,7 @@ export interface EditorState { selectedBlock?: number[]; initialized: boolean; - content: Content; + content: PageContentWithNavigation; blocks: Array<ItemConfig>; subBlocks: Array<ItemConfig>; global: Array<ConfigInput>; diff --git a/common/types/actions.ts b/common/types/actions.ts index 16114a740..2bde27f54 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -1,4 +1,4 @@ -import {Content} from '../../src/models'; +import {PageContentWithNavigation} from '../../src/models'; import {EditorState} from '../store'; export type MessageTypes = EventMessageTypes & ActionMessageTypes; @@ -12,7 +12,7 @@ export type EventMessageTypes = { ON_CLICK_BLOCK: {path: number[]; rect: DOMRect}; ON_RESIZE_BLOCK: {rect: DOMRect}; ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; - ON_INITIAL_CONTENT: Content; + ON_INITIAL_CONTENT: PageContentWithNavigation; }; export type ActionMessageTypes = { diff --git a/common/types/forms.ts b/common/types/forms.ts index 230d29f3b..190d349d4 100644 --- a/common/types/forms.ts +++ b/common/types/forms.ts @@ -1,6 +1,13 @@ -import {Content} from '../../src/models'; - -export type DynamicFormValue = string | number | [] | object | boolean | Content | undefined; +import {PageContentWithNavigation} from '../../src/models'; + +export type DynamicFormValue = + | string + | number + | [] + | object + | boolean + | PageContentWithNavigation + | undefined; export interface BlockConfig { name: string; diff --git a/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx index c15150628..ae3ac6820 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {Content} from '../../../src/models'; +import {PageContentWithNavigation} from '../../../src/models'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; import StoreViewer from '../../components/StoreViewer/StoreViewer'; @@ -23,7 +23,7 @@ interface SidebarTabComponent { component: React.ElementType; } interface EditorViewProps { - onUpdate?: (pageContent: Content) => void; + onUpdate?: (pageContent: PageContentWithNavigation) => void; initialUrl: string; disableUrlField?: boolean; componentsConfig?: { diff --git a/editor-v2/containers/SourceCode/SourceCode.tsx b/editor-v2/containers/SourceCode/SourceCode.tsx index 4e8b0de7f..bc6485135 100644 --- a/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/editor-v2/containers/SourceCode/SourceCode.tsx @@ -3,7 +3,7 @@ import {Button, ClipboardButton, Icon} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; import * as React from 'react'; -import {Content} from '../../../src/models'; +import {PageContentWithNavigation} from '../../../src/models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; @@ -22,13 +22,13 @@ const SourceCode = ({className, format}: SourceCodeProps) => { const [isOpen, setIsOpen] = React.useState(false); const handleUpdate = (tempConfig: string) => { - let object: Content | undefined; + let object: PageContentWithNavigation | undefined; try { if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) { object = JSON.parse(tempConfig); } else { - object = yaml.load(tempConfig) as Content; + object = yaml.load(tempConfig) as PageContentWithNavigation; } } catch { // eslint-disable-next-line no-console diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 48c3aaf91..46fa4e17d 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; import {EditorState, initialStore} from '../common/store'; import {DynamicFormValue} from '../common/types'; import {initializeStore} from '../common/utils'; -import {ConstructorBlock, Content} from '../src/models'; +import {ConstructorBlock, PageContentWithNavigation} from '../src/models'; import {ZOOM_STEPS} from './constants'; import { @@ -25,7 +25,7 @@ export interface EditorMethods { increaseZoom(): void; decreaseZoom(): void; setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; - setContent(data: Content): void; + setContent(data: PageContentWithNavigation): void; insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; enableInsertMode(blockType: string): void; enableReorderMode(path: number[]): void; diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index b0eb1fa58..6d308eae7 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -15,13 +15,13 @@ import {usePCEditorInitializeEvents} from '../../hooks/usePCEditorInitializeEven import {usePCEditorStore} from '../../hooks/usePCEditorStore'; import { BlockTypes, - Content, CustomConfig, CustomItems, HeaderBlockTypes, NavigationData, NavigationItemTypes, PageContent, + PageContentWithNavigation, ShouldRenderBlock, SubBlockTypes, } from '../../models'; @@ -62,7 +62,11 @@ export const Constructor = (props: PageConstructorProps) => { microdata, } = props; - const [content, setContent] = React.useState<Content>({blocks, background, navigation}); + const [content, setContent] = React.useState<PageContentWithNavigation>({ + blocks, + background, + navigation, + }); const theme = useTheme(); diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx index 5d7de0742..f42540ea1 100644 --- a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx +++ b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import _ from 'lodash'; -import {usePCEditorStore} from '../../../../../context/editorStoreContext'; +import {usePCEditorStore} from '../../../../../hooks/usePCEditorStore'; import {sendEventPostMessage} from '../../../../../hooks/usePostMessageAPI'; import {getCursorPositionOverElement} from '../../../../../utils/editor'; diff --git a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx index e2daf4d30..21ca4b234 100644 --- a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx +++ b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx @@ -18,7 +18,7 @@ export const ConstructorLoadable = (props: ConstructorLoadableProps) => { const Component = itemMap[type] as React.Component< React.ComponentProps<(typeof itemMap)[typeof type]> >; - const parentId = useContext(BlockIdContext); + const parentId = React.useContext(BlockIdContext); return ( <BlockIdContext.Provider value={[...parentId, Number(blockKey)]} key={blockKey}> diff --git a/src/editor-v2/components/BigOverlay/BigOverlay.tsx b/src/editor-v2/components/BigOverlay/BigOverlay.tsx deleted file mode 100644 index 25eba68bc..000000000 --- a/src/editor-v2/components/BigOverlay/BigOverlay.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; - -import {Stop} from '@gravity-ui/icons'; - -import {usePostMessageAPIListener} from '../../../common/postMessage'; -import {ClassNameProps} from '../../../models'; -import {block} from '../../../utils'; -import {IframeContext} from '../../context/iframeContext'; -import {useMainEditorStore} from '../../hooks/useMainEditorStore'; - -import './BigOverlay.scss'; - -const b = block('big-overlay'); - -interface BigOverlayProps extends ClassNameProps {} - -const BigOverlay: React.FC<BigOverlayProps> = ({className}) => { - const {zoom, manipulateOverlayMode} = useMainEditorStore(); - const {iframeElement} = useContext(IframeContext); - const [mousePosition, setMousePosition] = useState<{x: number; y: number} | undefined>( - undefined, - ); - const [source, setSource] = useState<'main' | 'iframe'>('main'); - - const onMouseUp = useCallback(() => { - setMousePosition(undefined); - }, []); - - const onIframeMouseEvent = useCallback((position: {x: number; y: number}) => { - setMousePosition(position); - setSource('iframe'); - }, []); - - usePostMessageAPIListener('ON_MOUSE_UP', onMouseUp); - usePostMessageAPIListener('ON_MOUSE_MOVE', onIframeMouseEvent); - - useEffect(() => { - const onEditorMouseEvent = (event: MouseEvent) => { - setMousePosition({x: event.clientX, y: event.clientY}); - setSource('main'); - }; - - document.addEventListener('mousemove', onEditorMouseEvent); - document.addEventListener('mousedown', onEditorMouseEvent); - - return () => { - document.removeEventListener('mousemove', onEditorMouseEvent); - document.removeEventListener('mousedown', onEditorMouseEvent); - }; - }, []); - - const realPositions = useMemo(() => { - if (mousePosition) { - const {x, y} = mousePosition; - const iframeRect = iframeElement?.getClientRects().item(0); - if (iframeRect) { - const zoomedX = (x * zoom) / 100; - const zoomedY = (y * zoom) / 100; - const newX = source === 'main' ? x : zoomedX + iframeRect.x; - const newY = source === 'main' ? y : zoomedY + iframeRect.y; - return {x: newX, y: newY}; - } - } - - return undefined; - }, [mousePosition, source, iframeElement, zoom]); - - return ( - <div className={b(null, className)}> - {realPositions && manipulateOverlayMode ? ( - <div - className={b('border')} - style={{ - top: realPositions.y, - left: realPositions.x, - }} - > - <Stop height={20} width={20} /> - </div> - ) : null} - </div> - ); -}; - -export default BigOverlay; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx deleted file mode 100644 index 994d82589..000000000 --- a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import * as React from 'react'; - -import {Plus} from '@gravity-ui/icons'; -import {Button, Card, Icon} from '@gravity-ui/uikit'; - -import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; -import {ClassNameProps, PageContent} from '../../../../../models'; -import {block} from '../../../../../utils'; -import {removeFromArray, swapArrayItems} from '../../../../utils'; -import DynamicForm from '../../DynamicForm'; -import FieldBase from '../../FieldBase/FieldBase'; -import Text from '../Text/Text'; - -import ItemButton from './ItemButton/ItemButton'; - -import './Array.scss'; - -const b = block('array-dynamic-field'); - -type ArrayInput = ArrayTextInput | ArrayObjectInput; - -interface ArrayFieldProps extends ClassNameProps { - title: string; - values: Array<DynamicFormValue>; - onUpdate: (key: string, value: DynamicFormValue) => void; - blockConfig: ArrayInput; -} - -const ArrayDynamicField: React.FC<ArrayFieldProps> = (props) => { - const {title, values, onUpdate, className, blockConfig} = props; - - const haveItems = values && Array.isArray(values) && values.length; - - const onAddItem = useCallback(() => { - if (blockConfig.arrayType === 'text') { - onUpdate('', haveItems ? [...values, ''] : ['']); - } else if (blockConfig.arrayType === 'object') { - onUpdate('', haveItems ? [...values, {}] : [{}]); - } - }, [blockConfig.arrayType, haveItems, onUpdate, values]); - - const onDeleteItem = useCallback( - (index: number) => { - if (Array.isArray(values)) { - const newArray = removeFromArray(values, index); - onUpdate('', newArray); - } - }, - [onUpdate, values], - ); - - const onReorderItem = useCallback( - (index: number, placement: 'up' | 'down') => { - if (Array.isArray(values)) { - const newArray = swapArrayItems( - values, - index, - placement === 'up' ? index - 1 : index + 1, - ); - onUpdate('', newArray); - } - }, - [onUpdate, values], - ); - - const renderInput = useCallback( - (value: DynamicFormValue, index: number) => { - const arrayItemButton = ( - <ItemButton - onRemove={() => onDeleteItem(index)} - onReorderUp={() => onReorderItem(index, 'up')} - onReorderDown={() => onReorderItem(index, 'down')} - disableReorderUp={index === 0} - disableReorderDown={Boolean(haveItems) && values.length === index + 1} - /> - ); - - switch (blockConfig.arrayType) { - case 'text': { - return ( - <div className={b('row')}> - <Text - className={b('row-field')} - value={String(value)} - onUpdate={(updateValue) => onUpdate(`[${index}]`, updateValue)} - onRefresh={(updatedValue) => onUpdate('', updatedValue)} - /> - {arrayItemButton} - </div> - ); - } - case 'object': { - if (!blockConfig.properties) { - return null; - } - return ( - <Card key={index} className={b('card')}> - <div className={`${b('row')} ${b('card-head')}`}> - <div className={b('row-title')}>#{index}</div> - {arrayItemButton} - </div> - <DynamicForm - contentConfig={value as PageContent} - blockConfig={blockConfig.properties} - onUpdate={(key, updateValue) => - onUpdate(`[${index}].${key}`, updateValue) - } - /> - </Card> - ); - } - default: { - return null; - } - } - }, - [blockConfig, haveItems, onDeleteItem, onReorderItem, onUpdate, values], - ); - - const renderInputs = useCallback(() => { - if (haveItems) { - const renderItems = values - .map(renderInput) - .filter(Boolean) as unknown as React.ReactNode[]; - return ( - <React.Fragment> - {renderItems} - <Button className={b('add-button')} onClick={onAddItem}> - <Icon data={Plus} /> - {blockConfig.buttonText} - </Button> - </React.Fragment> - ); - } else { - return ( - <div className={b('empty')}> - <Button className={b('add-button')} onClick={onAddItem}> - <Icon data={Plus} /> - Please, add new item - </Button> - </div> - ); - } - }, [blockConfig.buttonText, haveItems, onAddItem, renderInput, values]); - - return ( - <FieldBase - title={title} - className={b(null, className)} - onRefresh={(value) => onUpdate('', value)} - expandable - > - <Card className={b('card')}>{renderInputs()}</Card> - </FieldBase> - ); -}; - -export default ArrayDynamicField; diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts index 69ec82577..f72ea957c 100644 --- a/src/hooks/usePCEditorInitializeEvents.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import {ItemConfig} from '../../common/types'; import {blockDataMap} from '../constructor-items'; -import {Content} from '../models'; +import {PageContentWithNavigation} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; import {generateFromAJV} from '../utils/form-generator'; @@ -13,8 +13,8 @@ import {usePCEditorStore} from './usePCEditorStore'; import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; interface UseEditorInitializeProps { - initialContent: Content; - setContent: (content: Content) => void; + initialContent: PageContentWithNavigation; + setContent: (content: PageContentWithNavigation) => void; } export const usePCEditorInitializeEvents = ({ diff --git a/src/models/constructor.ts b/src/models/constructor.ts index ed7b0068c..477086ac3 100644 --- a/src/models/constructor.ts +++ b/src/models/constructor.ts @@ -24,7 +24,7 @@ export interface PageContent extends Animatable { background?: ThemedMediaProps; } -export interface Content extends PageContent { +export interface PageContentWithNavigation extends PageContent { navigation?: NavigationData; } diff --git a/src/navigation/components/Navigation/Navigation.tsx b/src/navigation/components/Navigation/Navigation.tsx index 2481cdbca..f5278113f 100644 --- a/src/navigation/components/Navigation/Navigation.tsx +++ b/src/navigation/components/Navigation/Navigation.tsx @@ -13,8 +13,8 @@ import './Navigation.scss'; const b = block('navigation'); export interface NavigationComponentProps extends ClassNameProps { - logo: ThemedNavigationLogoData; - data: HeaderData; + logo?: ThemedNavigationLogoData; + data?: HeaderData; mobilePortalContainer?: React.RefObject<HTMLElement>; } diff --git a/src/sub-blocks/BackgroundCard/dynamic-form.ts b/src/sub-blocks/BackgroundCard/dynamic-form.ts index 817ff35e2..1ab9100a1 100644 --- a/src/sub-blocks/BackgroundCard/dynamic-form.ts +++ b/src/sub-blocks/BackgroundCard/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig} from '../../common/types'; +import {BlockConfig} from '../../../common/types'; import {contentThemes, textSize} from '../../schema/validators/common'; const textSizeEnum = textSize.map((size) => ({value: size, content: size})); From 01e277c4b39d8e971d7c6cd8eb88d3cc7358ef8f Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Fri, 28 Mar 2025 18:58:47 +0300 Subject: [PATCH 25/84] feat: add device switcher --- common/store.ts | 2 + .../containers/MiddleScreen/MiddleScreen.scss | 11 +++- .../containers/MiddleScreen/MiddleScreen.tsx | 52 ++++++++++--------- .../containers/ViewSwitches/ViewSwitches.tsx | 20 +++++-- editor-v2/store.ts | 4 ++ 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/common/store.ts b/common/store.ts index 48befec43..796c844f4 100644 --- a/common/store.ts +++ b/common/store.ts @@ -7,6 +7,7 @@ import {initializeStore} from './utils'; export interface EditorState { height?: number; + deviceWidth?: string; zoom: number; manipulateOverlayMode: 'insert' | 'reorder' | false; @@ -24,6 +25,7 @@ export interface EditorState { export const initialStore: EditorState = { height: 100, + deviceWidth: '100%', zoom: 100, manipulateOverlayMode: false, selectedBlock: undefined, diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/editor-v2/containers/MiddleScreen/MiddleScreen.scss index 0b869dbdd..e2a530dd5 100644 --- a/editor-v2/containers/MiddleScreen/MiddleScreen.scss +++ b/editor-v2/containers/MiddleScreen/MiddleScreen.scss @@ -12,19 +12,26 @@ $block: '.#{$ns}middle-screen'; &__topbar { flex: 0 0 auto; - // min-height: 40px; } - &__wrapper { + &__content { flex: 1 1 auto; overflow-y: auto; // height: 100%; + background-color: var(--g-color-text-dark-secondary); + } + + &__wrapper { + width: 100%; + height: 100%; + margin: auto; } &__canvas { transform-origin: left top; overflow-y: scroll; position: relative; + height: 100%; } &__overlay { diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 7ce7b974f..6ccf9dbc3 100644 --- a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -17,7 +17,7 @@ interface MiddleScreenProps { } const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { - const {zoom, initialized} = useMainEditorStore(); + const {zoom, initialized, deviceWidth} = useMainEditorStore(); const {url, setIframeElement} = React.useContext(IframeContext); const [height, setHeight] = React.useState(0); @@ -43,30 +43,32 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { <CustomTop /> </div> ) : null} - <div className={b('wrapper')}> - <div - className={b('canvas', {hidden: !initialized})} - style={{ - transform: `scale(${zoom}%)`, - height: `${(100 / zoom) * 100}%`, - width: `${(100 / zoom) * 100}%`, - }} - > - <iframe - ref={(instance) => instance && setIframeElement(instance)} - className={b('iframe')} - src={url} - height={`${height}px`} - width="100%" - frameBorder="0" - title="Page Constructor Iframe" - /> - <Overlay className={b('overlay')} /> - {!initialized && ( - <div className={b('loading')}> - <Loader size={'l'} /> - </div> - )} + <div className={b('content')}> + <div className={b('wrapper')} style={{width: deviceWidth}}> + <div + className={b('canvas', {hidden: !initialized})} + style={{ + transform: `scale(${zoom}%)`, + height: `${(100 / zoom) * 100}%`, + width: `${(100 / zoom) * 100}%`, + }} + > + <iframe + ref={(instance) => instance && setIframeElement(instance)} + className={b('iframe')} + src={url} + height={`${height}px`} + width="100%" + frameBorder="0" + title="Page Constructor Iframe" + /> + <Overlay className={b('overlay')} /> + {!initialized && ( + <div className={b('loading')}> + <Loader size={'l'} /> + </div> + )} + </div> </div> </div> </div> diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index 9ad12ac3e..b811ca6a9 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,5 +1,5 @@ -import {Minus, Plus} from '@gravity-ui/icons'; -import {Button, Icon, Select} from '@gravity-ui/uikit'; +import {Display, Minus, Plus, Smartphone} from '@gravity-ui/icons'; +import {Button, Icon, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import {ZOOM_STEPS} from '../../constants'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; @@ -9,11 +9,23 @@ import './ViewSwitches.scss'; const b = editorCn('view-switches'); +const DEVICE_OPTIONS = [ + {label: <Icon data={Display} />, value: '100%'}, + {label: <Icon data={Smartphone} />, value: '768px'}, + {label: <Icon width={14} data={Smartphone} />, value: '576px'}, +]; + const ViewSwitches = () => { - const {zoom, setZoom, decreaseZoom, increaseZoom} = useMainEditorStore(); + const {zoom, setZoom, decreaseZoom, increaseZoom, deviceWidth, setDeviceWidth} = + useMainEditorStore(); + return ( <div className={b()}> - <div>TBD</div> + <SegmentedRadioGroup value={deviceWidth} onUpdate={setDeviceWidth}> + {DEVICE_OPTIONS.map(({value, label}) => ( + <SegmentedRadioGroup.Option value={value}>{label}</SegmentedRadioGroup.Option> + ))} + </SegmentedRadioGroup> <div className={b('zoom')}> <Button view={'flat'} onClick={decreaseZoom}> <Icon data={Minus} /> diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 46fa4e17d..576068945 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -21,6 +21,7 @@ export interface EditorMethods { initialize(): void; setSelectedBlock(path?: number[]): void; setHeight(height: number): void; + setDeviceWidth(deviceWidth: string): void; setZoom(zoom: number): void; increaseZoom(): void; decreaseZoom(): void; @@ -49,6 +50,9 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( const newHeight = height + 500; set((state) => ({...state, height: newHeight})); }, + setDeviceWidth(deviceWidth: string) { + set((state) => ({...state, deviceWidth})); + }, setZoom(zoom) { if (zoom > 0) { set((state) => ({...state, zoom})); From 7fd38a743b5902a1004ce48fe1e492b776b74380 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 31 Mar 2025 12:47:05 +0300 Subject: [PATCH 26/84] fix: ai refactor viewswitches --- .../containers/ViewSwitches/ViewSwitches.scss | 4 - .../containers/ViewSwitches/ViewSwitches.tsx | 118 +++++++++++++++--- 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.scss b/editor-v2/containers/ViewSwitches/ViewSwitches.scss index 7e0cee91b..852d29aab 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.scss +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.scss @@ -14,8 +14,4 @@ $block: '.#{$ns}view-switches'; display: flex; gap: 4px; } - - &__zoom-input { - width: 40px; - } } diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index b811ca6a9..fbebdcfb8 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,3 +1,4 @@ +import * as React from 'react'; import {Display, Minus, Plus, Smartphone} from '@gravity-ui/icons'; import {Button, Icon, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; @@ -9,37 +10,116 @@ import './ViewSwitches.scss'; const b = editorCn('view-switches'); -const DEVICE_OPTIONS = [ - {label: <Icon data={Display} />, value: '100%'}, - {label: <Icon data={Smartphone} />, value: '768px'}, - {label: <Icon width={14} data={Smartphone} />, value: '576px'}, +/** + * Device option type definition + */ +interface DeviceOption { + /** React node to display as the option label */ + label: React.ReactNode; + /** Device width value (e.g., '100%', '768px') */ + value: string; + /** Descriptive name for accessibility */ + ariaLabel: string; +} + +/** + * Available device viewport options + * - Desktop: 100% width + * - Tablet: 768px width + * - Mobile: 576px width + */ +const DEVICE_OPTIONS: DeviceOption[] = [ + { + label: <Icon data={Display} />, + value: '100%', + ariaLabel: 'Desktop view', + }, + { + label: <Icon data={Smartphone} />, + value: '768px', + ariaLabel: 'Tablet view', + }, + { + label: <Icon width={14} data={Smartphone} />, + value: '576px', + ariaLabel: 'Mobile view', + }, ]; -const ViewSwitches = () => { +/** + * ViewSwitches component + * + * Provides UI controls for: + * 1. Switching between device viewport widths (desktop, tablet, mobile) + * 2. Adjusting zoom level with increment/decrement buttons and dropdown + */ +const ViewSwitches: React.FC = () => { const {zoom, setZoom, decreaseZoom, increaseZoom, deviceWidth, setDeviceWidth} = useMainEditorStore(); + // Memoize zoom options to prevent unnecessary recalculations + const zoomOptions = React.useMemo( + () => + ZOOM_STEPS.map((step) => ({ + value: String(step), + content: `${step}%`, + })), + [], + ); + + // Memoize current zoom value for Select component + const currentZoomValue = React.useMemo(() => [String(zoom)], [zoom]); + + // Create stable callback for zoom updates + const handleZoomUpdate = React.useCallback( + (value: string | string[]) => { + const newZoom = Number(Array.isArray(value) ? value[0] : value); + if (!isNaN(newZoom) && ZOOM_STEPS.includes(newZoom)) { + setZoom(newZoom); + } + }, + [setZoom], + ); + return ( - <div className={b()}> - <SegmentedRadioGroup value={deviceWidth} onUpdate={setDeviceWidth}> - {DEVICE_OPTIONS.map(({value, label}) => ( - <SegmentedRadioGroup.Option value={value}>{label}</SegmentedRadioGroup.Option> + <div className={b()} role="toolbar" aria-label="View controls"> + <SegmentedRadioGroup + value={deviceWidth} + onUpdate={setDeviceWidth} + aria-label="Device viewport selector" + > + {DEVICE_OPTIONS.map(({value, label, ariaLabel}) => ( + <SegmentedRadioGroup.Option key={value} value={value} aria-label={ariaLabel}> + {label} + </SegmentedRadioGroup.Option> ))} </SegmentedRadioGroup> - <div className={b('zoom')}> - <Button view={'flat'} onClick={decreaseZoom}> + + <div className={b('zoom')} role="group" aria-label="Zoom controls"> + <Button + view="flat" + onClick={decreaseZoom} + aria-label="Decrease zoom" + disabled={zoom <= Math.min(...ZOOM_STEPS)} + > <Icon data={Minus} /> </Button> + <Select multiple={false} - value={[String(zoom)]} - options={ZOOM_STEPS.map((step) => ({ - value: String(step), - content: `${String(step)}%`, - }))} - onUpdate={(value) => setZoom(Number(value))} + value={currentZoomValue} + options={zoomOptions} + onUpdate={handleZoomUpdate} + aria-label="Select zoom level" + width="max" /> - <Button view={'flat'} onClick={increaseZoom}> + + <Button + view="flat" + onClick={increaseZoom} + aria-label="Increase zoom" + disabled={zoom >= Math.max(...ZOOM_STEPS)} + > <Icon data={Plus} /> </Button> </div> @@ -47,4 +127,4 @@ const ViewSwitches = () => { ); }; -export default ViewSwitches; +export default React.memo(ViewSwitches); From d3e84cf082f7621bab5c17c0412a4790edcf8be4 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 31 Mar 2025 13:56:11 +0300 Subject: [PATCH 27/84] feat: add c9r top component --- editor-v2/containers/Editor/Editor.tsx | 2 +- .../src/app/components/C9RComponent.scss | 30 +++++++++++++++++++ .../src/app/components/C9RComponent.tsx | 22 ++++++++++++++ playground/src/app/page.tsx | 2 ++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 playground/src/app/components/C9RComponent.scss create mode 100644 playground/src/app/components/C9RComponent.tsx diff --git a/editor-v2/containers/Editor/Editor.tsx b/editor-v2/containers/Editor/Editor.tsx index ae3ac6820..8b98d5d50 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/editor-v2/containers/Editor/Editor.tsx @@ -59,7 +59,7 @@ const EditorView = ({componentsConfig = {}}: EditorViewProps) => { <div className={b()} onMouseUp={onMouseUp}> <div className={b('body')}> <Panels - left={<Sidebar tabs={left} />} + left={<Sidebar tabs={left} top={componentsConfig.leftTop} />} right={ <Sidebar tabs={right} diff --git a/playground/src/app/components/C9RComponent.scss b/playground/src/app/components/C9RComponent.scss new file mode 100644 index 000000000..fb9a04fa4 --- /dev/null +++ b/playground/src/app/components/C9RComponent.scss @@ -0,0 +1,30 @@ +$block: '.c9r-component'; + +#{$block} { + width: 100%; + padding: 16px 12px; + + &__header { + display: flex; + justify-content: space-between; + align-items: flex-start; + } + + &__title { + font-size: 30px; + line-height: 28px; + font-weight: 900; + } + + &__info-bar { + margin-top: 16px; + display: flex; + justify-content: space-between; + align-items: center; + } + + &__editor-info { + font-size: 14px; + color: var(--g-color-private-black-300); + } +} diff --git a/playground/src/app/components/C9RComponent.tsx b/playground/src/app/components/C9RComponent.tsx new file mode 100644 index 000000000..1f4d2e791 --- /dev/null +++ b/playground/src/app/components/C9RComponent.tsx @@ -0,0 +1,22 @@ +'use client'; + +import block from 'bem-cn-lite'; + +import './C9RComponent.scss'; + +const b = block('c9r-component'); + +export const C9RComponent = () => { + return ( + <div className={b()}> + <div className={b('header')}> + <div className={b('title')}>C9R</div> + </div> + <div className={b('info-bar')}> + <div className={b('editor-info')}>Editor alpha 1.0.0</div> + </div> + </div> + ); +}; + +export default C9RComponent; diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index ef1feacef..ddae138bf 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import {Editor} from '../../../editor-v2'; import Source from '../../../editor-v2/containers/Source/Source'; import ViewSwitches from '../../../editor-v2/containers/ViewSwitches/ViewSwitches'; +import C9RComponent from './components/C9RComponent'; import './page.scss'; @@ -15,6 +16,7 @@ const Test = () => <div className={b('test')}>custom test</div>; export const COMPONENTS_CONFIG = { middleTop: Test, + leftTop: [C9RComponent], leftTabs: [ { id: 'test', From 62c731d90a02f25cf9bd7e2cb4686f782f28094b Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 31 Mar 2025 14:52:36 +0300 Subject: [PATCH 28/84] feat: editor tree selection --- editor-v2/containers/Tree/Tree.tsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index 4e698f7a1..7423dfbb1 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -28,6 +28,7 @@ interface ItemProps { selected: boolean; onCopy(path: number[]): void; onDelete(path: number[]): void; + onSelect(path: number[]): void; } const generateTree = (items: TreeItem[]): TreeItem[] => { @@ -54,6 +55,7 @@ const Item = ({ selected, onCopy, onDelete, + onSelect, }: React.PropsWithChildren<ItemProps>) => { const handleCopy = React.useCallback(() => { onCopy(path); @@ -63,12 +65,16 @@ const Item = ({ onDelete(path); }, [onDelete, path]); + const handleSelect = React.useCallback( + (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation(); + onSelect(path); + }, + [onSelect, path], + ); + return ( - <Card - className={b('item', { - selected, - })} - > + <Card className={b('item')} selected={selected} onClick={handleSelect} type="selection"> <div className={b('main')}> <div className={b('text')}> <div className={b('type')}>{type}</div> @@ -89,7 +95,8 @@ const Item = ({ }; const Tree = (_p: React.PropsWithChildren<TreeProps>) => { - const {content, resetBlocks, selectedBlock, duplicateBlock, deleteBlock} = useMainEditorStore(); + const {content, resetBlocks, selectedBlock, duplicateBlock, deleteBlock, setSelectedBlock} = + useMainEditorStore(); const selectedBlockPath = React.useMemo(() => { return generateChildrenPathFromArray(selectedBlock || []); @@ -113,6 +120,7 @@ const Tree = (_p: React.PropsWithChildren<TreeProps>) => { treeTitle={treeTitle} onCopy={duplicateBlock} onDelete={deleteBlock} + onSelect={setSelectedBlock} key={index} path={blockPathArray} selected={selectedBlockPath === blockPath} From e00f483e8fe92c3aa2d7166acc5f25158e2f92a2 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 31 Mar 2025 19:29:46 +0300 Subject: [PATCH 29/84] feat: editor tree dnd --- editor-v2/containers/Tree/DragContext.scss | 32 +++ editor-v2/containers/Tree/DragContext.tsx | 70 +++++++ editor-v2/containers/Tree/Tree.scss | 50 +---- editor-v2/containers/Tree/Tree.tsx | 139 +++---------- editor-v2/containers/Tree/TreeContent.scss | 24 +++ editor-v2/containers/Tree/TreeContent.tsx | 121 +++++++++++ editor-v2/containers/Tree/TreeItem.scss | 71 +++++++ editor-v2/containers/Tree/TreeItem.tsx | 226 +++++++++++++++++++++ editor-v2/containers/Tree/index.ts | 4 + editor-v2/hooks/useEditorTabs.tsx | 2 +- 10 files changed, 579 insertions(+), 160 deletions(-) create mode 100644 editor-v2/containers/Tree/DragContext.scss create mode 100644 editor-v2/containers/Tree/DragContext.tsx create mode 100644 editor-v2/containers/Tree/TreeContent.scss create mode 100644 editor-v2/containers/Tree/TreeContent.tsx create mode 100644 editor-v2/containers/Tree/TreeItem.scss create mode 100644 editor-v2/containers/Tree/TreeItem.tsx create mode 100644 editor-v2/containers/Tree/index.ts diff --git a/editor-v2/containers/Tree/DragContext.scss b/editor-v2/containers/Tree/DragContext.scss new file mode 100644 index 000000000..ae1097e5e --- /dev/null +++ b/editor-v2/containers/Tree/DragContext.scss @@ -0,0 +1,32 @@ +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}preview'; + +#{$block} { + position: absolute; + height: auto; + background-color: var(--g-color-base-brand-light); + border: 1px dashed var(--g-color-line-brand); + border-radius: 4px; + padding: 8px; + z-index: 1000; + pointer-events: none; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + + &-content { + display: flex; + flex-direction: column; + } + + &-type { + color: var(--g-color-text-dark-secondary); + font-size: 12px; + } + + &-title { + @include overflow-ellipsis(); + font-size: 14px; + } +} diff --git a/editor-v2/containers/Tree/DragContext.tsx b/editor-v2/containers/Tree/DragContext.tsx new file mode 100644 index 000000000..268793903 --- /dev/null +++ b/editor-v2/containers/Tree/DragContext.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import {editorCn} from '../../utils/cn'; + +import './DragContext.scss'; + +const b = editorCn('preview'); + +export interface DragItem { + path: number[]; + type: string; + treeTitle?: string; +} + +// Context for drag and drop operations +export const DragContext = React.createContext<{ + draggedItem: DragItem | null; + setDraggedItem: React.Dispatch<React.SetStateAction<DragItem | null>>; + previewRef: React.RefObject<HTMLDivElement>; + showPreview: (rect: DOMRect, item: DragItem) => void; + hidePreview: () => void; +}>({ + draggedItem: null, + setDraggedItem: () => {}, + previewRef: {current: null}, + showPreview: () => {}, + hidePreview: () => {}, +}); + +// Component for the drag preview +const DragPreview = React.forwardRef<HTMLDivElement>((_, ref) => { + return <div ref={ref} className={b()} style={{display: 'none'}} />; +}); + +// Provider component for drag context +export const DragContextProvider: React.FC<React.PropsWithChildren<{}>> = ({children}) => { + const [draggedItem, setDraggedItem] = React.useState<DragItem | null>(null); + const previewRef = React.useRef<HTMLDivElement>(null); + + const showPreview = React.useCallback((rect: DOMRect, item: DragItem) => { + if (!previewRef.current) return; + + const previewEl = previewRef.current; + previewEl.style.display = 'block'; + previewEl.style.width = `${rect.width}px`; + previewEl.style.top = `${rect.bottom}px`; + previewEl.style.left = `${rect.left}px`; + + previewEl.innerHTML = ` + <div class="${b('content')}"> + <div class="${b('type')}">${item.type}</div> + ${item.treeTitle ? `<div class="${b('title')}">${item.treeTitle}</div>` : ''} + </div> + `; + }, []); + + const hidePreview = React.useCallback(() => { + if (previewRef.current) { + previewRef.current.style.display = 'none'; + } + }, []); + + return ( + <DragContext.Provider + value={{draggedItem, setDraggedItem, previewRef, showPreview, hidePreview}} + > + {children} + <DragPreview ref={previewRef} /> + </DragContext.Provider> + ); +}; diff --git a/editor-v2/containers/Tree/Tree.scss b/editor-v2/containers/Tree/Tree.scss index 666e4f0d2..f2daed6b1 100644 --- a/editor-v2/containers/Tree/Tree.scss +++ b/editor-v2/containers/Tree/Tree.scss @@ -6,6 +6,7 @@ $block: '.#{$ns}tree'; #{$block} { padding: 12px; + position: relative; &__head { margin-bottom: 10px; @@ -13,53 +14,4 @@ $block: '.#{$ns}tree'; align-items: center; justify-content: space-between; } - - &__item { - padding: 8px; - margin-bottom: 8px; - - &_selected { - border: 1.5px var(--g-color-line-brand) solid; - } - - &:last-child { - margin-bottom: 0; - } - } - - &__main { - display: flex; - flex-direction: row; - align-items: center; - - &:hover { - #{$block}__buttons { - display: flex; - } - } - } - - &__text { - flex: 1 1 auto; - min-width: 1px; - } - - &__buttons { - flex: 0 0 auto; - flex-direction: row; - gap: 4px; - display: none; - } - - &__type { - color: var(--g-color-text-dark-secondary); - } - - &__title { - @include overflow-ellipsis(); - } - - &__children { - margin-top: 8px; - } } diff --git a/editor-v2/containers/Tree/Tree.tsx b/editor-v2/containers/Tree/Tree.tsx index 7423dfbb1..58053b44d 100644 --- a/editor-v2/containers/Tree/Tree.tsx +++ b/editor-v2/containers/Tree/Tree.tsx @@ -1,36 +1,18 @@ -import {Copy, TrashBin} from '@gravity-ui/icons'; -import {Button, Card, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; -import {ItemConfig} from '../../../common/types'; +import {editorCn} from '../../utils/cn'; +import {DragContextProvider} from './DragContext'; +import {TreeContent, TreeItem} from './TreeContent'; + import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {Button, Icon} from '@gravity-ui/uikit'; +import {TrashBin} from '@gravity-ui/icons'; import {generateChildrenPathFromArray, getItemTitle} from '../../utils'; -import {editorCn} from '../../utils/cn'; import './Tree.scss'; const b = editorCn('tree'); -export interface TreeProps { - blocks: ItemConfig[]; -} - -type TreeItem = { - type: string; - children?: TreeItem[]; - treeTitle?: string; -}; - -interface ItemProps { - type: string; - treeTitle?: string; - path: number[]; - selected: boolean; - onCopy(path: number[]): void; - onDelete(path: number[]): void; - onSelect(path: number[]): void; -} - const generateTree = (items: TreeItem[]): TreeItem[] => { return items.map((item) => { let children; @@ -47,104 +29,41 @@ const generateTree = (items: TreeItem[]): TreeItem[] => { }); }; -const Item = ({ - type, - children, - treeTitle, - path, - selected, - onCopy, - onDelete, - onSelect, -}: React.PropsWithChildren<ItemProps>) => { - const handleCopy = React.useCallback(() => { - onCopy(path); - }, [onCopy, path]); - - const handleDelete = React.useCallback(() => { - onDelete(path); - }, [onDelete, path]); - - const handleSelect = React.useCallback( - (e: React.MouseEvent<HTMLDivElement>) => { - e.stopPropagation(); - onSelect(path); - }, - [onSelect, path], - ); - - return ( - <Card className={b('item')} selected={selected} onClick={handleSelect} type="selection"> - <div className={b('main')}> - <div className={b('text')}> - <div className={b('type')}>{type}</div> - <div className={b('title')}>{treeTitle}</div> - </div> - <div className={b('buttons')}> - <Button view="flat" size="xs" onClick={handleCopy}> - <Icon size={12} data={Copy} /> - </Button> - <Button view="flat" size="xs" onClick={handleDelete}> - <Icon size={12} data={TrashBin} /> - </Button> - </div> - </div> - {children} - </Card> - ); -}; - -const Tree = (_p: React.PropsWithChildren<TreeProps>) => { - const {content, resetBlocks, selectedBlock, duplicateBlock, deleteBlock, setSelectedBlock} = - useMainEditorStore(); +// Main Tree component with context provider +const Tree = () => { + const { + content, + resetBlocks, + selectedBlock, + duplicateBlock, + deleteBlock, + setSelectedBlock, + reorderBlock, + } = useMainEditorStore(); const selectedBlockPath = React.useMemo(() => { return generateChildrenPathFromArray(selectedBlock || []); }, [selectedBlock]); - const blockTree = generateTree(content.blocks); - - const renderTree = (items: TreeItem[], parentPathArray?: number[]) => { - return items.map(({type, treeTitle, children}, index) => { - let blockPathArray: number[]; - if (parentPathArray) { - blockPathArray = [...parentPathArray, index]; - } else { - blockPathArray = [index]; - } - const blockPath = generateChildrenPathFromArray(blockPathArray); - - return ( - <Item - type={type} - treeTitle={treeTitle} - onCopy={duplicateBlock} - onDelete={deleteBlock} - onSelect={setSelectedBlock} - key={index} - path={blockPathArray} - selected={selectedBlockPath === blockPath} - > - {children && ( - <div className={b('children')}>{renderTree(children, blockPathArray)}</div> - )} - </Item> - ); - }); - }; - return ( - <div className={b()}> - <div className={b('head')}> - <div className={b('actions')}> + <DragContextProvider> + <div className={b()}> + <div className={b('head')}> <Button view="outlined-danger" onClick={() => resetBlocks()}> <Icon data={TrashBin} /> Clear all </Button> </div> + <TreeContent + reorderBlock={reorderBlock} + onCopy={duplicateBlock} + onDelete={deleteBlock} + onSelect={setSelectedBlock} + blockTree={generateTree(content.blocks)} + selectedBlockPath={selectedBlockPath} + /> </div> - <div className={b('cards')}>{renderTree(blockTree)}</div> - </div> + </DragContextProvider> ); }; diff --git a/editor-v2/containers/Tree/TreeContent.scss b/editor-v2/containers/Tree/TreeContent.scss new file mode 100644 index 000000000..2f5842021 --- /dev/null +++ b/editor-v2/containers/Tree/TreeContent.scss @@ -0,0 +1,24 @@ +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}tree-content'; + +#{$block} { + position: relative; + + &__drop-zone { + height: 8px; + margin-bottom: 8px; + border-radius: 4px; + transition: background-color 0.2s; + + &:hover { + background-color: var(--g-color-text-light-secondary); + } + } + + &__children { + margin-top: 8px; + } +} diff --git a/editor-v2/containers/Tree/TreeContent.tsx b/editor-v2/containers/Tree/TreeContent.tsx new file mode 100644 index 000000000..bc2e25234 --- /dev/null +++ b/editor-v2/containers/Tree/TreeContent.tsx @@ -0,0 +1,121 @@ +import * as React from 'react'; + +import {generateChildrenPathFromArray} from '../../utils'; +import {DragContext, DragItem} from './DragContext'; +import {Item} from './TreeItem'; +import {editorCn} from '../../utils/cn'; + +import './TreeContent.scss'; + +const b = editorCn('tree-content'); + +interface TreeContentProps { + blockTree: TreeItem[]; + selectedBlockPath: string; + reorderBlock( + selectedBlock: number[], + destination: number[], + position?: 'prepend' | 'append', + ): void; + onCopy(path: number[]): void; + onDelete(path: number[]): void; + onSelect(path: number[]): void; +} + +export type TreeItem = { + type: string; + children?: TreeItem[]; + treeTitle?: string; +}; + +export const TreeContent = ({ + blockTree, + selectedBlockPath, + reorderBlock, + onCopy, + onDelete, + onSelect, +}: React.PropsWithChildren<TreeContentProps>) => { + const {draggedItem, setDraggedItem, hidePreview, showPreview} = React.useContext(DragContext); + + const handleFirstPositionDrop = React.useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + + try { + const data = e.dataTransfer.getData('application/json'); + if (data) { + const dragItem: DragItem = JSON.parse(data); + // Reorder to the first position by using [0] as destination + // and 'prepend' as position + reorderBlock(dragItem.path, [0], 'prepend'); + + // Reset drag context + setDraggedItem(null); + hidePreview(); + } + } catch (error) { + console.error('Error parsing drag data:', error); + } + }, + [reorderBlock, hidePreview, setDraggedItem], + ); + + const handleDragOver = React.useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = 'move'; + + // Show preview element at this position + const rect = e.currentTarget.getBoundingClientRect(); + if (draggedItem) { + showPreview(rect, draggedItem); + } + }, + [draggedItem, showPreview], + ); + + const renderTree = (items: TreeItem[], parentPathArray?: number[]) => { + return items.map(({type, treeTitle, children}, index) => { + let blockPathArray: number[]; + if (parentPathArray) { + blockPathArray = [...parentPathArray, index]; + } else { + blockPathArray = [index]; + } + const blockPath = generateChildrenPathFromArray(blockPathArray); + + return ( + <Item + type={type} + treeTitle={treeTitle} + onCopy={onCopy} + onDelete={onDelete} + onSelect={onSelect} + onReorder={reorderBlock} + key={index} + path={blockPathArray} + selected={selectedBlockPath === blockPath} + > + {children && ( + <div className={b('children')}>{renderTree(children, blockPathArray)}</div> + )} + </Item> + ); + }); + }; + + return ( + <div className={b()}> + <div + className={b('drop-zone')} + onDragOver={handleDragOver} + onDragLeave={hidePreview} + onDrop={handleFirstPositionDrop} + ></div> + <div>{renderTree(blockTree)}</div> + </div> + ); +}; diff --git a/editor-v2/containers/Tree/TreeItem.scss b/editor-v2/containers/Tree/TreeItem.scss new file mode 100644 index 000000000..ae821fcfe --- /dev/null +++ b/editor-v2/containers/Tree/TreeItem.scss @@ -0,0 +1,71 @@ +@import '../../styles/root.scss'; +@import '../../styles/variables.scss'; +@import '../../styles/mixins.scss'; + +$block: '.#{$ns}tree-item'; + +#{$block} { + padding: 8px; + margin-bottom: 8px; + cursor: pointer; + + &_selected { + border: 1.5px var(--g-color-line-brand) solid; + } + + &_dragging { + opacity: 0.5; + } + + &_drag-over { + border: 1.5px var(--g-color-line-generic) dashed; + } + + &:last-child { + margin-bottom: 0; + } + + &__main { + display: flex; + flex-direction: row; + align-items: center; + + &:hover { + #{$block}__buttons { + display: flex; + } + } + } + + &__text { + flex: 1 1 auto; + min-width: 1px; + } + + &__buttons { + flex: 0 0 auto; + flex-direction: row; + gap: 4px; + display: none; + } + + &__type { + color: var(--g-color-text-dark-secondary); + } + + &__title { + @include overflow-ellipsis(); + } + + &__children-drop-zone { + height: 8px; + margin-top: 8px; + margin-bottom: 0; + border-radius: 4px; + transition: background-color 0.2s; + + &:hover { + background-color: var(--g-color-text-light-secondary); + } + } +} diff --git a/editor-v2/containers/Tree/TreeItem.tsx b/editor-v2/containers/Tree/TreeItem.tsx new file mode 100644 index 000000000..96fbf51e0 --- /dev/null +++ b/editor-v2/containers/Tree/TreeItem.tsx @@ -0,0 +1,226 @@ +import {Copy, TrashBin} from '@gravity-ui/icons'; +import {Button, Card, Icon} from '@gravity-ui/uikit'; +import * as React from 'react'; + +import {editorCn} from '../../utils/cn'; +import {DragContext, DragItem} from './DragContext'; + +import './TreeItem.scss'; + +const b = editorCn('tree-item'); + +export interface ItemProps { + type: string; + treeTitle?: string; + path: number[]; + selected: boolean; + onCopy(path: number[]): void; + onDelete(path: number[]): void; + onSelect(path: number[]): void; + onReorder( + sourcePath: number[], + destinationPath: number[], + position?: 'prepend' | 'append', + ): void; + children?: React.ReactNode; +} + +export const Item = ({ + type, + children, + treeTitle, + path, + selected, + onCopy, + onDelete, + onSelect, + onReorder, +}: ItemProps) => { + const {draggedItem, setDraggedItem, showPreview, hidePreview} = React.useContext(DragContext); + const [isDragging, setIsDragging] = React.useState(false); + const [isDragOver, setIsDragOver] = React.useState(false); + const [mouseDownPos, setMouseDownPos] = React.useState<{x: number; y: number} | null>(null); + + const handleCopy = React.useCallback(() => { + onCopy(path); + }, [onCopy, path]); + + const handleDelete = React.useCallback(() => { + onDelete(path); + }, [onDelete, path]); + + const handleMouseDown = React.useCallback((e: any) => { + setMouseDownPos({x: e.clientX, y: e.clientY}); + }, []); + + const handleMouseUp = React.useCallback( + (e: any) => { + if (mouseDownPos) { + // Check if the mouse has moved significantly (dragging) or just a click + const dx = Math.abs(e.clientX - mouseDownPos.x); + const dy = Math.abs(e.clientY - mouseDownPos.y); + + // If the mouse hasn't moved much, consider it a click for selection + if (dx < 5 && dy < 5) { + e.stopPropagation(); + onSelect(path); + } + } + setMouseDownPos(null); + }, + [mouseDownPos, onSelect, path], + ); + + const handleDragStart = React.useCallback( + (e: any) => { + e.stopPropagation(); + const dragData: DragItem = { + path, + type, + treeTitle, + }; + e.dataTransfer.setData('application/json', JSON.stringify(dragData)); + e.dataTransfer.effectAllowed = 'move'; + setIsDragging(true); + + // Update drag context + setDraggedItem(dragData); + }, + [path, type, treeTitle, setDraggedItem], + ); + + const handleDragEnd = React.useCallback(() => { + setIsDragging(false); + + // Reset drag context + setDraggedItem(null); + hidePreview(); + }, [setDraggedItem, hidePreview, path]); + + const handleDragOver = React.useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = 'move'; + setIsDragOver(true); + + // Show preview element at this position + const rect = e.currentTarget.getBoundingClientRect(); + if (draggedItem) { + showPreview(rect, draggedItem); + } + }, + [showPreview, draggedItem], + ); + + const handleDragLeave = React.useCallback(() => { + setIsDragOver(false); + hidePreview(); + }, [hidePreview]); + + const handleDrop = React.useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + + try { + const data = e.dataTransfer.getData('application/json'); + if (data) { + const dragItem: DragItem = JSON.parse(data); + if (dragItem.path.join(',') !== path.join(',')) { + onReorder(dragItem.path, path); + + // Reset drag context + setDraggedItem(null); + hidePreview(); + } + } + } catch (error) { + console.error('Error parsing drag data:', error); + } + }, + [onReorder, path, setDraggedItem, hidePreview], + ); + + const handleChildrenFirstPositionDrop = React.useCallback( + (e: any) => { + e.preventDefault(); + e.stopPropagation(); + + try { + const data = e.dataTransfer.getData('application/json'); + if (data) { + const dragItem: DragItem = JSON.parse(data); + // Create a path for the first child position + const firstChildPath = [...path, 0]; + // Reorder to the first position within children + onReorder(dragItem.path, firstChildPath, 'prepend'); + + // Reset drag context + setDraggedItem(null); + hidePreview(); + } + } catch (error) { + console.error('Error parsing drag data:', error); + } + }, + [onReorder, path, setDraggedItem, hidePreview], + ); + + const handleDropZoneDragOver = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = 'move'; + + // Show preview element at this position + const rect = e.currentTarget.getBoundingClientRect(); + if (draggedItem) { + showPreview(rect, draggedItem); + } + }; + + return ( + <Card + className={b({ + selected, + dragging: isDragging, + 'drag-over': isDragOver, + })} + onMouseDown={handleMouseDown} + onMouseUp={handleMouseUp} + draggable + onDragStart={handleDragStart} + onDragEnd={handleDragEnd} + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleDrop} + > + <div className={b('main')}> + <div className={b('text')}> + <div className={b('type')}>{type}</div> + <div className={b('title')}>{treeTitle}</div> + </div> + <div className={b('buttons')}> + <Button view="flat" size="xs" onClick={handleCopy}> + <Icon size={12} data={Copy} /> + </Button> + <Button view="flat" size="xs" onClick={handleDelete}> + <Icon size={12} data={TrashBin} /> + </Button> + </div> + </div> + {children && ( + <React.Fragment> + <div + className={b('children-drop-zone')} + onDragOver={handleDropZoneDragOver} + onDragLeave={hidePreview} + onDrop={handleChildrenFirstPositionDrop} + ></div> + {children} + </React.Fragment> + )} + </Card> + ); +}; diff --git a/editor-v2/containers/Tree/index.ts b/editor-v2/containers/Tree/index.ts new file mode 100644 index 000000000..f5143b3bd --- /dev/null +++ b/editor-v2/containers/Tree/index.ts @@ -0,0 +1,4 @@ +export {default} from './Tree'; +export * from './DragContext'; +export * from './TreeItem'; +export * from './TreeContent'; diff --git a/editor-v2/hooks/useEditorTabs.tsx b/editor-v2/hooks/useEditorTabs.tsx index fb2199017..1f0e544fc 100644 --- a/editor-v2/hooks/useEditorTabs.tsx +++ b/editor-v2/hooks/useEditorTabs.tsx @@ -5,7 +5,7 @@ import BlockConfig from '../containers/BlockConfig/BlockConfig'; import BlocksList from '../containers/BlocksList/BlocksList'; import GlobalConfig from '../containers/GlobalConfig/GlobalConfig'; import SourceCode from '../containers/SourceCode/SourceCode'; -import Tree from '../containers/Tree/Tree'; +import Tree from '../containers/Tree'; export const useEditorTabs = ({ leftTabs = [], From 503f547efc9f8806dace0c7c1abb620cc6f60c6b Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 1 Apr 2025 21:02:15 +0300 Subject: [PATCH 30/84] fix: update editor block config styles --- .../DynamicForm/FieldBase/FieldBase.scss | 42 ++++++++++++++----- .../DynamicForm/FieldBase/FieldBase.tsx | 26 +++--------- .../DynamicForm/Fields/Boolean/Boolean.scss | 10 +++++ .../DynamicForm/Fields/Boolean/Boolean.tsx | 4 +- .../DynamicForm/Fields/Object/Object.tsx | 9 +--- .../DynamicForm/Fields/OneOf/OneOf.scss | 8 ++-- .../DynamicForm/Fields/OneOf/OneOf.tsx | 34 ++++++++++----- .../Object.scss => Select/Select.scss} | 6 +-- .../DynamicForm/Fields/Select/Select.tsx | 5 ++- .../containers/BlockConfig/BlockConfig.scss | 12 ++++-- .../containers/BlockConfig/BlockConfig.tsx | 12 +++--- 11 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss rename editor-v2/components/DynamicForm/Fields/{Object/Object.scss => Select/Select.scss} (57%) diff --git a/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss index b029dd1fa..92209faa5 100644 --- a/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss +++ b/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -6,8 +6,14 @@ $block: '.#{$ns}field-base'; #{$block} { $class: &; + padding: 16px 12px; + border-bottom: 1px solid var(--g-color-line-generic); position: relative; + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 8px; &:hover { & > #{$class}__top > #{$class}__button { @@ -15,16 +21,36 @@ $block: '.#{$ns}field-base'; } } + &_expandable { + flex-direction: column; + gap: 16px; + + & > #{$block}__top { + width: 100%; + flex-basis: initial; + } + & > #{$block}__children { + width: 100%; + } + } + &__top { display: flex; align-items: center; gap: 4px; - margin-bottom: 8px; + flex: 0 0 64px; + word-break: break-all; + } + + &__children { + flex: 2 1 auto; } &__foldable { + width: 100%; display: flex; align-items: center; + justify-content: space-between; cursor: pointer; } @@ -43,28 +69,24 @@ $block: '.#{$ns}field-base'; &__title { @include text-body-2; + margin: 5px 0; &_size { &_s { @include text-body-1; } + &_m { @include text-body-2; } + &_l { @include text-body-3; } } } - margin-top: 20px; - - &:first-child { - margin-top: 0; - } - - &__arrow-toggle { - //position: absolute; - //right: calc(100%); + &:last-child { + border-bottom: none; } } diff --git a/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx index 3b4dc5a55..edfb58f07 100644 --- a/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx +++ b/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -1,5 +1,4 @@ -import {ArrowRotateLeft} from '@gravity-ui/icons'; -import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit'; +import {ArrowToggle} from '@gravity-ui/uikit'; import _ from 'lodash'; import * as React from 'react'; @@ -23,9 +22,8 @@ export interface FieldBaseProps extends React.PropsWithChildren, FieldBaseParams const FieldBase: React.FC<FieldBaseProps> = ({ className, title, - textSize, + textSize = 's', children, - onRefresh, expandable = false, }) => { const [showChildren, setShowChildren] = React.useState(!expandable); @@ -40,11 +38,11 @@ const FieldBase: React.FC<FieldBaseProps> = ({ return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions <div className={b('foldable')} onClick={() => setShowChildren(!showChildren)}> + {defaultTitle} <ArrowToggle direction={showChildren ? 'bottom' : 'right'} className={b('arrow-toggle')} /> - {defaultTitle} </div> ); } @@ -56,22 +54,8 @@ const FieldBase: React.FC<FieldBaseProps> = ({ }, [expandable, showChildren, textSize, title]); return ( - <div className={b(null, className)}> - {title && ( - <div className={b('top')}> - {titleComponent} - {onRefresh && ( - <Button - className={b('button')} - onClick={() => onRefresh(undefined)} - view={'flat'} - size={'xs'} - > - <Icon data={ArrowRotateLeft} size={14} /> - </Button> - )} - </div> - )} + <div className={b({expandable}, className)}> + {title && <div className={b('top')}>{titleComponent}</div>} {(!title || showChildren) && <div className={b('children')}>{children}</div>} </div> ); diff --git a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss new file mode 100644 index 000000000..a3342d965 --- /dev/null +++ b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss @@ -0,0 +1,10 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; + +$block: '.#{$ns}boolean-dynamic-field'; + +#{$block} { + &__switch { + margin-top: 5px; + } +} diff --git a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx index 1bacfdc8f..21f3238db 100644 --- a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx +++ b/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx @@ -3,6 +3,8 @@ import {Switch} from '@gravity-ui/uikit'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; +import './Boolean.scss'; + const b = editorCn('boolean-dynamic-field'); interface BooleanProps extends FieldBaseParams { @@ -14,7 +16,7 @@ interface BooleanProps extends FieldBaseParams { const BooleanDynamicField = ({title, value, onUpdate, className}: BooleanProps) => { return ( <FieldBase title={title} className={b(null, className)} onRefresh={onUpdate}> - <Switch checked={Boolean(value)} onUpdate={onUpdate} /> + <Switch className={b('switch')} checked={Boolean(value)} onUpdate={onUpdate} /> </FieldBase> ); }; diff --git a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index 72ad24941..3711df5be 100644 --- a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -1,14 +1,9 @@ import {Card} from '@gravity-ui/uikit'; import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; -import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -import './Object.scss'; - -const b = editorCn('object-dynamic-field'); - interface ObjectDynamicFieldProps extends FieldBaseParams { value: DynamicFormValue; onUpdate: (key: string, value: DynamicFormValue) => void; @@ -26,11 +21,11 @@ const ObjectDynamicField = ({ return ( <FieldBase title={title} - className={b(null, className)} + className={className} onRefresh={(updatedValue) => onUpdate('', updatedValue)} expandable > - <Card className={b('card')}> + <Card> <DynamicForm contentConfig={value} blockConfig={blockConfig} onUpdate={onUpdate} /> </Card> </FieldBase> diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss index b7660714b..35de5b6db 100644 --- a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss @@ -4,11 +4,11 @@ $block: '.#{$ns}oneof-dynamic-field'; #{$block} { - &__card { - padding: 12px; - } - &__radio { margin-bottom: 12px; } + + &__select { + min-width: 80px; + } } diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 941f5e070..31ed85ff3 100644 --- a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -1,4 +1,4 @@ -import {Card, SegmentedRadioGroup} from '@gravity-ui/uikit'; +import {Card, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import * as React from 'react'; import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; @@ -55,16 +55,28 @@ const OneOfDynamicField = ({ onRefresh={(value) => onUpdate('', value)} expandable > - <Card className={b('card')}> - <SegmentedRadioGroup - className={b('radio')} - options={inputConfig.options.map((option) => ({ - content: option.title, - value: option.value, - }))} - value={oneOfMetaValue} - onUpdate={onUpdateOneOf} - /> + <Card> + {inputConfig.options.length < 4 ? ( + <SegmentedRadioGroup + className={b('radio')} + options={inputConfig.options.map((option) => ({ + content: option.title, + value: option.value, + }))} + value={oneOfMetaValue} + onUpdate={onUpdateOneOf} + /> + ) : ( + <Select + options={inputConfig.options.map((option) => ({ + content: option.title, + value: option.value, + }))} + value={oneOfMetaValue ? [oneOfMetaValue] : []} + onUpdate={([selectValue]) => onUpdateOneOf(selectValue)} + className={b('select')} + /> + )} {oneOfChosenOption && ( <DynamicForm blockConfig={oneOfChosenOption.properties} diff --git a/editor-v2/components/DynamicForm/Fields/Object/Object.scss b/editor-v2/components/DynamicForm/Fields/Select/Select.scss similarity index 57% rename from editor-v2/components/DynamicForm/Fields/Object/Object.scss rename to editor-v2/components/DynamicForm/Fields/Select/Select.scss index 7092b9aff..dbc0748bc 100644 --- a/editor-v2/components/DynamicForm/Fields/Object/Object.scss +++ b/editor-v2/components/DynamicForm/Fields/Select/Select.scss @@ -1,10 +1,10 @@ @import '../../../../styles/variables.scss'; @import '../../../../styles/mixins.scss'; -$block: '.#{$ns}object-dynamic-field'; +$block: '.#{$ns}editor-select-field'; #{$block} { - &__card { - padding: 12px; + &__select { + min-width: 80px; } } diff --git a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx index a61e4e28d..13bcbe0dc 100644 --- a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -4,7 +4,9 @@ import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/type import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; -const b = editorCn('select-field'); +import './Select.scss'; + +const b = editorCn('editor-select-field'); type SelectInput = SelectSingleInput | SelectMultipleInput; @@ -26,6 +28,7 @@ const SelectDynamicField = ({input, value, onUpdate, className}: SelectDynamicFi value={value ? [value] : []} onUpdate={([selectValue]) => onUpdate(selectValue)} options={input.enum} + className={b('select')} /> )} {inputView === 'radiobutton' && ( diff --git a/editor-v2/containers/BlockConfig/BlockConfig.scss b/editor-v2/containers/BlockConfig/BlockConfig.scss index 0411ca8ad..14921baad 100644 --- a/editor-v2/containers/BlockConfig/BlockConfig.scss +++ b/editor-v2/containers/BlockConfig/BlockConfig.scss @@ -5,14 +5,18 @@ $block: '.#{$ns}block-config'; #{$block} { - padding: 12px; - &__title { - margin-bottom: 16px; - margin-top: 8px; + @include text-subheader-3; + padding: 12px 16px; + border-bottom: 1px solid var(--g-color-line-generic); + } + + &__form { + padding: 12px 0; } &__empty { + padding: 12px 16px; display: flex; justify-content: center; align-items: center; diff --git a/editor-v2/containers/BlockConfig/BlockConfig.tsx b/editor-v2/containers/BlockConfig/BlockConfig.tsx index e8f126356..c0b753d07 100644 --- a/editor-v2/containers/BlockConfig/BlockConfig.tsx +++ b/editor-v2/containers/BlockConfig/BlockConfig.tsx @@ -37,11 +37,13 @@ const BlockConfig = ({className}: BlockConfigProps) => { return ( <div className={b(null, className)}> <div className={b('title')}>{currentSchema.schema.name}</div> - <DynamicForm - contentConfig={currentConfig} - blockConfig={currentSchema.schema.inputs} - onUpdate={onUpdate} - /> + <div className={b('form')}> + <DynamicForm + contentConfig={currentConfig} + blockConfig={currentSchema.schema.inputs} + onUpdate={onUpdate} + /> + </div> </div> ); }; From baa0d2c0c7634f7a2a8a39af0c8adc64b291917d Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 2 Apr 2025 20:56:06 +0300 Subject: [PATCH 31/84] fix: update selected block borders --- common/types/actions.ts | 2 ++ editor-v2/containers/Overlay/Overlay.tsx | 26 ++++++++++++++++++++++-- src/hooks/usePCEditorBlockMouseEvents.ts | 20 +++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/common/types/actions.ts b/common/types/actions.ts index 2bde27f54..da55bd75f 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -11,6 +11,7 @@ export type EventMessageTypes = { ON_HOVER_BLOCK: {rect?: DOMRect; position?: string}; ON_CLICK_BLOCK: {path: number[]; rect: DOMRect}; ON_RESIZE_BLOCK: {rect: DOMRect}; + ON_UPDATE_SELECTED_BLOCK: {path: number[]; rect: DOMRect}; ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; ON_INITIAL_CONTENT: PageContentWithNavigation; }; @@ -18,4 +19,5 @@ export type EventMessageTypes = { export type ActionMessageTypes = { GET_SUPPORTED_BLOCKS: {}; GET_INITIAL_CONTENT: {}; + UPDATE_SELECTED_BLOCK: {path: number[]}; }; diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index 759d9fbb0..ea66cd5c5 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {usePostMessageEvents} from '../../hooks/usePostMessageEvents'; import {editorCn} from '../../utils/cn'; import './Overlay.scss'; @@ -22,6 +23,7 @@ interface InsertLineProps { } const Overlay = ({className}: OverlayProps) => { + const {requestPostMessage} = usePostMessageEvents(); const { height, selectedBlock, @@ -61,13 +63,33 @@ const Overlay = ({className}: OverlayProps) => { setBlockBorders(rect || null); }); + usePostMessageAPIListener( + 'ON_UPDATE_SELECTED_BLOCK', + ({rect, path}) => { + if (selectedBlock && JSON.stringify(selectedBlock) === JSON.stringify(path)) { + setBlockBorders(rect || null); + } + }, + [selectedBlock], + ); + + // Update blockBorders when selectedBlock changes + React.useEffect(() => { + if (!selectedBlock) { + setBlockBorders(null); + } else { + // If a block is selected, trigger the UPDATE_SELECTED_BLOCK action to update blockBorders + requestPostMessage('UPDATE_SELECTED_BLOCK', {path: selectedBlock}); + } + }, [selectedBlock]); + const handleMoveUp = () => { if (!selectedBlock) return; const destination = [...selectedBlock]; destination[destination.length - 1] = destination[destination.length - 1] - 1; reorderBlock(selectedBlock, destination, 'prepend'); setSelectedBlock(undefined); - setBlockBorders(null); + // blockBorders will be set to null by the useEffect hook }; const handleMoveDown = () => { @@ -76,7 +98,7 @@ const Overlay = ({className}: OverlayProps) => { destination[destination.length - 1] = destination[destination.length - 1] + 1; reorderBlock(selectedBlock, destination, 'append'); setSelectedBlock(undefined); - setBlockBorders(null); + // blockBorders will be set to null by the useEffect hook }; return ( diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts index 942422371..5027e2326 100644 --- a/src/hooks/usePCEditorBlockMouseEvents.ts +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import {getCursorPositionOverElement} from '../utils/editor'; import {usePCEditorStore} from './usePCEditorStore'; -import {sendEventPostMessage} from './usePostMessageAPI'; +import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { const {selectedBlock} = usePCEditorStore(); @@ -77,6 +77,24 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement }; }, [element, onResize]); + // Listen for the UPDATE_SELECTED_BLOCK action and handle it directly + const handleUpdateSelectedBlock = React.useCallback( + (data: {path?: number[]}) => { + if (data && data.path && Array.isArray(data.path) && _.isEqual(data.path, arrayIndex)) { + // Only proceed if the path matches arrayIndex + if (element) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_UPDATE_SELECTED_BLOCK', {rect, path: arrayIndex}); + } + } + } + }, + [arrayIndex, element], + ); + + useInternalPostMessageAPIListener('UPDATE_SELECTED_BLOCK', handleUpdateSelectedBlock); + return { onClick, onMouseMove, From b37e75e6462f2391e6296fa671e971e8bf8eb8cd Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 2 Apr 2025 21:02:22 +0300 Subject: [PATCH 32/84] fix: remove unused hook --- .../hooks/useEditorBlockMouseEvents.tsx | 87 ------------------- 1 file changed, 87 deletions(-) delete mode 100644 src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx diff --git a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx b/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx deleted file mode 100644 index f42540ea1..000000000 --- a/src/containers/PageConstructor/components/ConstructorBlock/hooks/useEditorBlockMouseEvents.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from 'react'; - -import _ from 'lodash'; - -import {usePCEditorStore} from '../../../../../hooks/usePCEditorStore'; -import {sendEventPostMessage} from '../../../../../hooks/usePostMessageAPI'; -import {getCursorPositionOverElement} from '../../../../../utils/editor'; - -const useEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { - const {selectedBlock} = usePCEditorStore(); - - const onMouseUp = React.useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - const position = getCursorPositionOverElement(rect, e); - sendEventPostMessage('ON_MOUSE_UP', {path: arrayIndex, rect, position}); - } - } - }, - [arrayIndex, element], - ); - - const onMouseMove = React.useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - const position = getCursorPositionOverElement(rect, e); - sendEventPostMessage('ON_HOVER_BLOCK', {rect, position}); - } - } - }, - [element], - ); - - const onMouseLeave = React.useCallback(() => { - if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_HOVER_BLOCK', {}); - } - } - }, [element]); - - const onClick = React.useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_CLICK_BLOCK', {rect, path: arrayIndex}); - } - } - }, - [arrayIndex, element], - ); - - const onResize = React.useCallback(() => { - if (element && _.isEqual(selectedBlock, arrayIndex)) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_RESIZE_BLOCK', {rect}); - } - } - }, [arrayIndex, element, selectedBlock]); - - React.useEffect(() => { - window.addEventListener('resize', onResize); - - return () => { - window.removeEventListener('resize', onResize); - }; - }, [element, onResize]); - - return { - onClick, - onMouseMove, - onMouseUp, - onMouseLeave, - }; -}; - -export default useEditorBlockMouseEvents; From fa92ec73335d1d028ef20dabcaf76a8bdc83d170 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 2 Apr 2025 21:09:40 +0300 Subject: [PATCH 33/84] fix: remove block selection from click event --- common/types/actions.ts | 2 +- editor-v2/containers/Overlay/Overlay.tsx | 3 +-- src/hooks/usePCEditorBlockMouseEvents.ts | 5 +---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/common/types/actions.ts b/common/types/actions.ts index da55bd75f..6751c228b 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -9,7 +9,7 @@ export type EventMessageTypes = { ON_MOUSE_UP: {path?: number[]; rect?: DOMRect; position?: string}; ON_MOUSE_MOVE: {x: number; y: number}; ON_HOVER_BLOCK: {rect?: DOMRect; position?: string}; - ON_CLICK_BLOCK: {path: number[]; rect: DOMRect}; + ON_CLICK_BLOCK: {path: number[]}; ON_RESIZE_BLOCK: {rect: DOMRect}; ON_UPDATE_SELECTED_BLOCK: {path: number[]; rect: DOMRect}; ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index ea66cd5c5..1a97cf9a1 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -54,9 +54,8 @@ const Overlay = ({className}: OverlayProps) => { } }); - usePostMessageAPIListener('ON_CLICK_BLOCK', ({rect, path}) => { + usePostMessageAPIListener('ON_CLICK_BLOCK', ({path}) => { setSelectedBlock(path); - setBlockBorders(rect || null); }); usePostMessageAPIListener('ON_RESIZE_BLOCK', ({rect}) => { diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts index 5027e2326..c758d5c22 100644 --- a/src/hooks/usePCEditorBlockMouseEvents.ts +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -51,10 +51,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement (e: React.MouseEvent) => { e.stopPropagation(); if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_CLICK_BLOCK', {rect, path: arrayIndex}); - } + sendEventPostMessage('ON_CLICK_BLOCK', {path: arrayIndex}); } }, [arrayIndex, element], From 6509d72deae7f4c16cda67ea2aa4f203ada62751 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 3 Apr 2025 12:57:37 +0300 Subject: [PATCH 34/84] feat: add scroll to selected block --- .../containers/MiddleScreen/MiddleScreen.tsx | 4 +- editor-v2/containers/Overlay/Overlay.tsx | 36 +++---------- editor-v2/hooks/useSelectedBlockBorders.ts | 54 +++++++++++++++++++ src/blocks/CardLayout/CardLayout.tsx | 17 +++--- src/blocks/Slider/Slider.tsx | 27 +++++----- .../editor/ChildrenWrap/ChildrenWrap.scss | 8 --- .../editor/ChildrenWrap/ChildrenWrap.tsx | 28 ---------- 7 files changed, 82 insertions(+), 92 deletions(-) create mode 100644 editor-v2/hooks/useSelectedBlockBorders.ts delete mode 100644 src/components/editor/ChildrenWrap/ChildrenWrap.scss delete mode 100644 src/components/editor/ChildrenWrap/ChildrenWrap.tsx diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 6ccf9dbc3..198c759ef 100644 --- a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -19,6 +19,7 @@ interface MiddleScreenProps { const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { const {zoom, initialized, deviceWidth} = useMainEditorStore(); const {url, setIframeElement} = React.useContext(IframeContext); + const [canvasRef, setCanvasRef] = React.useState<HTMLDivElement | null>(null); const [height, setHeight] = React.useState(0); const onResize = React.useCallback( @@ -46,6 +47,7 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { <div className={b('content')}> <div className={b('wrapper')} style={{width: deviceWidth}}> <div + ref={setCanvasRef} className={b('canvas', {hidden: !initialized})} style={{ transform: `scale(${zoom}%)`, @@ -62,7 +64,7 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { frameBorder="0" title="Page Constructor Iframe" /> - <Overlay className={b('overlay')} /> + <Overlay className={b('overlay')} canvasElement={canvasRef} /> {!initialized && ( <div className={b('loading')}> <Loader size={'l'} /> diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index 1a97cf9a1..4c6c22e05 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; -import {usePostMessageEvents} from '../../hooks/usePostMessageEvents'; +import {useSelectedBlockBorders} from '../../hooks/useSelectedBlockBorders'; import {editorCn} from '../../utils/cn'; import './Overlay.scss'; @@ -13,6 +13,7 @@ const b = editorCn('overlay'); interface OverlayProps { className?: string; + canvasElement?: HTMLDivElement | null; } interface InsertLineProps { top: number; @@ -22,8 +23,7 @@ interface InsertLineProps { position: string; } -const Overlay = ({className}: OverlayProps) => { - const {requestPostMessage} = usePostMessageEvents(); +const Overlay = ({className, canvasElement}: OverlayProps) => { const { height, selectedBlock, @@ -37,7 +37,9 @@ const Overlay = ({className}: OverlayProps) => { undefined, ); const [hoverBorders, setHoverBorders] = React.useState<DOMRect | null>(null); - const [blockBorders, setBlockBorders] = React.useState<DOMRect | null>(null); + + // Use the hook to get blockBorders and handle auto-scrolling + const blockBorders = useSelectedBlockBorders(selectedBlock, canvasElement); const margin = 0; @@ -58,37 +60,12 @@ const Overlay = ({className}: OverlayProps) => { setSelectedBlock(path); }); - usePostMessageAPIListener('ON_RESIZE_BLOCK', ({rect}) => { - setBlockBorders(rect || null); - }); - - usePostMessageAPIListener( - 'ON_UPDATE_SELECTED_BLOCK', - ({rect, path}) => { - if (selectedBlock && JSON.stringify(selectedBlock) === JSON.stringify(path)) { - setBlockBorders(rect || null); - } - }, - [selectedBlock], - ); - - // Update blockBorders when selectedBlock changes - React.useEffect(() => { - if (!selectedBlock) { - setBlockBorders(null); - } else { - // If a block is selected, trigger the UPDATE_SELECTED_BLOCK action to update blockBorders - requestPostMessage('UPDATE_SELECTED_BLOCK', {path: selectedBlock}); - } - }, [selectedBlock]); - const handleMoveUp = () => { if (!selectedBlock) return; const destination = [...selectedBlock]; destination[destination.length - 1] = destination[destination.length - 1] - 1; reorderBlock(selectedBlock, destination, 'prepend'); setSelectedBlock(undefined); - // blockBorders will be set to null by the useEffect hook }; const handleMoveDown = () => { @@ -97,7 +74,6 @@ const Overlay = ({className}: OverlayProps) => { destination[destination.length - 1] = destination[destination.length - 1] + 1; reorderBlock(selectedBlock, destination, 'append'); setSelectedBlock(undefined); - // blockBorders will be set to null by the useEffect hook }; return ( diff --git a/editor-v2/hooks/useSelectedBlockBorders.ts b/editor-v2/hooks/useSelectedBlockBorders.ts new file mode 100644 index 000000000..41cff4f7d --- /dev/null +++ b/editor-v2/hooks/useSelectedBlockBorders.ts @@ -0,0 +1,54 @@ +import * as React from 'react'; + +import {usePostMessageAPIListener} from '../../common/postMessage'; +import {usePostMessageEvents} from './usePostMessageEvents'; + +/** + * Hook to track selected block borders and auto-scroll to the selected block + * @param selectedBlock The currently selected block path + * @param canvasElement The canvas element reference that contains the iframe + * @returns The borders of the selected block + */ +export const useSelectedBlockBorders = ( + selectedBlock: any, + canvasElement?: HTMLDivElement | null, +) => { + const [blockBorders, setBlockBorders] = React.useState<DOMRect | null>(null); + const {requestPostMessage} = usePostMessageEvents(); + + // Listen for updates to the selected block's position + usePostMessageAPIListener('ON_UPDATE_SELECTED_BLOCK', ({rect}) => { + setBlockBorders(rect || null); + }); + + // Update blockBorders when selectedBlock changes + React.useEffect(() => { + console.log(321, selectedBlock); + if (!selectedBlock) { + setBlockBorders(null); + } else { + // Only request an update if we haven't already done so for this block + requestPostMessage('UPDATE_SELECTED_BLOCK', {path: selectedBlock}); + } + }, [selectedBlock]); + + // Auto scroll to the selected block when blockBorders changes + React.useEffect(() => { + if (blockBorders && canvasElement) { + // Calculate the scroll position to center the block in the viewport + const canvasHeight = canvasElement.clientHeight; + const scrollPosition = blockBorders.top - canvasHeight / 2 + blockBorders.height / 2; + + // Scroll the canvas element to the calculated position with smooth behavior + canvasElement.scrollTo({ + top: Math.max(0, scrollPosition), + behavior: 'smooth', + }); + } + }, [blockBorders, canvasElement]); + + React.useEffect(() => { + console.log('blockBorders'); + }, [blockBorders]); + return blockBorders; +}; diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx index e523278de..ba8357594 100644 --- a/src/blocks/CardLayout/CardLayout.tsx +++ b/src/blocks/CardLayout/CardLayout.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import isEmpty from 'lodash/isEmpty'; import {AnimateBlock, BackgroundImage, Title} from '../../components'; -import ChildrenWrap from '../../components/editor/ChildrenWrap/ChildrenWrap'; import ItemWrap from '../../components/editor/ItemWrap/ItemWrap'; import {useTheme} from '../../context/theme'; import {Col, Grid, GridColumnSizesType, Row} from '../../grid'; @@ -48,15 +47,13 @@ const CardLayout: React.FC<CardLayoutBlockProps> = ({ > <BackgroundImage className={b('image', {border})} {...backgroundImageProps} /> - <ChildrenWrap> - <Row> - {React.Children.map(children, (child, index) => ( - <Col key={index} sizes={colSizes} className={b('item')}> - <ItemWrap index={index}>{child}</ItemWrap> - </Col> - ))} - </Row> - </ChildrenWrap> + <Row> + {React.Children.map(children, (child, index) => ( + <Col key={index} sizes={colSizes} className={b('item')}> + <ItemWrap index={index}>{child}</ItemWrap> + </Col> + ))} + </Row> </div> </Grid> </AnimateBlock> diff --git a/src/blocks/Slider/Slider.tsx b/src/blocks/Slider/Slider.tsx index 9fb3ec23d..75b94f7a9 100644 --- a/src/blocks/Slider/Slider.tsx +++ b/src/blocks/Slider/Slider.tsx @@ -10,7 +10,6 @@ import Anchor from '../../components/Anchor/Anchor'; import AnimateBlock from '../../components/AnimateBlock/AnimateBlock'; import OutsideClick from '../../components/OutsideClick/OutsideClick'; import Title from '../../components/Title/Title'; -import ChildrenWrap from '../../components/editor/ChildrenWrap/ChildrenWrap'; import ItemWrap from '../../components/editor/ItemWrap/ItemWrap'; import {BREAKPOINTS} from '../../constants'; import {MobileContext} from '../../context/mobileContext'; @@ -428,20 +427,18 @@ export const SliderBlock = (props: React.PropsWithChildren<SliderProps>) => { }; return ( - <ChildrenWrap> - <OutsideClick onOutsideClick={isMobile ? unsetFocus : noop}> - <SlickSlider {...settings}> - {React.Children.map(disclosedChildren, (child, index) => ( - <ItemWrap index={index}>{child}</ItemWrap> - ))} - </SlickSlider> - - <div className={b('footer')}> - {renderDisclaimer()} - {renderNavigation()} - </div> - </OutsideClick> - </ChildrenWrap> + <OutsideClick onOutsideClick={isMobile ? unsetFocus : noop}> + <SlickSlider {...settings}> + {React.Children.map(disclosedChildren, (child, index) => ( + <ItemWrap index={index}>{child}</ItemWrap> + ))} + </SlickSlider> + + <div className={b('footer')}> + {renderDisclaimer()} + {renderNavigation()} + </div> + </OutsideClick> ); }; diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.scss b/src/components/editor/ChildrenWrap/ChildrenWrap.scss deleted file mode 100644 index c583e86a8..000000000 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import '../../../../styles/mixins.scss'; -@import '../../../../styles/variables.scss'; - -$block: '.#{$ns}children-wrap'; - -#{$block} { - min-height: 50px; -} diff --git a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx b/src/components/editor/ChildrenWrap/ChildrenWrap.tsx deleted file mode 100644 index 068ece91b..000000000 --- a/src/components/editor/ChildrenWrap/ChildrenWrap.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from 'react'; - -import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap'; -import {block} from '../../../utils'; - -import './ChildrenWrap.scss'; - -const b = block('children-wrap'); - -export interface ChildrenWrapProps extends React.PropsWithChildren { - checkChildren?: React.ReactNode; -} - -const ChildrenWrap = ({children}: ChildrenWrapProps) => { - const { - blockRef, - adminBlockMouseEvents: {onMouseMove, onMouseUp}, - } = usePCEditorItemWrap(); - - return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions - <div ref={blockRef} className={b()} onMouseMove={onMouseMove} onMouseUp={onMouseUp}> - {children} - </div> - ); -}; - -export default ChildrenWrap; From e1365b868a607007189def66813f0e5a29594b86 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 3 Apr 2025 13:01:46 +0300 Subject: [PATCH 35/84] feat: add scroll to selected block in the Tree component --- editor-v2/containers/Tree/TreeItem.tsx | 12 ++++++++++++ editor-v2/hooks/useSelectedBlockBorders.ts | 4 ---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/editor-v2/containers/Tree/TreeItem.tsx b/editor-v2/containers/Tree/TreeItem.tsx index 96fbf51e0..dc80e83a2 100644 --- a/editor-v2/containers/Tree/TreeItem.tsx +++ b/editor-v2/containers/Tree/TreeItem.tsx @@ -40,6 +40,17 @@ export const Item = ({ const [isDragging, setIsDragging] = React.useState(false); const [isDragOver, setIsDragOver] = React.useState(false); const [mouseDownPos, setMouseDownPos] = React.useState<{x: number; y: number} | null>(null); + const itemRef = React.useRef<HTMLDivElement>(null); + + // Scroll into view when selected + React.useEffect(() => { + if (selected && itemRef.current) { + itemRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + }, [selected]); const handleCopy = React.useCallback(() => { onCopy(path); @@ -182,6 +193,7 @@ export const Item = ({ return ( <Card + ref={itemRef} className={b({ selected, dragging: isDragging, diff --git a/editor-v2/hooks/useSelectedBlockBorders.ts b/editor-v2/hooks/useSelectedBlockBorders.ts index 41cff4f7d..087979809 100644 --- a/editor-v2/hooks/useSelectedBlockBorders.ts +++ b/editor-v2/hooks/useSelectedBlockBorders.ts @@ -23,7 +23,6 @@ export const useSelectedBlockBorders = ( // Update blockBorders when selectedBlock changes React.useEffect(() => { - console.log(321, selectedBlock); if (!selectedBlock) { setBlockBorders(null); } else { @@ -47,8 +46,5 @@ export const useSelectedBlockBorders = ( } }, [blockBorders, canvasElement]); - React.useEffect(() => { - console.log('blockBorders'); - }, [blockBorders]); return blockBorders; }; From 4fb393e6e86ce1294749c933c6cd6f05b2e60daa Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 3 Apr 2025 16:19:43 +0300 Subject: [PATCH 36/84] fix: selected block after blocks reorder --- editor-v2/containers/Overlay/Overlay.tsx | 2 -- editor-v2/hooks/useMainEditorInitialize.ts | 2 -- editor-v2/store.ts | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index 4c6c22e05..8e4c2f339 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -65,7 +65,6 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { const destination = [...selectedBlock]; destination[destination.length - 1] = destination[destination.length - 1] - 1; reorderBlock(selectedBlock, destination, 'prepend'); - setSelectedBlock(undefined); }; const handleMoveDown = () => { @@ -73,7 +72,6 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { const destination = [...selectedBlock]; destination[destination.length - 1] = destination[destination.length - 1] + 1; reorderBlock(selectedBlock, destination, 'append'); - setSelectedBlock(undefined); }; return ( diff --git a/editor-v2/hooks/useMainEditorInitialize.ts b/editor-v2/hooks/useMainEditorInitialize.ts index b1ba59560..2326b95ec 100644 --- a/editor-v2/hooks/useMainEditorInitialize.ts +++ b/editor-v2/hooks/useMainEditorInitialize.ts @@ -13,7 +13,6 @@ const useMainEditorInitialize = () => { disableMode, insertBlock, reorderBlock, - setSelectedBlock, preInsertBlockType, preReorderBlockPath, } = useMainEditorStore(); @@ -52,7 +51,6 @@ const useMainEditorInitialize = () => { path, ['left', 'top'].includes(position) ? 'prepend' : 'append', ); - setSelectedBlock(path); } disableMode(); }, diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 576068945..9fefcf93e 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -109,6 +109,9 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( ...state, content: {...state.content, blocks: newBlocksConfig}, })); + + // Set the inserted block as selected + get().setSelectedBlock(arrayPath); }, enableInsertMode(blockType: string) { set((state) => ({ @@ -180,6 +183,9 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( })); }, reorderBlock: (arrayPath, destination, position = 'append') => { + // Create a copy of the destination array before any modifications + let finalDestinationPath: number[] = _.cloneDeep(destination); + if (position === 'append') { // TODO: fix // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign @@ -199,8 +205,17 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( destination[destination.length - 1], ); }); + + if ( + position === 'append' && + destination[destination.length - 1] < arrayPath[arrayPath.length - 1] + ) { + finalDestinationPath[finalDestinationPath.length - 1] = + finalDestinationPath[finalDestinationPath.length - 1] + 1; + } } else { const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination); + finalDestinationPath = _.cloneDeep(arrayDest); // Delete const blocksConfigWithoutBlock = modifyObjectByPath( @@ -220,6 +235,8 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( ...state, content: {...state.content, blocks: newBlocksConfig}, })); + + get().setSelectedBlock(finalDestinationPath); }, resetInitialize: () => { set((state) => ({ From 8bc65a23f2e4f06a7d60368b27e0a4eb8c97853b Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 3 Apr 2025 18:05:48 +0300 Subject: [PATCH 37/84] fix: refactor pc editor events --- common/types/actions.ts | 4 +- editor-v2/containers/Overlay/Overlay.tsx | 30 +++++++++++-- editor-v2/hooks/useSelectedBlockBorders.ts | 50 ---------------------- src/hooks/usePCEditorBlockMouseEvents.ts | 40 +---------------- src/hooks/usePCEditorBlockSelection.ts | 32 ++++++++++++++ src/hooks/usePCEditorItemWrap.ts | 2 + 6 files changed, 63 insertions(+), 95 deletions(-) delete mode 100644 editor-v2/hooks/useSelectedBlockBorders.ts create mode 100644 src/hooks/usePCEditorBlockSelection.ts diff --git a/common/types/actions.ts b/common/types/actions.ts index 6751c228b..ecb2bcd9f 100644 --- a/common/types/actions.ts +++ b/common/types/actions.ts @@ -10,8 +10,7 @@ export type EventMessageTypes = { ON_MOUSE_MOVE: {x: number; y: number}; ON_HOVER_BLOCK: {rect?: DOMRect; position?: string}; ON_CLICK_BLOCK: {path: number[]}; - ON_RESIZE_BLOCK: {rect: DOMRect}; - ON_UPDATE_SELECTED_BLOCK: {path: number[]; rect: DOMRect}; + ON_UPDATE_BLOCK_SELECTION: {rect: DOMRect}; ON_SUPPORTED_BLOCKS: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>; ON_INITIAL_CONTENT: PageContentWithNavigation; }; @@ -19,5 +18,4 @@ export type EventMessageTypes = { export type ActionMessageTypes = { GET_SUPPORTED_BLOCKS: {}; GET_INITIAL_CONTENT: {}; - UPDATE_SELECTED_BLOCK: {path: number[]}; }; diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/editor-v2/containers/Overlay/Overlay.tsx index 8e4c2f339..6d8933909 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/editor-v2/containers/Overlay/Overlay.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; -import {useSelectedBlockBorders} from '../../hooks/useSelectedBlockBorders'; import {editorCn} from '../../utils/cn'; import './Overlay.scss'; @@ -37,9 +36,34 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { undefined, ); const [hoverBorders, setHoverBorders] = React.useState<DOMRect | null>(null); + const [blockBorders, setBlockBorders] = React.useState<DOMRect | null>(null); - // Use the hook to get blockBorders and handle auto-scrolling - const blockBorders = useSelectedBlockBorders(selectedBlock, canvasElement); + // Listen for updates to the selected block's position + usePostMessageAPIListener('ON_UPDATE_BLOCK_SELECTION', ({rect}) => { + setBlockBorders(rect || null); + }); + + // Update blockBorders when selectedBlock changes + React.useEffect(() => { + if (!selectedBlock) { + setBlockBorders(null); + } + }, [selectedBlock]); + + // Auto scroll to the selected block when blockBorders changes + React.useEffect(() => { + if (blockBorders && canvasElement) { + // Calculate the scroll position to center the block in the viewport + const canvasHeight = canvasElement.clientHeight; + const scrollPosition = blockBorders.top - canvasHeight / 2 + blockBorders.height / 2; + + // Scroll the canvas element to the calculated position with smooth behavior + canvasElement.scrollTo({ + top: Math.max(0, scrollPosition), + behavior: 'smooth', + }); + } + }, [blockBorders, canvasElement]); const margin = 0; diff --git a/editor-v2/hooks/useSelectedBlockBorders.ts b/editor-v2/hooks/useSelectedBlockBorders.ts deleted file mode 100644 index 087979809..000000000 --- a/editor-v2/hooks/useSelectedBlockBorders.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; - -import {usePostMessageAPIListener} from '../../common/postMessage'; -import {usePostMessageEvents} from './usePostMessageEvents'; - -/** - * Hook to track selected block borders and auto-scroll to the selected block - * @param selectedBlock The currently selected block path - * @param canvasElement The canvas element reference that contains the iframe - * @returns The borders of the selected block - */ -export const useSelectedBlockBorders = ( - selectedBlock: any, - canvasElement?: HTMLDivElement | null, -) => { - const [blockBorders, setBlockBorders] = React.useState<DOMRect | null>(null); - const {requestPostMessage} = usePostMessageEvents(); - - // Listen for updates to the selected block's position - usePostMessageAPIListener('ON_UPDATE_SELECTED_BLOCK', ({rect}) => { - setBlockBorders(rect || null); - }); - - // Update blockBorders when selectedBlock changes - React.useEffect(() => { - if (!selectedBlock) { - setBlockBorders(null); - } else { - // Only request an update if we haven't already done so for this block - requestPostMessage('UPDATE_SELECTED_BLOCK', {path: selectedBlock}); - } - }, [selectedBlock]); - - // Auto scroll to the selected block when blockBorders changes - React.useEffect(() => { - if (blockBorders && canvasElement) { - // Calculate the scroll position to center the block in the viewport - const canvasHeight = canvasElement.clientHeight; - const scrollPosition = blockBorders.top - canvasHeight / 2 + blockBorders.height / 2; - - // Scroll the canvas element to the calculated position with smooth behavior - canvasElement.scrollTo({ - top: Math.max(0, scrollPosition), - behavior: 'smooth', - }); - } - }, [blockBorders, canvasElement]); - - return blockBorders; -}; diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts index c758d5c22..8dea23f82 100644 --- a/src/hooks/usePCEditorBlockMouseEvents.ts +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -4,12 +4,9 @@ import _ from 'lodash'; import {getCursorPositionOverElement} from '../utils/editor'; -import {usePCEditorStore} from './usePCEditorStore'; -import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI'; +import {sendEventPostMessage} from './usePostMessageAPI'; const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => { - const {selectedBlock} = usePCEditorStore(); - const onMouseUp = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation(); @@ -57,41 +54,6 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement [arrayIndex, element], ); - const onResize = React.useCallback(() => { - if (element && _.isEqual(selectedBlock, arrayIndex)) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_RESIZE_BLOCK', {rect}); - } - } - }, [arrayIndex, element, selectedBlock]); - - React.useEffect(() => { - window.addEventListener('resize', onResize); - - return () => { - window.removeEventListener('resize', onResize); - }; - }, [element, onResize]); - - // Listen for the UPDATE_SELECTED_BLOCK action and handle it directly - const handleUpdateSelectedBlock = React.useCallback( - (data: {path?: number[]}) => { - if (data && data.path && Array.isArray(data.path) && _.isEqual(data.path, arrayIndex)) { - // Only proceed if the path matches arrayIndex - if (element) { - const rect = element.getClientRects().item(0); - if (rect) { - sendEventPostMessage('ON_UPDATE_SELECTED_BLOCK', {rect, path: arrayIndex}); - } - } - } - }, - [arrayIndex, element], - ); - - useInternalPostMessageAPIListener('UPDATE_SELECTED_BLOCK', handleUpdateSelectedBlock); - return { onClick, onMouseMove, diff --git a/src/hooks/usePCEditorBlockSelection.ts b/src/hooks/usePCEditorBlockSelection.ts new file mode 100644 index 000000000..41bb4f8b5 --- /dev/null +++ b/src/hooks/usePCEditorBlockSelection.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; +import _ from 'lodash'; + +import {usePCEditorStore} from './usePCEditorStore'; +import {sendEventPostMessage} from './usePostMessageAPI'; + +const usePCEditorBlockSelection = (arrayIndex: number[], element?: HTMLElement) => { + const {selectedBlock} = usePCEditorStore(); + + const onResize = React.useCallback(() => { + if (element && _.isEqual(selectedBlock, arrayIndex)) { + const rect = element.getClientRects().item(0); + if (rect) { + sendEventPostMessage('ON_UPDATE_BLOCK_SELECTION', {rect}); + } + } + }, [JSON.stringify(arrayIndex), element, JSON.stringify(selectedBlock)]); + + React.useEffect(() => { + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }, [element, onResize]); + + React.useEffect(() => { + onResize(); + }, [onResize]); +}; + +export default usePCEditorBlockSelection; diff --git a/src/hooks/usePCEditorItemWrap.ts b/src/hooks/usePCEditorItemWrap.ts index 6ab498a31..eb7eb55a8 100644 --- a/src/hooks/usePCEditorItemWrap.ts +++ b/src/hooks/usePCEditorItemWrap.ts @@ -3,6 +3,7 @@ import * as React from 'react'; import {BlockIdContext} from '../context/blockIdContext'; import usePCEditorBlockMouseEvents from './usePCEditorBlockMouseEvents'; +import usePCEditorBlockSelection from './usePCEditorBlockSelection'; export function usePCEditorItemWrap(index = 0) { const [element, setElement] = React.useState<HTMLElement | undefined>(); @@ -15,6 +16,7 @@ export function usePCEditorItemWrap(index = 0) { const parentBlockId = React.useContext(BlockIdContext); const adminBlockMouseEvents = usePCEditorBlockMouseEvents([...parentBlockId, index], element); + usePCEditorBlockSelection([...parentBlockId, index], element); return {adminBlockMouseEvents, blockRef}; } From c6d5eb0a7119b461d2c0c4968b17cdd95b9c94fc Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 3 Apr 2025 19:32:58 +0300 Subject: [PATCH 38/84] feat: unselect block on deletion --- common/store.ts | 4 ++-- editor-v2/store.ts | 11 ++++------- src/hooks/usePCEditorBlockSelection.ts | 3 ++- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/common/store.ts b/common/store.ts index 796c844f4..668684374 100644 --- a/common/store.ts +++ b/common/store.ts @@ -11,7 +11,7 @@ export interface EditorState { zoom: number; manipulateOverlayMode: 'insert' | 'reorder' | false; - selectedBlock?: number[]; + selectedBlock: number[] | null; initialized: boolean; content: PageContentWithNavigation; @@ -28,7 +28,7 @@ export const initialStore: EditorState = { deviceWidth: '100%', zoom: 100, manipulateOverlayMode: false, - selectedBlock: undefined, + selectedBlock: null, initialized: false, content: {blocks: []}, blocks: [], diff --git a/editor-v2/store.ts b/editor-v2/store.ts index 9fefcf93e..17c50cb65 100644 --- a/editor-v2/store.ts +++ b/editor-v2/store.ts @@ -19,7 +19,7 @@ import { export interface EditorMethods { initialize(): void; - setSelectedBlock(path?: number[]): void; + setSelectedBlock(path: number[] | null): void; setHeight(height: number): void; setDeviceWidth(deviceWidth: string): void; setZoom(zoom: number): void; @@ -108,10 +108,8 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( set((state) => ({ ...state, content: {...state.content, blocks: newBlocksConfig}, + selectedBlock: arrayPath, })); - - // Set the inserted block as selected - get().setSelectedBlock(arrayPath); }, enableInsertMode(blockType: string) { set((state) => ({ @@ -166,10 +164,10 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( const blocksConfig = get().content.blocks; const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray); - set((state) => ({ ...state, content: {...state.content, blocks: newBlocksConfig}, + selectedBlock: null, })); }, duplicateBlock: (arrayPath) => { @@ -234,9 +232,8 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( set((state) => ({ ...state, content: {...state.content, blocks: newBlocksConfig}, + selectedBlock: finalDestinationPath, })); - - get().setSelectedBlock(finalDestinationPath); }, resetInitialize: () => { set((state) => ({ diff --git a/src/hooks/usePCEditorBlockSelection.ts b/src/hooks/usePCEditorBlockSelection.ts index 41bb4f8b5..eb934b92c 100644 --- a/src/hooks/usePCEditorBlockSelection.ts +++ b/src/hooks/usePCEditorBlockSelection.ts @@ -14,7 +14,7 @@ const usePCEditorBlockSelection = (arrayIndex: number[], element?: HTMLElement) sendEventPostMessage('ON_UPDATE_BLOCK_SELECTION', {rect}); } } - }, [JSON.stringify(arrayIndex), element, JSON.stringify(selectedBlock)]); + }, [arrayIndex, element, selectedBlock]); React.useEffect(() => { window.addEventListener('resize', onResize); @@ -24,6 +24,7 @@ const usePCEditorBlockSelection = (arrayIndex: number[], element?: HTMLElement) }; }, [element, onResize]); + // Update blockBorders when selectedBlock changes React.useEffect(() => { onResize(); }, [onResize]); From 5caed6bbfed529571446dd0e3615d45c2ca31378 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Fri, 4 Apr 2025 12:42:24 +0300 Subject: [PATCH 39/84] fix: eslint --- editor-v2/containers/Tree/TreeContent.tsx | 6 ++-- editor-v2/containers/Tree/TreeItem.tsx | 30 +++++++++++-------- .../containers/ViewSwitches/ViewSwitches.tsx | 7 ----- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/editor-v2/containers/Tree/TreeContent.tsx b/editor-v2/containers/Tree/TreeContent.tsx index bc2e25234..afa07066f 100644 --- a/editor-v2/containers/Tree/TreeContent.tsx +++ b/editor-v2/containers/Tree/TreeContent.tsx @@ -39,7 +39,7 @@ export const TreeContent = ({ const {draggedItem, setDraggedItem, hidePreview, showPreview} = React.useContext(DragContext); const handleFirstPositionDrop = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); @@ -56,6 +56,7 @@ export const TreeContent = ({ hidePreview(); } } catch (error) { + // eslint-disable-next-line no-console console.error('Error parsing drag data:', error); } }, @@ -63,9 +64,10 @@ export const TreeContent = ({ ); const handleDragOver = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign e.dataTransfer.dropEffect = 'move'; // Show preview element at this position diff --git a/editor-v2/containers/Tree/TreeItem.tsx b/editor-v2/containers/Tree/TreeItem.tsx index dc80e83a2..80da0af57 100644 --- a/editor-v2/containers/Tree/TreeItem.tsx +++ b/editor-v2/containers/Tree/TreeItem.tsx @@ -60,12 +60,12 @@ export const Item = ({ onDelete(path); }, [onDelete, path]); - const handleMouseDown = React.useCallback((e: any) => { + const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => { setMouseDownPos({x: e.clientX, y: e.clientY}); }, []); const handleMouseUp = React.useCallback( - (e: any) => { + (e: React.MouseEvent<HTMLDivElement>) => { if (mouseDownPos) { // Check if the mouse has moved significantly (dragging) or just a click const dx = Math.abs(e.clientX - mouseDownPos.x); @@ -83,7 +83,7 @@ export const Item = ({ ); const handleDragStart = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.stopPropagation(); const dragData: DragItem = { path, @@ -91,6 +91,7 @@ export const Item = ({ treeTitle, }; e.dataTransfer.setData('application/json', JSON.stringify(dragData)); + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign e.dataTransfer.effectAllowed = 'move'; setIsDragging(true); @@ -109,13 +110,13 @@ export const Item = ({ }, [setDraggedItem, hidePreview, path]); const handleDragOver = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign e.dataTransfer.dropEffect = 'move'; setIsDragOver(true); - // Show preview element at this position const rect = e.currentTarget.getBoundingClientRect(); if (draggedItem) { showPreview(rect, draggedItem); @@ -130,7 +131,7 @@ export const Item = ({ }, [hidePreview]); const handleDrop = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); @@ -148,6 +149,7 @@ export const Item = ({ } } } catch (error) { + // eslint-disable-next-line no-console console.error('Error parsing drag data:', error); } }, @@ -155,7 +157,7 @@ export const Item = ({ ); const handleChildrenFirstPositionDrop = React.useCallback( - (e: any) => { + (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); @@ -173,15 +175,17 @@ export const Item = ({ hidePreview(); } } catch (error) { + // eslint-disable-next-line no-console console.error('Error parsing drag data:', error); } }, [onReorder, path, setDraggedItem, hidePreview], ); - const handleDropZoneDragOver = (e: any) => { + const handleDropZoneDragOver = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); e.stopPropagation(); + // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign e.dataTransfer.dropEffect = 'move'; // Show preview element at this position @@ -199,14 +203,14 @@ export const Item = ({ dragging: isDragging, 'drag-over': isDragOver, })} - onMouseDown={handleMouseDown} - onMouseUp={handleMouseUp} + onMouseDown={handleMouseDown as unknown as React.MouseEventHandler<'div'>} + onMouseUp={handleMouseUp as unknown as React.MouseEventHandler<'div'>} draggable - onDragStart={handleDragStart} + onDragStart={handleDragStart as unknown as React.DragEventHandler<'div'>} onDragEnd={handleDragEnd} - onDragOver={handleDragOver} + onDragOver={handleDragOver as unknown as React.DragEventHandler<'div'>} onDragLeave={handleDragLeave} - onDrop={handleDrop} + onDrop={handleDrop as unknown as React.DragEventHandler<'div'>} > <div className={b('main')}> <div className={b('text')}> diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index fbebdcfb8..78139a36a 100644 --- a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -46,13 +46,6 @@ const DEVICE_OPTIONS: DeviceOption[] = [ }, ]; -/** - * ViewSwitches component - * - * Provides UI controls for: - * 1. Switching between device viewport widths (desktop, tablet, mobile) - * 2. Adjusting zoom level with increment/decrement buttons and dropdown - */ const ViewSwitches: React.FC = () => { const {zoom, setZoom, decreaseZoom, increaseZoom, deviceWidth, setDeviceWidth} = useMainEditorStore(); From 6ee58c74fdb9085f9734046ea419001fb9361b4f Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:44:56 +0000 Subject: [PATCH 40/84] 6.3.2-alpha.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 010389716..ea7e325e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.8.1", + "version": "6.3.2-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.8.1", + "version": "6.3.2-alpha.0", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 7794af67a..da320ada5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.8.1", + "version": "6.3.2-alpha.0", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From d13fcc131519506f77ad73b56e50b1ed3d68ab49 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 12:04:38 +0300 Subject: [PATCH 41/84] fix: return editor-v2 back to src --- editor-v2/utils/store.ts | 0 playground/src/app/page.tsx | 6 +++--- .../editor-v2}/components/DynamicForm/DynamicForm.scss | 0 .../editor-v2}/components/DynamicForm/DynamicForm.tsx | 2 +- .../components/DynamicForm/FieldBase/FieldBase.scss | 0 .../components/DynamicForm/FieldBase/FieldBase.tsx | 0 .../components/DynamicForm/Fields/Array/Array.scss | 0 .../components/DynamicForm/Fields/Array/Array.tsx | 2 +- .../DynamicForm/Fields/Array/ItemButton/ItemButton.tsx | 0 .../components/DynamicForm/Fields/Boolean/Boolean.scss | 0 .../components/DynamicForm/Fields/Boolean/Boolean.tsx | 0 .../components/DynamicForm/Fields/Number/Number.tsx | 0 .../components/DynamicForm/Fields/Object/Object.tsx | 2 +- .../components/DynamicForm/Fields/OneOf/OneOf.scss | 0 .../components/DynamicForm/Fields/OneOf/OneOf.tsx | 2 +- .../components/DynamicForm/Fields/Select/Select.scss | 0 .../components/DynamicForm/Fields/Select/Select.tsx | 2 +- .../components/DynamicForm/Fields/Text/Text.tsx | 0 .../components/DynamicForm/Fields/TextArea/TextArea.tsx | 0 .../editor-v2}/components/DynamicForm/utils.ts | 2 +- .../editor-v2}/components/Panels/Panels.scss | 0 .../editor-v2}/components/Panels/Panels.tsx | 0 .../editor-v2}/components/Sidebar/Sidebar.scss | 0 .../editor-v2}/components/Sidebar/Sidebar.tsx | 0 .../editor-v2}/components/StoreViewer/StoreViewer.scss | 0 .../editor-v2}/components/StoreViewer/StoreViewer.tsx | 2 +- {editor-v2 => src/editor-v2}/components/Tabs/Tabs.scss | 0 {editor-v2 => src/editor-v2}/components/Tabs/Tabs.tsx | 0 {editor-v2 => src/editor-v2}/constants.ts | 0 .../editor-v2}/containers/BigOverlay/BigOverlay.scss | 2 -- .../editor-v2}/containers/BigOverlay/BigOverlay.tsx | 2 +- .../editor-v2}/containers/BlockConfig/BlockConfig.scss | 0 .../editor-v2}/containers/BlockConfig/BlockConfig.tsx | 2 +- .../editor-v2}/containers/BlocksList/BlocksList.scss | 0 .../editor-v2}/containers/BlocksList/BlocksList.tsx | 2 +- .../editor-v2}/containers/Editor/Editor.scss | 0 .../editor-v2}/containers/Editor/Editor.tsx | 2 +- .../editor-v2}/containers/GlobalConfig/GlobalConfig.scss | 0 .../editor-v2}/containers/GlobalConfig/GlobalConfig.tsx | 4 ++-- .../editor-v2}/containers/MiddleScreen/MiddleScreen.scss | 0 .../editor-v2}/containers/MiddleScreen/MiddleScreen.tsx | 2 +- .../editor-v2}/containers/Overlay/Overlay.scss | 0 .../editor-v2}/containers/Overlay/Overlay.tsx | 2 +- .../editor-v2}/containers/Source/Source.scss | 0 .../editor-v2}/containers/Source/Source.tsx | 0 .../editor-v2}/containers/SourceCode/SourceCode.scss | 0 .../editor-v2}/containers/SourceCode/SourceCode.tsx | 2 +- .../containers/SourceCode/UpdateModal/UpdateModal.scss | 0 .../containers/SourceCode/UpdateModal/UpdateModal.tsx | 9 +++++++-- .../editor-v2}/containers/Tree/DragContext.scss | 0 .../editor-v2}/containers/Tree/DragContext.tsx | 2 ++ {editor-v2 => src/editor-v2}/containers/Tree/Tree.scss | 0 {editor-v2 => src/editor-v2}/containers/Tree/Tree.tsx | 0 .../editor-v2}/containers/Tree/TreeContent.scss | 0 .../editor-v2}/containers/Tree/TreeContent.tsx | 0 .../editor-v2}/containers/Tree/TreeItem.scss | 0 .../editor-v2}/containers/Tree/TreeItem.tsx | 0 {editor-v2 => src/editor-v2}/containers/Tree/index.ts | 0 .../editor-v2}/containers/ViewSwitches/ViewSwitches.scss | 0 .../editor-v2}/containers/ViewSwitches/ViewSwitches.tsx | 0 .../editor-v2}/containers/__stories__/Editor.stories.tsx | 0 .../editor-v2}/containers/__stories__/utils.ts | 0 .../context/editorStore/MainEditorStoreContext.tsx | 0 .../context/editorStore/MainEditorStoreProvider.tsx | 6 +++--- .../editor-v2}/context/editorStore/index.ts | 0 .../editor-v2}/context/iframeContext/IframeContext.tsx | 0 .../editor-v2}/context/iframeContext/IframeProvider.tsx | 0 .../editor-v2}/context/iframeContext/index.ts | 0 {editor-v2 => src/editor-v2}/hooks/useEditorTabs.tsx | 0 .../editor-v2}/hooks/useMainEditorInitialize.ts | 2 +- {editor-v2 => src/editor-v2}/hooks/useMainEditorStore.ts | 0 .../editor-v2}/hooks/usePostMessageEvents.ts | 4 ++-- {editor-v2 => src/editor-v2}/index.ts | 2 +- {editor-v2 => src/editor-v2}/store.ts | 8 ++++---- {editor-v2 => src/editor-v2}/styles/mixins.scss | 0 {editor-v2 => src/editor-v2}/styles/root.scss | 0 {editor-v2 => src/editor-v2}/styles/variables.scss | 0 {editor-v2 => src/editor-v2}/utils/cn.ts | 0 {editor-v2 => src/editor-v2}/utils/code.ts | 2 +- {editor-v2 => src/editor-v2}/utils/index.ts | 2 +- 80 files changed, 41 insertions(+), 36 deletions(-) delete mode 100644 editor-v2/utils/store.ts rename {editor-v2 => src/editor-v2}/components/DynamicForm/DynamicForm.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/DynamicForm.tsx (99%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/FieldBase/FieldBase.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/FieldBase/FieldBase.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Array/Array.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Array/Array.tsx (99%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Boolean/Boolean.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Boolean/Boolean.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Number/Number.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Object/Object.tsx (92%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/OneOf/OneOf.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/OneOf/OneOf.tsx (97%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Select/Select.scss (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Select/Select.tsx (98%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/Text/Text.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/Fields/TextArea/TextArea.tsx (100%) rename {editor-v2 => src/editor-v2}/components/DynamicForm/utils.ts (87%) rename {editor-v2 => src/editor-v2}/components/Panels/Panels.scss (100%) rename {editor-v2 => src/editor-v2}/components/Panels/Panels.tsx (100%) rename {editor-v2 => src/editor-v2}/components/Sidebar/Sidebar.scss (100%) rename {editor-v2 => src/editor-v2}/components/Sidebar/Sidebar.tsx (100%) rename {editor-v2 => src/editor-v2}/components/StoreViewer/StoreViewer.scss (100%) rename {editor-v2 => src/editor-v2}/components/StoreViewer/StoreViewer.tsx (95%) rename {editor-v2 => src/editor-v2}/components/Tabs/Tabs.scss (100%) rename {editor-v2 => src/editor-v2}/components/Tabs/Tabs.tsx (100%) rename {editor-v2 => src/editor-v2}/constants.ts (100%) rename {editor-v2 => src/editor-v2}/containers/BigOverlay/BigOverlay.scss (90%) rename {editor-v2 => src/editor-v2}/containers/BigOverlay/BigOverlay.tsx (97%) rename {editor-v2 => src/editor-v2}/containers/BlockConfig/BlockConfig.scss (100%) rename {editor-v2 => src/editor-v2}/containers/BlockConfig/BlockConfig.tsx (96%) rename {editor-v2 => src/editor-v2}/containers/BlocksList/BlocksList.scss (100%) rename {editor-v2 => src/editor-v2}/containers/BlocksList/BlocksList.tsx (98%) rename {editor-v2 => src/editor-v2}/containers/Editor/Editor.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Editor/Editor.tsx (97%) rename {editor-v2 => src/editor-v2}/containers/GlobalConfig/GlobalConfig.scss (100%) rename {editor-v2 => src/editor-v2}/containers/GlobalConfig/GlobalConfig.tsx (89%) rename {editor-v2 => src/editor-v2}/containers/MiddleScreen/MiddleScreen.scss (100%) rename {editor-v2 => src/editor-v2}/containers/MiddleScreen/MiddleScreen.tsx (97%) rename {editor-v2 => src/editor-v2}/containers/Overlay/Overlay.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Overlay/Overlay.tsx (98%) rename {editor-v2 => src/editor-v2}/containers/Source/Source.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Source/Source.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/SourceCode/SourceCode.scss (100%) rename {editor-v2 => src/editor-v2}/containers/SourceCode/SourceCode.tsx (97%) rename {editor-v2 => src/editor-v2}/containers/SourceCode/UpdateModal/UpdateModal.scss (100%) rename {editor-v2 => src/editor-v2}/containers/SourceCode/UpdateModal/UpdateModal.tsx (86%) rename {editor-v2 => src/editor-v2}/containers/Tree/DragContext.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/DragContext.tsx (98%) rename {editor-v2 => src/editor-v2}/containers/Tree/Tree.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/Tree.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/TreeContent.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/TreeContent.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/TreeItem.scss (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/TreeItem.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/Tree/index.ts (100%) rename {editor-v2 => src/editor-v2}/containers/ViewSwitches/ViewSwitches.scss (100%) rename {editor-v2 => src/editor-v2}/containers/ViewSwitches/ViewSwitches.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/__stories__/Editor.stories.tsx (100%) rename {editor-v2 => src/editor-v2}/containers/__stories__/utils.ts (100%) rename {editor-v2 => src/editor-v2}/context/editorStore/MainEditorStoreContext.tsx (100%) rename {editor-v2 => src/editor-v2}/context/editorStore/MainEditorStoreProvider.tsx (89%) rename {editor-v2 => src/editor-v2}/context/editorStore/index.ts (100%) rename {editor-v2 => src/editor-v2}/context/iframeContext/IframeContext.tsx (100%) rename {editor-v2 => src/editor-v2}/context/iframeContext/IframeProvider.tsx (100%) rename {editor-v2 => src/editor-v2}/context/iframeContext/index.ts (100%) rename {editor-v2 => src/editor-v2}/hooks/useEditorTabs.tsx (100%) rename {editor-v2 => src/editor-v2}/hooks/useMainEditorInitialize.ts (96%) rename {editor-v2 => src/editor-v2}/hooks/useMainEditorStore.ts (100%) rename {editor-v2 => src/editor-v2}/hooks/usePostMessageEvents.ts (84%) rename {editor-v2 => src/editor-v2}/index.ts (58%) rename {editor-v2 => src/editor-v2}/store.ts (97%) rename {editor-v2 => src/editor-v2}/styles/mixins.scss (100%) rename {editor-v2 => src/editor-v2}/styles/root.scss (100%) rename {editor-v2 => src/editor-v2}/styles/variables.scss (100%) rename {editor-v2 => src/editor-v2}/utils/cn.ts (100%) rename {editor-v2 => src/editor-v2}/utils/code.ts (83%) rename {editor-v2 => src/editor-v2}/utils/index.ts (99%) diff --git a/editor-v2/utils/store.ts b/editor-v2/utils/store.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index ddae138bf..205147409 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -3,9 +3,9 @@ import {ThemeProvider} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import * as React from 'react'; -import {Editor} from '../../../editor-v2'; -import Source from '../../../editor-v2/containers/Source/Source'; -import ViewSwitches from '../../../editor-v2/containers/ViewSwitches/ViewSwitches'; +import {Editor} from '../../../src/editor-v2'; +import Source from '../../../src/editor-v2/containers/Source/Source'; +import ViewSwitches from '../../../src/editor-v2/containers/ViewSwitches/ViewSwitches'; import C9RComponent from './components/C9RComponent'; import './page.scss'; diff --git a/editor-v2/components/DynamicForm/DynamicForm.scss b/src/editor-v2/components/DynamicForm/DynamicForm.scss similarity index 100% rename from editor-v2/components/DynamicForm/DynamicForm.scss rename to src/editor-v2/components/DynamicForm/DynamicForm.scss diff --git a/editor-v2/components/DynamicForm/DynamicForm.tsx b/src/editor-v2/components/DynamicForm/DynamicForm.tsx similarity index 99% rename from editor-v2/components/DynamicForm/DynamicForm.tsx rename to src/editor-v2/components/DynamicForm/DynamicForm.tsx index 9531c2181..74a80edc1 100644 --- a/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/src/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -1,7 +1,7 @@ import _ from 'lodash'; import * as React from 'react'; -import {ConfigInput, DynamicFormValue} from '../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../../common/types'; import {editorCn} from '../../utils/cn'; import './DynamicForm.scss'; diff --git a/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss similarity index 100% rename from editor-v2/components/DynamicForm/FieldBase/FieldBase.scss rename to src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss diff --git a/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx similarity index 100% rename from editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx rename to src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx diff --git a/editor-v2/components/DynamicForm/Fields/Array/Array.scss b/src/editor-v2/components/DynamicForm/Fields/Array/Array.scss similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Array/Array.scss rename to src/editor-v2/components/DynamicForm/Fields/Array/Array.scss diff --git a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx similarity index 99% rename from editor-v2/components/DynamicForm/Fields/Array/Array.tsx rename to src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 295225c73..31381279f 100644 --- a/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -2,7 +2,7 @@ import {Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; -import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; +import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../../common/types'; import {removeFromArray, swapArrayItems} from '../../../../utils'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; diff --git a/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx rename to src/editor-v2/components/DynamicForm/Fields/Array/ItemButton/ItemButton.tsx diff --git a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss b/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss rename to src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.scss diff --git a/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx b/src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx rename to src/editor-v2/components/DynamicForm/Fields/Boolean/Boolean.tsx diff --git a/editor-v2/components/DynamicForm/Fields/Number/Number.tsx b/src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Number/Number.tsx rename to src/editor-v2/components/DynamicForm/Fields/Number/Number.tsx diff --git a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx similarity index 92% rename from editor-v2/components/DynamicForm/Fields/Object/Object.tsx rename to src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index 3711df5be..e6839cba8 100644 --- a/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -1,6 +1,6 @@ import {Card} from '@gravity-ui/uikit'; -import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../../../../common/types'; import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss similarity index 100% rename from editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss rename to src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss diff --git a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx similarity index 97% rename from editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx rename to src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 31ed85ff3..77559d1e7 100644 --- a/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -1,7 +1,7 @@ import {Card, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import * as React from 'react'; -import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; +import {DynamicFormValue, OneOfInput} from '../../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/Select/Select.scss b/src/editor-v2/components/DynamicForm/Fields/Select/Select.scss similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Select/Select.scss rename to src/editor-v2/components/DynamicForm/Fields/Select/Select.scss diff --git a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx similarity index 98% rename from editor-v2/components/DynamicForm/Fields/Select/Select.tsx rename to src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx index 13bcbe0dc..6d3eaabfe 100644 --- a/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -1,6 +1,6 @@ import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; -import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/types'; +import {SelectMultipleInput, SelectSingleInput} from '../../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/editor-v2/components/DynamicForm/Fields/Text/Text.tsx b/src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx similarity index 100% rename from editor-v2/components/DynamicForm/Fields/Text/Text.tsx rename to src/editor-v2/components/DynamicForm/Fields/Text/Text.tsx diff --git a/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx b/src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx similarity index 100% rename from editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx rename to src/editor-v2/components/DynamicForm/Fields/TextArea/TextArea.tsx diff --git a/editor-v2/components/DynamicForm/utils.ts b/src/editor-v2/components/DynamicForm/utils.ts similarity index 87% rename from editor-v2/components/DynamicForm/utils.ts rename to src/editor-v2/components/DynamicForm/utils.ts index 5ad3f80b7..2449db532 100644 --- a/editor-v2/components/DynamicForm/utils.ts +++ b/src/editor-v2/components/DynamicForm/utils.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {DynamicFormValue} from '../../../common/types'; +import {DynamicFormValue} from '../../../../common/types'; export const getFullPath = (path: string, name: string) => { if (!path && !name) { diff --git a/editor-v2/components/Panels/Panels.scss b/src/editor-v2/components/Panels/Panels.scss similarity index 100% rename from editor-v2/components/Panels/Panels.scss rename to src/editor-v2/components/Panels/Panels.scss diff --git a/editor-v2/components/Panels/Panels.tsx b/src/editor-v2/components/Panels/Panels.tsx similarity index 100% rename from editor-v2/components/Panels/Panels.tsx rename to src/editor-v2/components/Panels/Panels.tsx diff --git a/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss similarity index 100% rename from editor-v2/components/Sidebar/Sidebar.scss rename to src/editor-v2/components/Sidebar/Sidebar.scss diff --git a/editor-v2/components/Sidebar/Sidebar.tsx b/src/editor-v2/components/Sidebar/Sidebar.tsx similarity index 100% rename from editor-v2/components/Sidebar/Sidebar.tsx rename to src/editor-v2/components/Sidebar/Sidebar.tsx diff --git a/editor-v2/components/StoreViewer/StoreViewer.scss b/src/editor-v2/components/StoreViewer/StoreViewer.scss similarity index 100% rename from editor-v2/components/StoreViewer/StoreViewer.scss rename to src/editor-v2/components/StoreViewer/StoreViewer.scss diff --git a/editor-v2/components/StoreViewer/StoreViewer.tsx b/src/editor-v2/components/StoreViewer/StoreViewer.tsx similarity index 95% rename from editor-v2/components/StoreViewer/StoreViewer.tsx rename to src/editor-v2/components/StoreViewer/StoreViewer.tsx index 753842df2..b931bd084 100644 --- a/editor-v2/components/StoreViewer/StoreViewer.tsx +++ b/src/editor-v2/components/StoreViewer/StoreViewer.tsx @@ -3,7 +3,7 @@ import {Button, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; import ReactJson from 'react-json-view'; -import {removeFn} from '../../../common/utils'; +import {removeFn} from '../../../../common/utils'; import {editorCn} from '../../utils/cn'; import './StoreViewer.scss'; diff --git a/editor-v2/components/Tabs/Tabs.scss b/src/editor-v2/components/Tabs/Tabs.scss similarity index 100% rename from editor-v2/components/Tabs/Tabs.scss rename to src/editor-v2/components/Tabs/Tabs.scss diff --git a/editor-v2/components/Tabs/Tabs.tsx b/src/editor-v2/components/Tabs/Tabs.tsx similarity index 100% rename from editor-v2/components/Tabs/Tabs.tsx rename to src/editor-v2/components/Tabs/Tabs.tsx diff --git a/editor-v2/constants.ts b/src/editor-v2/constants.ts similarity index 100% rename from editor-v2/constants.ts rename to src/editor-v2/constants.ts diff --git a/editor-v2/containers/BigOverlay/BigOverlay.scss b/src/editor-v2/containers/BigOverlay/BigOverlay.scss similarity index 90% rename from editor-v2/containers/BigOverlay/BigOverlay.scss rename to src/editor-v2/containers/BigOverlay/BigOverlay.scss index 1ae968b87..3d79aed84 100644 --- a/editor-v2/containers/BigOverlay/BigOverlay.scss +++ b/src/editor-v2/containers/BigOverlay/BigOverlay.scss @@ -1,5 +1,3 @@ -@import '../../../styles/variables.scss'; -@import '../../../styles/mixins.scss'; @import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/editor-v2/containers/BigOverlay/BigOverlay.tsx b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx similarity index 97% rename from editor-v2/containers/BigOverlay/BigOverlay.tsx rename to src/editor-v2/containers/BigOverlay/BigOverlay.tsx index 34d50fefc..eb21962bd 100644 --- a/editor-v2/containers/BigOverlay/BigOverlay.tsx +++ b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx @@ -1,7 +1,7 @@ import {Stop} from '@gravity-ui/icons'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/editor-v2/containers/BlockConfig/BlockConfig.scss b/src/editor-v2/containers/BlockConfig/BlockConfig.scss similarity index 100% rename from editor-v2/containers/BlockConfig/BlockConfig.scss rename to src/editor-v2/containers/BlockConfig/BlockConfig.scss diff --git a/editor-v2/containers/BlockConfig/BlockConfig.tsx b/src/editor-v2/containers/BlockConfig/BlockConfig.tsx similarity index 96% rename from editor-v2/containers/BlockConfig/BlockConfig.tsx rename to src/editor-v2/containers/BlockConfig/BlockConfig.tsx index c0b753d07..39a67238d 100644 --- a/editor-v2/containers/BlockConfig/BlockConfig.tsx +++ b/src/editor-v2/containers/BlockConfig/BlockConfig.tsx @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {DynamicFormValue} from '../../../common/types'; +import {DynamicFormValue} from '../../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {generateChildrenPathFromArray} from '../../utils'; diff --git a/editor-v2/containers/BlocksList/BlocksList.scss b/src/editor-v2/containers/BlocksList/BlocksList.scss similarity index 100% rename from editor-v2/containers/BlocksList/BlocksList.scss rename to src/editor-v2/containers/BlocksList/BlocksList.scss diff --git a/editor-v2/containers/BlocksList/BlocksList.tsx b/src/editor-v2/containers/BlocksList/BlocksList.tsx similarity index 98% rename from editor-v2/containers/BlocksList/BlocksList.tsx rename to src/editor-v2/containers/BlocksList/BlocksList.tsx index b8652f238..2f7dd25e3 100644 --- a/editor-v2/containers/BlocksList/BlocksList.tsx +++ b/src/editor-v2/containers/BlocksList/BlocksList.tsx @@ -2,7 +2,7 @@ import {Magnifier, SquareBars} from '@gravity-ui/icons'; import {Card, Icon, TextInput} from '@gravity-ui/uikit'; import * as React from 'react'; -import {ItemConfig} from '../../../common/types'; +import {ItemConfig} from '../../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss similarity index 100% rename from editor-v2/containers/Editor/Editor.scss rename to src/editor-v2/containers/Editor/Editor.scss diff --git a/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx similarity index 97% rename from editor-v2/containers/Editor/Editor.tsx rename to src/editor-v2/containers/Editor/Editor.tsx index 8b98d5d50..fd4dbe397 100644 --- a/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import {PageContentWithNavigation} from '../../../src/models'; +import {PageContentWithNavigation} from '../../../models'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; import StoreViewer from '../../components/StoreViewer/StoreViewer'; diff --git a/editor-v2/containers/GlobalConfig/GlobalConfig.scss b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss similarity index 100% rename from editor-v2/containers/GlobalConfig/GlobalConfig.scss rename to src/editor-v2/containers/GlobalConfig/GlobalConfig.scss diff --git a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx similarity index 89% rename from editor-v2/containers/GlobalConfig/GlobalConfig.tsx rename to src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx index f36055530..9299a3150 100644 --- a/editor-v2/containers/GlobalConfig/GlobalConfig.tsx +++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx @@ -1,4 +1,4 @@ -import {DynamicFormValue} from '../../../common/types'; +import {DynamicFormValue} from '../../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; @@ -7,7 +7,7 @@ import './GlobalConfig.scss'; const b = editorCn('global-config'); -interface GlobalConfigProps { +export interface GlobalConfigProps { className?: string; } diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss similarity index 100% rename from editor-v2/containers/MiddleScreen/MiddleScreen.scss rename to src/editor-v2/containers/MiddleScreen/MiddleScreen.scss diff --git a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx similarity index 97% rename from editor-v2/containers/MiddleScreen/MiddleScreen.tsx rename to src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 198c759ef..9ab96a5da 100644 --- a/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -1,7 +1,7 @@ import {Loader} from '@gravity-ui/uikit'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/editor-v2/containers/Overlay/Overlay.scss b/src/editor-v2/containers/Overlay/Overlay.scss similarity index 100% rename from editor-v2/containers/Overlay/Overlay.scss rename to src/editor-v2/containers/Overlay/Overlay.scss diff --git a/editor-v2/containers/Overlay/Overlay.tsx b/src/editor-v2/containers/Overlay/Overlay.tsx similarity index 98% rename from editor-v2/containers/Overlay/Overlay.tsx rename to src/editor-v2/containers/Overlay/Overlay.tsx index 6d8933909..ac7759881 100644 --- a/editor-v2/containers/Overlay/Overlay.tsx +++ b/src/editor-v2/containers/Overlay/Overlay.tsx @@ -2,7 +2,7 @@ import {ChevronDown, ChevronUp, Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/editor-v2/containers/Source/Source.scss b/src/editor-v2/containers/Source/Source.scss similarity index 100% rename from editor-v2/containers/Source/Source.scss rename to src/editor-v2/containers/Source/Source.scss diff --git a/editor-v2/containers/Source/Source.tsx b/src/editor-v2/containers/Source/Source.tsx similarity index 100% rename from editor-v2/containers/Source/Source.tsx rename to src/editor-v2/containers/Source/Source.tsx diff --git a/editor-v2/containers/SourceCode/SourceCode.scss b/src/editor-v2/containers/SourceCode/SourceCode.scss similarity index 100% rename from editor-v2/containers/SourceCode/SourceCode.scss rename to src/editor-v2/containers/SourceCode/SourceCode.scss diff --git a/editor-v2/containers/SourceCode/SourceCode.tsx b/src/editor-v2/containers/SourceCode/SourceCode.tsx similarity index 97% rename from editor-v2/containers/SourceCode/SourceCode.tsx rename to src/editor-v2/containers/SourceCode/SourceCode.tsx index bc6485135..538c2ba2e 100644 --- a/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/src/editor-v2/containers/SourceCode/SourceCode.tsx @@ -3,7 +3,7 @@ import {Button, ClipboardButton, Icon} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; import * as React from 'react'; -import {PageContentWithNavigation} from '../../../src/models'; +import {PageContentWithNavigation} from '../../../models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss similarity index 100% rename from editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss rename to src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss diff --git a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx similarity index 86% rename from editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx rename to src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx index 4a3224adb..75a007e96 100644 --- a/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx +++ b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx @@ -7,7 +7,13 @@ import './UpdateModal.scss'; const b = editorCn('source-code-update-modal'); -export const UpdateModal = ({onClose, onApply, isOpen}) => { +interface UpdateModalProps { + onClose(): void; + onApply(tempConfig?: string): void; + isOpen: boolean; +} + +export const UpdateModal = ({onClose, onApply, isOpen}: UpdateModalProps) => { const [tempConfig, setTempConfig] = React.useState(''); const handleApply = () => { onApply(tempConfig); @@ -26,7 +32,6 @@ export const UpdateModal = ({onClose, onApply, isOpen}) => { </Dialog.Body> <Dialog.Footer showError={false} - listenKeyEnter={true} preset={'default'} textButtonApply={'Apply'} textButtonCancel={'Cancel'} diff --git a/editor-v2/containers/Tree/DragContext.scss b/src/editor-v2/containers/Tree/DragContext.scss similarity index 100% rename from editor-v2/containers/Tree/DragContext.scss rename to src/editor-v2/containers/Tree/DragContext.scss diff --git a/editor-v2/containers/Tree/DragContext.tsx b/src/editor-v2/containers/Tree/DragContext.tsx similarity index 98% rename from editor-v2/containers/Tree/DragContext.tsx rename to src/editor-v2/containers/Tree/DragContext.tsx index 268793903..703126f31 100644 --- a/editor-v2/containers/Tree/DragContext.tsx +++ b/src/editor-v2/containers/Tree/DragContext.tsx @@ -31,6 +31,8 @@ const DragPreview = React.forwardRef<HTMLDivElement>((_, ref) => { return <div ref={ref} className={b()} style={{display: 'none'}} />; }); +DragPreview.displayName = 'DragPreview'; + // Provider component for drag context export const DragContextProvider: React.FC<React.PropsWithChildren<{}>> = ({children}) => { const [draggedItem, setDraggedItem] = React.useState<DragItem | null>(null); diff --git a/editor-v2/containers/Tree/Tree.scss b/src/editor-v2/containers/Tree/Tree.scss similarity index 100% rename from editor-v2/containers/Tree/Tree.scss rename to src/editor-v2/containers/Tree/Tree.scss diff --git a/editor-v2/containers/Tree/Tree.tsx b/src/editor-v2/containers/Tree/Tree.tsx similarity index 100% rename from editor-v2/containers/Tree/Tree.tsx rename to src/editor-v2/containers/Tree/Tree.tsx diff --git a/editor-v2/containers/Tree/TreeContent.scss b/src/editor-v2/containers/Tree/TreeContent.scss similarity index 100% rename from editor-v2/containers/Tree/TreeContent.scss rename to src/editor-v2/containers/Tree/TreeContent.scss diff --git a/editor-v2/containers/Tree/TreeContent.tsx b/src/editor-v2/containers/Tree/TreeContent.tsx similarity index 100% rename from editor-v2/containers/Tree/TreeContent.tsx rename to src/editor-v2/containers/Tree/TreeContent.tsx diff --git a/editor-v2/containers/Tree/TreeItem.scss b/src/editor-v2/containers/Tree/TreeItem.scss similarity index 100% rename from editor-v2/containers/Tree/TreeItem.scss rename to src/editor-v2/containers/Tree/TreeItem.scss diff --git a/editor-v2/containers/Tree/TreeItem.tsx b/src/editor-v2/containers/Tree/TreeItem.tsx similarity index 100% rename from editor-v2/containers/Tree/TreeItem.tsx rename to src/editor-v2/containers/Tree/TreeItem.tsx diff --git a/editor-v2/containers/Tree/index.ts b/src/editor-v2/containers/Tree/index.ts similarity index 100% rename from editor-v2/containers/Tree/index.ts rename to src/editor-v2/containers/Tree/index.ts diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.scss b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss similarity index 100% rename from editor-v2/containers/ViewSwitches/ViewSwitches.scss rename to src/editor-v2/containers/ViewSwitches/ViewSwitches.scss diff --git a/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx similarity index 100% rename from editor-v2/containers/ViewSwitches/ViewSwitches.tsx rename to src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx diff --git a/editor-v2/containers/__stories__/Editor.stories.tsx b/src/editor-v2/containers/__stories__/Editor.stories.tsx similarity index 100% rename from editor-v2/containers/__stories__/Editor.stories.tsx rename to src/editor-v2/containers/__stories__/Editor.stories.tsx diff --git a/editor-v2/containers/__stories__/utils.ts b/src/editor-v2/containers/__stories__/utils.ts similarity index 100% rename from editor-v2/containers/__stories__/utils.ts rename to src/editor-v2/containers/__stories__/utils.ts diff --git a/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx similarity index 100% rename from editor-v2/context/editorStore/MainEditorStoreContext.tsx rename to src/editor-v2/context/editorStore/MainEditorStoreContext.tsx diff --git a/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx similarity index 89% rename from editor-v2/context/editorStore/MainEditorStoreProvider.tsx rename to src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx index e290b9880..b20b7d685 100644 --- a/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import {StoreApi} from 'zustand'; -import {EditorState} from '../../../common/store'; -import {StoreSyncMessage} from '../../../common/types'; -import {removeFn} from '../../../common/utils'; +import {EditorState} from '../../../../common/store'; +import {StoreSyncMessage} from '../../../../common/types'; +import {removeFn} from '../../../../common/utils'; import {EditorStore, createEditorStore} from '../../store'; import {IframeContext} from '../iframeContext'; diff --git a/editor-v2/context/editorStore/index.ts b/src/editor-v2/context/editorStore/index.ts similarity index 100% rename from editor-v2/context/editorStore/index.ts rename to src/editor-v2/context/editorStore/index.ts diff --git a/editor-v2/context/iframeContext/IframeContext.tsx b/src/editor-v2/context/iframeContext/IframeContext.tsx similarity index 100% rename from editor-v2/context/iframeContext/IframeContext.tsx rename to src/editor-v2/context/iframeContext/IframeContext.tsx diff --git a/editor-v2/context/iframeContext/IframeProvider.tsx b/src/editor-v2/context/iframeContext/IframeProvider.tsx similarity index 100% rename from editor-v2/context/iframeContext/IframeProvider.tsx rename to src/editor-v2/context/iframeContext/IframeProvider.tsx diff --git a/editor-v2/context/iframeContext/index.ts b/src/editor-v2/context/iframeContext/index.ts similarity index 100% rename from editor-v2/context/iframeContext/index.ts rename to src/editor-v2/context/iframeContext/index.ts diff --git a/editor-v2/hooks/useEditorTabs.tsx b/src/editor-v2/hooks/useEditorTabs.tsx similarity index 100% rename from editor-v2/hooks/useEditorTabs.tsx rename to src/editor-v2/hooks/useEditorTabs.tsx diff --git a/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts similarity index 96% rename from editor-v2/hooks/useMainEditorInitialize.ts rename to src/editor-v2/hooks/useMainEditorInitialize.ts index 2326b95ec..0670fab2d 100644 --- a/editor-v2/hooks/useMainEditorInitialize.ts +++ b/src/editor-v2/hooks/useMainEditorInitialize.ts @@ -1,4 +1,4 @@ -import {usePostMessageAPIListener} from '../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from './useMainEditorStore'; import {usePostMessageEvents} from './usePostMessageEvents'; diff --git a/editor-v2/hooks/useMainEditorStore.ts b/src/editor-v2/hooks/useMainEditorStore.ts similarity index 100% rename from editor-v2/hooks/useMainEditorStore.ts rename to src/editor-v2/hooks/useMainEditorStore.ts diff --git a/editor-v2/hooks/usePostMessageEvents.ts b/src/editor-v2/hooks/usePostMessageEvents.ts similarity index 84% rename from editor-v2/hooks/usePostMessageEvents.ts rename to src/editor-v2/hooks/usePostMessageEvents.ts index cc8000843..2eaf52d11 100644 --- a/editor-v2/hooks/usePostMessageEvents.ts +++ b/src/editor-v2/hooks/usePostMessageEvents.ts @@ -1,7 +1,7 @@ import * as React from 'react'; -import {requestActionPostMessage} from '../../common/postMessage'; -import {ActionMessageTypes} from '../../common/types'; +import {requestActionPostMessage} from '../../../common/postMessage'; +import {ActionMessageTypes} from '../../../common/types'; import {IframeContext} from '../context/iframeContext'; interface UsePostMessageRequestReturn { diff --git a/editor-v2/index.ts b/src/editor-v2/index.ts similarity index 58% rename from editor-v2/index.ts rename to src/editor-v2/index.ts index 4697a13c9..a42d4a430 100644 --- a/editor-v2/index.ts +++ b/src/editor-v2/index.ts @@ -1,2 +1,2 @@ export {Editor} from './containers/Editor/Editor'; -export * from '../common/types'; +export * from '../../common/types'; diff --git a/editor-v2/store.ts b/src/editor-v2/store.ts similarity index 97% rename from editor-v2/store.ts rename to src/editor-v2/store.ts index 17c50cb65..9fea7fcba 100644 --- a/editor-v2/store.ts +++ b/src/editor-v2/store.ts @@ -1,9 +1,9 @@ import _ from 'lodash'; -import {EditorState, initialStore} from '../common/store'; -import {DynamicFormValue} from '../common/types'; -import {initializeStore} from '../common/utils'; -import {ConstructorBlock, PageContentWithNavigation} from '../src/models'; +import {EditorState, initialStore} from '../../common/store'; +import {DynamicFormValue} from '../../common/types'; +import {initializeStore} from '../../common/utils'; +import {ConstructorBlock, PageContentWithNavigation} from '../models'; import {ZOOM_STEPS} from './constants'; import { diff --git a/editor-v2/styles/mixins.scss b/src/editor-v2/styles/mixins.scss similarity index 100% rename from editor-v2/styles/mixins.scss rename to src/editor-v2/styles/mixins.scss diff --git a/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss similarity index 100% rename from editor-v2/styles/root.scss rename to src/editor-v2/styles/root.scss diff --git a/editor-v2/styles/variables.scss b/src/editor-v2/styles/variables.scss similarity index 100% rename from editor-v2/styles/variables.scss rename to src/editor-v2/styles/variables.scss diff --git a/editor-v2/utils/cn.ts b/src/editor-v2/utils/cn.ts similarity index 100% rename from editor-v2/utils/cn.ts rename to src/editor-v2/utils/cn.ts diff --git a/editor-v2/utils/code.ts b/src/editor-v2/utils/code.ts similarity index 83% rename from editor-v2/utils/code.ts rename to src/editor-v2/utils/code.ts index 5fe329723..3b4663267 100644 --- a/editor-v2/utils/code.ts +++ b/src/editor-v2/utils/code.ts @@ -1,6 +1,6 @@ import yaml from 'js-yaml'; -import {PageContent} from '../../src/models'; +import {PageContent} from '../../models'; export function parseCode(code: string) { const pageContent = yaml.load(code) as PageContent; diff --git a/editor-v2/utils/index.ts b/src/editor-v2/utils/index.ts similarity index 99% rename from editor-v2/utils/index.ts rename to src/editor-v2/utils/index.ts index 9b416b484..8de140f3b 100644 --- a/editor-v2/utils/index.ts +++ b/src/editor-v2/utils/index.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {ConstructorBlock} from '../../src/models'; +import {ConstructorBlock} from '../../models'; export function insert<T>(arr: Array<T>, index: number, newItem: T) { return [...arr.slice(0, index), newItem, ...arr.slice(index)]; From 803ec6720d042c4f6f894162af92b5fc789b1461 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Mon, 7 Apr 2025 09:10:45 +0000 Subject: [PATCH 42/84] 6.3.2-alpha.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea7e325e0..2eb2084fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.0", + "version": "6.3.2-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.0", + "version": "6.3.2-alpha.1", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index da320ada5..a0b1095aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.0", + "version": "6.3.2-alpha.1", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From 9c2d512ae2066d2fa09e7b96e8cbb1c1fd032eb1 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 13:06:37 +0300 Subject: [PATCH 43/84] fix: eslint --- src/editor-v2/containers/Tree/TreeItem.tsx | 2 +- src/editor-v2/hooks/useEditorTabs.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/editor-v2/containers/Tree/TreeItem.tsx b/src/editor-v2/containers/Tree/TreeItem.tsx index 80da0af57..0aafed007 100644 --- a/src/editor-v2/containers/Tree/TreeItem.tsx +++ b/src/editor-v2/containers/Tree/TreeItem.tsx @@ -107,7 +107,7 @@ export const Item = ({ // Reset drag context setDraggedItem(null); hidePreview(); - }, [setDraggedItem, hidePreview, path]); + }, [setDraggedItem, hidePreview]); const handleDragOver = React.useCallback( (e: React.DragEvent<HTMLDivElement>) => { diff --git a/src/editor-v2/hooks/useEditorTabs.tsx b/src/editor-v2/hooks/useEditorTabs.tsx index 1f0e544fc..5db43263b 100644 --- a/src/editor-v2/hooks/useEditorTabs.tsx +++ b/src/editor-v2/hooks/useEditorTabs.tsx @@ -8,8 +8,8 @@ import SourceCode from '../containers/SourceCode/SourceCode'; import Tree from '../containers/Tree'; export const useEditorTabs = ({ - leftTabs = [], - rightTabs = [], + leftTabs, + rightTabs, }: { leftTabs?: TabsItemProps[]; rightTabs?: TabsItemProps[]; @@ -52,7 +52,7 @@ export const useEditorTabs = ({ title: 'NAVIGATION', component: GlobalConfig, }, - ...leftTabs, + ...(leftTabs || []), ], right: [ { @@ -80,10 +80,10 @@ export const useEditorTabs = ({ /> ), }, - ...rightTabs, + ...(rightTabs || []), ], }), - [], + [leftTabs, rightTabs], ); return tabs; From 2be24d8e0ee768122b3e1fa7555f3ed9ef4b2563 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 15:46:13 +0300 Subject: [PATCH 44/84] fix: editor for radiobutton --- src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss index 35de5b6db..76b3b96a2 100644 --- a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.scss @@ -5,7 +5,7 @@ $block: '.#{$ns}oneof-dynamic-field'; #{$block} { &__radio { - margin-bottom: 12px; + margin: 16px 12px; } &__select { From 3724f3fc5f194b5264c694bf8362143feee15272 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Mon, 7 Apr 2025 12:57:28 +0000 Subject: [PATCH 45/84] 6.3.2-alpha.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2eb2084fd..dda97ce2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.1", + "version": "6.3.2-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.1", + "version": "6.3.2-alpha.2", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index a0b1095aa..a3421b48f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.1", + "version": "6.3.2-alpha.2", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From ab2fc57f6725d8aade8800c650c8e44689d7ec4f Mon Sep 17 00:00:00 2001 From: DaffPunks <sawavas2@gmail.com> Date: Tue, 8 Apr 2025 12:06:21 +0300 Subject: [PATCH 46/84] feat: update styles for Overlay --- src/editor-v2/containers/Overlay/Overlay.scss | 48 ++++++++++++----- src/editor-v2/containers/Overlay/Overlay.tsx | 52 ++++++++++--------- src/editor-v2/styles/root.scss | 4 +- 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/editor-v2/containers/Overlay/Overlay.scss b/src/editor-v2/containers/Overlay/Overlay.scss index 975ae27ca..d478f7526 100644 --- a/src/editor-v2/containers/Overlay/Overlay.scss +++ b/src/editor-v2/containers/Overlay/Overlay.scss @@ -5,6 +5,11 @@ $block: '.#{$ns}overlay'; #{$block} { + $border-width: 3px; + $border-select: 5px var(--g-color-line-brand) solid; + $border: $border-width var(--g-color-line-brand) solid; + $border-radius: 24px; + position: absolute; left: 0; top: 0; @@ -12,18 +17,17 @@ $block: '.#{$ns}overlay'; height: 100%; pointer-events: none; - $border: 5px var(--g-color-line-brand) solid; - $border-2: 3px var(--g-color-base-selection) solid; - &__border { position: absolute; - border: 3px var(--g-color-line-brand) solid; - border-radius: 24px; + border: $border; + border-radius: $border-radius; box-sizing: border-box; box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow); + z-index: 100; &_hover { opacity: 0.5; + z-index: 99; } } @@ -31,22 +35,28 @@ $block: '.#{$ns}overlay'; position: absolute; //border: $border-2; box-sizing: border-box; + border-radius: $border-radius; + z-index: 10; &_position { &_top { - border-top: $border; + border-top: $border-select; + border-left: $border-select; } &_bottom { - border-bottom: $border; + border-right: $border-select; + border-bottom: $border-select; } &_left { - border-left: $border; + border-top: $border-select; + border-left: $border-select; } &_right { - border-right: $border; + border-right: $border-select; + border-bottom: $border-select; } } } @@ -54,18 +64,28 @@ $block: '.#{$ns}overlay'; &__actions { pointer-events: auto; position: absolute; - bottom: 0; + bottom: -$border-width; left: 50%; transform: translate(-50%, 100%); + z-index: 1000; display: flex; - align-items: center; + align-items: flex-start; background: transparent; } - &__action-button { - &_grip { - cursor: grab; + &__actions-box { + &_main { + display: flex; + gap: 2px; + align-items: center; + padding: 1px 8px 4px; + background-color: var(--g-color-base-brand); + border-radius: 0px 0px 8px 8px; + } + + &_reorder { + padding: 3px 4px; } } } diff --git a/src/editor-v2/containers/Overlay/Overlay.tsx b/src/editor-v2/containers/Overlay/Overlay.tsx index ac7759881..481e47458 100644 --- a/src/editor-v2/containers/Overlay/Overlay.tsx +++ b/src/editor-v2/containers/Overlay/Overlay.tsx @@ -111,30 +111,34 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { }} > <div className={b('actions')}> - <Button view="flat" size={'m'} onClick={handleMoveUp}> - <Icon data={ChevronUp} size={18} /> - </Button> - <Button - pin={'round-clear'} - className={b('action-button')} - size={'m'} - view={'action'} - onClick={() => selectedBlock && duplicateBlock(selectedBlock)} - > - <Icon data={Copy} size={18} /> - </Button> - <Button - pin={'clear-round'} - className={b('action-button')} - size={'m'} - view={'action'} - onClick={() => selectedBlock && deleteBlock(selectedBlock)} - > - <Icon data={TrashBin} size={18} /> - </Button> - <Button view="flat" size={'m'} onClick={handleMoveDown}> - <Icon data={ChevronDown} size={18} /> - </Button> + <div className={b('actions-box', {reorder: true})}> + <Button view="flat" size={'m'} onClick={handleMoveUp}> + <Icon data={ChevronUp} size={18} /> + </Button> + </div> + <div className={b('actions-box', {main: true})}> + <Button + className={b('action-button')} + size={'m'} + view={'action'} + onClick={() => selectedBlock && duplicateBlock(selectedBlock)} + > + <Icon data={Copy} size={18} /> + </Button> + <Button + className={b('action-button')} + size={'m'} + view={'action'} + onClick={() => selectedBlock && deleteBlock(selectedBlock)} + > + <Icon data={TrashBin} size={18} /> + </Button> + </div> + <div className={b('actions-box', {reorder: true})}> + <Button view="flat" size={'m'} onClick={handleMoveDown}> + <Icon data={ChevronDown} size={18} /> + </Button> + </div> </div> </div> ) : null} diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss index 0e2334f3a..d74f9a58f 100644 --- a/src/editor-v2/styles/root.scss +++ b/src/editor-v2/styles/root.scss @@ -3,8 +3,8 @@ --g-color-base-brand-hover: var(--g-color-private-black-600-solid); --g-color-base-selection: var(--g-color-private-black-200); --g-color-base-selection-hover: var(--g-color-private-black-300); - --g-color-base-brand: var(--g-color-text-primary); - --g-color-base-brand-hover: var(--g-color-text-complementary); + --g-color-base-brand: var(--g-color-private-black-850-solid); + --g-color-base-brand-hover: var(--g-color-private-black-700-solid); --g-color-text-brand-contrast: var(--g-color-text-light-primary); --g-color-line-brand: var(--g-color-text-primary); --g-color-text-brand: var(--g-color-private-brand-700-solid); From a26b621f2afc8c71085444695d881486dc1f49cb Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 18:27:54 +0300 Subject: [PATCH 47/84] fix: build --- package-lock.json | 224 +----------------- package.json | 1 - playground/src/app/page.tsx | 2 +- src/blocks/CardLayout/index.ts | 2 +- src/blocks/Header/dynamic-form.ts | 2 +- src/blocks/Slider/dynamic-form.ts | 2 +- src/blocks/TestEditorBlock/form.ts | 2 +- {common => src/common}/postMessage.ts | 0 {common => src/common}/store.ts | 2 +- {common => src/common}/types/actions.ts | 2 +- {common => src/common}/types/common.ts | 0 {common => src/common}/types/forms.ts | 2 +- {common => src/common}/types/index.ts | 0 {common => src/common}/types/messages.ts | 0 {common => src/common}/utils.ts | 0 src/components/Image/dynamic-form.ts | 2 +- src/constructor-items.ts | 2 +- .../PCEditorStoreContext.tsx | 2 +- .../PCEditorStoreProvider.tsx | 4 +- .../components/DynamicForm/DynamicForm.tsx | 2 +- .../DynamicForm/Fields/Array/Array.tsx | 2 +- .../DynamicForm/Fields/Object/Object.tsx | 2 +- .../DynamicForm/Fields/OneOf/OneOf.tsx | 2 +- .../DynamicForm/Fields/Select/Select.tsx | 2 +- src/editor-v2/components/DynamicForm/utils.ts | 2 +- .../components/StoreViewer/StoreViewer.scss | 39 --- .../components/StoreViewer/StoreViewer.tsx | 39 --- .../containers/BigOverlay/BigOverlay.tsx | 2 +- .../containers/BlockConfig/BlockConfig.tsx | 2 +- .../containers/BlocksList/BlocksList.tsx | 2 +- src/editor-v2/containers/Editor/Editor.tsx | 2 - .../containers/GlobalConfig/GlobalConfig.tsx | 2 +- .../containers/MiddleScreen/MiddleScreen.tsx | 2 +- src/editor-v2/containers/Overlay/Overlay.tsx | 2 +- .../editorStore/MainEditorStoreProvider.tsx | 6 +- .../hooks/useMainEditorInitialize.ts | 2 +- src/editor-v2/hooks/usePostMessageEvents.ts | 4 +- src/editor-v2/index.ts | 2 +- src/editor-v2/store.ts | 6 +- src/hooks/usePCEditorInitializeEvents.ts | 2 +- src/hooks/usePostMessageAPI.ts | 4 +- src/sub-blocks/BackgroundCard/dynamic-form.ts | 2 +- src/utils/form-generator.ts | 2 +- 43 files changed, 43 insertions(+), 342 deletions(-) rename {common => src/common}/postMessage.ts (100%) rename {common => src/common}/store.ts (94%) rename {common => src/common}/types/actions.ts (92%) rename {common => src/common}/types/common.ts (100%) rename {common => src/common}/types/forms.ts (97%) rename {common => src/common}/types/index.ts (100%) rename {common => src/common}/types/messages.ts (100%) rename {common => src/common}/utils.ts (100%) delete mode 100644 src/editor-v2/components/StoreViewer/StoreViewer.scss delete mode 100644 src/editor-v2/components/StoreViewer/StoreViewer.tsx diff --git a/package-lock.json b/package-lock.json index dda97ce2f..1d2d187c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", - "react-json-view": "^1.21.3", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", "react-resizable-panels": "^2.1.3", @@ -8255,11 +8254,6 @@ "node": ">=0.10.0" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -8665,11 +8659,6 @@ "dev": true, "optional": true }, - "node_modules/base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9704,14 +9693,6 @@ "typescript": ">=5" } }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -12124,33 +12105,6 @@ "bser": "2.1.1" } }, - "node_modules/fbemitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", - "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", - "dependencies": { - "fbjs": "^3.0.0" - } - }, - "node_modules/fbjs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", - "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", - "dependencies": { - "cross-fetch": "^3.1.5", - "fbjs-css-vars": "^1.0.0", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^1.0.35" - } - }, - "node_modules/fbjs-css-vars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", - "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -12350,18 +12304,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/flux": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz", - "integrity": "sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==", - "dependencies": { - "fbemitter": "^3.0.0", - "fbjs": "^3.0.1" - }, - "peerDependencies": { - "react": "^15.0.2 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -16492,21 +16434,11 @@ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true }, - "node_modules/lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "node_modules/lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" - }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -18108,25 +18040,6 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -20248,14 +20161,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dependencies": { - "asap": "~2.0.3" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -20314,11 +20219,6 @@ "node": ">=6" } }, - "node_modules/pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" - }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -20446,17 +20346,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-base16-styling": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", - "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==", - "dependencies": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, "node_modules/react-beautiful-dnd": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", @@ -20582,25 +20471,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-json-view": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", - "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", - "dependencies": { - "flux": "^4.0.1", - "react-base16-styling": "^0.6.0", - "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^8.3.2" - }, - "peerDependencies": { - "react": "^17.0.0 || ^16.3.0 || ^15.5.4", - "react-dom": "^17.0.0 || ^16.3.0 || ^15.5.4" - } - }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "dev": true }, "node_modules/react-monaco-editor": { "version": "0.53.0", @@ -20722,22 +20597,6 @@ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-textarea-autosize": { - "version": "8.5.7", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.7.tgz", - "integrity": "sha512-2MqJ3p0Jh69yt9ktFIaZmORHXw4c4bxSIhCeWiFwmJ9EYKgLmuNII3e9c9b2UO+ijl4StnpZdqpxNIhTdHvqtQ==", - "dependencies": { - "@babel/runtime": "^7.20.13", - "use-composed-ref": "^1.3.0", - "use-latest": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -22260,11 +22119,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -23932,11 +23786,6 @@ "node": ">= 4.0.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/trim-newlines": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", @@ -24220,31 +24069,6 @@ "node": ">= 4" } }, - "node_modules/ua-parser-js": { - "version": "1.0.40", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", - "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -24578,23 +24402,11 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/use-composed-ref": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", - "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "dev": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, @@ -24604,22 +24416,6 @@ } } }, - "node_modules/use-latest": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", - "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", - "dependencies": { - "use-isomorphic-layout-effect": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", @@ -25447,11 +25243,6 @@ "node": ">=10.13.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "node_modules/webpack": { "version": "5.98.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", @@ -25721,15 +25512,6 @@ "node": ">=12" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index a3421b48f..288158267 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,6 @@ "lodash": "^4.17.21", "monaco-editor": "^0.38.0", "react-final-form": "^6.5.9", - "react-json-view": "^1.21.3", "react-monaco-editor": "^0.53.0", "react-player": "^2.9.0", "react-resizable-panels": "^2.1.3", diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index 205147409..854c352b7 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -14,7 +14,7 @@ const b = block('home'); const Test = () => <div className={b('test')}>custom test</div>; -export const COMPONENTS_CONFIG = { +const COMPONENTS_CONFIG = { middleTop: Test, leftTop: [C9RComponent], leftTabs: [ diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts index 70f9abfca..5a4ce280d 100644 --- a/src/blocks/CardLayout/index.ts +++ b/src/blocks/CardLayout/index.ts @@ -1,6 +1,6 @@ import {JSONSchemaType} from 'ajv'; -import {ConfigInput} from '../../../common/types'; +import {ConfigInput} from '../../common/types'; import {BlockData} from '../../constructor-items'; import {sliderSizesArray, textSize} from '../../schema/validators/common'; import {generateFromAJV} from '../../utils/form-generator'; diff --git a/src/blocks/Header/dynamic-form.ts b/src/blocks/Header/dynamic-form.ts index 33c4b9b93..d3f174aba 100644 --- a/src/blocks/Header/dynamic-form.ts +++ b/src/blocks/Header/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig, ConfigInput} from '../../../common/types'; +import {BlockConfig, ConfigInput} from '../../common/types'; import {imageInputs} from '../../components/Image/dynamic-form'; import {Theme} from '../../models'; diff --git a/src/blocks/Slider/dynamic-form.ts b/src/blocks/Slider/dynamic-form.ts index 5bf5520fc..ea7468997 100644 --- a/src/blocks/Slider/dynamic-form.ts +++ b/src/blocks/Slider/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig, ConfigInput} from '../../../common/types'; +import {BlockConfig, ConfigInput} from '../../common/types'; import {sliderSizesArray, textSize} from '../../schema/validators/common'; const textSizeEnum = textSize.map((size) => ({value: size, content: size})); diff --git a/src/blocks/TestEditorBlock/form.ts b/src/blocks/TestEditorBlock/form.ts index 849cb6d30..838eacb06 100644 --- a/src/blocks/TestEditorBlock/form.ts +++ b/src/blocks/TestEditorBlock/form.ts @@ -9,7 +9,7 @@ import { SelectSingleInput, TextAreaInput, TextInput, -} from '../../../common/types'; +} from '../../common/types'; const textInput: TextInput = { type: 'text', diff --git a/common/postMessage.ts b/src/common/postMessage.ts similarity index 100% rename from common/postMessage.ts rename to src/common/postMessage.ts diff --git a/common/store.ts b/src/common/store.ts similarity index 94% rename from common/store.ts rename to src/common/store.ts index 668684374..1b8e37a70 100644 --- a/common/store.ts +++ b/src/common/store.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {PageContentWithNavigation} from '../src/models'; +import {PageContentWithNavigation} from '../models'; import {ConfigInput, ItemConfig} from './types'; import {initializeStore} from './utils'; diff --git a/common/types/actions.ts b/src/common/types/actions.ts similarity index 92% rename from common/types/actions.ts rename to src/common/types/actions.ts index ecb2bcd9f..1fbf0fdc5 100644 --- a/common/types/actions.ts +++ b/src/common/types/actions.ts @@ -1,4 +1,4 @@ -import {PageContentWithNavigation} from '../../src/models'; +import {PageContentWithNavigation} from '../../models'; import {EditorState} from '../store'; export type MessageTypes = EventMessageTypes & ActionMessageTypes; diff --git a/common/types/common.ts b/src/common/types/common.ts similarity index 100% rename from common/types/common.ts rename to src/common/types/common.ts diff --git a/common/types/forms.ts b/src/common/types/forms.ts similarity index 97% rename from common/types/forms.ts rename to src/common/types/forms.ts index 190d349d4..f5a53c37d 100644 --- a/common/types/forms.ts +++ b/src/common/types/forms.ts @@ -1,4 +1,4 @@ -import {PageContentWithNavigation} from '../../src/models'; +import {PageContentWithNavigation} from '../../models'; export type DynamicFormValue = | string diff --git a/common/types/index.ts b/src/common/types/index.ts similarity index 100% rename from common/types/index.ts rename to src/common/types/index.ts diff --git a/common/types/messages.ts b/src/common/types/messages.ts similarity index 100% rename from common/types/messages.ts rename to src/common/types/messages.ts diff --git a/common/utils.ts b/src/common/utils.ts similarity index 100% rename from common/utils.ts rename to src/common/utils.ts diff --git a/src/components/Image/dynamic-form.ts b/src/components/Image/dynamic-form.ts index 23b3f44cf..f5f8de747 100644 --- a/src/components/Image/dynamic-form.ts +++ b/src/components/Image/dynamic-form.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {ConfigInput} from '../../../common/types'; +import {ConfigInput} from '../../common/types'; const devices = ['desktop', 'tablet', 'mobile']; diff --git a/src/constructor-items.ts b/src/constructor-items.ts index 00b12df99..a49cdaa03 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import {BlockConfig} from '../common/types'; +import {BlockConfig} from './common/types'; import { BannerBlock, diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx index 64e0b6051..1b9e53899 100644 --- a/src/context/editorStoreContext/PCEditorStoreContext.tsx +++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import {StoreApi} from 'zustand'; -import {EditorState, createPCEditorStore} from '../../../common/store'; +import {EditorState, createPCEditorStore} from '../../common/store'; export interface PCEditorStoreContextProps { state: StoreApi<EditorState>; diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx index 9de324e5a..b3d851565 100644 --- a/src/context/editorStoreContext/PCEditorStoreProvider.tsx +++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import {StoreApi} from 'zustand'; -import {EditorState, createPCEditorStore} from '../../../common/store'; -import {StoreSyncMessage} from '../../../common/types'; +import {EditorState, createPCEditorStore} from '../../common/store'; +import {StoreSyncMessage} from '../../common/types'; import {PCEditorStoreContext} from './PCEditorStoreContext'; diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.tsx b/src/editor-v2/components/DynamicForm/DynamicForm.tsx index 74a80edc1..9531c2181 100644 --- a/src/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/src/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -1,7 +1,7 @@ import _ from 'lodash'; import * as React from 'react'; -import {ConfigInput, DynamicFormValue} from '../../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../common/types'; import {editorCn} from '../../utils/cn'; import './DynamicForm.scss'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx index 31381279f..295225c73 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Array/Array.tsx @@ -2,7 +2,7 @@ import {Plus} from '@gravity-ui/icons'; import {Button, Card, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; -import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../../common/types'; +import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../../../common/types'; import {removeFromArray, swapArrayItems} from '../../../../utils'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx index e6839cba8..3711df5be 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Object/Object.tsx @@ -1,6 +1,6 @@ import {Card} from '@gravity-ui/uikit'; -import {ConfigInput, DynamicFormValue} from '../../../../../../common/types'; +import {ConfigInput, DynamicFormValue} from '../../../../../common/types'; import DynamicForm from '../../DynamicForm'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx index 77559d1e7..31ed85ff3 100644 --- a/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/OneOf/OneOf.tsx @@ -1,7 +1,7 @@ import {Card, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import * as React from 'react'; -import {DynamicFormValue, OneOfInput} from '../../../../../../common/types'; +import {DynamicFormValue, OneOfInput} from '../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; import DynamicForm from '../../DynamicForm'; import FieldBase from '../../FieldBase/FieldBase'; diff --git a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx index 6d3eaabfe..13bcbe0dc 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -1,6 +1,6 @@ import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; -import {SelectMultipleInput, SelectSingleInput} from '../../../../../../common/types'; +import {SelectMultipleInput, SelectSingleInput} from '../../../../../common/types'; import {editorCn} from '../../../../utils/cn'; import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase'; diff --git a/src/editor-v2/components/DynamicForm/utils.ts b/src/editor-v2/components/DynamicForm/utils.ts index 2449db532..5ad3f80b7 100644 --- a/src/editor-v2/components/DynamicForm/utils.ts +++ b/src/editor-v2/components/DynamicForm/utils.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {DynamicFormValue} from '../../../../common/types'; +import {DynamicFormValue} from '../../../common/types'; export const getFullPath = (path: string, name: string) => { if (!path && !name) { diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.scss b/src/editor-v2/components/StoreViewer/StoreViewer.scss deleted file mode 100644 index 6fd241894..000000000 --- a/src/editor-v2/components/StoreViewer/StoreViewer.scss +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../styles/variables.scss'; -@import '../../styles/mixins.scss'; - -$block: '.#{$ns}store-viewer'; - -#{$block} { - background-color: var(--g-color-base-float-heavy); - border-radius: 12px; - border: 1px solid var(--g-color-line-generic); - overflow: hidden; - opacity: 0.3; - - &:hover { - opacity: 1; - } - - &_open { - opacity: 1; - - #{$block}__box { - display: block; - } - } - - &__head { - padding: 12px; - } - - &__box { - max-height: 80vh; - overflow: scroll; - display: none; - min-width: 400px; - max-width: 80vw; - padding: 12px; - background-color: var(--g-color-base-float); - white-space: pre; - } -} diff --git a/src/editor-v2/components/StoreViewer/StoreViewer.tsx b/src/editor-v2/components/StoreViewer/StoreViewer.tsx deleted file mode 100644 index b931bd084..000000000 --- a/src/editor-v2/components/StoreViewer/StoreViewer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import {Code} from '@gravity-ui/icons'; -import {Button, Icon} from '@gravity-ui/uikit'; -import * as React from 'react'; -import ReactJson from 'react-json-view'; - -import {removeFn} from '../../../../common/utils'; -import {editorCn} from '../../utils/cn'; - -import './StoreViewer.scss'; - -const b = editorCn('store-viewer'); - -interface StoreViewerProps { - className?: string; - store: object; -} - -const StoreViewer = ({store, className}: StoreViewerProps) => { - const [open, setOpen] = React.useState(false); - return ( - <div className={b({open}, className)}> - <div className={b('head')}> - <Button view="action" onClick={() => setOpen(!open)}> - <Icon data={Code} /> - </Button> - </div> - <div className={b('box')}> - <ReactJson - style={{backgroundColor: 'transparent'}} - src={removeFn(store)} - collapsed - theme="bright:inverted" - /> - </div> - </div> - ); -}; - -export default StoreViewer; diff --git a/src/editor-v2/containers/BigOverlay/BigOverlay.tsx b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx index eb21962bd..34d50fefc 100644 --- a/src/editor-v2/containers/BigOverlay/BigOverlay.tsx +++ b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx @@ -1,7 +1,7 @@ import {Stop} from '@gravity-ui/icons'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/src/editor-v2/containers/BlockConfig/BlockConfig.tsx b/src/editor-v2/containers/BlockConfig/BlockConfig.tsx index 39a67238d..c0b753d07 100644 --- a/src/editor-v2/containers/BlockConfig/BlockConfig.tsx +++ b/src/editor-v2/containers/BlockConfig/BlockConfig.tsx @@ -1,6 +1,6 @@ import _ from 'lodash'; -import {DynamicFormValue} from '../../../../common/types'; +import {DynamicFormValue} from '../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {generateChildrenPathFromArray} from '../../utils'; diff --git a/src/editor-v2/containers/BlocksList/BlocksList.tsx b/src/editor-v2/containers/BlocksList/BlocksList.tsx index 2f7dd25e3..b8652f238 100644 --- a/src/editor-v2/containers/BlocksList/BlocksList.tsx +++ b/src/editor-v2/containers/BlocksList/BlocksList.tsx @@ -2,7 +2,7 @@ import {Magnifier, SquareBars} from '@gravity-ui/icons'; import {Card, Icon, TextInput} from '@gravity-ui/uikit'; import * as React from 'react'; -import {ItemConfig} from '../../../../common/types'; +import {ItemConfig} from '../../../common/types'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index fd4dbe397..e8bef32f8 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import {PageContentWithNavigation} from '../../../models'; import {Panels} from '../../components/Panels/Panels'; import {Sidebar} from '../../components/Sidebar/Sidebar'; -import StoreViewer from '../../components/StoreViewer/StoreViewer'; import BigOverlay from '../../containers/BigOverlay/BigOverlay'; import MiddleScreen from '../../containers/MiddleScreen/MiddleScreen'; import {MainEditorStoreProvider} from '../../context/editorStore'; @@ -71,7 +70,6 @@ const EditorView = ({componentsConfig = {}}: EditorViewProps) => { /> </div> <BigOverlay className={b('overlay')} /> - <StoreViewer className={b('debug-store')} store={store} /> </div> ); }; diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx index 9299a3150..9defca140 100644 --- a/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx +++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx @@ -1,4 +1,4 @@ -import {DynamicFormValue} from '../../../../common/types'; +import {DynamicFormValue} from '../../../common/types'; import DynamicForm from '../../components/DynamicForm/DynamicForm'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 9ab96a5da..198c759ef 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -1,7 +1,7 @@ import {Loader} from '@gravity-ui/uikit'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {IframeContext} from '../../context/iframeContext'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/src/editor-v2/containers/Overlay/Overlay.tsx b/src/editor-v2/containers/Overlay/Overlay.tsx index 481e47458..86cbc6bef 100644 --- a/src/editor-v2/containers/Overlay/Overlay.tsx +++ b/src/editor-v2/containers/Overlay/Overlay.tsx @@ -2,7 +2,7 @@ import {ChevronDown, ChevronUp, Copy, TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import * as React from 'react'; -import {usePostMessageAPIListener} from '../../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../../common/postMessage'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx index b20b7d685..e290b9880 100644 --- a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx +++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import {StoreApi} from 'zustand'; -import {EditorState} from '../../../../common/store'; -import {StoreSyncMessage} from '../../../../common/types'; -import {removeFn} from '../../../../common/utils'; +import {EditorState} from '../../../common/store'; +import {StoreSyncMessage} from '../../../common/types'; +import {removeFn} from '../../../common/utils'; import {EditorStore, createEditorStore} from '../../store'; import {IframeContext} from '../iframeContext'; diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts index 0670fab2d..2326b95ec 100644 --- a/src/editor-v2/hooks/useMainEditorInitialize.ts +++ b/src/editor-v2/hooks/useMainEditorInitialize.ts @@ -1,4 +1,4 @@ -import {usePostMessageAPIListener} from '../../../common/postMessage'; +import {usePostMessageAPIListener} from '../../common/postMessage'; import {useMainEditorStore} from './useMainEditorStore'; import {usePostMessageEvents} from './usePostMessageEvents'; diff --git a/src/editor-v2/hooks/usePostMessageEvents.ts b/src/editor-v2/hooks/usePostMessageEvents.ts index 2eaf52d11..cc8000843 100644 --- a/src/editor-v2/hooks/usePostMessageEvents.ts +++ b/src/editor-v2/hooks/usePostMessageEvents.ts @@ -1,7 +1,7 @@ import * as React from 'react'; -import {requestActionPostMessage} from '../../../common/postMessage'; -import {ActionMessageTypes} from '../../../common/types'; +import {requestActionPostMessage} from '../../common/postMessage'; +import {ActionMessageTypes} from '../../common/types'; import {IframeContext} from '../context/iframeContext'; interface UsePostMessageRequestReturn { diff --git a/src/editor-v2/index.ts b/src/editor-v2/index.ts index a42d4a430..4697a13c9 100644 --- a/src/editor-v2/index.ts +++ b/src/editor-v2/index.ts @@ -1,2 +1,2 @@ export {Editor} from './containers/Editor/Editor'; -export * from '../../common/types'; +export * from '../common/types'; diff --git a/src/editor-v2/store.ts b/src/editor-v2/store.ts index 9fea7fcba..54edef1dd 100644 --- a/src/editor-v2/store.ts +++ b/src/editor-v2/store.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; -import {EditorState, initialStore} from '../../common/store'; -import {DynamicFormValue} from '../../common/types'; -import {initializeStore} from '../../common/utils'; +import {EditorState, initialStore} from '../common/store'; +import {DynamicFormValue} from '../common/types'; +import {initializeStore} from '../common/utils'; import {ConstructorBlock, PageContentWithNavigation} from '../models'; import {ZOOM_STEPS} from './constants'; diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts index f72ea957c..0b6990d6f 100644 --- a/src/hooks/usePCEditorInitializeEvents.ts +++ b/src/hooks/usePCEditorInitializeEvents.ts @@ -3,7 +3,7 @@ import * as React from 'react'; import {JSONSchemaType} from 'ajv'; import _ from 'lodash'; -import {ItemConfig} from '../../common/types'; +import {ItemConfig} from '../common/types'; import {blockDataMap} from '../constructor-items'; import {PageContentWithNavigation} from '../models'; import {defaultComponentsConfigurationSchema} from '../schema'; diff --git a/src/hooks/usePostMessageAPI.ts b/src/hooks/usePostMessageAPI.ts index 7d5c33c69..346828b6e 100644 --- a/src/hooks/usePostMessageAPI.ts +++ b/src/hooks/usePostMessageAPI.ts @@ -1,7 +1,7 @@ import * as React from 'react'; -import {PostMessageAPIMessage} from '../../common/types'; -import {ActionMessageTypes, EventMessageTypes} from '../../common/types/actions'; +import {PostMessageAPIMessage} from '../common/types'; +import {ActionMessageTypes, EventMessageTypes} from '../common/types/actions'; export function sendEventPostMessage<K extends keyof EventMessageTypes>( action: K, diff --git a/src/sub-blocks/BackgroundCard/dynamic-form.ts b/src/sub-blocks/BackgroundCard/dynamic-form.ts index 1ab9100a1..817ff35e2 100644 --- a/src/sub-blocks/BackgroundCard/dynamic-form.ts +++ b/src/sub-blocks/BackgroundCard/dynamic-form.ts @@ -1,4 +1,4 @@ -import {BlockConfig} from '../../../common/types'; +import {BlockConfig} from '../../common/types'; import {contentThemes, textSize} from '../../schema/validators/common'; const textSizeEnum = textSize.map((size) => ({value: size, content: size})); diff --git a/src/utils/form-generator.ts b/src/utils/form-generator.ts index 03d4ee6c7..9c8af8ca0 100644 --- a/src/utils/form-generator.ts +++ b/src/utils/form-generator.ts @@ -9,7 +9,7 @@ import { SelectBaseInput, TextAreaInput, TextInput, -} from '../../common/types'; +} from '../common/types'; export const generateFromAJV = (schema: JSONSchemaType<{}>): ConfigInput[] => { if (schema && schema.properties) { From a9c5e58280f02fc216d16080f906c286840c35ae Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 20:00:59 +0300 Subject: [PATCH 48/84] fix: styles for dark theme --- playground/src/app/components/C9RComponent.scss | 2 +- playground/src/styles/globals.scss | 1 + .../components/DynamicForm/FieldBase/FieldBase.scss | 1 - src/editor-v2/components/Panels/Panels.scss | 1 - src/editor-v2/components/Sidebar/Sidebar.scss | 1 - src/editor-v2/components/Tabs/Tabs.scss | 2 +- src/editor-v2/containers/BigOverlay/BigOverlay.scss | 1 - src/editor-v2/containers/BlockConfig/BlockConfig.scss | 1 - src/editor-v2/containers/BlocksList/BlocksList.scss | 7 +++---- src/editor-v2/containers/Editor/Editor.scss | 1 - src/editor-v2/containers/GlobalConfig/GlobalConfig.scss | 1 - src/editor-v2/containers/MiddleScreen/MiddleScreen.scss | 3 +-- src/editor-v2/containers/Overlay/Overlay.scss | 1 - src/editor-v2/containers/Source/Source.scss | 1 - src/editor-v2/containers/SourceCode/SourceCode.scss | 1 - src/editor-v2/containers/Tree/DragContext.scss | 5 ++--- src/editor-v2/containers/Tree/Tree.scss | 1 - src/editor-v2/containers/Tree/TreeContent.scss | 5 ----- src/editor-v2/containers/Tree/TreeItem.scss | 7 +------ src/editor-v2/containers/ViewSwitches/ViewSwitches.scss | 1 - src/editor-v2/styles/root.scss | 7 +++---- 21 files changed, 13 insertions(+), 38 deletions(-) diff --git a/playground/src/app/components/C9RComponent.scss b/playground/src/app/components/C9RComponent.scss index fb9a04fa4..6203e962e 100644 --- a/playground/src/app/components/C9RComponent.scss +++ b/playground/src/app/components/C9RComponent.scss @@ -25,6 +25,6 @@ $block: '.c9r-component'; &__editor-info { font-size: 14px; - color: var(--g-color-private-black-300); + color: var(--g-color-text-secondary); } } diff --git a/playground/src/styles/globals.scss b/playground/src/styles/globals.scss index 9fb6a01e7..bfbf1af4e 100644 --- a/playground/src/styles/globals.scss +++ b/playground/src/styles/globals.scss @@ -1,4 +1,5 @@ @import '../../../styles/styles.scss'; +@import '../../../src/editor-v2/styles/root.scss'; html, body { diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss index 92209faa5..50418c0c1 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -1,4 +1,3 @@ -@import '../../../styles/root.scss'; @import '../../../styles/variables.scss'; @import '../../../styles/mixins.scss'; diff --git a/src/editor-v2/components/Panels/Panels.scss b/src/editor-v2/components/Panels/Panels.scss index e7865c13f..bdad68a03 100644 --- a/src/editor-v2/components/Panels/Panels.scss +++ b/src/editor-v2/components/Panels/Panels.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss index fca4b373d..04cb06e72 100644 --- a/src/editor-v2/components/Sidebar/Sidebar.scss +++ b/src/editor-v2/components/Sidebar/Sidebar.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/components/Tabs/Tabs.scss b/src/editor-v2/components/Tabs/Tabs.scss index c4f2e727d..a67b9899f 100644 --- a/src/editor-v2/components/Tabs/Tabs.scss +++ b/src/editor-v2/components/Tabs/Tabs.scss @@ -8,7 +8,7 @@ $block: '.#{$ns}tabs'; width: 100%; &__item { - color: var(--g-color-private-black-300); + color: var(--g-color-text-hint); font-weight: 500; letter-spacing: 0.5px; cursor: pointer; diff --git a/src/editor-v2/containers/BigOverlay/BigOverlay.scss b/src/editor-v2/containers/BigOverlay/BigOverlay.scss index 3d79aed84..bbb1e76e1 100644 --- a/src/editor-v2/containers/BigOverlay/BigOverlay.scss +++ b/src/editor-v2/containers/BigOverlay/BigOverlay.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/BlockConfig/BlockConfig.scss b/src/editor-v2/containers/BlockConfig/BlockConfig.scss index 14921baad..306bf79ee 100644 --- a/src/editor-v2/containers/BlockConfig/BlockConfig.scss +++ b/src/editor-v2/containers/BlockConfig/BlockConfig.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/BlocksList/BlocksList.scss b/src/editor-v2/containers/BlocksList/BlocksList.scss index b6b793ff3..90bffb0cd 100644 --- a/src/editor-v2/containers/BlocksList/BlocksList.scss +++ b/src/editor-v2/containers/BlocksList/BlocksList.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -31,16 +30,16 @@ $block: '.#{$ns}blocks-list'; &__name { justify-self: flex-end; - color: var(--g-color-text-dark-secondary); + color: var(--g-color-text-secondary); } &__title { - color: var(--g-color-text-dark-secondary); + color: var(--g-color-text-secondary); margin-bottom: 10px; } &__icon { - color: var(--g-color-text-dark-complementary); + color: var(--g-color-text-complementary); } &__group { diff --git a/src/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss index cdb4c1f6d..da304c330 100644 --- a/src/editor-v2/containers/Editor/Editor.scss +++ b/src/editor-v2/containers/Editor/Editor.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss index 821017115..45849ce92 100644 --- a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss +++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss index e2a530dd5..2761a12d7 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -18,7 +17,7 @@ $block: '.#{$ns}middle-screen'; flex: 1 1 auto; overflow-y: auto; // height: 100%; - background-color: var(--g-color-text-dark-secondary); + background-color: var(--g-color-text-secondary); } &__wrapper { diff --git a/src/editor-v2/containers/Overlay/Overlay.scss b/src/editor-v2/containers/Overlay/Overlay.scss index d478f7526..e32b9da48 100644 --- a/src/editor-v2/containers/Overlay/Overlay.scss +++ b/src/editor-v2/containers/Overlay/Overlay.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/Source/Source.scss b/src/editor-v2/containers/Source/Source.scss index 04512580e..f2da4ce7c 100644 --- a/src/editor-v2/containers/Source/Source.scss +++ b/src/editor-v2/containers/Source/Source.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/SourceCode/SourceCode.scss b/src/editor-v2/containers/SourceCode/SourceCode.scss index 625207caf..421536a9f 100644 --- a/src/editor-v2/containers/SourceCode/SourceCode.scss +++ b/src/editor-v2/containers/SourceCode/SourceCode.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/Tree/DragContext.scss b/src/editor-v2/containers/Tree/DragContext.scss index ae1097e5e..19f078650 100644 --- a/src/editor-v2/containers/Tree/DragContext.scss +++ b/src/editor-v2/containers/Tree/DragContext.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -7,8 +6,8 @@ $block: '.#{$ns}preview'; #{$block} { position: absolute; height: auto; - background-color: var(--g-color-base-brand-light); - border: 1px dashed var(--g-color-line-brand); + background-color: var(--g-color-text-hint); + border: 1px dashed var(--g-color-text-primary); border-radius: 4px; padding: 8px; z-index: 1000; diff --git a/src/editor-v2/containers/Tree/Tree.scss b/src/editor-v2/containers/Tree/Tree.scss index f2daed6b1..547de744b 100644 --- a/src/editor-v2/containers/Tree/Tree.scss +++ b/src/editor-v2/containers/Tree/Tree.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/containers/Tree/TreeContent.scss b/src/editor-v2/containers/Tree/TreeContent.scss index 2f5842021..5b724500a 100644 --- a/src/editor-v2/containers/Tree/TreeContent.scss +++ b/src/editor-v2/containers/Tree/TreeContent.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -12,10 +11,6 @@ $block: '.#{$ns}tree-content'; margin-bottom: 8px; border-radius: 4px; transition: background-color 0.2s; - - &:hover { - background-color: var(--g-color-text-light-secondary); - } } &__children { diff --git a/src/editor-v2/containers/Tree/TreeItem.scss b/src/editor-v2/containers/Tree/TreeItem.scss index ae821fcfe..da2f2d6db 100644 --- a/src/editor-v2/containers/Tree/TreeItem.scss +++ b/src/editor-v2/containers/Tree/TreeItem.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; @@ -50,7 +49,7 @@ $block: '.#{$ns}tree-item'; } &__type { - color: var(--g-color-text-dark-secondary); + color: var(--g-color-text-secondary); } &__title { @@ -63,9 +62,5 @@ $block: '.#{$ns}tree-item'; margin-bottom: 0; border-radius: 4px; transition: background-color 0.2s; - - &:hover { - background-color: var(--g-color-text-light-secondary); - } } } diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss index 852d29aab..4844fad8f 100644 --- a/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss +++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss @@ -1,4 +1,3 @@ -@import '../../styles/root.scss'; @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss index d74f9a58f..8334b15f5 100644 --- a/src/editor-v2/styles/root.scss +++ b/src/editor-v2/styles/root.scss @@ -1,12 +1,11 @@ .g-root { - --g-color-base-brand: var(--g-color-private-black-550-solid); - --g-color-base-brand-hover: var(--g-color-private-black-600-solid); + --g-color-base-brand: var(--g-color-text-primary); + --g-color-base-brand-hover: var(--g-color-text-complementary); --g-color-base-selection: var(--g-color-private-black-200); --g-color-base-selection-hover: var(--g-color-private-black-300); --g-color-base-brand: var(--g-color-private-black-850-solid); --g-color-base-brand-hover: var(--g-color-private-black-700-solid); --g-color-text-brand-contrast: var(--g-color-text-light-primary); - --g-color-line-brand: var(--g-color-text-primary); - --g-color-text-brand: var(--g-color-private-brand-700-solid); --g-color-text-brand-heavy: var(--g-color-private-black-700-solid); + --g-color-line-brand: var(--g-color-text-primary); } From e9c63afa92463ba8f1aa9016984d3625f3c5b913 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 7 Apr 2025 20:24:49 +0300 Subject: [PATCH 49/84] fix: update editor-v2 exports --- playground/src/app/page.tsx | 3 --- src/common/postMessage.ts | 1 + .../BlockConfigForm.scss} | 2 +- .../BlockConfigForm.tsx} | 10 +++++----- src/editor-v2/containers/Editor/Editor.tsx | 4 +++- src/editor-v2/containers/index.ts | 7 +++++++ src/editor-v2/hooks/index.ts | 2 ++ src/editor-v2/hooks/useEditorTabs.tsx | 4 ++-- src/editor-v2/index.ts | 5 ++++- src/editor-v2/utils/index.ts | 2 ++ 10 files changed, 27 insertions(+), 13 deletions(-) rename src/editor-v2/containers/{BlockConfig/BlockConfig.scss => BlockConfigForm/BlockConfigForm.scss} (93%) rename src/editor-v2/containers/{BlockConfig/BlockConfig.tsx => BlockConfigForm/BlockConfigForm.tsx} (87%) create mode 100644 src/editor-v2/containers/index.ts create mode 100644 src/editor-v2/hooks/index.ts diff --git a/playground/src/app/page.tsx b/playground/src/app/page.tsx index 854c352b7..d3ff81a73 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/app/page.tsx @@ -4,8 +4,6 @@ import block from 'bem-cn-lite'; import * as React from 'react'; import {Editor} from '../../../src/editor-v2'; -import Source from '../../../src/editor-v2/containers/Source/Source'; -import ViewSwitches from '../../../src/editor-v2/containers/ViewSwitches/ViewSwitches'; import C9RComponent from './components/C9RComponent'; import './page.scss'; @@ -24,7 +22,6 @@ const COMPONENTS_CONFIG = { component: Test, }, ], - rightTop: [Source, ViewSwitches], }; export default function Home() { diff --git a/src/common/postMessage.ts b/src/common/postMessage.ts index 8faadd6fd..54fe90f93 100644 --- a/src/common/postMessage.ts +++ b/src/common/postMessage.ts @@ -39,5 +39,6 @@ export function usePostMessageAPIListener<K extends keyof EventMessageTypes>( ) { React.useEffect(() => { return listenPostMessageEvents(action, callback); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [...deps]); } diff --git a/src/editor-v2/containers/BlockConfig/BlockConfig.scss b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss similarity index 93% rename from src/editor-v2/containers/BlockConfig/BlockConfig.scss rename to src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss index 306bf79ee..316de2fb2 100644 --- a/src/editor-v2/containers/BlockConfig/BlockConfig.scss +++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss @@ -1,7 +1,7 @@ @import '../../styles/variables.scss'; @import '../../styles/mixins.scss'; -$block: '.#{$ns}block-config'; +$block: '.#{$ns}block-config-form'; #{$block} { &__title { diff --git a/src/editor-v2/containers/BlockConfig/BlockConfig.tsx b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx similarity index 87% rename from src/editor-v2/containers/BlockConfig/BlockConfig.tsx rename to src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx index c0b753d07..18e14fdd2 100644 --- a/src/editor-v2/containers/BlockConfig/BlockConfig.tsx +++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx @@ -6,15 +6,15 @@ import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {generateChildrenPathFromArray} from '../../utils'; import {editorCn} from '../../utils/cn'; -import './BlockConfig.scss'; +import './BlockConfigForm.scss'; -const b = editorCn('block-config'); +const b = editorCn('block-config-form'); -interface BlockConfigProps { +interface BlockConfigFormProps { className?: string; } -const BlockConfig = ({className}: BlockConfigProps) => { +const BlockConfigForm = ({className}: BlockConfigFormProps) => { const {selectedBlock, content, blocks, subBlocks, updateField} = useMainEditorStore(); const currentBlockPath = selectedBlock ? generateChildrenPathFromArray(selectedBlock) : '[]'; @@ -48,4 +48,4 @@ const BlockConfig = ({className}: BlockConfigProps) => { ); }; -export default BlockConfig; +export default BlockConfigForm; diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index e8bef32f8..cc1d658ea 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -11,6 +11,8 @@ import {useEditorTabs} from '../../hooks/useEditorTabs'; import useMainEditorInitialize from '../../hooks/useMainEditorInitialize'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; import {editorCn} from '../../utils/cn'; +import Source from '../Source/Source'; +import ViewSwitches from '../ViewSwitches/ViewSwitches'; import './Editor.scss'; @@ -62,7 +64,7 @@ const EditorView = ({componentsConfig = {}}: EditorViewProps) => { right={ <Sidebar tabs={right} - top={componentsConfig.rightTop} + top={[...(componentsConfig.rightTop || []), Source, ViewSwitches]} defaultTab="block-config" /> } diff --git a/src/editor-v2/containers/index.ts b/src/editor-v2/containers/index.ts new file mode 100644 index 000000000..fc0241a72 --- /dev/null +++ b/src/editor-v2/containers/index.ts @@ -0,0 +1,7 @@ +export {Editor} from './Editor/Editor'; +export {default as BlockConfigForm} from './BlockConfigForm/BlockConfigForm'; +export {default as BlocksList} from './BlocksList/BlocksList'; +export {default as Source} from './Source/Source'; +export {default as SourceCode} from './SourceCode/SourceCode'; +export {default as Tree} from './Tree/Tree'; +export {default as ViewSwitches} from './ViewSwitches/ViewSwitches'; diff --git a/src/editor-v2/hooks/index.ts b/src/editor-v2/hooks/index.ts new file mode 100644 index 000000000..3b3541883 --- /dev/null +++ b/src/editor-v2/hooks/index.ts @@ -0,0 +1,2 @@ +export {usePostMessageEvents} from './usePostMessageEvents'; +export {useMainEditorStore} from './useMainEditorStore'; diff --git a/src/editor-v2/hooks/useEditorTabs.tsx b/src/editor-v2/hooks/useEditorTabs.tsx index 5db43263b..e1257a2c4 100644 --- a/src/editor-v2/hooks/useEditorTabs.tsx +++ b/src/editor-v2/hooks/useEditorTabs.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import Tabs, {TabsItemProps} from '../components/Tabs/Tabs'; -import BlockConfig from '../containers/BlockConfig/BlockConfig'; +import BlockConfigForm from '../containers/BlockConfigForm/BlockConfigForm'; import BlocksList from '../containers/BlocksList/BlocksList'; import GlobalConfig from '../containers/GlobalConfig/GlobalConfig'; import SourceCode from '../containers/SourceCode/SourceCode'; @@ -64,7 +64,7 @@ export const useEditorTabs = ({ { id: 'block-config', title: 'INPUTS', - component: BlockConfig, + component: BlockConfigForm, }, { id: 'source-code-yaml', diff --git a/src/editor-v2/index.ts b/src/editor-v2/index.ts index 4697a13c9..346f94855 100644 --- a/src/editor-v2/index.ts +++ b/src/editor-v2/index.ts @@ -1,2 +1,5 @@ -export {Editor} from './containers/Editor/Editor'; export * from '../common/types'; +export * from './containers'; +export * from './hooks'; +export * from './utils'; +export * from './constants'; diff --git a/src/editor-v2/utils/index.ts b/src/editor-v2/utils/index.ts index 8de140f3b..8573f80f8 100644 --- a/src/editor-v2/utils/index.ts +++ b/src/editor-v2/utils/index.ts @@ -1,6 +1,8 @@ import _ from 'lodash'; import {ConstructorBlock} from '../../models'; +export * from './code'; +export * from './cn'; export function insert<T>(arr: Array<T>, index: number, newItem: T) { return [...arr.slice(0, index), newItem, ...arr.slice(index)]; From e30e265fb7f33dcaa6314e777e38c3cd9b348de6 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 8 Apr 2025 11:52:53 +0300 Subject: [PATCH 50/84] fix: editor blocks styles --- src/editor-v2/components/Sidebar/Sidebar.scss | 3 +++ src/editor-v2/components/Tabs/Tabs.scss | 7 ++++++- src/editor-v2/containers/GlobalConfig/GlobalConfig.scss | 5 ++--- src/editor-v2/containers/ViewSwitches/ViewSwitches.scss | 4 ++++ src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss index 04cb06e72..df6eff3df 100644 --- a/src/editor-v2/components/Sidebar/Sidebar.scss +++ b/src/editor-v2/components/Sidebar/Sidebar.scss @@ -13,5 +13,8 @@ $block: '.#{$ns}sidebar'; &__block { width: 100%; border-bottom: 1px solid var(--g-color-line-generic); + overflow-x: scroll; + box-sizing: border-box; + flex: 0 0 auto; } } diff --git a/src/editor-v2/components/Tabs/Tabs.scss b/src/editor-v2/components/Tabs/Tabs.scss index a67b9899f..c9b6b3dd1 100644 --- a/src/editor-v2/components/Tabs/Tabs.scss +++ b/src/editor-v2/components/Tabs/Tabs.scss @@ -6,6 +6,8 @@ $block: '.#{$ns}tabs'; #{$block} { overflow: auto; width: 100%; + display: flex; + flex-direction: column; &__item { color: var(--g-color-text-hint); @@ -25,11 +27,14 @@ $block: '.#{$ns}tabs'; display: inline-flex; flex-direction: row; padding: 16px 4px; + flex: 0 0 auto; + box-sizing: border-box; + overflow-x: auto; } &__body { flex: 1; - border-right: 1px solid var(--g-color-line-generic); + min-height: 1px; overflow-y: auto; } } diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss index 45849ce92..a51239af7 100644 --- a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss +++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss @@ -4,11 +4,10 @@ $block: '.#{$ns}global-config'; #{$block} { - padding: 12px; - &__title { - margin-bottom: 16px; + padding: 12px; margin-top: 8px; @include text-subheader-3; + border-bottom: 1px solid var(--g-color-line-generic); } } diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss index 4844fad8f..a6e4d7a65 100644 --- a/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss +++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss @@ -12,5 +12,9 @@ $block: '.#{$ns}view-switches'; &__zoom { display: flex; gap: 4px; + + &-select { + min-width: 80px; + } } } diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index 78139a36a..51ce4471d 100644 --- a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -99,6 +99,7 @@ const ViewSwitches: React.FC = () => { </Button> <Select + className={b('zoom-select')} multiple={false} value={currentZoomValue} options={zoomOptions} From 88c7e978a400c90d255e5c3702be8ba8b310e217 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 8 Apr 2025 12:36:12 +0300 Subject: [PATCH 51/84] fix: title style editor source code --- src/editor-v2/containers/SourceCode/SourceCode.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor-v2/containers/SourceCode/SourceCode.scss b/src/editor-v2/containers/SourceCode/SourceCode.scss index 421536a9f..fe73fd428 100644 --- a/src/editor-v2/containers/SourceCode/SourceCode.scss +++ b/src/editor-v2/containers/SourceCode/SourceCode.scss @@ -15,7 +15,7 @@ $block: '.#{$ns}source-code'; font-weight: 500; font-size: 13px; line-height: 18px; - color: var(--g-color-private-black-300); + color: var(--g-color-text-secondary); } &__code { From 131779d154c273765a28c99bbad0cb58d3a04e9a Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 8 Apr 2025 12:40:55 +0300 Subject: [PATCH 52/84] fix: editor root styles --- src/editor-v2/styles/root.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss index 8334b15f5..ff241f9a0 100644 --- a/src/editor-v2/styles/root.scss +++ b/src/editor-v2/styles/root.scss @@ -1,6 +1,4 @@ .g-root { - --g-color-base-brand: var(--g-color-text-primary); - --g-color-base-brand-hover: var(--g-color-text-complementary); --g-color-base-selection: var(--g-color-private-black-200); --g-color-base-selection-hover: var(--g-color-private-black-300); --g-color-base-brand: var(--g-color-private-black-850-solid); From e262f09a04afc2d7da159c2e1b007c541068f17f Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:12:49 +0000 Subject: [PATCH 53/84] 6.3.2-alpha.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d2d187c7..383da8b5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.2", + "version": "6.3.2-alpha.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.2", + "version": "6.3.2-alpha.3", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 288158267..66fec5150 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.2", + "version": "6.3.2-alpha.3", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From 054b66cb2c21494c8bdb721131d473fdbc2b9937 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 9 Apr 2025 15:43:30 +0300 Subject: [PATCH 54/84] fix: brand colors --- src/editor-v2/containers/BigOverlay/BigOverlay.scss | 2 +- src/editor-v2/containers/Overlay/Overlay.scss | 8 ++++++-- src/editor-v2/containers/Overlay/Overlay.tsx | 4 ++-- src/editor-v2/styles/root.scss | 10 +++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/editor-v2/containers/BigOverlay/BigOverlay.scss b/src/editor-v2/containers/BigOverlay/BigOverlay.scss index bbb1e76e1..1d09dfdb1 100644 --- a/src/editor-v2/containers/BigOverlay/BigOverlay.scss +++ b/src/editor-v2/containers/BigOverlay/BigOverlay.scss @@ -12,7 +12,7 @@ $block: '.#{$ns}big-overlay'; pointer-events: none; overflow: hidden; - $border: 3px var(--g-color-line-brand) solid; + $border: 3px var(--g-color-base-brand) solid; &__border { pointer-events: none; diff --git a/src/editor-v2/containers/Overlay/Overlay.scss b/src/editor-v2/containers/Overlay/Overlay.scss index e32b9da48..ce0926932 100644 --- a/src/editor-v2/containers/Overlay/Overlay.scss +++ b/src/editor-v2/containers/Overlay/Overlay.scss @@ -5,8 +5,8 @@ $block: '.#{$ns}overlay'; #{$block} { $border-width: 3px; - $border-select: 5px var(--g-color-line-brand) solid; - $border: $border-width var(--g-color-line-brand) solid; + $border-select: 5px var(--g-color-base-brand) solid; + $border: $border-width var(--g-color-base-brand) solid; $border-radius: 24px; position: absolute; @@ -87,4 +87,8 @@ $block: '.#{$ns}overlay'; padding: 3px 4px; } } + + &__reorder-icon { + color: var(--g-color-base-brand); + } } diff --git a/src/editor-v2/containers/Overlay/Overlay.tsx b/src/editor-v2/containers/Overlay/Overlay.tsx index 86cbc6bef..f639d0e73 100644 --- a/src/editor-v2/containers/Overlay/Overlay.tsx +++ b/src/editor-v2/containers/Overlay/Overlay.tsx @@ -113,7 +113,7 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { <div className={b('actions')}> <div className={b('actions-box', {reorder: true})}> <Button view="flat" size={'m'} onClick={handleMoveUp}> - <Icon data={ChevronUp} size={18} /> + <Icon className={b('reorder-icon')} data={ChevronUp} size={18} /> </Button> </div> <div className={b('actions-box', {main: true})}> @@ -136,7 +136,7 @@ const Overlay = ({className, canvasElement}: OverlayProps) => { </div> <div className={b('actions-box', {reorder: true})}> <Button view="flat" size={'m'} onClick={handleMoveDown}> - <Icon data={ChevronDown} size={18} /> + <Icon className={b('reorder-icon')} data={ChevronDown} size={18} /> </Button> </div> </div> diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss index ff241f9a0..4eeff88b0 100644 --- a/src/editor-v2/styles/root.scss +++ b/src/editor-v2/styles/root.scss @@ -1,9 +1,13 @@ .g-root { --g-color-base-selection: var(--g-color-private-black-200); --g-color-base-selection-hover: var(--g-color-private-black-300); - --g-color-base-brand: var(--g-color-private-black-850-solid); - --g-color-base-brand-hover: var(--g-color-private-black-700-solid); + --g-color-base-brand: rgb(38, 38, 38); // --g-color-private-black-850-solid only light theme + --g-color-base-brand-hover: rgb( + 76, + 76, + 76 + ); // --g-color-private-black-700-solid only light theme --g-color-text-brand-contrast: var(--g-color-text-light-primary); - --g-color-text-brand-heavy: var(--g-color-private-black-700-solid); + --g-color-text-brand-heavy: rgb(76, 76, 76); --g-color-line-brand: var(--g-color-text-primary); } From 6648ccd4c9462cd8aa1b808f12a186856762664c Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 9 Apr 2025 17:18:23 +0300 Subject: [PATCH 55/84] fix: editor zoom device centering --- .../containers/MiddleScreen/MiddleScreen.scss | 12 +++++++++++- .../containers/MiddleScreen/MiddleScreen.tsx | 7 ++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss index 2761a12d7..0944756ac 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss @@ -24,13 +24,23 @@ $block: '.#{$ns}middle-screen'; width: 100%; height: 100%; margin: auto; + display: flex; + justify-content: center; + align-items: flex-start; + overflow: hidden; } &__canvas { - transform-origin: left top; + transform-origin: center top; overflow-y: scroll; position: relative; height: 100%; + max-width: 100%; + } + + &__iframe { + display: block; + margin: 0 auto; } &__overlay { diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 198c759ef..5e6c8be06 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -45,14 +45,15 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { </div> ) : null} <div className={b('content')}> - <div className={b('wrapper')} style={{width: deviceWidth}}> + <div className={b('wrapper')}> <div ref={setCanvasRef} className={b('canvas', {hidden: !initialized})} style={{ transform: `scale(${zoom}%)`, height: `${(100 / zoom) * 100}%`, - width: `${(100 / zoom) * 100}%`, + width: deviceWidth, + maxWidth: `${(100 / zoom) * 100}%`, }} > <iframe @@ -60,7 +61,7 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { className={b('iframe')} src={url} height={`${height}px`} - width="100%" + width={deviceWidth} frameBorder="0" title="Page Constructor Iframe" /> From 3dcac2c966aae952f958e527d0bcb15496fa6fb7 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 9 Apr 2025 18:04:20 +0300 Subject: [PATCH 56/84] fix: restore editor refresh fields --- .../DynamicForm/FieldBase/FieldBase.scss | 15 +++++++----- .../DynamicForm/FieldBase/FieldBase.tsx | 23 ++++++++++++++++--- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss index 50418c0c1..df3effd9d 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -5,17 +5,16 @@ $block: '.#{$ns}field-base'; #{$block} { $class: &; - padding: 16px 12px; + padding: 16px 12px 16px 0; border-bottom: 1px solid var(--g-color-line-generic); position: relative; display: flex; flex-direction: row; align-items: flex-start; - gap: 8px; &:hover { - & > #{$class}__top > #{$class}__button { + & > #{$class}__top #{$class}__title > #{$class}__button { opacity: 1; } } @@ -28,6 +27,7 @@ $block: '.#{$ns}field-base'; width: 100%; flex-basis: initial; } + & > #{$block}__children { width: 100%; } @@ -37,12 +37,13 @@ $block: '.#{$ns}field-base'; display: flex; align-items: center; gap: 4px; - flex: 0 0 64px; - word-break: break-all; + flex: 0 0 84px; + word-break: break-word; } &__children { flex: 2 1 auto; + margin-left: 12px; } &__foldable { @@ -68,7 +69,9 @@ $block: '.#{$ns}field-base'; &__title { @include text-body-2; - margin: 5px 0; + margin: 5px 16px 5px 0; + display: flex; + flex-direction: row; &_size { &_s { diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx index edfb58f07..742636117 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.tsx @@ -1,4 +1,5 @@ -import {ArrowToggle} from '@gravity-ui/uikit'; +import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit'; +import {ArrowRotateLeft} from '@gravity-ui/icons'; import _ from 'lodash'; import * as React from 'react'; @@ -25,13 +26,29 @@ const FieldBase: React.FC<FieldBaseProps> = ({ textSize = 's', children, expandable = false, + onRefresh, }) => { const [showChildren, setShowChildren] = React.useState(!expandable); const titleComponent = React.useMemo(() => { if (title) { const defaultTitle = ( - <div className={b('title', {size: textSize})}>{_.capitalize(title)}</div> + <div className={b('title', {size: textSize})}> + {onRefresh && ( + <Button + className={b('button')} + onClick={(e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation(); + onRefresh(undefined); + }} + view={'flat'} + size={'xs'} + > + <Icon data={ArrowRotateLeft} size={14} /> + </Button> + )} + <span>{_.capitalize(title)}</span> + </div> ); if (expandable) { @@ -51,7 +68,7 @@ const FieldBase: React.FC<FieldBaseProps> = ({ } return null; - }, [expandable, showChildren, textSize, title]); + }, [expandable, showChildren, textSize, title, onRefresh]); return ( <div className={b({expandable}, className)}> From bd41fa8821bc29802da00c7f090f5afe159c89c0 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 9 Apr 2025 18:05:00 +0300 Subject: [PATCH 57/84] fix: paddings --- .../containers/BlockConfigForm/BlockConfigForm.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss index 316de2fb2..c21fc9318 100644 --- a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss +++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss @@ -3,19 +3,21 @@ $block: '.#{$ns}block-config-form'; +$padding: 12px; + #{$block} { &__title { @include text-subheader-3; - padding: 12px 16px; + padding: $padding; border-bottom: 1px solid var(--g-color-line-generic); } &__form { - padding: 12px 0; + padding: $padding 0; } &__empty { - padding: 12px 16px; + padding: $padding; display: flex; justify-content: center; align-items: center; From 6a321d56ae0c129b88f9ce220c9d7276d0ca3de1 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 9 Apr 2025 20:22:52 +0300 Subject: [PATCH 58/84] fix: root css --- src/editor-v2/styles/root.scss | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss index 4eeff88b0..c25a7298a 100644 --- a/src/editor-v2/styles/root.scss +++ b/src/editor-v2/styles/root.scss @@ -1,12 +1,8 @@ .g-root { --g-color-base-selection: var(--g-color-private-black-200); --g-color-base-selection-hover: var(--g-color-private-black-300); - --g-color-base-brand: rgb(38, 38, 38); // --g-color-private-black-850-solid only light theme - --g-color-base-brand-hover: rgb( - 76, - 76, - 76 - ); // --g-color-private-black-700-solid only light theme + --g-color-base-brand: rgb(38, 38, 38); // --g-color-private-black-850-solid light theme only + --g-color-base-brand-hover: rgb(76, 76, 76); // --g-color-private-black-700-solid --g-color-text-brand-contrast: var(--g-color-text-light-primary); --g-color-text-brand-heavy: rgb(76, 76, 76); --g-color-line-brand: var(--g-color-text-primary); From 55c558b5dbba8f951bc0ad4b5cef837679c1f7d8 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 15 Apr 2025 21:58:28 +0300 Subject: [PATCH 59/84] fix: editor add initialContent --- src/editor-v2/containers/Editor/Editor.tsx | 5 +++-- src/editor-v2/hooks/useMainEditorInitialize.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx index cc1d658ea..edc2c39ec 100644 --- a/src/editor-v2/containers/Editor/Editor.tsx +++ b/src/editor-v2/containers/Editor/Editor.tsx @@ -26,6 +26,7 @@ interface SidebarTabComponent { interface EditorViewProps { onUpdate?: (pageContent: PageContentWithNavigation) => void; initialUrl: string; + initialContent?: PageContentWithNavigation; disableUrlField?: boolean; componentsConfig?: { middleTop?: React.ElementType; @@ -36,11 +37,11 @@ interface EditorViewProps { }; } -const EditorView = ({componentsConfig = {}}: EditorViewProps) => { +const EditorView = ({componentsConfig = {}, initialContent}: EditorViewProps) => { const store = useMainEditorStore(); const {manipulateOverlayMode, disableMode} = store; - useMainEditorInitialize(); + useMainEditorInitialize(initialContent); // Disable insert mode on any MouseUp event // Maybe should be attached to body diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts index 2326b95ec..197ed7dd4 100644 --- a/src/editor-v2/hooks/useMainEditorInitialize.ts +++ b/src/editor-v2/hooks/useMainEditorInitialize.ts @@ -1,9 +1,11 @@ +import * as React from 'react'; import {usePostMessageAPIListener} from '../../common/postMessage'; +import {PageContentWithNavigation} from '../../models'; import {useMainEditorStore} from './useMainEditorStore'; import {usePostMessageEvents} from './usePostMessageEvents'; -const useMainEditorInitialize = () => { +const useMainEditorInitialize = (initialContent?: PageContentWithNavigation) => { const {requestPostMessage} = usePostMessageEvents(); const { initialize, @@ -22,11 +24,20 @@ const useMainEditorInitialize = () => { () => { initialize(); requestPostMessage('GET_SUPPORTED_BLOCKS', {}); - requestPostMessage('GET_INITIAL_CONTENT', {}); + + if (!initialContent) { + requestPostMessage('GET_INITIAL_CONTENT', {}); + } }, [requestPostMessage], ); + React.useEffect(() => { + if (initialContent) { + setContent(initialContent); + } + }, [initialContent]); + usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { setContent(data); }); From 74920030c5169d72c8e80ea4efe99493dc0a90a9 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:29:55 +0000 Subject: [PATCH 60/84] 6.3.2-alpha.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 383da8b5c..9e50de12e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.3", + "version": "6.3.2-alpha.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.3", + "version": "6.3.2-alpha.4", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 66fec5150..73222fb62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.3", + "version": "6.3.2-alpha.4", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From 551f0a281a15eb4713a6bddb4a16222962b49c7f Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Thu, 17 Apr 2025 01:33:23 +0300 Subject: [PATCH 61/84] fix: eslint disable --- src/editor-v2/hooks/useMainEditorInitialize.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts index 197ed7dd4..ae00f0123 100644 --- a/src/editor-v2/hooks/useMainEditorInitialize.ts +++ b/src/editor-v2/hooks/useMainEditorInitialize.ts @@ -36,6 +36,7 @@ const useMainEditorInitialize = (initialContent?: PageContentWithNavigation) => if (initialContent) { setContent(initialContent); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialContent]); usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => { From 06aeb4d9eeceff5fc21e4e3c397c9f22462bb0e1 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:37:39 +0000 Subject: [PATCH 62/84] 6.3.2-alpha.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e50de12e..ebea27084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.4", + "version": "6.3.2-alpha.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.4", + "version": "6.3.2-alpha.5", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 73222fb62..21554381e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.4", + "version": "6.3.2-alpha.5", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From 558bbf84b9c8cc705a34a1c2404077a2b017209e Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 28 Apr 2025 14:10:25 +0300 Subject: [PATCH 63/84] fix: json/yaml tab for selected block --- .../containers/SourceCode/SourceCode.tsx | 38 +++++++++++++++---- src/editor-v2/hooks/useEditorTabs.tsx | 8 +++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/editor-v2/containers/SourceCode/SourceCode.tsx b/src/editor-v2/containers/SourceCode/SourceCode.tsx index 538c2ba2e..aaa4d2268 100644 --- a/src/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/src/editor-v2/containers/SourceCode/SourceCode.tsx @@ -1,10 +1,12 @@ import {ArrowDownToSquare} from '@gravity-ui/icons'; import {Button, ClipboardButton, Icon} from '@gravity-ui/uikit'; import yaml from 'js-yaml'; +import _ from 'lodash'; import * as React from 'react'; import {PageContentWithNavigation} from '../../../models'; import {useMainEditorStore} from '../../hooks/useMainEditorStore'; +import {generateChildrenPathFromArray} from '../../utils'; import {editorCn} from '../../utils/cn'; import './SourceCode.scss'; @@ -15,36 +17,58 @@ const b = editorCn('source-code'); interface SourceCodeProps { className?: string; format: 'yaml' | 'json'; + showSelectedBlockOnly?: boolean; } -const SourceCode = ({className, format}: SourceCodeProps) => { - const {content, setContent} = useMainEditorStore(); +const SourceCode = ({className, format, showSelectedBlockOnly = false}: SourceCodeProps) => { + const {content, setContent, selectedBlock} = useMainEditorStore(); const [isOpen, setIsOpen] = React.useState(false); const handleUpdate = (tempConfig: string) => { - let object: PageContentWithNavigation | undefined; + let object: any; try { if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) { object = JSON.parse(tempConfig); } else { - object = yaml.load(tempConfig) as PageContentWithNavigation; + object = yaml.load(tempConfig); } } catch { // eslint-disable-next-line no-console console.error('JSON.parse failed'); + return; } - if (object) { - setContent(object); + if (showSelectedBlockOnly && selectedBlock) { + // Update only the selected block + const currentBlockPath = generateChildrenPathFromArray(selectedBlock); + const newContent = _.cloneDeep(content); + _.set(newContent.blocks, currentBlockPath, object); + setContent(newContent); + } else if (object) { + // Update the entire content + setContent(object as PageContentWithNavigation); } setIsOpen(false); }; const textContent = React.useMemo(() => { + if (showSelectedBlockOnly && selectedBlock) { + const currentBlockPath = generateChildrenPathFromArray(selectedBlock); + const currentConfig = _.get(content.blocks, currentBlockPath || ''); + + if (currentConfig) { + return format === 'yaml' + ? yaml.dump(currentConfig) + : JSON.stringify(currentConfig, null, 2); + } + + return 'No block selected'; + } + return format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2); - }, [format, content]); + }, [format, content, showSelectedBlockOnly, selectedBlock]); return ( <div className={b(null, className)}> diff --git a/src/editor-v2/hooks/useEditorTabs.tsx b/src/editor-v2/hooks/useEditorTabs.tsx index e1257a2c4..7e5d1acbd 100644 --- a/src/editor-v2/hooks/useEditorTabs.tsx +++ b/src/editor-v2/hooks/useEditorTabs.tsx @@ -69,12 +69,16 @@ export const useEditorTabs = ({ { id: 'source-code-yaml', title: 'YAML', - component: () => <SourceCode format="yaml" />, + component: () => ( + <SourceCode format="yaml" showSelectedBlockOnly={true} /> + ), }, { id: 'source-code-json', title: 'JSON', - component: () => <SourceCode format="json" />, + component: () => ( + <SourceCode format="json" showSelectedBlockOnly={true} /> + ), }, ]} /> From 0b71a88f8d54aab4acad1476ac90cc1f264a2df0 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 28 Apr 2025 20:19:55 +0300 Subject: [PATCH 64/84] fix: add anyof case for editorv2 --- src/common/types/forms.ts | 13 +++ .../components/DynamicForm/DynamicForm.tsx | 16 +++- .../DynamicForm/Fields/AnyOf/AnyOf.scss | 14 +++ .../DynamicForm/Fields/AnyOf/AnyOf.tsx | 92 +++++++++++++++++++ src/utils/form-generator.ts | 28 ++++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.scss create mode 100644 src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.tsx diff --git a/src/common/types/forms.ts b/src/common/types/forms.ts index f5a53c37d..31716225c 100644 --- a/src/common/types/forms.ts +++ b/src/common/types/forms.ts @@ -104,6 +104,18 @@ export interface OneOfInput { }[]; } +export interface AnyOfInput { + type: 'anyOf'; + name: string; + key?: string; + title: string; + options: { + value: string; + title: string; + properties: Array<ConfigInput>; + }[]; +} + export interface GeneralProps { showIf?: string; } @@ -119,5 +131,6 @@ export type ConfigInput = ( | ArrayTextInput | ArrayObjectInput | OneOfInput + | AnyOfInput ) & GeneralProps; diff --git a/src/editor-v2/components/DynamicForm/DynamicForm.tsx b/src/editor-v2/components/DynamicForm/DynamicForm.tsx index 9531c2181..853e6986e 100644 --- a/src/editor-v2/components/DynamicForm/DynamicForm.tsx +++ b/src/editor-v2/components/DynamicForm/DynamicForm.tsx @@ -9,6 +9,7 @@ import ArrayDynamicField from './Fields/Array/Array'; import BooleanDynamicField from './Fields/Boolean/Boolean'; import NumberDynamicField from './Fields/Number/Number'; import ObjectDynamicField from './Fields/Object/Object'; +import AnyOfDynamicField from './Fields/AnyOf/AnyOf'; import OneOfDynamicField from './Fields/OneOf/OneOf'; import SelectDynamicField from './Fields/Select/Select'; import TextDynamicField from './Fields/Text/Text'; @@ -183,6 +184,19 @@ const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) = /> ); } + case 'anyOf': { + if (!input || !('options' in input)) { + return null; + } + + return ( + <AnyOfDynamicField + inputConfig={input} + contentConfig={contentConfig} + onUpdate={onComplexDynamicFieldUpdate} + /> + ); + } default: { return <div>Ignore {JSON.stringify(input)}</div>; } @@ -192,7 +206,7 @@ const DynamicForm = ({blockConfig, onUpdate, contentConfig}: DynamicFormProps) = ); const sortedInputs = inputs.sort((x, y) => { - const nestingFieldTypes = ['object', 'array', 'oneOf']; + const nestingFieldTypes = ['object', 'array', 'oneOf', 'anyOf']; if (nestingFieldTypes.includes(x.type)) { return 1; } diff --git a/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.scss b/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.scss new file mode 100644 index 000000000..af2528c79 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.scss @@ -0,0 +1,14 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; + +$block: '.#{$ns}anyof-dynamic-field'; + +#{$block} { + &__radio { + margin: 16px 12px; + } + + &__select { + min-width: 80px; + } +} diff --git a/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.tsx b/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.tsx new file mode 100644 index 000000000..7ef0e2d99 --- /dev/null +++ b/src/editor-v2/components/DynamicForm/Fields/AnyOf/AnyOf.tsx @@ -0,0 +1,92 @@ +import {Card, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; +import * as React from 'react'; + +import {AnyOfInput, DynamicFormValue} from '../../../../../common/types'; +import {editorCn} from '../../../../utils/cn'; +import DynamicForm from '../../DynamicForm'; +import FieldBase from '../../FieldBase/FieldBase'; + +import './AnyOf.scss'; + +const b = editorCn('anyof-dynamic-field'); + +interface AnyOfDynamicFieldProps { + contentConfig: DynamicFormValue; + onUpdate: (key: string, value: DynamicFormValue) => void; + inputConfig: AnyOfInput; + className?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const getAnyOfContentConfig = (contentConfig: any, name: string) => { + if (name) { + return contentConfig ? contentConfig[name] : {}; + } + return contentConfig; +}; + +const AnyOfDynamicField = ({ + contentConfig, + onUpdate, + className, + inputConfig, +}: AnyOfDynamicFieldProps) => { + const defaultValue = inputConfig.options[0].value; + + const [anyOfMetaValue, setAnyOfMetaValue] = React.useState(defaultValue); + + const anyOfContentConfig = getAnyOfContentConfig(contentConfig, inputConfig.name); + const anyOfChosenOption = React.useMemo( + () => + inputConfig.options.find( + ({value: foundAnyOfValue}) => foundAnyOfValue === anyOfMetaValue, + ), + [inputConfig.options, anyOfMetaValue], + ); + + const onUpdateAnyOf = React.useCallback((value: string) => { + setAnyOfMetaValue(value); + }, []); + + return ( + <FieldBase + title={inputConfig.title} + className={b(null, className)} + onRefresh={(value) => onUpdate('', value)} + expandable + > + <Card> + {inputConfig.options.length < 4 ? ( + <SegmentedRadioGroup + className={b('radio')} + options={inputConfig.options.map((option) => ({ + content: option.title, + value: option.value, + }))} + value={anyOfMetaValue} + onUpdate={onUpdateAnyOf} + /> + ) : ( + <Select + options={inputConfig.options.map((option) => ({ + content: option.title, + value: option.value, + }))} + value={anyOfMetaValue ? [anyOfMetaValue] : []} + onUpdate={([selectValue]) => onUpdateAnyOf(selectValue)} + className={b('select')} + /> + )} + {anyOfChosenOption && ( + <DynamicForm + blockConfig={anyOfChosenOption.properties} + contentConfig={anyOfContentConfig} + onUpdate={onUpdate} + /> + )} + </Card> + </FieldBase> + ); +}; + +export default AnyOfDynamicField; diff --git a/src/utils/form-generator.ts b/src/utils/form-generator.ts index 9c8af8ca0..c34993f8b 100644 --- a/src/utils/form-generator.ts +++ b/src/utils/form-generator.ts @@ -68,6 +68,34 @@ export const generateSingleEntity = (key: string, schema: JSONSchemaType<{}>) => }; } + if (schema.anyOf) { + return { + type: 'anyOf', + name: key, + title: key, + options: schema.anyOf.map((item: JSONSchemaType<{}>) => { + let properties; + if (item.properties) { + properties = generateFromAJV(item); + } else { + properties = [ + generateSingleEntity('', { + ...item, + name: '', + title: item.optionName, + }), + ]; + } + + return { + value: item.optionName, + title: item.optionName, + properties: properties, + }; + }), + }; + } + switch (type) { case 'string': { if (schema.inputType === 'textarea') { From 5d175793c383c9f8335babc89147d40837ec3404 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 28 Apr 2025 20:38:59 +0300 Subject: [PATCH 65/84] fix: navigation item props --- src/navigation/schema.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/navigation/schema.ts b/src/navigation/schema.ts index 5c809d633..7c2b80f1b 100644 --- a/src/navigation/schema.ts +++ b/src/navigation/schema.ts @@ -86,9 +86,18 @@ const NavigationDropdownItemProps = { const NavigationItemProps = { oneOf: [ - filteredArray(NavigationLinkItemProps), - filteredArray(NavigationButtonItemProps), - filteredArray(NavigationDropdownItemProps), + { + optionName: 'link', + ...filteredArray(NavigationLinkItemProps), + }, + { + optionName: 'button', + ...filteredArray(NavigationButtonItemProps), + }, + { + optionName: 'dropdown', + ...filteredArray(NavigationDropdownItemProps), + }, ], }; From 1d308bc061cd70bb860229bfd5e33fda275e8c7f Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 29 Apr 2025 19:19:55 +0300 Subject: [PATCH 66/84] feat: add previewMode for editor --- src/common/store.ts | 2 + .../PageConstructor/PageConstructor.tsx | 17 ++++++- .../containers/MiddleScreen/MiddleScreen.scss | 34 +++++++++++++ .../containers/MiddleScreen/MiddleScreen.tsx | 50 +++++++++++++------ .../containers/ViewSwitches/ViewSwitches.tsx | 22 ++++++-- src/editor-v2/store.ts | 4 ++ src/hooks/usePCEditorBlockMouseEvents.ts | 1 + 7 files changed, 111 insertions(+), 19 deletions(-) diff --git a/src/common/store.ts b/src/common/store.ts index 1b8e37a70..68e978a5d 100644 --- a/src/common/store.ts +++ b/src/common/store.ts @@ -13,6 +13,7 @@ export interface EditorState { manipulateOverlayMode: 'insert' | 'reorder' | false; selectedBlock: number[] | null; initialized: boolean; + isPreviewMode: boolean; content: PageContentWithNavigation; blocks: Array<ItemConfig>; @@ -30,6 +31,7 @@ export const initialStore: EditorState = { manipulateOverlayMode: false, selectedBlock: null, initialized: false, + isPreviewMode: false, content: {blocks: []}, blocks: [], subBlocks: [], diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 6d308eae7..89c8d9fd6 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -71,7 +71,7 @@ export const Constructor = (props: PageConstructorProps) => { const theme = useTheme(); const store = usePCEditorStore(); - const {initialized} = store; + const {initialized, isPreviewMode} = store; usePCEditorInitializeEvents({initialContent: {blocks, background, navigation}, setContent}); @@ -108,6 +108,21 @@ export const Constructor = (props: PageConstructorProps) => { const restBlocks = content.blocks; const themedBackground = getThemedValue(content.background, theme); + // disable click events + React.useEffect(() => { + if (initialized && !isPreviewMode) { + const handler: React.EventHandler<any> = (e) => { + e?.preventDefault(); + const blockElement = e.target.closest('[data-editor-item]'); + blockElement.click(e); + }; + document.body.addEventListener('click', handler); + return () => { + document.body.removeEventListener('click', handler); + }; + } + }, [initialized, isPreviewMode]); + return ( <InnerContext.Provider value={context}> <RootCn className={b('', {['with-editor']: initialized})}> diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss index 0944756ac..55a03886f 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss @@ -18,6 +18,16 @@ $block: '.#{$ns}middle-screen'; overflow-y: auto; // height: 100%; background-color: var(--g-color-text-secondary); + + &_fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1000; + background-color: #000; + } } &__wrapper { @@ -36,11 +46,35 @@ $block: '.#{$ns}middle-screen'; position: relative; height: 100%; max-width: 100%; + + &_fullscreen { + overflow: hidden; + width: 100%; + height: 100%; + max-width: 100%; + } } &__iframe { display: block; margin: 0 auto; + + &_fullscreen { + width: 100%; + height: 100%; + } + } + + &__exit-preview { + position: fixed; + opacity: 0.5; + top: 16px; + right: 16px; + z-index: 1001; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; } &__overlay { diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx index 5e6c8be06..75f7a08b0 100644 --- a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx +++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx @@ -1,4 +1,5 @@ -import {Loader} from '@gravity-ui/uikit'; +import {Xmark} from '@gravity-ui/icons'; +import {Button, Icon, Loader} from '@gravity-ui/uikit'; import * as React from 'react'; import {usePostMessageAPIListener} from '../../../common/postMessage'; @@ -17,11 +18,21 @@ interface MiddleScreenProps { } const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { - const {zoom, initialized, deviceWidth} = useMainEditorStore(); + const {zoom, initialized, deviceWidth, isPreviewMode, togglePreviewMode} = useMainEditorStore(); const {url, setIframeElement} = React.useContext(IframeContext); const [canvasRef, setCanvasRef] = React.useState<HTMLDivElement | null>(null); const [height, setHeight] = React.useState(0); + const canvasStyle = React.useMemo( + () => ({ + transform: isPreviewMode ? 'none' : `scale(${zoom}%)`, + height: isPreviewMode ? '100%' : `${(100 / zoom) * 100}%`, + width: isPreviewMode ? '100%' : deviceWidth, + maxWidth: isPreviewMode ? '100%' : `${(100 / zoom) * 100}%`, + }), + [isPreviewMode, zoom, deviceWidth], + ); + const onResize = React.useCallback( (newHeight: number) => { setHeight(newHeight + 500); @@ -39,33 +50,42 @@ const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => { return ( <div className={b(null, className)}> - {CustomTop ? ( + {CustomTop && !isPreviewMode ? ( <div className={b('topbar')}> <CustomTop /> </div> ) : null} - <div className={b('content')}> + <div className={b('content', {fullscreen: isPreviewMode})}> <div className={b('wrapper')}> <div ref={setCanvasRef} - className={b('canvas', {hidden: !initialized})} - style={{ - transform: `scale(${zoom}%)`, - height: `${(100 / zoom) * 100}%`, - width: deviceWidth, - maxWidth: `${(100 / zoom) * 100}%`, - }} + className={b('canvas', {hidden: !initialized, fullscreen: isPreviewMode})} + style={canvasStyle} > <iframe ref={(instance) => instance && setIframeElement(instance)} - className={b('iframe')} + className={b('iframe', {fullscreen: isPreviewMode})} src={url} - height={`${height}px`} - width={deviceWidth} + height={isPreviewMode ? '100%' : `${height}px`} + width={isPreviewMode ? '100%' : deviceWidth} frameBorder="0" title="Page Constructor Iframe" /> - <Overlay className={b('overlay')} canvasElement={canvasRef} /> + {!isPreviewMode && ( + <Overlay className={b('overlay')} canvasElement={canvasRef} /> + )} + {isPreviewMode && ( + <Button + view="action" + className={b('exit-preview')} + onClick={togglePreviewMode} + aria-label="Exit preview mode" + title="Exit preview mode" + size="l" + > + <Icon size={24} data={Xmark} /> + </Button> + )} {!initialized && ( <div className={b('loading')}> <Loader size={'l'} /> diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index 51ce4471d..a1fb322e4 100644 --- a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Display, Minus, Plus, Smartphone} from '@gravity-ui/icons'; +import {Display, Eye, Minus, Plus, Smartphone} from '@gravity-ui/icons'; import {Button, Icon, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import {ZOOM_STEPS} from '../../constants'; @@ -47,8 +47,15 @@ const DEVICE_OPTIONS: DeviceOption[] = [ ]; const ViewSwitches: React.FC = () => { - const {zoom, setZoom, decreaseZoom, increaseZoom, deviceWidth, setDeviceWidth} = - useMainEditorStore(); + const { + zoom, + setZoom, + decreaseZoom, + increaseZoom, + deviceWidth, + setDeviceWidth, + togglePreviewMode, + } = useMainEditorStore(); // Memoize zoom options to prevent unnecessary recalculations const zoomOptions = React.useMemo( @@ -88,6 +95,15 @@ const ViewSwitches: React.FC = () => { ))} </SegmentedRadioGroup> + <Button + view="flat" + onClick={togglePreviewMode} + aria-label="Switch to preview mode" + title="Switch to preview mode" + > + <Icon data={Eye} /> + </Button> + <div className={b('zoom')} role="group" aria-label="Zoom controls"> <Button view="flat" diff --git a/src/editor-v2/store.ts b/src/editor-v2/store.ts index 54edef1dd..eef9f2ba1 100644 --- a/src/editor-v2/store.ts +++ b/src/editor-v2/store.ts @@ -25,6 +25,7 @@ export interface EditorMethods { setZoom(zoom: number): void; increaseZoom(): void; decreaseZoom(): void; + togglePreviewMode(): void; setConfig(data: Pick<EditorState, 'blocks' | 'subBlocks' | 'global'>): void; setContent(data: PageContentWithNavigation): void; insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void; @@ -79,6 +80,9 @@ export const createEditorStore = initializeStore<EditorState, EditorMethods>( } } }, + togglePreviewMode() { + set((state) => ({...state, isPreviewMode: !state.isPreviewMode})); + }, setConfig(data) { set((state) => ({...state, ...data})); }, diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts index 8dea23f82..1376145a4 100644 --- a/src/hooks/usePCEditorBlockMouseEvents.ts +++ b/src/hooks/usePCEditorBlockMouseEvents.ts @@ -59,6 +59,7 @@ const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement onMouseMove, onMouseUp, onMouseLeave, + 'data-editor-item': true, }; }; From d27806873f914cb1518dc991d651a48fe6a7f4cb Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 29 Apr 2025 19:40:30 +0300 Subject: [PATCH 67/84] fix: linter --- src/containers/PageConstructor/PageConstructor.tsx | 1 + src/editor-v2/containers/SourceCode/SourceCode.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 89c8d9fd6..533af0817 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -121,6 +121,7 @@ export const Constructor = (props: PageConstructorProps) => { document.body.removeEventListener('click', handler); }; } + return; }, [initialized, isPreviewMode]); return ( diff --git a/src/editor-v2/containers/SourceCode/SourceCode.tsx b/src/editor-v2/containers/SourceCode/SourceCode.tsx index aaa4d2268..4abb10173 100644 --- a/src/editor-v2/containers/SourceCode/SourceCode.tsx +++ b/src/editor-v2/containers/SourceCode/SourceCode.tsx @@ -25,6 +25,7 @@ const SourceCode = ({className, format, showSelectedBlockOnly = false}: SourceCo const [isOpen, setIsOpen] = React.useState(false); const handleUpdate = (tempConfig: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let object: any; try { From 4dcf19f735b5d78af48f92d7c0b69fa59555fc55 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Tue, 29 Apr 2025 20:02:00 +0300 Subject: [PATCH 68/84] fix: linter2 --- .../PageConstructor/PageConstructor.tsx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx index 533af0817..988fd5754 100644 --- a/src/containers/PageConstructor/PageConstructor.tsx +++ b/src/containers/PageConstructor/PageConstructor.tsx @@ -110,18 +110,21 @@ export const Constructor = (props: PageConstructorProps) => { // disable click events React.useEffect(() => { - if (initialized && !isPreviewMode) { - const handler: React.EventHandler<any> = (e) => { - e?.preventDefault(); - const blockElement = e.target.closest('[data-editor-item]'); - blockElement.click(e); - }; - document.body.addEventListener('click', handler); - return () => { - document.body.removeEventListener('click', handler); - }; + if (!initialized || isPreviewMode) { + return; } - return; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handler: React.EventHandler<any> = (e) => { + e?.preventDefault(); + const blockElement = e.target.closest('[data-editor-item]'); + blockElement.click(e); + }; + document.body.addEventListener('click', handler); + + // eslint-disable-next-line consistent-return + return () => { + document.body.removeEventListener('click', handler); + }; }, [initialized, isPreviewMode]); return ( From 41f8b10903c752231cda598b92d0bcaac57fe85b Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Mon, 5 May 2025 10:46:39 +0300 Subject: [PATCH 69/84] fix: filter editor blocks --- src/constructor-items.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/constructor-items.ts b/src/constructor-items.ts index a49cdaa03..c255204e0 100644 --- a/src/constructor-items.ts +++ b/src/constructor-items.ts @@ -29,7 +29,7 @@ import CardLayoutBlockConfig from './blocks/CardLayout'; import CompaniesBlockConfig from './blocks/Companies'; import ContentLayoutBlockConfig from './blocks/ContentLayout'; import ExtendedFeaturesBlockConfig from './blocks/ExtendedFeatures'; -import FilterBlockConfig from './blocks/FilterBlock'; +// import FilterBlockConfig from './blocks/FilterBlock'; import FormBlockConfig from './blocks/Form'; import HeaderBlockConfig from './blocks/Header'; import HeaderSliderBlockConfig from './blocks/HeaderSlider'; @@ -71,7 +71,6 @@ import ImageCardConfig from './sub-blocks/ImageCard'; import LayoutItemConfig from './sub-blocks/LayoutItem'; import MediaCardConfig from './sub-blocks/MediaCard'; import PriceCardConfig from './sub-blocks/PriceCard'; -import PriceDetailedConfig from './sub-blocks/PriceDetailed'; import QuoteConfig from './sub-blocks/Quote'; /** @@ -154,13 +153,13 @@ export const blockDataMap: Record<string, BlockData> = { [BlockType.ContentLayoutBlock]: ContentLayoutBlockConfig, [BlockType.ShareBlock]: ShareBlockConfig, [BlockType.MapBlock]: MapBlockConfig, - [BlockType.FilterBlock]: FilterBlockConfig, + // TODO: fix items prop for editor compatibility + // [BlockType.FilterBlock]: FilterBlockConfig, [BlockType.FormBlock]: FormBlockConfig, [BlockType.TestEditorBlock]: TestEditorBlockConfig, [BlockType.SliderBlock]: SliderBlockConfig, [SubBlockType.Divider]: DividerConfig, - [SubBlockType.PriceDetailed]: PriceDetailedConfig, [SubBlockType.MediaCard]: MediaCardConfig, [SubBlockType.BannerCard]: BannerCardConfig, [SubBlockType.LayoutItem]: LayoutItemConfig, From f6463b0c0417ae7fb06fa8b473375cc3e586ac26 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Wed, 7 May 2025 12:00:19 +0000 Subject: [PATCH 70/84] 6.3.2-alpha.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebea27084..e9e457de1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.5", + "version": "6.3.2-alpha.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.5", + "version": "6.3.2-alpha.6", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 21554381e..9e1d09a48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.5", + "version": "6.3.2-alpha.6", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From dea8b2b754da9f0694d003ed1a7349fdfa1cdda5 Mon Sep 17 00:00:00 2001 From: qradle <qradle@yandex-team.ru> Date: Wed, 14 May 2025 10:55:31 +0300 Subject: [PATCH 71/84] fix: ui review fixes --- .../src/app/components/C9RComponent.scss | 1 + .../DynamicForm/FieldBase/FieldBase.scss | 1 - src/editor-v2/components/Tabs/Tabs.scss | 27 +++++++++++++++++-- .../BlockConfigForm/BlockConfigForm.scss | 1 - .../containers/BlocksList/BlocksList.scss | 3 ++- .../containers/GlobalConfig/GlobalConfig.scss | 1 - .../containers/ViewSwitches/ViewSwitches.tsx | 8 +++--- 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/playground/src/app/components/C9RComponent.scss b/playground/src/app/components/C9RComponent.scss index 6203e962e..4beb537a6 100644 --- a/playground/src/app/components/C9RComponent.scss +++ b/playground/src/app/components/C9RComponent.scss @@ -3,6 +3,7 @@ $block: '.c9r-component'; #{$block} { width: 100%; padding: 16px 12px; + box-sizing: border-box; &__header { display: flex; diff --git a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss index df3effd9d..9e7ed039d 100644 --- a/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss +++ b/src/editor-v2/components/DynamicForm/FieldBase/FieldBase.scss @@ -6,7 +6,6 @@ $block: '.#{$ns}field-base'; #{$block} { $class: &; padding: 16px 12px 16px 0; - border-bottom: 1px solid var(--g-color-line-generic); position: relative; display: flex; diff --git a/src/editor-v2/components/Tabs/Tabs.scss b/src/editor-v2/components/Tabs/Tabs.scss index c9b6b3dd1..0029e5f67 100644 --- a/src/editor-v2/components/Tabs/Tabs.scss +++ b/src/editor-v2/components/Tabs/Tabs.scss @@ -14,6 +14,9 @@ $block: '.#{$ns}tabs'; font-weight: 500; letter-spacing: 0.5px; cursor: pointer; + font-size: var(--g-text-code-inline-1-font-size); + line-height: var(--g-text-code-inline-2-line-height); + align-items: center; &_active { pointer-events: none; @@ -26,15 +29,35 @@ $block: '.#{$ns}tabs'; border-bottom: 1px solid var(--g-color-line-generic); display: inline-flex; flex-direction: row; - padding: 16px 4px; + padding: 12px 4px; flex: 0 0 auto; box-sizing: border-box; overflow-x: auto; + position: sticky; + top: 0; + background-color: var(--g-color-base-background); + /* This ensures the background is visible when sticky */ + z-index: 2; + /* Higher z-index for parent tabs-wrapper */ + } + + /* Make sure any nested tabs component also has proper overflow behavior */ + #{$block} { + overflow: visible; + + /* Ensure nested tabs-wrapper elements are also sticky but positioned below parent tabs-wrapper */ + /* This selector specifically targets tabs-wrapper elements inside a nested tabs component */ + #{$block} &__tabs-wrapper { + position: sticky; + top: 48.5px; + /* Position below parent tabs-wrapper */ + z-index: 1; + background-color: var(--g-color-base-background); + } } &__body { flex: 1; min-height: 1px; - overflow-y: auto; } } diff --git a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss index c21fc9318..31dab48f7 100644 --- a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss +++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss @@ -9,7 +9,6 @@ $padding: 12px; &__title { @include text-subheader-3; padding: $padding; - border-bottom: 1px solid var(--g-color-line-generic); } &__form { diff --git a/src/editor-v2/containers/BlocksList/BlocksList.scss b/src/editor-v2/containers/BlocksList/BlocksList.scss index 90bffb0cd..711caa281 100644 --- a/src/editor-v2/containers/BlocksList/BlocksList.scss +++ b/src/editor-v2/containers/BlocksList/BlocksList.scss @@ -21,6 +21,7 @@ $block: '.#{$ns}blocks-list'; justify-content: space-between; text-align: center; gap: 4px; + max-width: 192px; &:active { background-color: var(--g-color-base-generic-hover); @@ -57,7 +58,7 @@ $block: '.#{$ns}blocks-list'; &-items { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(auto-fill, minmax(98px, 1fr)); gap: 8px; } } diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss index a51239af7..d747c9468 100644 --- a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss +++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss @@ -8,6 +8,5 @@ $block: '.#{$ns}global-config'; padding: 12px; margin-top: 8px; @include text-subheader-3; - border-bottom: 1px solid var(--g-color-line-generic); } } diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx index a1fb322e4..a5751b642 100644 --- a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx +++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Display, Eye, Minus, Plus, Smartphone} from '@gravity-ui/icons'; +import {Display, Minus, Plus, Smartphone, SquareDashed} from '@gravity-ui/icons'; import {Button, Icon, SegmentedRadioGroup, Select} from '@gravity-ui/uikit'; import {ZOOM_STEPS} from '../../constants'; @@ -26,7 +26,7 @@ interface DeviceOption { * Available device viewport options * - Desktop: 100% width * - Tablet: 768px width - * - Mobile: 576px width + * - Mobile: 375px width */ const DEVICE_OPTIONS: DeviceOption[] = [ { @@ -41,7 +41,7 @@ const DEVICE_OPTIONS: DeviceOption[] = [ }, { label: <Icon width={14} data={Smartphone} />, - value: '576px', + value: '375px', ariaLabel: 'Mobile view', }, ]; @@ -101,7 +101,7 @@ const ViewSwitches: React.FC = () => { aria-label="Switch to preview mode" title="Switch to preview mode" > - <Icon data={Eye} /> + <Icon data={SquareDashed} /> </Button> <div className={b('zoom')} role="group" aria-label="Zoom controls"> From 06ab2093e49f82f8e92d8e7e2f09c8220300e290 Mon Sep 17 00:00:00 2001 From: gravity-ui-bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Wed, 14 May 2025 14:09:59 +0000 Subject: [PATCH 72/84] 6.3.2-alpha.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9e457de1..2da77066c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.6", + "version": "6.3.2-alpha.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.6", + "version": "6.3.2-alpha.7", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index 9e1d09a48..c410d057e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/page-constructor", - "version": "6.3.2-alpha.6", + "version": "6.3.2-alpha.7", "description": "Gravity UI Page Constructor", "license": "MIT", "type": "commonjs", From 10cee9fae534feddb7c1324e66160068f39976d4 Mon Sep 17 00:00:00 2001 From: DaffPunks <sawavas2@gmail.com> Date: Mon, 9 Jun 2025 10:03:59 +0300 Subject: [PATCH 73/84] feat: update playground, add form builder --- package-lock.json | 5585 +++++++++-------- package.json | 13 +- playground/.eslintignore | 3 + playground/.gitignore | 14 +- playground/.prettierignore | 3 + playground/index.html | 20 + playground/next.config.mjs | 4 - playground/package-lock.json | 898 --- playground/package.json | 23 - playground/{src/app => public}/favicon.ico | Bin playground/public/manifest.json | 13 + playground/public/vite.svg | 1 + .../src/app/components/C9RComponent.scss | 31 - .../src/app/components/C9RComponent.tsx | 22 - playground/src/app/layout.tsx | 17 - playground/src/app/page.scss | 9 - playground/src/app/pc-2/page.tsx | 15 - playground/src/app/pc/page.tsx | 13 - playground/src/constants.ts | 4 - playground/src/main.tsx | 17 + playground/src/pages/editor/editor.scss | 13 + .../{app/page.tsx => pages/editor/editor.tsx} | 36 +- .../AddPropertyButton/AddPropertyButton.scss | 3 + .../AddPropertyButton/AddPropertyButton.tsx | 36 + .../ArrayFieldRenderer.scss | 29 + .../ArrayFieldRenderer/ArrayFieldRenderer.tsx | 83 + .../form/components/ConfigRow/ConfigRow.scss | 14 + .../form/components/ConfigRow/ConfigRow.tsx | 33 + .../form/components/FieldCard/FieldCard.scss | 17 + .../form/components/FieldCard/FieldCard.tsx | 164 + .../components/FieldHeader/FieldHeader.scss | 6 + .../components/FieldHeader/FieldHeader.tsx | 28 + .../components/FormBuilder/FormBuilder.scss | 110 + .../components/FormBuilder/FormBuilder.tsx | 94 + .../components/FormOutput/FormOutput.scss | 13 + .../form/components/FormOutput/FormOutput.tsx | 25 + .../ObjectFieldRenderer.scss | 8 + .../ObjectFieldRenderer.tsx | 47 + .../components/OptionHeader/OptionHeader.scss | 6 + .../components/OptionHeader/OptionHeader.tsx | 17 + .../OptionsRenderer/OptionsRenderer.scss | 38 + .../OptionsRenderer/OptionsRenderer.tsx | 110 + .../SectionHeader/SectionHeader.scss | 3 + .../SectionHeader/SectionHeader.tsx | 18 + .../SelectFieldRenderer.scss | 45 + .../SelectFieldRenderer.tsx | 145 + playground/src/pages/form/form.scss | 56 + playground/src/pages/form/form.tsx | 105 + .../src/pages/form/hooks/FormContext.tsx | 25 + playground/src/pages/form/hooks/types.ts | 81 + .../src/pages/form/hooks/useFormFields.ts | 965 +++ .../pc => pages/pc/example-1}/content.json | 0 .../pc-2 => pages/pc/example-2}/content.json | 0 .../pc/example-2}/navigation.json | 0 .../pc-2 => pages/pc/example-2}/styles.scss | 0 playground/src/pages/pc/pc.tsx | 43 + playground/src/router.tsx | 21 + playground/src/styles/globals.scss | 8 +- playground/src/vite-env.d.ts | 2 + playground/tsconfig.json | 40 +- playground/vite.config.mts | 26 + .../DynamicForm/Fields/Select/Select.tsx | 23 +- .../containers/MiddleScreen/MiddleScreen.tsx | 6 +- src/editor-v2/utils/index.ts | 2 +- styles/yfm.scss | 2 +- 65 files changed, 5697 insertions(+), 3554 deletions(-) create mode 100644 playground/.eslintignore create mode 100644 playground/.prettierignore create mode 100644 playground/index.html delete mode 100644 playground/next.config.mjs delete mode 100644 playground/package-lock.json delete mode 100644 playground/package.json rename playground/{src/app => public}/favicon.ico (100%) create mode 100644 playground/public/manifest.json create mode 100644 playground/public/vite.svg delete mode 100644 playground/src/app/components/C9RComponent.scss delete mode 100644 playground/src/app/components/C9RComponent.tsx delete mode 100644 playground/src/app/layout.tsx delete mode 100644 playground/src/app/page.scss delete mode 100644 playground/src/app/pc-2/page.tsx delete mode 100644 playground/src/app/pc/page.tsx delete mode 100644 playground/src/constants.ts create mode 100644 playground/src/main.tsx create mode 100644 playground/src/pages/editor/editor.scss rename playground/src/{app/page.tsx => pages/editor/editor.tsx} (52%) create mode 100644 playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.scss create mode 100644 playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.tsx create mode 100644 playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.scss create mode 100644 playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx create mode 100644 playground/src/pages/form/components/ConfigRow/ConfigRow.scss create mode 100644 playground/src/pages/form/components/ConfigRow/ConfigRow.tsx create mode 100644 playground/src/pages/form/components/FieldCard/FieldCard.scss create mode 100644 playground/src/pages/form/components/FieldCard/FieldCard.tsx create mode 100644 playground/src/pages/form/components/FieldHeader/FieldHeader.scss create mode 100644 playground/src/pages/form/components/FieldHeader/FieldHeader.tsx create mode 100644 playground/src/pages/form/components/FormBuilder/FormBuilder.scss create mode 100644 playground/src/pages/form/components/FormBuilder/FormBuilder.tsx create mode 100644 playground/src/pages/form/components/FormOutput/FormOutput.scss create mode 100644 playground/src/pages/form/components/FormOutput/FormOutput.tsx create mode 100644 playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.scss create mode 100644 playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx create mode 100644 playground/src/pages/form/components/OptionHeader/OptionHeader.scss create mode 100644 playground/src/pages/form/components/OptionHeader/OptionHeader.tsx create mode 100644 playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.scss create mode 100644 playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.tsx create mode 100644 playground/src/pages/form/components/SectionHeader/SectionHeader.scss create mode 100644 playground/src/pages/form/components/SectionHeader/SectionHeader.tsx create mode 100644 playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.scss create mode 100644 playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.tsx create mode 100644 playground/src/pages/form/form.scss create mode 100644 playground/src/pages/form/form.tsx create mode 100644 playground/src/pages/form/hooks/FormContext.tsx create mode 100644 playground/src/pages/form/hooks/types.ts create mode 100644 playground/src/pages/form/hooks/useFormFields.ts rename playground/src/{app/pc => pages/pc/example-1}/content.json (100%) rename playground/src/{app/pc-2 => pages/pc/example-2}/content.json (100%) rename playground/src/{app/pc-2 => pages/pc/example-2}/navigation.json (100%) rename playground/src/{app/pc-2 => pages/pc/example-2}/styles.scss (100%) create mode 100644 playground/src/pages/pc/pc.tsx create mode 100644 playground/src/router.tsx create mode 100644 playground/src/vite-env.d.ts create mode 100644 playground/vite.config.mts diff --git a/package-lock.json b/package-lock.json index 2da77066c..332fdddd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,10 +81,13 @@ "@types/uuid": "^9.0.0", "@types/webpack-env": "^1.18.1", "@types/youtube-player": "^5.5.11", + "@vitejs/plugin-react": "^4.5.0", "autoprefixer": "^10.4.14", "babel-loader": "^8.3.0", + "bem-cn-lite": "^4.1.0", "css-loader": "^5.2.7", "es5-ext": "0.10.53", + "esbuild-sass-plugin": "^3.3.1", "eslint": "^8.57.1", "eslint-plugin-no-not-accumulator-reassign": "^0.1.0", "eslint-plugin-react": "^7.37.4", @@ -115,6 +118,7 @@ "react": "^18.3.1", "react-docgen-typescript": "^2.2.2", "react-dom": "^18.3.1", + "react-router": "^7.6.1", "resolve-url-loader": "^3.1.5", "rimraf": "^6.0.1", "sass": "^1.63.6", @@ -127,8 +131,9 @@ "ts-jest": "^29.2.5", "tslib": "^2.4.0", "typescript": "^5.7.3", + "vite": "^6.3.5", "vite-plugin-commonjs": "^0.10.1", - "vite-plugin-svgr": "^4.2.0", + "vite-plugin-svgr": "^4.3.0", "webpack": "^5.98.0", "webpack-cli": "^6.0.1", "webpack-shell-plugin-next": "^2.3.1" @@ -159,44 +164,47 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", - "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.7", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.26.7", - "@babel/types": "^7.26.7", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -254,13 +262,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -282,13 +291,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -407,27 +417,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -449,10 +461,11 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -505,28 +518,31 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -546,25 +562,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", + "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -1538,12 +1556,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1553,12 +1572,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1945,30 +1965,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", - "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1977,13 +1999,14 @@ } }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3428,6 +3451,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -4973,365 +4997,1184 @@ "node": ">=12.4.0" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@playwright/experimental-ct-core": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.45.3.tgz", - "integrity": "sha512-uYcWBxRPu2G2Mj2e+XUxRBzRNnG/Yz0A5DVWFewiG3qEfC92MaGYGxmzKeFeU9NcMA2fWwaqB3XWHXjn9qSM5Q==", + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "playwright": "1.45.3", - "playwright-core": "1.45.3", - "vite": "^5.2.8" + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/experimental-ct-react": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.45.3.tgz", - "integrity": "sha512-cEgiZ2+DqVCeFJWJdHgOUwhlEof41Rg1GX48FtMX/xrjAnxs2ccqqAXMILD+mVV1ftsC2jeS1EiRLoAmf8QXgA==", - "dev": true, - "dependencies": { - "@playwright/experimental-ct-core": "1.45.3", - "@vitejs/plugin-react": "^4.2.1" + "node": ">= 10.0.0" }, - "bin": { - "playwright": "cli.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/test": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz", - "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==", + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "playwright": "1.45.3" - }, - "bin": { - "playwright": "cli.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18" - } - }, - "node_modules/@react-spring/animated": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", - "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", - "dependencies": { - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/core": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", - "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", - "dependencies": { - "@react-spring/animated": "~9.7.3", - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" + "node": ">= 10.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "url": "https://opencollective.com/parcel" } }, - "node_modules/@react-spring/shared": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", - "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", - "dependencies": { - "@react-spring/types": "~9.7.3" + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@react-spring/types": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", - "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" - }, - "node_modules/@react-spring/web": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", - "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", - "dependencies": { - "@react-spring/animated": "~9.7.3", - "@react-spring/core": "~9.7.3", - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node": ">= 10.0.0" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", - "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", - "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ - "arm64" + "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", - "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", - "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", - "cpu": [ - "x64" + "linux" ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", - "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", - "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", - "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ - "arm" + "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", - "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ - "arm" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", - "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", "cpu": [ - "arm64" + "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", - "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ - "arm64" + "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", - "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@playwright/experimental-ct-core": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.45.3.tgz", + "integrity": "sha512-uYcWBxRPu2G2Mj2e+XUxRBzRNnG/Yz0A5DVWFewiG3qEfC92MaGYGxmzKeFeU9NcMA2fWwaqB3XWHXjn9qSM5Q==", + "dev": true, + "dependencies": { + "playwright": "1.45.3", + "playwright-core": "1.45.3", + "vite": "^5.2.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ - "loong64" + "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", - "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", - "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ - "riscv64" + "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", - "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", - "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ - "s390x" + "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", - "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ - "x64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", - "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/@playwright/experimental-ct-react": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.45.3.tgz", + "integrity": "sha512-cEgiZ2+DqVCeFJWJdHgOUwhlEof41Rg1GX48FtMX/xrjAnxs2ccqqAXMILD+mVV1ftsC2jeS1EiRLoAmf8QXgA==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.45.3", + "@vitejs/plugin-react": "^4.2.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/test": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz", + "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==", + "dev": true, + "dependencies": { + "playwright": "1.45.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", + "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", + "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", + "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", + "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", + "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", + "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", + "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", + "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", + "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", + "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", + "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", + "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", + "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", + "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", + "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", + "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", + "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.38.0", @@ -7568,22 +8411,24 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/@webassemblyjs/ast": { @@ -8679,6 +9524,23 @@ } ] }, + "node_modules/bem-cn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bem-cn/-/bem-cn-3.0.1.tgz", + "integrity": "sha512-kWC76a09vSk6cJXDYsH1erjxdBf856HTxl0IHOvYItSmBC6wQCsRCf9bmKR0hmeUDcUP5XPMr8MNXDgKbKJi0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bem-cn-lite": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bem-cn-lite/-/bem-cn-lite-4.1.0.tgz", + "integrity": "sha512-0IEVRYK2MQKQO00P3sY3hNv7vH8P+Z8mR46qFcaiwsQAWp0MuMWpLSuUUhZEjKD2HzTGXMqMsFysWEeeJa1drQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bem-cn": "^3.0.1" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -9611,6 +10473,16 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -10392,6 +11264,20 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -11091,6 +11977,22 @@ "esbuild": ">=0.12 <1" } }, + "node_modules/esbuild-sass-plugin": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.3.1.tgz", + "integrity": "sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.22.8", + "safe-identifier": "^0.4.2", + "sass": "^1.71.1" + }, + "peerDependencies": { + "esbuild": ">=0.20.1", + "sass-embedded": "^1.71.1" + } + }, "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -11885,7 +12787,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", @@ -12105,6 +13008,21 @@ "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -18040,6 +18958,14 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -20537,10 +21463,11 @@ "dev": true }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -20554,6 +21481,29 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/react-router": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz", + "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-select": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz", @@ -21757,6 +22707,13 @@ } ] }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true, + "license": "ISC" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -21874,13 +22831,14 @@ } }, "node_modules/sass": { - "version": "1.63.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", - "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "version": "1.89.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz", + "integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -21888,6 +22846,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { @@ -21969,11 +22930,48 @@ "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/immutable": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/saxes": { @@ -22073,6 +23071,13 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -22334,561 +23339,238 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/ssr-window": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz", - "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==" - }, - "node_modules/stable-hash": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", - "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/storybook": { - "version": "8.6.11", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.11.tgz", - "integrity": "sha512-B2wxpmq1QYS4JV7RQu1mOHD7akfoGbuoUSkx2D2GZgv/zXAHZmDpSFcTvvBBm8FAtzChI9HhITSJ0YS0ePfnJQ==", - "dev": true, - "dependencies": { - "@storybook/core": "8.6.11" - }, - "bin": { - "getstorybook": "bin/index.cjs", - "sb": "bin/index.cjs", - "storybook": "bin/index.cjs" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "prettier": "^2 || ^3" - }, - "peerDependenciesMeta": { - "prettier": { - "optional": true - } - } - }, - "node_modules/stream-composer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", - "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", - "dev": true, - "dependencies": { - "streamx": "^2.13.2" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/streamx": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", - "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", - "dev": true, - "dependencies": { - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-convert": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" - }, - "node_modules/string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", - "dev": true - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-replace-loader": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", - "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "peerDependencies": { - "webpack": "^5" - } - }, - "node_modules/string-replace-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/string-replace-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/string-replace-loader/node_modules/json-schema-traverse": { + "node_modules/source-map-url": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", "dev": true }, - "node_modules/string-replace-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">= 10.x" } }, - "node_modules/string.prototype.includes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", - "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "node_modules/ssr-window": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz", + "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==" + }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", - "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/string.prototype.repeat": { + "node_modules/stop-iteration-iterator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "internal-slot": "^1.0.4" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "node_modules/storybook": { + "version": "8.6.11", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.11.tgz", + "integrity": "sha512-B2wxpmq1QYS4JV7RQu1mOHD7akfoGbuoUSkx2D2GZgv/zXAHZmDpSFcTvvBBm8FAtzChI9HhITSJ0YS0ePfnJQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "@storybook/core": "8.6.11" }, - "engines": { - "node": ">= 0.4" + "bin": { + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "type": "opencollective", + "url": "https://opencollective.com/storybook" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "prettier": "^2 || ^3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "prettier": { + "optional": true + } } }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", "dev": true, "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" + "streamx": "^2.13.2" } }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "bare-events": "^2.2.0" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "safe-buffer": "~5.2.0" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=0.6.19" } }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } + "node_modules/string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", + "dev": true }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/style-inject": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", - "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", - "dev": true - }, - "node_modules/style-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", - "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "node_modules/string-replace-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", + "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", "dev": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "webpack": "^5" } }, - "node_modules/style-loader/node_modules/ajv": { + "node_modules/string-replace-loader/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", @@ -22904,7 +23586,7 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/style-loader/node_modules/ajv-keywords": { + "node_modules/string-replace-loader/node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", @@ -22913,13 +23595,13 @@ "ajv": "^6.9.1" } }, - "node_modules/style-loader/node_modules/json-schema-traverse": { + "node_modules/string-replace-loader/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/style-loader/node_modules/schema-utils": { + "node_modules/string-replace-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", @@ -22930,2269 +23612,2249 @@ "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/style-search": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true - }, - "node_modules/stylelint": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", - "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", - "dev": true, - "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4", - "@csstools/selector-specificity": "^3.0.0", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^8.2.0", - "css-functions-list": "^3.2.1", - "css-tree": "^2.3.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.1", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^7.0.0", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^5.2.4", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.29.0", - "mathml-tag-names": "^2.1.3", - "meow": "^10.1.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.28", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^3.0.0", - "svg-tags": "^1.0.0", - "table": "^6.8.1", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - } - }, - "node_modules/stylelint-order": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz", - "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==", - "dev": true, - "dependencies": { - "postcss": "^8.4.32", - "postcss-sorting": "^8.0.2" - }, - "peerDependencies": { - "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1" - } - }, - "node_modules/stylelint-prettier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.1.0.tgz", - "integrity": "sha512-dd653q/d1IfvsSQshz1uAMe+XDm6hfM/7XiFH0htYY8Lse/s5ERTg7SURQehZPwVvm/rs7AsFhda9EQ2E9TS0g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "prettier": ">=3.0.0", - "stylelint": ">=15.8.0" - } - }, - "node_modules/stylelint-scss": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz", - "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==", - "dev": true, - "dependencies": { - "known-css-properties": "^0.29.0", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" - } - }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true - }, - "node_modules/stylelint/node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz", - "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "flat-cache": "^3.2.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/stylelint/node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "global-prefix": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/stylelint/node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", + "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", "dev": true, "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, - "node_modules/stylelint/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stylelint/node_modules/meow": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", - "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", + "node_modules/string.prototype.padend": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", + "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==", "dev": true, "dependencies": { - "@types/minimist": "^1.2.2", - "camelcase-keys": "^7.0.0", - "decamelize": "^5.0.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.2", - "read-pkg-up": "^8.0.0", - "redent": "^4.0.0", - "trim-newlines": "^4.0.2", - "type-fest": "^1.2.2", - "yargs-parser": "^20.2.9" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stylelint/node_modules/redent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", - "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "dependencies": { - "indent-string": "^5.0.0", - "strip-indent": "^4.0.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stylelint/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stylelint/node_modules/strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "min-indent": "^1.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stylelint/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, - "node_modules/stylelint/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/stringify-object/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "ansi-regex": "^5.0.1" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=8" } }, - "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "dev": true + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "min-indent": "^1.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, "engines": { - "node": ">=14.18" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", + "dev": true + }, + "node_modules/style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/sver": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", - "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "node_modules/style-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "optionalDependencies": { - "semver": "^6.3.0" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/sver/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/style-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "optional": true, - "bin": { - "semver": "bin/semver.js" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "node_modules/style-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/swiper": { - "version": "6.8.4", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.8.4.tgz", - "integrity": "sha512-O+buF9Q+sMA0H7luMS8R59hCaJKlpo8PXhQ6ZYu6Rn2v9OsFd4d1jmrv14QvxtQpKAvL/ZiovEeANI/uDGet7g==", - "funding": [ - { - "type": "patreon", - "url": "https://www.patreon.com/vladimirkharlampidi" - }, - { - "type": "open_collective", - "url": "http://opencollective.com/swiper" - } - ], - "hasInstallScript": true, + "node_modules/style-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, "dependencies": { - "dom7": "^3.0.0", - "ssr-window": "^3.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 4.7.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", "dev": true }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "node_modules/stylelint": { + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", + "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", "dev": true, "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@csstools/css-parser-algorithms": "^2.3.1", + "@csstools/css-tokenizer": "^2.2.0", + "@csstools/media-query-list-parser": "^2.1.4", + "@csstools/selector-specificity": "^3.0.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.1", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^7.0.0", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^5.2.4", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.29.0", + "mathml-tag-names": "^2.1.3", + "meow": "^10.1.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.28", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^3.0.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "type": "opencollective", + "url": "https://opencollective.com/stylelint" } }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true + "node_modules/stylelint-order": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz", + "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==", + "dev": true, + "dependencies": { + "postcss": "^8.4.32", + "postcss-sorting": "^8.0.2" + }, + "peerDependencies": { + "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1" + } }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "node_modules/stylelint-prettier": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.1.0.tgz", + "integrity": "sha512-dd653q/d1IfvsSQshz1uAMe+XDm6hfM/7XiFH0htYY8Lse/s5ERTg7SURQehZPwVvm/rs7AsFhda9EQ2E9TS0g==", "dev": true, "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=10.0.0" + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "prettier": ">=3.0.0", + "stylelint": ">=15.8.0" } }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "node_modules/stylelint-scss": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz", + "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "known-css-properties": "^0.29.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1 || ^15.0.0" } }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", "dev": true, - "dependencies": { - "streamx": "^2.12.5" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz", + "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", "dev": true, "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" + "flat-cache": "^3.2.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=12.0.0" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "node_modules/stylelint/node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" + "global-prefix": "^3.0.0" }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "node": ">=6" } }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "node_modules/stylelint/node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=6" } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "node_modules/stylelint/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/stylelint/node_modules/meow": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "node_modules/stylelint/node_modules/redent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/stylelint/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "node": ">=14" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "node_modules/stylelint/node_modules/strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", "dev": true, "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/text-extensions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", - "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", - "dev": true, + "min-indent": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", + "node_modules/stylelint/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "dependencies": { - "readable-stream": "3" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "node_modules/stylelint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "node_modules/through2-filter/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/stylelint/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/through2-filter/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "dev": true }, - "node_modules/through2-filter/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/through2-filter/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/timers-ext": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", - "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "dependencies": { - "es5-ext": "^0.10.64", - "next-tick": "^1.1.0" - }, "engines": { - "node": ">=0.12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/timers-ext/node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" + "optionalDependencies": { + "semver": "^6.3.0" } }, - "node_modules/timers-ext/node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true + "node_modules/sver/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "dev": true }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "node_modules/swiper": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.8.4.tgz", + "integrity": "sha512-O+buF9Q+sMA0H7luMS8R59hCaJKlpo8PXhQ6ZYu6Rn2v9OsFd4d1jmrv14QvxtQpKAvL/ZiovEeANI/uDGet7g==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/vladimirkharlampidi" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "hasInstallScript": true, + "dependencies": { + "dom7": "^3.0.0", + "ssr-window": "^3.0.0" + }, + "engines": { + "node": ">= 4.7.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=0.10.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "dependencies": { - "is-number": "^7.0.0" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=8.0" + "node": ">=10.0.0" } }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, - "dependencies": { - "through2": "^2.0.3" - }, "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/to-through/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "streamx": "^2.12.5" } }, - "node_modules/to-through/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/to-through/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/to-through/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", - "dev": true - }, - "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=6" + "node": ">= 10.13.0" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, "engines": { - "node": ">= 4.0.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/trim-newlines": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", - "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "node_modules/terser-webpack-plugin/node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" } }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" + "node": ">=0.10.0" } }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=6.10" + "node": ">=8" } }, - "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.6.3", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, + "b4a": "^1.6.4" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" + "node": ">=8" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/textextensions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "readable-stream": "3" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "through2": "~2.0.0", + "xtend": "~4.0.0" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/through2-filter/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "node_modules/through2-filter/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/through2-filter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/through2-filter/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", "dev": true, "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=0.12" } }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "node_modules/timers-ext/node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, + "hasInstallScript": true, "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10" } }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "node_modules/timers-ext/node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" }, "engines": { - "node": ">=14.17" + "node": ">=0.10.0" } }, - "node_modules/typograf": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/typograf/-/typograf-7.4.1.tgz", - "integrity": "sha512-V+1jqkv574pzPTW/JcqkhXQzmA7U3B2xVKc6QMzNxmN+1eecVn164Z8Wm8xM6ArKSk/sUjKvOPoT0U3G6zOMjQ==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, "engines": { - "node": ">= 4" + "node": ">=8.0" } }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", "dev": true, "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" + "through2": "^2.0.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "node_modules/to-through/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/undertaker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", - "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", + "node_modules/to-through/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/to-through/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "dependencies": { - "bach": "^2.0.1", - "fast-levenshtein": "^3.0.0", - "last-run": "^2.0.0", - "undertaker-registry": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/undertaker-registry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", - "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "node_modules/to-through/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "engines": { - "node": ">= 10.13.0" + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/undertaker/node_modules/fast-levenshtein": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", - "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "dev": true + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "dev": true, "dependencies": { - "fastest-levenshtein": "^1.0.7" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" } }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { - "node": ">=4" + "node": ">= 4.0.0" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/trim-newlines": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unicode-match-property-value-ecmascript": { + "node_modules/trough": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, - "engines": { - "node": ">=4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "dev": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.10" } }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", "dev": true, "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } } }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { - "@types/unist": "^3.0.0" + "minimist": "^1.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { - "@types/unist": "^3.0.0" + "tslib": "^1.8.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" + "prelude-ls": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.4" } }, - "node_modules/universal-cookie": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz", - "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^0.7.2" - } - }, - "node_modules/universal-cookie/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, "engines": { - "node": ">= 10.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unplugin": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", - "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "dependencies": { - "acorn": "^8.14.0", - "webpack-virtual-modules": "^0.6.2" + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, "bin": { - "update-browserslist-db": "cli.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "engines": { + "node": ">=14.17" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" + "node_modules/typograf": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/typograf/-/typograf-7.4.1.tgz", + "integrity": "sha512-V+1jqkv574pzPTW/JcqkhXQzmA7U3B2xVKc6QMzNxmN+1eecVn164Z8Wm8xM6ArKSk/sUjKvOPoT0U3G6zOMjQ==", + "engines": { + "node": ">= 4" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=10.13.0" } }, - "node_modules/use-memo-one": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", "dev": true, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", - "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "fastest-levenshtein": "^1.0.7" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "engines": { - "node": ">= 4" - } + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, "engines": { - "node": ">=10.12.0" + "node": ">=4" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=4" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dev": true, "dependencies": { "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, "engines": { - "node": ">= 0.10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vinyl-contents": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", - "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, "dependencies": { - "bl": "^5.0.0", - "vinyl": "^3.0.0" - }, - "engines": { - "node": ">=10.13.0" + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" } }, - "node_modules/vinyl-contents/node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "dev": true, "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vinyl-contents/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "dev": true, - "engines": { - "node": ">= 10" + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vinyl-contents/node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "dev": true, "dependencies": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", "dev": true, "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" }, - "engines": { - "node": ">= 0.10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vinyl-fs/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, + "node_modules/universal-cookie": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz", + "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "@types/cookie": "^0.6.0", + "cookie": "^0.7.2" } }, - "node_modules/vinyl-fs/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/vinyl-fs/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" + "node_modules/universal-cookie/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/vinyl-fs/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", "dev": true, "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">= 0.10" + "node": ">=14.0.0" } }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "remove-trailing-separator": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", - "dev": true, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { - "source-map": "^0.5.1" + "punycode": "^2.1.0" } }, - "node_modules/vite": { - "version": "5.4.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.16.tgz", - "integrity": "sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==", + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "dev": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" + "punycode": "^1.4.1", + "qs": "^6.12.3" }, "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, + "node": ">= 0.4" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "dev": true, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { + "@types/react": { "optional": true } } }, - "node_modules/vite-plugin-commonjs": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/vite-plugin-commonjs/-/vite-plugin-commonjs-0.10.1.tgz", - "integrity": "sha512-taP8R9kYGlCW5OzkVR0UIWRCnG6rSxeWWuA7tnU5b9t5MniibOnDY219NhisTeDhJAeGT8cEnrhVWZ9A5yD+vg==", + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", "dev": true, - "dependencies": { - "acorn": "^8.8.2", - "fast-glob": "^3.2.12", - "magic-string": "^0.30.1", - "vite-plugin-dynamic-import": "^1.5.0" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/vite-plugin-dynamic-import": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-import/-/vite-plugin-dynamic-import-1.5.0.tgz", - "integrity": "sha512-Qp85c+AVJmLa8MLni74U4BDiWpUeFNx7NJqbGZyR2XJOU7mgW0cb7nwlAMucFyM4arEd92Nfxp4j44xPi6Fu7g==", - "dev": true, - "dependencies": { - "acorn": "^8.8.2", - "es-module-lexer": "^1.2.1", - "fast-glob": "^3.2.12", - "magic-string": "^0.30.1" + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/vite-plugin-svgr": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", - "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dev": true, "dependencies": { - "@rollup/pluginutils": "^5.0.5", - "@svgr/core": "^8.1.0", - "@svgr/plugin-jsx": "^8.1.0" - }, - "peerDependencies": { - "vite": "^2.6.0 || 3 || 4 || 5" + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", "engines": { - "node": ">=12" + "node": ">= 4" } }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, "engines": { - "node": ">=12" + "node": ">=10.12.0" } }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">= 10" } }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], + "node_modules/vinyl-fs/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/vinyl-fs/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], + "node_modules/vinyl-fs/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", "dev": true, - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", "dev": true, - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "dependencies": { + "source-map": "^0.5.1" } }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=12" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], + "node_modules/vite-plugin-commonjs": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/vite-plugin-commonjs/-/vite-plugin-commonjs-0.10.1.tgz", + "integrity": "sha512-taP8R9kYGlCW5OzkVR0UIWRCnG6rSxeWWuA7tnU5b9t5MniibOnDY219NhisTeDhJAeGT8cEnrhVWZ9A5yD+vg==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "acorn": "^8.8.2", + "fast-glob": "^3.2.12", + "magic-string": "^0.30.1", + "vite-plugin-dynamic-import": "^1.5.0" } }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], + "node_modules/vite-plugin-dynamic-import": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-import/-/vite-plugin-dynamic-import-1.5.0.tgz", + "integrity": "sha512-Qp85c+AVJmLa8MLni74U4BDiWpUeFNx7NJqbGZyR2XJOU7mgW0cb7nwlAMucFyM4arEd92Nfxp4j44xPi6Fu7g==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "acorn": "^8.8.2", + "es-module-lexer": "^1.2.1", + "fast-glob": "^3.2.12", + "magic-string": "^0.30.1" } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "peerDependencies": { + "vite": ">=2.6.0" } }, "node_modules/vite/node_modules/fsevents": { @@ -25209,6 +25871,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index c410d057e..8837d0b0f 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,7 @@ "*.scss" ], "scripts": { - "deps:install:playground": "cd playground && npm ci", - "deps:install:package": "npm ci", - "deps:install": "run-p deps:install:playground deps:install:package", + "deps:install": "npm ci", "deps:truncate": "npm prune --production", "lint:fix": "run-s lint:js:fix lint:styles:fix lint:prettier:fix typecheck", "lint:js": "eslint '**/*.{js,jsx,ts,tsx}' --max-warnings=0", @@ -80,6 +78,7 @@ "lint": "run-p lint:js lint:styles lint:prettier typecheck", "typecheck": "tsc --noEmit", "dev": "npm run storybook:start", + "dev:playground": "cd playground && vite -c vite.config.mts", "storybook:start": "storybook dev -p 7009", "storybook:build": "storybook build -c .storybook -o storybook-static", "start": "node dist", @@ -89,6 +88,7 @@ "build:widget": "webpack --config widget.webpack.js", "build:schema": "webpack --config schema.webpack.js", "build": "run-p build:client build:server build:widget build:schema", + "build:playground": "cd playground && tsc && vite build -c vite.config.mts", "prepublishOnly": "npm run lint && npm run build", "prepare": "husky install", "test": "jest", @@ -180,10 +180,13 @@ "@types/uuid": "^9.0.0", "@types/webpack-env": "^1.18.1", "@types/youtube-player": "^5.5.11", + "@vitejs/plugin-react": "^4.5.0", "autoprefixer": "^10.4.14", "babel-loader": "^8.3.0", + "bem-cn-lite": "^4.1.0", "css-loader": "^5.2.7", "es5-ext": "0.10.53", + "esbuild-sass-plugin": "^3.3.1", "eslint": "^8.57.1", "eslint-plugin-no-not-accumulator-reassign": "^0.1.0", "eslint-plugin-react": "^7.37.4", @@ -214,6 +217,7 @@ "react": "^18.3.1", "react-docgen-typescript": "^2.2.2", "react-dom": "^18.3.1", + "react-router": "^7.6.1", "resolve-url-loader": "^3.1.5", "rimraf": "^6.0.1", "sass": "^1.63.6", @@ -226,8 +230,9 @@ "ts-jest": "^29.2.5", "tslib": "^2.4.0", "typescript": "^5.7.3", + "vite": "^6.3.5", "vite-plugin-commonjs": "^0.10.1", - "vite-plugin-svgr": "^4.2.0", + "vite-plugin-svgr": "^4.3.0", "webpack": "^5.98.0", "webpack-cli": "^6.0.1", "webpack-shell-plugin-next": "^2.3.1" diff --git a/playground/.eslintignore b/playground/.eslintignore new file mode 100644 index 000000000..dffb6a9ed --- /dev/null +++ b/playground/.eslintignore @@ -0,0 +1,3 @@ +build +dist +public diff --git a/playground/.gitignore b/playground/.gitignore index 583394ff2..cf74115dd 100644 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -1,12 +1,16 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + # dependencies /node_modules +/.pnp +.pnp.js -# next.js -/.next/ -/out/ +# testing +/coverage # production /build +/dist # misc .DS_Store @@ -20,9 +24,5 @@ yarn-error.log* # local env files .env*.local -# vercel -.vercel - # typescript *.tsbuildinfo -next-env.d.ts diff --git a/playground/.prettierignore b/playground/.prettierignore new file mode 100644 index 000000000..dffb6a9ed --- /dev/null +++ b/playground/.prettierignore @@ -0,0 +1,3 @@ +build +dist +public diff --git a/playground/index.html b/playground/index.html new file mode 100644 index 000000000..be673cfe6 --- /dev/null +++ b/playground/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <link rel="icon" type="image/png" href="/favicon.ico" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="Web site created using vite" /> + <!-- + manifest.json provides metadata used when your web app is installed on a + user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ + --> + <link rel="manifest" href="/manifest.json" /> + <title>Page Constructor Playground + + + +
+ + + diff --git a/playground/next.config.mjs b/playground/next.config.mjs deleted file mode 100644 index 4678774e6..000000000 --- a/playground/next.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default nextConfig; diff --git a/playground/package-lock.json b/playground/package-lock.json deleted file mode 100644 index 4352df272..000000000 --- a/playground/package-lock.json +++ /dev/null @@ -1,898 +0,0 @@ -{ - "name": "page-constructor-playground", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "page-constructor-playground", - "version": "1.0.0", - "dependencies": { - "bem-cn-lite": "^4.1.0", - "next": "15.1.2", - "react": "^19", - "react-dom": "^19" - }, - "devDependencies": { - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "typescript": "^5" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@next/env": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.2.tgz", - "integrity": "sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.2.tgz", - "integrity": "sha512-b9TN7q+j5/7+rGLhFAVZiKJGIASuo8tWvInGfAd8wsULjB1uNGRCj1z1WZwwPWzVQbIKWFYqc+9L7W09qwt52w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.2.tgz", - "integrity": "sha512-caR62jNDUCU+qobStO6YJ05p9E+LR0EoXh1EEmyU69cYydsAy7drMcOlUlRtQihM6K6QfvNwJuLhsHcCzNpqtA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.2.tgz", - "integrity": "sha512-fHHXBusURjBmN6VBUtu6/5s7cCeEkuGAb/ZZiGHBLVBXMBy4D5QpM8P33Or8JD1nlOjm/ZT9sEE5HouQ0F+hUA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.2.tgz", - "integrity": "sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.2.tgz", - "integrity": "sha512-tINV7WmcTUf4oM/eN3Yuu/f8jQ5C6AkueZPKeALs/qfdfX57eNv4Ij7rt0SA6iZ8+fMobVfcFVv664Op0caCCg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.2.tgz", - "integrity": "sha512-jf2IseC4WRsGkzeUw/cK3wci9pxR53GlLAt30+y+B+2qAQxMw6WAC3QrANIKxkcoPU3JFh/10uFfmoMDF9JXKg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.2.tgz", - "integrity": "sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.2.tgz", - "integrity": "sha512-D3cNA8NoT3aWISWmo7HF5Eyko/0OdOO+VagkoJuiTk7pyX3P/b+n8XA/MYvyR+xSVcbKn68B1rY9fgqjNISqzQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/react": { - "version": "19.0.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", - "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", - "dev": true, - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", - "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", - "dev": true, - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/bem-cn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bem-cn/-/bem-cn-3.0.1.tgz", - "integrity": "sha512-kWC76a09vSk6cJXDYsH1erjxdBf856HTxl0IHOvYItSmBC6wQCsRCf9bmKR0hmeUDcUP5XPMr8MNXDgKbKJi0A==" - }, - "node_modules/bem-cn-lite": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bem-cn-lite/-/bem-cn-lite-4.1.0.tgz", - "integrity": "sha512-0IEVRYK2MQKQO00P3sY3hNv7vH8P+Z8mR46qFcaiwsQAWp0MuMWpLSuUUhZEjKD2HzTGXMqMsFysWEeeJa1drQ==", - "dependencies": { - "bem-cn": "^3.0.1" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001618", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz", - "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/next/-/next-15.1.2.tgz", - "integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==", - "dependencies": { - "@next/env": "15.1.2", - "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.15", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.1.2", - "@next/swc-darwin-x64": "15.1.2", - "@next/swc-linux-arm64-gnu": "15.1.2", - "@next/swc-linux-arm64-musl": "15.1.2", - "@next/swc-linux-x64-gnu": "15.1.2", - "@next/swc-linux-x64-musl": "15.1.2", - "@next/swc-win32-arm64-msvc": "15.1.2", - "@next/swc-win32-x64-msvc": "15.1.2", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", - "dependencies": { - "scheduler": "^0.25.0" - }, - "peerDependencies": { - "react": "^19.0.0" - } - }, - "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" - }, - "node_modules/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - } - } -} diff --git a/playground/package.json b/playground/package.json deleted file mode 100644 index 76783faa5..000000000 --- a/playground/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "page-constructor-playground", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "bem-cn-lite": "^4.1.0", - "next": "15.1.2", - "react": "^19", - "react-dom": "^19" - }, - "devDependencies": { - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "typescript": "^5" - } -} diff --git a/playground/src/app/favicon.ico b/playground/public/favicon.ico similarity index 100% rename from playground/src/app/favicon.ico rename to playground/public/favicon.ico diff --git a/playground/public/manifest.json b/playground/public/manifest.json new file mode 100644 index 000000000..c5e41e11a --- /dev/null +++ b/playground/public/manifest.json @@ -0,0 +1,13 @@ +{ + "short_name": "React App", + "name": "Gravity UI – Vite Example", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone" +} diff --git a/playground/public/vite.svg b/playground/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/playground/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground/src/app/components/C9RComponent.scss b/playground/src/app/components/C9RComponent.scss deleted file mode 100644 index 4beb537a6..000000000 --- a/playground/src/app/components/C9RComponent.scss +++ /dev/null @@ -1,31 +0,0 @@ -$block: '.c9r-component'; - -#{$block} { - width: 100%; - padding: 16px 12px; - box-sizing: border-box; - - &__header { - display: flex; - justify-content: space-between; - align-items: flex-start; - } - - &__title { - font-size: 30px; - line-height: 28px; - font-weight: 900; - } - - &__info-bar { - margin-top: 16px; - display: flex; - justify-content: space-between; - align-items: center; - } - - &__editor-info { - font-size: 14px; - color: var(--g-color-text-secondary); - } -} diff --git a/playground/src/app/components/C9RComponent.tsx b/playground/src/app/components/C9RComponent.tsx deleted file mode 100644 index 1f4d2e791..000000000 --- a/playground/src/app/components/C9RComponent.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; - -import block from 'bem-cn-lite'; - -import './C9RComponent.scss'; - -const b = block('c9r-component'); - -export const C9RComponent = () => { - return ( -
-
-
C9R
-
-
-
Editor alpha 1.0.0
-
-
- ); -}; - -export default C9RComponent; diff --git a/playground/src/app/layout.tsx b/playground/src/app/layout.tsx deleted file mode 100644 index 9efb03f02..000000000 --- a/playground/src/app/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import '@gravity-ui/uikit/styles/fonts.css'; -import '@gravity-ui/uikit/styles/styles.css'; - -import {DEFAULT_BODY_CLASSNAME} from '../constants'; -import '../styles/globals.scss'; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/playground/src/app/page.scss b/playground/src/app/page.scss deleted file mode 100644 index e4fa88e63..000000000 --- a/playground/src/app/page.scss +++ /dev/null @@ -1,9 +0,0 @@ -$block: '.home'; - -#{$block} { - height: 100vh; - - &__test { - padding: 16px; - } -} diff --git a/playground/src/app/pc-2/page.tsx b/playground/src/app/pc-2/page.tsx deleted file mode 100644 index 319e2bcf8..000000000 --- a/playground/src/app/pc-2/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import {NavigationData, PageConstructor, PageConstructorProvider} from '../../../../src'; - -import content from './content.json'; -import navigation from './navigation.json'; -import './styles.scss'; - -export default function Home() { - return ( - - - - ); -} diff --git a/playground/src/app/pc/page.tsx b/playground/src/app/pc/page.tsx deleted file mode 100644 index 5a7dbab8e..000000000 --- a/playground/src/app/pc/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; - -import {PageConstructor, PageConstructorProvider} from '../../../../src'; - -import content from './content.json'; - -export default function Home() { - return ( - - - - ); -} diff --git a/playground/src/constants.ts b/playground/src/constants.ts deleted file mode 100644 index 428aee7cb..000000000 --- a/playground/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {Theme} from '@gravity-ui/uikit'; - -export const DEFAULT_THEME: Theme = 'light'; -export const DEFAULT_BODY_CLASSNAME = `g-root g-root_theme_${DEFAULT_THEME}`; diff --git a/playground/src/main.tsx b/playground/src/main.tsx new file mode 100644 index 000000000..b87d84bd4 --- /dev/null +++ b/playground/src/main.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import ReactDOM from 'react-dom/client'; +import {BrowserRouter} from 'react-router'; +import '@gravity-ui/uikit/styles/fonts.css'; +import '@gravity-ui/uikit/styles/styles.css'; +import './styles/globals.scss'; + +import Router from './router'; + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + + + , +); diff --git a/playground/src/pages/editor/editor.scss b/playground/src/pages/editor/editor.scss new file mode 100644 index 000000000..69b14306b --- /dev/null +++ b/playground/src/pages/editor/editor.scss @@ -0,0 +1,13 @@ +$block: '.editor'; + +#{$block} { + height: 100vh; + + &__other { + padding: 16px; + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-start; + } +} diff --git a/playground/src/app/page.tsx b/playground/src/pages/editor/editor.tsx similarity index 52% rename from playground/src/app/page.tsx rename to playground/src/pages/editor/editor.tsx index d3ff81a73..69196b779 100644 --- a/playground/src/app/page.tsx +++ b/playground/src/pages/editor/editor.tsx @@ -1,35 +1,41 @@ -'use client'; -import {ThemeProvider} from '@gravity-ui/uikit'; -import block from 'bem-cn-lite'; import * as React from 'react'; +import block from 'bem-cn-lite'; +import {useNavigate} from 'react-router'; +import {Button, Text, ThemeProvider} from '@gravity-ui/uikit'; + +import {Editor} from '../../../../src/editor-v2'; -import {Editor} from '../../../src/editor-v2'; -import C9RComponent from './components/C9RComponent'; +import './editor.scss'; -import './page.scss'; +const b = block('editor'); -const b = block('home'); +const Other = () => { + const navigate = useNavigate(); -const Test = () =>
custom test
; + return ( +
+ Form Editor + +
+ ); +}; const COMPONENTS_CONFIG = { - middleTop: Test, - leftTop: [C9RComponent], leftTabs: [ { - id: 'test', - title: 'TEST', - component: Test, + id: 'other', + title: 'OTHER', + component: Other, }, ], }; -export default function Home() { +export default function EditorPage() { const [initialUrl, setInitialUrl] = React.useState(''); React.useEffect(() => { if (typeof window !== 'undefined') { - setInitialUrl(window.location.origin + '/pc'); + setInitialUrl(window.location.origin + '/?page=pc&id=1'); } }, []); diff --git a/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.scss b/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.scss new file mode 100644 index 000000000..dfc01cd7b --- /dev/null +++ b/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.scss @@ -0,0 +1,3 @@ +.add-property-button { + margin-top: 8px; +} diff --git a/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.tsx b/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.tsx new file mode 100644 index 000000000..42630570f --- /dev/null +++ b/playground/src/pages/form/components/AddPropertyButton/AddPropertyButton.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import {Button, DropdownMenu} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {ConfigInput} from '../../../../../../src/editor-v2'; + +import './AddPropertyButton.scss'; + +const b = block('add-property-button'); + +interface AddPropertyButtonProps { + inputTypeMenuItems: Array<{action: () => void; text: string; type: string}>; + onAdd: (type: ConfigInput['type']) => void; + buttonText?: string; +} + +export const AddPropertyButton: React.FC = ({ + inputTypeMenuItems, + onAdd, + buttonText = '+ Add Property', +}) => ( +
+ ({ + ...item, + action: () => { + onAdd(item.type as ConfigInput['type']); + }, + }))} + renderSwitcher={(props) => ( + + )} + /> +
+); diff --git a/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.scss b/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.scss new file mode 100644 index 000000000..e36cc0a33 --- /dev/null +++ b/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.scss @@ -0,0 +1,29 @@ +.array-field-renderer { + &__config { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__config-row { + display: flex; + align-items: center; + gap: 8px; + + & > span { + min-width: 90px; + font-weight: 500; + } + + & > div { + flex: 1; + } + } + + &__nested-fields { + margin-top: 12px; + margin-left: 16px; + padding-left: 12px; + border-left: 2px solid var(--g-color-line-generic); + } +} diff --git a/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx b/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx new file mode 100644 index 000000000..8da5e9011 --- /dev/null +++ b/playground/src/pages/form/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import {Button, DropdownMenu, Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {ArrayObjectInput, ConfigInput} from '../../../../../../src/editor-v2'; +import {ConfigRow} from '../ConfigRow/ConfigRow'; +import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton'; +import {SectionHeader} from '../SectionHeader/SectionHeader'; +import {useFormContext} from '../../hooks/FormContext'; +import {FormArrayField, InputTypeMenuItem} from '../../hooks/types'; + +import './ArrayFieldRenderer.scss'; + +const b = block('array-field-renderer'); + +interface ArrayFieldRendererProps { + field: FormArrayField; + fieldName: string; + inputTypeMenuItems: InputTypeMenuItem[]; + renderNestedField: ( + field: ConfigInput, + index: number, + parentId: string, + optionIndex?: number, + ) => React.ReactNode; +} + +export const ArrayFieldRenderer: React.FC = ({ + field, + fieldName: _fieldName, + inputTypeMenuItems, + renderNestedField, +}) => { + const {addObjectProperty, updateField} = useFormContext(); + + return ( +
+ {/* Array Type Selection */} +
+ Array Type: + updateField(field.id, {arrayType: 'text'}), + text: 'Text', + }, + { + action: () => updateField(field.id, {arrayType: 'object'}), + text: 'Object', + }, + ]} + renderSwitcher={(props) => ( + + )} + /> +
+ + {/* Button Text Configuration */} + updateField(field.id, {buttonText: value})} + /> + + {/* Properties for Object Array Type */} + {field.arrayType === 'object' && ( +
+ + + {(field as ArrayObjectInput)?.properties?.map((property, index) => + renderNestedField(property, index, field.id), + )} + + addObjectProperty(field.id, type)} + /> +
+ )} +
+ ); +}; diff --git a/playground/src/pages/form/components/ConfigRow/ConfigRow.scss b/playground/src/pages/form/components/ConfigRow/ConfigRow.scss new file mode 100644 index 000000000..320669a5b --- /dev/null +++ b/playground/src/pages/form/components/ConfigRow/ConfigRow.scss @@ -0,0 +1,14 @@ +.config-row { + display: flex; + align-items: center; + gap: 8px; + + & > span { + min-width: 90px; + font-weight: 500; + } + + & > div { + flex: 1; + } +} diff --git a/playground/src/pages/form/components/ConfigRow/ConfigRow.tsx b/playground/src/pages/form/components/ConfigRow/ConfigRow.tsx new file mode 100644 index 000000000..ec728dad8 --- /dev/null +++ b/playground/src/pages/form/components/ConfigRow/ConfigRow.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import {Text, TextInput} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './ConfigRow.scss'; + +const b = block('config-row'); + +interface ConfigRowProps { + label: string; + value: string; + onUpdate: (value: string) => void; +} + +const ConfigRowComponent: React.FC = ({label, value, onUpdate}) => { + // Memoize the onUpdate callback to prevent unnecessary rerenders + const handleUpdate = React.useCallback( + (newValue: string) => { + onUpdate(newValue); + }, + [onUpdate], + ); + + return ( +
+ {label}: + +
+ ); +}; + +// Use React.memo to prevent unnecessary rerenders +export const ConfigRow = React.memo(ConfigRowComponent); diff --git a/playground/src/pages/form/components/FieldCard/FieldCard.scss b/playground/src/pages/form/components/FieldCard/FieldCard.scss new file mode 100644 index 000000000..c2a456ea7 --- /dev/null +++ b/playground/src/pages/form/components/FieldCard/FieldCard.scss @@ -0,0 +1,17 @@ +.field-card { + padding: 16px; + + &__config { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__nested-field { + margin-top: 8px; + padding: 12px; + background-color: var(--g-color-base-generic); + border-radius: 6px; + border: 1px solid var(--g-color-line-generic); + } +} diff --git a/playground/src/pages/form/components/FieldCard/FieldCard.tsx b/playground/src/pages/form/components/FieldCard/FieldCard.tsx new file mode 100644 index 000000000..1a6f33e4a --- /dev/null +++ b/playground/src/pages/form/components/FieldCard/FieldCard.tsx @@ -0,0 +1,164 @@ +import * as React from 'react'; +import {Card} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {ConfigInput} from '../../../../../../src/editor-v2'; +import {ConfigRow} from '../ConfigRow/ConfigRow'; +import {FieldHeader} from '../FieldHeader/FieldHeader'; +import {ArrayFieldRenderer} from '../ArrayFieldRenderer/ArrayFieldRenderer'; +import {ObjectFieldRenderer} from '../ObjectFieldRenderer/ObjectFieldRenderer'; +import {OptionsRenderer} from '../OptionsRenderer/OptionsRenderer'; +import {SelectFieldRenderer} from '../SelectFieldRenderer/SelectFieldRenderer'; +import {useFormContext} from '../../hooks/FormContext'; +import { + FormAnyOfField, + FormArrayField, + FormField, + FormObjectField, + FormOneOfField, + InputTypeMenuItem, +} from '../../hooks/types'; + +import './FieldCard.scss'; + +const b = block('field-card'); + +interface FieldCardProps { + field: FormField; + inputTypeMenuItems: InputTypeMenuItem[]; +} + +export const FieldCard: React.FC = ({field, inputTypeMenuItems}) => { + const { + removeField, + updateField, + removeObjectProperty, + removeOptionProperty, + updateObjectProperty, + updateOptionProperty, + } = useFormContext(); + + // Render a nested field (for object properties and oneOf/anyOf options) + const renderNestedField = ( + nestedField: ConfigInput, + index: number, + parentId: string, + optionIndex?: number, + ) => { + const isInOption = optionIndex !== undefined; + + const handleRemove = () => { + if (isInOption) { + removeOptionProperty(parentId, optionIndex, index); + } else { + removeObjectProperty(parentId, index); + } + }; + + const handleUpdate = (updates: Partial) => { + if (isInOption) { + updateOptionProperty(parentId, optionIndex, index, updates); + } else { + updateObjectProperty(parentId, index, updates); + } + }; + + return ( +
+ + +
+ handleUpdate({name: value})} + /> + handleUpdate({title: value})} + /> + + {renderFieldTypeSpecificContent(nestedField, parentId)} +
+
+ ); + }; + + // Render field-type specific content using switch/case + const renderFieldTypeSpecificContent = (fieldConfig: ConfigInput, parentId: string) => { + switch (fieldConfig.type) { + case 'object': + return ( + + ); + + case 'oneOf': + return ( + + ); + + case 'anyOf': + return ( + + ); + + case 'select': + return ; + + case 'array': + return ( + + ); + + default: + return null; + } + }; + + return ( + + removeField(field.id)} /> + +
+ updateField(field.id, {name: value})} + /> + updateField(field.id, {title: value})} + /> + + {/* Render type-specific configuration */} + {renderFieldTypeSpecificContent(field, field.id)} +
+
+ ); +}; diff --git a/playground/src/pages/form/components/FieldHeader/FieldHeader.scss b/playground/src/pages/form/components/FieldHeader/FieldHeader.scss new file mode 100644 index 000000000..91ce8b646 --- /dev/null +++ b/playground/src/pages/form/components/FieldHeader/FieldHeader.scss @@ -0,0 +1,6 @@ +.field-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} diff --git a/playground/src/pages/form/components/FieldHeader/FieldHeader.tsx b/playground/src/pages/form/components/FieldHeader/FieldHeader.tsx new file mode 100644 index 000000000..0d7497775 --- /dev/null +++ b/playground/src/pages/form/components/FieldHeader/FieldHeader.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import {Button, Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './FieldHeader.scss'; + +const b = block('field-header'); + +interface FieldHeaderProps { + title: string; + onRemove: () => void; + variant?: 'subheader-2' | 'subheader-3'; + buttonSize?: 's' | 'xs'; +} + +export const FieldHeader: React.FC = ({ + title, + onRemove, + variant = 'subheader-2', + buttonSize = 's', +}) => ( +
+ {title} + +
+); diff --git a/playground/src/pages/form/components/FormBuilder/FormBuilder.scss b/playground/src/pages/form/components/FormBuilder/FormBuilder.scss new file mode 100644 index 000000000..ef47824af --- /dev/null +++ b/playground/src/pages/form/components/FormBuilder/FormBuilder.scss @@ -0,0 +1,110 @@ +.form-builder { + width: 100%; + + &__field { + margin-bottom: 16px; + } + + &__fields-list { + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 12px; + } + + &__field-card { + padding: 16px; + border: 1px solid #d1d5db; + border-radius: 8px; + background-color: #ffffff; + } + + &__field-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + &__field-config { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__config-row { + display: flex; + align-items: center; + gap: 8px; + + & > span { + min-width: 90px; + font-weight: 500; + } + + & > div { + flex: 1; + } + } + + &__nested-fields { + margin-top: 12px; + margin-left: 16px; + padding-left: 12px; + border-left: 2px solid #e5e7eb; + } + + &__nested-field { + margin-top: 8px; + padding: 12px; + background-color: #f9fafb; + border-radius: 6px; + border: 1px solid #e5e7eb; + } + + &__nested-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + } + + &__option { + margin-top: 12px; + padding: 12px; + background-color: #f9fafb; + border-radius: 6px; + border: 1px solid #e5e7eb; + } + + &__option-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + } + + &__add-field-button { + margin-top: 8px; + } + + &__enum-option { + margin-top: 8px; + padding: 12px; + background-color: #f3f4f6; + border-radius: 6px; + border: 1px solid #e5e7eb; + display: flex; + flex-direction: column; + gap: 8px; + + .forms__config-row { + margin-bottom: 4px; + } + + button { + align-self: flex-end; + margin-top: 4px; + } + } +} diff --git a/playground/src/pages/form/components/FormBuilder/FormBuilder.tsx b/playground/src/pages/form/components/FormBuilder/FormBuilder.tsx new file mode 100644 index 000000000..fc0b661ec --- /dev/null +++ b/playground/src/pages/form/components/FormBuilder/FormBuilder.tsx @@ -0,0 +1,94 @@ +'use client'; + +import * as React from 'react'; +import {Button, DropdownMenu} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {FieldCard} from '../FieldCard/FieldCard'; +import {useFormContext} from '../../hooks/FormContext'; +import {FormField, InputTypeMenuItem} from '../../hooks/types'; + +import './FormBuilder.scss'; + +const b = block('form-builder'); + +interface FormBuilderProps { + className?: string; + formFields: Array; +} + +export const FormBuilder: React.FC = ({className, formFields}) => { + const {addField} = useFormContext(); + + const inputTypeMenuItems: InputTypeMenuItem[] = [ + { + type: 'text', + action: () => addField('text'), + text: 'Text Input', + }, + { + type: 'number', + action: () => addField('number'), + text: 'Number Input', + }, + { + type: 'boolean', + action: () => addField('boolean'), + text: 'Boolean Input', + }, + { + type: 'textarea', + action: () => addField('textarea'), + text: 'Textarea Input', + }, + { + type: 'select', + action: () => addField('select'), + text: 'Select Input', + }, + { + type: 'object', + action: () => addField('object'), + text: 'Object Input', + }, + { + type: 'array', + action: () => addField('array'), + text: 'Array Input', + }, + { + type: 'oneOf', + action: () => addField('oneOf'), + text: 'OneOf Input', + }, + { + type: 'anyOf', + action: () => addField('anyOf'), + text: 'AnyOf Input', + }, + ]; + + return ( +
+
+ ( + + )} + /> +
+ +
+ {formFields.map((field) => ( + + ))} +
+
+ ); +}; diff --git a/playground/src/pages/form/components/FormOutput/FormOutput.scss b/playground/src/pages/form/components/FormOutput/FormOutput.scss new file mode 100644 index 000000000..0fea24c8f --- /dev/null +++ b/playground/src/pages/form/components/FormOutput/FormOutput.scss @@ -0,0 +1,13 @@ +.form-output { + margin-top: 24px; + + &__content { + background-color: var(--g-color-base-generic); + padding: 12px; + border-radius: 6px; + border: 1px solid var(--g-color-line-generic); + font-family: monospace; + font-size: 12px; + white-space: pre-wrap; + } +} diff --git a/playground/src/pages/form/components/FormOutput/FormOutput.tsx b/playground/src/pages/form/components/FormOutput/FormOutput.tsx new file mode 100644 index 000000000..fc777d563 --- /dev/null +++ b/playground/src/pages/form/components/FormOutput/FormOutput.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import {Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './FormOutput.scss'; + +const b = block('form-output'); + +interface FormOutputProps { + title: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + className?: string; +} + +export const FormOutput: React.FC = ({title, data, className}) => { + return ( +
+ + {title} + +
{JSON.stringify(data, null, 2)}
+
+ ); +}; diff --git a/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.scss b/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.scss new file mode 100644 index 000000000..bcf9aea3b --- /dev/null +++ b/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.scss @@ -0,0 +1,8 @@ +.object-field-renderer { + &__nested-fields { + margin-top: 12px; + margin-left: 16px; + padding-left: 12px; + border-left: 2px solid var(--g-color-line-generic); + } +} diff --git a/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx b/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx new file mode 100644 index 000000000..3f8105c3b --- /dev/null +++ b/playground/src/pages/form/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import block from 'bem-cn-lite'; +import {ConfigInput} from '../../../../../../src/editor-v2'; +import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton'; +import {SectionHeader} from '../SectionHeader/SectionHeader'; +import {useFormContext} from '../../hooks/FormContext'; +import {FormObjectField, InputTypeMenuItem} from '../../hooks/types'; + +import './ObjectFieldRenderer.scss'; + +const b = block('object-field-renderer'); + +interface ObjectFieldRendererProps { + field: FormObjectField; + fieldName: string; + inputTypeMenuItems: InputTypeMenuItem[]; + renderNestedField: ( + field: ConfigInput, + index: number, + parentId: string, + optionIndex?: number, + ) => React.ReactNode; +} + +export const ObjectFieldRenderer: React.FC = ({ + field, + fieldName: _fieldName, + inputTypeMenuItems, + renderNestedField, +}) => { + const {addObjectProperty} = useFormContext(); + + return ( +
+ + + {field.properties.map((property, index) => + renderNestedField(property, index, field.id), + )} + + addObjectProperty(field.id, type)} + /> +
+ ); +}; diff --git a/playground/src/pages/form/components/OptionHeader/OptionHeader.scss b/playground/src/pages/form/components/OptionHeader/OptionHeader.scss new file mode 100644 index 000000000..8a29db70c --- /dev/null +++ b/playground/src/pages/form/components/OptionHeader/OptionHeader.scss @@ -0,0 +1,6 @@ +.option-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} diff --git a/playground/src/pages/form/components/OptionHeader/OptionHeader.tsx b/playground/src/pages/form/components/OptionHeader/OptionHeader.tsx new file mode 100644 index 000000000..0b4a444f4 --- /dev/null +++ b/playground/src/pages/form/components/OptionHeader/OptionHeader.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import {Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './OptionHeader.scss'; + +const b = block('option-header'); + +interface OptionHeaderProps { + title: string; +} + +export const OptionHeader: React.FC = ({title}) => ( +
+ {title} +
+); diff --git a/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.scss b/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.scss new file mode 100644 index 000000000..c70d16461 --- /dev/null +++ b/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.scss @@ -0,0 +1,38 @@ +.options-renderer { + &__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + } + + &__option-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + } + + &__add-option-button { + margin-left: 8px; + } + + &__remove-option-button { + margin-left: 8px; + } + + &__nested-fields { + margin-top: 12px; + margin-left: 16px; + padding-left: 12px; + border-left: 2px solid var(--g-color-line-generic); + } + + &__option { + margin-top: 12px; + padding: 12px; + background-color: var(--g-color-base-background); + border-radius: 6px; + border: 1px solid var(--g-color-line-generic); + } +} diff --git a/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.tsx b/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.tsx new file mode 100644 index 000000000..2081aa4d2 --- /dev/null +++ b/playground/src/pages/form/components/OptionsRenderer/OptionsRenderer.tsx @@ -0,0 +1,110 @@ +import * as React from 'react'; +import block from 'bem-cn-lite'; +import {Button} from '@gravity-ui/uikit'; +import {ConfigInput} from '../../../../../../src/editor-v2'; +import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton'; +import {OptionHeader} from '../OptionHeader/OptionHeader'; +import {SectionHeader} from '../SectionHeader/SectionHeader'; +import {useFormContext} from '../../hooks/FormContext'; +import {FormOptionsField, InputTypeMenuItem} from '../../hooks/types'; + +import './OptionsRenderer.scss'; + +const b = block('options-renderer'); + +interface OptionsRendererProps { + field: FormOptionsField; + fieldName: string; + inputTypeMenuItems: InputTypeMenuItem[]; + renderNestedField: ( + field: ConfigInput, + index: number, + parentId: string, + optionIndex?: number, + ) => React.ReactNode; +} + +export const OptionsRenderer: React.FC = ({ + field, + fieldName: _fieldName, + inputTypeMenuItems, + renderNestedField, +}) => { + const {addOptionProperty, addOption, removeOption} = useFormContext(); + + // Отладочный вывод для проверки поля + console.log('OptionsRenderer rendering field:', field); + console.log('OptionsRenderer options:', field.options); + + return ( +
+
+ + +
+ + {field.options.map((option, optionIndex) => ( +
+
+ + {field.options.length > 1 && ( + + )} +
+ +
+ + + {option.properties.map((property, propIndex) => + renderNestedField(property, propIndex, field.id, optionIndex), + )} + + { + console.log('AddPropertyButton onAdd called with:', { + fieldId: field.id, + optionIndex, + isOneOf: field.type === 'oneOf', + type, + currentProperties: option.properties, + }); + addOptionProperty( + field.id, + optionIndex, + field.type === 'oneOf', + type, + ); + }} + /> +
+
+ ))} +
+ ); +}; diff --git a/playground/src/pages/form/components/SectionHeader/SectionHeader.scss b/playground/src/pages/form/components/SectionHeader/SectionHeader.scss new file mode 100644 index 000000000..1e6fada2f --- /dev/null +++ b/playground/src/pages/form/components/SectionHeader/SectionHeader.scss @@ -0,0 +1,3 @@ +.section-header { + margin-bottom: 8px; +} diff --git a/playground/src/pages/form/components/SectionHeader/SectionHeader.tsx b/playground/src/pages/form/components/SectionHeader/SectionHeader.tsx new file mode 100644 index 000000000..e90831a78 --- /dev/null +++ b/playground/src/pages/form/components/SectionHeader/SectionHeader.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import {Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './SectionHeader.scss'; + +const b = block('section-header'); + +interface SectionHeaderProps { + title: string; + variant?: 'body-1' | 'body-2'; +} + +export const SectionHeader: React.FC = ({title, variant = 'body-1'}) => ( +
+ {title} +
+); diff --git a/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.scss b/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.scss new file mode 100644 index 000000000..c55dd6931 --- /dev/null +++ b/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.scss @@ -0,0 +1,45 @@ +.select-field-renderer { + &__config-row { + display: flex; + align-items: center; + gap: 8px; + + & > span { + min-width: 90px; + font-weight: 500; + } + + & > div { + flex: 1; + } + + &_vertical { + flex-direction: column; + align-items: flex-start; + } + } + + &__options-title { + margin-bottom: 8px; + } + + &__enum-option { + margin-top: 8px; + padding: 12px; + background-color: var(--g-color-base-generic-ultralight); + border-radius: 6px; + border: 1px solid var(--g-color-line-generic); + display: flex; + flex-direction: column; + gap: 8px; + } + + &__remove-button { + align-self: flex-end; + margin-top: 4px; + } + + &__add-button { + margin-top: 8px; + } +} diff --git a/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.tsx b/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.tsx new file mode 100644 index 000000000..ec442b819 --- /dev/null +++ b/playground/src/pages/form/components/SelectFieldRenderer/SelectFieldRenderer.tsx @@ -0,0 +1,145 @@ +import * as React from 'react'; +import {Button, DropdownMenu, Text, TextInput} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {FormField} from '../../hooks/types'; +import {useFormContext} from '../../hooks/FormContext'; + +import './SelectFieldRenderer.scss'; + +const b = block('select-field-renderer'); + +interface SelectFieldRendererProps { + field: FormField; +} + +interface SelectFieldOption { + content: string; + value: string; +} + +export const SelectFieldRenderer: React.FC = ({field}) => { + const {updateField} = useFormContext(); + + // Проверяем, что поле имеет тип 'select' + if (field.type !== 'select') { + return null; + } + + // Безопасное приведение типа для доступа к специфичным полям + const view = field.view as 'select' | 'radiobutton'; + const mode = field.mode as 'single' | 'multiple'; + const enumValues = (field.enum || []) as SelectFieldOption[]; + + return ( + +
+ View: + updateField(field.id, {view: 'select'}), + text: 'Select', + }, + { + action: () => updateField(field.id, {view: 'radiobutton'}), + text: 'Radio Button', + }, + ]} + renderSwitcher={(props) => ( + + )} + /> +
+ +
+ Mode: + updateField(field.id, {mode: 'single'}), + text: 'Single', + }, + { + action: () => updateField(field.id, {mode: 'multiple'}), + text: 'Multiple', + }, + ]} + renderSwitcher={(props) => ( + + )} + /> +
+ +
+ + Options: + + {enumValues.map((option, index) => ( +
+
+ Label: + { + const newEnum = [...enumValues]; + newEnum[index] = { + ...newEnum[index], + content: value, + }; + updateField(field.id, {enum: newEnum}); + }} + size="s" + /> +
+
+ Value: + { + const newEnum = [...enumValues]; + newEnum[index] = { + ...newEnum[index], + value, + }; + updateField(field.id, {enum: newEnum}); + }} + size="s" + /> +
+ +
+ ))} + +
+
+ ); +}; diff --git a/playground/src/pages/form/form.scss b/playground/src/pages/form/form.scss new file mode 100644 index 000000000..fe6834284 --- /dev/null +++ b/playground/src/pages/form/form.scss @@ -0,0 +1,56 @@ +$block: '.form'; + +#{$block} { + width: 100%; + height: 100vh; + overflow: hidden; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; + margin-bottom: 16px; + } + + &__header-buttons { + display: flex; + gap: 8px; + } + + &__panel-group { + width: 100%; + height: 100%; + } + + &__panel-content { + padding: 20px; + height: 100%; + overflow: auto; + background-color: var(--g-color-base-background); + box-sizing: border-box; + } + + &__resize-handle { + background-color: var(--g-color-line-generic); + transition: background-color 0.2s; + + &:hover { + background-color: var(--g-color-line-generic-hover); + } + + &_horizontal { + height: 8px; + cursor: row-resize; + } + + &_vertical { + width: 8px; + cursor: col-resize; + } + } + + &__form { + margin-top: 20px; + } +} diff --git a/playground/src/pages/form/form.tsx b/playground/src/pages/form/form.tsx new file mode 100644 index 000000000..265fa5d66 --- /dev/null +++ b/playground/src/pages/form/form.tsx @@ -0,0 +1,105 @@ +import {Button, Text, ThemeProvider} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels'; +import {useNavigate} from 'react-router'; +import DynamicForm from '../../../../src/editor-v2/components/DynamicForm/DynamicForm'; +import {FormBuilder} from './components/FormBuilder/FormBuilder'; +import {FormOutput} from './components/FormOutput/FormOutput'; +import {FormProvider, useFormContext} from './hooks/FormContext'; + +import './form.scss'; + +const b = block('form'); + +// Компонент содержимого формы, использующий контекст +const FormContent = () => { + const {formFields, contentConfig, handleFormUpdate, resetForm} = useFormContext(); + + const navigate = useNavigate(); + + return ( +
+ + {/* Left Panel Group */} + + + {/* Form Builder Panel */} + +
+
+ Form Builder +
+ + +
+
+
+ +
+
+
+ + {/* Resize Handle */} + + + {/* Form Output Panel */} + +
+ + rest)} + /> +
+
+
+
+ + {/* Resize Handle */} + + + {/* Right Panel Group */} + + + {/* Form Preview Panel */} + +
+ Form Preview + +
+
+ + {/* Resize Handle */} + + + {/* Form Output Panel */} + +
+ +
+
+
+
+
+
+ ); +}; + +// Главный компонент формы, оборачивающий содержимое в провайдер +export default function FormPage() { + return ( + + + + + + ); +} diff --git a/playground/src/pages/form/hooks/FormContext.tsx b/playground/src/pages/form/hooks/FormContext.tsx new file mode 100644 index 000000000..c71e93281 --- /dev/null +++ b/playground/src/pages/form/hooks/FormContext.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import {useFormFields} from './useFormFields'; +import {FormContextType} from './types'; + +// Создаем контекст с дефолтными значениями +export const FormContext = React.createContext({} as FormContextType); + +// Provider компонент, который будет оборачивать наше приложение +export const FormProvider: React.FC<{children: React.ReactNode}> = ({children}) => { + // Используем наш существующий хук + const formFieldsData = useFormFields(); + + return {children}; +}; + +// Кастомный хук для использования контекста +export const useFormContext = () => { + const context = React.useContext(FormContext); + + if (!context) { + throw new Error('useFormContext must be used within a FormProvider'); + } + + return context; +}; diff --git a/playground/src/pages/form/hooks/types.ts b/playground/src/pages/form/hooks/types.ts new file mode 100644 index 000000000..24ea41838 --- /dev/null +++ b/playground/src/pages/form/hooks/types.ts @@ -0,0 +1,81 @@ +import { + AnyOfInput, + ArrayObjectInput, + ArrayTextInput, + ConfigInput, + ObjectInput, + OneOfInput, +} from '../../../../../src/editor-v2'; + +// Базовый тип для поля формы +export type FormField = ConfigInput & {id: string}; + +// Типизированные поля формы +export type FormObjectField = ObjectInput & {id: string}; +export type FormArrayField = (ArrayTextInput | ArrayObjectInput) & { + id: string; + properties?: FormField[]; +}; +export type FormOneOfField = OneOfInput & {id: string}; +export type FormAnyOfField = AnyOfInput & {id: string}; +export type FormOptionsField = (OneOfInput | AnyOfInput) & {id: string}; + +// Тип для элемента меню выбора типа поля +export interface InputTypeMenuItem { + action: () => void; + text: string; + type: string; +} + +// Тип для конфигурации контента +export interface ContentConfig { + [key: string]: unknown; +} + +// Интерфейс для методов работы с полями формы +export interface FormFieldsActions { + // Основные методы для работы с полями + createField: (type: ConfigInput['type'], name?: string) => FormField; + addField: (type: ConfigInput['type']) => void; + removeField: (fieldId: string) => void; + updateField: (fieldId: string, updates: Partial) => void; + + // Методы для работы с объектными полями + addObjectProperty: (objectId: string, type: ConfigInput['type']) => void; + removeObjectProperty: (fieldId: string, propertyIndex: number) => void; + updateObjectProperty: ( + fieldId: string, + propertyIndex: number, + updates: Partial, + ) => void; + + // Методы для работы с опциями + addOption: (fieldId: string) => void; + removeOption: (fieldId: string, optionIndex: number) => void; + addOptionProperty: ( + fieldId: string, + optionIndex: number, + isOneOf: boolean, + type?: ConfigInput['type'], + ) => void; + removeOptionProperty: (fieldId: string, optionIndex: number, propertyIndex: number) => void; + updateOptionProperty: ( + fieldId: string, + optionIndex: number, + propertyIndex: number, + updates: Partial, + ) => void; + + // Сброс формы + resetForm: () => void; +} + +// Интерфейс для данных формы +export interface FormData { + formFields: Array; + contentConfig: ContentConfig; + handleFormUpdate: (key: string, value: unknown) => void; +} + +// Полный интерфейс контекста формы +export interface FormContextType extends FormData, FormFieldsActions {} diff --git a/playground/src/pages/form/hooks/useFormFields.ts b/playground/src/pages/form/hooks/useFormFields.ts new file mode 100644 index 000000000..59cb6e386 --- /dev/null +++ b/playground/src/pages/form/hooks/useFormFields.ts @@ -0,0 +1,965 @@ +import * as React from 'react'; +import {set} from 'lodash'; +import {AnyOfInput, ConfigInput, ObjectInput, OneOfInput} from '../../../../../src/editor-v2'; +import {ContentConfig, FormArrayField, FormField} from './types'; + +// Key for storing form data in localStorage +const FORM_STORAGE_KEY = 'formBuilder_fields'; +const FORM_ID_COUNTER_KEY = 'formBuilder_nextId'; + +export const useFormFields = () => { + // Initialize state from localStorage if available + const [formFields, setFormFields] = React.useState(() => { + try { + const savedFields = localStorage.getItem(FORM_STORAGE_KEY); + return savedFields ? JSON.parse(savedFields) : []; + } catch (error) { + console.error('Error loading form fields from localStorage:', error); + return []; + } + }); + + // Отслеживаем изменения formFields + React.useEffect(() => { + console.log('formFields updated:', formFields); + }, [formFields]); + + const [nextId, setNextId] = React.useState(() => { + try { + const savedNextId = localStorage.getItem(FORM_ID_COUNTER_KEY); + return savedNextId ? parseInt(savedNextId, 10) : 1; + } catch (error) { + console.error('Error loading next ID from localStorage:', error); + return 1; + } + }); + + const [contentConfig, setContentConfig] = React.useState({}); + + // Save form fields to localStorage whenever they change + React.useEffect(() => { + try { + localStorage.setItem(FORM_STORAGE_KEY, JSON.stringify(formFields)); + } catch (error) { + console.error('Error saving form fields to localStorage:', error); + } + }, [formFields]); + + // Save next ID to localStorage whenever it changes + React.useEffect(() => { + try { + localStorage.setItem(FORM_ID_COUNTER_KEY, nextId.toString()); + } catch (error) { + console.error('Error saving next ID to localStorage:', error); + } + }, [nextId]); + + // Helper function to set nested object properties using dot notation + /** + * Sets a value at a nested path within an object using dot notation. + * Uses lodash's set method for reliable deep property setting. + * + * @param obj - The source object to modify + * @param path - Dot-separated path to the target property + * @param value - The value to set at the specified path + * @returns The modified object + */ + const setNestedProperty = >( + obj: T, + path: string, + value: unknown, + ): T => { + return set({...obj}, path, value); + }; + + const handleFormUpdate = (key: string, value: unknown) => { + setContentConfig((prev) => { + const newConfig = JSON.parse(JSON.stringify(prev)); // Deep clone + return setNestedProperty(newConfig, key, value); + }); + }; + + // Generate a unique ID for new fields + const generateId = () => { + const id = `field_id_${nextId}`; + setNextId((prev) => prev + 1); + return id; + }; + + // Generate a unique name for new fields + const generateName = () => { + const name = `field_${nextId}`; + return name; + }; + + // Create a new field configuration based on type + const createField = (type: ConfigInput['type'], name = ''): FormField => { + console.log('createField called with:', {type, name}); + + const fieldName = name || generateName(); + const fieldId = generateId(); + let newField: FormField; + + switch (type) { + case 'text': + newField = { + type: 'text', + name: fieldName, + title: 'Text Input', + id: fieldId, + } as FormField; + break; + case 'number': + newField = { + type: 'number', + name: fieldName, + title: 'Number Input', + id: fieldId, + } as FormField; + break; + case 'boolean': + newField = { + type: 'boolean', + name: fieldName, + title: 'Boolean Input', + id: fieldId, + } as FormField; + break; + case 'textarea': + newField = { + type: 'textarea', + name: fieldName, + title: 'Textarea Input', + id: fieldId, + } as FormField; + break; + case 'select': + newField = { + type: 'select', + name: fieldName, + title: 'Select Input', + view: 'select', + mode: 'single', + enum: [ + {content: 'Option 1', value: 'option1'}, + {content: 'Option 2', value: 'option2'}, + ], + id: fieldId, + } as FormField; + break; + case 'object': + newField = { + type: 'object', + name: fieldName, + title: 'Object Input', + properties: [ + { + type: 'text', + name: 'property1', + title: 'Property 1', + id: generateId(), + } as FormField, + ], + id: fieldId, + } as FormField; + break; + case 'array': + newField = { + type: 'array', + name: fieldName, + title: 'Array Input', + buttonText: 'Add Item', + arrayType: 'text', + id: fieldId, + } as FormField; + break; + case 'oneOf': + newField = { + type: 'oneOf', + name: fieldName, + title: 'OneOf Input', + options: [ + { + title: 'Option A', + value: 'optionA', + properties: [ + { + type: 'text', + name: 'textField', + title: 'Text Field', + id: generateId(), + } as FormField, + ], + }, + { + title: 'Option B', + value: 'optionB', + properties: [ + { + type: 'number', + name: 'numberField', + title: 'Number Field', + id: generateId(), + } as FormField, + ], + }, + ], + id: fieldId, + } as FormField; + break; + case 'anyOf': + newField = { + type: 'anyOf', + name: fieldName, + title: 'AnyOf Input', + options: [ + { + title: 'Option A', + value: 'optionA', + properties: [ + { + type: 'text', + name: 'textField', + title: 'Text Field', + id: generateId(), + } as FormField, + ], + }, + { + title: 'Option B', + value: 'optionB', + properties: [ + { + type: 'boolean', + name: 'booleanField', + title: 'Boolean Field', + id: generateId(), + } as FormField, + ], + }, + ], + id: fieldId, + } as FormField; + break; + default: + return createField('text', fieldName); + } + + console.log('Created new field:', newField); + return newField; + }; + + // Helper function to update a field by ID in a nested structure + const updateFieldById = ( + fields: FormField[], + fieldId: string, + updates: Partial, + ): FormField[] => { + return fields.map((field) => { + // If this is the field we're looking for, update it + if (field.id === fieldId) { + return {...field, ...updates} as FormField; + } + + // Check if this field has properties (for object type) + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: updateFieldById( + objectField.properties as FormField[], + fieldId, + updates, + ), + } as FormField; + } + + // Check if this field has options (for oneOf/anyOf types) + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: updateFieldById( + option.properties as FormField[], + fieldId, + updates, + ), + })), + } as FormField; + } + + // Check if this field has array items with properties + if (field.type === 'array' && field.arrayType === 'object') { + const arrayField = field as FormArrayField; + return { + ...field, + properties: updateFieldById( + arrayField.properties as FormField[], + fieldId, + updates, + ), + } as FormField; + } + + return field; + }); + }; + + // Add a top-level field + const addField = (type: ConfigInput['type']) => { + const newField = createField(type); + setFormFields((prev) => [...prev, newField] as FormField[]); + }; + + // Remove a field + const removeField = (fieldId: string) => { + setFormFields((prev) => prev.filter((field) => field.id !== fieldId) as FormField[]); + }; + + // Update a field - now using the field's ID for stability and supporting nested fields + const updateField = (fieldId: string, updates: Partial) => { + setFormFields((prev) => updateFieldById(prev, fieldId, updates)); + }; + + // Helper function to add a property to an object field by ID + const addPropertyToObjectById = ( + fields: FormField[], + objectId: string, + type: ConfigInput['type'] = 'text', + ): FormField[] => { + return fields.map((field) => { + // If this is the object field we're looking for, add the property to it + if (field.id === objectId && field.type === 'object') { + const objectField = field as ObjectInput; + const propertyName = `property${(objectField.properties?.length || 0) + 1}`; + const newProperty = createField(type, propertyName); + + return { + ...field, + properties: [...(objectField.properties || []), newProperty], + } as FormField; + } + + // Check if this field has properties (for object type) + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: addPropertyToObjectById( + objectField.properties as FormField[], + objectId, + type, + ), + } as FormField; + } + + // Check if this field has options (for oneOf/anyOf types) + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: addPropertyToObjectById( + option.properties as FormField[], + objectId, + type, + ), + })), + } as FormField; + } + + // Check if this field has array items with properties + if (field.type === 'array' && field.arrayType === 'object') { + const arrayField = field as FormArrayField; + + // If this is the array field we're looking for, add the property to it + if (field.id === objectId) { + const propertyName = `property${(arrayField.properties?.length || 0) + 1}`; + const newProperty = createField(type, propertyName); + + return { + ...field, + properties: [...(arrayField.properties || []), newProperty], + } as FormField; + } + + // Otherwise, recursively search through existing properties if they exist + if (arrayField.properties && arrayField.properties.length > 0) { + return { + ...field, + properties: addPropertyToObjectById(arrayField.properties, objectId, type), + } as FormField; + } + } + + return field; + }); + }; + + // Add a field to an object's properties - now using the object's ID + const addObjectProperty = (objectId: string, type: ConfigInput['type'] = 'text') => { + setFormFields((prev) => addPropertyToObjectById(prev, objectId, type)); + }; + + // Add a field to a oneOf/anyOf option's properties + const addOptionProperty = ( + fieldId: string, + optionIndex: number, + isOneOf: boolean, + type: ConfigInput['type'] = 'text', + ) => { + console.log('addOptionProperty called with:', {fieldId, optionIndex, isOneOf, type}); + + // Функция для рекурсивного поиска и обновления поля + const addPropertyToOptionRecursive = ( + fields: FormField[], + targetFieldId: string, + targetOptionIndex: number, + ): FormField[] => { + return fields.map((field) => { + // Если это искомое поле, добавляем свойство к указанной опции + if ( + field.id === targetFieldId && + (field.type === 'oneOf' || field.type === 'anyOf') + ) { + const updatedField = JSON.parse(JSON.stringify(field)); + const options = updatedField.options; + + if ( + options && + Array.isArray(options) && + targetOptionIndex >= 0 && + targetOptionIndex < options.length && + options[targetOptionIndex].properties + ) { + // Создаем новое свойство + const propertyName = `property${options[targetOptionIndex].properties.length + 1}`; + const newProperty = createField(type, propertyName); + + // Добавляем свойство в опцию + options[targetOptionIndex].properties.push(newProperty); + } + + return updatedField; + } + + // Проверяем вложенные объекты + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: addPropertyToOptionRecursive( + objectField.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + } as FormField; + } + + // Проверяем вложенные oneOf/anyOf + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: addPropertyToOptionRecursive( + option.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + })), + } as FormField; + } + + // Проверяем массивы с объектными свойствами + if ( + field.type === 'array' && + field.arrayType === 'object' && + (field as FormArrayField).properties + ) { + const arrayField = field as FormArrayField; + return { + ...field, + properties: addPropertyToOptionRecursive( + arrayField.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + } as FormField; + } + + return field; + }); + }; + + // Применяем рекурсивную функцию ко всем полям формы + setFormFields((prev) => addPropertyToOptionRecursive(prev, fieldId, optionIndex)); + }; + + // Remove a property from an object + const removeObjectProperty = (fieldId: string, propertyIndex: number) => { + setFormFields((prev) => { + return prev.map((field) => { + if (field.id !== fieldId) return field; + + const updatedField = JSON.parse(JSON.stringify(field)); // Deep clone + const objectInput = updatedField as ObjectInput; + + // Remove the property at the specified index + objectInput.properties.splice(propertyIndex, 1); + + return updatedField; + }) as FormField[]; + }); + }; + + // Remove a property from a oneOf/anyOf option + const removeOptionProperty = (fieldId: string, optionIndex: number, propertyIndex: number) => { + // Функция для рекурсивного поиска и удаления свойства + const removePropertyFromOptionRecursive = ( + fields: FormField[], + targetFieldId: string, + targetOptionIndex: number, + targetPropertyIndex: number, + ): FormField[] => { + return fields.map((field) => { + // Если это искомое поле, удаляем свойство из указанной опции + if ( + field.id === targetFieldId && + (field.type === 'oneOf' || field.type === 'anyOf') + ) { + const updatedField = JSON.parse(JSON.stringify(field)); + const options = updatedField.options; + + if ( + options && + Array.isArray(options) && + targetOptionIndex >= 0 && + targetOptionIndex < options.length && + options[targetOptionIndex].properties && + targetPropertyIndex >= 0 && + targetPropertyIndex < options[targetOptionIndex].properties.length + ) { + // Удаляем свойство из опции + options[targetOptionIndex].properties.splice(targetPropertyIndex, 1); + } + + return updatedField; + } + + // Проверяем вложенные объекты + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: removePropertyFromOptionRecursive( + objectField.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + ), + } as FormField; + } + + // Проверяем вложенные oneOf/anyOf + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: removePropertyFromOptionRecursive( + option.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + ), + })), + } as FormField; + } + + // Проверяем массивы с объектными свойствами + if ( + field.type === 'array' && + field.arrayType === 'object' && + (field as FormArrayField).properties + ) { + const arrayField = field as FormArrayField; + return { + ...field, + properties: removePropertyFromOptionRecursive( + arrayField.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + ), + } as FormField; + } + + return field; + }); + }; + + // Применяем рекурсивную функцию ко всем полям формы + setFormFields((prev) => + removePropertyFromOptionRecursive(prev, fieldId, optionIndex, propertyIndex), + ); + }; + + // Update a property in an object + const updateObjectProperty = ( + fieldId: string, + propertyIndex: number, + updates: Partial, + ) => { + setFormFields((prev) => { + return prev.map((field) => { + if (field.id !== fieldId) return field; + + const updatedField = JSON.parse(JSON.stringify(field)); // Deep clone + const objectInput = updatedField as ObjectInput; + + // Update the property at the specified index + objectInput.properties[propertyIndex] = { + ...objectInput.properties[propertyIndex], + ...updates, + } as ConfigInput; + + return updatedField; + }) as FormField[]; + }); + }; + + // Update a property in a oneOf/anyOf option + const updateOptionProperty = ( + fieldId: string, + optionIndex: number, + propertyIndex: number, + updates: Partial, + ) => { + // Функция для рекурсивного поиска и обновления свойства + const updatePropertyInOptionRecursive = ( + fields: FormField[], + targetFieldId: string, + targetOptionIndex: number, + targetPropertyIndex: number, + propertyUpdates: Partial, + ): FormField[] => { + return fields.map((field) => { + // Если это искомое поле, обновляем свойство в указанной опции + if ( + field.id === targetFieldId && + (field.type === 'oneOf' || field.type === 'anyOf') + ) { + const updatedField = JSON.parse(JSON.stringify(field)); + const options = updatedField.options; + + if ( + options && + Array.isArray(options) && + targetOptionIndex >= 0 && + targetOptionIndex < options.length && + options[targetOptionIndex].properties && + targetPropertyIndex >= 0 && + targetPropertyIndex < options[targetOptionIndex].properties.length + ) { + // Обновляем свойство в опции + options[targetOptionIndex].properties[targetPropertyIndex] = { + ...options[targetOptionIndex].properties[targetPropertyIndex], + ...propertyUpdates, + } as ConfigInput; + } + + return updatedField; + } + + // Проверяем вложенные объекты + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: updatePropertyInOptionRecursive( + objectField.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + propertyUpdates, + ), + } as FormField; + } + + // Проверяем вложенные oneOf/anyOf + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: updatePropertyInOptionRecursive( + option.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + propertyUpdates, + ), + })), + } as FormField; + } + + // Проверяем массивы с объектными свойствами + if ( + field.type === 'array' && + field.arrayType === 'object' && + (field as FormArrayField).properties + ) { + const arrayField = field as FormArrayField; + return { + ...field, + properties: updatePropertyInOptionRecursive( + arrayField.properties as FormField[], + targetFieldId, + targetOptionIndex, + targetPropertyIndex, + propertyUpdates, + ), + } as FormField; + } + + return field; + }); + }; + + // Применяем рекурсивную функцию ко всем полям формы + setFormFields((prev) => + updatePropertyInOptionRecursive(prev, fieldId, optionIndex, propertyIndex, updates), + ); + }; + + // Add a new option to a oneOf/anyOf field + const addOption = (fieldId: string) => { + console.log('addOption called with fieldId:', fieldId); + + // Функция для рекурсивного поиска и добавления опции + const addOptionRecursive = (fields: FormField[], targetFieldId: string): FormField[] => { + return fields.map((field) => { + // Если это искомое поле, добавляем новую опцию + if ( + field.id === targetFieldId && + (field.type === 'oneOf' || field.type === 'anyOf') + ) { + const updatedField = JSON.parse(JSON.stringify(field)); + const options = updatedField.options; + + if (options && Array.isArray(options)) { + // Создаем новую опцию с дефолтным свойством + const optionLetter = String.fromCharCode(65 + options.length); // A, B, C, etc. + const newOption = { + title: `Option ${optionLetter}`, + value: `option${optionLetter.toLowerCase()}`, + properties: [ + { + type: 'text', + name: 'textField', + title: 'Text Field', + id: generateId(), + } as FormField, + ], + }; + + // Добавляем новую опцию + options.push(newOption); + } + + return updatedField; + } + + // Проверяем вложенные объекты + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: addOptionRecursive( + objectField.properties as FormField[], + targetFieldId, + ), + } as FormField; + } + + // Проверяем вложенные oneOf/anyOf + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: addOptionRecursive( + option.properties as FormField[], + targetFieldId, + ), + })), + } as FormField; + } + + // Проверяем массивы с объектными свойствами + if ( + field.type === 'array' && + field.arrayType === 'object' && + (field as FormArrayField).properties + ) { + const arrayField = field as FormArrayField; + return { + ...field, + properties: addOptionRecursive( + arrayField.properties as FormField[], + targetFieldId, + ), + } as FormField; + } + + return field; + }); + }; + + // Применяем рекурсивную функцию ко всем полям формы + setFormFields((prev) => addOptionRecursive(prev, fieldId)); + }; + + // Remove an option from a oneOf/anyOf field + const removeOption = (fieldId: string, optionIndex: number) => { + console.log('removeOption called with:', {fieldId, optionIndex}); + + // Функция для рекурсивного поиска и удаления опции + const removeOptionRecursive = ( + fields: FormField[], + targetFieldId: string, + targetOptionIndex: number, + ): FormField[] => { + return fields.map((field) => { + // Если это искомое поле, удаляем опцию + if ( + field.id === targetFieldId && + (field.type === 'oneOf' || field.type === 'anyOf') + ) { + const updatedField = JSON.parse(JSON.stringify(field)); + const options = updatedField.options; + + if ( + options && + Array.isArray(options) && + targetOptionIndex >= 0 && + targetOptionIndex < options.length + ) { + // Проверяем, что можно удалить опцию (должна остаться хотя бы одна) + if (options.length > 1) { + // Удаляем опцию по индексу + options.splice(targetOptionIndex, 1); + } + } + + return updatedField; + } + + // Проверяем вложенные объекты + if (field.type === 'object' && (field as ObjectInput).properties) { + const objectField = field as ObjectInput; + return { + ...field, + properties: removeOptionRecursive( + objectField.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + } as FormField; + } + + // Проверяем вложенные oneOf/anyOf + if ( + (field.type === 'oneOf' || field.type === 'anyOf') && + (field as OneOfInput | AnyOfInput).options + ) { + const optionsField = field as OneOfInput | AnyOfInput; + return { + ...field, + options: optionsField.options.map((option) => ({ + ...option, + properties: removeOptionRecursive( + option.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + })), + } as FormField; + } + + // Проверяем массивы с объектными свойствами + if ( + field.type === 'array' && + field.arrayType === 'object' && + (field as FormArrayField).properties + ) { + const arrayField = field as FormArrayField; + return { + ...field, + properties: removeOptionRecursive( + arrayField.properties as FormField[], + targetFieldId, + targetOptionIndex, + ), + } as FormField; + } + + return field; + }); + }; + + // Применяем рекурсивную функцию ко всем полям формы + setFormFields((prev) => removeOptionRecursive(prev, fieldId, optionIndex)); + }; + + // Reset the form + const resetForm = () => { + setFormFields([]); + setContentConfig({}); + }; + + return { + formFields, + contentConfig, + handleFormUpdate, + createField, + addField, + removeField, + updateField, + addObjectProperty, + addOptionProperty, + removeObjectProperty, + removeOptionProperty, + updateObjectProperty, + updateOptionProperty, + addOption, + removeOption, + resetForm, + }; +}; diff --git a/playground/src/app/pc/content.json b/playground/src/pages/pc/example-1/content.json similarity index 100% rename from playground/src/app/pc/content.json rename to playground/src/pages/pc/example-1/content.json diff --git a/playground/src/app/pc-2/content.json b/playground/src/pages/pc/example-2/content.json similarity index 100% rename from playground/src/app/pc-2/content.json rename to playground/src/pages/pc/example-2/content.json diff --git a/playground/src/app/pc-2/navigation.json b/playground/src/pages/pc/example-2/navigation.json similarity index 100% rename from playground/src/app/pc-2/navigation.json rename to playground/src/pages/pc/example-2/navigation.json diff --git a/playground/src/app/pc-2/styles.scss b/playground/src/pages/pc/example-2/styles.scss similarity index 100% rename from playground/src/app/pc-2/styles.scss rename to playground/src/pages/pc/example-2/styles.scss diff --git a/playground/src/pages/pc/pc.tsx b/playground/src/pages/pc/pc.tsx new file mode 100644 index 000000000..2d46b8b2b --- /dev/null +++ b/playground/src/pages/pc/pc.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import {NavigationData, PageConstructor, PageConstructorProvider} from '../../../../src'; + +// Example 1 +import contentExample1 from './example-1/content.json'; + +// Example 2 +import contentExample2 from './example-2/content.json'; +import navigationExample2 from './example-2/navigation.json'; + +interface PCPageProps { + id?: string | null; +} + +export default function PCPage({id}: PCPageProps) { + const pageId = id || '1'; + + const page = React.useMemo(() => { + switch (Number(pageId)) { + case 2: + import('./example-2/styles.scss'); + return { + content: contentExample2, + navigation: navigationExample2, + }; + default: + case 1: + return { + content: contentExample1, + navigation: undefined, + }; + } + }, [pageId]); + + return ( + + + + ); +} diff --git a/playground/src/router.tsx b/playground/src/router.tsx new file mode 100644 index 000000000..d79a7bd20 --- /dev/null +++ b/playground/src/router.tsx @@ -0,0 +1,21 @@ +import {useSearchParams} from 'react-router'; + +import EditorPage from './pages/editor/editor'; +import PCPage from './pages/pc/pc'; +import FormPage from './pages/form/form'; + +export default function Router() { + const [searchParams] = useSearchParams(); + const page = searchParams.get('page'); + const id = searchParams.get('id'); + + // Route based on query parameters + switch (page) { + case 'form': + return ; + case 'pc': + return ; + default: + return ; + } +} diff --git a/playground/src/styles/globals.scss b/playground/src/styles/globals.scss index bfbf1af4e..5d12d0923 100644 --- a/playground/src/styles/globals.scss +++ b/playground/src/styles/globals.scss @@ -1,7 +1,7 @@ -@import '../../../styles/styles.scss'; -@import '../../../src/editor-v2/styles/root.scss'; - html, -body { +body, +#root { + height: 100%; + min-width: 320px; margin: 0; } diff --git a/playground/src/vite-env.d.ts b/playground/src/vite-env.d.ts new file mode 100644 index 000000000..b1f45c786 --- /dev/null +++ b/playground/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 5912c1974..8261a7284 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -1,32 +1,26 @@ { - "extends": "@gravity-ui/tsconfig/tsconfig.json", + "extends": "@gravity-ui/tsconfig/tsconfig", "compilerOptions": { + "target": "es2022", + "lib": ["dom", "dom.iterable", "esnext"], + "types": ["vite-plugin-svgr/client"], "allowJs": true, - "outDir": "build/esm", + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, "module": "esnext", - "jsx": "preserve", - "baseUrl": ".", - "target": "ES2020", + "moduleResolution": "bundler", "resolveJsonModule": true, - "importHelpers": true, - "lib": ["dom", "dom.iterable", "esnext"], - "noEmit": true, - "incremental": true, "isolatedModules": true, - "plugins": [ - { - "name": "next" - } - ] + "jsx": "react-jsx", + "incremental": true, + "allowSyntheticDefaultImports": true, + "paths": { + "@/*": ["./src/*"], + "*": ["../src/internal-typings/*"] + } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "src/typings/*.d.ts", - ".next/types/**/*.ts", - "../src", - "../styles" - ], + "include": ["**/*.ts", "**/*.tsx", "vite.config.mts", "../src/internal-typings/*"], "exclude": ["node_modules"] } diff --git a/playground/vite.config.mts b/playground/vite.config.mts new file mode 100644 index 000000000..bc93fe0aa --- /dev/null +++ b/playground/vite.config.mts @@ -0,0 +1,26 @@ +import {defineConfig} from 'vite'; +import react from '@vitejs/plugin-react'; +import svgr from 'vite-plugin-svgr'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svgr(), react()], + optimizeDeps: { + //workaround for the problem https://github.com/vitejs/vite/issues/7719 + extensions: ['.css'], + esbuildOptions: { + plugins: [ + (await import('esbuild-sass-plugin')).sassPlugin({ + type: 'style', + }), + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + silenceDeprecations: ['import', 'mixed-decls', 'global-builtin'], + }, + }, + }, +}); diff --git a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx index 13bcbe0dc..0f27cf0d5 100644 --- a/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx +++ b/src/editor-v2/components/DynamicForm/Fields/Select/Select.tsx @@ -12,27 +12,36 @@ type SelectInput = SelectSingleInput | SelectMultipleInput; interface SelectDynamicFieldProps extends FieldBaseParams { input: SelectInput; - value: string; - onUpdate: (value: string | undefined) => void; + value: string | string[]; + onUpdate: (value: string | string[] | undefined) => void; className?: string; } const SelectDynamicField = ({input, value, onUpdate, className}: SelectDynamicFieldProps) => { const inputView = input.view || 'radiobutton'; + const isMultiple = input.mode === 'multiple'; + const currentValue = Array.isArray(value) ? value : [value]; return ( - {inputView === 'select' && ( + {(inputView === 'select' || isMultiple) && ( ({ - content: option.title, - value: option.value, - }))} - value={oneOfMetaValue ? [oneOfMetaValue] : []} - onUpdate={([selectValue]) => onUpdateOneOf(selectValue)} - className={b('select')} - /> - )} - {oneOfChosenOption && ( - - )} - + {inputConfig.options.length < 4 ? ( + ({ + content: option.title, + value: option.value, + }))} + value={oneOfMetaValue} + onUpdate={onUpdateOneOf} + /> + ) : ( +