From 61e9dd5cc16c1f335b4a439893f0a6a75a6fbb60 Mon Sep 17 00:00:00 2001 From: jack zhang <1098626505@qq.com> Date: Mon, 22 Jul 2024 14:06:36 +0800 Subject: [PATCH] feat: plugin mobile v2 (#4777) * feat: init * fix: mobile layout * feat: more code * feat: improve navigate bar * fix: mobile title * feat: improve code * fix: add settings and initailzer * fix: settings * fix: tabbar items settings * feat: tabbar initializer * fix: api * fix: styles * feat: navbar * feat: navigate bar tabs initializer * feat: navigate bar tab settings * feat: navigation bar actions * fix: bug * fix: bug * fix: bug * fix: tabbar active * fix: bug * fix: mobile login and layout * fix: update version * fix: build error * feat: plugin settings support link * fix: add mobile meta * fix: desktop mode * fix: remove old code and change collection name and mobile path * fix: tabbar and tabs initialer layout * fix: initializer style * fix: adjust schema position * fix: mobile style * fix: delete relation resource and home page bug * fix: support multi app * fix: not found page * fix: js bridge * fix: bug * fix: navigation bar schema flat * fix: navigation bar action style * fix: change version * fix: mobile meta and real mobile test * refactor: folder and name * fix: navigation bar sticky and zIndex * fix: full mobile schema * fix: mobile readme and package.json * fix: e2e bug * fix: bug * fix: tabbar style on productino * fix: bug * fix: rename MobileTabBar.Page * fix: support tabbar sort * fix: support page tabs sort * fix: i18n * fix: settings utils import bug * docs: api doc * fix: qrcode refresh * test: unit tests * fix: bug * fix: unit test * fix: build bug * fix: e2e test * fix: overflow scroll * fix: bug * fix: scroll and overflow * fix: bug * fix: e2e expect await * fix: e2e bug * fix: bug * fix: change name * fix: add more e2e * fix: page header * fix: tab support icon * fix: bug * fix: bug * fix: docs * fix(T-4811): scroll bar too long * fix(T-4810): desktop mode * fix: e2e * fix(T-4812): title empty * fix: unit test * feat: hide Open mode option in mobile mode * feat: change default value of Open mode on mobile * feat: add OpenModeProvider * feat: support page mode * fix: fix build * test: update unit tests * chore: remove pro-plugins * fix: bug * fix(T-4812): title is required * fix: bug * fix: bug * fix: bug * fix: bug * refactor: remove z-index * refactor: make better for subpages * fix: drag bug * fix: bug * fix: theme bug * fix(T-4859): create tab bar title empty * fix(T-4857): action too long * fix: e2e bug * fix: remove comment * fix: bug * fix: theme bug * fix: should provider modal component * fix: bug --------- Co-authored-by: chenos Co-authored-by: Zeke Zhang <958414905@qq.com> --- .gitignore | 1 + .../client/src/antd-config-provider/index.tsx | 6 +- .../client/src/application/Application.tsx | 2 + .../core/client/src/application/Plugin.ts | 2 +- .../client/src/application/PluginManager.ts | 2 +- .../src/application/PluginSettingsManager.ts | 2 + .../__tests__/Application.test.tsx | 5 + .../client/src/application/hooks/usePlugin.ts | 2 +- packages/core/client/src/application/index.ts | 4 +- .../SchemaInitializerActionModal.tsx | 26 +- .../components/SchemaInitializerButton.tsx | 27 +- .../application/schema-initializer/types.ts | 3 +- .../schema-initializer/withInitializer.tsx | 11 +- .../utils/createModalSettingsItem.tsx | 19 +- .../utils/createSelectSettingsItem.tsx | 8 +- .../utils/createSwitchSettingsItem.tsx | 10 +- .../utils/createTextSettingsItem.tsx | 39 ++ .../schema-settings/utils/index.ts | 13 + .../application/schema-settings/utils/util.ts | 16 +- .../client/src/block-provider/hooks/index.ts | 9 +- .../core/client/src/block-provider/index.tsx | 1 + .../core/client/src/global-theme/index.tsx | 8 +- packages/core/client/src/index.ts | 1 + .../add-child/CreateChildInitializer.tsx | 4 +- .../add-child/addChildActionSettings.tsx | 13 +- .../add-new/CreateActionInitializer.tsx | 4 +- .../actions/add-new/addNewActionSettings.tsx | 10 +- .../customizeAddRecordActionSettings.tsx | 10 +- .../link/customizeLinkActionSettings.tsx | 11 +- .../PopupActionInitializer.tsx | 4 +- .../UpdateActionInitializer.tsx | 4 +- .../view-edit-popup/ViewActionInitializer.tsx | 4 +- .../customizePopupActionSettings.tsx | 16 +- .../view-edit-popup/editActionSettings.tsx | 10 +- .../view-edit-popup/viewActionSettings.tsx | 10 +- .../Select/selectComponentFieldSettings.tsx | 4 +- .../src/modules/popup/OpenModeProvider.tsx | 99 ++++ .../core/client/src/pm/PluginManagerLink.tsx | 3 +- packages/core/client/src/pm/PluginSetting.tsx | 6 + .../antd/action/Action.Container.tsx | 18 +- .../antd/action/Action.Designer.tsx | 6 +- .../antd/action/Action.Page.tsx | 23 +- .../antd/action/ActionBar.tsx | 4 +- .../src/schema-component/antd/action/types.ts | 6 +- .../antd/association-field/InternalViewer.tsx | 4 +- .../antd/block-item/BlockItem.tsx | 10 +- .../antd/color-select/ColorSelect.tsx | 8 +- .../antd/form-item/FormItem.Settings.tsx | 7 +- .../antd/page/BackButtonUsedInSubPage.tsx | 27 +- .../src/schema-component/antd/page/Page.tsx | 2 +- .../schema-component/antd/page/PagePopups.tsx | 10 +- .../antd/page/PopupSettingsProvider.tsx | 11 +- .../src/schema-component/antd/page/index.ts | 2 + .../antd/page/pagePopupUtils.tsx | 2 +- .../core/DesignableSwitch.tsx | 2 + .../core/RemoteSchemaComponent.tsx | 27 +- .../schema-settings/GeneralSchemaDesigner.tsx | 30 +- .../src/schema-settings/SchemaSettings.tsx | 34 +- packages/core/test/src/e2e/e2eUtils.ts | 299 +++++++++- .../src/client/BulkEditAction.Settings.tsx | 6 +- .../src/client/DuplicateAction.Settings.tsx | 6 +- .../src/client/pages/AuthLayout.tsx | 6 +- .../plugin-block-iframe/src/client/index.ts | 5 + .../src/client/index.tsx | 5 + .../plugin-calendar/src/client/index.tsx | 4 + .../src/client/index.tsx | 4 + .../initializers/UploadActionInitializer.tsx | 4 +- .../src/client/Kanban.Card.Designer.tsx | 8 + .../src/client/components/Configuration.tsx | 4 +- .../@nocobase/plugin-map/src/client/index.tsx | 5 +- .../@nocobase/plugin-mobile/.dumirc.ts | 30 + .../@nocobase/plugin-mobile/.npmignore | 2 + .../plugins/@nocobase/plugin-mobile/README.md | 15 + .../@nocobase/plugin-mobile/README.zh-CN.md | 15 + .../@nocobase/plugin-mobile/client.d.ts | 2 + .../plugins/@nocobase/plugin-mobile/client.js | 1 + .../@nocobase/plugin-mobile/package.json | 28 + .../@nocobase/plugin-mobile/server.d.ts | 2 + .../plugins/@nocobase/plugin-mobile/server.js | 1 + .../src/client/__e2e__/desktop.test.ts | 52 ++ .../src/client/__e2e__/pageHeader.test.ts | 263 +++++++++ .../src/client/__e2e__/tabbar.test.tsx | 147 +++++ .../src/client/__tests__/DesktopMode.test.tsx | 53 ++ .../__tests__/DynamicPage/MobilePage.test.tsx | 114 ++++ .../DynamicPage/MobilePageContent.test.tsx | 51 ++ .../MobilePageNavigationBar.test.tsx | 64 +++ .../MobilePageNavigationBarTabs.test.tsx | 29 + .../src/client/__tests__/Mobile.test.tsx | 38 ++ .../client/__tests__/MobileLayout.test.tsx | 44 ++ .../client/__tests__/MobileProviders.test.tsx | 38 ++ .../__tests__/MobileTabBar.Item.test.tsx | 58 ++ .../__tests__/MobileTabBar.Link.test.tsx | 88 +++ .../__tests__/MobileTabBar.Page.test.tsx | 68 +++ .../src/client/__tests__/js-bridge.test.tsx | 46 ++ .../src/client/__tests__/pages/home.test.tsx | 42 ++ .../client/__tests__/pages/not-found.test.tsx | 29 + .../plugin-mobile/src/client/client.d.ts | 259 +++++++++ .../plugin-mobile/src/client/constants.ts | 12 + .../src/client/demos/DesktopMode-basic.tsx | 19 + .../src/client/demos/Mobile-basic.tsx | 290 ++++++++++ .../demos/MobileRoutesProvider-basic.tsx | 84 +++ .../src/client/demos/MobileTabBar-basic.tsx | 303 ++++++++++ .../src/client/demos/MobileTabBar-false.tsx | 303 ++++++++++ .../client/demos/MobileTabBar-inner-page.tsx | 308 ++++++++++ .../client/demos/MobileTabBar.Item-basic.tsx | 8 + .../demos/MobileTabBar.Item-on-click.tsx | 14 + .../demos/MobileTabBar.Item-selected-icon.tsx | 15 + .../demos/MobileTabBar.Item-selected.tsx | 8 + .../MobileTabBar.Item-with-icon-node.tsx | 9 + .../demos/MobileTabBar.Item-with-icon.tsx | 8 + .../client/demos/MobileTabBar.Link-inner.tsx | 32 ++ .../client/demos/MobileTabBar.Link-outer.tsx | 27 + .../client/demos/MobileTabBar.Link-schema.tsx | 49 ++ .../demos/MobileTabBar.Link-selected.tsx | 27 + .../demos/MobileTabBar.Link-settings.tsx | 60 ++ .../client/demos/MobileTabBar.Page-basic.tsx | 38 ++ .../client/demos/MobileTabBar.Page-schema.tsx | 48 ++ .../demos/MobileTabBar.Page-selected.tsx | 31 + .../demos/MobileTabBar.Page-settings.tsx | 58 ++ .../demos/MobileTitleProvider-basic.tsx | 22 + .../fixtures/createSingleItemInitializer.ts | 15 + .../src/client/demos/fixtures/getMockData.ts | 35 ++ .../client/demos/fixtures/schemaViewer.tsx | 29 + .../client/demos/pages-dynamic-page-404.tsx | 32 ++ .../client/demos/pages-dynamic-page-basic.tsx | 36 ++ .../demos/pages-dynamic-page-schema.tsx | 75 +++ .../demos/pages-dynamic-page-settings.tsx | 42 ++ .../src/client/demos/pages-home-basic.tsx | 45 ++ .../src/client/demos/pages-home-custom.tsx | 94 +++ .../src/client/demos/pages-home-null.tsx | 34 ++ .../demos/pages-navigation-bar-actions.tsx | 96 ++++ .../demos/pages-navigation-bar-basic.tsx | 36 ++ .../demos/pages-navigation-bar-false.tsx | 36 ++ .../pages-navigation-bar-title-false.tsx | 36 ++ .../src/client/demos/pages-not-found.tsx | 22 + .../client/demos/pages-page-content-404.tsx | 52 ++ .../client/demos/pages-page-content-basic.tsx | 54 ++ .../demos/pages-page-content-first-route.tsx | 61 ++ .../client/demos/pages-page-header-basic.tsx | 17 + .../client/demos/pages-page-header-false.tsx | 14 + .../client/demos/pages-page-tabs-false.tsx | 83 +++ .../src/client/demos/pages-page-tabs.tsx | 84 +++ .../src/client/desktop-mode/Content.tsx | 49 ++ .../src/client/desktop-mode/DesktopMode.tsx | 39 ++ .../src/client/desktop-mode/Header.tsx | 128 +++++ .../src/client/desktop-mode/index.md | 12 + .../src/client/desktop-mode/index.ts | 12 + .../src/client/desktop-mode/sizeContext.tsx | 31 + .../plugin-mobile/src/client/dynamic-page.md | 182 ++++++ .../plugin-mobile/src/client/index.md | 337 +++++++++++ .../plugin-mobile/src/client/index.tsx | 229 ++++++++ .../src/client/js-bridge/index.ts | 45 ++ .../plugin-mobile/src/client/locale.ts | 20 + .../src/client/mobile-layout/MobileLayout.tsx | 32 ++ .../src/client/mobile-layout/index.md | 214 +++++++ .../src/client/mobile-layout/index.ts | 11 + .../MobileTabBar.Item/MobileTabBar.Item.tsx | 62 ++ .../MobileTabBar.Item/index.tsx | 14 + .../MobileTabBar.Item/schema.ts | 22 + .../MobileTabBar.Item/schemaFormFields.tsx | 30 + .../MobileTabBar.Item/settingsItem.tsx | 72 +++ .../MobileTabBar.Item/useUpdateTabBarItem.tsx | 42 ++ .../mobile-tab-bar/MobileTabBar.tsx | 93 +++ .../mobile-layout/mobile-tab-bar/index.md | 224 ++++++++ .../mobile-layout/mobile-tab-bar/index.ts | 13 + .../mobile-tab-bar/initializer.tsx | 25 + .../mobile-layout/mobile-tab-bar/styles.ts | 55 ++ .../MobileTabBar.Link/MobileTabBar.Link.tsx | 27 + .../types/MobileTabBar.Link/index.ts | 12 + .../types/MobileTabBar.Link/initializer.tsx | 53 ++ .../types/MobileTabBar.Link/settings.tsx | 33 ++ .../MobileTabBar.Page/MobileTabBar.Page.tsx | 31 + .../types/MobileTabBar.Page/index.ts | 12 + .../types/MobileTabBar.Page/initializer.tsx | 84 +++ .../types/MobileTabBar.Page/settings.tsx | 23 + .../mobile-tab-bar/types/index.ts | 11 + .../mobile-providers/MobileProviders.tsx | 34 ++ .../mobile-providers/context/MobileRoutes.tsx | 120 ++++ .../mobile-providers/context/MobileTitle.tsx | 32 ++ .../client/mobile-providers/context/index.ts | 11 + .../src/client/mobile-providers/index.md | 39 ++ .../src/client/mobile-providers/index.tsx | 11 + .../src/client/mobile/Mobile.tsx | 69 +++ .../src/client/mobile/MobileAppContext.tsx | 39 ++ .../plugin-mobile/src/client/mobile/index.md | 12 + .../plugin-mobile/src/client/mobile/index.tsx | 11 + .../client/pages/dynamic-page/MobilePage.tsx | 46 ++ .../content/MobilePageContent.tsx | 30 + .../content/MobilePageContentContainer.tsx | 44 ++ .../pages/dynamic-page/content/index.ts | 12 + .../dynamic-page/content/initializer.tsx | 66 +++ .../pages/dynamic-page/content/schema.ts | 33 ++ .../pages/dynamic-page/content/styles.ts | 26 + .../src/client/pages/dynamic-page/context.tsx | 44 ++ .../dynamic-page/header/MobilePageHeader.tsx | 30 + .../client/pages/dynamic-page/header/index.ts | 13 + .../MobilePageNavigationBar.tsx | 53 ++ .../actions/ActionColorSelect.tsx | 33 ++ .../actions/ActionFillSelect.tsx | 34 ++ .../actions/MobileNavigationActionBar.tsx | 104 ++++ .../actions/actionCommonInitializerSchema.ts | 69 +++ .../actions/actionCommonSettings.ts | 82 +++ .../header/navigation-bar/actions/index.ts | 13 + .../navigation-bar/actions/items/index.ts | 10 + .../navigation-bar/actions/items/link.ts | 81 +++ .../MobileNavigationBarAction.tsx | 69 +++ .../mobile-navigation-bar-action/index.ts | 10 + .../mobile-navigation-bar-action/styles.ts | 49 ++ .../mobileNavigationBarActionsInitializer.ts | 17 + .../header/navigation-bar/index.ts | 11 + .../header/navigation-bar/styles.ts | 31 + .../pages/dynamic-page/header/schema.ts | 40 ++ .../pages/dynamic-page/header/styles.ts | 21 + .../header/tabs/MobilePageTabs.tsx | 84 +++ .../pages/dynamic-page/header/tabs/index.ts | 13 + .../dynamic-page/header/tabs/initializer.tsx | 81 +++ .../dynamic-page/header/tabs/settings.tsx | 126 +++++ .../pages/dynamic-page/header/tabs/styles.ts | 28 + .../src/client/pages/dynamic-page/index.ts | 15 + .../src/client/pages/dynamic-page/schema.ts | 60 ++ .../client/pages/dynamic-page/settings.tsx | 103 ++++ .../src/client/pages/home/MobileHomePage.tsx | 25 + .../src/client/pages/home/index.ts | 10 + .../plugin-mobile/src/client/pages/index.md | 27 + .../plugin-mobile/src/client/pages/index.ts | 12 + .../MobileActionPage.style.ts | 27 + .../mobile-action-page/MobileActionPage.tsx | 68 +++ .../MobileTabsForMobileActionPage.style.ts | 46 ++ .../MobileTabsForMobileActionPage.tsx | 150 +++++ .../pages/not-found/MobileNotFoundPage.tsx | 31 + .../src/client/pages/not-found/index.ts | 10 + .../providers/MobileCheckerProvider.tsx | 30 + .../src/client/providers/index.md | 11 + .../src/client/providers/index.ts | 10 + .../plugin-mobile/src/client/utils.ts | 13 + .../@nocobase/plugin-mobile/src/index.ts | 11 + .../plugin-mobile/src/locale/en-US.json | 22 + .../plugin-mobile/src/locale/zh-CN.json | 23 + .../src/server/collections/.gitkeep | 0 .../src/server/collections/mobileRoutes.ts | 310 ++++++++++ .../plugin-mobile/src/server/index.ts | 10 + .../plugin-mobile/src/server/plugin.ts | 28 + .../src/client/components/InitializeTheme.tsx | 6 +- packages/presets/nocobase/package.json | 1 + packages/presets/nocobase/src/server/index.ts | 3 +- yarn.lock | 534 +++++++++++++++++- 246 files changed, 10854 insertions(+), 200 deletions(-) create mode 100644 packages/core/client/src/application/schema-settings/utils/createTextSettingsItem.tsx create mode 100644 packages/core/client/src/application/schema-settings/utils/index.ts create mode 100644 packages/core/client/src/modules/popup/OpenModeProvider.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/.dumirc.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/.npmignore create mode 100644 packages/plugins/@nocobase/plugin-mobile/README.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/README.zh-CN.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/client.d.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/client.js create mode 100644 packages/plugins/@nocobase/plugin-mobile/package.json create mode 100644 packages/plugins/@nocobase/plugin-mobile/server.d.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/server.js create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/desktop.test.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/pageHeader.test.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/tabbar.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/DesktopMode.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/DynamicPage/MobilePage.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/DynamicPage/MobilePageContent.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/DynamicPage/MobilePageNavigationBar.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/DynamicPage/MobilePageNavigationBarTabs.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/Mobile.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/MobileLayout.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/MobileProviders.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/MobileTabBar.Item.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/MobileTabBar.Link.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/MobileTabBar.Page.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/js-bridge.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/pages/home.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/__tests__/pages/not-found.test.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/client.d.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/constants.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/DesktopMode-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/Mobile-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileRoutesProvider-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar-false.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar-inner-page.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-on-click.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-selected-icon.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-selected.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-with-icon-node.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Item-with-icon.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Link-inner.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Link-outer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Link-schema.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Link-selected.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Link-settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Page-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Page-schema.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Page-selected.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTabBar.Page-settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/MobileTitleProvider-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/createSingleItemInitializer.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/getMockData.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/schemaViewer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-404.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-schema.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-custom.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-null.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-actions.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-false.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-title-false.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-not-found.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-404.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-first-route.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-basic.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-false.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs-false.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Content.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/DesktopMode.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/sizeContext.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/dynamic-page.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/js-bridge/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/locale.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/MobileLayout.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/index.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schemaFormFields.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/settingsItem.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/useUpdateTabBarItem.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/initializer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/MobileTabBar.Link.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/initializer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/initializer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/MobileProviders.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileRoutes.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileTitle.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile/MobileAppContext.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContent.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/initializer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/schema.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/context.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/MobilePageHeader.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/MobilePageNavigationBar.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionColorSelect.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionFillSelect.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/MobileNavigationActionBar.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonInitializerSchema.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonSettings.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/link.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/MobileNavigationBarAction.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobileNavigationBarActionsInitializer.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/schema.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/MobilePageTabs.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/initializer.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/styles.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/schema.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/MobileHomePage.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.style.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.style.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/MobileNotFoundPage.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/providers/MobileCheckerProvider.tsx create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.md create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/client/utils.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/locale/en-US.json create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/locale/zh-CN.json create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/server/collections/.gitkeep create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/server/collections/mobileRoutes.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/server/index.ts create mode 100644 packages/plugins/@nocobase/plugin-mobile/src/server/plugin.ts diff --git a/.gitignore b/.gitignore index e671380205..b4c906a000 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ storage/backups/* **/.dumi/tmp-production packages/core/client/docs/contributing.md packages/core/app/client/src/.plugins +packages/pro-plugins/ storage/plugins storage/tar storage/tmp diff --git a/packages/core/client/src/antd-config-provider/index.tsx b/packages/core/client/src/antd-config-provider/index.tsx index 9708e3e3a3..50b12f59ed 100644 --- a/packages/core/client/src/antd-config-provider/index.tsx +++ b/packages/core/client/src/antd-config-provider/index.tsx @@ -59,7 +59,11 @@ export function AntdConfigProvider(props) { }, ); if (loading) { - return ; + return ( +
+ +
+ ); } return ( diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index 452b5b789f..43abc97c54 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -39,6 +39,7 @@ import { DataSourceApplicationProvider } from '../data-source/components/DataSou import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider'; import { DataSourceManager, type DataSourceManagerOptions } from '../data-source/data-source/DataSourceManager'; +import { OpenModeProvider } from '../modules/popup/OpenModeProvider'; import { AppSchemaComponentProvider } from './AppSchemaComponentProvider'; import type { Plugin } from './Plugin'; import type { RequireJS } from './utils/requirejs'; @@ -158,6 +159,7 @@ export class Application { }); this.use(AntdAppProvider); this.use(DataSourceApplicationProvider, { dataSourceManager: this.dataSourceManager }); + this.use(OpenModeProvider); } private addReactRouterComponents() { diff --git a/packages/core/client/src/application/Plugin.ts b/packages/core/client/src/application/Plugin.ts index 56f0f225ac..d710ad4816 100644 --- a/packages/core/client/src/application/Plugin.ts +++ b/packages/core/client/src/application/Plugin.ts @@ -12,7 +12,7 @@ import type { Application } from './Application'; export class Plugin { constructor( - protected options: T, + public options: T, protected app: Application, ) { this.options = options; diff --git a/packages/core/client/src/application/PluginManager.ts b/packages/core/client/src/application/PluginManager.ts index a1a7664c87..936fd43a19 100644 --- a/packages/core/client/src/application/PluginManager.ts +++ b/packages/core/client/src/application/PluginManager.ts @@ -12,7 +12,7 @@ import type { Plugin } from './Plugin'; import { getPlugins } from './utils/remotePlugins'; export type PluginOptions = { name?: string; packageName?: string; config?: T }; -export type PluginType = typeof Plugin | [typeof Plugin, PluginOptions]; +export type PluginType = typeof Plugin | [typeof Plugin, PluginOptions]; export type PluginData = { name: string; packageName: string; diff --git a/packages/core/client/src/application/PluginSettingsManager.ts b/packages/core/client/src/application/PluginSettingsManager.ts index 5bc22f3439..d50d3d2c21 100644 --- a/packages/core/client/src/application/PluginSettingsManager.ts +++ b/packages/core/client/src/application/PluginSettingsManager.ts @@ -32,12 +32,14 @@ export interface PluginSettingOptions { */ sort?: number; aclSnippet?: string; + link?: string; [index: string]: any; } export interface PluginSettingsPageType { label?: string | React.ReactElement; title: string | React.ReactElement; + link?: string; key: string; icon: any; path: string; diff --git a/packages/core/client/src/application/__tests__/Application.test.tsx b/packages/core/client/src/application/__tests__/Application.test.tsx index a45689fe44..9f5bc6b1cd 100644 --- a/packages/core/client/src/application/__tests__/Application.test.tsx +++ b/packages/core/client/src/application/__tests__/Application.test.tsx @@ -13,6 +13,7 @@ import MockAdapter from 'axios-mock-adapter'; import React, { Component } from 'react'; import { Link, Outlet } from 'react-router-dom'; import { describe } from 'vitest'; +import { OpenModeProvider } from '../../modules/popup/OpenModeProvider'; import { Application } from '../Application'; import { Plugin } from '../Plugin'; import { useApp } from '../hooks'; @@ -211,6 +212,7 @@ describe('Application', () => { it('initial', () => { const app = new Application({ router, providers: [Hello, [World, { name: 'aaa' }]] }); expect(app.providers.slice(initialProvidersLength)).toEqual([ + [OpenModeProvider, undefined], [Hello, undefined], [World, { name: 'aaa' }], ]); @@ -220,6 +222,7 @@ describe('Application', () => { const app = new Application({ router, providers: [Hello] }); app.addProviders([[World, { name: 'aaa' }], Foo]); expect(app.providers.slice(initialProvidersLength)).toEqual([ + [OpenModeProvider, undefined], [Hello, undefined], [World, { name: 'aaa' }], [Foo, undefined], @@ -230,6 +233,7 @@ describe('Application', () => { const app = new Application({ router, providers: [Hello] }); app.addProvider(World, { name: 'aaa' }); expect(app.providers.slice(initialProvidersLength)).toEqual([ + [OpenModeProvider, undefined], [Hello, undefined], [World, { name: 'aaa' }], ]); @@ -239,6 +243,7 @@ describe('Application', () => { const app = new Application({ router, providers: [Hello] }); app.use(World, { name: 'aaa' }); expect(app.providers.slice(initialProvidersLength)).toEqual([ + [OpenModeProvider, undefined], [Hello, undefined], [World, { name: 'aaa' }], ]); diff --git a/packages/core/client/src/application/hooks/usePlugin.ts b/packages/core/client/src/application/hooks/usePlugin.ts index a890131bf7..8905f9f732 100644 --- a/packages/core/client/src/application/hooks/usePlugin.ts +++ b/packages/core/client/src/application/hooks/usePlugin.ts @@ -10,7 +10,7 @@ import { Plugin } from '../Plugin'; import { useApp } from './useApp'; -export function usePlugin(plugin: T): InstanceType; +export function usePlugin(plugin: T): InstanceType; export function usePlugin(name: string): T; export function usePlugin(name: any) { const app = useApp(); diff --git a/packages/core/client/src/application/index.ts b/packages/core/client/src/application/index.ts index 12adce1865..c2d6fe347c 100644 --- a/packages/core/client/src/application/index.ts +++ b/packages/core/client/src/application/index.ts @@ -18,10 +18,8 @@ export * from './globalType'; export * from './hooks'; export * from './schema-initializer'; export * from './schema-settings'; +export * from './schema-settings/utils'; export * from './schema-settings/context/SchemaSettingItemContext'; export * from './schema-settings/hooks/useSchemaSettingsRender'; -export * from './schema-settings/utils/createModalSettingsItem'; -export * from './schema-settings/utils/createSelectSettingsItem'; -export * from './schema-settings/utils/createSwitchSettingsItem'; export * from './schema-toolbar'; export * from './utils'; diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerActionModal.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerActionModal.tsx index 1a774b330f..1645d0a9fa 100644 --- a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerActionModal.tsx +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerActionModal.tsx @@ -10,7 +10,7 @@ import { useForm } from '@formily/react'; import React, { FC, useCallback, useMemo } from 'react'; import { useActionContext, SchemaComponent } from '../../../schema-component'; -import { useSchemaInitializerItem } from '../context'; +import { useSchemaInitializer, useSchemaInitializerItem } from '../context'; import { SchemaInitializerItem } from './SchemaInitializerItem'; import { uid } from '@formily/shared'; @@ -19,10 +19,12 @@ export interface SchemaInitializerActionModalProps { icon?: string | React.ReactNode; schema: any; onCancel?: () => void; - onSubmit?: (values: any) => void; + onSubmit?: (values: any) => Promise | void; buttonText?: any; component?: any; isItem?: boolean; + width?: string; + btnStyles?: React.CSSProperties; } const SchemaInitializerActionModalItemComponent = React.forwardRef((props: any, ref: any) => { @@ -31,7 +33,8 @@ const SchemaInitializerActionModalItemComponent = React.forwardRef((props: any, }); export const SchemaInitializerActionModal: FC = (props) => { - const { title, icon, schema, buttonText, isItem, component, onCancel, onSubmit } = props; + const { title, icon, width, schema, buttonText, btnStyles, isItem, component, onCancel, onSubmit } = props; + const { setVisible: initializerSetVisible } = useSchemaInitializer(); const useCancelAction = useCallback(() => { // eslint-disable-next-line react-hooks/rules-of-hooks const form = useForm(); @@ -54,9 +57,13 @@ export const SchemaInitializerActionModal: FC return { async run() { await form.validate(); - await onSubmit?.(form.values); - ctx.setVisible(false); - void form.reset(); + try { + await onSubmit?.(form.values); + ctx.setVisible(false); + void form.reset(); + } catch (err) { + console.error(err); + } }, }; }, [onSubmit]); @@ -92,6 +99,7 @@ export const SchemaInitializerActionModal: FC style: { borderColor: 'var(--colorSettings)', color: 'var(--colorSettings)', + ...(btnStyles || {}), }, title: buttonText, type: 'dashed', @@ -101,10 +109,14 @@ export const SchemaInitializerActionModal: FC 'x-decorator': 'Form', 'x-component': 'Action.Modal', 'x-component-props': { + width: width, style: { - maxWidth: '520px', + maxWidth: width ? width : '520px', width: '100%', }, + afterOpenChange: () => { + initializerSetVisible(false); + }, }, type: 'void', title, diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerButton.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerButton.tsx index 4e69cef93b..54c7780603 100644 --- a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerButton.tsx +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerButton.tsx @@ -31,23 +31,26 @@ export const SchemaInitializerButton: FC = React.m style={{ borderColor: 'var(--colorSettings)', color: 'var(--colorSettings)', + flex: 'none', ...style, }} icon={typeof options.icon === 'string' ? : options.icon} {...others} > - - {compile(options.title)} - + {options.title && ( + + {compile(options.title)} + + )} ); }); diff --git a/packages/core/client/src/application/schema-initializer/types.ts b/packages/core/client/src/application/schema-initializer/types.ts index 4c6954f3e0..188ee33f08 100644 --- a/packages/core/client/src/application/schema-initializer/types.ts +++ b/packages/core/client/src/application/schema-initializer/types.ts @@ -21,7 +21,7 @@ import type { export type InsertType = (s: ISchema) => void; -type SchemaInitializerItemBuiltInType = T & { +type SchemaInitializerItemBuiltInType = Partial & { name: string; sort?: number; componentProps?: Omit; @@ -110,6 +110,7 @@ export interface SchemaInitializerOptions { insertPosition?: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd'; designable?: boolean; wrap?: (s: ISchema, options?: any) => ISchema; + useWrap?: () => ((s: ISchema, options?: any) => ISchema); onSuccess?: (data: any) => void; insert?: InsertType; useInsert?: () => InsertType; diff --git a/packages/core/client/src/application/schema-initializer/withInitializer.tsx b/packages/core/client/src/application/schema-initializer/withInitializer.tsx index 1f2bce5c99..997a571d5e 100644 --- a/packages/core/client/src/application/schema-initializer/withInitializer.tsx +++ b/packages/core/client/src/application/schema-initializer/withInitializer.tsx @@ -21,6 +21,7 @@ import { SchemaInitializerOptions } from './types'; import { ErrorBoundary } from 'react-error-boundary'; const defaultWrap = (s: ISchema) => s; +const useWrapDefault = (wrap = defaultWrap) => wrap; export function withInitializer(C: ComponentType) { const WithInitializer = observer( @@ -30,6 +31,7 @@ export function withInitializer(C: ComponentType) { const { insert, useInsert, + useWrap = useWrapDefault, wrap = defaultWrap, insertPosition = 'beforeEnd', onSuccess, @@ -43,15 +45,16 @@ export function withInitializer(C: ComponentType) { // 插入 schema 的能力 const insertCallback = useInsert ? useInsert() : insert; + const wrapCallback = useWrap(wrap); const insertSchema = useCallback( (schema) => { if (insertCallback) { - insertCallback(wrap(schema, { isInSubTable })); + insertCallback(wrapCallback(schema, { isInSubTable })); } else { - insertAdjacent(insertPosition, wrap(schema, { isInSubTable }), { onSuccess }); + insertAdjacent(insertPosition, wrapCallback(schema, { isInSubTable }), { onSuccess }); } }, - [insertCallback, wrap, insertAdjacent, insertPosition, onSuccess], + [insertCallback, wrapCallback, insertAdjacent, insertPosition, onSuccess], ); const { wrapSSR, hashId, componentCls } = useSchemaInitializerStyles(); @@ -105,7 +108,7 @@ export function withInitializer(C: ComponentType) { {...popoverProps} arrow={false} overlayClassName={overlayClassName} - open={visible} + open={visible} onOpenChange={setVisible} content={wrapSSR(
) => string); - parentSchemaKey: string; + parentSchemaKey?: string; defaultValue?: any; useDefaultValue?: () => any; schema: (defaultValue: any) => ISchema; valueKeys?: string[]; useVisible?: () => boolean; + width?: number | string; + useSubmit?: () => (values: any) => void; } /** @@ -38,9 +42,11 @@ export function createModalSettingsItem(options: CreateModalSchemaSettingsItemPr valueKeys, schema, title, + useSubmit = useHookDefault, useVisible, defaultValue: propsDefaultValue, useDefaultValue = useHookDefault, + width, } = options; return { name, @@ -50,15 +56,18 @@ export function createModalSettingsItem(options: CreateModalSchemaSettingsItemPr const fieldSchema = useFieldSchema(); const { deepMerge } = useDesignable(); const defaultValue = useDefaultValue(propsDefaultValue); - const values = _.get(fieldSchema, parentSchemaKey); + const values = parentSchemaKey ? _.get(fieldSchema, parentSchemaKey) : undefined; const compile = useCompile(); const { t } = useTranslation(); + const onSubmit = useSubmit(); return { title: typeof title === 'function' ? title(t) : compile(title), + width, schema: schema({ ...defaultValue, ...values }), onSubmit(values) { - deepMerge(getNewSchema({ fieldSchema, schemaKey: parentSchemaKey, value: values, valueKeys })); + deepMerge(getNewSchema({ fieldSchema, parentSchemaKey, value: values, valueKeys })); + return onSubmit?.(values); }, }; }, diff --git a/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx index fe392d9890..f4d1be2437 100644 --- a/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx +++ b/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx @@ -9,10 +9,14 @@ import _ from 'lodash'; import { useFieldSchema } from '@formily/react'; -import { SchemaSettingsItemType, SelectProps, useCompile, useDesignable } from '@nocobase/client'; -import { getNewSchema, useHookDefault } from './util'; import { TFunction, useTranslation } from 'react-i18next'; +import { SchemaSettingsItemType } from '../types'; +import { getNewSchema, useHookDefault } from './util'; +import { SelectProps } from '../../../schema-component/antd/select'; +import { useCompile } from '../../../schema-component/hooks/useCompile'; +import { useDesignable } from '../../../schema-component/hooks/useDesignable'; + interface CreateSelectSchemaSettingsItemProps { name: string; title: string | ((t: TFunction<'translation', undefined>) => string); diff --git a/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx index 32177aec32..1dab8683f2 100644 --- a/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx +++ b/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx @@ -7,13 +7,15 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { SchemaSettingsItemType, useCompile, useDesignable } from '@nocobase/client'; -import { useFieldSchema } from '@formily/react'; import _ from 'lodash'; - -import { getNewSchema, useHookDefault } from './util'; +import { useFieldSchema } from '@formily/react'; import { TFunction, useTranslation } from 'react-i18next'; +import { SchemaSettingsItemType } from '../types'; +import { getNewSchema, useHookDefault } from './util'; +import { useCompile } from '../../../schema-component/hooks/useCompile'; +import { useDesignable } from '../../../schema-component/hooks/useDesignable'; + export interface CreateSwitchSchemaSettingsItemProps { name: string; title: string | ((t: TFunction<'translation', undefined>) => string); diff --git a/packages/core/client/src/application/schema-settings/utils/createTextSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createTextSettingsItem.tsx new file mode 100644 index 0000000000..d3726c84ea --- /dev/null +++ b/packages/core/client/src/application/schema-settings/utils/createTextSettingsItem.tsx @@ -0,0 +1,39 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { TFunction, useTranslation } from 'react-i18next'; + +import { useHookDefault } from './util'; +import { SchemaSettingsItemType } from '../types'; +import { useCompile } from '../../../schema-component/hooks/useCompile'; + +export interface CreateTextSchemaSettingsItemProps { + name: string; + useVisible?: () => boolean; + title: string | ((t: TFunction<'translation', undefined>) => string); + useTextClick: () => void; +} + +export function createTextSettingsItem(options: CreateTextSchemaSettingsItemProps): SchemaSettingsItemType { + const { name, useVisible, title, useTextClick = useHookDefault } = options; + return { + name, + type: 'item', + useVisible, + useComponentProps() { + const compile = useCompile(); + const { t } = useTranslation(); + const onClick = useTextClick(); + return { + title: typeof title === 'function' ? title(t) : compile(title), + onClick, + }; + }, + }; +} diff --git a/packages/core/client/src/application/schema-settings/utils/index.ts b/packages/core/client/src/application/schema-settings/utils/index.ts new file mode 100644 index 0000000000..6c220104c8 --- /dev/null +++ b/packages/core/client/src/application/schema-settings/utils/index.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './createModalSettingsItem'; +export * from './createSelectSettingsItem'; +export * from './createSwitchSettingsItem'; +export * from './createTextSettingsItem'; diff --git a/packages/core/client/src/application/schema-settings/utils/util.ts b/packages/core/client/src/application/schema-settings/utils/util.ts index e41403e1bb..ef3538dcc1 100644 --- a/packages/core/client/src/application/schema-settings/utils/util.ts +++ b/packages/core/client/src/application/schema-settings/utils/util.ts @@ -12,28 +12,24 @@ import _ from 'lodash'; type IGetNewSchema = { fieldSchema: ISchema; - schemaKey: string; + schemaKey?: string; + parentSchemaKey?: string; value: any; valueKeys?: string[]; }; export function getNewSchema(options: IGetNewSchema) { - const { fieldSchema, schemaKey, value, valueKeys } = options as any; - const schemaKeyArr = schemaKey.split('.'); - const clonedSchema = _.cloneDeep(fieldSchema[schemaKeyArr[0]]); + const { fieldSchema, schemaKey, value, parentSchemaKey, valueKeys } = options; if (value != undefined && typeof value === 'object') { Object.keys(value).forEach((key) => { if (valueKeys && !valueKeys.includes(key)) return; - _.set(clonedSchema, `${schemaKeyArr.slice(1)}.${key}`, value[key]); + _.set(fieldSchema, `${parentSchemaKey}.${key}`, value[key]); }); } else { - _.set(clonedSchema, schemaKeyArr.slice(1), value); + _.set(fieldSchema, schemaKey, value); } - return { - 'x-uid': fieldSchema['x-uid'], - [schemaKeyArr[0]]: clonedSchema, - }; + return fieldSchema; } export const useHookDefault = (defaultValues?: any) => defaultValues; diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 0cd84c974f..df84a3c970 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -7,6 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { css } from '@emotion/css'; import { Field, Form } from '@formily/core'; import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; import { untracked } from '@formily/reactive'; @@ -21,7 +22,6 @@ import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next'; import { NavigateFunction } from 'react-router-dom'; import { useReactToPrint } from 'react-to-print'; -import { css } from '@emotion/css'; import { AssociationFilter, useCollection, @@ -1582,12 +1582,13 @@ export const useParseURLAndParams = () => { return { parseURLAndParams }; }; -export function useLinkActionProps() { +export function useLinkActionProps(componentProps?: any) { const navigate = useNavigateNoUpdate(); const fieldSchema = useFieldSchema(); + const componentPropsValue = fieldSchema?.['x-component-props'] || componentProps; const { t } = useTranslation(); - const url = fieldSchema?.['x-component-props']?.['url']; - const searchParams = fieldSchema?.['x-component-props']?.['params'] || []; + const url = componentPropsValue?.['url']; + const searchParams = componentPropsValue?.['params'] || []; const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow']; const { parseURLAndParams } = useParseURLAndParams(); diff --git a/packages/core/client/src/block-provider/index.tsx b/packages/core/client/src/block-provider/index.tsx index 1547eaf172..558329067e 100644 --- a/packages/core/client/src/block-provider/index.tsx +++ b/packages/core/client/src/block-provider/index.tsx @@ -17,3 +17,4 @@ export * from './TableFieldProvider'; export * from './TableSelectorProvider'; export * from './DetailsBlockProvider'; export * from './hooks'; +export { useLinkActionProps } from './hooks/index'; diff --git a/packages/core/client/src/global-theme/index.tsx b/packages/core/client/src/global-theme/index.tsx index 592f50642b..d809657a39 100644 --- a/packages/core/client/src/global-theme/index.tsx +++ b/packages/core/client/src/global-theme/index.tsx @@ -9,7 +9,7 @@ import { ConfigProvider, theme as antdTheme } from 'antd'; import _ from 'lodash'; -import React, { createContext, useCallback, useMemo, useRef } from 'react'; +import React, { createContext, FC, useCallback, useMemo, useRef } from 'react'; import compatOldTheme from './compatOldTheme'; import { addCustomAlgorithmToTheme } from './customAlgorithm'; import defaultTheme from './defaultTheme'; @@ -41,7 +41,11 @@ export const useGlobalTheme = () => { return React.useContext(GlobalThemeContext) || ({ theme: {}, isDarkTheme: false } as GlobalThemeContextProps); }; -export const GlobalThemeProvider = ({ children, theme: themeFromProps }) => { +interface GlobalThemeProviderProps { + theme?: ThemeConfig; +} + +export const GlobalThemeProvider: FC = ({ children, theme: themeFromProps }) => { const [theme, setTheme] = React.useState(themeFromProps || defaultTheme); const currentSettingThemeRef = useRef(null); const currentEditingThemeRef = useRef(null); diff --git a/packages/core/client/src/index.ts b/packages/core/client/src/index.ts index d2284473cd..f6dbc4f821 100644 --- a/packages/core/client/src/index.ts +++ b/packages/core/client/src/index.ts @@ -70,5 +70,6 @@ export * from './modules/blocks/data-blocks/table'; export * from './modules/blocks/data-blocks/table-selector'; export * from './modules/blocks/index'; export * from './modules/blocks/useParentRecordCommon'; +export { OpenModeProvider, useOpenModeContext } from './modules/popup/OpenModeProvider'; export { VariablePopupRecordProvider } from './modules/variable/variablesProvider/VariablePopupRecordProvider'; diff --git a/packages/core/client/src/modules/actions/add-child/CreateChildInitializer.tsx b/packages/core/client/src/modules/actions/add-child/CreateChildInitializer.tsx index c42d3a2fc5..06b7cf539e 100644 --- a/packages/core/client/src/modules/actions/add-child/CreateChildInitializer.tsx +++ b/packages/core/client/src/modules/actions/add-child/CreateChildInitializer.tsx @@ -11,7 +11,9 @@ import React from 'react'; import { usePagePopup } from '../../../schema-component/antd/page/pagePopupUtils'; import { CONTEXT_SCHEMA_KEY } from '../../../schema-component/antd/page/usePopupContextInActionOrAssociationField'; import { ActionInitializerItem } from '../../../schema-initializer/items/ActionInitializerItem'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const CreateChildInitializer = (props) => { + const { defaultOpenMode } = useOpenModeContext(); const { getPopupContext } = usePagePopup(); const schema = { type: 'void', @@ -22,7 +24,7 @@ export const CreateChildInitializer = (props) => { 'x-component': 'Action', 'x-visible': '{{treeTable}}', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, type: 'link', addChild: true, style: { height: 'auto', lineHeight: 'normal' }, diff --git a/packages/core/client/src/modules/actions/add-child/addChildActionSettings.tsx b/packages/core/client/src/modules/actions/add-child/addChildActionSettings.tsx index c69de3eb8a..bb2d13bacf 100644 --- a/packages/core/client/src/modules/actions/add-child/addChildActionSettings.tsx +++ b/packages/core/client/src/modules/actions/add-child/addChildActionSettings.tsx @@ -7,14 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useFieldSchema } from '@formily/react'; import { useMemo } from 'react'; import { useSchemaToolbar } from '../../../application'; import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings'; import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager'; import { ButtonEditor } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; -import { SchemaSettingsLinkageRules, SchemaSettingsEnableChildCollections } from '../../../schema-settings'; +import { SchemaSettingsEnableChildCollections, SchemaSettingsLinkageRules } from '../../../schema-settings'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const addChildActionSettings = new SchemaSettings({ name: 'actionSettings:addChild', @@ -42,9 +42,12 @@ export const addChildActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/actions/add-new/CreateActionInitializer.tsx b/packages/core/client/src/modules/actions/add-new/CreateActionInitializer.tsx index d3b1f21099..2713434cf2 100644 --- a/packages/core/client/src/modules/actions/add-new/CreateActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/add-new/CreateActionInitializer.tsx @@ -12,8 +12,10 @@ import { useSchemaInitializerItem } from '../../../application'; import { usePagePopup } from '../../../schema-component/antd/page/pagePopupUtils'; import { CONTEXT_SCHEMA_KEY } from '../../../schema-component/antd/page/usePopupContextInActionOrAssociationField'; import { ActionInitializerItem } from '../../../schema-initializer/items/ActionInitializerItem'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const CreateActionInitializer = () => { + const { defaultOpenMode } = useOpenModeContext(); const { getPopupContext } = usePagePopup(); const schema = { type: 'void', @@ -25,7 +27,7 @@ export const CreateActionInitializer = () => { 'x-component': 'Action', 'x-decorator': 'ACLActionProvider', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, type: 'primary', component: 'CreateRecordAction', icon: 'PlusOutlined', diff --git a/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx b/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx index e53c9dd311..f0e79b3eb7 100644 --- a/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx +++ b/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx @@ -14,6 +14,7 @@ import { useCollection_deprecated, useCollectionManager_deprecated } from '../.. import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; import { SchemaSettingsEnableChildCollections } from '../../../schema-settings/SchemaSettings'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const addNewActionSettings = new SchemaSettings({ name: 'actionSettings:addNew', @@ -29,9 +30,12 @@ export const addNewActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/actions/add-record/customizeAddRecordActionSettings.tsx b/packages/core/client/src/modules/actions/add-record/customizeAddRecordActionSettings.tsx index 25476a2331..5bb83ac04c 100644 --- a/packages/core/client/src/modules/actions/add-record/customizeAddRecordActionSettings.tsx +++ b/packages/core/client/src/modules/actions/add-record/customizeAddRecordActionSettings.tsx @@ -11,6 +11,7 @@ import { useSchemaToolbar } from '../../../application'; import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings'; import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const customizeAddRecordActionSettings = new SchemaSettings({ name: 'actionSettings:addRecord', @@ -26,9 +27,12 @@ export const customizeAddRecordActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx index 5d23ba1386..7bbf375e39 100644 --- a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx +++ b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx @@ -9,7 +9,7 @@ import { ArrayItems } from '@formily/antd-v5'; import { useField, useFieldSchema } from '@formily/react'; import _ from 'lodash'; -import React from 'react'; +import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useCollectionRecord, useDesignable } from '../../../'; import { useSchemaToolbar } from '../../../application'; @@ -19,7 +19,11 @@ import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/actio import { SchemaSettingsLinkageRules, SchemaSettingsModalItem } from '../../../schema-settings'; import { useURLAndHTMLSchema } from './useURLAndHTMLSchema'; -export function SchemaSettingsActionLinkItem() { +interface SchemaSettingsActionLinkItemProps { + afterSubmit?: () => void; +} + +export const SchemaSettingsActionLinkItem: FC = ({ afterSubmit }) => { const field = useField(); const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); @@ -66,11 +70,12 @@ export function SchemaSettingsActionLinkItem() { }, }); dn.refresh(); + afterSubmit?.(); }} initialValues={initialValues} /> ); -} +}; export const customizeLinkActionSettings = new SchemaSettings({ name: 'actionSettings:link', diff --git a/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx b/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx index 2c930cf550..c908c742c8 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/PopupActionInitializer.tsx @@ -12,8 +12,10 @@ import { useSchemaInitializerItem } from '../../../application'; import { usePagePopup } from '../../../schema-component/antd/page/pagePopupUtils'; import { CONTEXT_SCHEMA_KEY } from '../../../schema-component/antd/page/usePopupContextInActionOrAssociationField'; import { BlockInitializer } from '../../../schema-initializer/items'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const PopupActionInitializer = (props) => { + const { defaultOpenMode } = useOpenModeContext(); const { getPopupContext } = usePagePopup(); const schema = { type: 'void', @@ -23,7 +25,7 @@ export const PopupActionInitializer = (props) => { 'x-settings': 'actionSettings:popup', 'x-component': props?.['x-component'] || 'Action.Link', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, refreshDataBlockRequest: true, }, properties: { diff --git a/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx b/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx index 094db370ba..2d395d846b 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/UpdateActionInitializer.tsx @@ -11,8 +11,10 @@ import React from 'react'; import { usePagePopup } from '../../../schema-component/antd/page/pagePopupUtils'; import { CONTEXT_SCHEMA_KEY } from '../../../schema-component/antd/page/usePopupContextInActionOrAssociationField'; import { ActionInitializerItem } from '../../../schema-initializer/items/ActionInitializerItem'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const UpdateActionInitializer = (props) => { + const { defaultOpenMode } = useOpenModeContext(); const { getPopupContext } = usePagePopup(); const schema = { type: 'void', @@ -22,7 +24,7 @@ export const UpdateActionInitializer = (props) => { 'x-settings': 'actionSettings:edit', 'x-component': 'Action', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, icon: 'EditOutlined', }, properties: { diff --git a/packages/core/client/src/modules/actions/view-edit-popup/ViewActionInitializer.tsx b/packages/core/client/src/modules/actions/view-edit-popup/ViewActionInitializer.tsx index e24f7120fc..3324842450 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/ViewActionInitializer.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/ViewActionInitializer.tsx @@ -11,8 +11,10 @@ import React from 'react'; import { usePagePopup } from '../../../schema-component/antd/page/pagePopupUtils'; import { CONTEXT_SCHEMA_KEY } from '../../../schema-component/antd/page/usePopupContextInActionOrAssociationField'; import { ActionInitializerItem } from '../../../schema-initializer/items/ActionInitializerItem'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const ViewActionInitializer = (props) => { + const { defaultOpenMode } = useOpenModeContext(); const { getPopupContext } = usePagePopup(); const schema = { type: 'void', @@ -22,7 +24,7 @@ export const ViewActionInitializer = (props) => { 'x-settings': 'actionSettings:view', 'x-component': 'Action', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, }, properties: { drawer: { diff --git a/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx b/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx index d88525acd6..bb8a34a452 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/customizePopupActionSettings.tsx @@ -10,13 +10,10 @@ import { useSchemaToolbar } from '../../../application'; import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings'; import { useCollection_deprecated } from '../../../collection-manager'; -import { - ButtonEditor, - RemoveButton, - RefreshDataBlockRequest, -} from '../../../schema-component/antd/action/Action.Designer'; +import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; import { SchemaSettingsLinkageRules } from '../../../schema-settings'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const customizePopupActionSettings = new SchemaSettings({ name: 'actionSettings:popup', @@ -44,9 +41,12 @@ export const customizePopupActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/actions/view-edit-popup/editActionSettings.tsx b/packages/core/client/src/modules/actions/view-edit-popup/editActionSettings.tsx index 2531d1a3fd..863a0a431d 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/editActionSettings.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/editActionSettings.tsx @@ -13,6 +13,7 @@ import { useCollection_deprecated } from '../../../collection-manager'; import { ButtonEditor } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; import { SchemaSettingsLinkageRules } from '../../../schema-settings'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const editActionSettings = new SchemaSettings({ name: 'actionSettings:edit', @@ -40,9 +41,12 @@ export const editActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/actions/view-edit-popup/viewActionSettings.tsx b/packages/core/client/src/modules/actions/view-edit-popup/viewActionSettings.tsx index 7d705b93e1..7e67a50561 100644 --- a/packages/core/client/src/modules/actions/view-edit-popup/viewActionSettings.tsx +++ b/packages/core/client/src/modules/actions/view-edit-popup/viewActionSettings.tsx @@ -13,6 +13,7 @@ import { useCollection_deprecated } from '../../../collection-manager'; import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; import { SchemaSettingsLinkageRules } from '../../../schema-settings'; +import { useOpenModeContext } from '../../popup/OpenModeProvider'; export const viewActionSettings = new SchemaSettings({ name: 'actionSettings:view', @@ -40,9 +41,12 @@ export const viewActionSettings = new SchemaSettings({ { name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, - componentProps: { - openMode: true, - openSize: true, + useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); + return { + openMode: !hideOpenMode, + openSize: !hideOpenMode, + }; }, }, { diff --git a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx index 2701d6762b..9b3f2970e3 100644 --- a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx @@ -33,6 +33,7 @@ import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSetti import { SchemaSettingsSortingRule } from '../../../../schema-settings/SchemaSettingsSortingRule'; import { useIsShowMultipleSwitch } from '../../../../schema-settings/hooks/useIsShowMultipleSwitch'; import { useLocalVariables, useVariables } from '../../../../variables'; +import { useOpenModeContext } from '../../../popup/OpenModeProvider'; const enableLink = { name: 'enableLink', @@ -162,6 +163,7 @@ const quickCreate: any = { name: 'quickCreate', type: 'select', useComponentProps() { + const { defaultOpenMode } = useOpenModeContext(); const { t } = useTranslation(); const field = useField(); const fieldSchema = useFieldSchema(); @@ -194,7 +196,7 @@ const quickCreate: any = { 'x-component': 'Action', 'x-decorator': 'ACLActionProvider', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, type: 'default', component: 'CreateRecordAction', }, diff --git a/packages/core/client/src/modules/popup/OpenModeProvider.tsx b/packages/core/client/src/modules/popup/OpenModeProvider.tsx new file mode 100644 index 0000000000..a3d102e8fe --- /dev/null +++ b/packages/core/client/src/modules/popup/OpenModeProvider.tsx @@ -0,0 +1,99 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useCallback, useMemo } from 'react'; +import ActionDrawer from '../../schema-component/antd/action/Action.Drawer'; +import ActionModal from '../../schema-component/antd/action/Action.Modal'; +import ActionPage from '../../schema-component/antd/action/Action.Page'; + +type OpenMode = 'drawer' | 'page' | 'modal'; + +interface OpenModeProviderProps { + /** + * @default 'drawer' + * open mode 的全局默认值 + */ + defaultOpenMode?: OpenMode; + /** + * @default { drawer: ActionDrawer, page: ActionPage, modal: ActionModal } + * 根据 open mode 获取对应的组件 + */ + openModeToComponent?: Partial>; + /** + * @default false + * 隐藏 open mode 的配置选项 + */ + hideOpenMode?: boolean; +} + +const defaultContext: OpenModeProviderProps = { + defaultOpenMode: 'drawer', + openModeToComponent: { + drawer: ActionDrawer, + page: ActionPage, + modal: ActionModal, + }, + hideOpenMode: false, +}; + +const OpenModeContext = React.createContext<{ + defaultOpenMode: OpenModeProviderProps['defaultOpenMode']; + hideOpenMode: boolean; + getComponentByOpenMode: (openMode: OpenMode) => any; +}>(null); + +/** + * 为按钮的 Open mode 选项提供上下文 + * @param props + * @returns + */ +export const OpenModeProvider: FC = (props) => { + const context = useMemo(() => { + const result = { ...defaultContext }; + + if (props.defaultOpenMode !== undefined) { + result.defaultOpenMode = props.defaultOpenMode; + } + if (props.openModeToComponent !== undefined) { + result.openModeToComponent = props.openModeToComponent; + } + if (props.hideOpenMode !== undefined) { + result.hideOpenMode = props.hideOpenMode; + } + + return result; + }, [props.defaultOpenMode, props.openModeToComponent, props.hideOpenMode]); + + const getComponentByOpenMode = useCallback( + (openMode: OpenMode) => { + const result = context.openModeToComponent[openMode]; + + if (!result) { + console.error(`OpenModeProvider: openModeToComponent[${openMode}] is not defined`); + } + + return result; + }, + [context], + ); + + const value = useMemo(() => { + return { + defaultOpenMode: context.defaultOpenMode, + hideOpenMode: context.hideOpenMode, + getComponentByOpenMode, + }; + }, [context.defaultOpenMode, context.hideOpenMode, getComponentByOpenMode]); + + return {props.children}; +}; + +export const useOpenModeContext = () => { + return React.useContext(OpenModeContext); +}; diff --git a/packages/core/client/src/pm/PluginManagerLink.tsx b/packages/core/client/src/pm/PluginManagerLink.tsx index 98cefbd363..657cad7f78 100644 --- a/packages/core/client/src/pm/PluginManagerLink.tsx +++ b/packages/core/client/src/pm/PluginManagerLink.tsx @@ -47,7 +47,8 @@ export const SettingsCenterDropdown = () => { return { key: setting.name, icon: setting.icon, - label: {compile(setting.title)}, + label: setting.link ?
window.open(setting.link)}>{compile(setting.title)}
: + {compile(setting.title)} }; }); }, [app, t]); diff --git a/packages/core/client/src/pm/PluginSetting.tsx b/packages/core/client/src/pm/PluginSetting.tsx index 69bab956b7..96577e8a63 100644 --- a/packages/core/client/src/pm/PluginSetting.tsx +++ b/packages/core/client/src/pm/PluginSetting.tsx @@ -144,6 +144,12 @@ export const AdminSettingsLayout = () => { style={{ height: 'calc(100vh - 46px)', overflowY: 'auto', overflowX: 'hidden' }} onClick={({ key }) => { const plugin = settings.find((item) => item.name === key); + + if (plugin.link) { + window.open(plugin.link, '_blank'); + return; + } + if (plugin.children?.length) { return navigate(getFirstDeepChildPath(plugin.children)); } else { diff --git a/packages/core/client/src/schema-component/antd/action/Action.Container.tsx b/packages/core/client/src/schema-component/antd/action/Action.Container.tsx index 6033ebb988..8e4f48f83a 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Container.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Container.tsx @@ -10,21 +10,19 @@ import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; import React from 'react'; import { useActionContext } from '.'; -import { ActionDrawer } from './Action.Drawer'; -import { ActionModal } from './Action.Modal'; -import { ActionPage } from './Action.Page'; +import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider'; +import { useCurrentPopupContext } from '../page/PagePopups'; import { ComposedActionDrawer } from './types'; export const ActionContainer: ComposedActionDrawer = observer( (props: any) => { const { openMode } = useActionContext(); - if (openMode === 'drawer') { - return ; - } - if (openMode === 'modal') { - return ; - } - return ; + const { getComponentByOpenMode } = useOpenModeContext(); + const { currentLevel } = useCurrentPopupContext(); + + const Component = getComponentByOpenMode(openMode); + + return ; }, { displayName: 'ActionContainer' }, ); diff --git a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx index d39603c45d..5862d10a37 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx @@ -28,6 +28,7 @@ import { import { DataSourceProvider, useDataSourceKey } from '../../../data-source'; import { FlagProvider } from '../../../flag-provider'; import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings'; +import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; import { GeneralSchemaDesigner } from '../../../schema-settings/GeneralSchemaDesigner'; import { @@ -656,6 +657,7 @@ export const actionSettingsItems: SchemaSettingOptions['items'] = [ name: 'openMode', Component: SchemaSettingOpenModeSchemaItems, useComponentProps() { + const { hideOpenMode } = useOpenModeContext(); const fieldSchema = useFieldSchema(); const isPopupAction = [ 'create', @@ -667,8 +669,8 @@ export const actionSettingsItems: SchemaSettingOptions['items'] = [ ].includes(fieldSchema['x-action'] || ''); return { - openMode: isPopupAction, - openSize: isPopupAction, + openMode: isPopupAction && !hideOpenMode, + openSize: isPopupAction && !hideOpenMode, }; }, }, diff --git a/packages/core/client/src/schema-component/antd/action/Action.Page.tsx b/packages/core/client/src/schema-component/antd/action/Action.Page.tsx index 4565e60584..e06898c3e6 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Page.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Page.tsx @@ -11,25 +11,26 @@ import { RecursionField, observer, useFieldSchema } from '@formily/react'; import React, { useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useActionContext } from '.'; -import { useCurrentPopupContext } from '../page/PagePopups'; +import { BackButtonUsedInSubPage } from '../page/BackButtonUsedInSubPage'; +import { TabsContextProvider, useTabsContext } from '../tabs/context'; import { useActionPageStyle } from './Action.Page.style'; import { usePopupOrSubpagesContainerDOM } from './hooks/usePopupSlotDOM'; import { ComposedActionDrawer } from './types'; export const ActionPage: ComposedActionDrawer = observer( - () => { + ({ level }) => { const filedSchema = useFieldSchema(); const ctx = useActionContext(); const { getContainerDOM } = usePopupOrSubpagesContainerDOM(); const { styles } = useActionPageStyle(); - const { currentLevel } = useCurrentPopupContext(); + const tabContext = useTabsContext(); const style = useMemo(() => { return { // 20 is the z-index value of the main page - zIndex: 20 + currentLevel, + zIndex: 20 + level, }; - }, [currentLevel]); + }, [level]); if (!ctx.visible) { return null; @@ -37,11 +38,19 @@ export const ActionPage: ComposedActionDrawer = observer( const actionPageNode = (
- + }> + +
); - return createPortal(actionPageNode, getContainerDOM()); + const container = getContainerDOM(); + + if (container) { + return createPortal(actionPageNode, container); + } + + return actionPageNode; }, { displayName: 'ActionPage' }, ); diff --git a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx index b91552960b..9e8e8dfb92 100644 --- a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx +++ b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx @@ -79,7 +79,7 @@ export const ActionBar = withDynamicSchemaProps( > {props.children && (
- + {fieldSchema.mapProperties((schema, key) => { return ; })} @@ -129,7 +129,7 @@ export const ActionBar = withDynamicSchemaProps( return ; })} - + {fieldSchema.mapProperties((schema, key) => { if (schema['x-align'] === 'left') { return null; diff --git a/packages/core/client/src/schema-component/antd/action/types.ts b/packages/core/client/src/schema-component/antd/action/types.ts index 78484c1814..af5cbaf24b 100644 --- a/packages/core/client/src/schema-component/antd/action/types.ts +++ b/packages/core/client/src/schema-component/antd/action/types.ts @@ -87,7 +87,11 @@ export type ComposedAction = React.FC & { [key: string]: any; }; -export type ActionDrawerProps = T & { footerNodeName?: string }; +export type ActionDrawerProps = T & { + footerNodeName?: string; + /** 当前弹窗嵌套的层级 */ + level?: number; +}; export type ComposedActionDrawer = React.FC> & { Footer?: React.FC; diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx index f33f496904..153e4b8f8e 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx @@ -13,6 +13,7 @@ import React, { FC, Fragment, useRef, useState } from 'react'; import { useDesignable } from '../../'; import { WithoutTableFieldResource } from '../../../block-provider'; import { useCollectionManager, useCollectionRecordData } from '../../../data-source'; +import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider'; import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider'; import { useCompile } from '../../hooks'; import { ActionContextProvider, useActionContext } from '../action'; @@ -139,6 +140,7 @@ export const ReadPrettyInternalViewer: React.FC = observer( const ellipsisWithTooltipRef = useRef(); const { visibleWithURL, setVisibleWithURL } = usePagePopup(); const [btnHover, setBtnHover] = useState(!!visibleWithURL); + const { defaultOpenMode } = useOpenModeContext(); const btnElement = ( @@ -175,7 +177,7 @@ export const ReadPrettyInternalViewer: React.FC = observer( setVisible?.(value); setVisibleWithURL?.(value); }, - openMode: 'drawer', + openMode: defaultOpenMode, snapshot: collectionField?.interface === 'snapshot', fieldSchema: fieldSchema, }} diff --git a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx index 1b348571d5..39facb63b8 100644 --- a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx +++ b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx @@ -73,13 +73,14 @@ const useStyles = createStyles(({ css, token }: CustomCreateStylesUtils) => { export interface BlockItemProps { name?: string; className?: string; + style?: React.CSSProperties; children?: React.ReactNode; } export const BlockItem: React.FC = withDynamicSchemaProps( (props) => { // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema - const { className, children } = useProps(props); + const { className, children, style } = useProps(props); const { styles: blockItemCss } = useStyles(); const fieldSchema = useFieldSchema(); const { render } = useSchemaToolbarRender(fieldSchema); @@ -87,7 +88,12 @@ export const BlockItem: React.FC = withDynamicSchemaProps( const label = useMemo(() => getAriaLabel(), [getAriaLabel]); return ( - + {render()} console.log(err)}> {children} diff --git a/packages/core/client/src/schema-component/antd/color-select/ColorSelect.tsx b/packages/core/client/src/schema-component/antd/color-select/ColorSelect.tsx index 7324b4dae7..98dcba09b1 100644 --- a/packages/core/client/src/schema-component/antd/color-select/ColorSelect.tsx +++ b/packages/core/client/src/schema-component/antd/color-select/ColorSelect.tsx @@ -13,7 +13,7 @@ import { Select, SelectProps, Tag } from 'antd'; import React from 'react'; import { useCompile } from '../../hooks/useCompile'; -const colors = { +const defaultColors = { red: '{{t("Red")}}', magenta: '{{t("Magenta")}}', volcano: '{{t("Volcano")}}', @@ -30,13 +30,15 @@ const colors = { export interface ColorSelectProps extends SelectProps { suffix?: React.ReactNode; + colors?: Record; } export const ColorSelect = connect( (props: ColorSelectProps) => { const compile = useCompile(); + const { colors = defaultColors, ...selectProps } = props; return ( - {Object.keys(colors).map((color) => ( {compile(colors[color] || colors.default)} @@ -53,7 +55,7 @@ export const ColorSelect = connect( }), mapReadPretty((props) => { const compile = useCompile(); - const { value } = props; + const { value, colors = defaultColors } = props; if (!colors[value]) { return null; } diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx index e96c47884b..57782fff10 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx @@ -23,6 +23,7 @@ import { } from '../../../collection-manager'; import { useCollectionManager } from '../../../data-source'; import { useFlag } from '../../../flag-provider'; +import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider'; import { useRecord } from '../../../record-provider'; import { useColumnSchema } from '../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { generalSettingsItems } from '../../../schema-items/GeneralSettings'; @@ -54,6 +55,7 @@ export const allowAddNew: SchemaSettingsItemType = { return !flag?.isInSubTable && !readPretty && isAssociationField && ['Picker'].includes(fieldMode); }, useComponentProps() { + const { defaultOpenMode } = useOpenModeContext(); const { t } = useTranslation(); const field = useField(); const fieldSchema = useFieldSchema(); @@ -83,7 +85,7 @@ export const allowAddNew: SchemaSettingsItemType = { 'x-component': 'Action', 'x-decorator': 'ACLActionProvider', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, type: 'default', component: 'CreateRecordAction', }, @@ -540,6 +542,7 @@ export const formItemSettings = new SchemaSettings({ return !readPretty && isAssociationField && ['Select'].includes(fieldMode); }, useComponentProps() { + const { defaultOpenMode } = useOpenModeContext(); const { t } = useTranslation(); const field = useField(); const fieldSchema = useFieldSchema(); @@ -572,7 +575,7 @@ export const formItemSettings = new SchemaSettings({ 'x-component': 'Action', 'x-decorator': 'ACLActionProvider', 'x-component-props': { - openMode: 'drawer', + openMode: defaultOpenMode, type: 'default', component: 'CreateRecordAction', }, diff --git a/packages/core/client/src/schema-component/antd/page/BackButtonUsedInSubPage.tsx b/packages/core/client/src/schema-component/antd/page/BackButtonUsedInSubPage.tsx index 3229e968be..517e6152c7 100644 --- a/packages/core/client/src/schema-component/antd/page/BackButtonUsedInSubPage.tsx +++ b/packages/core/client/src/schema-component/antd/page/BackButtonUsedInSubPage.tsx @@ -14,14 +14,25 @@ import { useToken } from '../../../style'; import { useCurrentPopupContext } from './PagePopups'; import { usePagePopup } from './pagePopupUtils'; +export const useBackButton = () => { + const { params } = useCurrentPopupContext(); + const { closePopup } = usePagePopup(); + const goBack = useCallback(() => { + closePopup(params?.popupuid); + }, [closePopup, params?.popupuid]); + + return { + goBack, + }; +}; + /** * Used for the back button in subpages * @returns */ export const BackButtonUsedInSubPage = () => { - const { params } = useCurrentPopupContext(); - const { closePopup } = usePagePopup(); const { token } = useToken(); + const { goBack } = useBackButton(); // tab item gutter, this is fixed value in antd const horizontalItemGutter = 32; @@ -35,17 +46,7 @@ export const BackButtonUsedInSubPage = () => { }; }, [token.paddingXS]); - const handleClick = useCallback(() => { - closePopup(params.popupuid); - }, [params.popupuid]); - return ( - +
+ ); +}; + +const Demo = () => { + return ( + + + + ); +}; + +export default Demo; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/createSingleItemInitializer.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/createSingleItemInitializer.ts new file mode 100644 index 0000000000..03b1b5d905 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/createSingleItemInitializer.ts @@ -0,0 +1,15 @@ +import { Grid, SchemaInitializerItemType } from '@nocobase/client'; +import { SchemaInitializer } from '@nocobase/client'; + +export function createSingleItemInitializer(initializerItem: SchemaInitializerItemType) { + return new SchemaInitializer({ + name: 'test', + icon: 'SettingOutlined', + wrap: Grid.wrap, + title: 'Test', + style: { + marginLeft: 8, + }, + items: [initializerItem], + }); +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/getMockData.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/getMockData.ts new file mode 100644 index 0000000000..3aeb151a5d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/getMockData.ts @@ -0,0 +1,35 @@ +import type { TreeDataNode } from 'antd'; + +const x = 3; +const y = 2; +const z = 1; + +export function getMockData() { + const treeData: TreeDataNode[] = []; + + const generateData = (_level: number, _preKey?: React.Key, _tns?: TreeDataNode[]) => { + const preKey = _preKey || '0'; + const tns = _tns || treeData; + + const children: React.Key[] = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({ title: key, key }); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(level, key, tns[index].children); + }); + }; + + generateData(z); + + return treeData; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/schemaViewer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/schemaViewer.tsx new file mode 100644 index 0000000000..f5e0195904 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/fixtures/schemaViewer.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import _ from 'lodash'; + +import { useFieldSchema, ISchema } from '@formily/react'; + +function ShowSchema({ children, schemaKey }) { + const filedSchema = useFieldSchema(); + const key = schemaKey ? `properties.schema.${schemaKey}` : `properties.schema`; + return ( + <> +
{JSON.stringify(_.get(filedSchema.toJSON(), key), null, 2)}
+ {children} + + ); +} + +export function schemaViewer(schema: ISchema, schemaKey?: string) { + return { + type: 'void', + name: 'schema-viewer', + 'x-component': ShowSchema, + 'x-component-props': { + schemaKey, + }, + properties: { + schema, + }, + }; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-404.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-404.tsx new file mode 100644 index 0000000000..9d90ace5f7 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-404.tsx @@ -0,0 +1,32 @@ +import { Plugin } from '@nocobase/client'; +import { MobilePage, MobileNotFoundPage } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobileNotFoundPage }); + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + Component: MobilePage, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/test'], + }, + plugins: [DemoPlugin], + apis: { + 'uiSchemas:getJsonSchema/test': { + data: {}, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-basic.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-basic.tsx new file mode 100644 index 0000000000..586a91d0b5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-basic.tsx @@ -0,0 +1,36 @@ +import { Plugin } from '@nocobase/client'; +import { MobilePage } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + Component: MobilePage, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/test'], + }, + plugins: [DemoPlugin], + apis: { + 'uiSchemas:getJsonSchema/test': { + data: { + type: 'void', + name: 'test', + 'x-uid': 'test', + 'x-content': 'Schema Test Page', + }, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-schema.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-schema.tsx new file mode 100644 index 0000000000..be289ba70a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-schema.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin, Grid, BlockItem } from '@nocobase/client'; +import PluginMobileClient, { MobileProviders, getMobilePageSchema } from '@nocobase/plugin-mobile/client'; + +import { schemaViewer } from './fixtures/schemaViewer'; + +const Demo = () => { + return ( + + + + ); +}; + +class DemoPlugin extends Plugin { + async load() { + // this.app.router.add('root', { path: '/', Component: Demo }); + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + Component: Demo, + }); + this.app.router.add('schema.page.tabs', { + path: '/page/:pageSchemaUid/tabs', + }); + this.app.router.add('schema.page.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/page1/tabs/tab1'], + }, + plugins: [DemoPlugin, PluginMobileClient], + components: { + Grid, + BlockItem, + }, + designable: true, + apis: { + 'mobileRoutes:list': { + data: [ + { + id: 1, + title: 'Page1', + schemaUid: 'page1', + children: [ + { + id: 2, + title: 'Tab1', + schemaUid: 'tab1', + }, + ], + }, + ], + }, + 'uiSchemas:getJsonSchema/tab1': { + data: Grid.wrap({ + type: 'void', + name: 'test', + 'x-component': 'div', + 'x-content': 'Tab1 Content', + }), + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-settings.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-settings.tsx new file mode 100644 index 0000000000..398e11b98b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-dynamic-page-settings.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { mobilePageSettings } from '@nocobase/plugin-mobile/client'; + +import { schemaViewer } from './fixtures/schemaViewer'; + +const schema = { + type: 'void', + name: 'test', + 'x-settings': mobilePageSettings.name, + 'x-decorator': 'BlockItem', + 'x-component': 'div', + 'x-decorator-props': { + style: { + height: 100, + }, + }, + 'x-content': 'Settings', +}; + +const Demo = () => { + return ( +
+ +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + plugins: [DemoPlugin], + schemaSettings: [mobilePageSettings], + designable: true, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-basic.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-basic.tsx new file mode 100644 index 0000000000..3b21f16ccf --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-basic.tsx @@ -0,0 +1,45 @@ +import { Plugin } from '@nocobase/client'; +import { MobileHomePage, MobileRoutesProvider } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; +import React from 'react'; + +const Demo = () => { + return ( +
+ + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/', Component: Demo }); + this.app.router.add('test', { path: '/test', element:
Test Page
}); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [ + { + id: 28, + type: 'link', + options: { + url: '/test', + }, + }, + ], + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-custom.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-custom.tsx new file mode 100644 index 0000000000..eff0c83bb4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-custom.tsx @@ -0,0 +1,94 @@ +/** + * iframe: true + */ +import { Plugin } from '@nocobase/client'; +import PluginMobileClient, { Mobile } from '@nocobase/plugin-mobile/client'; +import { mockApp } from '@nocobase/client/demo-utils'; +import React from 'react'; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/m', element: }); + const mobilePlugin = this.pluginManager.get(PluginMobileClient); + mobilePlugin.mobileRouter.add('mobile.home', { + path: '/', + element:
Custom Home Page
, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/m'], + }, + plugins: [ + [ + PluginMobileClient, + { + config: { + router: { + type: 'memory', + basename: '/m', + initialEntries: ['/m'], + }, + skipLogin: true, + desktopMode: false, + }, + }, + ], + DemoPlugin, + ], + apis: { + 'mobileRoutes:list': { + data: [ + { + id: '1', + title: 'Home', + icon: 'AppstoreOutlined', + type: 'link', + options: { + url: '/', + }, + }, + ], + }, + 'uiSchemas:getJsonSchema/nocobase-mobile': { + data: { + type: 'void', + properties: { + pageOutlet: { + type: 'void', + 'x-component': 'MobilePageOutlet', + 'x-uid': '5dix5scrv77', + 'x-async': false, + 'x-index': 1, + }, + tabBar: { + type: 'void', + 'x-component': 'MobileTabBar', + 'x-decorator': 'BlockItem', + 'x-decorator-props': { + style: { + position: 'sticky', + bottom: 0, + zIndex: 1000, + }, + }, + 'x-toolbar-props': { + draggable: false, + }, + 'x-uid': 'cwf8ti4suno', + 'x-async': false, + 'x-index': 2, + }, + }, + name: 'nocobase-mobile', + 'x-uid': 'nocobase-mobile', + 'x-async': false, + }, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-null.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-null.tsx new file mode 100644 index 0000000000..13cee4def3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-home-null.tsx @@ -0,0 +1,34 @@ +import { Plugin } from '@nocobase/client'; +import { MobileHomePage, MobileRoutesProvider } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; +import React from 'react'; + +const Demo = () => { + return ( + + + + ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [], + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-actions.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-actions.tsx new file mode 100644 index 0000000000..0e99b957df --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-actions.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import PluginMobileClient, { MobilePageProvider, MobileTitleProvider } from '@nocobase/plugin-mobile/client'; + +const schema = { + type: 'void', + name: 'test', + 'x-component': 'MobilePageNavigationBar', + properties: { + actionBar: { + type: 'void', + 'x-component': 'MobileNavigationActionBar', + 'x-component-props': { + spaceProps: { + style: { + flexWrap: 'nowrap', + }, + }, + }, + properties: { + action1: { + version: '2.0', + 'x-position': 'right', + type: 'void', + 'x-component': 'Action', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-use-component-props': 'useMobileNavigationBarLink', + 'x-component-props': { + link: '/?right=true', + title: 'Right1', + component: 'MobileNavigationBarAction', + }, + }, + action2: { + version: '2.0', + 'x-position': 'right', + type: 'void', + 'x-component': 'Action', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-use-component-props': 'useMobileNavigationBarLink', + 'x-component-props': { + link: '/?right=true', + title: 'Right2', + component: 'MobileNavigationBarAction', + }, + }, + action3: { + version: '2.0', + 'x-position': 'left', + type: 'void', + 'x-component': 'Action', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-use-component-props': 'useMobileNavigationBarLink', + 'x-component-props': { + link: '/?left=true', + title: 'Left', + component: 'MobileNavigationBarAction', + }, + }, + action4: { + version: '2.0', + 'x-position': 'bottom', + type: 'void', + 'x-component': 'div', + 'x-content': 'Bottom', + }, + }, + }, + }, +}; + +const Demo = () => { + return ( +
+ + + + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + plugins: [DemoPlugin, PluginMobileClient], + designable: true, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-basic.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-basic.tsx new file mode 100644 index 0000000000..a2c43e9f7d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-basic.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { MobilePageNavigationBar, MobilePageProvider, MobileTitleProvider } from '@nocobase/plugin-mobile/client'; + +const schema = { + type: 'void', + name: 'test', + 'x-component': 'MobilePageNavigationBar', +}; + +const Demo = () => { + return ( +
+ + + + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobilePageNavigationBar }); + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + plugins: [DemoPlugin], + designable: true, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-false.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-false.tsx new file mode 100644 index 0000000000..dc97edc638 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-false.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { MobilePageNavigationBar, MobilePageProvider, MobileTitleProvider } from '@nocobase/plugin-mobile/client'; + +const schema = { + type: 'void', + name: 'test', + 'x-component': 'MobilePageNavigationBar', +}; + +const Demo = () => { + return ( +
+ + + + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobilePageNavigationBar }); + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + plugins: [DemoPlugin], + designable: true, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-title-false.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-title-false.tsx new file mode 100644 index 0000000000..c733ba49a8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-navigation-bar-title-false.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { MobilePageNavigationBar, MobilePageProvider, MobileTitleProvider } from '@nocobase/plugin-mobile/client'; + +const schema = { + type: 'void', + name: 'test', + 'x-component': 'MobilePageNavigationBar', +}; + +const Demo = () => { + return ( +
+ + + + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobilePageNavigationBar }); + this.app.router.add('root', { path: '/', Component: Demo }); + } +} + +const app = mockApp({ + plugins: [DemoPlugin], + designable: true, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-not-found.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-not-found.tsx new file mode 100644 index 0000000000..ddd291a14d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-not-found.tsx @@ -0,0 +1,22 @@ +import { Plugin } from '@nocobase/client'; +import { MobileNotFoundPage } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; +import React from 'react'; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('root', { path: '/', element:
Home Page
}); + this.app.router.add('not-found', { path: '*', Component: MobileNotFoundPage }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/not-found'], + }, + plugins: [DemoPlugin], +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-404.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-404.tsx new file mode 100644 index 0000000000..73030d6382 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-404.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Plugin } from '@nocobase/client'; +import { MobileNotFoundPage, MobilePageContent, MobileRoutesProvider } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; + +const Demo = () => { + return ( + + + + ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ + MobileNotFoundPage, + }); + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + }); + this.app.router.add('schema.page.tabs', { + path: '/page/:pageSchemaUid/tabs', + }); + this.app.router.add('schema.page.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/test/tabs/tab1'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [], + }, + 'uiSchemas:getJsonSchema/tab1': { + data: {}, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-basic.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-basic.tsx new file mode 100644 index 0000000000..4c615a945e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-basic.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Plugin } from '@nocobase/client'; +import { MobilePageContent, MobileRoutesProvider } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; + +const Demo = () => { + return ( + + + + ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + }); + this.app.router.add('schema.page.tabs', { + path: '/page/:pageSchemaUid/tabs', + }); + this.app.router.add('schema.page.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/test/tabs/tab1'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [], + }, + 'uiSchemas:getJsonSchema/tab1': { + data: { + type: 'void', + name: 'test', + 'x-uid': 'test', + 'x-content': 'Schema Test Page', + }, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-first-route.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-first-route.tsx new file mode 100644 index 0000000000..daea74ce56 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-content-first-route.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Plugin } from '@nocobase/client'; +import { MobilePageContent, MobileRoutesProvider } from '@nocobase/plugin-mobile/client'; + +import { mockApp } from '@nocobase/client/demo-utils'; + +const Demo = () => { + return ( + + + + ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/page1'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [ + { + id: 28, + title: 'Test', + schemaUid: 'page1', + children: [ + { + id: 29, + title: 'First Route', + schemaUid: 'tab1', + }, + ], + }, + ], + }, + 'uiSchemas:getJsonSchema/tab1': { + data: { + type: 'void', + name: 'test', + 'x-uid': 'test', + 'x-content': 'First Route Content', + }, + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-basic.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-basic.tsx new file mode 100644 index 0000000000..2e4bcac1eb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-basic.tsx @@ -0,0 +1,17 @@ +/** + * iframe: true + */ +import { MobilePageHeader, MobilePageProvider } from '@nocobase/plugin-mobile/client'; +import React from 'react'; + +const App = () => { + return ( + + +
content
+
+
+ ); +}; + +export default App; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-false.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-false.tsx new file mode 100644 index 0000000000..2565b9f0f3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-header-false.tsx @@ -0,0 +1,14 @@ +import { MobilePageHeader, MobilePageProvider } from '@nocobase/plugin-mobile/client'; +import React from 'react'; + +const App = () => { + return ( + + +
content
+
+
+ ); +}; + +export default App; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs-false.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs-false.tsx new file mode 100644 index 0000000000..d37fad8e09 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs-false.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { + MobilePageNavigationBar, + MobilePageProvider, + MobilePageTabs, + MobileRoutesProvider, + MobileTitleProvider, +} from '@nocobase/plugin-mobile/client'; + +const Demo = () => { + const { pathname } = useLocation(); + return ( +
+ + + + + + + +
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobilePageNavigationBar }); + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + }); + this.app.router.add('schema.page.tabs', { + path: '/page/:pageSchemaUid/tabs', + }); + this.app.router.add('schema.page.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/page1/tabs/tab1'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [ + { + id: 1, + title: 'Page1', + schemaUid: 'page1', + type: 'page', + children: [ + { + id: 2, + parentId: 1, + schemaUid: 'tab1', + title: 'Tab1', + type: 'tabs', + }, + { + id: 3, + parentId: 1, + schemaUid: 'tab2', + title: 'Tab2', + type: 'tabs', + }, + ], + }, + ], + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs.tsx new file mode 100644 index 0000000000..702dcbb98c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/demos/pages-page-tabs.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { mockApp } from '@nocobase/client/demo-utils'; +import { SchemaComponent, Plugin } from '@nocobase/client'; +import { + MobilePageNavigationBar, + MobilePageProvider, + MobilePageTabs, + MobileRoutesProvider, + MobileTitleProvider, +} from '@nocobase/plugin-mobile/client'; + +const Demo = () => { + const { pathname } = useLocation(); + return ( +
+ + + + +
{pathname}
+
+
+
+
+ ); +}; + +class DemoPlugin extends Plugin { + async load() { + this.app.addComponents({ MobilePageNavigationBar }); + this.app.router.add('schema', { + path: '/page', + }); + this.app.router.add('schema.page', { + path: '/page/:pageSchemaUid', + }); + this.app.router.add('schema.page.tabs', { + path: '/page/:pageSchemaUid/tabs', + }); + this.app.router.add('schema.page.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: Demo, + }); + } +} + +const app = mockApp({ + router: { + type: 'memory', + initialEntries: ['/page/page1/tabs/tab1'], + }, + plugins: [DemoPlugin], + apis: { + 'mobileRoutes:list': { + data: [ + { + id: 1, + title: 'Page1', + schemaUid: 'page1', + type: 'page', + children: [ + { + id: 2, + parentId: 1, + schemaUid: 'tab1', + title: 'Tab1', + type: 'tabs', + }, + { + id: 3, + parentId: 1, + schemaUid: 'tab2', + title: 'Tab2', + type: 'tabs', + }, + ], + }, + ], + }, + }, +}); + +export default app.getRootComponent(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Content.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Content.tsx new file mode 100644 index 0000000000..a684e7ff44 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Content.tsx @@ -0,0 +1,49 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { Resizable } from 're-resizable'; +import { useSize } from './sizeContext'; + +interface DesktopModeContentProps { + children?: React.ReactNode; +} + +export const DesktopModeContent: FC = ({ children }) => { + const { size, setSize } = useSize(); + return ( +
+ { + setSize({ + width: size.width + d.width, + height: size.height + d.height, + }); + }} + > + {children} + +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/DesktopMode.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/DesktopMode.tsx new file mode 100644 index 0000000000..4bac8c8902 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/DesktopMode.tsx @@ -0,0 +1,39 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { Layout } from 'antd'; +import { isDesktop } from 'react-device-detect'; + +import { DesktopModeHeader } from './Header'; +import { DesktopModeContent } from './Content'; +import { SizeContextProvider } from './sizeContext'; +import { PageBackgroundColor } from '../constants'; + +interface DesktopModeProps { + children?: React.ReactNode; +} + +export const DesktopMode: FC = ({ children }) => { + if (!isDesktop) { + return <>{children}; + } + return ( + + + + + + + {children} + + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx new file mode 100644 index 0000000000..c36b079151 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx @@ -0,0 +1,128 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useState } from 'react'; +import { QRCode, Popover, Button } from 'antd'; +import { QrcodeOutlined } from '@ant-design/icons'; + +import { usePluginTranslation } from '../locale'; +import { useSize } from './sizeContext'; +import { css, DesignableSwitch, Icon } from '@nocobase/client'; +import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; + +const PadSvg = () => ( + + pad icon + + + +); + +const MobileSvg = () => ( + + mobile icon + + + +); + +const PadIcon = (props: Partial) => ; + +const MobileIcon = (props: Partial) => ; + +export const DesktopModeHeader: FC = () => { + const { t } = usePluginTranslation(); + const { setSize } = useSize(); + const [open, setOpen] = useState(false); + const handleQrcodeOpen = (newOpen: boolean) => { + setOpen(newOpen); + }; + + return ( +
+ +
+ + + + : ' '} + > + + +
+
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.md new file mode 100644 index 0000000000..f5a1b581df --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.md @@ -0,0 +1,12 @@ + +# Desktop Mode + +在桌面端访问移动端页面时,会渲染此组件。 + +```tsx | pure + + + +``` + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.ts new file mode 100644 index 0000000000..f7264c0533 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './DesktopMode'; +export * from './Header'; +export * from './Content'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/sizeContext.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/sizeContext.tsx new file mode 100644 index 0000000000..c5514de64a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/sizeContext.tsx @@ -0,0 +1,31 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, createContext, useContext, useState } from 'react'; + +interface SizeContextProps { + size: { width: number; height: number }; + setSize: (size: { width: number; height: number }) => void; +} + +const SizeContext = createContext(null); +SizeContext.displayName = 'SizeContext'; + +export function useSize() { + return useContext(SizeContext); +} + +interface SizeContextProviderProps { + children?: React.ReactNode; +} + +export const SizeContextProvider: FC = ({ children }) => { + const [size, setSize] = useState({ width: 375, height: 667 }); + return {children}; +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/dynamic-page.md b/packages/plugins/@nocobase/plugin-mobile/src/client/dynamic-page.md new file mode 100644 index 0000000000..aefcceb4a6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/dynamic-page.md @@ -0,0 +1,182 @@ + +# DynamicPage + +动态页面。 + +通过读取 URL 的参数,获取 schema,并渲染成页面,如果不存在则渲染 404 页面。 + +```bash +/page/:pageSchemaUid/tabs/:tabSchemaUid +``` + +## MobilePage + +读取 `pageSchemaUid` 获取页面 schema,渲染成页面。 + +### Examples + +#### Basic + + + +#### NotFound + +如果 schema 返回未空,则渲染 404 页面。 + + + +### Schema + + + +### Settings + + + +## MobilePageContext + +将页面的配置通过 context 传递给子组件。 + +```ts +interface MobilePageContextProps { + /** + * @default true + */ + displayPageHeader?: boolean; + /** + * @default true + */ + displayNavigationBar?: boolean; + /** + * @default true + */ + displayPageTitle?: boolean; + /** + * @default false + */ + displayTabs?: boolean; +} +``` + +- `MobilePageProvider` 提供页面配置 +- `useMobilePage` 获取页面配置 + +## MobilePageContent + +读取 `tabSchemaUid` 获取页面 schema,渲染成页面。 + +### Examples + +#### Basic + + + +### First route + + + +#### NotFound + +如果 schema 返回未空,则渲染 404 页面。 + + + +## MobilePageHeader + +页面顶部。 + +根据页面配置的 `displayPageHeader` 控制是否显示。 + +### Examples + +#### Basic + + + +#### displayPageHeader: false + + + +### MobilePageNavigationBar + +页面导航栏。 + +其会通过 `useMobilePage()` 获取页面配置,并根据配置渲染导航栏。 + +#### Examples + +##### Basic + + + +##### displayNavigationBar: false + + + +##### displayPageTitle: false + + + +#### Schema + +不同位置通过 `x-position` 的值控制。 + +```json +{ + "type": "void", + "x-component": "MobilePageNavigationBar", + "properties": { + "type": "void", + "x-component": "MobileNavigationActionBar", + "x-initializer": "mobile:navigation-bar:actions", + "properties": { + "action1": { + "x-position": "right", + "type": "void", + "x-component": "Action", + "x-toolbar": "ActionSchemaToolbar", + "x-settings": "mobile:navigation-bar:actions:link", + "x-use-component-props": "useMobileNavigationBarLink", + "x-component-props": { + "link": "/", + "title": "Left", + "component": "MobileNavigationBarAction" + } + }, + "action2": { + "x-position": "left", + "type": "void", + "x-component": "Action", + "x-toolbar": "ActionSchemaToolbar", + "x-settings": "mobile:navigation-bar:actions:link", + "x-use-component-props": "useMobileNavigationBarLink", + "x-component-props": { + "link": "/", + "title": "Right", + "component": "MobileNavigationBarAction" + } + }, + "action3": { + "x-position": "bottom", + "type": "void", + "x-component": "div", + "x-content": "Bottom" + } + }, + } +} +``` + + + +### MobilePageTabs + +用于显示页面 Tabs。 + +#### Basic + + + +#### false + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/index.md new file mode 100644 index 0000000000..c7196015cb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/index.md @@ -0,0 +1,337 @@ +# Client + +## 目录 + +- `providers`:主应用的 providers +- `mobile-providers`:移动端内置的 `providers` + - `context`:mobile 的全局上下文 + - `mobileTitle`:用于设置 mobile title + - `mobileRoutes`:增删改查移动端路由 + - `MobileProviders`:组件 + - `AdminProvider` + - `MobileTitleProvider` 对应上面的 `mobileTitle` + - `MobileRoutesProvider` 对应上面的 `mobileRoutes` +- `desktop-mode`:桌面模式 +- `js-bridge`:JS Bridge +- `mobile`:移动端入口组件,主要是渲染移动端 `Routes` + - `MobileAppContext`:移动端全局上下文 +- `mobile-layout`:移动端 Layout + - `MobilePageOutlet`:页面内容区域 + - `mobile-tab-bar`:底部 TabBar + - `MobileTabBar`:组件 + - `types`:内置的 TabBar 类型 + - `MobileTabBar.Link`:链接类型 + - `MobileTabBar.Page`:Page 类型 + - `MobileTabBar.Item`:基础 Item,供其他类型继承 +- `pages`:页面 + - `not-found`:404 页面 + - `home`: `/` 页面 + - `dynamic-page`:动态 schema 页面 + - `MobilePage`:读取 URL 中 uid,渲染页面 + - `header`:`MobilePageHeader`:页面头部 + - `navigationBar`:`MobilePageNavigationBar`:页面导航栏 + - `actions`:内置的 actions + - `link`:链接 + - `tabs`:`MobilePageTabs`:页面 Tabs + - `content`:`MobilePageContent`:页面内容区域 + +## 嵌套关系 + +### 全局嵌套关系 + +```tsx | pure + // 渲染移动端 Routes + + + // 提供移动端上下文和布局 + + + // 页面内容 + // 底部 TabBar + + + + + + +``` + +```tsx | pure +// 首先是将 `/mobile` 路由添加根项目的路由中,这样所有访问 `/mobile` 的请求都会进入到 mobile 路由中 +app.router.add('mobile', { + path: '/mobile/*', + element: , +}); +``` + +```tsx | pure +// 匹配 Layout,这样默认情况下所有页面都是 +mobileRouter.add('mobile', { + element: , +}); +``` + +### 动态 schema 页面的嵌套关系 + +```tsx | pure + // react-router 匹配的 Schema 页面 router.add('/page/:pageSchemaUid', { Component: 'MobilePage' }) + // 通过 URL 获取 uid,加载整个页面的 Schema + // 提供页面级别的上下文 + // 顶部 + // 页面导航栏 + // 页面 Tabs + + // 页面内容区 + // `/page/:pageSchemaUid/tabs/:tabSchemaUid` 读取 `tabSchemaUid` 渲染对应的 Tab 页面 + + + + +``` + +```tsx | pure +// schema 页面路由 +mobileRouter.add('mobile.schema.page', { + path: '/page/:pageSchemaUid', + Component: 'MobilePage', +}); + +// Tab 路由 +mobileRouter.add('mobile.schema.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: 'MobilePage', +}); +``` + +## 路由接口 + +```ts +export interface MobileRouteItem { + id: number; + schemaUid?: string; + type: 'page' | 'link' | 'tabs'; + options: any; + title?: string; + icon?: string; + parentId?: number; + children?: MobileRouteItem[]; +} +``` + +对于 `TabBar` 而言,`options` 是 `MobileRouteItem` 对应的 `schema`。 +对于 `Tabs` 而言,`options` 只要是存着对应的页面 `schemaUid` 和 `title`。 + +```json +[ + { + "id": 10, + "parentId": null, + "title": "Test1", + "icon": "AppstoreOutlined", + "schemaUid": "d4o6esth2ik", + "type": "page", + "options": null, + "sort": 1, + "children": [ + { + "id": 11, + "parentId": 10, + "title": "Tab1", + "icon": null, + "schemaUid": "pm65m9y0o2y", + "type": "tabs", + "options": null, + "sort": 2, + "__index": "0.children.0" + }, + { + "id": 12, + "parentId": 10, + "title": "Tab2", + "icon": null, + "schemaUid": "1mcth1tfcb6", + "type": "tabs", + "options": null, + "sort": 3, + "__index": "0.children.1" + } + ], + "__index": "0" + }, + { + "id": 13, + "parentId": null, + "title": "Test2", + "icon": "aliwangwangoutlined", + "schemaUid": null, + "type": "link", + "options": { + "schemaUid": null, + "url": "https://github.com", + "params": [ + {} + ] + }, + "sort": 4, + "__index": "1" + } +] +``` + +## Schema + + +### MobileTabBarItem Schema + +```ts +function getMobileTabBarItemSchema(routeItem: MobileRouteItem) { + return { + name: routeItem.id, + type: 'void', + 'x-decorator': 'BlockItem', + 'x-settings': `mobile:tab-bar:${routeItem.type}`, + 'x-component': `MobileTabBar.${upperFirst(routeItem.type)}`, + 'x-toolbar-props': { + showBorder: false, + showBackground: true, + }, + 'x-component-props': { + title: routeItem.title, + icon: routeItem.icon, + schemaUid: routeItem.schemaUid, + ...(routeItem.options || {}), + }, + } +} +``` + +#### `page` type: + +```ts +{ + name: 1, + type: 'void', + 'x-decorator': 'BlockItem', + 'x-settings': `mobile:tab-bar:page`, + 'x-component': `MobileTabBar.Page`, + 'x-toolbar-props': { + showBorder: false, + showBackground: true, + }, + 'x-component-props': { + title: 'Test', + icon: 'AppstoreOutlined', + schemaUid: 'd4o6esth2ik', + }, +} +``` + +#### `link` type: + +```ts +{ + name: 1, + type: 'void', + 'x-decorator': 'BlockItem', + 'x-settings': `mobile:tab-bar:link`, + 'x-component': `MobileTabBar.Link`, + 'x-toolbar-props': { + showBorder: false, + showBackground: true, + }, + 'x-component-props': { + title: 'Test', + icon: 'AppstoreOutlined', + schemaUid: 'd4o6esth2ik', + url: 'https://github.com', + }, +} +``` + +### Page Schema + +```json +{ + "type": "void", + "name": "schema", + "x-uid": "page1", + "x-component": "MobilePageProvider", + "x-settings": "mobile:page", + "x-decorator": "BlockItem", + "x-decorator-props": { + "style": { + "height": "100%" + } + }, + "x-toolbar-props": { + "draggable": false, + "spaceWrapperStyle": { + "right": -15, + "top": -15 + }, + "spaceClassName": "css-m1q7xw", + "toolbarStyle": { + "overflowX": "hidden" + } + }, + "properties": { + "header": { + "type": "void", + "x-component": "MobilePageHeader", + "properties": { + "pageNavigationBar": { + "type": "void", + "x-component": "MobilePageNavigationBar", + "properties": { + "actionBar": { + "type": "void", + "x-component": "MobileNavigationActionBar", + "x-initializer": "mobile:navigation-bar:actions", + "x-component-props": { + "spaceProps": { + "style": { + "flexWrap": "nowrap" + } + } + }, + "name": "actionBar" + } + }, + "name": "pageNavigationBar" + }, + "pageTabs": { + "type": "void", + "x-component": "MobilePageTabs", + "name": "pageTabs" + } + }, + "name": "header" + }, + "content": { + "type": "void", + "x-component": "MobilePageContent", + "properties": { + "tab1": { + "type": "void", + "x-uid": "tab1", + "x-async": true, + "x-component": "Grid", + "x-initializer": "mobile:addBlock", + "name": "tab1" + }, + "tab2": { + "type": "void", + "x-uid": "tab2", + "x-async": true, + "x-component": "Grid", + "x-initializer": "mobile:addBlock", + "name": "tab2" + } + }, + "name": "content" + } + } +} +``` + +其中 `tab1` 和 `tab2` 的 `x-async` 为 true,表示是异步加载的。 diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx new file mode 100644 index 0000000000..4f2afadfdb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx @@ -0,0 +1,229 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { PagePopups, Plugin, RouterManager, createRouterManager } from '@nocobase/client'; +import React from 'react'; +import { Outlet } from 'react-router-dom'; + +// @ts-ignore +import { name } from '../../package.json'; + +import { generatePluginTranslationTemplate } from './locale'; +import { Mobile } from './mobile'; +import { + MobileLayout, + MobileTabBar, + mobileTabBarInitializer, + mobileTabBarLinkSettings, + mobileTabBarPageSettings, +} from './mobile-layout'; +import { + MobileHomePage, + MobileNavigationActionBar, + MobileNavigationBarAction, + MobileNotFoundPage, + MobilePage, + MobilePageContent, + MobilePageHeader, + MobilePageNavigationBar, + MobilePageProvider, + MobilePageTabs, + mobileAddBlockInitializer, + mobileNavigationBarActionsInitializer, + mobileNavigationBarLinkSettings, + mobilePageSettings, + mobilePageTabsSettings, + mobilePagesTabInitializer, + useMobileNavigationBarLink, +} from './pages'; + +// 导出 JSBridge,会挂在到 window 上 +import './js-bridge'; +import { MobileCheckerProvider } from './providers'; + +export * from './desktop-mode'; +export * from './mobile'; +export * from './mobile-layout'; +export * from './mobile-providers'; +export * from './pages'; +export * from './providers'; + +export class PluginMobileClient extends Plugin { + mobileRouter?: RouterManager; + mobilePath = '/m'; + + get desktopMode() { + return this.options?.config?.desktopMode ?? true; + } + + get mobileBasename() { + return `${this.router.getBasename()}m`; // `/m` or `/apps/aaa/m`(多应用) + } + + async updateOptions(value: { showTabBar?: boolean }) { + if (!this.options) { + this.options = {}; + } + + this.options.options = { + ...this.options?.options, + ...value, + }; + return this.app.apiClient.request({ + url: 'applicationPlugins:update', + method: 'post', + params: { + filter: { + packageName: name, + }, + }, + data: { + options: this.options.options, + }, + }); + } + + getPluginOptions() { + return this.options?.options; + } + + async afterAdd(): Promise { + this.setMobileRouter(); + } + + async load() { + this.addComponents(); + this.addAppRoutes(); + this.addRoutes(); + this.addInitializers(); + this.addSettings(); + this.addScopes(); + this.app.addProvider(MobileCheckerProvider); + + this.app.pluginSettingsManager.add('mobile', { + title: generatePluginTranslationTemplate('Mobile'), + icon: 'MobileOutlined', + link: this.mobileBasename, + }); + } + + addScopes() { + this.app.addScopes({ + useMobileNavigationBarLink, + }); + } + + addInitializers() { + this.app.schemaInitializerManager.add( + mobileAddBlockInitializer, + mobileTabBarInitializer, + mobilePagesTabInitializer, + mobileNavigationBarActionsInitializer, + ); + } + + addSettings() { + this.app.schemaSettingsManager.add( + mobilePageSettings, + mobileTabBarPageSettings, + mobileTabBarLinkSettings, + mobilePageTabsSettings, + mobileNavigationBarLinkSettings, + ); + } + + addComponents() { + this.app.addComponents({ + MobilePageProvider, + MobileNavigationBarAction, + MobilePageNavigationBar, + MobileHomePage, + MobilePageContent, + MobileTabBar, + MobilePageHeader, + MobilePageTabs, + MobileNavigationActionBar, + MobileNotFoundPage, + }); + } + + setMobileRouter() { + const router = createRouterManager( + this.options?.config?.router || { type: 'browser', basename: this.mobileBasename }, + this.app, + ); + this.mobileRouter = router; + } + + addRoutes() { + this.app.addComponents({ MobileLayout, MobilePage }); + + this.mobileRouter.add('mobile', { + Component: 'MobileLayout', + }); + + this.mobileRouter.add('mobile.home', { + path: '/', + Component: 'MobileHomePage', + }); + + // 跳转到主应用的登录页 + this.mobileRouter.add('signin', { + path: '/signin', + Component: () => { + window.location.href = window.location.href + .replace(this.mobilePath, '') + .replace('redirect=', `redirect=${this.mobilePath}`); + return null; + }, + }); + + this.mobileRouter.add('mobile.schema', { + element: , + }); + this.mobileRouter.add('mobile.schema.page', { + path: '/page/:pageSchemaUid', + Component: 'MobilePage', + }); + this.mobileRouter.add('mobile.schema.page.popup', { + path: '/page/:pageSchemaUid/popups/*', + Component: PagePopups, + }); + this.mobileRouter.add('mobile.schema.tabs', { + element: , + }); + this.mobileRouter.add('mobile.schema.tabs.page', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid', + Component: 'MobilePage', + }); + this.mobileRouter.add('mobile.schema.tabs.page.popup', { + path: '/page/:pageSchemaUid/tabs/:tabSchemaUid/popups/*', + Component: PagePopups, + }); + + this.mobileRouter.add('not-found', { + path: '*', + Component: 'MobileNotFoundPage', + }); + } + + addAppRoutes() { + this.app.addComponents({ Mobile }); + this.app.router.add('m', { + path: `${this.mobilePath}/*`, + Component: 'Mobile', + }); + } + + getRouterComponent() { + return this.mobileRouter.getRouterComponent(); + } +} + +export default PluginMobileClient; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/js-bridge/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/js-bridge/index.ts new file mode 100644 index 0000000000..a3d830d5bc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/js-bridge/index.ts @@ -0,0 +1,45 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +interface InvokeFunction { + (params: { action: 'scan' }, cb: (data: { url: string }) => void): void; + (params: { action: 'moveTaskToBack' }, cb?: () => void): void; +} + +export const getJsBridge = () => + (window as any).JsBridge as { + invoke: InvokeFunction; + }; + +export const invoke: InvokeFunction = (params, cb) => { + return getJsBridge().invoke(params, cb); +}; + +export const isJSBridge = () => !!getJsBridge(); + +/** + * App 右滑返回 + * @param cb 回调函数,返回 true 表示 web 自己消费,false表示 app 消费 + */ +export const JSBridgeFunctions = { + /** + * @description JSBridge injects + */ + onBackPressed: () => { + if (history.length === 1) { + invoke({ action: 'moveTaskToBack' }); + } else { + history.back(); + } + }, +}; + +Object.keys(JSBridgeFunctions).forEach((key) => { + window[key] = JSBridgeFunctions[key]; +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/locale.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/locale.ts new file mode 100644 index 0000000000..1a2233ddde --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/locale.ts @@ -0,0 +1,20 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +// @ts-ignore +import pkg from './../../package.json'; +import { useTranslation } from 'react-i18next'; + +export function usePluginTranslation() { + return useTranslation([pkg.name, 'client'], { nsMode: 'fallback' }); +} + +export function generatePluginTranslationTemplate(key: string) { + return `{{t('${key}', { ns: ['${pkg.name}', 'client'], nsMode: 'fallback' })}}`; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/MobileLayout.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/MobileLayout.tsx new file mode 100644 index 0000000000..2d204bec82 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/MobileLayout.tsx @@ -0,0 +1,32 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { usePlugin } from '@nocobase/client'; +import React, { FC } from 'react'; +import { Outlet } from 'react-router-dom'; + +import { PluginMobileClient } from '../index'; +import { useMobileApp } from '../mobile'; +import { MobileProviders } from '../mobile-providers'; +import { MobileTabBar } from './mobile-tab-bar'; + +export interface MobileLayoutProps { + children?: React.ReactNode; +} + +export const MobileLayout: FC = () => { + const mobilePlugin = usePlugin(PluginMobileClient); + const { showTabBar } = useMobileApp(); + return ( + + + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.md new file mode 100644 index 0000000000..f0bcbc8cfc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.md @@ -0,0 +1,214 @@ + +# MobileLayout + +移动端布局组件。 + +主要作用有: + +- 渲染 `MobileProviders` 上下文 +- 通过 `RemoteSchemaComponent` 获取 `x-uid` 为 `nocobase-mobile` 的 schema + +## Schema + +```json +{ + "type": "void", + "name": "nocobase-mobile", + "x-uid": "nocobase-mobile", + "properties": { + "pageOutlet": { + "type": "void", + "x-component": "MobilePageOutlet", + "x-uid": "d1e6jk9sm1s", + "x-async": false, + "x-index": 1 + }, + "tabBar": { + "x-uid": "ib20ma8464k", + "type": "void", + "x-component": "MobileTabBar", + "x-decorator": "BlockItem", + "x-decorator-props": { + "style": { + "position": "sticky", + "bottom": 0 + } + }, + "x-toolbar-props": { + "draggable": false + }, + "_isJSONSchemaObject": true, + "version": "2.0", + "x-async": false, + "x-index": 2, + "x-component-props": { + "enableTabBar": true + } + } + }, + "x-async": false +} +``` + +其中核心是 `MobilePageOutlet` 和 `MobileTabBar` 两个组件。 + + +## MobilePageOutlet + +此组件功能较为简单,即使用 `react-router-dom` 的 `Outlet` 组件渲染子路由。 + +除此之外,其设置了 `minHeight` 为 `calc(100% - ${NavigationBarHeight}px)`,让其撑开页面。 + +## MobileTabBar + +页面底部的 `TabBar`。 + + + +### 类型 + +```ts +interface MobileTabBarProps { + /** + * @default true + */ + enableTabBar?: boolean; +} +``` + +- `enableTabBar`:如果值为 `false`,则不渲染。 + +### Examples + +- `enableTabBar`:`false` + +会隐藏 `TabBar`。 + +```json +"x-component-props": { + "enableTabBar": false, +} +``` + + + +- 内页会自定隐藏 + +如果是内页,会自动隐藏 `TabBar`。 + + + +### Initializer + +TODO + + + +## MobileTabBar.Item + +基础的 `TabBar.Item`,一般被其他特殊的 `TabBar.Item` 继承,例如 `MobileTabBar.Page` 和 `MobileTabBar.Link`。 + + +### 类型 + +```ts +interface MobileTabBarItemProps { + // 图标 + icon?: string; + // 选中时的图标 + selectedIcon?: string; + // 标题 + title?: string; + // 点击事件 + onClick?: () => void; + // 是否选中 + selected?: boolean; + // 是否显示徽标 + badge?: number | string | boolean; +} +``` + +### Examples + +#### Basic + + + +#### With Icon + +- icon: string + + + +- icon: React.Node + + + +#### Selected + + + +#### Selected Icon + + + +#### onClick + + + +## MobileTabBar.Page + +用于渲染和创建 Schema 页面的 `TabBar.Item`。 + +其继承自 `MobileTabBar.Item`,并自定义了 `onClick` 事件,添加了 `pageSchemaId` 属性。 + +其点击效果是跳转到 `/page/${pageSchemaId}` 页面。 + +### Examples + +#### Basic + + + +#### selected + + + +### Schema + + + +### Settings + + + + +## MobileTabBar.Link + +用于渲染外部链接的 `TabBar.Item`。 + +其继承自 `MobileTabBar.Item`,并自定义了 `onClick` 事件,添加了 `link` 属性。 + +其点击效果是跳转到 `link` 页面。 + +### Examples + +#### inner link + + + +#### outer page + + + +#### selected + + + +### Schema + + + +### Settings + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.ts new file mode 100644 index 0000000000..218814c847 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileLayout'; +export * from './mobile-tab-bar'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx new file mode 100644 index 0000000000..ff95766c5a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx @@ -0,0 +1,62 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { Icon } from '@nocobase/client'; +import { Badge } from 'antd-mobile'; +import classnames from 'classnames'; + +export interface MobileTabBarItemProps { + // 图标 + icon?: string | React.ReactNode; + // 选中时的图标 + selectedIcon?: string; + // 标题 + title?: string; + // 点击事件 + onClick?: () => void; + // 是否选中 + selected?: boolean; + // 是否显示徽标 + badge?: number | string | boolean; +} + +function getIcon(item: MobileTabBarItemProps, selected?: boolean) { + const { icon, selectedIcon } = item; + const res = selected && selectedIcon ? selectedIcon : icon; + if (!res) return undefined; + if (typeof res === 'string') return ; + return icon; +} + +export const MobileTabBarItem: FC = (props) => { + const { title, onClick, selected, badge } = props; + const icon = getIcon(props, selected); + return ( +
+ + {icon} + + + {title} + +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/index.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/index.tsx new file mode 100644 index 0000000000..a662e9d9c1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/index.tsx @@ -0,0 +1,14 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileTabBar.Item'; +export * from './settingsItem'; +export * from './useUpdateTabBarItem'; +export * from './schemaFormFields'; +export * from './schema'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts new file mode 100644 index 0000000000..6a9691873e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts @@ -0,0 +1,22 @@ +import { upperFirst } from 'lodash'; +import { MobileRouteItem } from '../../../mobile-providers'; + +export function getMobileTabBarItemSchema(routeItem: MobileRouteItem) { + return { + name: routeItem.id, + type: 'void', + 'x-decorator': 'BlockItem', + 'x-settings': `mobile:tab-bar:${routeItem.type}`, + 'x-component': `MobileTabBar.${upperFirst(routeItem.type)}`, + 'x-toolbar-props': { + showBorder: false, + showBackground: true, + }, + 'x-component-props': { + title: routeItem.title, + icon: routeItem.icon, + schemaUid: routeItem.schemaUid, + ...(routeItem.options || {}), + }, + } +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schemaFormFields.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schemaFormFields.tsx new file mode 100644 index 0000000000..b26847ee10 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schemaFormFields.tsx @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { ISchema } from '@nocobase/client'; +import { generatePluginTranslationTemplate } from '../../../locale'; + +export const getMobileTabBarItemSchemaFields = (values: any = {}): Record => ({ + title: { + title: generatePluginTranslationTemplate('Title'), + type: 'string', + default: values.title, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required: true, + }, + icon: { + title: generatePluginTranslationTemplate('Icon'), + type: 'string', + default: values.icon, + 'x-decorator': 'FormItem', + 'x-component': 'IconPicker', + required: true, + }, +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/settingsItem.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/settingsItem.tsx new file mode 100644 index 0000000000..a611c965af --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/settingsItem.tsx @@ -0,0 +1,72 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { ISchema, createModalSettingsItem, createTextSettingsItem } from '@nocobase/client'; +import { useFieldSchema } from '@formily/react'; +import { useNavigate } from 'react-router-dom'; +import { App } from 'antd'; + +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../../locale'; +import { useUpdateTabBarItem } from './useUpdateTabBarItem'; +import { getMobileTabBarItemSchemaFields } from './schemaFormFields'; +import { useMobileRoutes } from '../../../mobile-providers'; + +export const editTabItemSettingsItem = (getMoreFields?: (values: any) => Record) => + createModalSettingsItem({ + title: generatePluginTranslationTemplate('Edit button'), + name: 'tabBarItem', + parentSchemaKey: 'x-component-props', + schema: (defaultValues) => ({ + type: 'object', + title: 'Edit button', + properties: { + ...getMobileTabBarItemSchemaFields(defaultValues), + ...(getMoreFields ? getMoreFields(defaultValues) : {}), + }, + }), + useSubmit: useUpdateTabBarItem, + }); + +export const removeTabItemSettingsItem = createTextSettingsItem({ + name: 'remove', + title: generatePluginTranslationTemplate('Delete'), + useTextClick: () => { + const schema = useFieldSchema(); + const id = Number(schema.toJSON().name); + const { refresh, resource, routeList, api } = useMobileRoutes(); + const navigate = useNavigate(); + const { t } = usePluginTranslation(); + const { modal, message } = App.useApp(); + return async () => { + modal.confirm({ + title: t('Delete action'), + content: t('Are you sure you want to delete it?'), + onOk: async () => { + // 删除 tabBarItem + await resource.destroy({ filterByTk: id }); + + // 删除所有的子节点 + await resource.destroy({ filter: { parentId: id } }); + + // 删除 tabBar 对应的页面 schema + const routeItem = routeList.find((item) => item.id === id); + await api.request({ url: `/uiSchemas:remove/${routeItem.schemaUid}`, method: 'delete' }); + + await refresh(); + + // 跳转到首页 + navigate('/'); + message.success({ + content: 'Delete successfully', + }); + }, + }); + }; + }, +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/useUpdateTabBarItem.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/useUpdateTabBarItem.tsx new file mode 100644 index 0000000000..f8f4f8cbba --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/useUpdateTabBarItem.tsx @@ -0,0 +1,42 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useFieldSchema } from '@formily/react'; +import { omit } from 'lodash'; +import { App } from 'antd'; + +import { usePluginTranslation } from '../../../locale'; +import { useMobileRoutes } from '../../../mobile-providers'; + +export function useUpdateTabBarItem() { + const { refresh, resource } = useMobileRoutes(); + const fieldSchema = useFieldSchema(); + const { t } = usePluginTranslation(); + const { message } = App.useApp(); + + return async (values) => { + if (!values.title || values.title.trim() === '') { + message.error(t('Title field is required')); + return Promise.reject(new Error(t('Title field is required'))); + } + if (!values.icon) { + message.error(t('Icon field is required')); + return Promise.reject(new Error(t('Icon field is required'))); + } + + const schema = fieldSchema.toJSON(); + const id = Number(schema.name); + const title = schema['x-component-props'].title; + const icon = schema['x-component-props'].icon; + + const options = omit(schema['x-component-props'], 'title', 'icon'); + await resource.update({ filterByTk: id, values: { title, icon, options } }); + refresh(); + }; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx new file mode 100644 index 0000000000..e4daa8de1a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx @@ -0,0 +1,93 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useCallback } from 'react'; +import { SafeArea } from 'antd-mobile'; +import 'antd-mobile/es/components/tab-bar/tab-bar.css'; + +import { useStyles } from './styles'; +import { useMobileRoutes } from '../../mobile-providers'; + +import { getMobileTabBarItemSchema, MobileTabBarItem } from './MobileTabBar.Item'; +import { MobileTabBarPage, MobileTabBarLink } from './types'; +import { cx, DndContext, DndContextProps, SchemaComponent, useDesignable, css } from '@nocobase/client'; +import { MobileTabBarInitializer } from './initializer'; +import { isInnerLink } from '../../utils'; + +export interface MobileTabBarProps { + /** + * @default true + */ + enableTabBar?: boolean; +} + +export const MobileTabBar: FC & { + Item: typeof MobileTabBarItem; + Page: typeof MobileTabBarPage; + Link: typeof MobileTabBarLink; +} = ({ enableTabBar = true }) => { + const { styles } = useStyles(); + const { designable } = useDesignable(); + const { routeList, activeTabBarItem, resource, refresh } = useMobileRoutes(); + const validRouteList = routeList.filter((item) => item.schemaUid || isInnerLink(item.options?.url)); + const handleDragEnd: DndContextProps['onDragEnd'] = useCallback( + async (event) => { + const { active, over } = event; + const activeIdName = active?.id; + const overIdName = over?.id; + + if (!activeIdName || !overIdName || activeIdName === overIdName) { + return; + } + const activeId = Number(activeIdName.replace('nocobase-mobile.tabBar.', '')); + const overId = Number(overIdName.replace('nocobase-mobile.tabBar.', '')); + await resource.move({ sourceId: activeId, targetId: overId, sortField: 'sort' }); + await refresh(); + }, + [resource, refresh], + ); + + if (!enableTabBar) { + return null; + } + + // 如果是 routeList 中的 pathname 则显示 tabBar,如果是内页则不显示 + // 判断内页的方法:没有激活的 activeTabBarItem 并且 routeList 中有数据 + if (!activeTabBarItem && validRouteList.length > 0) return null; + return ( +
+
+ +
+ {routeList.map((item) => { + return ; + })} +
+
+ +
+ + +
+ ); +}; + +MobileTabBar.Item = MobileTabBarItem; +MobileTabBar.Link = MobileTabBarLink; +MobileTabBar.Page = MobileTabBarPage; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.md new file mode 100644 index 0000000000..6d3fde3659 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.md @@ -0,0 +1,224 @@ +# Bottom + +## Schema + +```js +{ + type: 'array', + 'x-component': 'MobileTabBar', + 'x-component-props': { + 'defaultSelectedKey': 'tab1-schema', + }, + 'x-settings': 'MobileTabBar:settings', + 'x-initializer': 'MobileTabBar:initializer', + items: { + type: 'object', + properties: { + 'tab1-schema': { + type: 'void', + 'title': '首页', + 'x-decorator': 'MobileTabBar.Item', + 'x-component': 'MobileTabBar.Page', + 'x-component-props': { + 'icon': 'AppleOutlined', + 'selectedIcon': 'AppstoreOutlined', + 'pageSchemaId': 'home', + }, + 'x-link': '/page/home', + 'x-index': 1, + 'x-settings': 'MobileTabBar.Page:settings', + }, + 'tab2-schema': { + type: 'void', + 'title': 'Message', + 'x-decorator': 'MobileTabBar.Item', + 'x-component': 'MobileTabBar.Page', + 'x-component-props': { + 'icon': 'MessageOutlined', + 'pageSchemaId': 'message', + }, + 'x-index': 2, + 'x-link': '/page/message', + 'x-settings': 'MobileTabBar.Page:settings', + }, + 'tab3-link': { + type: 'void', + 'title': 'Github', + 'x-decorator': 'MobileTabBar.Item', + 'x-component': 'MobileTabBar.Link', + 'x-component-props': { + 'icon': 'GithubOutlined', + 'link': 'https://github.com', + }, + 'x-settings': 'MobileTabBar.Link:settings', + 'x-index': 3, + }, + 'tab4-link': { + type: 'void', + 'title': 'My', + 'x-decorator': 'MobileTabBar.Item', + 'x-component': 'MobileTabBar.Link', + 'x-component-props': { + 'icon': 'GithubOutlined', + 'link': '/my', + },<> + 'x-link': '/my', + 'x-settings': 'MobileTabBar.Link:settings', + 'x-index': 4, + }, + 'tab5-scan': { + type: 'void', + 'title': 'Scan', + 'x-decorator': 'MobileTabBar.Item', + 'x-component': 'MobileTabBar.Scan', + 'x-component-props': { + 'icon': 'ScanOutlined', + }, + 'x-index': 5, + }, + } + } +} +``` + +其中关于 `x-link` 是用于判断是在 Tab 页面还是在内页,如果当前的 `pathname` 不等于不等于任何一个子项目的 `x-link` 值则隐藏。 + +## Components + +### MobileTabBar 组件 + +移动端底部导航栏。 + +属性参考了:[小程序 TabBar](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar) + +```ts +interface MobileTabBarProps { + /** + * 默认激活的 tab + */ + defaultSelectedKey?: string; + /** + * tabBar 上的文字选中颜色 + * @default var(--adm-color-text-secondary) + */ + color?: string; + /** + * tabBar 上的文字选中颜色 + * @default var(--adm-color-text-primary) + */ + selectedColor?: string; + /** + * tabBar 的背景色 + */ + backgroundColor?: string; + /** + * tabBar 上边框的颜色 + * @default var(--adm-color-border) + */ + borderColor?: string; + /** + * 是否显示 tabBar + * @default true + */ + visible?: boolean; + /** + * 是否固定在底部 + * @default true + */ + fixed?: boolean; +} +``` + +这里并没有直接使用 ant-mobile 的 `TabBar` 组件,原因如下: + +原因 1:因为 `TabBar` 和 `TabBar.Item` 之间不能有其他元素,导致无法增加 toolbar 这一层 +原因 2:`TabBar.Item` 不够灵活,只能定义 `icon` 和 `title`,无法自定义 + +需要讨论的点: + +- items 是数组还是对象? +- `MobileTabBar.Page` 这种点击后怎么联动到页面? + - 链接从 `/mobile/:name` 改为 `/mobile/page/:name`:`/mobile/page/${pageSchemaId}` 读取 `params.pageSchemaId` +- 技术上,怎么操作 `items`? + +### MobileTabBar.Item + +基于 [BlockItem](https://client.docs.nocobase.com/components/block-item) 的扩展。 + +其继承了 BlockItem 的拖拽和 SchemaToolbar 和 SchemaSettings 的渲染功能。 + +未来可能有其他属性。 + +### MobileTabBar.Common + +```ts +interface MobileTabBarCommonProps { + // 图标 + icon?: string; + // 选中时的图标 + selectedIcon?: string; + // 标题 + title?: string; + // 点击事件 + onClick?: () => void; + // 是否选中 + selected?: boolean; + // 是否显示徽标 + badge?: number | string | boolean; +} +``` + +### MobileTabBar.Page + +用于渲染 Schema 的 TabBar.Item。 + +```ts +interface MobileTabBarPageProps extends Omit { + // 页面的 schema id + pageSchemaId: string; +} +``` + +当 `onClick` 时,会执行 `history.push` 到 `/mobile/${pageSchemaId}` 的页面。 + +### MobileTabBar.Link + +用于渲染外部链接的 TabBar.Item。 + +```ts +interface MobileTabBarLinkProps extends Omit { + // 链接地址 + link: string; +} +``` + +当 `onClick` 时: + +- 内部链接:执行 `history.push` 到 `link` 的页面。 +- 外部链接:执行 `window.open` 到 `link` 的页面。 + +### MobileTabBar.Scan + +用于渲染扫码的 TabBar.Item。 + +```ts +interface MobileTabBarScanProps extends Omit { + +} +``` + +当 `onClick` 时,会执行扫码的操作。 + +## Settings + +### MobileTabBar:settings + +### MobileTabBar.Page:settings + +### MobileTabBar.Link:settings + +### MobileTabBar.Scan:settings + +## Initializer + +### MobileTabBar:initializer diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.ts new file mode 100644 index 0000000000..1ec3e0f2ba --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/index.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileTabBar'; +export * from './MobileTabBar.Item'; +export * from './initializer'; +export * from './types'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/initializer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/initializer.tsx new file mode 100644 index 0000000000..27169eff6d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/initializer.tsx @@ -0,0 +1,25 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaInitializer, useSchemaInitializerRender } from '@nocobase/client'; +import { mobileTabBarSchemaInitializerItem, mobileTabBarLinkInitializerItem } from './types'; + +export const mobileTabBarInitializer = new SchemaInitializer({ + name: 'mobile:tab-bar', + icon: 'PlusOutlined', + style: { + marginRight: 12, + }, + items: [mobileTabBarSchemaInitializerItem, mobileTabBarLinkInitializerItem], +}); + +export const MobileTabBarInitializer = () => { + const { render } = useSchemaInitializerRender(mobileTabBarInitializer.name); + return render({ 'data-testid': 'schema-initializer-MobileTabBar' }); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts new file mode 100644 index 0000000000..b1ec925057 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts @@ -0,0 +1,55 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; +import { NavigationBarHeight } from '../../constants'; + +export const useStyles = createStyles(() => ({ + mobileTabBar: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + height: NavigationBarHeight, + boxSizing: 'border-box', + borderTop: '1px solid var(--adm-color-border)', + backgroundColor: 'var(--adm-color-background)', + }, + mobileTabBarContent: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + gap: '1em', + height: '100%', + }, + mobileTabBarList: { + display: 'flex', + justifyContent: 'space-around', + flex: 1, + alignItems: 'center', + '.adm-tab-bar-item': { + maxWidth: '100%', + '.adm-tab-bar-item-title': { + maxWidth: '100%', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, + }, + '&>div': { + flex: 1, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + '.ant-btn-icon': { + marginInlineEnd: '0 !important', + }, + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/MobileTabBar.Link.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/MobileTabBar.Link.tsx new file mode 100644 index 0000000000..eacec9ee1b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/MobileTabBar.Link.tsx @@ -0,0 +1,27 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { MobileTabBarItemProps, MobileTabBarItem } from '../../MobileTabBar.Item'; +import { useLinkActionProps } from '@nocobase/client'; + +export interface MobileTabBarLinkProps extends Omit { + url: string; +} + +export const MobileTabBarLink: FC = (props) => { + const location = useLocation(); + const { onClick } = useLinkActionProps(props); + + const selected = location.pathname === props.url; + + return ; +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/index.ts new file mode 100644 index 0000000000..d84b21a6b0 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileTabBar.Link'; +export * from './settings'; +export * from './initializer'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/initializer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/initializer.tsx new file mode 100644 index 0000000000..7cf2c111bf --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/initializer.tsx @@ -0,0 +1,53 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaInitializerItemActionModalType } from '@nocobase/client'; +import { App } from 'antd'; +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../../../locale'; +import { getMobileTabBarItemSchemaFields } from '../../MobileTabBar.Item'; +import { MobileRouteItem, useMobileRoutes } from '../../../../mobile-providers'; + +export const mobileTabBarLinkInitializerItem: SchemaInitializerItemActionModalType = { + name: 'link', + type: 'actionModal', + useComponentProps() { + const { resource, refresh } = useMobileRoutes(); + const { t } = usePluginTranslation(); + const { message } = App.useApp(); + + return { + isItem: true, + title: generatePluginTranslationTemplate('Add link'), + buttonText: generatePluginTranslationTemplate('Link'), + schema: getMobileTabBarItemSchemaFields(), + async onSubmit(values) { + if (!values.title || values.title.trim() === '') { + message.error(t('Title field is required')); + return Promise.reject(new Error(t('Title field is required'))); + } + if (!values.icon) { + message.error(t('Icon field is required')); + return Promise.reject(new Error(t('Icon field is required'))); + } + + // 先创建 tab item + await resource.create({ + values: { + type: 'link', + title: values.title, + icon: values.icon, + } as MobileRouteItem, + }); + + // 刷新 tabs + await refresh(); + }, + }; + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/settings.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/settings.tsx new file mode 100644 index 0000000000..23f8202c58 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Link/settings.tsx @@ -0,0 +1,33 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaSettings, SchemaSettingsActionLinkItem } from '@nocobase/client'; +import { editTabItemSettingsItem, removeTabItemSettingsItem, useUpdateTabBarItem } from '../../MobileTabBar.Item'; + +export const mobileTabBarLinkSettings = new SchemaSettings({ + name: 'mobile:tab-bar:link', + items: [ + editTabItemSettingsItem(), + { + name: 'editLink', + Component: SchemaSettingsActionLinkItem, + useComponentProps() { + const afterSubmit = useUpdateTabBarItem(); + return { + afterSubmit, + }; + }, + }, + { + name: 'divider', + type: 'divider', + }, + removeTabItemSettingsItem, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx new file mode 100644 index 0000000000..5bc842a77f --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx @@ -0,0 +1,31 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useCallback, useMemo } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; + +import { MobileTabBarItemProps, MobileTabBarItem } from '../../MobileTabBar.Item'; + +export interface MobileTabBarPageProps extends Omit { + schemaUid: string; +} + +export const MobileTabBarPage: FC = (props) => { + const { schemaUid, ...rests } = props; + const navigate = useNavigate(); + const location = useLocation(); + const url = useMemo(() => `/page/${schemaUid}`, [schemaUid]); + const handleClick = useCallback(() => { + navigate(url); + }, [url, navigate]); + + const selected = location.pathname.startsWith(url); + + return ; +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/index.ts new file mode 100644 index 0000000000..c4dd1a433a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileTabBar.Page'; +export * from './settings'; +export * from './initializer'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/initializer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/initializer.tsx new file mode 100644 index 0000000000..ba3854e723 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/initializer.tsx @@ -0,0 +1,84 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaInitializerItemActionModalType } from '@nocobase/client'; +import { useNavigate } from 'react-router-dom'; +import { uid } from '@formily/shared'; +import { App } from 'antd'; + +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../../../locale'; +import { getMobileTabBarItemSchemaFields } from '../../MobileTabBar.Item'; +import { MobileRouteItem, useMobileRoutes } from '../../../../mobile-providers'; +import { getMobilePageSchema } from '../../../../pages'; + +export const mobileTabBarSchemaInitializerItem: SchemaInitializerItemActionModalType = { + name: 'schema', + type: 'actionModal', + useComponentProps() { + const { resource, refresh, schemaResource } = useMobileRoutes(); + const navigate = useNavigate(); + const { t } = usePluginTranslation(); + const { message } = App.useApp(); + + return { + isItem: true, + title: generatePluginTranslationTemplate('Add page'), + buttonText: generatePluginTranslationTemplate('Page'), + schema: getMobileTabBarItemSchemaFields(), + async onSubmit(values) { + if (!values.title || values.title.trim() === '') { + message.error(t('Title field is required')); + return Promise.reject(new Error(t('Title field is required'))); + } + if (!values.icon) { + message.error(t('Icon field is required')); + return Promise.reject(new Error(t('Icon field is required'))); + } + + const pageSchemaUid = uid(); + const firstTabUid = uid(); + const url = `/page/${pageSchemaUid}`; + + // 先创建 TabBar item + const { data } = await resource.create({ + values: { + type: 'page', + schemaUid: pageSchemaUid, + title: values.title, + icon: values.icon, + } as MobileRouteItem, + }); + + // 创建空页面 + await schemaResource.insertAdjacent({ + resourceIndex: 'mobile', + position: 'beforeEnd', + values: getMobilePageSchema(pageSchemaUid, firstTabUid), + }); + + // 创建 TabBar item 的第一个 tab + const parentId = data.data.id; + await resource.create({ + values: { + type: 'tabs', + parentId, + title: 'Unnamed', + schemaUid: firstTabUid, + } as MobileRouteItem, + }); + + // 刷新 tabs + await refresh(); + + // 再跳转到页面 + navigate(url); + }, + }; + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/settings.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/settings.tsx new file mode 100644 index 0000000000..5988270965 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/settings.tsx @@ -0,0 +1,23 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaSettings } from '@nocobase/client'; +import { editTabItemSettingsItem, removeTabItemSettingsItem } from '../../MobileTabBar.Item'; + +export const mobileTabBarPageSettings = new SchemaSettings({ + name: 'mobile:tab-bar:page', + items: [ + editTabItemSettingsItem(), + { + name: 'divider', + type: 'divider', + }, + removeTabItemSettingsItem, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/index.ts new file mode 100644 index 0000000000..bb45de54fa --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileTabBar.Link'; +export * from './MobileTabBar.Page'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/MobileProviders.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/MobileProviders.tsx new file mode 100644 index 0000000000..30bb49ddfe --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/MobileProviders.tsx @@ -0,0 +1,34 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { AdminProvider } from '@nocobase/client'; +import React, { FC, useEffect } from 'react'; + +import { MobileRoutesProvider, MobileTitleProvider } from './context'; + +export interface MobileProvidersProps { + children?: React.ReactNode; + skipLogin?: boolean; +} + +export const MobileProviders: FC = ({ children, skipLogin }) => { + const AdminProviderComponent = skipLogin ? React.Fragment : AdminProvider; + + useEffect(() => { + document.body.style.setProperty('--nb-mobile-page-tabs-content-padding', '12px'); + }, []); + + return ( + + + {children} + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileRoutes.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileRoutes.tsx new file mode 100644 index 0000000000..5dc7c5615b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileRoutes.tsx @@ -0,0 +1,120 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Spin } from 'antd'; +import { useLocation } from 'react-router-dom'; +import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import { APIClient, useAPIClient, useRequest } from '@nocobase/client'; + +import type { IResource } from '@nocobase/sdk'; + +import { useMobileTitle } from './MobileTitle'; + +export interface MobileRouteItem { + id: number; + schemaUid?: string; + type: 'page' | 'link' | 'tabs'; + options?: any; + title?: string; + icon?: string; + parentId?: number; + children?: MobileRouteItem[]; +} + + +export interface MobileRoutesContextValue { + routeList?: MobileRouteItem[]; + refresh: () => Promise; + resource: IResource; + schemaResource: IResource; + activeTabBarItem?: MobileRouteItem; + activeTabItem?: MobileRouteItem; + api: APIClient; +} + +export const MobileRoutesContext = createContext(null); +MobileRoutesContext.displayName = 'MobileRoutesContext'; + +export const useMobileRoutes = () => { + return useContext(MobileRoutesContext); +}; + +function useActiveTabBar(routeList: MobileRouteItem[]) { + const { pathname } = useLocation(); + const urlMap = routeList.reduce>((map, item) => { + const url = item.schemaUid ? `/${item.type}/${item.schemaUid}` : item.options?.url; + if (url) { + map[url] = item; + } + if (item.children) { + item.children.forEach((child) => { + const childUrl = child.schemaUid ? `${url}/${child.type}/${child.schemaUid}` : child.options?.url; + if (childUrl) { + map[childUrl] = child; + } + }); + } + return map; + }, {}); + const activeTabBarItem = Object.values(urlMap).find((item) => { + if (item.schemaUid) { + return pathname.includes(`/${item.schemaUid}`); + } + if (item.options.url) { + return pathname.includes(item.options.url); + } + return false; + }); + + return { + activeTabBarItem, // 第一层 + activeTabItem: urlMap[pathname] || activeTabBarItem, // 任意层 + }; +} + +function useTitle(activeTabBar: MobileRouteItem) { + const context = useMobileTitle(); + useEffect(() => { + if (!context) return; + if (activeTabBar) { + context.setTitle(activeTabBar.title); + document.title = activeTabBar.title; + } + }, [activeTabBar, context]); +} + +export const MobileRoutesProvider = ({ children }) => { + const api = useAPIClient(); + const resource = useMemo(() => api.resource('mobileRoutes'), [api]); + const schemaResource = useMemo(() => api.resource('uiSchemas'), [api]); + const { + data, + runAsync: refresh, + loading, + } = useRequest<{ data: MobileRouteItem[] }>(() => resource.list({ tree: true, sort: 'sort' }).then((res) => res.data)); + const routeList = useMemo(() => data?.data || [], [data]); + const { activeTabBarItem, activeTabItem } = useActiveTabBar(routeList); + + useTitle(activeTabBarItem); + + if (loading) { + return ( +
+ +
+ ); + } + return ( + + {children} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileTitle.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileTitle.tsx new file mode 100644 index 0000000000..d60de19137 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/MobileTitle.tsx @@ -0,0 +1,32 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, createContext } from 'react'; + +interface MobileTitleContextProps { + title: string; + setTitle: (title: string) => void; +} + +export const MobileTitleContext = createContext(null); +MobileTitleContext.displayName = 'MobileTitleContext'; + +export interface MobileTitleProviderProps { + children?: React.ReactNode; + title?: string; +} + +export const MobileTitleProvider: FC = ({ children, title: defaultTitle }) => { + const [title, setTitle] = React.useState(defaultTitle); + return {children}; +}; + +export const useMobileTitle = () => { + return React.useContext(MobileTitleContext); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/index.ts new file mode 100644 index 0000000000..3ab2c8c1c1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/context/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileRoutes'; +export * from './MobileTitle'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.md new file mode 100644 index 0000000000..2347cc3cdd --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.md @@ -0,0 +1,39 @@ +# MobileProviders + +## MobileProviders + +移动端上下文组件。主要是包含: + +- `AdminProvider`:用于管理用户登录状态 +- `MobileTitleProvider`:用于管理页面标题 +- `MobileRoutesProvider`:用于管理页面路由 + +```jsx | pure + + {children} + +``` + +## MobileTitleProvider + +用于设置页面标题。 + +其包含 2 个组成部分: + +- `MobileTitleProvider`:用于设置上下文内容 +- `useMobileTitle`:用于获取上下文内容 + + + +## MobileRoutesProvider + +用户获取移动端路由,并向子节点传递。 + +其包含 2 个组成部分: + +- `MobileRoutesProvider`:用于设置上下文内容 +- `useMobileRoutes`:用于获取上下文内容 + +如果获取到路由,会将当前路由的 title 通过 `useMobileTitle` 设置到页面标题。 + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.tsx new file mode 100644 index 0000000000..2ed105d73e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-providers/index.tsx @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileProviders'; +export * from './context'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx new file mode 100644 index 0000000000..00c6fc9c77 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx @@ -0,0 +1,69 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Action, AntdAppProvider, GlobalThemeProvider, OpenModeProvider, usePlugin } from '@nocobase/client'; +import React from 'react'; +import { isDesktop } from 'react-device-detect'; + +import { PageBackgroundColor } from '../constants'; +import { DesktopMode } from '../desktop-mode/DesktopMode'; +import { PluginMobileClient } from '../index'; +import { MobileActionPage } from '../pages/mobile-action-page/MobileActionPage'; +import { MobileAppProvider } from './MobileAppContext'; + +export const Mobile = () => { + const mobilePlugin = usePlugin(PluginMobileClient); + const MobileRouter = mobilePlugin.getRouterComponent(); + + // 设置的移动端 meta + React.useEffect(() => { + if (!isDesktop) { + let viewportMeta = document.querySelector('meta[name="viewport"]'); + if (!viewportMeta) { + viewportMeta = document.createElement('meta'); + viewportMeta.setAttribute('name', 'viewport'); + document.head.appendChild(viewportMeta); + } + viewportMeta.setAttribute('content', 'width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'); + + document.body.style.backgroundColor = PageBackgroundColor; + document.body.style.overflow = 'hidden'; + + // 触发视图重绘 + const fakeBody = document.createElement('div'); + document.body.appendChild(fakeBody); + document.body.removeChild(fakeBody); + } + }, []); + + const DesktopComponent = mobilePlugin.desktopMode === false ? React.Fragment : DesktopMode; + + return ( + + {/* 目前移动端由于和客户端的主题对不上,所以先使用 `GlobalThemeProvider` 和 `AntdAppProvider` 进行重置为默认主题 */} + + + + + + + + + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/MobileAppContext.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/MobileAppContext.tsx new file mode 100644 index 0000000000..8233eede56 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/MobileAppContext.tsx @@ -0,0 +1,39 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { usePlugin } from '@nocobase/client'; +import React, { FC, createContext } from 'react'; +import { PluginMobileClient } from '../index'; + +interface MobileAppContextProps { + showTabBar?: boolean; + setShowTabBar?: (showTabBar: boolean) => void; +} + +export const MobileAppContext = createContext(undefined); +MobileAppContext.displayName = 'MobileAppContext'; + +export interface MobileAppProviderProps { + children?: React.ReactNode; +} + +export const MobileAppProvider: FC = ({ children }) => { + const mobilePlugin = usePlugin(PluginMobileClient); + const [showTabBar, _setShowTabBar] = React.useState(mobilePlugin.getPluginOptions()?.showTabBar ?? true); + const setShowTabBar = (showTabBar: boolean) => { + _setShowTabBar(showTabBar); + mobilePlugin.updateOptions({ showTabBar }); + }; + + return {children}; +}; + +export const useMobileApp = () => { + return React.useContext(MobileAppContext); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.md new file mode 100644 index 0000000000..d7d13488c6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.md @@ -0,0 +1,12 @@ + +# Mobile + +移动端入口组件。 + +主要内容: + +- 渲染移动端路由和 [DesktopMode](/components/desktop-mode) +- 当为手机端时,会设置 `meta` 移动端标签 + + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.tsx new file mode 100644 index 0000000000..7dfce027a1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/index.tsx @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './Mobile'; +export * from './MobileAppContext'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx new file mode 100644 index 0000000000..9eaed0c876 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx @@ -0,0 +1,46 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { RemoteSchemaComponent } from '@nocobase/client'; +import React, { useCallback } from 'react'; +import { Outlet, useParams } from 'react-router-dom'; + +export const MobilePage = () => { + const { pageSchemaUid } = useParams<{ pageSchemaUid: string }>(); + const [pageNotFind, setPageNotFind] = React.useState(false); + + const onPageNotFind = useCallback(() => { + setPageNotFind(true); + }, []); + + if (pageNotFind) { + return ( + + ); + } + + return ( + <> + + {/* 用于渲染子页面 */} + +
+ + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContent.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContent.tsx new file mode 100644 index 0000000000..5a2a8135d7 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContent.tsx @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { RemoteSchemaComponent } from '@nocobase/client'; +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { useMobileRoutes } from '../../../mobile-providers'; +import { MobilePageContentContainer } from './MobilePageContentContainer'; + +export const MobilePageContent = () => { + const { tabSchemaUid } = useParams(); + const { activeTabBarItem } = useMobileRoutes(); + + // 如果 URL 中有 tabSchemaUid,则使用 tabSchemaUid,否则使用第一个 tab 的 pageSchemaUid + return ( + + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx new file mode 100644 index 0000000000..6d3d5571d5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx @@ -0,0 +1,44 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import _ from 'lodash'; +import React, { FC, useEffect } from 'react'; +import { useStyles } from './styles'; + +export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ children, hideTabBar }) => { + const { styles } = useStyles(); + const [mobileTabBarHeight, setMobileTabBarHeight] = React.useState(0); + const [mobilePageHeader, setMobilePageHeader] = React.useState(0); + useEffect(() => { + const navigationBar = _.last(document.querySelectorAll('.mobile-page-header')); + setMobilePageHeader(navigationBar?.offsetHeight); + + if (!hideTabBar) { + const mobileTabBar = document.querySelector('.mobile-tab-bar'); + setMobileTabBarHeight(mobileTabBar?.offsetHeight); + } + // 这里依赖项要不需要填,每次都刷新 + }); + return ( + <> + {mobilePageHeader ?
: null} +
+ {children} +
+ {mobileTabBarHeight ?
: null} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts new file mode 100644 index 0000000000..110d754a9a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobilePageContent'; +export * from './initializer'; +export * from './schema'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/initializer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/initializer.tsx new file mode 100644 index 0000000000..b6998b35a6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/initializer.tsx @@ -0,0 +1,66 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaInitializer, gridRowColWrap } from '@nocobase/client'; + +export const mobileAddBlockInitializer = new SchemaInitializer({ + title: '{{t("Add block")}}', + name: 'mobile:addBlock', + icon: 'PlusOutlined', + wrap: gridRowColWrap, + style: { + margin: 20, + }, + items: [ + { + name: 'dataBlocks', + title: '{{t("Data blocks")}}', + type: 'itemGroup', + children: [ + { + name: 'table', + title: '{{t("Table")}}', + Component: 'TableBlockInitializer', + }, + { + name: 'form', + title: '{{t("Form")}}', + Component: 'FormBlockInitializer', + }, + { + name: 'details', + title: '{{t("Details")}}', + Component: 'DetailsBlockInitializer', + }, + // { + // name: 'list', + // title: '{{t("List")}}', + // Component: 'ListBlockInitializer', + // }, + { + name: 'gridCard', + title: '{{t("Grid Card")}}', + Component: 'GridCardBlockInitializer', + }, + ], + }, + { + name: 'otherBlocks', + type: 'itemGroup', + title: '{{t("Other blocks")}}', + children: [ + { + name: 'markdown', + title: '{{t("Markdown")}}', + Component: 'MarkdownBlockInitializer', + }, + ], + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/schema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/schema.ts new file mode 100644 index 0000000000..5a9b25e030 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/schema.ts @@ -0,0 +1,33 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { mobileAddBlockInitializer } from './initializer'; + +export function getMobilePageContentSchema(firstTabUid: string) { + return { + type: 'void', + 'x-component': 'MobilePageContent', + properties: { + [firstTabUid]: getPageContentTabSchema(firstTabUid), + }, + }; +} + +export function getPageContentTabSchema(pageSchemaUid: string) { + return { + type: 'void', + 'x-uid': pageSchemaUid, + 'x-async': true, + 'x-component': 'Grid', + 'x-component-props': { + showDivider: false, + }, + 'x-initializer': mobileAddBlockInitializer.name, + }; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/styles.ts new file mode 100644 index 0000000000..53db60cd7d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/styles.ts @@ -0,0 +1,26 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(() => ({ + mobilePageContent: { + maxWidth: '100%', + overflowX: 'hidden', + '.ant-card': { + marginBottom: '18px !important', + borderRadius: '0 !important', + boxShadow: 'none', + borderBottom: '1px solid var(--adm-color-border)', + }, + '.ant-nb-card-item': { + marginBottom: '18px !important', + }, + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/context.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/context.tsx new file mode 100644 index 0000000000..0f6fa71ba4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/context.tsx @@ -0,0 +1,44 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, createContext } from 'react'; + +export interface MobilePageContextProps { + /** + * @default true + */ + displayPageHeader?: boolean; + /** + * @default true + */ + displayNavigationBar?: boolean; + /** + * @default true + */ + displayPageTitle?: boolean; + /** + * @default false + */ + displayTabs?: boolean; +} + +export const MobilePageContext = createContext(null); +MobilePageContext.displayName = 'MobilePageContext'; + +export interface MobilePageProviderProps extends MobilePageContextProps { + children?: React.ReactNode; +} + +export const MobilePageProvider: FC = ({ children, ...props }) => { + return {children}; +}; + +export const useMobilePage = () => { + return React.useContext(MobilePageContext); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/MobilePageHeader.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/MobilePageHeader.tsx new file mode 100644 index 0000000000..0911cbbb7b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/MobilePageHeader.tsx @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { cx } from '@nocobase/client'; +import { SafeArea } from 'antd-mobile'; +import React, { FC } from 'react'; + +import { useMobilePage } from '../context'; +import { useStyles } from './styles'; + +export const MobilePageHeader: FC = ({ children }) => { + const { displayPageHeader = true } = useMobilePage() || {}; + const { styles } = useStyles(); + if (!displayPageHeader) { + return null; + } + + return ( +
+ + {children} +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/index.ts new file mode 100644 index 0000000000..6ae97f600e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/index.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobilePageHeader'; +export * from './tabs'; +export * from './navigation-bar'; +export * from './schema'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/MobilePageNavigationBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/MobilePageNavigationBar.tsx new file mode 100644 index 0000000000..7848b1fcb2 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/MobilePageNavigationBar.tsx @@ -0,0 +1,53 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { NavBar } from 'antd-mobile'; +import { RecursionField, useFieldSchema } from '@formily/react'; +import { cx, SchemaToolbarProvider } from '@nocobase/client'; + +import { useMobilePage } from '../../context'; +import { useMobileTitle } from '../../../../mobile-providers'; +import { useStyles } from './styles'; + +export const MobilePageNavigationBar: FC = () => { + const { title } = useMobileTitle(); + const { displayNavigationBar = true, displayPageTitle = true } = useMobilePage(); + const fieldSchema = useFieldSchema(); + const { styles } = useStyles(); + if (!displayNavigationBar) return null; + + return ( +
+ + + + } + right={ + + + + } + > + {displayPageTitle ? title : null} + + + + + +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionColorSelect.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionColorSelect.tsx new file mode 100644 index 0000000000..ac6611b5c1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionColorSelect.tsx @@ -0,0 +1,33 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Radio } from 'antd'; +import { connect } from '@formily/react'; +import React from 'react'; +import { Button } from 'antd-mobile'; + +const colors = ['default', 'primary', 'success', 'danger', 'warning']; +export const ActionColorSelect = connect((props) => { + return ( + + {colors.map((color) => { + return ( + + + + ); + })} + + ); +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionFillSelect.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionFillSelect.tsx new file mode 100644 index 0000000000..4fd8a5c531 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/ActionFillSelect.tsx @@ -0,0 +1,34 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Radio } from 'antd'; +import { connect } from '@formily/react'; +import React from 'react'; +import { Button } from 'antd-mobile'; + +const fillList = ['solid', 'outline']; +export const ActionFillSelect = connect((props) => { + return ( + + {fillList.map((fill) => { + return ( + + + + ); + })} + + ); +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/MobileNavigationActionBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/MobileNavigationActionBar.tsx new file mode 100644 index 0000000000..9f3967383b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/MobileNavigationActionBar.tsx @@ -0,0 +1,104 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { cx } from '@emotion/css'; +import { SpaceProps } from 'antd'; +import React, { CSSProperties, useContext } from 'react'; +import { ISchema, RecursionField, observer, useFieldSchema } from '@formily/react'; +import { + DndContext, + useProps, + useSchemaInitializerRender, + useSchemaToolbar, + withDynamicSchemaProps, +} from '@nocobase/client'; + +export interface ActionBarProps { + style?: CSSProperties; + className?: string; + spaceProps?: SpaceProps; +} + +export interface ActionBarContextValue { + container?: Element | DocumentFragment; + /** + * override props + */ + forceProps?: ActionBarProps; + parentComponents?: string[]; +} + +const ActionBarContext = React.createContext({ + container: undefined, +}); + +export const ActionBarProvider: React.FC = ({ children, ...props }) => { + return {children}; +}; + +export const useActionBarContext = () => { + return useContext(ActionBarContext); +}; + +export const MobileNavigationActionBar = withDynamicSchemaProps( + observer((props: any) => { + const { forceProps = {} } = useActionBarContext(); + const { style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any; + const { position } = useSchemaToolbar(); + + const fieldSchema = useFieldSchema(); + const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], { + ...fieldSchema['x-initializer-props'], + wrap(actionSchema: ISchema) { + return { + 'x-position': position, + ...actionSchema, + }; + }, + }); + return ( + + {position !== 'bottom' ? ( +
+ {position === 'left' && render({})} + {props.children && ( +
+ schema['x-position'] === position} + /> +
+ )} + {position === 'right' && render({})} +
+ ) : ( + schema['x-position'] === position} + /> + )} +
+ ); + }), + { displayName: 'MobileNavigationActionBar' }, +); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonInitializerSchema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonInitializerSchema.ts new file mode 100644 index 0000000000..df34cb35a8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonInitializerSchema.ts @@ -0,0 +1,69 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { generatePluginTranslationTemplate } from '../../../../../locale'; +import { ActionColorSelect } from './ActionColorSelect'; +import { ActionFillSelect } from './ActionFillSelect'; + +export const actionCommonInitializerSchema = { + title: { + type: 'string', + title: generatePluginTranslationTemplate('Title'), + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-reactions': [ + { + target: 'icon', + fulfill: { + state: { + required: '{{!$self.value}}', + }, + }, + }, + ], + }, + icon: { + type: 'string', + title: generatePluginTranslationTemplate('Icon'), + 'x-decorator': 'FormItem', + 'x-component': 'IconPicker', + 'x-reactions': [ + { + target: 'title', + fulfill: { + state: { + required: '{{!$self.value}}', + }, + }, + }, + ], + }, + color: { + type: 'string', + title: generatePluginTranslationTemplate('Color'), + 'x-decorator': 'FormItem', + 'x-component': ActionColorSelect, + }, + fill: { + type: 'boolean', + title: generatePluginTranslationTemplate('Fill'), + 'x-decorator': 'FormItem', + 'x-component': ActionFillSelect, + 'x-reactions': [ + { + dependencies: ['title', 'icon'], + fulfill: { + state: { + visible: '{{!!$deps[0] && !!$deps[1]}}', + }, + }, + }, + ], + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonSettings.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonSettings.ts new file mode 100644 index 0000000000..6e63534bc8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/actionCommonSettings.ts @@ -0,0 +1,82 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { ISchema, createModalSettingsItem } from '@nocobase/client'; +import { generatePluginTranslationTemplate } from '../../../../../locale'; +import { ActionColorSelect } from './ActionColorSelect'; +import { ActionFillSelect } from './ActionFillSelect'; + +export const editAction = (extraProperties?: (values: any) => Record) => { + return createModalSettingsItem({ + title: generatePluginTranslationTemplate('Edit button'), + name: 'action', + parentSchemaKey: 'x-component-props', + schema: (values) => ({ + type: 'object', + title: generatePluginTranslationTemplate('Edit'), + properties: { + ...(extraProperties ? extraProperties(values) : {}), + title: { + title: generatePluginTranslationTemplate('Title'), + type: 'string', + default: values.title, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-reactions': { + target: 'icon', + fulfill: { + state: { + required: '{{!$self.value}}', + }, + }, + }, + }, + icon: { + title: generatePluginTranslationTemplate('Icon'), + type: 'string', + default: values.icon, + 'x-decorator': 'FormItem', + 'x-component': 'IconPicker', + 'x-reactions': { + target: 'title', + fulfill: { + state: { + required: '{{!$self.value}}', + }, + }, + }, + }, + color: { + type: 'string', + default: values.color, + title: generatePluginTranslationTemplate('Color'), + 'x-decorator': 'FormItem', + 'x-component': ActionColorSelect, + }, + fill: { + type: 'boolean', + default: values.fill, + title: generatePluginTranslationTemplate('Fill'), + 'x-decorator': 'FormItem', + 'x-component': ActionFillSelect, + 'x-reactions': [ + { + dependencies: ['title', 'icon'], + fulfill: { + state: { + visible: '{{!!$deps[0] && !!$deps[1]}}', + }, + }, + }, + ], + }, + }, + }), + }); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/index.ts new file mode 100644 index 0000000000..7d451488ea --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/index.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './items'; +export * from './mobileNavigationBarActionsInitializer'; +export * from './mobile-navigation-bar-action'; +export * from './MobileNavigationActionBar'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/index.ts new file mode 100644 index 0000000000..060922c469 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './link'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/link.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/link.ts new file mode 100644 index 0000000000..1e3ebbf61c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/items/link.ts @@ -0,0 +1,81 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { + ISchema, + SchemaInitializerItemActionModalType, + SchemaSettings, + SchemaSettingsActionLinkItem, + useSchemaInitializer, +} from '@nocobase/client'; +import { App } from 'antd'; +import { usePluginTranslation, generatePluginTranslationTemplate } from '../../../../../../locale'; +import { editAction } from '../actionCommonSettings'; +import { useLinkActionProps } from '@nocobase/client'; +import { actionCommonInitializerSchema } from '../actionCommonInitializerSchema'; + +export const mobileNavigationBarLinkSettings = new SchemaSettings({ + name: `mobile:navigation-bar:actions:link`, + items: [ + editAction(), + { + name: 'editLink', + Component: SchemaSettingsActionLinkItem, + }, + { + name: 'remove', + type: 'remove', + componentProps: { + title: generatePluginTranslationTemplate('Delete action'), + }, + }, + ], +}); + +export const useMobileNavigationBarLink = () => { + const { onClick } = useLinkActionProps(); + return { + onClick, + }; +}; + +export const getMobileNavigationBarLinkSchema = (values: any): ISchema => ({ + type: 'void', + 'x-component': 'Action', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': mobileNavigationBarLinkSettings.name, + 'x-use-component-props': 'useMobileNavigationBarLink', + 'x-component-props': { + ...values, + component: 'MobileNavigationBarAction', + }, +}); + +export const mobileNavigationBarLinkInitializerItem: SchemaInitializerItemActionModalType = { + name: 'link', + type: 'actionModal', + useComponentProps() { + const { insert } = useSchemaInitializer(); + const { t } = usePluginTranslation(); + const { message } = App.useApp(); + return { + title: t('Add link'), + buttonText: t('Link'), + schema: actionCommonInitializerSchema, + isItem: true, + onSubmit(values) { + if ((!values.title || values.title.trim().length === 0) && !values.icon) { + message.error(t('Please enter title or select icon')); + return Promise.reject(new Error('Please enter title or select icon')); + } + insert(getMobileNavigationBarLinkSchema(values)); + }, + }; + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/MobileNavigationBarAction.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/MobileNavigationBarAction.tsx new file mode 100644 index 0000000000..e11e700498 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/MobileNavigationBarAction.tsx @@ -0,0 +1,69 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useMemo } from 'react'; +import { Button, ButtonProps, Space } from 'antd-mobile'; +import { cx, Icon, useSchemaToolbar } from '@nocobase/client'; +import { useStyles } from './styles'; + +interface MobileNavigationBarActionProps extends ButtonProps { + icon?: string | React.ReactNode; + title?: string; + style?: React.CSSProperties; + className?: string; + onClick?: () => void; +} + +export const MobileNavigationBarAction: FC = React.forwardRef< + any, + MobileNavigationBarActionProps +>((props, ref) => { + const { icon, color, fill, children, style = {}, className, onClick } = props; + const { position } = useSchemaToolbar(); + const title = children[0]; + const designer = children[1]; + const contentLength = [icon, title].filter(Boolean).length; + const iconElement = useMemo(() => (typeof icon === 'string' ? : icon), [icon]); + const { styles } = useStyles(); + return ( +
+ +
+ ); +}); + +MobileNavigationBarAction.displayName = 'MobileNavigationBarAction'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/index.ts new file mode 100644 index 0000000000..ee7a3f14b2 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileNavigationBarAction'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/styles.ts new file mode 100644 index 0000000000..213fe2aa7c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobile-navigation-bar-action/styles.ts @@ -0,0 +1,49 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(() => ({ + navigationBarAction: { + maxWidth: '10em', + '.adm-space': { + maxWidth: '100%', + overflow: 'hidden', + }, + }, + navigationBarActionIcon: { + width: 24, + height: 24, + lineHeight: '24px', + fontSize: 24, + padding: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + + '.schema-toolbar': { + inset: '-15px -8px', + }, + }, + navigationBarActionTitle: { + fontSize: 17, + padding: 0, + '.schema-toolbar': { + inset: '-15px -8px', + }, + }, + navigationBarActionIconAndTitle: { + height: '32px !important', + fontSize: '17px !important', + padding: '0 6px !important', + '.schema-toolbar': { + inset: '-15px', + }, + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobileNavigationBarActionsInitializer.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobileNavigationBarActionsInitializer.ts new file mode 100644 index 0000000000..37b930424f --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/actions/mobileNavigationBarActionsInitializer.ts @@ -0,0 +1,17 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaInitializer } from '@nocobase/client'; +import { mobileNavigationBarLinkInitializerItem } from './items'; + +export const mobileNavigationBarActionsInitializer = new SchemaInitializer({ + name: 'mobile:navigation-bar:actions', + icon: 'PlusOutlined', + items: [mobileNavigationBarLinkInitializerItem], +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/index.ts new file mode 100644 index 0000000000..cb94d02df3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './actions'; +export * from './MobilePageNavigationBar'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/styles.ts new file mode 100644 index 0000000000..599869ed6c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/navigation-bar/styles.ts @@ -0,0 +1,31 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(() => ({ + mobilePageNavigationBar: { + '.adm-nav-bar': { + maxWidth: '100%', + height: 49, + overflow: 'hidden', + + '.adm-nav-bar-left': { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + }, + '.adm-nav-bar-right': { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + }, + }, + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/schema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/schema.ts new file mode 100644 index 0000000000..77378f00ad --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/schema.ts @@ -0,0 +1,40 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { mobileNavigationBarActionsInitializer } from './navigation-bar'; + +export const mobilePageHeaderSchema = { + type: 'void', + 'x-component': 'MobilePageHeader', + properties: { + pageNavigationBar: { + type: 'void', + 'x-component': 'MobilePageNavigationBar', + properties: { + actionBar: { + type: 'void', + 'x-component': 'MobileNavigationActionBar', + 'x-initializer': mobileNavigationBarActionsInitializer.name, + 'x-component-props': { + spaceProps: { + style: { + flexWrap: 'nowrap', + }, + }, + }, + properties: {}, + }, + }, + }, + pageTabs: { + type: 'void', + 'x-component': 'MobilePageTabs', + }, + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts new file mode 100644 index 0000000000..f64e39d64b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(() => ({ + mobilePageHeader: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + borderBottom: '1px solid var(--adm-color-border)', + backgroundColor: 'var(--adm-color-background)', + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/MobilePageTabs.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/MobilePageTabs.tsx new file mode 100644 index 0000000000..7e08e9cd35 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/MobilePageTabs.tsx @@ -0,0 +1,84 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC, useCallback } from 'react'; +import { Space, Tabs, TabsProps } from 'antd-mobile'; +import { useParams, useNavigate, Navigate } from 'react-router-dom'; + +import { useMobileRoutes } from '../../../../mobile-providers'; +import { DndContext, DndContextProps, Icon, SortableItem } from '@nocobase/client'; +import { useStyles } from './styles'; +import { MobilePageTabsSettings } from './settings'; +import { MobilePageTabInitializer } from './initializer'; +import { useMobilePage } from '../../context'; + +export const MobilePageTabs: FC = () => { + const { activeTabBarItem, resource, refresh } = useMobileRoutes(); + const { displayTabs = false } = useMobilePage(); + + const navigate = useNavigate(); + const { styles } = useStyles(); + const { tabSchemaUid } = useParams<{ tabSchemaUid: string }>(); + const [activeKey, setActiveKey] = React.useState(() => { + return tabSchemaUid || activeTabBarItem?.children[0]?.schemaUid; + }); + const handleChange: TabsProps['onChange'] = (schemaUid) => { + setActiveKey(schemaUid); + navigate(`/${activeTabBarItem.type}/${activeTabBarItem.schemaUid}/tabs/${schemaUid}`); + }; + + const handleDragEnd: DndContextProps['onDragEnd'] = useCallback( + async (event) => { + const { active, over } = event; + const activeId = active?.id; + const overId = over?.id; + + if (!activeId || !overId || activeId === overId) { + return; + } + await resource.move({ sourceId: activeId, targetId: overId, sortField: 'sort' }); + await refresh(); + }, + [resource, refresh], + ); + + if (!activeTabBarItem) return ; + if (!displayTabs) return null; + + return ( +
+ + + {activeTabBarItem.children?.map((item) => ( + + + {item.icon ? ( + + + {item.title} + + ) : ( + item.title + )} + + } + key={String(item.schemaUid)} + > + ))} + + +
+ +
+
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/index.ts new file mode 100644 index 0000000000..7d423faecb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/index.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobilePageTabs'; +export * from './styles'; +export * from './settings'; +export * from './initializer'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/initializer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/initializer.tsx new file mode 100644 index 0000000000..90b8466f78 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/initializer.tsx @@ -0,0 +1,81 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { uid } from '@formily/shared'; +import { App } from 'antd'; +import { + SchemaInitializer, + useSchemaInitializer, + useSchemaInitializerRender, + SchemaInitializerActionModal, +} from '@nocobase/client'; + +import { getPageContentTabSchema } from '../../content'; +import { MobileRouteItem, useMobileRoutes } from '../../../../mobile-providers'; +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../../../locale'; + +export const mobilePagesTabInitializer = new SchemaInitializer({ + name: 'mobile:tabs', + Component: () => { + const { refresh, resource, activeTabBarItem } = useMobileRoutes(); + const { insert } = useSchemaInitializer(); + const { t } = usePluginTranslation(); + const { message } = App.useApp(); + + return ( + { + if (title && title.trim().length == 0) { + message.error(t('Title field is required')); + return Promise.reject(new Error('Title field is required')); + } + // 创建 Tab + const tabSchemaUid = uid(); + await resource.create({ + values: { + type: 'tabs', + title, + icon, + schemaUid: tabSchemaUid, + parentId: activeTabBarItem.id, + } as MobileRouteItem, + }); + + // 创建 Schema + insert(getPageContentTabSchema(tabSchemaUid)); + + await refresh(); + }} + schema={{ + title: { + type: 'string', + title: generatePluginTranslationTemplate('Title'), + required: true, + 'x-component': 'Input', + 'x-decorator': 'FormItem', + }, + icon: { + title: generatePluginTranslationTemplate('Icon'), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'IconPicker', + }, + }} + > + ); + }, +}); + +export const MobilePageTabInitializer = () => { + const { render } = useSchemaInitializerRender(mobilePagesTabInitializer.name); + return render(); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/settings.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/settings.tsx new file mode 100644 index 0000000000..9f6fa411f6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/settings.tsx @@ -0,0 +1,126 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { FC } from 'react'; +import { App } from 'antd'; +import { useNavigate } from 'react-router-dom'; +import { + SchemaSettings, + SchemaToolbar, + useSchemaToolbar, + SchemaToolbarProvider, + createTextSettingsItem, + SchemaSettingsItemType, +} from '@nocobase/client'; + +import { MobileRouteItem, useMobileRoutes } from '../../../../mobile-providers'; +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../../../locale'; + +const remove = createTextSettingsItem({ + name: 'remove', + title: generatePluginTranslationTemplate('Delete'), + useTextClick: () => { + const { t } = usePluginTranslation(); + const { tab } = useSchemaToolbar<{ tab: MobileRouteItem }>(); + const { refresh, resource, activeTabBarItem, api } = useMobileRoutes(); + const navigate = useNavigate(); + const { modal, message } = App.useApp(); + return async () => { + modal.confirm({ + title: t('Delete action'), + content: t('Are you sure you want to delete it?'), + onOk: async () => { + // 删除 tab + await resource.destroy({ filterByTk: tab.id }); + await refresh(); + + // 删除 schema + await api.request({ url: `/uiSchemas:remove/${tab.schemaUid}`, method: 'delete' }); + + // 跳转到第一个 tab + const firstTab = activeTabBarItem.children.find((item) => item.id !== tab.id); + navigate(`/${activeTabBarItem.type}/${activeTabBarItem.schemaUid}/${firstTab.type}/${firstTab.schemaUid}`); + message.success({ + content: 'Delete successfully', + }); + }, + }); + }; + }, + useVisible() { + const { activeTabBarItem } = useMobileRoutes(); + return activeTabBarItem.children?.length > 1; + }, +}); + +const editTitle: SchemaSettingsItemType = { + name: 'title', + type: 'actionModal', + useComponentProps() { + const { t } = usePluginTranslation(); + const { tab } = useSchemaToolbar(); + const { refresh, resource } = useMobileRoutes(); + const { message } = App.useApp(); + + return { + title: t('Edit'), + schema: { + type: 'object', + properties: { + title: { + type: 'string', + title: generatePluginTranslationTemplate('Title'), + default: tab.title, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required: true, + }, + icon: { + title: generatePluginTranslationTemplate('Icon'), + type: 'string', + 'x-decorator': 'FormItem', + default: tab.icon, + 'x-component': 'IconPicker', + }, + }, + }, + async onSubmit({ title, icon }) { + if (title && title.trim().length == 0) { + message.error(t('Title field is required')); + return Promise.reject(new Error('Title field is required')); + } + await resource.update({ filterByTk: tab.id, values: { title, icon } }); + refresh(); + }, + }; + }, +}; + +export const mobilePageTabsSettings = new SchemaSettings({ + name: 'mobile:tabs', + items: [editTitle, remove], +}); + +interface MobilePageTabsSettingsProps { + tab: MobileRouteItem; +} + +export const MobilePageTabsSettings: FC = ({ tab }) => { + return ( + + + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/styles.ts new file mode 100644 index 0000000000..1b358ab099 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/tabs/styles.ts @@ -0,0 +1,28 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(() => ({ + mobilePageTabs: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + mobilePageTabsList: { + '.adm-tabs-header': { + borderBottomWidth: 0, + }, + + '.adm-tabs-tab': { + height: 49, + padding: '10px 0 10px', + }, + }, +})); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/index.ts new file mode 100644 index 0000000000..6769a1ae4d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/index.ts @@ -0,0 +1,15 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobilePage'; +export * from './schema'; +export * from './settings'; +export * from './context'; +export * from './content'; +export * from './header'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/schema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/schema.ts new file mode 100644 index 0000000000..1180c76029 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/schema.ts @@ -0,0 +1,60 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { getMobilePageContentSchema } from './content'; +import { mobilePageHeaderSchema } from './header'; +import { mobilePageSettings } from './settings'; +import { css } from '@nocobase/client'; + +const spaceClassName = css(` +&:first-child { + .ant-space-item { + width: 30px; + height: 30px; + transform: rotate(45deg); + span { + position: relative; + bottom: -15px; + right: -8px; + transform: rotate(-45deg); + font-size: 10px; + } + } +} +`); + +export function getMobilePageSchema(pageSchemaUid: string, firstTabUid: string) { + const pageSchema = { + type: 'void', + name: pageSchemaUid, + 'x-uid': pageSchemaUid, + 'x-component': 'MobilePageProvider', + 'x-settings': mobilePageSettings.name, + 'x-decorator': 'BlockItem', + 'x-decorator-props': { + style: { + height: '100%', + }, + }, + 'x-toolbar-props': { + draggable: false, + spaceWrapperStyle: { right: -15, top: -15 }, + spaceClassName, + toolbarStyle: { + overflowX: 'hidden', + }, + }, + properties: { + header: mobilePageHeaderSchema, + content: getMobilePageContentSchema(firstTabUid), + }, + }; + + return { schema: pageSchema }; +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/settings.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/settings.tsx new file mode 100644 index 0000000000..8c1d35e6e5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/settings.tsx @@ -0,0 +1,103 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaSettings, createSwitchSettingsItem, useDesignable } from '@nocobase/client'; +import { generatePluginTranslationTemplate, usePluginTranslation } from '../../locale'; +import { useFieldSchema } from '@formily/react'; +import { useMobileApp } from '../../mobile'; + +export const mobilePageSettings = new SchemaSettings({ + name: 'mobile:page', + items: [ + { + type: 'itemGroup', + name: 'app', + useComponentProps() { + const { t } = usePluginTranslation(); + return { + title: t('App settings'), + }; + }, + useVisible() { + const app = useMobileApp(); + return !!app; + }, + children: [ + { + name: 'enableTabBar', + type: 'switch', + useComponentProps() { + const { t } = usePluginTranslation(); + const { showTabBar, setShowTabBar } = useMobileApp(); + const { refresh } = useDesignable(); + return { + title: t('Display tab bar'), + checked: showTabBar, + onChange(v) { + setShowTabBar(v); + refresh(); + }, + }; + }, + }, + ], + }, + { + type: 'itemGroup', + name: 'page', + useComponentProps() { + const { t } = usePluginTranslation(); + return { + title: t('Page settings'), + }; + }, + children: [ + createSwitchSettingsItem({ + name: 'displayPageHeader', + title: generatePluginTranslationTemplate('Display page header'), + defaultValue: true, + schemaKey: 'x-component-props.displayPageHeader', + }), + createSwitchSettingsItem({ + name: 'displayNavigationBar', + title: generatePluginTranslationTemplate('Display navigation bar'), + defaultValue: true, + schemaKey: 'x-component-props.displayNavigationBar', + useVisible() { + const schema = useFieldSchema(); + return schema['x-component-props']?.['displayPageHeader'] !== false; + }, + }), + createSwitchSettingsItem({ + name: 'displayPageTitle', + title: generatePluginTranslationTemplate('Display page title'), + defaultValue: true, + schemaKey: 'x-component-props.displayPageTitle', + useVisible() { + const schema = useFieldSchema(); + return ( + schema['x-component-props']?.['displayNavigationBar'] !== false && + schema['x-component-props']?.['displayPageHeader'] !== false + ); + }, + }), + createSwitchSettingsItem({ + name: 'displayTabs', + title: generatePluginTranslationTemplate('Display tabs'), + defaultValue: false, + schemaKey: 'x-component-props.displayTabs', + useVisible() { + const schema = useFieldSchema(); + return schema['x-component-props']?.['displayPageHeader'] !== false; + }, + }), + ], + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/MobileHomePage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/MobileHomePage.tsx new file mode 100644 index 0000000000..5984def1cb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/MobileHomePage.tsx @@ -0,0 +1,25 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useMobileRoutes } from '../../mobile-providers'; +import { isInnerLink } from '../../utils'; + +export const MobileHomePage = () => { + const { routeList } = useMobileRoutes(); + const firstValidTabBar = routeList.find((tab) => tab.schemaUid || isInnerLink(tab.options?.url)); + if (!firstValidTabBar) return null; + const url = firstValidTabBar?.options?.url || `/${firstValidTabBar.type}/${firstValidTabBar.schemaUid}`; + if (url) { + return ; + } + + return null; +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/index.ts new file mode 100644 index 0000000000..dcf164c84b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/home/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileHomePage'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.md new file mode 100644 index 0000000000..74d6a6a481 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.md @@ -0,0 +1,27 @@ +# Pages + +页面。 + +## Home Page + +首页。 + +首页如果没有自定义注册,则会跳到到第一个路由页面;如果注册了自定义首页,则会渲染。 + +#### Basic + + + +#### Custom Home + + + +#### No Routes + + + +## NotFound Page + +404 页面。 + + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.ts new file mode 100644 index 0000000000..b7a795d169 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './home'; +export * from './not-found'; +export * from './dynamic-page'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.style.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.style.ts new file mode 100644 index 0000000000..96d9ae0d8b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.style.ts @@ -0,0 +1,27 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from '@nocobase/client'; + +export const useMobileActionPageStyle = createStyles(({ css, token }: any) => { + return { + container: css` + position: absolute !important; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: ${token.colorBgLayout}; + + .mobile-page-content > .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn { + margin: 20px; + } + `, + }; +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.tsx new file mode 100644 index 0000000000..e5f131b9b3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileActionPage.tsx @@ -0,0 +1,68 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useFieldSchema } from '@formily/react'; +import { + BackButtonUsedInSubPage, + SchemaComponent, + TabsContextProvider, + useActionContext, + useTabsContext, +} from '@nocobase/client'; +import { ConfigProvider } from 'antd'; +import React, { useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { useMobileActionPageStyle } from './MobileActionPage.style'; +import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage'; + +const components = { Tabs: MobileTabsForMobileActionPage }; +const theme: any = { + token: { + marginBlock: 18, + }, +}; + +/** + * 在移动端通过 Action 按钮打开的页面 + * @returns + */ +export const MobileActionPage = ({ level }) => { + const filedSchema = useFieldSchema(); + const ctx = useActionContext(); + const { styles } = useMobileActionPageStyle(); + const tabContext = useTabsContext(); + const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []); + + const style = useMemo(() => { + return { + // 10 为基数,是为了要确保能大于 Table 中的悬浮行的 z-index + zIndex: 10 + level, + }; + }, [level]); + + if (!ctx.visible) { + return null; + } + + const actionPageNode = ( +
+ } tabBarGutter={48}> + + + + +
+ ); + + if (containerDOM) { + return createPortal(actionPageNode, containerDOM); + } + + return actionPageNode; +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.style.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.style.ts new file mode 100644 index 0000000000..15ff80290c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.style.ts @@ -0,0 +1,46 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { createStyles } from '@nocobase/client'; + +export const useMobileTabsForMobileActionPageStyle = createStyles(({ css, token }: any) => { + return { + container: css` + cursor: pointer; + text-align: right; + flex: 1; + padding-right: var(--nb-mobile-page-tabs-content-padding); + + .ant-btn { + width: 32px; + height: 32px; + padding: 0; + } + + .ant-btn > span { + display: none; + } + + span.ant-btn-icon { + display: inline-block; + font-size: 16px; + margin: 0 !important; + } + `, + + backButton: css` + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + height: 50px; + width: 50px; + `, + }; +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.tsx new file mode 100644 index 0000000000..b65e0f9044 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/mobile-action-page/MobileTabsForMobileActionPage.tsx @@ -0,0 +1,150 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { + css, + DndContext, + Icon, + SchemaComponent, + SortableItem, + Tabs as TabsOfPC, + useBackButton, + useDesigner, + useSchemaInitializerRender, + useTabsContext, +} from '@nocobase/client'; +import { Tabs } from 'antd-mobile'; +import { LeftOutline } from 'antd-mobile-icons'; +import classNames from 'classnames'; +import React, { useMemo, useRef } from 'react'; +import { MobilePageHeader } from '../dynamic-page'; +import { MobilePageContentContainer } from '../dynamic-page/content/MobilePageContentContainer'; +import { useStyles } from '../dynamic-page/header/tabs'; +import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style'; + +export const MobileTabsForMobileActionPage: any = observer( + (props) => { + const fieldSchema = useFieldSchema(); + const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']); + const { activeKey, onChange } = useTabsContext() || {}; + const { styles } = useStyles(); + const { styles: mobileTabsForMobileActionPageStyle } = useMobileTabsForMobileActionPageStyle(); + const { goBack } = useBackButton(); + const keyToTabRef = useRef({}); + + const items = useMemo(() => { + const result = fieldSchema.mapProperties((schema, key) => { + keyToTabRef.current[key] = ; + return } key={key}>; + }); + + return result; + }, [fieldSchema.mapProperties((s, key) => key).join()]); + + const tabContent = useMemo(() => { + const list = fieldSchema.mapProperties((schema, key) => { + return { + key, + node: , + }; + }); + + if (!activeKey) { + return list[0]?.node; + } + + return list.find((item) => item.key === activeKey)?.node; + }, [activeKey, fieldSchema]); + + return ( + <> + +
+
+ +
+ + + {items} + + +
{render()}
+
+
+ {tabContent} + + ); + }, + { displayName: 'MobileTabsForMobileActionPage' }, +); + +const designerCss = css` + position: relative; + &:hover { + > .general-schema-designer { + display: block; + } + } + &.nb-action-link { + > .general-schema-designer { + top: -10px; + bottom: -10px; + left: -10px; + right: -10px; + } + } + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: var(--colorBgSettingsHover); + border: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: var(--colorSettings); + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + align-self: stretch; + } + } + } +`; + +MobileTabsForMobileActionPage.TabPane = observer( + (props: any) => { + const Designer = useDesigner(); + const field = useField(); + return ( + + {props.icon && } {props.tab || field.title} + + + ); + }, + { displayName: 'MobileTabsForMobileActionPage.TabPane' }, +); + +MobileTabsForMobileActionPage.Designer = TabsOfPC.Designer; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/MobileNotFoundPage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/MobileNotFoundPage.tsx new file mode 100644 index 0000000000..d1c8b9a8e0 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/MobileNotFoundPage.tsx @@ -0,0 +1,31 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { Result } from 'antd'; +import { Button } from 'antd-mobile'; +import { useNavigate } from 'react-router-dom'; +import { usePluginTranslation } from '../../locale'; + +export const MobileNotFoundPage = () => { + const navigate = useNavigate(); + const { t } = usePluginTranslation(); + return ( + navigate('/', { replace: true })} color="primary" fill="solid"> + {t('Back Home')} + + } + /> + ); +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/index.ts new file mode 100644 index 0000000000..8472f259af --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/not-found/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileNotFoundPage'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/providers/MobileCheckerProvider.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/MobileCheckerProvider.tsx new file mode 100644 index 0000000000..670ad0f16e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/MobileCheckerProvider.tsx @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { usePlugin } from '@nocobase/client'; +import { isDesktop } from 'react-device-detect'; + +import PluginMobileClient from '../index'; + +export const MobileCheckerProvider = React.memo((props) => { + const location = useLocation(); + const navigation = useNavigate(); + const mobilePlugin = usePlugin(PluginMobileClient); + + useEffect(() => { + if (!isDesktop && location.pathname.startsWith(mobilePlugin.router.get('admin').path)) { + navigation(mobilePlugin.mobileBasename, { replace: true }); + } + }, [location.pathname]); + + return <>{props.children}; +}); +MobileCheckerProvider.displayName = 'MobileCheckerProvider'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.md b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.md new file mode 100644 index 0000000000..31483458c4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.md @@ -0,0 +1,11 @@ +# Providers + +主应用 Providers。 + + +## MobileCheckerProvider + +当为移动端时,访问 `/admin` 会自动跳转到 `/m`。 + +用于在移动端访问后台时,自动跳转到移动端后台。 + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts new file mode 100644 index 0000000000..afe67bb560 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './MobileCheckerProvider'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/utils.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/utils.ts new file mode 100644 index 0000000000..a5ee12989b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/utils.ts @@ -0,0 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export function isInnerLink(url: string) { + if (!url) return false; + return url.startsWith('/') && !url.startsWith('//'); +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/index.ts new file mode 100644 index 0000000000..be99a2ff1a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-mobile/src/locale/en-US.json new file mode 100644 index 0000000000..ccdd3fcfcf --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/locale/en-US.json @@ -0,0 +1,22 @@ +{ + "App settings": "App settings", + "Page settings": "Page settings", + "Info": "Info", + "Back": "Back", + "Link": "Link", + "Remove": "Remove", + "Add page": "Add page", + "Title": "Title", + "Icon": "Icon", + "Selected icon": "Selected icon", + "Add link": "Add link", + "Display tab bar": "Display tab bar", + "Display page header": "Display page header", + "Display navigation bar": "Display navigation bar", + "Display page title": "Display page title", + "Display tabs": "Display tabs", + "Add tab": "Add tab", + "Mobile": "Mobile", + "Title field is required": "Title field is required", + "Icon field is required": "Icon field is required" +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-mobile/src/locale/zh-CN.json new file mode 100644 index 0000000000..6569da685e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/locale/zh-CN.json @@ -0,0 +1,23 @@ +{ + "Fill": "填充", + "App settings": "应用设置", + "Info": "信息", + "Back": "返回", + "Page settings": "页面设置", + "Link": "链接", + "Remove": "删除", + "Add page": "添加页面", + "Title": "标题", + "Icon": "图标", + "Selected icon": "选中时图标", + "Add link": "添加链接", + "Display tab bar": "显示标签栏", + "Display page header": "显示页面标题", + "Display navigation bar": "显示导航栏", + "Display page title": "显示导航栏标题", + "Display tabs": "显示标签页", + "Add tab": "添加标签页", + "Mobile": "移动端", + "Title field is required": "标题必填", + "Icon field is required": "图标必填" +} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/server/collections/.gitkeep b/packages/plugins/@nocobase/plugin-mobile/src/server/collections/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/plugins/@nocobase/plugin-mobile/src/server/collections/mobileRoutes.ts b/packages/plugins/@nocobase/plugin-mobile/src/server/collections/mobileRoutes.ts new file mode 100644 index 0000000000..2cb1b2bbde --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/server/collections/mobileRoutes.ts @@ -0,0 +1,310 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { defineCollection } from '@nocobase/database'; + +export default defineCollection({ + "key": "d1za29o7irk", + "name": "mobileRoutes", + "title": "mobileRoutes", + "inherit": false, + "hidden": false, + "description": null, + "fields": [ + { + "key": "ymgf0jxu1kg", + "name": "parentId", + "type": "bigInt", + "interface": "integer", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "isForeignKey": true, + "uiSchema": { + "type": "number", + "title": "{{t(\"Parent ID\")}}", + "x-component": "InputNumber", + "x-read-pretty": true + } + }, + { + "key": "b07aqgs2shv", + "name": "parent", + "type": "belongsTo", + "interface": "m2o", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "foreignKey": "parentId", + "treeParent": true, + "onDelete": "CASCADE", + "uiSchema": { + "title": "{{t(\"Parent\")}}", + "x-component": "AssociationField", + "x-component-props": { + "multiple": false, + "fieldNames": { + "label": "id", + "value": "id" + } + } + }, + "target": "mobileRoutes", + "targetKey": "id" + }, + { + "key": "p8sxllsgin1", + "name": "children", + "type": "hasMany", + "interface": "o2m", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "foreignKey": "parentId", + "treeChildren": true, + "onDelete": "CASCADE", + "uiSchema": { + "title": "{{t(\"Children\")}}", + "x-component": "AssociationField", + "x-component-props": { + "multiple": true, + "fieldNames": { + "label": "id", + "value": "id" + } + } + }, + "target": "mobileRoutes", + "targetKey": "id", + "sourceKey": "id" + }, + { + "key": "7y601o9bmih", + "name": "id", + "type": "bigInt", + "interface": "integer", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "autoIncrement": true, + "primaryKey": true, + "allowNull": false, + "uiSchema": { + "type": "number", + "title": "{{t(\"ID\")}}", + "x-component": "InputNumber", + "x-read-pretty": true + } + }, + { + "key": "m8s9b94amz3", + "name": "createdAt", + "type": "date", + "interface": "createdAt", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "field": "createdAt", + "uiSchema": { + "type": "datetime", + "title": "{{t(\"Created at\")}}", + "x-component": "DatePicker", + "x-component-props": {}, + "x-read-pretty": true + } + }, + { + "key": "p3p69woziuu", + "name": "createdBy", + "type": "belongsTo", + "interface": "createdBy", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "target": "users", + "foreignKey": "createdById", + "uiSchema": { + "type": "object", + "title": "{{t(\"Created by\")}}", + "x-component": "AssociationField", + "x-component-props": { + "fieldNames": { + "value": "id", + "label": "nickname" + } + }, + "x-read-pretty": true + }, + "targetKey": "id" + }, + { + "key": "s0gw1blo4hm", + "name": "updatedAt", + "type": "date", + "interface": "updatedAt", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "field": "updatedAt", + "uiSchema": { + "type": "string", + "title": "{{t(\"Last updated at\")}}", + "x-component": "DatePicker", + "x-component-props": {}, + "x-read-pretty": true + } + }, + { + "key": "d1l988n09gd", + "name": "updatedBy", + "type": "belongsTo", + "interface": "updatedBy", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "target": "users", + "foreignKey": "updatedById", + "uiSchema": { + "type": "object", + "title": "{{t(\"Last updated by\")}}", + "x-component": "AssociationField", + "x-component-props": { + "fieldNames": { + "value": "id", + "label": "nickname" + } + }, + "x-read-pretty": true + }, + "targetKey": "id" + }, + { + "key": "bo7btzkbyan", + "name": "title", + "type": "string", + "interface": "input", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "uiSchema": { + "type": "string", + "x-component": "Input", + "title": "title" + } + }, + { + "key": "ozl5d8t2d5e", + "name": "icon", + "type": "string", + "interface": "input", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "uiSchema": { + "type": "string", + "x-component": "Input", + "title": "icon" + } + }, + { + "key": "6bbyhv00bp4", + "name": "schemaUid", + "type": "string", + "interface": "input", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "uiSchema": { + "type": "string", + "x-component": "Input", + "title": "schemaUid" + } + }, + { + "key": "m0k5qbaktab", + "name": "type", + "type": "string", + "interface": "input", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "uiSchema": { + "type": "string", + "x-component": "Input", + "title": "type" + } + }, + { + "key": "ssuml1j2v1b", + "name": "options", + "type": "json", + "interface": "json", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "defaultValue": null, + "uiSchema": { + "type": "object", + "x-component": "Input.JSON", + "x-component-props": { + "autoSize": { + "minRows": 5 + } + }, + "default": null, + "title": "options" + } + }, + { + "key": "jjmosjqhz8l", + "name": "sort", + "type": "sort", + "interface": "sort", + "description": null, + "collectionName": "mobileRoutes", + "parentKey": null, + "reverseKey": null, + "uiSchema": { + "type": "number", + "x-component": "InputNumber", + "x-component-props": { + "stringMode": true, + "step": "1" + }, + "x-validator": "integer", + "title": "sort" + } + } + ], + "category": [], + "logging": true, + "autoGenId": true, + "createdAt": true, + "createdBy": true, + "updatedAt": true, + "updatedBy": true, + "template": "tree", + "view": false, + "tree": "adjacencyList", + "schema": "public", + "filterTargetKey": "id" +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/server/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/server/index.ts new file mode 100644 index 0000000000..be989de7c3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/server/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export { default } from './plugin'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-mobile/src/server/plugin.ts new file mode 100644 index 0000000000..f017972026 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/server/plugin.ts @@ -0,0 +1,28 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Plugin } from '@nocobase/server'; + +export class PluginMobileServer extends Plugin { + async afterAdd() {} + + async beforeLoad() {} + + async load() {} + + async install() {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default PluginMobileServer; diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx index e924608656..8949fb09de 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx @@ -76,7 +76,11 @@ const InitializeTheme: React.FC = ({ children }) => { }, [api.auth, currentUser?.data?.data?.systemSettings?.themeId, data, run, setTheme, defaultTheme]); if (loading && !data) { - return ; + return ( +
+ +
+ ); } return ( diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 202b3c3ec3..223336097f 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -41,6 +41,7 @@ "@nocobase/plugin-localization": "1.3.0-alpha", "@nocobase/plugin-logger": "1.3.0-alpha", "@nocobase/plugin-map": "1.3.0-alpha", + "@nocobase/plugin-mobile": "1.3.0-alpha", "@nocobase/plugin-mobile-client": "1.3.0-alpha", "@nocobase/plugin-mock-collections": "1.3.0-alpha", "@nocobase/plugin-multi-app-manager": "1.3.0-alpha", diff --git a/packages/presets/nocobase/src/server/index.ts b/packages/presets/nocobase/src/server/index.ts index 3a0d84e575..7bc069a99c 100644 --- a/packages/presets/nocobase/src/server/index.ts +++ b/packages/presets/nocobase/src/server/index.ts @@ -62,7 +62,8 @@ export class PresetNocoBase extends Plugin { // 'snapshot-field>=0.8.1-alpha.3', 'graph-collection-manager>=0.9.0-alpha.1', // 'multi-app-share-collection>=0.9.2-alpha.1', - 'mobile-client>=0.10.0-alpha.2', + 'mobile', + // 'mobile-client>=0.10.0-alpha.2', 'api-keys>=0.10.1-alpha.1', 'localization>=0.11.1-alpha.1', 'theme-editor>=0.11.1-alpha.1', diff --git a/yarn.lock b/yarn.lock index 4707a7c22a..b85b7f353a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,6 +21,17 @@ query-string "^6.9.0" tslib "^2.4.1" +"@alicloud/captcha20230305@^1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@alicloud/captcha20230305/-/captcha20230305-1.1.3.tgz#e6f27c16a1dfb9e22689b867bbc1a3d5e91f82e7" + integrity sha512-AUyW7z6xgtC+Khp0BuzerirKLuxCQcovwcsvXPUib5kCxsrKvWuVWrGyTHP9rIpA1pE9ijOlDw8domtgPlY/KQ== + dependencies: + "@alicloud/endpoint-util" "^0.0.1" + "@alicloud/openapi-client" "^0.4.7" + "@alicloud/openapi-util" "^0.3.2" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.7" + "@alicloud/credentials@^2": version "2.3.0" resolved "https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.3.0.tgz#941233a07ba74cd2fdaa3f6a5d2a3cca5a10c184" @@ -31,6 +42,19 @@ ini "^1.3.5" kitx "^2.0.0" +"@alicloud/dingtalk@^2.1.23": + version "2.1.27" + resolved "https://registry.npmmirror.com/@alicloud/dingtalk/-/dingtalk-2.1.27.tgz#fc4785c8c8edde737d82620272fa8b6790724b4d" + integrity sha512-xmXFElqgZBbjH2Ct8UJLQut2TwX2OutPhtdIAjIOkOWM5F6w9MOe6xUv+k+7Jnl6O/35g41OVVMmIK8QJrDSMQ== + dependencies: + "@alicloud/endpoint-util" "^0.0.1" + "@alicloud/gateway-dingtalk" "^1.0.2" + "@alicloud/gateway-spi" "^0.0.8" + "@alicloud/openapi-client" "^0.4.9" + "@alicloud/openapi-util" "^0.3.2" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.8" + "@alicloud/dysmsapi20170525@2.0.17": version "2.0.17" resolved "https://registry.npmmirror.com/@alicloud/dysmsapi20170525/-/dysmsapi20170525-2.0.17.tgz#a350a443f52456b823772345dd57cc5fe2e6c8da" @@ -50,6 +74,15 @@ "@alicloud/tea-typescript" "^1.5.1" kitx "^2.0.0" +"@alicloud/gateway-dingtalk@^1.0.2": + version "1.0.2" + resolved "https://registry.npmmirror.com/@alicloud/gateway-dingtalk/-/gateway-dingtalk-1.0.2.tgz#3970f07324c59935892f5b9abce66e6c2ae29dfc" + integrity sha512-T8ml6kth/nCRthrtHIYnCYv7+q/41SnJaR8c99491azNSPcmMmgxis5ujYIl5irKm0cvoOCCjI9EWUFb2Tx7JA== + dependencies: + "@alicloud/gateway-spi" "^0.0.8" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.5" + "@alicloud/gateway-spi@^0.0.8": version "0.0.8" resolved "https://registry.npmmirror.com/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz#1d251986ed40d8b98690dcac8128fec0c56f0f53" @@ -81,6 +114,18 @@ "@alicloud/tea-util" "^1.4.7" "@alicloud/tea-xml" "0.0.2" +"@alicloud/openapi-client@^0.4.7", "@alicloud/openapi-client@^0.4.8", "@alicloud/openapi-client@^0.4.9": + version "0.4.9" + resolved "https://registry.npmmirror.com/@alicloud/openapi-client/-/openapi-client-0.4.9.tgz#b5627a889e48ad5223bc6fe26e2361bbff1cace0" + integrity sha512-07LzSvIHrhFmk1yOtDQsrtFU8woj29s80TzG7Bv15b7Uo+Q/3EcyoCZU4phig1FM7B0e+2ZWnTnVnKG3FgXvig== + dependencies: + "@alicloud/credentials" "^2" + "@alicloud/gateway-spi" "^0.0.8" + "@alicloud/openapi-util" "^0.3.2" + "@alicloud/tea-typescript" "^1.7.1" + "@alicloud/tea-util" "^1.4.8" + "@alicloud/tea-xml" "0.0.3" + "@alicloud/openapi-util@^0.2.7", "@alicloud/openapi-util@^0.2.9": version "0.2.9" resolved "https://registry.npmmirror.com/@alicloud/openapi-util/-/openapi-util-0.2.9.tgz#2379cd81f993dcab32066a2b892ddcbdd266d51c" @@ -101,7 +146,7 @@ kitx "^2.1.0" sm3 "^1.0.3" -"@alicloud/tea-typescript@^1", "@alicloud/tea-typescript@^1.5.1", "@alicloud/tea-typescript@^1.5.3", "@alicloud/tea-typescript@^1.7.1": +"@alicloud/tea-typescript@^1", "@alicloud/tea-typescript@^1.5.1", "@alicloud/tea-typescript@^1.5.3", "@alicloud/tea-typescript@^1.7.1", "@alicloud/tea-typescript@^1.8.0": version "1.8.0" resolved "https://registry.npmmirror.com/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz#aa9b04b6ee53e1b22aa51e224a950ea5bcd966e9" integrity sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ== @@ -125,6 +170,14 @@ "@alicloud/tea-typescript" "^1.5.1" kitx "^2.0.0" +"@alicloud/tea-util@^1.4.5", "@alicloud/tea-util@^1.4.8": + version "1.4.8" + resolved "https://registry.npmmirror.com/@alicloud/tea-util/-/tea-util-1.4.8.tgz#5948a9d14a64edeb244fcbbb065b1f3c018deca7" + integrity sha512-CPmRUAWhUMewZXLVZ8HTCrqRzzcT6F3o/1sB3IY27oU8RLjFj3FMpZe423pJoC/noNXo4Ja3FTTEFF5k8asQBw== + dependencies: + "@alicloud/tea-typescript" "^1.5.1" + kitx "^2.0.0" + "@alicloud/tea-xml@0.0.2": version "0.0.2" resolved "https://registry.npmmirror.com/@alicloud/tea-xml/-/tea-xml-0.0.2.tgz#7c97a38255d5e4f009c437facd3a2afc0ef17f45" @@ -134,6 +187,15 @@ "@types/xml2js" "^0.4.5" xml2js "^0.4.22" +"@alicloud/tea-xml@0.0.3": + version "0.0.3" + resolved "https://registry.npmmirror.com/@alicloud/tea-xml/-/tea-xml-0.0.3.tgz#14561d4dde59da1d5eaf87e898e26a68cea073c4" + integrity sha512-+/9GliugjrLglsXVrd1D80EqqKgGpyA0eQ6+1ZdUOYCaRguaSwz44trX3PaxPu/HhIPJg9PsGQQ3cSLXWZjbAA== + dependencies: + "@alicloud/tea-typescript" "^1" + "@types/xml2js" "^0.4.5" + xml2js "^0.6.0" + "@amap/amap-jsapi-loader@^1.0.1": version "1.0.1" resolved "https://registry.npmmirror.com/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz#9ec4b4d5d2467eac451f6c852e35db69e9f9f0c0" @@ -3854,6 +3916,77 @@ methods "^1.1.2" path-to-regexp "^6.1.0" +"@ldapjs/asn1@2.0.0", "@ldapjs/asn1@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@ldapjs/asn1/-/asn1-2.0.0.tgz#e25fa38fcf0b4310275d6a5a05fe4603efef5eb4" + integrity sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA== + +"@ldapjs/asn1@^1.2.0": + version "1.2.0" + resolved "https://registry.npmmirror.com/@ldapjs/asn1/-/asn1-1.2.0.tgz#5e99338fb39ff518c205827bec0fd9a6bf6b42db" + integrity sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw== + +"@ldapjs/attribute@1.0.0", "@ldapjs/attribute@^1.0.0": + version "1.0.0" + resolved "https://registry.npmmirror.com/@ldapjs/attribute/-/attribute-1.0.0.tgz#d81d626080584c1c80ef300a214458f9f78a8abb" + integrity sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ== + dependencies: + "@ldapjs/asn1" "2.0.0" + "@ldapjs/protocol" "^1.2.1" + process-warning "^2.1.0" + +"@ldapjs/change@^1.0.0": + version "1.0.0" + resolved "https://registry.npmmirror.com/@ldapjs/change/-/change-1.0.0.tgz#34818a3a31cb337d3b90ab853bb7fa90517c2c4f" + integrity sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q== + dependencies: + "@ldapjs/asn1" "2.0.0" + "@ldapjs/attribute" "1.0.0" + +"@ldapjs/controls@^2.1.0": + version "2.1.0" + resolved "https://registry.npmmirror.com/@ldapjs/controls/-/controls-2.1.0.tgz#28449cd4352f9389fb52fbf699cfa62f3e8762e6" + integrity sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw== + dependencies: + "@ldapjs/asn1" "^1.2.0" + "@ldapjs/protocol" "^1.2.1" + +"@ldapjs/dn@^1.1.0": + version "1.1.0" + resolved "https://registry.npmmirror.com/@ldapjs/dn/-/dn-1.1.0.tgz#3687c86d658d2e10aedc2c65a1ef40155dd7370b" + integrity sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A== + dependencies: + "@ldapjs/asn1" "2.0.0" + process-warning "^2.1.0" + +"@ldapjs/filter@^2.1.1": + version "2.1.1" + resolved "https://registry.npmmirror.com/@ldapjs/filter/-/filter-2.1.1.tgz#34f4774aa17086ed0186afe11c698f13dd586d56" + integrity sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw== + dependencies: + "@ldapjs/asn1" "2.0.0" + "@ldapjs/protocol" "^1.2.1" + process-warning "^2.1.0" + +"@ldapjs/messages@^1.3.0": + version "1.3.0" + resolved "https://registry.npmmirror.com/@ldapjs/messages/-/messages-1.3.0.tgz#dea3c35de6e768e54abd3c7fbaee151d3d01f386" + integrity sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw== + dependencies: + "@ldapjs/asn1" "^2.0.0" + "@ldapjs/attribute" "^1.0.0" + "@ldapjs/change" "^1.0.0" + "@ldapjs/controls" "^2.1.0" + "@ldapjs/dn" "^1.1.0" + "@ldapjs/filter" "^2.1.1" + "@ldapjs/protocol" "^1.2.1" + process-warning "^2.2.0" + +"@ldapjs/protocol@^1.2.1": + version "1.2.1" + resolved "https://registry.npmmirror.com/@ldapjs/protocol/-/protocol-1.2.1.tgz#d58d371d6958f28095e8de23b35341bcaba55cf3" + integrity sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ== + "@lerna/add@4.0.0": version "4.0.0" resolved "https://registry.npmmirror.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" @@ -4575,6 +4708,23 @@ dependencies: eslint-scope "5.1.1" +"@node-saml/node-saml@^4.0.2": + version "4.0.5" + resolved "https://registry.npmmirror.com/@node-saml/node-saml/-/node-saml-4.0.5.tgz#039e387095b54639b06df62b1b4a6d8941c6d907" + integrity sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw== + dependencies: + "@types/debug" "^4.1.7" + "@types/passport" "^1.0.11" + "@types/xml-crypto" "^1.4.2" + "@types/xml-encryption" "^1.2.1" + "@types/xml2js" "^0.4.11" + "@xmldom/xmldom" "^0.8.6" + debug "^4.3.4" + xml-crypto "^3.0.1" + xml-encryption "^3.0.2" + xml2js "^0.5.0" + xmlbuilder "^15.1.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -4814,6 +4964,15 @@ dependencies: "@opentelemetry/semantic-conventions" "1.19.0" +"@opentelemetry/exporter-prometheus@^0.46.0": + version "0.46.0" + resolved "https://registry.npmmirror.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.46.0.tgz#c411a1e8a5266f9f3ddc44a088a538c3c1ee4830" + integrity sha512-AXcoCHG31K2PLGizlJJWcfQqZsGfUZkT7ik6C8VJu7U2Cenk0Xhvd3rO+vVNSSjP1+LHkP4MQtqEXpIZttw5cw== + dependencies: + "@opentelemetry/core" "1.19.0" + "@opentelemetry/resources" "1.19.0" + "@opentelemetry/sdk-metrics" "1.19.0" + "@opentelemetry/instrumentation@^0.46.0": version "0.46.0" resolved "https://registry.npmmirror.com/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz#a8a252306f82e2eace489312798592a14eb9830e" @@ -4847,7 +5006,7 @@ "@opentelemetry/core" "1.19.0" "@opentelemetry/semantic-conventions" "1.19.0" -"@opentelemetry/sdk-metrics@^1.19.0": +"@opentelemetry/sdk-metrics@1.19.0", "@opentelemetry/sdk-metrics@^1.19.0": version "1.19.0" resolved "https://registry.npmmirror.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.19.0.tgz#fe8029af29402563eb8dba75a85fc02006ea92c4" integrity sha512-FiMii40zr0Fmys4F1i8gmuCvbinBnBsDeGBr4FQemOf0iPCLytYQm5AZJ/nn4xSc71IgKBQwTFQRAGJI7JvZ4Q== @@ -6347,7 +6506,7 @@ resolved "https://registry.npmmirror.com/@types/date-arithmetic/-/date-arithmetic-4.1.4.tgz#bdb441f61a916f11af1874a8c2cf787f77ffcb94" integrity sha512-p9eZ2X9B80iKiTW4ukVj8B4K6q9/+xFtQ5MGYA5HWToY9nL4EkhV9+6ftT2VHpVMEZb5Tv00Iel516bVdO+yRw== -"@types/debug@^4.0.0", "@types/debug@^4.1.8": +"@types/debug@^4.0.0", "@types/debug@^4.1.7", "@types/debug@^4.1.8": version "4.1.12" resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== @@ -6424,6 +6583,11 @@ resolved "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e" integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ== +"@types/geojson@^7946.0.14": + version "7946.0.14" + resolved "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" + integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== + "@types/glob-stream@*": version "8.0.2" resolved "https://registry.npmmirror.com/@types/glob-stream/-/glob-stream-8.0.2.tgz#56234435cd20f9b7b08c993be9267d661f9b914d" @@ -6606,6 +6770,13 @@ "@types/koa-compose" "*" "@types/node" "*" +"@types/ldapjs@^3.0.6": + version "3.0.6" + resolved "https://registry.npmmirror.com/@types/ldapjs/-/ldapjs-3.0.6.tgz#63aec9036c2acfb0e0b7322df336cda2c37f8bbe" + integrity sha512-E2Tn1ltJDYBsidOT9QG4engaQeQzRQ9aYNxVmjCkD33F7cIeLPgrRDXAYs0O35mK2YDU20c/+ZkNjeAPRGLM0Q== + dependencies: + "@types/node" "*" + "@types/lerna__package@*": version "5.1.3" resolved "https://registry.npmmirror.com/@types/lerna__package/-/lerna__package-5.1.3.tgz#3604531e882229dee8e3f2bd8c819c405e2fcb43" @@ -6729,6 +6900,13 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== +"@types/node@^20.11.17": + version "20.14.8" + resolved "https://registry.npmmirror.com/@types/node/-/node-20.14.8.tgz#45c26a2a5de26c3534a9504530ddb3b27ce031ac" + integrity sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA== + dependencies: + undici-types "~5.26.4" + "@types/nodemailer@6.4.4": version "6.4.4" resolved "https://registry.npmmirror.com/@types/nodemailer/-/nodemailer-6.4.4.tgz#c265f7e7a51df587597b3a49a023acaf0c741f4b" @@ -6756,6 +6934,22 @@ resolved "https://registry.npmmirror.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== +"@types/passport@^1.0.11": + version "1.0.16" + resolved "https://registry.npmmirror.com/@types/passport/-/passport-1.0.16.tgz#5a2918b180a16924c4d75c31254c31cdca5ce6cf" + integrity sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A== + dependencies: + "@types/express" "*" + +"@types/pg@^8.10.9": + version "8.11.6" + resolved "https://registry.npmmirror.com/@types/pg/-/pg-8.11.6.tgz#a2d0fb0a14b53951a17df5197401569fb9c0c54b" + integrity sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + "@types/picomatch@*": version "2.3.3" resolved "https://registry.npmmirror.com/@types/picomatch/-/picomatch-2.3.3.tgz#be60498568c19e989e43fb39aa84be1ed3655e92" @@ -6996,7 +7190,22 @@ dependencies: "@types/node" "*" -"@types/xml2js@^0.4.5": +"@types/xml-crypto@^1.4.2": + version "1.4.6" + resolved "https://registry.npmmirror.com/@types/xml-crypto/-/xml-crypto-1.4.6.tgz#6d1fd7d41c91554f2aed97c2ba273af0388fa5cf" + integrity sha512-A6jEW2FxLZo1CXsRWnZHUX2wzR3uDju2Bozt6rDbSmU/W8gkilaVbwFEVN0/NhnUdMVzwYobWtM6bU1QJJFb7Q== + dependencies: + "@types/node" "*" + xpath "0.0.27" + +"@types/xml-encryption@^1.2.1": + version "1.2.4" + resolved "https://registry.npmmirror.com/@types/xml-encryption/-/xml-encryption-1.2.4.tgz#0eceea58c82a89f62c0a2dc383a6461dfc2fe1ba" + integrity sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q== + dependencies: + "@types/node" "*" + +"@types/xml2js@^0.4.11", "@types/xml2js@^0.4.5": version "0.4.14" resolved "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a" integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ== @@ -7689,6 +7898,11 @@ loupe "^2.3.7" pretty-format "^29.7.0" +"@xmldom/xmldom@^0.8.5", "@xmldom/xmldom@^0.8.6", "@xmldom/xmldom@^0.8.8": + version "0.8.10" + resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" + integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== + "@zeit/schemas@2.6.0": version "2.6.0" resolved "https://registry.npmmirror.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3" @@ -7712,6 +7926,11 @@ abbrev@1: resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abstract-logging@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + accepts@^1.3.5, accepts@~1.3.5: version "1.3.8" resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -8093,6 +8312,31 @@ antd-mobile@^5.29.1: tslib "^2.5.0" use-sync-external-store "^1.2.0" +antd-mobile@^5.36.1: + version "5.36.1" + resolved "https://registry.npmmirror.com/antd-mobile/-/antd-mobile-5.36.1.tgz#6b0d1ada43923bcdc592aa4448f2b60e13eef75a" + integrity sha512-TneFsSc0OV95+lYSA1DzOyDdZZI3bitA071QT71cu+duS7RLMJXoOUPGcDxkCrqPC4ticwkfNx3s6NTdP2d8fQ== + dependencies: + "@floating-ui/dom" "^1.4.2" + "@rc-component/mini-decimal" "^1.1.0" + "@react-spring/web" "~9.6.1" + "@use-gesture/react" "10.3.0" + ahooks "^3.7.6" + antd-mobile-icons "^0.3.0" + antd-mobile-v5-count "^1.0.1" + classnames "^2.3.2" + dayjs "^1.11.7" + deepmerge "^4.3.1" + nano-memoize "^3.0.16" + rc-field-form "~1.27.4" + rc-util "^5.38.1" + react-fast-compare "^3.2.2" + react-is "^18.2.0" + runes2 "^1.1.2" + staged-components "^1.1.3" + tslib "^2.5.0" + use-sync-external-store "^1.2.0" + antd-style@3.4.5: version "3.4.5" resolved "https://registry.npmmirror.com/antd-style/-/antd-style-3.4.5.tgz#ed1c81a738b3ae9c4079824b13a30c27ab934f4e" @@ -8897,6 +9141,13 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" +backoff@^2.5.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" + integrity sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA== + dependencies: + precond "0.2" + bail@^2.0.0: version "2.0.2" resolved "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -10596,7 +10847,7 @@ copy-props@^2.0.1: each-props "^1.3.2" is-plain-object "^5.0.0" -copy-to-clipboard@^3.3.1, copy-to-clipboard@^3.3.3: +copy-to-clipboard@3.3.3, copy-to-clipboard@^3.3.1, copy-to-clipboard@^3.3.3: version "3.3.3" resolved "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== @@ -11778,7 +12029,7 @@ delegates@^1.0.0: resolved "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -denque@^2.0.1: +denque@^2.0.1, denque@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -13348,6 +13599,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-memoize@^2.5.1: + version "2.5.2" + resolved "https://registry.npmmirror.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + fast-redact@^3.0.0: version "3.3.0" resolved "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" @@ -16378,6 +16634,16 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" +jmespath@^0.16.0: + version "0.16.0" + resolved "https://registry.npmmirror.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + +jose@^4.15.5: + version "4.15.7" + resolved "https://registry.npmmirror.com/jose/-/jose-4.15.7.tgz#96ad68d786632bd03c9068aa281810dbbe1b60d8" + integrity sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A== + joycon@^3.0.1: version "3.1.1" resolved "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" @@ -16567,6 +16833,11 @@ json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonata@^2.0.3: + version "2.0.5" + resolved "https://registry.npmmirror.com/jsonata/-/jsonata-2.0.5.tgz#2b3b5098c019b264c4fae061a9cb24d59c7115a2" + integrity sha512-wEse9+QLIIU5IaCgtJCPsFi/H4F3qcikWzF4bAELZiRz08ohfx3Q6CjDRf4ZPF5P/92RI3KIHtb7u3jqPaHXdQ== + jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -16600,6 +16871,11 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonpath-plus@^7.2.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz#7ad94e147b3ed42f7939c315d2b9ce490c5a3899" + integrity sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA== + jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -16864,6 +17140,26 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +ldapjs@^3.0.7: + version "3.0.7" + resolved "https://registry.npmmirror.com/ldapjs/-/ldapjs-3.0.7.tgz#c69fe2965bc50a747bce834f8183f1f77c3be75d" + integrity sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg== + dependencies: + "@ldapjs/asn1" "^2.0.0" + "@ldapjs/attribute" "^1.0.0" + "@ldapjs/change" "^1.0.0" + "@ldapjs/controls" "^2.1.0" + "@ldapjs/dn" "^1.1.0" + "@ldapjs/filter" "^2.1.1" + "@ldapjs/messages" "^1.3.0" + "@ldapjs/protocol" "^1.2.1" + abstract-logging "^2.0.1" + assert-plus "^1.0.0" + backoff "^2.5.0" + once "^1.4.0" + vasync "^2.2.1" + verror "^1.10.1" + leac@^0.6.0: version "0.6.0" resolved "https://registry.npmmirror.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" @@ -17371,7 +17667,7 @@ long@^4.0.0: resolved "https://registry.npmmirror.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.2.0: +long@^5.2.0, long@^5.2.1: version "5.2.3" resolved "https://registry.npmmirror.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== @@ -17424,7 +17720,7 @@ lru-cache@6.0.0, lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@8.0.5: +lru-cache@8.0.5, lru-cache@^8.0.0: version "8.0.5" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== @@ -17434,6 +17730,11 @@ lru-cache@^10.0.2: resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +lru-cache@^10.2.0: + version "10.2.2" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" + integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== + lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.5" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -17638,6 +17939,17 @@ mariadb@^2.5.6: moment-timezone "^0.5.34" please-upgrade-node "^3.2.0" +mariadb@^3.3.0: + version "3.3.1" + resolved "https://registry.npmmirror.com/mariadb/-/mariadb-3.3.1.tgz#3ee361242411ba4cf4a80b06e304d1c68c97ad90" + integrity sha512-L8bh4iuZU3J8H7Co7rQ6OY9FDLItAN1rGy8kPA7Dyxo8AiHADuuONoypKKp1pE09drs6e5LR7UW9luLZ/A4znA== + dependencies: + "@types/geojson" "^7946.0.14" + "@types/node" "^20.11.17" + denque "^2.1.0" + iconv-lite "^0.6.3" + lru-cache "^10.2.0" + markdown-it-highlightjs@3.3.1: version "3.3.1" resolved "https://registry.npmmirror.com/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#38403610487292b8a1ae2d1acc7bb66e4ede6be8" @@ -18815,6 +19127,20 @@ mysql2@^2.3.3: seq-queue "^0.0.5" sqlstring "^2.3.2" +mysql2@^3.9.1: + version "3.10.1" + resolved "https://registry.npmmirror.com/mysql2/-/mysql2-3.10.1.tgz#c39b8faf24ef4fd56330ef269122471a22d19198" + integrity sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew== + dependencies: + denque "^2.1.0" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + mz@^2.7.0: version "2.7.0" resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -18824,7 +19150,7 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: +named-placeholders@^1.1.2, named-placeholders@^1.1.3: version "1.1.3" resolved "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== @@ -18836,6 +19162,11 @@ nan@^2.12.1: resolved "https://registry.npmmirror.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nano-memoize@^3.0.16: + version "3.0.16" + resolved "https://registry.npmmirror.com/nano-memoize/-/nano-memoize-3.0.16.tgz#454100602713973ac8639bde301e255dd54920ea" + integrity sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA== + nanoid@3.3.4: version "3.3.4" resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -19056,7 +19387,7 @@ node-releases@^2.0.14: resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -node-sql-parser@^4.18.0: +node-sql-parser@^4.11.0, node-sql-parser@^4.18.0: version "4.18.0" resolved "https://registry.npmmirror.com/node-sql-parser/-/node-sql-parser-4.18.0.tgz#516b6e633c55c5abbba1ca588ab372db81ae9318" integrity sha512-2YEOR5qlI1zUFbGMLKNfsrR5JUvFg9LxIRVE+xJe962pfVLH0rnItqLzv96XVs1Y1UIR8FxsXAuvX/lYAWZ2BQ== @@ -19377,6 +19708,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-hash@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" @@ -19531,11 +19867,16 @@ object.values@^1.1.6, object.values@^1.1.7: define-properties "^1.2.0" es-abstract "^1.22.1" -obuf@^1.0.0, obuf@^1.1.2: +obuf@^1.0.0, obuf@^1.1.2, obuf@~1.1.2: version "1.1.2" resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +oidc-token-hash@^5.0.3: + version "5.0.3" + resolved "https://registry.npmmirror.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6" + integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== + omit-deep@0.3.0: version "0.3.0" resolved "https://registry.npmmirror.com/omit-deep/-/omit-deep-0.3.0.tgz#21c8af3499bcadd29651a232cbcacbc52445ebec" @@ -19632,6 +19973,16 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" +openid-client@^5.4.2: + version "5.6.5" + resolved "https://registry.npmmirror.com/openid-client/-/openid-client-5.6.5.tgz#c149ad07b9c399476dc347097e297bbe288b8b00" + integrity sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w== + dependencies: + jose "^4.15.5" + lru-cache "^6.0.0" + object-hash "^2.2.0" + oidc-token-hash "^5.0.3" + opt-cli@1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/opt-cli/-/opt-cli-1.5.1.tgz#04db447b13c96b992eb31685266f4ed0d9736dc2" @@ -20345,11 +20696,21 @@ pg-int8@1.0.1: resolved "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + pg-pool@^3.6.1: version "3.6.1" resolved "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== +pg-protocol@*: + version "1.6.1" + resolved "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + pg-protocol@^1.6.0: version "1.6.0" resolved "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" @@ -20366,6 +20727,19 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg-types@^4.0.1: + version "4.0.2" + resolved "https://registry.npmmirror.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" + integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.1.0" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + pg@^8.11.3, pg@^8.7.3: version "8.11.3" resolved "https://registry.npmmirror.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" @@ -20964,16 +21338,33 @@ postgres-array@~2.0.0: resolved "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.npmmirror.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== +postgres-date@~2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" + integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== + postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -20981,6 +21372,21 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.4" + resolved "https://registry.npmmirror.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" + integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== + +precond@0.2: + version "0.2.3" + resolved "https://registry.npmmirror.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" + integrity sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -21109,6 +21515,11 @@ process-warning@^1.0.0: resolved "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== +process-warning@^2.1.0, process-warning@^2.2.0: + version "2.3.2" + resolved "https://registry.npmmirror.com/process-warning/-/process-warning-2.3.2.tgz#70d8a3251aab0eafe3a595d8ae2c5d2277f096a5" + integrity sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA== + process@^0.11.10: version "0.11.10" resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -21839,6 +22250,13 @@ rc@1.2.8, rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-resizable@6.6.0: + version "6.6.0" + resolved "https://registry.npmmirror.com/re-resizable/-/re-resizable-6.6.0.tgz#b2abdaa2325e015b96ef95ea7393ce762500979b" + integrity sha512-cBlRjpb/HVuFo9nOTmUBrzbcyHd3XIq0ZW/ZcT6G6sHplVIwmUIh6sm81WSDGMXOI1zTtPbMgfiCLZ8qTR4BJw== + dependencies: + fast-memoize "^2.5.1" + react-beautiful-dnd@^13.1.0: version "13.1.1" resolved "https://registry.npmmirror.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" @@ -21887,6 +22305,13 @@ react-copy-to-clipboard@^5.1.0: copy-to-clipboard "^3.3.1" prop-types "^15.8.1" +react-device-detect@2.2.3: + version "2.2.3" + resolved "https://registry.npmmirror.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca" + integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw== + dependencies: + ua-parser-js "^1.0.33" + react-dom@18.1.0, react-dom@18.x, react-dom@^18.0.0, react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -21922,7 +22347,7 @@ react-error-overlay@6.0.9: resolved "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== -react-fast-compare@^3.1.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.1.1, react-fast-compare@^3.2.0, react-fast-compare@^3.2.2: version "3.2.2" resolved "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -23252,6 +23677,28 @@ sequelize@^6.26.0: validator "^13.9.0" wkx "^0.5.0" +sequelize@^6.35.0: + version "6.37.3" + resolved "https://registry.npmmirror.com/sequelize/-/sequelize-6.37.3.tgz#ed6212029a52c59a18638d2a703da84bc2f81311" + integrity sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A== + dependencies: + "@types/debug" "^4.1.8" + "@types/validator" "^13.7.17" + debug "^4.3.4" + dottie "^2.0.6" + inflection "^1.13.4" + lodash "^4.17.21" + moment "^2.29.4" + moment-timezone "^0.5.43" + pg-connection-string "^2.6.1" + retry-as-promised "^7.0.4" + semver "^7.5.4" + sequelize-pool "^7.1.0" + toposort-class "^1.0.1" + uuid "^8.3.2" + validator "^13.9.0" + wkx "^0.5.0" + serve-handler@6.1.3: version "6.1.3" resolved "https://registry.npmmirror.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8" @@ -25295,6 +25742,11 @@ typescript@^4.4.3: resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +ua-parser-js@^1.0.33: + version "1.0.38" + resolved "https://registry.npmmirror.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2" + integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ== + uc.micro@^2.0.0, uc.micro@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" @@ -25946,6 +26398,13 @@ vary@^1, vary@^1.1.2, vary@~1.1.2: resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +vasync@^2.2.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/vasync/-/vasync-2.2.1.tgz#d881379ff3685e4affa8e775cf0fd369262a201b" + integrity sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ== + dependencies: + verror "1.10.0" + vditor@^3.10.3: version "3.10.4" resolved "https://registry.npmmirror.com/vditor/-/vditor-3.10.4.tgz#df7e5cdf8c737b588152b2119942ff0e0904c9cd" @@ -25962,6 +26421,15 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +verror@^1.10.1: + version "1.10.1" + resolved "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" + integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vfile-location@^4.0.0: version "4.1.0" resolved "https://registry.npmmirror.com/vfile-location/-/vfile-location-4.1.0.tgz#69df82fb9ef0a38d0d02b90dd84620e120050dd0" @@ -26570,6 +27038,23 @@ xlsx@^0.17.0: version "0.20.2" resolved "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz#0f64eeed3f1a46e64724620c3553f2dbd3cd2d7d" +xml-crypto@^3.0.1: + version "3.2.0" + resolved "https://registry.npmmirror.com/xml-crypto/-/xml-crypto-3.2.0.tgz#a9debab572c8e895cff5fb351a8d8be3f6e1962e" + integrity sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg== + dependencies: + "@xmldom/xmldom" "^0.8.8" + xpath "0.0.32" + +xml-encryption@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/xml-encryption/-/xml-encryption-3.0.2.tgz#d3cb67d97cdd9673313a42cc0d7fa43ff0886c21" + integrity sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg== + dependencies: + "@xmldom/xmldom" "^0.8.5" + escape-html "^1.0.3" + xpath "0.0.32" + xml-lexer@^0.2.2: version "0.2.2" resolved "https://registry.npmmirror.com/xml-lexer/-/xml-lexer-0.2.2.tgz#518193a4aa334d58fc7d248b549079b89907e046" @@ -26598,7 +27083,15 @@ xml2js@^0.4.22: sax ">=0.6.0" xmlbuilder "~11.0.0" -xml2js@^0.6.2: +xml2js@^0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xml2js@^0.6.0, xml2js@^0.6.2: version "0.6.2" resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== @@ -26606,6 +27099,11 @@ xml2js@^0.6.2: sax ">=0.6.0" xmlbuilder "~11.0.0" +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" @@ -26616,6 +27114,16 @@ xmlchars@^2.2.0: resolved "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xpath@0.0.27: + version "0.0.27" + resolved "https://registry.npmmirror.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" + integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== + +xpath@0.0.32: + version "0.0.32" + resolved "https://registry.npmmirror.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" + integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== + xpipe@^1.0.5: version "1.0.5" resolved "https://registry.npmmirror.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"