From 902635e60f90ea6525a0cce2f17a21a141b331e7 Mon Sep 17 00:00:00 2001 From: EYHN Date: Tue, 5 Nov 2024 11:00:33 +0800 Subject: [PATCH] refactor(core): desktop project struct (#8334) --- .../infra/src/framework/react/index.tsx | 5 +- .../framework/react/scope-root-components.tsx | 74 ---- packages/frontend/apps/android/src/app.tsx | 2 - .../frontend/apps/electron/renderer/app.tsx | 64 ++- .../apps/electron/renderer/shell/app.css.ts | 11 +- .../apps/electron/renderer/shell/app.tsx | 9 +- .../apps/electron/renderer/theme-sync.ts | 2 +- .../apps/electron/src/helper/dialog/dialog.ts | 3 - .../apps/electron/src/main/export/index.ts | 10 - .../apps/electron/src/main/export/pdf.ts | 90 ----- .../apps/electron/src/main/export/utils.ts | 24 -- .../apps/electron/src/main/handlers.ts | 2 - packages/frontend/apps/ios/src/app.tsx | 2 - packages/frontend/apps/mobile/src/app.tsx | 2 - packages/frontend/apps/web/src/app.tsx | 16 +- .../src/components/global-loading/index.tsx | 30 -- .../frontend/component/src/ui/modal/index.ts | 1 + .../frontend/component/src/ui/modal/modal.tsx | 1 + .../src/ui/modal/prompt-modal.css.ts | 109 ++++++ .../component/src/ui/modal/prompt-modal.tsx | 235 +++++++++++ .../component/src/ui/modal/styles.css.ts | 9 +- .../core/src/commands/affine-creation.tsx | 10 +- .../core/src/commands/affine-help.tsx | 10 +- .../core/src/commands/affine-navigation.tsx | 18 +- .../affine/ai-onboarding/edgeless.dialog.tsx | 16 +- .../affine/ai-onboarding/general.dialog.tsx | 14 +- .../components/affine/app-container.css.ts | 5 - .../src/components/affine/app-container.tsx | 59 --- .../affine/auth/user-plan-button.tsx | 12 +- .../affine/empty/collection-detail.tsx | 28 +- .../components/affine/empty/collections.tsx | 37 +- .../components/affine/hub-island/index.tsx | 10 - .../page-history-modal/history-modal.tsx | 12 +- .../quota-reached-modal/cloud-quota-modal.tsx | 15 +- .../components/affine/setting-modal/atoms.ts | 3 - .../components/affine/setting-modal/types.ts | 20 - .../setting-modal/workspace-setting/index.tsx | 22 -- .../share-menu/share-page.tsx | 10 +- .../affine/subscription-landing/notify.tsx | 41 -- .../core/src/components/atoms/index.ts | 27 -- .../block-suite-editor/ai/setup-provider.tsx | 11 +- .../block-suite-header/info/index.tsx | 8 +- .../menu}/history-tips-modal/index.tsx | 11 +- .../menu}/history-tips-modal/top-svg.tsx | 0 .../block-suite-header/menu/index.tsx | 51 ++- .../core/src/components/context/index.tsx | 3 +- .../src/components/doc-properties/index.ts | 1 - ...se-register-blocksuite-editor-commands.tsx | 55 +-- .../hooks/affine/use-subscription-notify.tsx | 83 ---- .../hooks/affine/use-trash-modal-helper.ts | 27 -- .../hooks/use-register-workspace-commands.ts | 23 +- .../components/layouts/workspace-layout.tsx | 250 ------------ .../src/components/over-capacity/index.tsx | 10 +- .../components/page-list/collections/index.ts | 1 - .../page-list/docs/page-list-header.tsx | 46 +-- .../page-list/docs/select-page.css.ts | 26 ++ .../edit-collection => docs}/select-page.tsx | 27 +- .../edit-collection => docs}/use-filter.tsx | 6 +- .../edit-collection => docs}/use-search.tsx | 0 .../page-list/docs/virtualized-page-list.tsx | 44 ++- .../components/page-list/operation-cell.tsx | 102 ++--- .../components/page-list/selector/index.ts | 27 -- .../page-list/selector/use-select-dialog.tsx | 102 ----- .../src/components/page-list/tags/index.ts | 1 - .../page-list/view/collection-operations.tsx | 50 +-- .../src/components/page-list/view/index.ts | 2 - .../view/save-as-collection-button.css.ts | 5 + .../view/save-as-collection-button.tsx | 37 +- .../page-list/view/use-edit-collection.tsx | 97 ----- .../components/providers/modal-provider.tsx | 218 ----------- .../providers/workspace-side-effects.tsx | 164 ++++++++ .../pure/header/windows-app-controls.tsx | 2 +- .../src/components/pure/help-island/index.tsx | 15 +- .../src/components/root-app-sidebar/index.tsx | 20 +- .../components/root-app-sidebar/user-info.tsx | 38 +- .../components/workspace-selector/index.tsx | 16 +- .../user-with-workspace-list/index.tsx | 20 +- .../workspace-list/index.tsx | 11 +- .../core/src/components/workspace/index.tsx | 93 ----- .../components/ai-island/container.css.ts | 17 + .../components/ai-island/container.tsx | 26 ++ .../components}/ai-island/icons.tsx | 0 .../components}/ai-island/index.tsx | 6 +- .../components}/ai-island/styles.css.ts | 0 .../components/app-container/index.tsx | 133 +++++++ .../components/app-container/styles.css.ts} | 59 +-- .../collection-editor}/edit-collection.css.ts | 22 -- .../collection-editor}/edit-collection.tsx | 68 +--- .../dialogs/collection-editor/index.tsx | 60 +++ .../dialogs/collection-editor}/rules-mode.tsx | 15 +- .../dialogs/create-workspace}/dialog.css.ts | 0 .../dialogs/create-workspace/index.tsx} | 101 ++--- .../src/desktop/dialogs/doc-info/index.tsx | 62 +++ .../dialogs/doc-info}/info-modal.css.ts | 0 .../dialogs/doc-info}/info-modal.tsx | 80 +--- .../dialogs/doc-info}/links-row.css.ts | 0 .../dialogs/doc-info}/links-row.tsx | 2 +- .../desktop/dialogs/doc-info/styles.css.ts | 84 ++++ .../dialogs/doc-info}/time-row.css.ts | 0 .../dialogs/doc-info}/time-row.tsx | 0 .../dialogs/import-template}/dialog.css.ts | 0 .../dialogs/import-template/index.tsx} | 47 +-- .../dialogs/import-workspace/dialog.css.ts | 77 ++++ .../dialogs/import-workspace/index.tsx | 59 +++ .../core/src/desktop/dialogs/import/index.tsx | 365 ++++++++++++++++++ .../src/desktop/dialogs/import/styles.css.ts | 110 ++++++ .../core/src/desktop/dialogs/index.tsx | 96 +++++ .../dialogs/selectors/collection.tsx} | 94 +++-- .../src/desktop/dialogs/selectors/doc.tsx | 35 ++ .../dialogs/selectors/tag.tsx} | 90 +++-- .../account-setting/ai-usage-panel.tsx | 15 +- .../setting}/account-setting/index.tsx | 43 ++- .../account-setting/storage-progress.css.ts | 0 .../account-setting/storage-progress.tsx | 0 .../setting}/account-setting/style.css.ts | 0 .../setting}/general-setting/about/config.tsx | 0 .../setting}/general-setting/about/icons.tsx | 0 .../setting}/general-setting/about/index.tsx | 12 +- .../general-setting/about/style.css.ts | 0 .../about/update-check-section.tsx | 0 .../general-setting/appearance/index.tsx | 3 +- .../general-setting/appearance/links.css.ts | 0 .../general-setting/appearance/links.tsx | 0 .../general-setting/appearance/style.css.ts | 0 .../appearance/theme-editor-setting.tsx | 4 +- .../general-setting/billing/index.tsx | 28 +- .../general-setting/billing/style.css.ts | 2 +- .../editor/edgeless/connector.tsx | 0 .../editor/edgeless/docs/connector.json | 0 .../editor/edgeless/docs/flow.json | 0 .../editor/edgeless/docs/index.ts | 0 .../editor/edgeless/docs/mindmap.json | 0 .../editor/edgeless/docs/note.json | 0 .../editor/edgeless/docs/pen.json | 0 .../editor/edgeless/docs/shape.json | 0 .../editor/edgeless/docs/text.json | 0 .../editor/edgeless/edgeless.tsx | 0 .../editor/edgeless/general.tsx | 0 .../general-setting/editor/edgeless/index.ts | 0 .../editor/edgeless/mind-map.tsx | 0 .../general-setting/editor/edgeless/note.tsx | 0 .../general-setting/editor/edgeless/pen.tsx | 0 .../general-setting/editor/edgeless/point.tsx | 0 .../general-setting/editor/edgeless/shape.tsx | 0 .../editor/edgeless/snapshot.tsx | 0 .../general-setting/editor/edgeless/text.tsx | 0 .../general-setting/editor/edgeless/utils.ts | 0 .../general-setting/editor/general.tsx | 0 .../setting}/general-setting/editor/index.tsx | 0 .../setting}/general-setting/editor/menu.tsx | 0 .../setting}/general-setting/editor/page.tsx | 0 .../general-setting/editor/preferences.tsx | 0 .../general-setting/editor/style.css.ts | 0 .../setting}/general-setting/editor/utils.ts | 0 .../experimental-features/arts.css.ts | 0 .../experimental-features/arts.tsx | 0 .../experimental-features/dark-art-svg.ts | 0 .../experimental-features/index.css.ts | 0 .../experimental-features/index.tsx | 0 .../experimental-features/light-art-svg.ts | 0 .../setting}/general-setting/icons.tsx | 0 .../setting}/general-setting/index.tsx | 21 +- .../general-setting/plans/actions.tsx | 2 +- .../plans/ai/actions/cancel.tsx | 0 .../general-setting/plans/ai/actions/index.ts | 0 .../plans/ai/actions/login.tsx | 0 .../plans/ai/actions/redeem.tsx | 0 .../plans/ai/actions/resume.tsx | 0 .../plans/ai/actions/subscribe.tsx | 0 .../general-setting/plans/ai/ai-plan.css.ts | 0 .../general-setting/plans/ai/ai-plan.tsx | 0 .../general-setting/plans/ai/benefits.tsx | 0 .../general-setting/plans/ai/layout.tsx | 0 .../general-setting/plans/checkout-slot.tsx | 0 .../general-setting/plans/cloud-plans.tsx | 0 .../plans/icons/bulled-list.tsx | 0 .../setting}/general-setting/plans/index.tsx | 18 +- .../general-setting/plans/layout.css.ts | 0 .../setting}/general-setting/plans/layout.tsx | 83 +--- .../general-setting/plans/lifetime/assets.ts | 0 .../plans/lifetime/believer-card.css.ts | 0 .../plans/lifetime/believer-card.tsx | 0 .../plans/lifetime/benefits.css.ts | 0 .../plans/lifetime/benefits.tsx | 0 .../plans/lifetime/lifetime-plan.tsx | 0 .../plans/lifetime/style.css.ts | 0 .../setting}/general-setting/plans/modals.tsx | 0 .../general-setting/plans/plan-card.tsx | 2 +- .../general-setting/plans/skeleton.css.ts | 0 .../general-setting/plans/skeleton.tsx | 0 .../general-setting/plans/style.css.ts | 0 .../general-setting/shortcuts/index.tsx | 0 .../general-setting/shortcuts/style.css.ts | 0 .../dialogs/setting}/index.tsx | 121 +++--- .../setting}/issue-feedback-modal/index.tsx | 11 +- .../setting}/setting-sidebar/index.tsx | 64 +-- .../setting}/setting-sidebar/style.css.ts | 0 .../setting}/star-affine-modal/index.tsx | 11 +- .../dialogs/setting}/style.css.ts | 0 .../core/src/desktop/dialogs/setting/types.ts | 8 + .../setting/workspace-setting/index.tsx | 34 ++ .../delete-leave-workspace/delete/index.tsx | 0 .../delete/style.css.ts | 0 .../delete-leave-workspace/index.tsx | 13 +- .../enable-cloud.tsx | 15 +- .../new-workspace-setting-detail/export.tsx | 6 +- .../new-workspace-setting-detail/index.tsx | 6 +- .../new-workspace-setting-detail/labels.tsx | 0 .../new-workspace-setting-detail/members.tsx | 23 +- .../new-workspace-setting-detail/profile.tsx | 0 .../new-workspace-setting-detail/sharing.tsx | 0 .../new-workspace-setting-detail/style.css.ts | 0 .../new-workspace-setting-detail/types.ts | 4 + .../workspace-setting/properties/index.tsx | 0 .../properties/styles.css.ts | 0 .../frontend/core/src/desktop/pages/404.tsx | 84 ---- .../core/src/desktop/pages/404/index.tsx | 67 ++++ .../src/desktop/pages/ai-upgrade-success.tsx | 5 - .../pages/ai-upgrade-success}/index.tsx | 47 +-- .../pages/ai-upgrade-success/styles.css.ts | 15 + .../pages/{expired.tsx => expired/index.tsx} | 7 +- .../index.tsx} | 15 +- .../src/desktop/pages/{ => index}/index.tsx | 32 +- .../pages/{invite.tsx => invite/index.tsx} | 13 +- .../core/src/desktop/pages/onboarding.tsx | 42 -- .../src/desktop/pages/onboarding/index.tsx | 33 ++ .../{open-app.tsx => open-app/index.tsx} | 2 +- .../pages/{ => open-app}/open-app.css.ts | 0 .../{redirect.tsx => redirect/index.tsx} | 5 + .../frontend/core/src/desktop/pages/root.tsx | 12 - .../pages/root/custom-theme/index.tsx} | 17 +- .../find-in-page}/find-in-page-modal.css.ts | 0 .../root/find-in-page}/find-in-page-modal.tsx | 2 +- .../pages/root}/global-loading/index.css.ts | 0 .../pages/root/global-loading/index.tsx | 28 ++ .../core/src/desktop/pages/root/index.tsx | 18 + .../pages/root/telemetry/index.tsx} | 0 .../{subscribe.tsx => subscribe/index.tsx} | 6 +- .../pages/{ => subscribe}/subscribe.css.ts | 0 .../core/src/desktop/pages/theme-editor.tsx | 5 - .../theme-editor}/components/color-cell.tsx | 0 .../pages/theme-editor}/components/empty.tsx | 0 .../components/simple-color-picker.css.ts | 0 .../components/simple-color-picker.tsx | 0 .../components/string-cell.css.ts | 0 .../theme-editor}/components/string-cell.tsx | 0 .../theme-editor}/components/tree-node.tsx | 0 .../components/variable-list.tsx | 2 +- .../src/desktop/pages/theme-editor/index.tsx | 8 + .../pages/theme-editor}/resource.ts | 0 .../pages/theme-editor}/theme-editor.css.ts | 0 .../pages/theme-editor}/theme-editor.tsx | 2 +- .../pages/theme-editor}/utils.ts | 0 .../src/desktop/pages/upgrade-success.tsx | 5 - .../desktop/pages/upgrade-success/index.tsx | 57 +++ .../pages/upgrade-success/styles.css.ts | 15 + .../workspace/all-collection/index.css.ts | 7 + .../pages/workspace/all-collection/index.tsx | 40 +- .../pages/workspace/collection/index.tsx | 21 +- .../workspace/detail-page/tabs/journal.tsx | 20 +- .../src/desktop/pages/workspace/index.tsx | 8 +- .../pages/workspace}/layouts/styles.css.ts | 0 .../workspace/layouts/workspace-layout.tsx | 77 ++++ .../pages/workspace/share/share-page.tsx | 156 ++------ .../explorer/nodes/collection/index.tsx | 32 +- .../components/explorer/nodes/doc/index.tsx | 8 +- .../explorer/nodes/folder/index.tsx | 52 ++- .../explorer/nodes/tag/operations.tsx | 89 ++++- .../sections/collections/index.css.ts | 8 + .../explorer/sections/collections/index.tsx | 63 +-- .../core/src/mobile/components/index.ts | 1 - .../selector/collection-selector.tsx | 40 -- .../src/mobile/components/selector/index.tsx | 47 --- .../core/src/mobile/dialogs/index.tsx | 90 +++++ .../dialogs/selectors/collection-selector.tsx | 55 +++ .../selectors}/doc-selector.tsx | 53 ++- .../selectors}/generic-selector.tsx | 58 +-- .../selectors}/generic.css.ts | 0 .../selectors}/tag-selector.tsx | 54 ++- .../setting}/about/index.tsx | 0 .../setting}/appearance/font.tsx | 2 +- .../setting}/appearance/index.tsx | 0 .../setting}/appearance/language.tsx | 0 .../setting}/appearance/theme.tsx | 2 +- .../setting}/dropdown-select.css.ts | 0 .../setting}/dropdown-select.tsx | 0 .../settings => dialogs/setting}/group.css.ts | 0 .../settings => dialogs/setting}/group.tsx | 0 .../settings => dialogs/setting}/index.tsx | 60 ++- .../setting}/others/index.tsx | 0 .../setting}/row.layout.tsx | 0 .../settings => dialogs/setting}/style.css.ts | 0 .../setting}/user-profile/index.tsx | 0 .../setting}/user-profile/style.css.ts | 0 .../setting}/user-usage/index.tsx | 0 .../setting}/user-usage/style.css.ts | 0 .../workspace/detail/sheets/doc-info.tsx | 4 +- .../core/src/mobile/pages/workspace/index.tsx | 3 +- .../src/mobile/pages/workspace/layout.tsx | 30 +- .../src/mobile/provider/model-provider.tsx | 76 ---- packages/frontend/core/src/mobile/router.tsx | 8 +- .../src/mobile/views/home-header/index.tsx | 9 +- .../frontend/core/src/mobile/views/index.ts | 1 - .../create-workspace/entities/dialog.ts | 33 -- .../src/modules/create-workspace/index.ts | 12 - .../create-workspace/services/dialog.ts | 7 - .../src/modules/create-workspace/types.ts | 7 - .../core/src/modules/desktop-api/index.ts | 19 +- .../src/modules/desktop-api/service/index.ts | 2 - .../desktop-api/service/workspace-events.ts | 41 -- .../core/src/modules/dialogs/constant.ts | 55 +++ .../core/src/modules/dialogs/index.ts | 17 + .../src/modules/dialogs/services/dialog.ts | 41 ++ .../dialogs/services/workspace-dialog.ts | 41 ++ .../core/src/modules/dialogs/types.ts | 16 + .../src/modules/doc-info/entities/modal.ts | 22 -- .../core/src/modules/doc-info/index.ts | 8 +- .../src/modules/doc-info/services/doc-info.ts | 7 - .../explorer/views/nodes/collection/index.tsx | 25 +- .../explorer/views/nodes/doc/index.tsx | 8 +- .../explorer/views/nodes/folder/index.tsx | 52 ++- .../views/sections/collections/index.css.ts | 8 + .../views/sections/collections/index.tsx | 37 +- .../core/src/modules/find-in-page/index.ts | 2 + .../core/src/modules/import-template/index.ts | 6 +- .../import-template/services/dialog.ts | 7 - packages/frontend/core/src/modules/index.ts | 8 +- .../core/src/modules/open-in-app/index.ts | 2 +- .../open-in-app/views/open-in-app-page.tsx | 14 +- .../peek-view/view/modal-container.css.ts | 22 +- .../peek-view/view/modal-container.tsx | 2 + .../peek-view/view/peek-view-controls.tsx | 10 +- .../core/src/modules/theme-editor/index.ts | 2 - .../workbench/view/split-view/split-view.tsx | 21 +- .../i18n/src/i18n-completenesses.json | 2 +- packages/frontend/i18n/src/resources/en.json | 3 +- tests/affine-cloud/e2e/collaboration.spec.ts | 4 +- tests/affine-local/e2e/all-page.spec.ts | 6 +- tests/affine-local/e2e/drag-page.spec.ts | 4 +- .../e2e/local-first-collections-items.spec.ts | 10 +- .../e2e/local-first-favorite-page.spec.ts | 18 - tests/affine-local/e2e/quick-search.spec.ts | 3 +- tests/kit/utils/properties.ts | 2 +- 343 files changed, 3846 insertions(+), 3508 deletions(-) delete mode 100644 packages/common/infra/src/framework/react/scope-root-components.tsx delete mode 100644 packages/frontend/apps/electron/src/main/export/index.ts delete mode 100644 packages/frontend/apps/electron/src/main/export/pdf.ts delete mode 100644 packages/frontend/apps/electron/src/main/export/utils.ts create mode 100644 packages/frontend/component/src/ui/modal/prompt-modal.css.ts create mode 100644 packages/frontend/component/src/ui/modal/prompt-modal.tsx delete mode 100644 packages/frontend/core/src/components/affine/app-container.css.ts delete mode 100644 packages/frontend/core/src/components/affine/app-container.tsx delete mode 100644 packages/frontend/core/src/components/affine/hub-island/index.tsx delete mode 100644 packages/frontend/core/src/components/affine/setting-modal/atoms.ts delete mode 100644 packages/frontend/core/src/components/affine/setting-modal/types.ts delete mode 100644 packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx rename packages/frontend/core/src/components/{affine => blocksuite/block-suite-header/menu}/history-tips-modal/index.tsx (84%) rename packages/frontend/core/src/components/{affine => blocksuite/block-suite-header/menu}/history-tips-modal/top-svg.tsx (100%) delete mode 100644 packages/frontend/core/src/components/hooks/affine/use-trash-modal-helper.ts delete mode 100644 packages/frontend/core/src/components/layouts/workspace-layout.tsx create mode 100644 packages/frontend/core/src/components/page-list/docs/select-page.css.ts rename packages/frontend/core/src/components/page-list/{view/edit-collection => docs}/select-page.tsx (90%) rename packages/frontend/core/src/components/page-list/{view/edit-collection => docs}/use-filter.tsx (88%) rename packages/frontend/core/src/components/page-list/{view/edit-collection => docs}/use-search.tsx (100%) delete mode 100644 packages/frontend/core/src/components/page-list/selector/index.ts delete mode 100644 packages/frontend/core/src/components/page-list/selector/use-select-dialog.tsx delete mode 100644 packages/frontend/core/src/components/page-list/view/use-edit-collection.tsx delete mode 100644 packages/frontend/core/src/components/providers/modal-provider.tsx create mode 100644 packages/frontend/core/src/components/providers/workspace-side-effects.tsx delete mode 100644 packages/frontend/core/src/components/workspace/index.tsx create mode 100644 packages/frontend/core/src/desktop/components/ai-island/container.css.ts create mode 100644 packages/frontend/core/src/desktop/components/ai-island/container.tsx rename packages/frontend/core/src/{components/pure => desktop/components}/ai-island/icons.tsx (100%) rename packages/frontend/core/src/{components/pure => desktop/components}/ai-island/index.tsx (90%) rename packages/frontend/core/src/{components/pure => desktop/components}/ai-island/styles.css.ts (100%) create mode 100644 packages/frontend/core/src/desktop/components/app-container/index.tsx rename packages/frontend/core/src/{components/workspace/index.css.ts => desktop/components/app-container/styles.css.ts} (79%) rename packages/frontend/core/src/{components/page-list/view/edit-collection => desktop/dialogs/collection-editor}/edit-collection.css.ts (88%) rename packages/frontend/core/src/{components/page-list/view/edit-collection => desktop/dialogs/collection-editor}/edit-collection.tsx (68%) create mode 100644 packages/frontend/core/src/desktop/dialogs/collection-editor/index.tsx rename packages/frontend/core/src/{components/page-list/view/edit-collection => desktop/dialogs/collection-editor}/rules-mode.tsx (96%) rename packages/frontend/core/src/{modules/create-workspace/views => desktop/dialogs/create-workspace}/dialog.css.ts (100%) rename packages/frontend/core/src/{modules/create-workspace/views/dialog.tsx => desktop/dialogs/create-workspace/index.tsx} (64%) create mode 100644 packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/info-modal.css.ts (100%) rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/info-modal.tsx (62%) rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/links-row.css.ts (100%) rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/links-row.tsx (90%) create mode 100644 packages/frontend/core/src/desktop/dialogs/doc-info/styles.css.ts rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/time-row.css.ts (100%) rename packages/frontend/core/src/{components/doc-properties/info-modal => desktop/dialogs/doc-info}/time-row.tsx (100%) rename packages/frontend/core/src/{modules/import-template/views => desktop/dialogs/import-template}/dialog.css.ts (100%) rename packages/frontend/core/src/{modules/import-template/views/dialog.tsx => desktop/dialogs/import-template/index.tsx} (86%) create mode 100644 packages/frontend/core/src/desktop/dialogs/import-workspace/dialog.css.ts create mode 100644 packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx create mode 100644 packages/frontend/core/src/desktop/dialogs/import/index.tsx create mode 100644 packages/frontend/core/src/desktop/dialogs/import/styles.css.ts create mode 100644 packages/frontend/core/src/desktop/dialogs/index.tsx rename packages/frontend/core/src/{components/page-list/collections/select-collection.tsx => desktop/dialogs/selectors/collection.tsx} (53%) create mode 100644 packages/frontend/core/src/desktop/dialogs/selectors/doc.tsx rename packages/frontend/core/src/{components/page-list/tags/select-tag.tsx => desktop/dialogs/selectors/tag.tsx} (51%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/account-setting/ai-usage-panel.tsx (93%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/account-setting/index.tsx (90%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/account-setting/storage-progress.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/account-setting/storage-progress.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/account-setting/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/about/config.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/about/icons.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/about/index.tsx (99%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/about/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/about/update-check-section.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/appearance/index.tsx (98%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/appearance/links.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/appearance/links.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/appearance/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/appearance/theme-editor-setting.tsx (95%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/billing/index.tsx (97%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/billing/style.css.ts (99%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/connector.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/connector.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/flow.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/index.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/mindmap.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/note.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/pen.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/shape.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/docs/text.json (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/edgeless.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/general.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/index.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/mind-map.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/note.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/pen.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/point.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/shape.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/snapshot.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/text.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/edgeless/utils.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/general.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/index.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/menu.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/page.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/preferences.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/editor/utils.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/arts.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/arts.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/dark-art-svg.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/index.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/index.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/experimental-features/light-art-svg.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/icons.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/index.tsx (87%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/actions.tsx (97%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/cancel.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/index.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/login.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/redeem.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/resume.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/actions/subscribe.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/ai-plan.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/ai-plan.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/benefits.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/ai/layout.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/checkout-slot.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/cloud-plans.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/icons/bulled-list.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/index.tsx (81%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/layout.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/layout.tsx (57%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/assets.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/believer-card.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/believer-card.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/benefits.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/benefits.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/lifetime-plan.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/lifetime/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/modals.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/plan-card.tsx (99%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/skeleton.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/skeleton.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/plans/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/shortcuts/index.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/general-setting/shortcuts/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/index.tsx (64%) rename packages/frontend/core/src/{components/affine => desktop/dialogs/setting}/issue-feedback-modal/index.tsx (78%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/setting-sidebar/index.tsx (86%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/setting-sidebar/style.css.ts (100%) rename packages/frontend/core/src/{components/affine => desktop/dialogs/setting}/star-affine-modal/index.tsx (78%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/style.css.ts (100%) create mode 100644 packages/frontend/core/src/desktop/dialogs/setting/types.ts create mode 100644 packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx (93%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx (83%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/export.tsx (92%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/index.tsx (94%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/labels.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/members.tsx (96%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/profile.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/sharing.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/style.css.ts (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/new-workspace-setting-detail/types.ts (51%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/properties/index.tsx (100%) rename packages/frontend/core/src/{components/affine/setting-modal => desktop/dialogs/setting}/workspace-setting/properties/styles.css.ts (100%) delete mode 100644 packages/frontend/core/src/desktop/pages/404.tsx create mode 100644 packages/frontend/core/src/desktop/pages/404/index.tsx delete mode 100644 packages/frontend/core/src/desktop/pages/ai-upgrade-success.tsx rename packages/frontend/core/src/{components/affine/subscription-landing => desktop/pages/ai-upgrade-success}/index.tsx (58%) create mode 100644 packages/frontend/core/src/desktop/pages/ai-upgrade-success/styles.css.ts rename packages/frontend/core/src/desktop/pages/{expired.tsx => expired/index.tsx} (88%) rename packages/frontend/core/src/desktop/pages/{import-template.tsx => import-template/index.tsx} (58%) rename packages/frontend/core/src/desktop/pages/{ => index}/index.tsx (88%) rename packages/frontend/core/src/desktop/pages/{invite.tsx => invite/index.tsx} (90%) delete mode 100644 packages/frontend/core/src/desktop/pages/onboarding.tsx create mode 100644 packages/frontend/core/src/desktop/pages/onboarding/index.tsx rename packages/frontend/core/src/desktop/pages/{open-app.tsx => open-app/index.tsx} (97%) rename packages/frontend/core/src/desktop/pages/{ => open-app}/open-app.css.ts (100%) rename packages/frontend/core/src/desktop/pages/{redirect.tsx => redirect/index.tsx} (95%) delete mode 100644 packages/frontend/core/src/desktop/pages/root.tsx rename packages/frontend/core/src/{modules/theme-editor/views/custom-theme.tsx => desktop/pages/root/custom-theme/index.tsx} (75%) rename packages/frontend/core/src/{modules/find-in-page/view => desktop/pages/root/find-in-page}/find-in-page-modal.css.ts (100%) rename packages/frontend/core/src/{modules/find-in-page/view => desktop/pages/root/find-in-page}/find-in-page-modal.tsx (99%) rename packages/frontend/{component/src/components => core/src/desktop/pages/root}/global-loading/index.css.ts (100%) create mode 100644 packages/frontend/core/src/desktop/pages/root/global-loading/index.tsx create mode 100644 packages/frontend/core/src/desktop/pages/root/index.tsx rename packages/frontend/core/src/{components/telemetry.tsx => desktop/pages/root/telemetry/index.tsx} (100%) rename packages/frontend/core/src/desktop/pages/{subscribe.tsx => subscribe/index.tsx} (96%) rename packages/frontend/core/src/desktop/pages/{ => subscribe}/subscribe.css.ts (100%) delete mode 100644 packages/frontend/core/src/desktop/pages/theme-editor.tsx rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/color-cell.tsx (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/empty.tsx (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/simple-color-picker.css.ts (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/simple-color-picker.tsx (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/string-cell.css.ts (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/string-cell.tsx (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/tree-node.tsx (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/components/variable-list.tsx (96%) create mode 100644 packages/frontend/core/src/desktop/pages/theme-editor/index.tsx rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/resource.ts (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/theme-editor.css.ts (100%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/theme-editor.tsx (97%) rename packages/frontend/core/src/{modules/theme-editor/views => desktop/pages/theme-editor}/utils.ts (100%) delete mode 100644 packages/frontend/core/src/desktop/pages/upgrade-success.tsx create mode 100644 packages/frontend/core/src/desktop/pages/upgrade-success/index.tsx create mode 100644 packages/frontend/core/src/desktop/pages/upgrade-success/styles.css.ts rename packages/frontend/core/src/{components => desktop/pages/workspace}/layouts/styles.css.ts (100%) create mode 100644 packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx create mode 100644 packages/frontend/core/src/mobile/components/explorer/sections/collections/index.css.ts delete mode 100644 packages/frontend/core/src/mobile/components/selector/collection-selector.tsx delete mode 100644 packages/frontend/core/src/mobile/components/selector/index.tsx create mode 100644 packages/frontend/core/src/mobile/dialogs/index.tsx create mode 100644 packages/frontend/core/src/mobile/dialogs/selectors/collection-selector.tsx rename packages/frontend/core/src/mobile/{components/selector => dialogs/selectors}/doc-selector.tsx (56%) rename packages/frontend/core/src/mobile/{components/selector => dialogs/selectors}/generic-selector.tsx (81%) rename packages/frontend/core/src/mobile/{components/selector => dialogs/selectors}/generic.css.ts (100%) rename packages/frontend/core/src/mobile/{components/selector => dialogs/selectors}/tag-selector.tsx (53%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/about/index.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/appearance/font.tsx (89%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/appearance/index.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/appearance/language.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/appearance/theme.tsx (84%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/dropdown-select.css.ts (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/dropdown-select.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/group.css.ts (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/group.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/index.tsx (72%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/others/index.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/row.layout.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/style.css.ts (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/user-profile/index.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/user-profile/style.css.ts (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/user-usage/index.tsx (100%) rename packages/frontend/core/src/mobile/{views/settings => dialogs/setting}/user-usage/style.css.ts (100%) delete mode 100644 packages/frontend/core/src/mobile/provider/model-provider.tsx delete mode 100644 packages/frontend/core/src/modules/create-workspace/entities/dialog.ts delete mode 100644 packages/frontend/core/src/modules/create-workspace/index.ts delete mode 100644 packages/frontend/core/src/modules/create-workspace/services/dialog.ts delete mode 100644 packages/frontend/core/src/modules/create-workspace/types.ts delete mode 100644 packages/frontend/core/src/modules/desktop-api/service/index.ts delete mode 100644 packages/frontend/core/src/modules/desktop-api/service/workspace-events.ts create mode 100644 packages/frontend/core/src/modules/dialogs/constant.ts create mode 100644 packages/frontend/core/src/modules/dialogs/index.ts create mode 100644 packages/frontend/core/src/modules/dialogs/services/dialog.ts create mode 100644 packages/frontend/core/src/modules/dialogs/services/workspace-dialog.ts create mode 100644 packages/frontend/core/src/modules/dialogs/types.ts delete mode 100644 packages/frontend/core/src/modules/doc-info/entities/modal.ts delete mode 100644 packages/frontend/core/src/modules/doc-info/services/doc-info.ts create mode 100644 packages/frontend/core/src/modules/explorer/views/sections/collections/index.css.ts delete mode 100644 packages/frontend/core/src/modules/import-template/services/dialog.ts diff --git a/packages/common/infra/src/framework/react/index.tsx b/packages/common/infra/src/framework/react/index.tsx index ff21a2704c..4e6986f9a0 100644 --- a/packages/common/infra/src/framework/react/index.tsx +++ b/packages/common/infra/src/framework/react/index.tsx @@ -4,9 +4,6 @@ import type { FrameworkProvider, Scope, Service } from '../core'; import { ComponentNotFoundError, Framework } from '../core'; import { parseIdentifier } from '../core/identifier'; import type { GeneralIdentifier, IdentifierType, Type } from '../core/types'; -import { MountPoint } from './scope-root-components'; - -export { useMount } from './scope-root-components'; export const FrameworkStackContext = React.createContext([ Framework.EMPTY.provider(), @@ -129,7 +126,7 @@ export const FrameworkScope = ({ return ( - {children} + {children} ); }; diff --git a/packages/common/infra/src/framework/react/scope-root-components.tsx b/packages/common/infra/src/framework/react/scope-root-components.tsx deleted file mode 100644 index 04b51ebcc5..0000000000 --- a/packages/common/infra/src/framework/react/scope-root-components.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; - -type NodesMap = Map< - number, - { - node: React.ReactNode; - debugKey?: string; - } ->; - -const ScopeRootComponentsContext = React.createContext<{ - nodes: NodesMap; - setNodes: React.Dispatch>; -}>({ nodes: new Map(), setNodes: () => {} }); - -let _id = 0; -/** - * A hook to add nodes to the nearest scope's root - */ -export const useMount = (debugKey?: string) => { - const [id] = React.useState(_id++); - const { setNodes } = React.useContext(ScopeRootComponentsContext); - - const unmount = React.useCallback(() => { - setNodes(prev => { - if (!prev.has(id)) { - return prev; - } - const next = new Map(prev); - next.delete(id); - return next; - }); - }, [id, setNodes]); - - const mount = React.useCallback( - (node: React.ReactNode) => { - setNodes(prev => new Map(prev).set(id, { node, debugKey })); - return unmount; - }, - [setNodes, id, debugKey, unmount] - ); - - return React.useMemo(() => { - return { - /** - * Add a node to the nearest scope root - * ```tsx - * const { mount } = useMount(); - * useEffect(() => { - * const unmount = mount(
Node
); - * return unmount; - * }, []) - * ``` - * @return A function to unmount the added node. - */ - mount, - }; - }, [mount]); -}; - -export const MountPoint = ({ children }: React.PropsWithChildren) => { - const [nodes, setNodes] = React.useState(new Map()); - - return ( - - {children} - {Array.from(nodes.entries()).map(([id, { node, debugKey }]) => ( -
- {node} -
- ))} -
- ); -}; diff --git a/packages/frontend/apps/android/src/app.tsx b/packages/frontend/apps/android/src/app.tsx index 269d79b5d5..ac43d5e79c 100644 --- a/packages/frontend/apps/android/src/app.tsx +++ b/packages/frontend/apps/android/src/app.tsx @@ -1,5 +1,4 @@ import { AffineContext } from '@affine/core/components/context'; -import { Telemetry } from '@affine/core/components/telemetry'; import { AppFallback } from '@affine/core/mobile/components'; import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; @@ -47,7 +46,6 @@ export function App() { - } router={router} diff --git a/packages/frontend/apps/electron/renderer/app.tsx b/packages/frontend/apps/electron/renderer/app.tsx index 3c06bc4995..8c1b077352 100644 --- a/packages/frontend/apps/electron/renderer/app.tsx +++ b/packages/frontend/apps/electron/renderer/app.tsx @@ -1,8 +1,6 @@ -import { GlobalLoading } from '@affine/component/global-loading'; -import { AppFallback } from '@affine/core/components/affine/app-container'; import { AffineContext } from '@affine/core/components/context'; import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; -import { Telemetry } from '@affine/core/components/telemetry'; +import { AppContainer } from '@affine/core/desktop/components/app-container'; import { router } from '@affine/core/desktop/router'; import { configureCommonModules } from '@affine/core/modules'; import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header'; @@ -11,27 +9,35 @@ import { configureDesktopApiModule, DesktopApiService, } from '@affine/core/modules/desktop-api'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; +import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { configureFindInPageModule } from '@affine/core/modules/find-in-page'; import { I18nProvider } from '@affine/core/modules/i18n'; import { configureElectronStateStorageImpls } from '@affine/core/modules/storage'; -import { CustomThemeModifier } from '@affine/core/modules/theme-editor'; import { ClientSchemeProvider, PopupWindowProvider, } from '@affine/core/modules/url'; import { configureSqliteUserspaceStorageProvider } from '@affine/core/modules/userspace'; -import { configureDesktopWorkbenchModule } from '@affine/core/modules/workbench'; +import { + configureDesktopWorkbenchModule, + WorkbenchService, +} from '@affine/core/modules/workbench'; import { configureBrowserWorkspaceFlavours, configureSqliteWorkspaceEngineStorageProvider, } from '@affine/core/modules/workspace-engine'; import createEmotionCache from '@affine/core/utils/create-emotion-cache'; +import { apis, events } from '@affine/electron-api'; import { CacheProvider } from '@emotion/react'; import { + DocsService, Framework, FrameworkRoot, getCurrentStore, + GlobalContextService, LifecycleService, + WorkspacesService, } from '@toeverything/infra'; import { Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; @@ -111,6 +117,49 @@ window.addEventListener('focus', () => { }); frameworkProvider.get(LifecycleService).applicationStart(); +events?.applicationMenu.openAboutPageInSettingModal(() => + frameworkProvider.get(GlobalDialogService).open('setting', { + activeTab: 'about', + }) +); +events?.applicationMenu.onNewPageAction(() => { + const currentWorkspaceId = frameworkProvider + .get(GlobalContextService) + .globalContext.workspaceId.get(); + const workspacesService = frameworkProvider.get(WorkspacesService); + const workspaceMetadata = currentWorkspaceId + ? workspacesService.list.workspace$(currentWorkspaceId).value + : null; + const workspaceRef = + workspaceMetadata && + workspacesService.open({ metadata: workspaceMetadata }); + if (!workspaceRef) { + return; + } + const { workspace, dispose } = workspaceRef; + const editorSettingService = frameworkProvider.get(EditorSettingService); + const docsService = workspace.scope.get(DocsService); + const editorSetting = editorSettingService.editorSetting; + + const docProps = { + note: editorSetting.get('affine:note'), + }; + apis?.ui + .isActiveTab() + .then(isActive => { + if (!isActive) { + return; + } + const page = docsService.createDoc({ docProps }); + workspace.scope.get(WorkbenchService).workbench.openDoc(page.id); + }) + .catch(err => { + console.error(err); + }); + + dispose(); +}); + export function App() { return ( @@ -119,11 +168,8 @@ export function App() { - - - } + fallbackElement={} router={router} future={future} /> diff --git a/packages/frontend/apps/electron/renderer/shell/app.css.ts b/packages/frontend/apps/electron/renderer/shell/app.css.ts index 7439f0985a..fa8f33ce42 100644 --- a/packages/frontend/apps/electron/renderer/shell/app.css.ts +++ b/packages/frontend/apps/electron/renderer/shell/app.css.ts @@ -16,18 +16,17 @@ export const root = style({ }, }); +export const body = style({ + flex: 1, + paddingTop: 52, +}); + export const appTabsHeader = style({ zIndex: 1, position: 'absolute', top: 0, }); -export const fallbackRoot = style({ - width: '100%', - height: '100%', - paddingTop: 52, -}); - export const splitViewFallback = style({ width: '100%', height: '100%', diff --git a/packages/frontend/apps/electron/renderer/shell/app.tsx b/packages/frontend/apps/electron/renderer/shell/app.tsx index 58f44fd821..5ceb79b082 100644 --- a/packages/frontend/apps/electron/renderer/shell/app.tsx +++ b/packages/frontend/apps/electron/renderer/shell/app.tsx @@ -1,7 +1,7 @@ -import { ShellAppFallback } from '@affine/core/components/affine/app-container'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { ThemeProvider } from '@affine/core/components/theme-provider'; import { configureAppSidebarModule } from '@affine/core/modules/app-sidebar'; +import { ShellAppSidebarFallback } from '@affine/core/modules/app-sidebar/views'; import { AppTabsHeader, configureAppTabsHeaderModule, @@ -10,7 +10,6 @@ import { configureDesktopApiModule } from '@affine/core/modules/desktop-api'; import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n'; import { configureElectronStateStorageImpls } from '@affine/core/modules/storage'; import { configureAppThemeModule } from '@affine/core/modules/theme'; -import { SplitViewFallback } from '@affine/core/modules/workbench/view/split-view/split-view'; import { configureGlobalStorageModule, Framework, @@ -41,9 +40,9 @@ export function App() {
- - - +
+ +
diff --git a/packages/frontend/apps/electron/renderer/theme-sync.ts b/packages/frontend/apps/electron/renderer/theme-sync.ts index 30a43c500d..77029267a4 100644 --- a/packages/frontend/apps/electron/renderer/theme-sync.ts +++ b/packages/frontend/apps/electron/renderer/theme-sync.ts @@ -1,4 +1,4 @@ -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; +import { DesktopApiService } from '@affine/core/modules/desktop-api'; import { useService } from '@toeverything/infra'; import { useTheme } from 'next-themes'; import { useRef } from 'react'; diff --git a/packages/frontend/apps/electron/src/helper/dialog/dialog.ts b/packages/frontend/apps/electron/src/helper/dialog/dialog.ts index 7a6fc9d3fa..ff8e385e20 100644 --- a/packages/frontend/apps/electron/src/helper/dialog/dialog.ts +++ b/packages/frontend/apps/electron/src/helper/dialog/dialog.ts @@ -9,11 +9,8 @@ import { storeWorkspaceMeta } from '../workspace'; import { getWorkspaceDBPath, getWorkspacesBasePath } from '../workspace/meta'; export type ErrorMessage = - | 'DB_FILE_ALREADY_LOADED' | 'DB_FILE_PATH_INVALID' | 'DB_FILE_INVALID' - | 'DB_FILE_MIGRATION_FAILED' - | 'FILE_ALREADY_EXISTS' | 'UNKNOWN_ERROR'; export interface LoadDBFileResult { diff --git a/packages/frontend/apps/electron/src/main/export/index.ts b/packages/frontend/apps/electron/src/main/export/index.ts deleted file mode 100644 index 57fadbe7e9..0000000000 --- a/packages/frontend/apps/electron/src/main/export/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { NamespaceHandlers } from '../type'; -import { savePDFFileAs } from './pdf'; - -export const exportHandlers = { - savePDFFileAs: async (_, title: string) => { - return savePDFFileAs(title); - }, -} satisfies NamespaceHandlers; - -export * from './pdf'; diff --git a/packages/frontend/apps/electron/src/main/export/pdf.ts b/packages/frontend/apps/electron/src/main/export/pdf.ts deleted file mode 100644 index 29d2959a3a..0000000000 --- a/packages/frontend/apps/electron/src/main/export/pdf.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { BrowserWindow, dialog } from 'electron'; -import fs from 'fs-extra'; - -import { logger } from '../logger'; -import type { ErrorMessage } from './utils'; -import { getFakedResult } from './utils'; - -export interface SavePDFFileResult { - filePath?: string; - canceled?: boolean; - error?: ErrorMessage; -} - -/** - * This function is called when the user clicks the "Export to PDF" button in the electron. - * - * It will just copy the file to the given path - */ -export async function savePDFFileAs( - pageTitle: string -): Promise { - try { - const ret = - getFakedResult() ?? - (await dialog.showSaveDialog({ - properties: ['showOverwriteConfirmation'], - title: 'Save PDF', - showsTagField: false, - buttonLabel: 'Save', - defaultPath: `${pageTitle}.pdf`, - message: 'Save Page as a PDF file', - filters: [{ name: 'PDF Files', extensions: ['pdf'] }], - })); - const filePath = ret.filePath; - if (ret.canceled || !filePath) { - return { - canceled: true, - }; - } - - await BrowserWindow.getFocusedWindow() - ?.webContents.printToPDF({ - pageSize: 'A4', - margins: { - bottom: 0.5, - }, - printBackground: true, - landscape: false, - displayHeaderFooter: true, - headerTemplate: '
', - footerTemplate: getFootTemple(), - }) - .then(data => { - fs.writeFile(filePath, data, error => { - if (error) throw error; - logger.log(`Wrote PDF successfully to ${filePath}`); - }); - }); - return { filePath }; - } catch (err) { - logger.error('savePDFFileAs', err); - return { - error: 'UNKNOWN_ERROR', - }; - } -} - -function getFootTemple(): string { - const logo = ` - - - - - - - - - `; - - const footerTemp = ` - - `; - - return footerTemp; -} diff --git a/packages/frontend/apps/electron/src/main/export/utils.ts b/packages/frontend/apps/electron/src/main/export/utils.ts deleted file mode 100644 index ebd1336a4d..0000000000 --- a/packages/frontend/apps/electron/src/main/export/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -// provide a backdoor to set dialog path for testing in playwright -interface FakeDialogResult { - canceled?: boolean; - filePath?: string; - filePaths?: string[]; -} -// result will be used in the next call to showOpenDialog -// if it is being read once, it will be reset to undefined -let fakeDialogResult: FakeDialogResult | undefined = undefined; -export function getFakedResult() { - const result = fakeDialogResult; - fakeDialogResult = undefined; - return result; -} - -export function setFakeDialogResult(result: FakeDialogResult | undefined) { - fakeDialogResult = result; - // for convenience, we will fill filePaths with filePath if it is not set - if (result?.filePaths === undefined && result?.filePath !== undefined) { - result.filePaths = [result.filePath]; - } -} -const ErrorMessages = ['FILE_ALREADY_EXISTS', 'UNKNOWN_ERROR'] as const; -export type ErrorMessage = (typeof ErrorMessages)[number]; diff --git a/packages/frontend/apps/electron/src/main/handlers.ts b/packages/frontend/apps/electron/src/main/handlers.ts index 8e9f9ef052..e67b39c64f 100644 --- a/packages/frontend/apps/electron/src/main/handlers.ts +++ b/packages/frontend/apps/electron/src/main/handlers.ts @@ -3,7 +3,6 @@ import { ipcMain } from 'electron'; import { AFFINE_API_CHANNEL_NAME } from '../shared/type'; import { clipboardHandlers } from './clipboard'; import { configStorageHandlers } from './config-storage'; -import { exportHandlers } from './export'; import { findInPageHandlers } from './find-in-page'; import { getLogFilePath, logger, revealLogFile } from './logger'; import { sharedStorageHandlers } from './shared-storage'; @@ -24,7 +23,6 @@ export const allHandlers = { debug: debugHandlers, ui: uiHandlers, clipboard: clipboardHandlers, - export: exportHandlers, updater: updaterHandlers, configStorage: configStorageHandlers, findInPage: findInPageHandlers, diff --git a/packages/frontend/apps/ios/src/app.tsx b/packages/frontend/apps/ios/src/app.tsx index eb3ef91e1b..55ed00565c 100644 --- a/packages/frontend/apps/ios/src/app.tsx +++ b/packages/frontend/apps/ios/src/app.tsx @@ -1,5 +1,4 @@ import { AffineContext } from '@affine/core/components/context'; -import { Telemetry } from '@affine/core/components/telemetry'; import { AppFallback } from '@affine/core/mobile/components'; import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; @@ -123,7 +122,6 @@ export function App() { - } router={router} diff --git a/packages/frontend/apps/mobile/src/app.tsx b/packages/frontend/apps/mobile/src/app.tsx index 762907f13a..fb3010d245 100644 --- a/packages/frontend/apps/mobile/src/app.tsx +++ b/packages/frontend/apps/mobile/src/app.tsx @@ -1,5 +1,4 @@ import { AffineContext } from '@affine/core/components/context'; -import { Telemetry } from '@affine/core/components/telemetry'; import { AppFallback } from '@affine/core/mobile/components'; import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; @@ -67,7 +66,6 @@ export function App() { - } router={router} diff --git a/packages/frontend/apps/web/src/app.tsx b/packages/frontend/apps/web/src/app.tsx index 0f8bc76c45..7909b8ac89 100644 --- a/packages/frontend/apps/web/src/app.tsx +++ b/packages/frontend/apps/web/src/app.tsx @@ -1,16 +1,10 @@ -import { GlobalLoading } from '@affine/component/global-loading'; -import { AppFallback } from '@affine/core/components/affine/app-container'; import { AffineContext } from '@affine/core/components/context'; -import { Telemetry } from '@affine/core/components/telemetry'; +import { AppContainer } from '@affine/core/desktop/components/app-container'; import { router } from '@affine/core/desktop/router'; import { configureCommonModules } from '@affine/core/modules'; import { I18nProvider } from '@affine/core/modules/i18n'; -import { - configureOpenInApp, - OpenInAppGuard, -} from '@affine/core/modules/open-in-app'; +import { OpenInAppGuard } from '@affine/core/modules/open-in-app'; import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; -import { CustomThemeModifier } from '@affine/core/modules/theme-editor'; import { PopupWindowProvider } from '@affine/core/modules/url'; import { configureIndexedDBUserspaceStorageProvider } from '@affine/core/modules/userspace'; import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench'; @@ -42,7 +36,6 @@ configureLocalStorageStateStorageImpls(framework); configureBrowserWorkspaceFlavours(framework); configureIndexedDBWorkspaceEngineStorageProvider(framework); configureIndexedDBUserspaceStorageProvider(framework); -configureOpenInApp(framework); framework.impl(PopupWindowProvider, { open: (target: string) => { const targetUrl = new URL(target); @@ -77,12 +70,9 @@ export function App() { - - - } + fallbackElement={} router={router} future={future} /> diff --git a/packages/frontend/component/src/components/global-loading/index.tsx b/packages/frontend/component/src/components/global-loading/index.tsx index a205365129..7d4f53d12b 100644 --- a/packages/frontend/component/src/components/global-loading/index.tsx +++ b/packages/frontend/component/src/components/global-loading/index.tsx @@ -1,35 +1,5 @@ -import { useAtomValue } from 'jotai'; -import type { ReactNode } from 'react'; -import { useEffect, useState } from 'react'; - -import { Loading } from '../../ui/loading'; -import * as styles from './index.css'; -import { globalLoadingEventsAtom } from './index.jotai'; - export { type GlobalLoadingEvent, pushGlobalLoadingEventAtom, resolveGlobalLoadingEventAtom, } from './index.jotai'; - -export function GlobalLoading(): ReactNode { - const globalLoadingEvents = useAtomValue(globalLoadingEventsAtom); - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (globalLoadingEvents.length) { - setLoading(true); - } else { - setLoading(false); - } - }, [globalLoadingEvents]); - - if (!globalLoadingEvents.length) { - return null; - } - return ( -
- -
- ); -} diff --git a/packages/frontend/component/src/ui/modal/index.ts b/packages/frontend/component/src/ui/modal/index.ts index f1de4385bc..be56583f78 100644 --- a/packages/frontend/component/src/ui/modal/index.ts +++ b/packages/frontend/component/src/ui/modal/index.ts @@ -1,3 +1,4 @@ export * from './confirm-modal'; export * from './modal'; export * from './overlay-modal'; +export * from './prompt-modal'; diff --git a/packages/frontend/component/src/ui/modal/modal.tsx b/packages/frontend/component/src/ui/modal/modal.tsx index 1210244f84..50beae578c 100644 --- a/packages/frontend/component/src/ui/modal/modal.tsx +++ b/packages/frontend/component/src/ui/modal/modal.tsx @@ -229,6 +229,7 @@ export const ModalInner = forwardRef( styles.modalContentWrapper, contentWrapperClassName )} + data-mobile={BUILD_CONFIG.isMobileEdition ? '' : undefined} style={contentWrapperStyle} > ; + onConfirm?: ((text: string) => void) | ((text: string) => Promise); + onCancel?: () => void; + confirmText?: React.ReactNode; + cancelText?: React.ReactNode; + + label?: React.ReactNode; + defaultValue?: string; + required?: boolean; + cancelButtonOptions?: Omit; + + inputOptions?: Omit; + reverseFooter?: boolean; + /** + * Auto focus on confirm button when modal opened + * @default true + */ + autoFocusConfirm?: boolean; +} + +export const PromptModal = ({ + children, + confirmButtonOptions, + // FIXME: we need i18n + confirmText, + cancelText = 'Cancel', + cancelButtonOptions, + reverseFooter, + onConfirm, + onCancel, + label, + required = true, + inputOptions, + defaultValue, + width = 480, + autoFocusConfirm = true, + headerClassName, + descriptionClassName, + ...props +}: PromptModalProps) => { + const [value, setValue] = useState(defaultValue ?? ''); + const onConfirmClick = useCallback(() => { + Promise.resolve(onConfirm?.(value)) + .catch(err => { + console.error(err); + }) + .finally(() => { + setValue(''); + }); + }, [onConfirm, value]); + + const onKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + if (value) { + e.preventDefault(); + return; + } else { + e.currentTarget.blur(); + } + } + }, + [value] + ); + + return ( + { + e.stopPropagation(); + onCancel?.(); + }, + }} + width={width} + closeButtonOptions={{ + onClick: onCancel, + }} + headerClassName={clsx(styles.header, headerClassName)} + descriptionClassName={clsx(styles.description, descriptionClassName)} + {...props} + > +
{label}
+
+ +
+ {children ?
{children}
: null} +
+ + + + +
+
+ ); +}; + +interface OpenPromptModalOptions { + autoClose?: boolean; + onSuccess?: () => void; +} +interface PromptModalContextProps { + modalProps: PromptModalProps; + openPromptModal: ( + props?: PromptModalProps, + options?: OpenPromptModalOptions + ) => void; + closePromptModal: () => void; +} +const PromptModalContext = createContext({ + modalProps: { open: false }, + openPromptModal: () => {}, + closePromptModal: () => {}, +}); +export const PromptModalProvider = ({ children }: PropsWithChildren) => { + const [modalProps, setModalProps] = useState({ + open: false, + }); + + const setLoading = useCallback((value: boolean) => { + setModalProps(prev => ({ + ...prev, + confirmButtonOptions: { + ...prev.confirmButtonOptions, + loading: value, + }, + })); + }, []); + + const closePromptModal = useCallback(() => { + setModalProps({ open: false }); + }, []); + + const openPromptModal = useCallback( + (props?: PromptModalProps, options?: OpenPromptModalOptions) => { + const { autoClose = true, onSuccess } = options ?? {}; + if (!props) { + setModalProps({ open: true }); + return; + } + + const { onConfirm: _onConfirm, ...otherProps } = props; + + const onConfirm = (text: string) => { + setLoading(true); + return Promise.resolve(_onConfirm?.(text)) + .then(() => onSuccess?.()) + .catch(console.error) + .finally(() => setLoading(false)) + .finally(() => autoClose && closePromptModal()); + }; + setModalProps({ ...otherProps, onConfirm, open: true }); + }, + [closePromptModal, setLoading] + ); + + const onOpenChange = useCallback( + (open: boolean) => { + modalProps.onOpenChange?.(open); + setModalProps(props => ({ ...props, open })); + }, + [modalProps] + ); + + return ( + + {children} + {/* TODO(@catsjuice): multi-instance support(unnecessary for now) */} + + + ); +}; + +export const usePromptModal = () => { + const context = useContext(PromptModalContext); + if (!context) { + throw new Error( + 'useConfirmModal must be used within a ConfirmModalProvider' + ); + } + return { + openPromptModal: context.openPromptModal, + closePromptModal: context.closePromptModal, + }; +}; diff --git a/packages/frontend/component/src/ui/modal/styles.css.ts b/packages/frontend/component/src/ui/modal/styles.css.ts index 71c1d87d5e..740ea78f03 100644 --- a/packages/frontend/component/src/ui/modal/styles.css.ts +++ b/packages/frontend/component/src/ui/modal/styles.css.ts @@ -79,15 +79,12 @@ export const modalContentWrapper = style({ alignItems: 'center', justifyContent: 'center', zIndex: cssVar('zIndexModal'), - '@media': { - 'screen and (width <= 640px)': { - // todo: adjust animation + + selectors: { + '&[data-mobile]': { alignItems: 'flex-end', paddingBottom: 'env(safe-area-inset-bottom, 20px)', }, - }, - - selectors: { '&[data-full-screen="true"]': { padding: '0 !important', }, diff --git a/packages/frontend/core/src/commands/affine-creation.tsx b/packages/frontend/core/src/commands/affine-creation.tsx index f9c87abe4b..5cf4d66000 100644 --- a/packages/frontend/core/src/commands/affine-creation.tsx +++ b/packages/frontend/core/src/commands/affine-creation.tsx @@ -4,17 +4,17 @@ import type { DocMode } from '@blocksuite/affine/blocks'; import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc'; import type { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils'; -import type { CreateWorkspaceDialogService } from '../modules/create-workspace'; +import type { GlobalDialogService } from '../modules/dialogs'; import { registerAffineCommand } from './registry'; export function registerAffineCreationCommands({ pageHelper, t, - createWorkspaceDialogService, + globalDialogService, }: { t: ReturnType; pageHelper: ReturnType; - createWorkspaceDialogService: CreateWorkspaceDialogService; + globalDialogService: GlobalDialogService; }) { const unsubs: Array<() => void> = []; unsubs.push( @@ -62,7 +62,7 @@ export function registerAffineCreationCommands({ run() { track.$.cmdk.workspace.createWorkspace(); - createWorkspaceDialogService.dialog.open('new'); + globalDialogService.open('create-workspace', undefined); }, }) ); @@ -80,7 +80,7 @@ export function registerAffineCreationCommands({ control: 'import', }); - createWorkspaceDialogService.dialog.open('add'); + globalDialogService.open('import-workspace', undefined); }, }) ); diff --git a/packages/frontend/core/src/commands/affine-help.tsx b/packages/frontend/core/src/commands/affine-help.tsx index 1486c050d9..fcb67bdd84 100644 --- a/packages/frontend/core/src/commands/affine-help.tsx +++ b/packages/frontend/core/src/commands/affine-help.tsx @@ -1,20 +1,19 @@ import type { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { ContactWithUsIcon, NewIcon } from '@blocksuite/icons/rc'; -import type { createStore } from 'jotai'; -import { openSettingModalAtom } from '../components/atoms'; +import type { GlobalDialogService } from '../modules/dialogs'; import type { UrlService } from '../modules/url'; import { registerAffineCommand } from './registry'; export function registerAffineHelpCommands({ t, - store, urlService, + globalDialogService, }: { t: ReturnType; - store: ReturnType; urlService: UrlService; + globalDialogService: GlobalDialogService; }) { const unsubs: Array<() => void> = []; unsubs.push( @@ -37,8 +36,7 @@ export function registerAffineHelpCommands({ label: t['com.affine.cmdk.affine.contact-us'](), run() { track.$.cmdk.help.contactUs(); - store.set(openSettingModalAtom, { - open: true, + globalDialogService.open('setting', { activeTab: 'about', workspaceMetadata: null, }); diff --git a/packages/frontend/core/src/commands/affine-navigation.tsx b/packages/frontend/core/src/commands/affine-navigation.tsx index a7715a9608..de2a3f708d 100644 --- a/packages/frontend/core/src/commands/affine-navigation.tsx +++ b/packages/frontend/core/src/commands/affine-navigation.tsx @@ -4,11 +4,9 @@ import type { DocCollection } from '@blocksuite/affine/store'; import { ArrowRightBigIcon } from '@blocksuite/icons/rc'; import type { createStore } from 'jotai'; -import { - openSettingModalAtom, - openWorkspaceListModalAtom, -} from '../components/atoms'; +import { openWorkspaceListModalAtom } from '../components/atoms'; import type { useNavigateHelper } from '../components/hooks/use-navigate-helper'; +import type { GlobalDialogService } from '../modules/dialogs'; import { registerAffineCommand } from './registry'; export function registerAffineNavigationCommands({ @@ -16,11 +14,13 @@ export function registerAffineNavigationCommands({ store, docCollection, navigationHelper, + globalDialogService, }: { t: ReturnType; store: ReturnType; navigationHelper: ReturnType; docCollection: DocCollection; + globalDialogService: GlobalDialogService; }) { const unsubs: Array<() => void> = []; unsubs.push( @@ -96,10 +96,9 @@ export function registerAffineNavigationCommands({ keyBinding: '$mod+,', run() { track.$.cmdk.settings.openSettings(); - store.set(openSettingModalAtom, s => ({ + globalDialogService.open('setting', { activeTab: 'appearance', - open: !s.open, - })); + }); }, }) ); @@ -112,10 +111,9 @@ export function registerAffineNavigationCommands({ label: t['com.affine.cmdk.affine.navigation.open-account-settings'](), run() { track.$.cmdk.settings.openSettings({ to: 'account' }); - store.set(openSettingModalAtom, s => ({ + globalDialogService.open('setting', { activeTab: 'account', - open: !s.open, - })); + }); }, }) ); diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx index ab0fc26e25..9d60e268b7 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx @@ -1,13 +1,12 @@ import { Button, FlexWrapper, notify } from '@affine/component'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { SubscriptionService } from '@affine/core/modules/cloud'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { EditorService } from '@affine/core/modules/editor'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { AiIcon } from '@blocksuite/icons/rc'; -import { useLiveData, useServices } from '@toeverything/infra'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; -import { useAtomValue, useSetAtom } from 'jotai'; import Lottie from 'lottie-react'; import { useTheme } from 'next-themes'; import { useCallback, useEffect, useMemo, useRef } from 'react'; @@ -51,24 +50,20 @@ export const AIOnboardingEdgeless = () => { const notifyId = useLiveData(edgelessNotifyId$); const generalAIOnboardingOpened = useLiveData(showAIOnboardingGeneral$); const aiSubscription = useLiveData(subscriptionService.subscription.ai$); - const settingModalOpen = useAtomValue(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const timeoutRef = useRef>(); - const setSettingModal = useSetAtom(openSettingModalAtom); - const mode = useLiveData(editorService.editor.mode$); const goToPricingPlans = useCallback(() => { track.$.aiOnboarding.dialog.viewPlans(); - setSettingModal({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'aiPricingPlan', }); - }, [setSettingModal]); + }, [globalDialogService]); useEffect(() => { - if (settingModalOpen.open) return; if (generalAIOnboardingOpened) return; if (notifyId) return; if (mode !== 'edgeless') return; @@ -128,7 +123,6 @@ export const AIOnboardingEdgeless = () => { goToPricingPlans, mode, notifyId, - settingModalOpen, t, ]); diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx index 7c2195a60b..1a9f9d02a1 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx @@ -1,12 +1,11 @@ import { Button, IconButton, Modal } from '@affine/component'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { useBlurRoot } from '@affine/core/components/hooks/use-blur-root'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { Trans, useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc'; -import { useLiveData, useServices } from '@toeverything/infra'; -import { useAtom } from 'jotai'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import type { ReactNode } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -96,8 +95,8 @@ export const AIOnboardingGeneral = () => { const aiSubscription = useLiveData(subscriptionService.subscription.ai$); const [index, setIndex] = useState(0); const list = useMemo(() => getPlayList(t), [t]); - const [settingModal, setSettingModal] = useAtom(openSettingModalAtom); - const readyToOpen = isLoggedIn && !settingModal.open; + const globalDialogService = useService(GlobalDialogService); + const readyToOpen = isLoggedIn; useBlurRoot(open && readyToOpen); const isFirst = index === 0; @@ -111,14 +110,13 @@ export const AIOnboardingGeneral = () => { toggleGeneralAIOnboarding(false); }, []); const goToPricingPlans = useCallback(() => { - setSettingModal({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'aiPricingPlan', }); track.$.aiOnboarding.dialog.viewPlans(); closeAndDismiss(); - }, [closeAndDismiss, setSettingModal]); + }, [closeAndDismiss, globalDialogService]); const onPrev = useCallback(() => { setIndex(i => Math.max(0, i - 1)); }, []); diff --git a/packages/frontend/core/src/components/affine/app-container.css.ts b/packages/frontend/core/src/components/affine/app-container.css.ts deleted file mode 100644 index 18aa43aa19..0000000000 --- a/packages/frontend/core/src/components/affine/app-container.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css'; - -export const electronFallback = style({ - paddingTop: 52, -}); diff --git a/packages/frontend/core/src/components/affine/app-container.tsx b/packages/frontend/core/src/components/affine/app-container.tsx deleted file mode 100644 index adc2a2dac1..0000000000 --- a/packages/frontend/core/src/components/affine/app-container.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { - AppSidebarFallback, - ShellAppSidebarFallback, -} from '@affine/core/modules/app-sidebar/views'; -import clsx from 'clsx'; -import type { PropsWithChildren, ReactElement } from 'react'; - -import { useAppSettingHelper } from '../../components/hooks/affine/use-app-setting-helper'; -import type { WorkspaceRootProps } from '../workspace'; -import { - AppContainer as AppContainerWithoutSettings, - MainContainerFallback, -} from '../workspace'; -import * as styles from './app-container.css'; - -export const AppContainer = (props: WorkspaceRootProps) => { - const { appSettings } = useAppSettingHelper(); - - return ( - - ); -}; - -export const AppFallback = ({ - className, - children, -}: PropsWithChildren<{ - className?: string; -}>): ReactElement => { - return ( - - - {children} - - ); -}; - -export const ShellAppFallback = ({ - className, - children, -}: PropsWithChildren<{ - className?: string; -}>): ReactElement => { - return ( - - - {children} - - ); -}; diff --git a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx index ed6f4f89a2..a6480f5112 100644 --- a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx +++ b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx @@ -1,16 +1,15 @@ import { Tooltip } from '@affine/component/ui/tooltip'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; -import { useLiveData, useServices } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; import { useEffect } from 'react'; import { ServerConfigService, SubscriptionService, } from '../../../modules/cloud'; -import { openSettingModalAtom } from '../../atoms'; import * as styles from './style.css'; export const UserPlanButton = () => { @@ -35,14 +34,13 @@ export const UserPlanButton = () => { subscriptionService.subscription.revalidate(); }, [subscriptionService]); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const handleClick = useCatchEventCallback(() => { - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - }, [setSettingModalAtom]); + }, [globalDialogService]); const t = useI18n(); diff --git a/packages/frontend/core/src/components/affine/empty/collection-detail.tsx b/packages/frontend/core/src/components/affine/empty/collection-detail.tsx index 02cd8f74c8..041d8e0544 100644 --- a/packages/frontend/core/src/components/affine/empty/collection-detail.tsx +++ b/packages/frontend/core/src/components/affine/empty/collection-detail.tsx @@ -1,11 +1,10 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; -import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { AllDocsIcon, FilterIcon } from '@blocksuite/icons/rc'; import { useService } from '@toeverything/infra'; +import { useCallback } from 'react'; -import { useEditCollection } from '../../page-list'; import { ActionButton } from './action-button'; import collectionDetailDark from './assets/collection-detail.dark.png'; import collectionDetailLight from './assets/collection-detail.light.png'; @@ -41,18 +40,21 @@ export const EmptyCollectionDetail = ({ const Actions = ({ collection }: { collection: Collection }) => { const t = useI18n(); - const collectionService = useService(CollectionService); - const { open } = useEditCollection(); + const workspaceDialogService = useService(WorkspaceDialogService); - const openAddDocs = useAsyncCallback(async () => { - const ret = await open({ ...collection }, 'page'); - collectionService.updateCollection(ret.id, () => ret); - }, [open, collection, collectionService]); + const openAddDocs = useCallback(() => { + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + mode: 'page', + }); + }, [collection, workspaceDialogService]); - const openAddRules = useAsyncCallback(async () => { - const ret = await open({ ...collection }, 'rule'); - collectionService.updateCollection(ret.id, () => ret); - }, [collection, open, collectionService]); + const openAddRules = useCallback(() => { + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + mode: 'rule', + }); + }, [collection, workspaceDialogService]); return (
diff --git a/packages/frontend/core/src/components/affine/empty/collections.tsx b/packages/frontend/core/src/components/affine/empty/collections.tsx index c566df2191..8ae47d7a93 100644 --- a/packages/frontend/core/src/components/affine/empty/collections.tsx +++ b/packages/frontend/core/src/components/affine/empty/collections.tsx @@ -1,3 +1,4 @@ +import { usePromptModal } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { CollectionService } from '@affine/core/modules/collection'; import { useI18n } from '@affine/i18n'; @@ -6,7 +7,7 @@ import { useService, WorkspaceService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { useCallback } from 'react'; -import { createEmptyCollection, useEditCollectionName } from '../../page-list'; +import { createEmptyCollection } from '../../page-list'; import { ActionButton } from './action-button'; import collectionListDark from './assets/collection-list.dark.png'; import collectionListLight from './assets/collection-list.light.png'; @@ -19,24 +20,36 @@ export const EmptyCollections = (props: UniversalEmptyProps) => { const currentWorkspace = useService(WorkspaceService).workspace; const navigateHelper = useNavigateHelper(); - const { open } = useEditCollectionName({ - title: t['com.affine.editCollection.createCollection'](), - showTips: true, - }); + const { openPromptModal } = usePromptModal(); const showAction = true; const handleCreateCollection = useCallback(() => { - open('') - .then(name => { + openPromptModal({ + title: t['com.affine.editCollection.saveCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + children: t['com.affine.editCollectionName.createTips'](), + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { const id = nanoid(); collectionService.addCollection(createEmptyCollection(id, { name })); navigateHelper.jumpToCollection(currentWorkspace.id, id); - }) - .catch(err => { - console.error(err); - }); - }, [collectionService, currentWorkspace, navigateHelper, open]); + }, + }); + }, [ + collectionService, + currentWorkspace.id, + navigateHelper, + openPromptModal, + t, + ]); return ( { - return ( - - - - ); -}; diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index 1c07238578..71ab00ed6d 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -2,8 +2,8 @@ import { Loading, Scrollable } from '@affine/component'; import { EditorLoading } from '@affine/component/page-detail-skeleton'; import { Button, IconButton } from '@affine/component/ui/button'; import { Modal, useConfirmModal } from '@affine/component/ui/modal'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { useDocCollectionPageTitle } from '@affine/core/components/hooks/use-block-suite-workspace-page-title'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { EditorService } from '@affine/core/modules/editor'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; @@ -18,7 +18,7 @@ import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; import type { DialogContentProps } from '@radix-ui/react-dialog'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; -import { atom, useAtom, useSetAtom } from 'jotai'; +import { atom, useAtom } from 'jotai'; import type { PropsWithChildren } from 'react'; import { Fragment, @@ -188,21 +188,19 @@ const PlanPrompt = () => { permissionService.permission.revalidate(); }, [permissionService]); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); const [planPromptClosed, setPlanPromptClosed] = useAtom(planPromptClosedAtom); - + const globalDialogService = useService(GlobalDialogService); const closeFreePlanPrompt = useCallback(() => { setPlanPromptClosed(true); }, [setPlanPromptClosed]); const onClickUpgrade = useCallback(() => { - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); track.$.docHistory.$.viewPlans(); - }, [setSettingModalAtom]); + }, [globalDialogService]); const t = useI18n(); diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx index cee4528963..ba96d94793 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx @@ -1,16 +1,14 @@ import { ConfirmModal } from '@affine/component/ui/modal'; -import { - openQuotaModalAtom, - openSettingModalAtom, -} from '@affine/core/components/atoms'; +import { openQuotaModalAtom } from '@affine/core/components/atoms'; import { UserQuotaService } from '@affine/core/modules/cloud'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import bytes from 'bytes'; -import { useAtom, useSetAtom } from 'jotai'; +import { useAtom } from 'jotai'; import { useCallback, useEffect, useMemo } from 'react'; export const CloudQuotaModal = () => { @@ -45,17 +43,16 @@ export const CloudQuotaModal = () => { return isOwner && userQuota?.name === 'free'; }, [isOwner, userQuota]); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const handleUpgradeConfirm = useCallback(() => { - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); track.$.paywall.storage.viewPlans(); setOpen(false); - }, [setOpen, setSettingModalAtom]); + }, [globalDialogService, setOpen]); const description = useMemo(() => { if (userQuota && isFreePlanOwner) { diff --git a/packages/frontend/core/src/components/affine/setting-modal/atoms.ts b/packages/frontend/core/src/components/affine/setting-modal/atoms.ts deleted file mode 100644 index a740fac2a2..0000000000 --- a/packages/frontend/core/src/components/affine/setting-modal/atoms.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { atom } from 'jotai'; - -export const settingModalScrollContainerAtom = atom(null); diff --git a/packages/frontend/core/src/components/affine/setting-modal/types.ts b/packages/frontend/core/src/components/affine/setting-modal/types.ts deleted file mode 100644 index 06f9f8b06d..0000000000 --- a/packages/frontend/core/src/components/affine/setting-modal/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const GeneralSettingKeys = [ - 'shortcuts', - 'appearance', - 'about', - 'plans', - 'billing', - 'experimental-features', - 'editor', -] as const; - -export const WorkspaceSubTabs = ['preference', 'properties'] as const; - -export type GeneralSettingKey = (typeof GeneralSettingKeys)[number]; - -export type WorkspaceSubTab = (typeof WorkspaceSubTabs)[number]; - -export type ActiveTab = - | GeneralSettingKey - | 'account' - | `workspace:${WorkspaceSubTab}`; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx deleted file mode 100644 index 34ae66ce7a..0000000000 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { WorkspaceMetadata } from '@toeverything/infra'; - -import type { WorkspaceSubTab } from '../types'; -import { WorkspaceSettingDetail } from './new-workspace-setting-detail'; -import { WorkspaceSettingProperties } from './properties'; - -export const WorkspaceSetting = ({ - workspaceMetadata, - subTab, -}: { - workspaceMetadata: WorkspaceMetadata; - subTab: WorkspaceSubTab; -}) => { - switch (subTab) { - case 'preference': - return ; - case 'properties': - return ( - - ); - } -}; diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx index 4c7d312e93..10cbdd4d45 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx @@ -1,13 +1,13 @@ import { notify, Skeleton } from '@affine/component'; import { Button } from '@affine/component/ui/button'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { getSelectedNodes, useSharingUrl, } from '@affine/core/components/hooks/affine/use-share-url'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { ServerConfigService } from '@affine/core/modules/cloud'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { EditorService } from '@affine/core/modules/editor'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { ShareInfoService } from '@affine/core/modules/share-doc'; @@ -28,7 +28,6 @@ import { } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; -import { useSetAtom } from 'jotai'; import { Suspense, useCallback, useEffect, useMemo } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; @@ -83,15 +82,14 @@ export const AFFiNESharePage = (props: ShareMenuProps) => { const permissionService = useService(WorkspacePermissionService); const isOwner = useLiveData(permissionService.permission.isOwner$); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const onOpenWorkspaceSettings = useCallback(() => { - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'workspace:preference', workspaceMetadata: props.workspaceMetadata, }); - }, [props.workspaceMetadata, setSettingModalAtom]); + }, [globalDialogService, props.workspaceMetadata]); const onClickAnyoneReadOnlyShare = useAsyncCallback(async () => { if (isSharedPage) { diff --git a/packages/frontend/core/src/components/affine/subscription-landing/notify.tsx b/packages/frontend/core/src/components/affine/subscription-landing/notify.tsx index 4f3d990400..973349aff2 100644 --- a/packages/frontend/core/src/components/affine/subscription-landing/notify.tsx +++ b/packages/frontend/core/src/components/affine/subscription-landing/notify.tsx @@ -49,47 +49,6 @@ const SubscriptionChangedNotifyFooter = ({ ); }; -export const useUpgradeNotify = () => { - const t = useI18n(); - const prevNotifyIdRef = useRef(null); - - return useCallback( - (link: string) => { - prevNotifyIdRef.current && notify.dismiss(prevNotifyIdRef.current); - const id = notify( - { - title: ( - - {t['com.affine.payment.upgrade-success-notify.title']()} - - ), - message: t['com.affine.payment.upgrade-success-notify.content'](), - alignMessage: 'title', - icon: null, - footer: ( - notify.dismiss(id)} - onConfirm={() => notify.dismiss(id)} - /> - ), - }, - { duration: 24 * 60 * 60 * 1000 } - ); - prevNotifyIdRef.current = id; - }, - [t] - ); -}; - export const useDowngradeNotify = () => { const t = useI18n(); const prevNotifyIdRef = useRef(null); diff --git a/packages/frontend/core/src/components/atoms/index.ts b/packages/frontend/core/src/components/atoms/index.ts index c018dd4f11..00011f2b44 100644 --- a/packages/frontend/core/src/components/atoms/index.ts +++ b/packages/frontend/core/src/components/atoms/index.ts @@ -1,40 +1,13 @@ import { atom } from 'jotai'; -import type { SettingProps } from '../affine/setting-modal'; -import type { ActiveTab } from '../affine/setting-modal/types'; // modal atoms export const openWorkspacesModalAtom = atom(false); /** * @deprecated use `useSignOut` hook instated */ -export const openSignOutModalAtom = atom(false); export const openQuotaModalAtom = atom(false); -export const openStarAFFiNEModalAtom = atom(false); -export const openIssueFeedbackModalAtom = atom(false); -export const openHistoryTipsModalAtom = atom(false); - export const rightSidebarWidthAtom = atom(320); -export type PlansScrollAnchor = - | 'aiPricingPlan' - | 'cloudPricingPlan' - | 'lifetimePricingPlan'; -export type SettingAtom = { - open: boolean; - workspaceMetadata?: SettingProps['workspaceMetadata']; -} & ( - | { - activeTab: 'plans'; - scrollAnchor?: PlansScrollAnchor; - } - | { activeTab: Exclude } -); - -export const openSettingModalAtom = atom({ - activeTab: 'appearance', - open: false, -}); - export const openImportModalAtom = atom(false); export type AuthAtomData = diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx index 57d2e5c3b4..c1bbe7f0cb 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx @@ -1,12 +1,11 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis'; -import { authAtom, openSettingModalAtom } from '@affine/core/components/atoms'; +import { authAtom } from '@affine/core/components/atoms'; import { getBaseUrl, type getCopilotHistoriesQuery, type RequestOptions, } from '@affine/graphql'; -import { track } from '@affine/track'; import { UnauthorizedError } from '@blocksuite/affine/blocks'; import { assertExists } from '@blocksuite/affine/global/utils'; import { getCurrentStore } from '@toeverything/infra'; @@ -463,14 +462,6 @@ Could you make a new website based on these notes and send back just the html fi return forkCopilotSession(options); }); - AIProvider.slots.requestUpgradePlan.on(() => { - getCurrentStore().set(openSettingModalAtom, { - activeTab: 'billing', - open: true, - }); - track.$.paywall.aiAction.viewPlans(); - }); - AIProvider.slots.requestLogin.on(() => { getCurrentStore().set(authAtom, s => ({ ...s, diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/info/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/info/index.tsx index 55936d8afc..dad89e4f66 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/info/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/info/index.tsx @@ -1,5 +1,5 @@ import { IconButton } from '@affine/component'; -import { DocInfoService } from '@affine/core/modules/doc-info'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { InformationIcon } from '@blocksuite/icons/rc'; @@ -7,13 +7,13 @@ import { useService } from '@toeverything/infra'; import { useCallback } from 'react'; export const InfoButton = ({ docId }: { docId: string }) => { - const modal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const t = useI18n(); const onOpenInfoModal = useCallback(() => { track.$.header.actions.openDocInfo(); - modal.open(docId); - }, [docId, modal]); + workspaceDialogService.open('doc-info', { docId }); + }, [docId, workspaceDialogService]); return ( { +export const HistoryTipsModal = ({ + open, + setOpen, +}: { + open: boolean; + setOpen: (open: boolean) => void; +}) => { const t = useI18n(); const currentWorkspace = useService(WorkspaceService).workspace; - const [open, setOpen] = useAtom(openHistoryTipsModalAtom); const confirmEnableCloud = useEnableCloud(); const handleConfirm = useCallback(() => { diff --git a/packages/frontend/core/src/components/affine/history-tips-modal/top-svg.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/top-svg.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/history-tips-modal/top-svg.tsx rename to packages/frontend/core/src/components/blocksuite/block-suite-header/menu/history-tips-modal/top-svg.tsx diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx index 6530ebe82f..a7f8003d4a 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx @@ -1,4 +1,4 @@ -import { notify } from '@affine/component'; +import { notify, useConfirmModal } from '@affine/component'; import { Menu, MenuItem, @@ -7,14 +7,9 @@ import { } from '@affine/component/ui/menu'; import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal'; import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu'; -import { - openHistoryTipsModalAtom, - openImportModalAtom, -} from '@affine/core/components/atoms'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page'; -import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { Export, @@ -23,7 +18,10 @@ import { } from '@affine/core/components/page-list'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive'; -import { DocInfoService } from '@affine/core/modules/doc-info'; +import { + GlobalDialogService, + WorkspaceDialogService, +} from '@affine/core/modules/dialogs'; import { EditorService } from '@affine/core/modules/editor'; import { OpenInAppService } from '@affine/core/modules/open-in-app/services'; import { WorkbenchService } from '@affine/core/modules/workbench'; @@ -55,11 +53,11 @@ import { useServiceOptional, WorkspaceService, } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { useCallback, useState } from 'react'; import { HeaderDropDownButton } from '../../../pure/header-drop-down-button'; import { useFavorite } from '../favorite'; +import { HistoryTipsModal } from './history-tips-modal'; type PageMenuProps = { rename?: () => void; @@ -81,6 +79,7 @@ export const PageHeaderMenuButton = ({ const workspace = useService(WorkspaceService).workspace; + const globalDialogService = useService(GlobalDialogService); const editorService = useService(EditorService); const isInTrash = useLiveData( editorService.editor.doc.meta$.map(meta => meta.trash) @@ -98,7 +97,6 @@ export const PageHeaderMenuButton = ({ const { favorite, toggleFavorite } = useFavorite(pageId); const { duplicate } = useBlockSuiteMetaHelper(); - const { setTrashModal } = useTrashModalHelper(); const [isEditing, setEditing] = useState(!page.readonly); const { setDocReadonly } = useDocMetaHelper(); @@ -122,8 +120,7 @@ export const PageHeaderMenuButton = ({ }, [openSidePanel]); const [historyModalOpen, setHistoryModalOpen] = useState(false); - const setOpenHistoryTipsModal = useSetAtom(openHistoryTipsModalAtom); - const setOpenImportModalAtom = useSetAtom(openImportModalAtom); + const [openHistoryTipsModal, setOpenHistoryTipsModal] = useState(false); const openHistoryModal = useCallback(() => { track.$.header.history.open(); @@ -133,11 +130,11 @@ export const PageHeaderMenuButton = ({ return setOpenHistoryTipsModal(true); }, [setOpenHistoryTipsModal, workspace.flavour]); - const docInfoModal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const openInfoModal = useCallback(() => { track.$.header.pageInfo.open(); - docInfoModal.open(pageId); - }, [docInfoModal, pageId]); + workspaceDialogService.open('doc-info', { docId: pageId }); + }, [workspaceDialogService, pageId]); const handleOpenInNewTab = useCallback(() => { workbench.openDoc(pageId, { @@ -151,14 +148,22 @@ export const PageHeaderMenuButton = ({ }); }, [pageId, workbench]); + const { openConfirmModal } = useConfirmModal(); + const handleOpenTrashModal = useCallback(() => { track.$.header.docOptions.deleteDoc(); - setTrashModal({ - open: true, - pageIds: [pageId], - pageTitles: [editorService.editor.doc.meta$.value.title ?? ''], + openConfirmModal({ + title: t['com.affine.moveToTrash.confirmModal.title'](), + description: t['com.affine.moveToTrash.confirmModal.description']({ + title: editorService.editor.doc.title$.value || t['Untitled'](), + }), + cancelText: t['com.affine.confirmModal.button.cancel'](), + confirmText: t.Delete(), + onConfirm: () => { + editorService.editor.doc.moveToTrash(); + }, }); - }, [editorService, pageId, setTrashModal]); + }, [editorService.editor.doc, openConfirmModal, t]); const handleRename = useCallback(() => { rename?.(); @@ -201,8 +206,8 @@ export const PageHeaderMenuButton = ({ const handleOpenImportModal = useCallback(() => { track.$.header.importModal.open(); - setOpenImportModalAtom(true); - }, [setOpenImportModalAtom]); + globalDialogService.open('import', undefined); + }, [globalDialogService]); const handleShareMenuOpenChange = useCallback((open: boolean) => { if (open) { @@ -411,6 +416,10 @@ export const PageHeaderMenuButton = ({ onOpenChange={setHistoryModalOpen} /> ) : null} + ); }; diff --git a/packages/frontend/core/src/components/context/index.tsx b/packages/frontend/core/src/components/context/index.tsx index 0551d414ff..2e9bec4ffd 100644 --- a/packages/frontend/core/src/components/context/index.tsx +++ b/packages/frontend/core/src/components/context/index.tsx @@ -1,4 +1,4 @@ -import { ConfirmModalProvider } from '@affine/component'; +import { ConfirmModalProvider, PromptModalProvider } from '@affine/component'; import { ProviderComposer } from '@affine/component/provider-composer'; import { ThemeProvider } from '@affine/core/components/theme-provider'; import type { createStore } from 'jotai'; @@ -19,6 +19,7 @@ export function AffineContext(props: AffineContextProps) { , , , + , ].filter(Boolean), [props.store] )} diff --git a/packages/frontend/core/src/components/doc-properties/index.ts b/packages/frontend/core/src/components/doc-properties/index.ts index 3c5d854fc8..01643f0f57 100644 --- a/packages/frontend/core/src/components/doc-properties/index.ts +++ b/packages/frontend/core/src/components/doc-properties/index.ts @@ -1,2 +1 @@ -export * from './info-modal/info-modal'; export * from './table'; diff --git a/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx index bce9f0d93b..f3dc4adda4 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -1,9 +1,9 @@ -import { toast } from '@affine/component'; +import { toast, useConfirmModal } from '@affine/component'; import { PreconditionStrategy, registerAffineCommand, } from '@affine/core/commands'; -import { DocInfoService } from '@affine/core/modules/doc-info'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { Editor } from '@affine/core/modules/editor'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { WorkspaceFlavour } from '@affine/env/workspace'; @@ -22,7 +22,6 @@ import { useCallback, useEffect } from 'react'; import { pageHistoryModalAtom } from '../../../components/atoms/page-history'; import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper'; import { useExportPage } from './use-export-page'; -import { useTrashModalHelper } from './use-trash-modal-helper'; export function useRegisterBlocksuiteEditorCommands(editor: Editor) { const doc = useService(DocService).doc; @@ -36,7 +35,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { const trash = useLiveData(doc.trash$); const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom); - const docInfoModal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const openHistoryModal = useCallback(() => { setPageHistoryModalState(() => ({ @@ -46,22 +45,25 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { }, [docId, setPageHistoryModalState]); const openInfoModal = useCallback(() => { - docInfoModal.open(docId); - }, [docId, docInfoModal]); + workspaceDialogService.open('doc-info', { docId }); + }, [docId, workspaceDialogService]); const { duplicate } = useBlockSuiteMetaHelper(); const exportHandler = useExportPage(); - const { setTrashModal } = useTrashModalHelper(); - const onClickDelete = useCallback( - (title: string) => { - setTrashModal({ - open: true, - pageIds: [docId], - pageTitles: [title], - }); - }, - [docId, setTrashModal] - ); + const { openConfirmModal } = useConfirmModal(); + const onClickDelete = useCallback(() => { + openConfirmModal({ + title: t['com.affine.moveToTrash.confirmModal.title'](), + description: t['com.affine.moveToTrash.confirmModal.description']({ + title: doc.title$.value || t['Untitled'](), + }), + cancelText: t['com.affine.confirmModal.button.cancel'](), + confirmText: t.Delete(), + onConfirm: () => { + doc.moveToTrash(); + }, + }); + }, [doc, openConfirmModal, t]); const isCloudWorkspace = workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD; @@ -174,23 +176,6 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { }) ); - unsubs.push( - registerAffineCommand({ - id: `editor:${mode}-export-to-pdf`, - preconditionStrategy: () => mode === 'page' && !trash, - category: `editor:${mode}`, - icon: mode === 'page' ? : , - label: t['Export to PDF'](), - async run() { - track.$.cmdk.editor.export({ - type: 'pdf', - }); - - exportHandler('pdf'); - }, - }) - ); - unsubs.push( registerAffineCommand({ id: `editor:${mode}-export-to-html`, @@ -252,7 +237,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) { run() { track.$.cmdk.editor.deleteDoc(); - onClickDelete(doc.title$.value); + onClickDelete(); }, }) ); diff --git a/packages/frontend/core/src/components/hooks/affine/use-subscription-notify.tsx b/packages/frontend/core/src/components/hooks/affine/use-subscription-notify.tsx index e4e272db42..5c01cc724c 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-subscription-notify.tsx +++ b/packages/frontend/core/src/components/hooks/affine/use-subscription-notify.tsx @@ -1,16 +1,10 @@ -import { useUpgradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; -import { track } from '@affine/track'; import { nanoid } from 'nanoid'; -import { useCallback, useEffect } from 'react'; -import { useSearchParams } from 'react-router-dom'; import { type AuthAccountInfo } from '../../../modules/cloud'; const separator = '::'; const recoverSeparator = nanoid(); -const localStorageKey = 'subscription-succeed-info'; - const typeFormUrl = 'https://6dxre9ihosp.typeform.com/to'; const typeFormUpgradeId = 'mUMGGQS8'; const typeFormDowngradeId = 'RvD9AoRg'; @@ -69,80 +63,3 @@ export const generateSubscriptionCallbackLink = ( return `${baseUrl}?info=${encodeURIComponent(query)}`; }; - -/** - * Parse subscription callback query.info - * @returns - */ -export const parseSubscriptionCallbackLink = (query: string) => { - const [plan, recurring, id, email, rawName] = - decodeURIComponent(query).split(separator); - const name = rawName.replaceAll(recoverSeparator, separator); - - return { - plan: plan as SubscriptionPlan, - recurring: recurring as SubscriptionRecurring, - account: { - id, - email, - info: { - name, - }, - }, - }; -}; - -/** - * Hook to parse subscription callback link, and save to local storage and delete the query - */ -export const useSubscriptionNotifyWriter = () => { - const [searchParams] = useSearchParams(); - - useEffect(() => { - const query = searchParams.get('info'); - if (query) { - localStorage.setItem(localStorageKey, query); - searchParams.delete('info'); - } - }, [searchParams]); -}; - -/** - * Hook to read and parse subscription info from localStorage - */ -export const useSubscriptionNotifyReader = () => { - const upgradeNotify = useUpgradeNotify(); - - const readAndNotify = useCallback(() => { - const query = localStorage.getItem(localStorageKey); - if (!query) return; - - try { - const { plan, recurring, account } = parseSubscriptionCallbackLink(query); - const link = getUpgradeQuestionnaireLink({ - id: account.id, - email: account.email, - name: account.info?.name ?? '', - plan, - recurring, - }); - upgradeNotify(link); - localStorage.removeItem(localStorageKey); - - track.$.settingsPanel.plans.subscribe({ - plan, - recurring, - }); - } catch (err) { - console.error('Failed to parse subscription callback link', err); - } - }, [upgradeNotify]); - - useEffect(() => { - readAndNotify(); - window.addEventListener('focus', readAndNotify); - return () => { - window.removeEventListener('focus', readAndNotify); - }; - }, [readAndNotify]); -}; diff --git a/packages/frontend/core/src/components/hooks/affine/use-trash-modal-helper.ts b/packages/frontend/core/src/components/hooks/affine/use-trash-modal-helper.ts deleted file mode 100644 index 6751ede888..0000000000 --- a/packages/frontend/core/src/components/hooks/affine/use-trash-modal-helper.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { toast } from '@affine/component'; -import { useI18n } from '@affine/i18n'; -import { useAtom } from 'jotai'; -import { useCallback } from 'react'; - -import { trashModalAtom } from '../../../components/atoms/trash-modal'; -import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper'; - -export function useTrashModalHelper() { - const t = useI18n(); - const [trashModal, setTrashModal] = useAtom(trashModalAtom); - const { pageIds } = trashModal; - const { removeToTrash } = useBlockSuiteMetaHelper(); - const handleOnConfirm = useCallback(() => { - pageIds.forEach(pageId => { - removeToTrash(pageId); - }); - toast(t['com.affine.toastMessage.movedTrash']()); - setTrashModal({ ...trashModal, open: false }); - }, [pageIds, removeToTrash, setTrashModal, t, trashModal]); - - return { - trashModal, - setTrashModal, - handleOnConfirm, - }; -} diff --git a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts index e94d278d18..f3030e98b8 100644 --- a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts +++ b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts @@ -1,5 +1,6 @@ import { AppSidebarService } from '@affine/core/modules/app-sidebar'; -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; +import { DesktopApiService } from '@affine/core/modules/desktop-api'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { I18nService } from '@affine/core/modules/i18n'; import { UrlService } from '@affine/core/modules/url'; import { useI18n } from '@affine/i18n'; @@ -25,7 +26,6 @@ import { registerAffineUpdatesCommands, } from '../../commands'; import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils'; -import { CreateWorkspaceDialogService } from '../../modules/create-workspace'; import { EditorSettingService } from '../../modules/editor-setting'; import { CMDKQuickSearchService } from '../../modules/quicksearch/services/cmdk'; import { useActiveBlocksuiteEditor } from './use-block-suite-editor'; @@ -78,7 +78,7 @@ export function useRegisterWorkspaceCommands() { const [editor] = useActiveBlocksuiteEditor(); const cmdkQuickSearchService = useService(CMDKQuickSearchService); const editorSettingService = useService(EditorSettingService); - const createWorkspaceDialogService = useService(CreateWorkspaceDialogService); + const globalDialogService = useService(GlobalDialogService); const appSidebarService = useService(AppSidebarService); const i18n = useService(I18nService).i18n; @@ -117,12 +117,19 @@ export function useRegisterWorkspaceCommands() { t, docCollection: currentWorkspace.docCollection, navigationHelper, + globalDialogService, }); return () => { unsub(); }; - }, [store, t, currentWorkspace.docCollection, navigationHelper]); + }, [ + store, + t, + currentWorkspace.docCollection, + navigationHelper, + globalDialogService, + ]); // register AffineSettingsCommands useEffect(() => { @@ -162,7 +169,7 @@ export function useRegisterWorkspaceCommands() { // register AffineCreationCommands useEffect(() => { const unsub = registerAffineCreationCommands({ - createWorkspaceDialogService, + globalDialogService, pageHelper: pageHelper, t, }); @@ -170,18 +177,18 @@ export function useRegisterWorkspaceCommands() { return () => { unsub(); }; - }, [store, pageHelper, t, createWorkspaceDialogService]); + }, [store, pageHelper, t, globalDialogService]); // register AffineHelpCommands useEffect(() => { const unsub = registerAffineHelpCommands({ - store, t, urlService, + globalDialogService, }); return () => { unsub(); }; - }, [store, t, urlService]); + }, [t, globalDialogService, urlService]); } diff --git a/packages/frontend/core/src/components/layouts/workspace-layout.tsx b/packages/frontend/core/src/components/layouts/workspace-layout.tsx deleted file mode 100644 index 7b46e7180a..0000000000 --- a/packages/frontend/core/src/components/layouts/workspace-layout.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import { toast } from '@affine/component'; -import { - pushGlobalLoadingEventAtom, - resolveGlobalLoadingEventAtom, -} from '@affine/component/global-loading'; -import { - OpenInAppCard, - SidebarSwitch, -} from '@affine/core/modules/app-sidebar/views'; -import { WorkspaceDesktopApiService } from '@affine/core/modules/desktop-api/service'; -import { useI18n } from '@affine/i18n'; -import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks'; -import { - DocsService, - effect, - fromPromise, - LiveData, - onStart, - throwIfAborted, - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; -import type { PropsWithChildren } from 'react'; -import { useEffect } from 'react'; -import { - catchError, - EMPTY, - finalize, - mergeMap, - switchMap, - timeout, -} from 'rxjs'; -import { Map as YMap } from 'yjs'; - -import { AIProvider } from '../../blocksuite/presets/ai'; -import { AppTabsHeader } from '../../modules/app-tabs-header'; -import { EditorSettingService } from '../../modules/editor-setting'; -import { NavigationButtons } from '../../modules/navigation'; -import { useRegisterNavigationCommands } from '../../modules/navigation/view/use-register-navigation-commands'; -import { QuickSearchContainer } from '../../modules/quicksearch'; -import { WorkbenchService } from '../../modules/workbench'; -import { WorkspaceAIOnboarding } from '../affine/ai-onboarding'; -import { AppContainer } from '../affine/app-container'; -import { SyncAwareness } from '../affine/awareness'; -import { useRegisterFindInPageCommands } from '../hooks/affine/use-register-find-in-page-commands'; -import { useSubscriptionNotifyReader } from '../hooks/affine/use-subscription-notify'; -import { useRegisterWorkspaceCommands } from '../hooks/use-register-workspace-commands'; -import { OverCapacityNotification } from '../over-capacity'; -import { CurrentWorkspaceModals } from '../providers/modal-provider'; -import { SWRConfigProvider } from '../providers/swr-config-provider'; -import { AIIsland } from '../pure/ai-island'; -import { RootAppSidebar } from '../root-app-sidebar'; -import { MainContainer } from '../workspace'; -import { WorkspaceUpgrade } from '../workspace-upgrade'; -import * as styles from './styles.css'; - -export const WorkspaceLayout = function WorkspaceLayout({ - children, -}: PropsWithChildren) { - return ( - - {/* load all workspaces is costly, do not block the whole UI */} - - {children} - {/* should show after workspace loaded */} - - - - ); -}; - -export const WorkspaceLayoutProviders = ({ children }: PropsWithChildren) => { - const t = useI18n(); - const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom); - const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom); - const { workspaceService, docsService } = useServices({ - WorkspaceService, - DocsService, - EditorSettingService, - }); - const currentWorkspace = workspaceService.workspace; - const docsList = docsService.list; - - const workbench = useService(WorkbenchService).workbench; - useEffect(() => { - const insertTemplate = effect( - switchMap(({ template, mode }: { template: string; mode: string }) => { - return fromPromise(async abort => { - const templateZip = await fetch(template, { signal: abort }); - const templateBlob = await templateZip.blob(); - throwIfAborted(abort); - const [doc] = await ZipTransformer.importDocs( - currentWorkspace.docCollection, - templateBlob - ); - if (doc) { - doc.resetHistory(); - } - - return { doc, mode }; - }).pipe( - timeout(10000 /* 10s */), - mergeMap(({ mode, doc }) => { - if (doc) { - docsList.setPrimaryMode(doc.id, mode as DocMode); - workbench.openDoc(doc.id); - } - return EMPTY; - }), - onStart(() => { - pushGlobalLoadingEvent({ - key: 'insert-template', - }); - }), - catchError(err => { - console.error(err); - toast(t['com.affine.ai.template-insert.failed']()); - return EMPTY; - }), - finalize(() => { - resolveGlobalLoadingEvent('insert-template'); - }) - ); - }) - ); - - const disposable = AIProvider.slots.requestInsertTemplate.on( - ({ template, mode }) => { - insertTemplate({ template, mode }); - } - ); - return () => { - disposable.dispose(); - insertTemplate.unsubscribe(); - }; - }, [ - currentWorkspace.docCollection, - docsList, - pushGlobalLoadingEvent, - resolveGlobalLoadingEvent, - t, - workbench, - ]); - - useSubscriptionNotifyReader(); - useRegisterWorkspaceCommands(); - useRegisterNavigationCommands(); - useRegisterFindInPageCommands(); - - useEffect(() => { - // hotfix for blockVersions - // this is a mistake in the - // 0.8.0 ~ 0.8.1 - // 0.8.0-beta.0 ~ 0.8.0-beta.3 - // 0.8.0-canary.17 ~ 0.9.0-canary.3 - const meta = currentWorkspace.docCollection.doc.getMap('meta'); - const blockVersions = meta.get('blockVersions'); - if ( - !(blockVersions instanceof YMap) && - blockVersions !== null && - blockVersions !== undefined && - typeof blockVersions === 'object' - ) { - meta.set( - 'blockVersions', - new YMap(Object.entries(blockVersions as Record)) - ); - } - }, [currentWorkspace.docCollection.doc]); - - return ( - <> - {/* This DndContext is used for drag page from all-pages list into a folder in sidebar */} - {children} - - - - - ); -}; - -const DesktopLayout = ({ children }: PropsWithChildren) => { - // is there a better way to make sure service is always available even if it's not explicitly used? - useService(WorkspaceDesktopApiService); - return ( -
-
- - - - - } - /> -
-
- - {children} -
-
- ); -}; - -const BrowserLayout = ({ children }: PropsWithChildren) => { - return ( -
- - - {children} -
- ); -}; - -const LayoutComponent = BUILD_CONFIG.isElectron ? DesktopLayout : BrowserLayout; - -/** - * Wraps the workspace layout main router view - */ -const WorkspaceLayoutUIContainer = ({ children }: PropsWithChildren) => { - const workbench = useService(WorkbenchService).workbench; - const currentPath = useLiveData( - LiveData.computed(get => { - return get(workbench.basename$) + get(workbench.location$).pathname; - }) - ); - - return ( - - {children} - - ); -}; -export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => { - const workspace = useService(WorkspaceService).workspace; - - const upgrading = useLiveData(workspace.upgrade.upgrading$); - const needUpgrade = useLiveData(workspace.upgrade.needUpgrade$); - - return ( - - - {needUpgrade || upgrading ? : children} - - - ); -}; diff --git a/packages/frontend/core/src/components/over-capacity/index.tsx b/packages/frontend/core/src/components/over-capacity/index.tsx index 75eff38c07..fbb86dea17 100644 --- a/packages/frontend/core/src/components/over-capacity/index.tsx +++ b/packages/frontend/core/src/components/over-capacity/index.tsx @@ -1,9 +1,8 @@ import { notify } from '@affine/component'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { debounce } from 'lodash-es'; import { useCallback, useEffect } from 'react'; @@ -20,14 +19,13 @@ export const OverCapacityNotification = () => { permissionService.permission.revalidate(); }, [permissionService]); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const jumpToPricePlan = useCallback(() => { - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - }, [setSettingModalAtom]); + }, [globalDialogService]); // debounce sync engine status useEffect(() => { diff --git a/packages/frontend/core/src/components/page-list/collections/index.ts b/packages/frontend/core/src/components/page-list/collections/index.ts index ed58ee5e14..afed0c1444 100644 --- a/packages/frontend/core/src/components/page-list/collections/index.ts +++ b/packages/frontend/core/src/components/page-list/collections/index.ts @@ -1,4 +1,3 @@ export * from './collection-list-header'; export * from './collection-list-item'; -export * from './select-collection'; export * from './virtualized-collection-list'; diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx index cb187ae00c..0028e37c67 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx @@ -8,6 +8,7 @@ import { } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import { isNewTabTrigger } from '@affine/core/utils'; @@ -28,18 +29,13 @@ import { WorkspaceService, } from '@toeverything/infra'; import clsx from 'clsx'; -import { nanoid } from 'nanoid'; import { useCallback, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { CollectionService } from '../../../modules/collection'; import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils'; import { createTagFilter } from '../filter/utils'; -import { createEmptyCollection } from '../use-collection-manager'; -import { - useEditCollection, - useEditCollectionName, -} from '../view/use-edit-collection'; +import { SaveAsCollectionButton } from '../view'; import * as styles from './page-list-header.css'; import { PageListNewPageButton } from './page-list-new-page-button'; @@ -102,21 +98,22 @@ export const CollectionPageListHeader = ({ }) => { const t = useI18n(); const { jumpToCollections } = useNavigateHelper(); - const { collectionService, workspaceService } = useServices({ - CollectionService, - WorkspaceService, - }); + const { collectionService, workspaceService, workspaceDialogService } = + useServices({ + CollectionService, + WorkspaceService, + WorkspaceDialogService, + }); const handleJumpToCollections = useCallback(() => { jumpToCollections(workspaceId); }, [jumpToCollections, workspaceId]); - const { open } = useEditCollection(); - - const handleEdit = useAsyncCallback(async () => { - const ret = await open({ ...collection }, 'page'); - collectionService.updateCollection(collection.id, () => ret); - }, [collection, collectionService, open]); + const handleEdit = useCallback(() => { + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); + }, [collection, workspaceDialogService]); const workspace = workspaceService.workspace; const { createEdgeless, createPage } = usePageHelper(workspace.docCollection); @@ -203,10 +200,6 @@ export const TagPageListHeader = ({ const { jumpToTags, jumpToCollection } = useNavigateHelper(); const collectionService = useService(CollectionService); const [openMenu, setOpenMenu] = useState(false); - const { open } = useEditCollectionName({ - title: t['com.affine.editCollection.saveCollection'](), - showTips: true, - }); const handleJumpToTags = useCallback(() => { jumpToTags(workspaceId); @@ -222,15 +215,6 @@ export const TagPageListHeader = ({ }, [collectionService, tag.id, jumpToCollection, workspaceId] ); - const handleClick = useCallback(() => { - open('') - .then(name => { - return saveToCollection(createEmptyCollection(nanoid(), { name })); - }) - .catch(err => { - console.error(err); - }); - }, [open, saveToCollection]); return (
@@ -267,9 +251,7 @@ export const TagPageListHeader = ({
- + ); }; diff --git a/packages/frontend/core/src/components/page-list/docs/select-page.css.ts b/packages/frontend/core/src/components/page-list/docs/select-page.css.ts new file mode 100644 index 0000000000..8ea3187514 --- /dev/null +++ b/packages/frontend/core/src/components/page-list/docs/select-page.css.ts @@ -0,0 +1,26 @@ +import { style } from '@vanilla-extract/css'; + +export const pagesTab = style({ + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + overflow: 'hidden', +}); +export const pagesTabContent = style({ + display: 'flex', + justifyContent: 'space-between', + gap: 8, + alignItems: 'center', + padding: '16px 16px 8px 16px', +}); + +export const pageList = style({ + width: '100%', +}); + +export const ellipsis = style({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx b/packages/frontend/core/src/components/page-list/docs/select-page.tsx similarity index 90% rename from packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx rename to packages/frontend/core/src/components/page-list/docs/select-page.tsx index 20d24de0c3..a80db7652a 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/select-page.tsx +++ b/packages/frontend/core/src/components/page-list/docs/select-page.tsx @@ -13,18 +13,16 @@ import { } from '@toeverything/infra'; import { type ReactNode, useCallback, useEffect, useState } from 'react'; -import { FavoriteTag } from '../../components/favorite-tag'; -import { FilterList } from '../../filter'; -import { VariableSelect } from '../../filter/vars'; -import { usePageHeaderColsDef } from '../../header-col-def'; -import { PageListItemRenderer } from '../../page-group'; -import { ListTableHeader } from '../../page-header'; -import type { BaseSelectorDialogProps } from '../../selector'; -import { SelectorLayout } from '../../selector/selector-layout'; -import type { ListItem } from '../../types'; -import { VirtualizedList } from '../../virtualized-list'; -import { AffineShapeIcon } from '../affine-shape'; -import * as styles from './edit-collection.css'; +import { AffineShapeIcon, FavoriteTag } from '..'; +import { FilterList } from '../filter'; +import { VariableSelect } from '../filter/vars'; +import { usePageHeaderColsDef } from '../header-col-def'; +import { PageListItemRenderer } from '../page-group'; +import { ListTableHeader } from '../page-header'; +import { SelectorLayout } from '../selector/selector-layout'; +import type { ListItem } from '../types'; +import { VirtualizedList } from '../virtualized-list'; +import * as styles from './select-page.css'; import { useFilter } from './use-filter'; import { useSearch } from './use-search'; @@ -40,7 +38,10 @@ export const SelectPage = ({ confirmText?: ReactNode; header?: ReactNode; buttons?: ReactNode; -} & BaseSelectorDialogProps) => { + init?: string[]; + onConfirm?: (data: string[]) => void; + onCancel?: () => void; +}) => { const t = useI18n(); const [value, setValue] = useState(init); const onChange = useCallback( diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/use-filter.tsx b/packages/frontend/core/src/components/page-list/docs/use-filter.tsx similarity index 88% rename from packages/frontend/core/src/components/page-list/view/edit-collection/use-filter.tsx rename to packages/frontend/core/src/components/page-list/docs/use-filter.tsx index 2c3b609bbf..a0846a20fa 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/use-filter.tsx +++ b/packages/frontend/core/src/components/page-list/docs/use-filter.tsx @@ -2,8 +2,10 @@ import type { Filter } from '@affine/env/filter'; import type { MouseEvent } from 'react'; import { useCallback, useState } from 'react'; -import type { PageDataForFilter } from '../../use-collection-manager'; -import { filterPageByRules } from '../../use-collection-manager'; +import { + filterPageByRules, + type PageDataForFilter, +} from '../use-collection-manager'; export const useFilter = (list: PageDataForFilter[]) => { const [filters, changeFilters] = useState([]); diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/use-search.tsx b/packages/frontend/core/src/components/page-list/docs/use-search.tsx similarity index 100% rename from packages/frontend/core/src/components/page-list/view/edit-collection/use-search.tsx rename to packages/frontend/core/src/components/page-list/docs/use-search.tsx diff --git a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx index ade146738a..40a977f50f 100644 --- a/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx +++ b/packages/frontend/core/src/components/page-list/docs/virtualized-page-list.tsx @@ -1,12 +1,11 @@ -import { toast } from '@affine/component'; -import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; +import { toast, useConfirmModal } from '@affine/component'; import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta'; import { CollectionService } from '@affine/core/modules/collection'; import type { Tag } from '@affine/core/modules/tag'; import type { Collection, Filter } from '@affine/env/filter'; import { Trans, useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { DocsService, useService, WorkspaceService } from '@toeverything/infra'; import { useCallback, useMemo, useRef, useState } from 'react'; import { ListFloatingToolbar } from '../components/list-floating-toolbar'; @@ -62,10 +61,12 @@ export const VirtualizedPageList = ({ listItem?: DocMeta[]; setHideHeaderCreateNewPage?: (hide: boolean) => void; }) => { + const t = useI18n(); const listRef = useRef(null); const [showFloatingToolbar, setShowFloatingToolbar] = useState(false); const [selectedPageIds, setSelectedPageIds] = useState([]); const currentWorkspace = useService(WorkspaceService).workspace; + const docsService = useService(DocsService); const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection); const pageOperations = usePageOperationsRenderer(); const pageHeaderColsDef = usePageHeaderColsDef(); @@ -122,26 +123,39 @@ export const VirtualizedPageList = ({ return ; }, [collection, currentWorkspace.id, tag]); - const { setTrashModal } = useTrashModalHelper(); + const { openConfirmModal } = useConfirmModal(); const handleMultiDelete = useCallback(() => { if (filteredSelectedPageIds.length === 0) { return; } - const pageNameMapping = Object.fromEntries( - pageMetas.map(meta => [meta.id, meta.title]) - ); - const pageNames = filteredSelectedPageIds.map( - id => pageNameMapping[id] ?? '' - ); - setTrashModal({ - open: true, - pageIds: filteredSelectedPageIds, - pageTitles: pageNames, + openConfirmModal({ + title: t['com.affine.moveToTrash.confirmModal.title.multiple']({ + number: filteredSelectedPageIds.length.toString(), + }), + description: t[ + 'com.affine.moveToTrash.confirmModal.description.multiple' + ]({ + number: filteredSelectedPageIds.length.toString(), + }), + cancelText: t['com.affine.confirmModal.button.cancel'](), + confirmText: t.Delete(), + onConfirm: () => { + for (const docId of filteredSelectedPageIds) { + const doc = docsService.list.doc$(docId).value; + doc?.moveToTrash(); + } + }, }); hideFloatingToolbar(); - }, [filteredSelectedPageIds, hideFloatingToolbar, pageMetas, setTrashModal]); + }, [ + docsService.list, + filteredSelectedPageIds, + hideFloatingToolbar, + openConfirmModal, + t, + ]); const group = usePageItemGroupDefinitions(); diff --git a/packages/frontend/core/src/components/page-list/operation-cell.tsx b/packages/frontend/core/src/components/page-list/operation-cell.tsx index 785c702720..fc09b9d3e7 100644 --- a/packages/frontend/core/src/components/page-list/operation-cell.tsx +++ b/packages/frontend/core/src/components/page-list/operation-cell.tsx @@ -4,11 +4,11 @@ import { MenuItem, toast, useConfirmModal, + usePromptModal, } from '@affine/component'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; -import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; -import { DocInfoService } from '@affine/core/modules/doc-info'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter, FavoriteService, @@ -33,6 +33,7 @@ import { SplitViewIcon, } from '@blocksuite/icons/rc'; import { + DocsService, FeatureFlagService, useLiveData, useService, @@ -51,7 +52,6 @@ import { DisablePublicSharing, MoveToTrash } from './operation-menu-items'; import { CreateOrEditTag } from './tags/create-tag'; import type { TagMeta } from './types'; import { ColWrapper } from './utils'; -import { useEditCollection, useEditCollectionName } from './view'; const tooltipSideTop = { side: 'top' as const }; const tooltipSideTopAlignEnd = { side: 'top' as const, align: 'end' as const }; @@ -83,17 +83,19 @@ export const PageOperationCell = ({ featureFlagService.flags.enable_multi_view.$ ); const currentWorkspace = workspaceService.workspace; - const { setTrashModal } = useTrashModalHelper(); const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc')); const workbench = workbenchService.workbench; const { duplicate } = useBlockSuiteMetaHelper(); + const docRecord = useLiveData(useService(DocsService).list.doc$(page.id)); const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id); - const docInfoModal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const onOpenInfoModal = useCallback(() => { - track.$.docInfoPanel.$.open(); - docInfoModal.open(blocksuiteDoc?.id); - }, [blocksuiteDoc?.id, docInfoModal]); + if (blocksuiteDoc?.id) { + track.$.docInfoPanel.$.open(); + workspaceDialogService.open('doc-info', { docId: blocksuiteDoc.id }); + } + }, [blocksuiteDoc?.id, workspaceDialogService]); const onDisablePublicSharing = useCallback(() => { // TODO(@EYHN): implement disable public sharing @@ -102,15 +104,26 @@ export const PageOperationCell = ({ }); }, []); + const { openConfirmModal } = useConfirmModal(); + const onRemoveToTrash = useCallback(() => { + if (!docRecord) { + return; + } track.allDocs.list.docMenu.deleteDoc(); - setTrashModal({ - open: true, - pageIds: [page.id], - pageTitles: [page.title], + openConfirmModal({ + title: t['com.affine.moveToTrash.confirmModal.title'](), + description: t['com.affine.moveToTrash.confirmModal.description']({ + title: docRecord.title$.value || t['Untitled'](), + }), + cancelText: t['com.affine.confirmModal.button.cancel'](), + confirmText: t.Delete(), + onConfirm: () => { + docRecord.moveToTrash(); + }, }); - }, [page.id, page.title, setTrashModal]); + }, [docRecord, openConfirmModal, t]); const onOpenInSplitView = useCallback(() => { track.allDocs.list.docMenu.openInSplitView(); @@ -297,11 +310,15 @@ export const CollectionOperationCell = ({ info, }: CollectionOperationCellProps) => { const t = useI18n(); - const { compatibleFavoriteItemsAdapter: favAdapter, workspaceService } = - useServices({ - CompatibleFavoriteItemsAdapter, - WorkspaceService, - }); + const { + compatibleFavoriteItemsAdapter: favAdapter, + workspaceService, + workspaceDialogService, + } = useServices({ + CompatibleFavoriteItemsAdapter, + WorkspaceService, + WorkspaceDialogService, + }); const docCollection = workspaceService.workspace.docCollection; const { createPage } = usePageHelper(docCollection); const { openConfirmModal } = useConfirmModal(); @@ -309,11 +326,7 @@ export const CollectionOperationCell = ({ favAdapter.isFavorite$(collection.id, 'collection') ); - const { open: openEditCollectionModal } = useEditCollection(); - - const { open: openEditCollectionNameModal } = useEditCollectionName({ - title: t['com.affine.editCollection.renameCollection'](), - }); + const { openPromptModal } = usePromptModal(); const handlePropagation = useCallback((event: MouseEvent) => { event.preventDefault(); @@ -323,39 +336,36 @@ export const CollectionOperationCell = ({ const handleEditName = useCallback( (event: MouseEvent) => { handlePropagation(event); - // use openRenameModal if it is in the sidebar collection list - openEditCollectionNameModal(collection.name) - .then(name => { - return service.updateCollection(collection.id, collection => ({ + openPromptModal({ + title: t['com.affine.editCollection.renameCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { + service.updateCollection(collection.id, () => ({ ...collection, name, })); - }) - .catch(err => { - console.error(err); - }); + }, + }); }, - [ - collection.id, - collection.name, - handlePropagation, - openEditCollectionNameModal, - service, - ] + [collection, handlePropagation, openPromptModal, service, t] ); const handleEdit = useCallback( (event: MouseEvent) => { handlePropagation(event); - openEditCollectionModal(collection) - .then(collection => { - return service.updateCollection(collection.id, () => collection); - }) - .catch(err => { - console.error(err); - }); + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); }, - [handlePropagation, openEditCollectionModal, collection, service] + [handlePropagation, workspaceDialogService, collection.id] ); const handleDelete = useCallback(() => { diff --git a/packages/frontend/core/src/components/page-list/selector/index.ts b/packages/frontend/core/src/components/page-list/selector/index.ts deleted file mode 100644 index 5396c4e801..0000000000 --- a/packages/frontend/core/src/components/page-list/selector/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SelectCollection } from '../collections'; -import { SelectTag } from '../tags'; -import { SelectPage } from '../view/edit-collection/select-page'; -import { useSelectDialog } from './use-select-dialog'; - -export * from './use-select-dialog'; - -/** - * Return a `open` function to open the select collection dialog. - */ -export const useSelectCollection = () => { - return useSelectDialog(SelectCollection, 'select-collection'); -}; - -/** - * Return a `open` function to open the select page dialog. - */ -export const useSelectDoc = () => { - return useSelectDialog(SelectPage, 'select-doc-dialog'); -}; - -/** - * Return a `open` function to open the select tag dialog. - */ -export const useSelectTag = () => { - return useSelectDialog(SelectTag, 'select-tag-dialog'); -}; diff --git a/packages/frontend/core/src/components/page-list/selector/use-select-dialog.tsx b/packages/frontend/core/src/components/page-list/selector/use-select-dialog.tsx deleted file mode 100644 index 86bf4f3213..0000000000 --- a/packages/frontend/core/src/components/page-list/selector/use-select-dialog.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Modal, type ModalProps } from '@affine/component'; -import { useMount } from '@toeverything/infra'; -import { cssVar } from '@toeverything/theme'; -import { useCallback, useEffect, useState } from 'react'; - -export interface BaseSelectorDialogProps { - init?: T; - onConfirm?: (data: T) => void; - onCancel?: () => void; -} - -const defaultModalProps: Partial> = {}; -export const useSelectDialog = function useSelectDialog( - Component: React.FC & P>, - debugKey?: string, - options?: { - modalProps?: Partial>; - } -) { - // to control whether the dialog is open, it's not equal to !!value - // when closing the dialog, show will be `false` first, then after the animation, value turns to `undefined` - const [show, setShow] = useState(false); - const [value, setValue] = useState<{ - init?: T; - onConfirm: (v: T) => void; - }>(); - const [additionalProps, setAdditionalProps] = useState

(); - - const onOpenChanged = useCallback((open: boolean) => { - if (!open) setValue(undefined); - setShow(open); - }, []); - - const close = useCallback(() => setShow(false), []); - - /** - * Open a dialog to select items - */ - const open = useCallback( - (ids?: T, additionalProps?: P) => { - return new Promise(resolve => { - setShow(true); - setAdditionalProps(additionalProps); - setValue({ - init: ids, - onConfirm: list => { - close(); - resolve(list); - }, - }); - }); - }, - [close] - ); - - const { mount } = useMount(debugKey); - - useEffect(() => { - const { contentOptions, ...otherModalProps } = - options?.modalProps ?? defaultModalProps; - - return mount( - - {value ? ( - - ) : null} - - ); - }, [ - Component, - additionalProps, - close, - debugKey, - mount, - onOpenChanged, - options?.modalProps, - show, - value, - ]); - - return open; -}; diff --git a/packages/frontend/core/src/components/page-list/tags/index.ts b/packages/frontend/core/src/components/page-list/tags/index.ts index 45cd9b3605..11a1b31754 100644 --- a/packages/frontend/core/src/components/page-list/tags/index.ts +++ b/packages/frontend/core/src/components/page-list/tags/index.ts @@ -1,4 +1,3 @@ -export * from './select-tag'; export * from './tag-list-header'; export * from './tag-list-item'; export * from './virtualized-tag-list'; diff --git a/packages/frontend/core/src/components/page-list/view/collection-operations.tsx b/packages/frontend/core/src/components/page-list/view/collection-operations.tsx index df7a862321..6e67b3910b 100644 --- a/packages/frontend/core/src/components/page-list/view/collection-operations.tsx +++ b/packages/frontend/core/src/components/page-list/view/collection-operations.tsx @@ -1,6 +1,7 @@ import type { MenuItemProps } from '@affine/component'; -import { Menu, MenuItem } from '@affine/component'; +import { Menu, MenuItem, usePromptModal } from '@affine/component'; import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { WorkbenchService } from '@affine/core/modules/workbench'; import type { Collection } from '@affine/env/filter'; @@ -25,10 +26,6 @@ import { useCallback, useMemo } from 'react'; import { CollectionService } from '../../../modules/collection'; import { IsFavoriteIcon } from '../../pure/icons'; import * as styles from './collection-operations.css'; -import { - useEditCollection, - useEditCollectionName, -} from './use-edit-collection'; export const CollectionOperations = ({ collection, @@ -44,18 +41,17 @@ export const CollectionOperations = ({ collectionService: service, workbenchService, featureFlagService, + workspaceDialogService, } = useServices({ CollectionService, WorkbenchService, FeatureFlagService, + WorkspaceDialogService, }); const deleteInfo = useDeleteCollectionInfo(); const workbench = workbenchService.workbench; - const { open: openEditCollectionModal } = useEditCollection(); const t = useI18n(); - const { open: openEditCollectionNameModal } = useEditCollectionName({ - title: t['com.affine.editCollection.renameCollection'](), - }); + const { openPromptModal } = usePromptModal(); const enableMultiView = useLiveData( featureFlagService.flags.enable_multi_view.$ ); @@ -65,27 +61,31 @@ export const CollectionOperations = ({ if (openRenameModal) { return openRenameModal(); } - openEditCollectionNameModal(collection.name) - .then(name => { - return service.updateCollection(collection.id, () => ({ + openPromptModal({ + title: t['com.affine.editCollection.renameCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { + service.updateCollection(collection.id, () => ({ ...collection, name, })); - }) - .catch(err => { - console.error(err); - }); - }, [openRenameModal, openEditCollectionNameModal, collection, service]); + }, + }); + }, [openRenameModal, openPromptModal, t, service, collection]); const showEdit = useCallback(() => { - openEditCollectionModal(collection) - .then(collection => { - return service.updateCollection(collection.id, () => collection); - }) - .catch(err => { - console.error(err); - }); - }, [openEditCollectionModal, collection, service]); + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); + }, [workspaceDialogService, collection.id]); const openCollectionSplitView = useCallback(() => { workbench.openCollection(collection.id, { at: 'tail' }); diff --git a/packages/frontend/core/src/components/page-list/view/index.ts b/packages/frontend/core/src/components/page-list/view/index.ts index 396ac12308..4fc8898b3c 100644 --- a/packages/frontend/core/src/components/page-list/view/index.ts +++ b/packages/frontend/core/src/components/page-list/view/index.ts @@ -2,6 +2,4 @@ export * from './affine-shape'; export * from './collection-list'; export * from './collection-operations'; export * from './create-collection'; -export * from './edit-collection/edit-collection'; export * from './save-as-collection-button'; -export * from './use-edit-collection'; diff --git a/packages/frontend/core/src/components/page-list/view/save-as-collection-button.css.ts b/packages/frontend/core/src/components/page-list/view/save-as-collection-button.css.ts index d53b83b301..fc8e36bd56 100644 --- a/packages/frontend/core/src/components/page-list/view/save-as-collection-button.css.ts +++ b/packages/frontend/core/src/components/page-list/view/save-as-collection-button.css.ts @@ -8,3 +8,8 @@ export const button = style({ fontWeight: 500, height: '28px', }); +export const createTips = style({ + color: cssVar('textSecondaryColor'), + fontSize: 12, + lineHeight: '20px', +}); diff --git a/packages/frontend/core/src/components/page-list/view/save-as-collection-button.tsx b/packages/frontend/core/src/components/page-list/view/save-as-collection-button.tsx index c1ef31d5f1..2f2c11a5f4 100644 --- a/packages/frontend/core/src/components/page-list/view/save-as-collection-button.tsx +++ b/packages/frontend/core/src/components/page-list/view/save-as-collection-button.tsx @@ -1,4 +1,4 @@ -import { Button } from '@affine/component'; +import { Button, usePromptModal } from '@affine/component'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { SaveIcon } from '@blocksuite/icons/rc'; @@ -7,7 +7,6 @@ import { useCallback } from 'react'; import { createEmptyCollection } from '../use-collection-manager'; import * as styles from './save-as-collection-button.css'; -import { useEditCollectionName } from './use-edit-collection'; interface SaveAsCollectionButtonProps { onConfirm: (collection: Collection) => void; @@ -17,19 +16,29 @@ export const SaveAsCollectionButton = ({ onConfirm, }: SaveAsCollectionButtonProps) => { const t = useI18n(); - const { open } = useEditCollectionName({ - title: t['com.affine.editCollection.saveCollection'](), - showTips: true, - }); + const { openPromptModal } = usePromptModal(); const handleClick = useCallback(() => { - open('') - .then(name => { - return onConfirm(createEmptyCollection(nanoid(), { name })); - }) - .catch(err => { - console.error(err); - }); - }, [open, onConfirm]); + openPromptModal({ + title: t['com.affine.editCollection.saveCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + children: ( +

+ {t['com.affine.editCollectionName.createTips']()} +
+ ), + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { + onConfirm(createEmptyCollection(nanoid(), { name })); + }, + }); + }, [openPromptModal, t, onConfirm]); return ( - + ); }; diff --git a/packages/frontend/core/src/components/pure/ai-island/styles.css.ts b/packages/frontend/core/src/desktop/components/ai-island/styles.css.ts similarity index 100% rename from packages/frontend/core/src/components/pure/ai-island/styles.css.ts rename to packages/frontend/core/src/desktop/components/ai-island/styles.css.ts diff --git a/packages/frontend/core/src/desktop/components/app-container/index.tsx b/packages/frontend/core/src/desktop/components/app-container/index.tsx new file mode 100644 index 0000000000..249958723c --- /dev/null +++ b/packages/frontend/core/src/desktop/components/app-container/index.tsx @@ -0,0 +1,133 @@ +import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; +import { RootAppSidebar } from '@affine/core/components/root-app-sidebar'; +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; +import { + AppSidebarFallback, + OpenInAppCard, + SidebarSwitch, +} from '@affine/core/modules/app-sidebar/views'; +import { AppTabsHeader } from '@affine/core/modules/app-tabs-header'; +import { NavigationButtons } from '@affine/core/modules/navigation'; +import { + useLiveData, + useService, + useServiceOptional, + WorkspaceService, +} from '@toeverything/infra'; +import clsx from 'clsx'; +import { + forwardRef, + type HTMLAttributes, + type PropsWithChildren, + type ReactElement, +} from 'react'; + +import * as styles from './styles.css'; + +export const AppContainer = ({ + children, + className, + fallback = false, + ...rest +}: PropsWithChildren<{ + className?: string; + fallback?: boolean; +}>) => { + const { appSettings } = useAppSettingHelper(); + + const noisyBackground = + BUILD_CONFIG.isElectron && appSettings.enableNoisyBackground; + const blurBackground = + BUILD_CONFIG.isElectron && + environment.isMacOs && + appSettings.enableBlurBackground; + return ( +
+ {children} +
+ ); +}; + +const DesktopLayout = ({ + children, + fallback = false, +}: PropsWithChildren<{ fallback?: boolean }>) => { + const workspaceService = useServiceOptional(WorkspaceService); + const isInWorkspace = !!workspaceService; + return ( +
+
+ + {isInWorkspace && } + {isInWorkspace && } + + } + /> +
+
+ {fallback ? ( + + ) : ( + isInWorkspace && + )} + {children} +
+
+ ); +}; + +const BrowserLayout = ({ + children, + fallback = false, +}: PropsWithChildren<{ fallback?: boolean }>) => { + const workspaceService = useServiceOptional(WorkspaceService); + const isInWorkspace = !!workspaceService; + + return ( +
+ + {fallback ? : isInWorkspace && } + {children} +
+ ); +}; + +const LayoutComponent = BUILD_CONFIG.isElectron ? DesktopLayout : BrowserLayout; + +const MainContainer = forwardRef< + HTMLDivElement, + PropsWithChildren> +>(function MainContainer({ className, children, ...props }, ref): ReactElement { + const workspaceService = useServiceOptional(WorkspaceService); + const isInWorkspace = !!workspaceService; + const { appSettings } = useAppSettingHelper(); + const appSidebarService = useService(AppSidebarService).sidebar; + const open = useLiveData(appSidebarService.open$); + + return ( +
+ {children} +
+ ); +}); + +MainContainer.displayName = 'MainContainer'; diff --git a/packages/frontend/core/src/components/workspace/index.css.ts b/packages/frontend/core/src/desktop/components/app-container/styles.css.ts similarity index 79% rename from packages/frontend/core/src/components/workspace/index.css.ts rename to packages/frontend/core/src/desktop/components/app-container/styles.css.ts index daef387a2d..6349142184 100644 --- a/packages/frontend/core/src/components/workspace/index.css.ts +++ b/packages/frontend/core/src/desktop/components/app-container/styles.css.ts @@ -1,7 +1,5 @@ import { cssVar, lightCssVariables } from '@toeverything/theme'; -import { createVar, globalStyle, style } from '@vanilla-extract/css'; - -export const panelWidthVar = createVar('panel-width'); +import { globalStyle, style } from '@vanilla-extract/css'; export const appStyle = style({ width: '100%', @@ -42,6 +40,38 @@ globalStyle(`html[data-theme="dark"] ${appStyle}`, { }, }); +export const browserAppViewContainer = style({ + display: 'flex', + flexFlow: 'row', + height: '100%', + width: '100%', + position: 'relative', +}); + +export const desktopAppViewContainer = style({ + display: 'flex', + flexFlow: 'column', + height: '100%', + width: '100%', +}); + +export const desktopAppViewMain = style({ + display: 'flex', + flexFlow: 'row', + width: '100%', + height: 'calc(100% - 52px)', + position: 'relative', +}); + +export const desktopTabsHeader = style({ + display: 'flex', + flexFlow: 'row', + height: '52px', + zIndex: 1, + width: '100%', + overflow: 'hidden', +}); + export const mainContainerStyle = style({ position: 'relative', zIndex: 0, @@ -83,26 +113,3 @@ export const mainContainerStyle = style({ }, }, }); -export const toolStyle = style({ - position: 'absolute', - right: 16, - bottom: 16, - zIndex: 1, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - gap: '12px', - selectors: { - '&.trash': { - bottom: '78px', - }, - }, -}); - -export const fallbackRootStyle = style({ - paddingTop: 52, - display: 'flex', - flex: 1, - width: '100%', - height: '100%', -}); diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.css.ts b/packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.css.ts similarity index 88% rename from packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.css.ts rename to packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.css.ts index 21b658252e..1f65274d5d 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.css.ts +++ b/packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.css.ts @@ -5,25 +5,6 @@ export const ellipsis = style({ textOverflow: 'ellipsis', whiteSpace: 'nowrap', }); -export const pagesTabContent = style({ - display: 'flex', - justifyContent: 'space-between', - gap: 8, - alignItems: 'center', - padding: '16px 16px 8px 16px', -}); -export const pagesTab = style({ - display: 'flex', - flexDirection: 'column', - width: '100%', - height: '100%', - overflow: 'hidden', -}); -export const pagesList = style({ - display: 'flex', - flex: 1, - overflow: 'hidden', -}); export const bottomLeft = style({ display: 'flex', gap: 8, @@ -130,9 +111,6 @@ export const confirmButton = style({ export const resultPages = style({ width: '100%', }); -export const pageList = style({ - width: '100%', -}); export const previewCountTipsHighlight = style({ color: cssVar('primaryColor'), }); diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx b/packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.tsx similarity index 68% rename from packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx rename to packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.tsx index 0ae50aff87..df671db2b8 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/edit-collection.tsx +++ b/packages/frontend/core/src/desktop/dialogs/collection-editor/edit-collection.tsx @@ -1,80 +1,16 @@ -import { Button, Modal, RadioGroup } from '@affine/component'; +import { Button, RadioGroup } from '@affine/component'; import { useAllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config'; +import { SelectPage } from '@affine/core/components/page-list/docs/select-page'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; -import type { DialogContentProps } from '@radix-ui/react-dialog'; import { useCallback, useMemo, useState } from 'react'; import * as styles from './edit-collection.css'; import { RulesMode } from './rules-mode'; -import { SelectPage } from './select-page'; export type EditCollectionMode = 'page' | 'rule'; -export interface EditCollectionModalProps { - init?: Collection; - title?: string; - open: boolean; - mode?: EditCollectionMode; - onOpenChange: (open: boolean) => void; - onConfirm: (view: Collection) => void; -} - -const contentOptions: DialogContentProps = { - style: { - padding: 0, - maxWidth: 944, - backgroundColor: 'var(--affine-background-primary-color)', - }, -}; -export const EditCollectionModal = ({ - init, - onConfirm, - open, - onOpenChange, - title, - mode, -}: EditCollectionModalProps) => { - const t = useI18n(); - const onConfirmOnCollection = useCallback( - (view: Collection) => { - onConfirm(view); - onOpenChange(false); - }, - [onConfirm, onOpenChange] - ); - const onCancel = useCallback(() => { - onOpenChange(false); - }, [onOpenChange]); - - if (!(open && init)) { - return null; - } - - return ( - - - - ); -}; - export interface EditCollectionProps { - title?: string; onConfirmText?: string; init: Collection; mode?: EditCollectionMode; diff --git a/packages/frontend/core/src/desktop/dialogs/collection-editor/index.tsx b/packages/frontend/core/src/desktop/dialogs/collection-editor/index.tsx new file mode 100644 index 0000000000..16e2793b77 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/collection-editor/index.tsx @@ -0,0 +1,60 @@ +import { Modal } from '@affine/component'; +import { CollectionService } from '@affine/core/modules/collection'; +import type { DialogComponentProps } from '@affine/core/modules/dialogs'; +import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; +import type { Collection } from '@affine/env/filter'; +import { useI18n } from '@affine/i18n'; +import { useLiveData, useService } from '@toeverything/infra'; +import { useCallback } from 'react'; + +import { EditCollection } from './edit-collection'; + +export const CollectionEditorDialog = ({ + close, + collectionId, + mode, +}: DialogComponentProps) => { + const t = useI18n(); + const collectionService = useService(CollectionService); + const collection = useLiveData(collectionService.collection$(collectionId)); + const onConfirmOnCollection = useCallback( + (collection: Collection) => { + collectionService.updateCollection(collection.id, () => collection); + close(); + }, + [close, collectionService] + ); + const onCancel = useCallback(() => { + close(); + }, [close]); + + if (!collection) { + return null; + } + + return ( + + + + ); +}; diff --git a/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx b/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx similarity index 96% rename from packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx rename to packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx index ddd19a3999..8f1b894ece 100644 --- a/packages/frontend/core/src/components/page-list/view/edit-collection/rules-mode.tsx +++ b/packages/frontend/core/src/desktop/dialogs/collection-editor/rules-mode.tsx @@ -1,4 +1,13 @@ import { Button, IconButton, Tooltip } from '@affine/component'; +import type { AllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config'; +import { + AffineShapeIcon, + FilterList, + filterPageByRules, + List, + type ListItem, + ListScrollContainer, +} from '@affine/core/components/page-list'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import type { Collection } from '@affine/env/filter'; import { Trans, useI18n } from '@affine/i18n'; @@ -15,12 +24,6 @@ import clsx from 'clsx'; import type { ReactNode } from 'react'; import { useCallback, useMemo, useState } from 'react'; -import type { AllPageListConfig } from '../../../hooks/affine/use-all-page-list-config'; -import { FilterList } from '../../filter'; -import { List, ListScrollContainer } from '../../list'; -import type { ListItem } from '../../types'; -import { filterPageByRules } from '../../use-collection-manager'; -import { AffineShapeIcon } from '../affine-shape'; import * as styles from './edit-collection.css'; export const RulesMode = ({ diff --git a/packages/frontend/core/src/modules/create-workspace/views/dialog.css.ts b/packages/frontend/core/src/desktop/dialogs/create-workspace/dialog.css.ts similarity index 100% rename from packages/frontend/core/src/modules/create-workspace/views/dialog.css.ts rename to packages/frontend/core/src/desktop/dialogs/create-workspace/dialog.css.ts diff --git a/packages/frontend/core/src/modules/create-workspace/views/dialog.tsx b/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx similarity index 64% rename from packages/frontend/core/src/modules/create-workspace/views/dialog.tsx rename to packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx index fffb57bb80..efc8efc9d1 100644 --- a/packages/frontend/core/src/modules/create-workspace/views/dialog.tsx +++ b/packages/frontend/core/src/desktop/dialogs/create-workspace/index.tsx @@ -1,9 +1,13 @@ -import { Avatar, ConfirmModal, Input, Switch, toast } from '@affine/component'; +import { Avatar, ConfirmModal, Input, Switch } from '@affine/component'; import type { ConfirmModalProps } from '@affine/component/ui/modal'; import { CloudSvg } from '@affine/core/components/affine/share-page-modal/cloud-svg'; import { authAtom } from '@affine/core/components/atoms'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; -import { DebugLogger } from '@affine/debug'; +import { AuthService } from '@affine/core/modules/cloud'; +import { + type DialogComponentProps, + type GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; @@ -11,21 +15,14 @@ import { FeatureFlagService, useLiveData, useService, - useServiceOptional, WorkspacesService, } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; -import { useCallback, useLayoutEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; -import { AuthService } from '../../../modules/cloud'; -import { _addLocalWorkspace } from '../../../modules/workspace-engine'; import { buildShowcaseWorkspace } from '../../../utils/first-app-data'; -import { DesktopApiService } from '../../desktop-api'; -import { CreateWorkspaceDialogService } from '../services/dialog'; import * as styles from './dialog.css'; -const logger = new DebugLogger('CreateWorkspaceModal'); - interface NameWorkspaceContentProps extends ConfirmModalProps { loading: boolean; onConfirmName: ( @@ -157,53 +154,11 @@ const NameWorkspaceContent = ({ ); }; -const CreateWorkspaceDialog = () => { - const createWorkspaceDialogService = useService(CreateWorkspaceDialogService); - const mode = useLiveData(createWorkspaceDialogService.dialog.mode$); - const t = useI18n(); +export const CreateWorkspaceDialog = ({ + close, +}: DialogComponentProps) => { const workspacesService = useService(WorkspacesService); const [loading, setLoading] = useState(false); - const electronApi = useServiceOptional(DesktopApiService); - - // TODO(@Peng): maybe refactor using xstate? - useLayoutEffect(() => { - let canceled = false; - // if mode changed, reset step - if (mode === 'add') { - // a hack for now - // when adding a workspace, we will immediately let user select a db file - // after it is done, it will effectively add a new workspace to app-data folder - // so after that, we will be able to load it via importLocalWorkspace - (async () => { - if (!electronApi) { - return; - } - logger.info('load db file'); - const result = await electronApi.handler.dialog.loadDBFile(); - if (result.workspaceId && !canceled) { - _addLocalWorkspace(result.workspaceId); - workspacesService.list.revalidate(); - createWorkspaceDialogService.dialog.callback({ - meta: { - flavour: WorkspaceFlavour.LOCAL, - id: result.workspaceId, - }, - }); - } else if (result.error || result.canceled) { - if (result.error) { - toast(t[result.error]()); - } - createWorkspaceDialogService.dialog.callback(undefined); - createWorkspaceDialogService.dialog.close(); - } - })().catch(err => { - console.error(err); - }); - } - return () => { - canceled = true; - }; - }, [createWorkspaceDialogService, electronApi, mode, t, workspacesService]); const onConfirmName = useAsyncCallback( async (name: string, workspaceFlavour: WorkspaceFlavour) => { @@ -218,40 +173,28 @@ const CreateWorkspaceDialog = () => { workspaceFlavour, name ); - createWorkspaceDialogService.dialog.callback({ meta, defaultDocId }); - createWorkspaceDialogService.dialog.close(); + close({ metadata: meta, defaultDocId }); setLoading(false); }, - [createWorkspaceDialogService.dialog, loading, workspacesService] + [loading, workspacesService, close] ); const onOpenChange = useCallback( (open: boolean) => { if (!open) { - createWorkspaceDialogService.dialog.close(); + close(); } }, - [createWorkspaceDialogService] + [close] ); - if (mode === 'new') { - return ( - - ); - } else { - return null; - } -}; - -export const CreateWorkspaceDialogProvider = () => { - const createWorkspaceDialogService = useService(CreateWorkspaceDialogService); - const isOpen = useLiveData(createWorkspaceDialogService.dialog.isOpen$); - - return isOpen ? : null; + return ( + + ); }; diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx new file mode 100644 index 0000000000..04231c7abc --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/index.tsx @@ -0,0 +1,62 @@ +import { Modal, Scrollable } from '@affine/component'; +import { BlocksuiteHeaderTitle } from '@affine/core/components/blocksuite/block-suite-header/title'; +import type { DialogComponentProps } from '@affine/core/modules/dialogs'; +import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; +import type { Doc } from '@toeverything/infra'; +import { DocsService, FrameworkScope, useService } from '@toeverything/infra'; +import { useEffect, useState } from 'react'; + +import { InfoTable } from './info-modal'; +import * as styles from './styles.css'; + +export const DocInfoDialog = ({ + close, + docId, +}: DialogComponentProps) => { + const docsService = useService(DocsService); + + const [doc, setDoc] = useState(null); + useEffect(() => { + if (!docId) return; + const docRef = docsService.open(docId); + setDoc(docRef.doc); + return () => { + docRef.release(); + setDoc(null); + }; + }, [docId, docsService]); + + if (!doc || !docId) return null; + + return ( + + close()} + withoutCloseButton + > + + +
+ +
+ close()} /> +
+ +
+
+
+ ); +}; diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/info-modal.css.ts b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.css.ts similarity index 100% rename from packages/frontend/core/src/components/doc-properties/info-modal/info-modal.css.ts rename to packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.css.ts diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/info-modal.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx similarity index 62% rename from packages/frontend/core/src/components/doc-properties/info-modal/info-modal.tsx rename to packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx index e960094613..b7ab1719fa 100644 --- a/packages/frontend/core/src/components/doc-properties/info-modal/info-modal.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx @@ -1,99 +1,27 @@ import { Button, Divider, - type InlineEditHandle, Menu, - Modal, PropertyCollapsibleContent, PropertyCollapsibleSection, - Scrollable, } from '@affine/component'; -import { - DocDatabaseBacklinkInfo, - DocInfoService, -} from '@affine/core/modules/doc-info'; +import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property'; +import { DocPropertyRow } from '@affine/core/components/doc-properties/table'; +import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import { useI18n } from '@affine/i18n'; import { PlusIcon } from '@blocksuite/icons/rc'; -import type { Doc } from '@toeverything/infra'; import { DocsService, - FrameworkScope, LiveData, useLiveData, - useService, useServices, } from '@toeverything/infra'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useMemo, useState } from 'react'; -import { BlocksuiteHeaderTitle } from '../../blocksuite/block-suite-header/title'; -import { CreatePropertyMenuItems } from '../menu/create-doc-property'; -import { DocPropertyRow } from '../table'; import * as styles from './info-modal.css'; import { LinksRow } from './links-row'; -export const InfoModal = () => { - const modal = useService(DocInfoService).modal; - const docId = useLiveData(modal.docId$); - const docsService = useService(DocsService); - - const [doc, setDoc] = useState(null); - useEffect(() => { - if (!docId) return; - const docRef = docsService.open(docId); - setDoc(docRef.doc); - return () => { - docRef.release(); - setDoc(null); - }; - }, [docId, docsService]); - - if (!doc || !docId) return null; - - return ( - - - - ); -}; - -const InfoModalOpened = ({ docId }: { docId: string }) => { - const modal = useService(DocInfoService).modal; - - const titleInputHandleRef = useRef(null); - const handleClose = useCallback(() => { - modal.close(); - }, [modal]); - - return ( - modal.onOpenChange(v)} - withoutCloseButton - > - - -
- -
- -
- -
-
- ); -}; - export const InfoTable = ({ onClose, docId, diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/links-row.css.ts b/packages/frontend/core/src/desktop/dialogs/doc-info/links-row.css.ts similarity index 100% rename from packages/frontend/core/src/components/doc-properties/info-modal/links-row.css.ts rename to packages/frontend/core/src/desktop/dialogs/doc-info/links-row.css.ts diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/links-row.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/links-row.tsx similarity index 90% rename from packages/frontend/core/src/components/doc-properties/info-modal/links-row.tsx rename to packages/frontend/core/src/desktop/dialogs/doc-info/links-row.tsx index cbc133a272..8be18ad8bc 100644 --- a/packages/frontend/core/src/components/doc-properties/info-modal/links-row.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/links-row.tsx @@ -1,8 +1,8 @@ import { PropertyCollapsibleSection } from '@affine/component'; +import { AffinePageReference } from '@affine/core/components/affine/reference-link'; import type { Backlink, Link } from '@affine/core/modules/doc-link'; import type { MouseEvent } from 'react'; -import { AffinePageReference } from '../../affine/reference-link'; import * as styles from './links-row.css'; export const LinksRow = ({ diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/styles.css.ts b/packages/frontend/core/src/desktop/dialogs/doc-info/styles.css.ts new file mode 100644 index 0000000000..a858d161af --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/styles.css.ts @@ -0,0 +1,84 @@ +import { cssVar } from '@toeverything/theme'; +import { cssVarV2 } from '@toeverything/theme/v2'; +import { globalStyle, style } from '@vanilla-extract/css'; + +export const container = style({ + maxWidth: 480, + minWidth: 360, + padding: '20px 0', + alignSelf: 'start', + marginTop: '120px', +}); + +export const titleContainer = style({ + display: 'flex', + width: '100%', + flexDirection: 'column', +}); + +export const titleStyle = style({ + fontSize: cssVar('fontH6'), + fontWeight: '600', +}); + +export const rowNameContainer = style({ + display: 'flex', + flexDirection: 'row', + gap: 6, + padding: 6, + width: '160px', +}); + +export const viewport = style({ + maxHeight: 'calc(100vh - 220px)', + padding: '0 24px', +}); + +export const scrollBar = style({ + width: 6, + transform: 'translateX(-4px)', +}); + +export const hiddenInput = style({ + width: '0', + height: '0', + position: 'absolute', +}); + +export const timeRow = style({ + marginTop: 20, + borderBottom: 4, +}); + +export const tableBodyRoot = style({ + display: 'flex', + flexDirection: 'column', + position: 'relative', +}); + +export const addPropertyButton = style({ + alignSelf: 'flex-start', + fontSize: cssVar('fontSm'), + color: `${cssVarV2('text/secondary')}`, + padding: '0 4px', + height: 36, + fontWeight: 400, + gap: 6, + '@media': { + print: { + display: 'none', + }, + }, + selectors: { + [`[data-property-collapsed="true"] &`]: { + display: 'none', + }, + }, +}); +globalStyle(`${addPropertyButton} svg`, { + fontSize: 16, + color: cssVarV2('icon/secondary'), +}); +globalStyle(`${addPropertyButton}:hover svg`, { + color: cssVarV2('icon/primary'), +}); diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/time-row.css.ts b/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.css.ts similarity index 100% rename from packages/frontend/core/src/components/doc-properties/info-modal/time-row.css.ts rename to packages/frontend/core/src/desktop/dialogs/doc-info/time-row.css.ts diff --git a/packages/frontend/core/src/components/doc-properties/info-modal/time-row.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx similarity index 100% rename from packages/frontend/core/src/components/doc-properties/info-modal/time-row.tsx rename to packages/frontend/core/src/desktop/dialogs/doc-info/time-row.tsx diff --git a/packages/frontend/core/src/modules/import-template/views/dialog.css.ts b/packages/frontend/core/src/desktop/dialogs/import-template/dialog.css.ts similarity index 100% rename from packages/frontend/core/src/modules/import-template/views/dialog.css.ts rename to packages/frontend/core/src/desktop/dialogs/import-template/dialog.css.ts diff --git a/packages/frontend/core/src/modules/import-template/views/dialog.tsx b/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx similarity index 86% rename from packages/frontend/core/src/modules/import-template/views/dialog.tsx rename to packages/frontend/core/src/desktop/dialogs/import-template/index.tsx index 86f25f46d4..e4e9e263e1 100644 --- a/packages/frontend/core/src/modules/import-template/views/dialog.tsx +++ b/packages/frontend/core/src/desktop/dialogs/import-template/index.tsx @@ -3,6 +3,15 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { useWorkspaceName } from '@affine/core/components/hooks/use-workspace-info'; import { WorkspaceSelector } from '@affine/core/components/workspace-selector'; +import { AuthService } from '@affine/core/modules/cloud'; +import { + type DialogComponentProps, + type GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import { + ImportTemplateService, + TemplateDownloaderService, +} from '@affine/core/modules/import-template'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; import type { DocMode } from '@blocksuite/affine/blocks'; @@ -16,11 +25,6 @@ import { import { cssVar } from '@toeverything/theme'; import { useCallback, useEffect, useState } from 'react'; -import { AuthService } from '../../cloud'; -import type { CreateWorkspaceCallbackPayload } from '../../create-workspace'; -import { ImportTemplateDialogService } from '../services/dialog'; -import { TemplateDownloaderService } from '../services/downloader'; -import { ImportTemplateService } from '../services/import'; import * as styles from './dialog.css'; const Dialog = ({ @@ -102,8 +106,8 @@ const Dialog = ({ ); const handleCreatedWorkspace = useCallback( - (payload: CreateWorkspaceCallbackPayload) => { - return setSelectedWorkspace(payload.meta); + (payload: { metadata: WorkspaceMetadata; defaultDocId?: string }) => { + return setSelectedWorkspace(payload.metadata); }, [] ); @@ -224,30 +228,29 @@ const Dialog = ({ ); }; -export const ImportTemplateDialogProvider = () => { - const importTemplateDialogService = useService(ImportTemplateDialogService); - const isOpen = useLiveData(importTemplateDialogService.dialog.isOpen$); - const template = useLiveData(importTemplateDialogService.dialog.template$); - +export const ImportTemplateDialog = ({ + close, + snapshotUrl, + templateName, + templateMode, +}: DialogComponentProps) => { return ( importTemplateDialogService.dialog.close()} + onOpenChange={() => close()} > - {template && ( - importTemplateDialogService.dialog.close()} - /> - )} + close()} + /> ); }; diff --git a/packages/frontend/core/src/desktop/dialogs/import-workspace/dialog.css.ts b/packages/frontend/core/src/desktop/dialogs/import-workspace/dialog.css.ts new file mode 100644 index 0000000000..5b0ede3fe8 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/import-workspace/dialog.css.ts @@ -0,0 +1,77 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + +export const header = style({ + position: 'relative', + marginTop: '44px', +}); + +export const subTitle = style({ + fontSize: cssVar('fontSm'), + color: cssVar('textPrimaryColor'), + fontWeight: 600, +}); + +export const avatarWrapper = style({ + display: 'flex', + margin: '10px 0', +}); + +export const workspaceNameWrapper = style({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + padding: '12px 0', +}); +export const affineCloudWrapper = style({ + display: 'flex', + flexDirection: 'column', + gap: '6px', + paddingTop: '10px', +}); + +export const card = style({ + padding: '12px', + display: 'flex', + alignItems: 'center', + borderRadius: '8px', + backgroundColor: cssVar('backgroundSecondaryColor'), + minHeight: '114px', + position: 'relative', +}); + +export const cardText = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + width: '100%', + gap: '12px', +}); + +export const cardTitle = style({ + fontSize: cssVar('fontBase'), + color: cssVar('textPrimaryColor'), + display: 'flex', + justifyContent: 'space-between', +}); +export const cardDescription = style({ + fontSize: cssVar('fontXs'), + color: cssVar('textSecondaryColor'), + maxWidth: '288px', +}); + +export const cloudTips = style({ + fontSize: cssVar('fontXs'), + color: cssVar('textSecondaryColor'), +}); + +export const cloudSvgContainer = style({ + width: '146px', + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + position: 'absolute', + bottom: '0', + right: '0', + pointerEvents: 'none', +}); diff --git a/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx b/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx new file mode 100644 index 0000000000..0c4af461c0 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/import-workspace/index.tsx @@ -0,0 +1,59 @@ +import { toast } from '@affine/component'; +import { + type DialogComponentProps, + type GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import { _addLocalWorkspace } from '@affine/core/modules/workspace-engine'; +import { DebugLogger } from '@affine/debug'; +import { apis } from '@affine/electron-api'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { useI18n } from '@affine/i18n'; +import { useService, WorkspacesService } from '@toeverything/infra'; +import { useLayoutEffect } from 'react'; + +const logger = new DebugLogger('ImportWorkspaceDialog'); + +export const ImportWorkspaceDialog = ({ + close, +}: DialogComponentProps) => { + const t = useI18n(); + const workspacesService = useService(WorkspacesService); + + // TODO(@Peng): maybe refactor using xstate? + useLayoutEffect(() => { + let canceled = false; + // a hack for now + // when adding a workspace, we will immediately let user select a db file + // after it is done, it will effectively add a new workspace to app-data folder + // so after that, we will be able to load it via importLocalWorkspace + (async () => { + if (!apis) { + return; + } + logger.info('load db file'); + const result = await apis.dialog.loadDBFile(); + if (result.workspaceId && !canceled) { + _addLocalWorkspace(result.workspaceId); + workspacesService.list.revalidate(); + close({ + workspace: { + flavour: WorkspaceFlavour.LOCAL, + id: result.workspaceId, + }, + }); + } else if (result.error || result.canceled) { + if (result.error) { + toast(t[result.error]()); + } + close(); + } + })().catch(err => { + console.error(err); + }); + return () => { + canceled = true; + }; + }, [close, t, workspacesService]); + + return null; +}; diff --git a/packages/frontend/core/src/desktop/dialogs/import/index.tsx b/packages/frontend/core/src/desktop/dialogs/import/index.tsx new file mode 100644 index 0000000000..174ace83c2 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/import/index.tsx @@ -0,0 +1,365 @@ +import { Button, IconButton, Modal } from '@affine/component'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import type { + DialogComponentProps, + GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import { UrlService } from '@affine/core/modules/url'; +import { WorkbenchService } from '@affine/core/modules/workbench'; +import { useI18n } from '@affine/i18n'; +import { + MarkdownTransformer, + NotionHtmlTransformer, + openFileOrFiles, +} from '@blocksuite/affine/blocks'; +import type { DocCollection } from '@blocksuite/affine/store'; +import { + ExportToMarkdownIcon, + HelpIcon, + NotionIcon, +} from '@blocksuite/icons/rc'; +import { useService, WorkspaceService } from '@toeverything/infra'; +import { cssVar } from '@toeverything/theme'; +import { cssVarV2 } from '@toeverything/theme/v2'; +import { type ReactElement, useCallback, useState } from 'react'; + +import * as style from './styles.css'; + +type ImportType = 'markdown' | 'markdownZip' | 'notion'; +type AcceptType = 'Markdown' | 'Zip'; +type Status = 'idle' | 'importing' | 'success' | 'error'; + +type ImportConfig = { + fileOptions: { acceptType: AcceptType; multiple: boolean }; + importFunction: ( + docCollection: DocCollection, + file: File | File[] + ) => Promise; +}; + +const DISCORD_URL = 'https://discord.gg/whd5mjYqVw'; + +const importOptions = [ + { + label: 'com.affine.import.markdown-files', + prefixIcon: ( + + ), + testId: 'editor-option-menu-import-markdown-files', + type: 'markdown' as ImportType, + }, + { + label: 'com.affine.import.markdown-with-media-files', + prefixIcon: ( + + ), + testId: 'editor-option-menu-import-markdown-with-media', + type: 'markdownZip' as ImportType, + }, + { + label: 'com.affine.import.notion', + prefixIcon: , + suffixIcon: ( + + ), + suffixTooltip: 'com.affine.import.notion.tooltip', + testId: 'editor-option-menu-import-notion', + type: 'notion' as ImportType, + }, +]; + +const importConfigs: Record = { + markdown: { + fileOptions: { acceptType: 'Markdown', multiple: true }, + importFunction: async (docCollection, files) => { + if (!Array.isArray(files)) { + throw new Error('Expected an array of files for markdown files import'); + } + const pageIds: string[] = []; + for (const file of files) { + const text = await file.text(); + const fileName = file.name.split('.').slice(0, -1).join('.'); + const pageId = await MarkdownTransformer.importMarkdownToDoc({ + collection: docCollection, + markdown: text, + fileName, + }); + if (pageId) pageIds.push(pageId); + } + return pageIds; + }, + }, + markdownZip: { + fileOptions: { acceptType: 'Zip', multiple: false }, + importFunction: async (docCollection, file) => { + if (Array.isArray(file)) { + throw new Error('Expected a single zip file for markdownZip import'); + } + return MarkdownTransformer.importMarkdownZip({ + collection: docCollection, + imported: file, + }); + }, + }, + notion: { + fileOptions: { acceptType: 'Zip', multiple: false }, + importFunction: async (docCollection, file) => { + if (Array.isArray(file)) { + throw new Error('Expected a single zip file for notion import'); + } + const { pageIds } = await NotionHtmlTransformer.importNotionZip({ + collection: docCollection, + imported: file, + }); + return pageIds; + }, + }, +}; + +const ImportOptionItem = ({ + label, + prefixIcon, + suffixIcon, + suffixTooltip, + type, + onImport, +}: { + label: string; + prefixIcon: ReactElement; + suffixIcon?: ReactElement; + suffixTooltip?: string; + type: ImportType; + onImport: (type: ImportType) => void; +}) => { + const t = useI18n(); + return ( +
onImport(type)}> + {prefixIcon} +
{t[label]()}
+ {suffixIcon && ( + + )} +
+ ); +}; + +const ImportOptions = ({ + onImport, +}: { + onImport: (type: ImportType) => void; +}) => { + const t = useI18n(); + return ( + <> +
{t['Import']()}
+
+ {importOptions.map( + ({ label, prefixIcon, suffixIcon, suffixTooltip, testId, type }) => ( + + ) + )} +
+
+ {t['com.affine.import.modal.tip']()}{' '} + + Discord + {' '} + . +
+ + ); +}; + +const ImportingStatus = () => { + const t = useI18n(); + return ( + <> +
+ {t['com.affine.import.status.importing.title']()} +
+

+ {t['com.affine.import.status.importing.message']()} +

+ + ); +}; + +const SuccessStatus = ({ onComplete }: { onComplete: () => void }) => { + const t = useI18n(); + return ( + <> +
+ {t['com.affine.import.status.success.title']()} +
+

+ {t['com.affine.import.status.success.message']()}{' '} + + Discord + + . +

+
+ +
+ + ); +}; + +const ErrorStatus = ({ + error, + onRetry, +}: { + error: string | null; + onRetry: () => void; +}) => { + const t = useI18n(); + const urlService = useService(UrlService); + return ( + <> +
+ {t['com.affine.import.status.failed.title']()} +
+

+ {error || 'Unknown error occurred'} +

+
+ + +
+ + ); +}; + +export const ImportDialog = ({ + close, +}: DialogComponentProps) => { + const t = useI18n(); + const [status, setStatus] = useState('idle'); + const [importError, setImportError] = useState(null); + const [pageIds, setPageIds] = useState([]); + const workspace = useService(WorkspaceService).workspace; + const workbench = useService(WorkbenchService).workbench; + const docCollection = workspace.docCollection; + + const handleImport = useAsyncCallback( + async (type: ImportType) => { + setImportError(null); + try { + const importConfig = importConfigs[type]; + const file = await openFileOrFiles(importConfig.fileOptions); + + if (!file || (Array.isArray(file) && file.length === 0)) { + throw new Error( + t['com.affine.import.status.failed.message.no-file-selected']() + ); + } + + setStatus('importing'); + + const pageIds = await importConfig.importFunction(docCollection, file); + + setPageIds(pageIds); + setStatus('success'); + } catch (error) { + setImportError( + error instanceof Error ? error.message : 'Unknown error occurred' + ); + setStatus('error'); + } + }, + [docCollection, t] + ); + + const handleComplete = useCallback(() => { + if (pageIds.length > 1) { + workbench.openAll(); + } else if (pageIds.length === 1) { + workbench.openDoc(pageIds[0]); + } + close(); + }, [pageIds, close, workbench]); + + const handleRetry = () => { + setStatus('idle'); + }; + + const statusComponents = { + idle: , + importing: , + success: , + error: , + }; + + return ( + { + close(); + }} + width={480} + contentOptions={{ + ['data-testid' as string]: 'import-modal', + style: { + maxHeight: '85vh', + maxWidth: '70vw', + minHeight: '126px', + padding: 0, + overflow: 'hidden', + display: 'flex', + background: cssVarV2('layer/background/primary'), + }, + }} + closeButtonOptions={{ + className: style.closeButton, + }} + withoutCloseButton={status === 'importing'} + persistent={status === 'importing'} + > +
+ {statusComponents[status]} +
+
+ ); +}; diff --git a/packages/frontend/core/src/desktop/dialogs/import/styles.css.ts b/packages/frontend/core/src/desktop/dialogs/import/styles.css.ts new file mode 100644 index 0000000000..3876a60bb6 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/import/styles.css.ts @@ -0,0 +1,110 @@ +import { cssVar } from '@toeverything/theme'; +import { cssVarV2 } from '@toeverything/theme/v2'; +import { style } from '@vanilla-extract/css'; + +export const importModalContainer = style({ + width: '100%', + height: '100%', + display: 'flex', + boxSizing: 'border-box', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '20px 24px', + gap: '12px', +}); + +export const importModalTitle = style({ + width: '100%', + height: 'auto', + fontSize: cssVar('fontH6'), + fontWeight: '600', + lineHeight: cssVar('lineHeight'), +}); + +export const importModalContent = style({ + width: '100%', + flex: 1, + display: 'flex', + flexDirection: 'column', + gap: '12px', +}); + +export const closeButton = style({ + top: '24px', + right: '24px', +}); + +export const importModalTip = style({ + width: '100%', + height: 'auto', + fontSize: cssVar('fontSm'), + lineHeight: cssVar('lineHeight'), + fontWeight: '400', + color: cssVar('textSecondaryColor'), +}); + +export const link = style({ + color: cssVar('linkColor'), + cursor: 'pointer', +}); + +export const importStatusContent = style({ + width: '100%', + fontSize: cssVar('fontBase'), + lineHeight: cssVar('lineHeight'), + fontWeight: '400', + color: cssVar('textPrimaryColor'), +}); + +export const importModalButtonContainer = style({ + width: '100%', + display: 'flex', + flexDirection: 'row', + gap: '20px', + justifyContent: 'end', + marginTop: '20px', +}); + +export const importItem = style({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + height: 'auto', + gap: '4px', + padding: '8px 12px', + borderRadius: '8px', + border: `1px solid ${cssVarV2('layer/insideBorder/border')}`, + background: cssVarV2('button/secondary'), + selectors: { + '&:hover': { + background: cssVarV2('layer/background/hoverOverlay'), + cursor: 'pointer', + transition: 'background .30s', + }, + }, +}); + +export const importItemLabel = style({ + display: 'flex', + alignItems: 'center', + padding: '0 4px', + textAlign: 'left', + flex: 1, + color: cssVar('textPrimaryColor'), + fontSize: cssVar('fontBase'), + lineHeight: cssVar('lineHeight'), + fontWeight: '500', + whiteSpace: 'nowrap', + overflow: 'hidden', +}); + +export const importItemPrefix = style({ + marginRight: 'auto', +}); + +export const importItemSuffix = style({ + marginLeft: 'auto', +}); diff --git a/packages/frontend/core/src/desktop/dialogs/index.tsx b/packages/frontend/core/src/desktop/dialogs/index.tsx new file mode 100644 index 0000000000..453cc271bb --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/index.tsx @@ -0,0 +1,96 @@ +import { AuthModal } from '@affine/core/components/affine/auth'; +import { + type DialogComponentProps, + type GLOBAL_DIALOG_SCHEMA, + GlobalDialogService, + WorkspaceDialogService, +} from '@affine/core/modules/dialogs'; +import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; +import { useLiveData, useService } from '@toeverything/infra'; + +import { CollectionEditorDialog } from './collection-editor'; +import { CreateWorkspaceDialog } from './create-workspace'; +import { DocInfoDialog } from './doc-info'; +import { ImportDialog } from './import'; +import { ImportTemplateDialog } from './import-template'; +import { ImportWorkspaceDialog } from './import-workspace'; +import { CollectionSelectorDialog } from './selectors/collection'; +import { DocSelectorDialog } from './selectors/doc'; +import { TagSelectorDialog } from './selectors/tag'; +import { SettingDialog } from './setting'; + +const GLOBAL_DIALOGS = { + 'create-workspace': CreateWorkspaceDialog, + 'import-workspace': ImportWorkspaceDialog, + 'import-template': ImportTemplateDialog, + setting: SettingDialog, + import: ImportDialog, +} satisfies { + [key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC< + DialogComponentProps + >; +}; + +const WORKSPACE_DIALOGS = { + 'doc-info': DocInfoDialog, + 'collection-editor': CollectionEditorDialog, + 'tag-selector': TagSelectorDialog, + 'doc-selector': DocSelectorDialog, + 'collection-selector': CollectionSelectorDialog, +} satisfies { + [key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC< + DialogComponentProps + >; +}; + +export const GlobalDialogs = () => { + const globalDialogService = useService(GlobalDialogService); + const dialogs = useLiveData(globalDialogService.dialogs$); + return ( + <> + {dialogs.map(dialog => { + const DialogComponent = + GLOBAL_DIALOGS[dialog.type as keyof typeof GLOBAL_DIALOGS]; + if (!DialogComponent) { + return null; + } + return ( + { + globalDialogService.close(dialog.id, result); + }} + /> + ); + })} + + + + ); +}; + +export const WorkspaceDialogs = () => { + const workspaceDialogService = useService(WorkspaceDialogService); + const dialogs = useLiveData(workspaceDialogService.dialogs$); + return ( + <> + {dialogs.map(dialog => { + const DialogComponent = + WORKSPACE_DIALOGS[dialog.type as keyof typeof WORKSPACE_DIALOGS]; + if (!DialogComponent) { + return null; + } + return ( + { + workspaceDialogService.close(dialog.id, result); + }} + /> + ); + })} + + ); +}; diff --git a/packages/frontend/core/src/components/page-list/collections/select-collection.tsx b/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx similarity index 53% rename from packages/frontend/core/src/components/page-list/collections/select-collection.tsx rename to packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx index 9ffeb93282..39a3c26c93 100644 --- a/packages/frontend/core/src/components/page-list/collections/select-collection.tsx +++ b/packages/frontend/core/src/desktop/dialogs/selectors/collection.tsx @@ -1,19 +1,23 @@ -import { toast } from '@affine/component'; +import { Modal, toast } from '@affine/component'; +import { + collectionHeaderColsDef, + CollectionListItemRenderer, + type CollectionMeta, + FavoriteTag, + type ListItem, + ListTableHeader, + VirtualizedList, +} from '@affine/core/components/page-list'; +import { SelectorLayout } from '@affine/core/components/page-list/selector/selector-layout'; import { CollectionService } from '@affine/core/modules/collection'; +import type { DialogComponentProps } from '@affine/core/modules/dialogs'; +import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { cssVar } from '@toeverything/theme'; import { useCallback, useMemo, useState } from 'react'; -import { FavoriteTag } from '../components/favorite-tag'; -import { collectionHeaderColsDef } from '../header-col-def'; -import { CollectionListItemRenderer } from '../page-group'; -import { ListTableHeader } from '../page-header'; -import type { BaseSelectorDialogProps } from '../selector'; -import { SelectorLayout } from '../selector/selector-layout'; -import type { CollectionMeta, ListItem } from '../types'; -import { VirtualizedList } from '../virtualized-list'; - const FavoriteOperation = ({ collection }: { collection: ListItem }) => { const t = useI18n(); const favAdapter = useService(CompatibleFavoriteItemsAdapter); @@ -39,17 +43,16 @@ const FavoriteOperation = ({ collection }: { collection: ListItem }) => { ); }; -export const SelectCollection = ({ - init = [], - onCancel, - onConfirm, -}: BaseSelectorDialogProps) => { +export const CollectionSelectorDialog = ({ + close, + init: selectedCollectionIds, +}: DialogComponentProps) => { const t = useI18n(); const collectionService = useService(CollectionService); const workspace = useService(WorkspaceService).workspace; const collections = useLiveData(collectionService.collections$); - const [selection, setSelection] = useState(init); + const [selection, setSelection] = useState(selectedCollectionIds); const [keyword, setKeyword] = useState(''); const collectionMetas = useMemo(() => { @@ -80,28 +83,43 @@ export const SelectCollection = ({ }, []); return ( - setSelection([])} - onCancel={() => onCancel?.()} - onConfirm={() => onConfirm?.(selection)} + close()} + withoutCloseButton + width="calc(100% - 32px)" + height="80%" + contentOptions={{ + style: { + padding: 0, + maxWidth: 976, + background: cssVar('backgroundPrimaryColor'), + }, + }} > - - + setSelection([])} + onCancel={() => close()} + onConfirm={() => close(selection)} + > + + + ); }; diff --git a/packages/frontend/core/src/desktop/dialogs/selectors/doc.tsx b/packages/frontend/core/src/desktop/dialogs/selectors/doc.tsx new file mode 100644 index 0000000000..a3578271ae --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/selectors/doc.tsx @@ -0,0 +1,35 @@ +import { Modal } from '@affine/component'; +import { SelectPage } from '@affine/core/components/page-list/docs/select-page'; +import type { + DialogComponentProps, + WORKSPACE_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import { cssVar } from '@toeverything/theme'; + +export const DocSelectorDialog = ({ + close, + init: selectedDocIds, +}: DialogComponentProps) => { + return ( + close()} + withoutCloseButton + width="calc(100% - 32px)" + height="80%" + contentOptions={{ + style: { + padding: 0, + maxWidth: 976, + background: cssVar('backgroundPrimaryColor'), + }, + }} + > + close()} + onConfirm={value => close(value)} + /> + + ); +}; diff --git a/packages/frontend/core/src/components/page-list/tags/select-tag.tsx b/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx similarity index 51% rename from packages/frontend/core/src/components/page-list/tags/select-tag.tsx rename to packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx index 231be68be4..574eb76604 100644 --- a/packages/frontend/core/src/components/page-list/tags/select-tag.tsx +++ b/packages/frontend/core/src/desktop/dialogs/selectors/tag.tsx @@ -1,19 +1,25 @@ -import { toast } from '@affine/component'; +import { Modal, toast } from '@affine/component'; +import { + FavoriteTag, + type ListItem, + ListTableHeader, + tagHeaderColsDef, + TagListItemRenderer, + type TagMeta, + VirtualizedList, +} from '@affine/core/components/page-list'; +import { SelectorLayout } from '@affine/core/components/page-list/selector/selector-layout'; +import type { + DialogComponentProps, + WORKSPACE_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; import { FavoriteService } from '@affine/core/modules/favorite'; import { TagService } from '@affine/core/modules/tag'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; +import { cssVar } from '@toeverything/theme'; import { useCallback, useMemo, useState } from 'react'; -import { FavoriteTag } from '../components/favorite-tag'; -import { tagHeaderColsDef } from '../header-col-def'; -import { TagListItemRenderer } from '../page-group'; -import { ListTableHeader } from '../page-header'; -import type { BaseSelectorDialogProps } from '../selector'; -import { SelectorLayout } from '../selector/selector-layout'; -import type { ListItem, TagMeta } from '../types'; -import { VirtualizedList } from '../virtualized-list'; - const FavoriteOperation = ({ tag }: { tag: ListItem }) => { const t = useI18n(); const favoriteService = useService(FavoriteService); @@ -39,17 +45,16 @@ const FavoriteOperation = ({ tag }: { tag: ListItem }) => { ); }; -export const SelectTag = ({ - init = [], - onConfirm, - onCancel, -}: BaseSelectorDialogProps) => { +export const TagSelectorDialog = ({ + close, + init: selectedTagIds, +}: DialogComponentProps) => { const t = useI18n(); const workspace = useService(WorkspaceService).workspace; const tagList = useService(TagService).tagList; - const [selection, setSelection] = useState(init); + const [selection, setSelection] = useState(selectedTagIds); const [keyword, setKeyword] = useState(''); const tagMetas: TagMeta[] = useLiveData(tagList.tagMetas$); @@ -73,25 +78,40 @@ export const SelectTag = ({ }, []); return ( - onConfirm?.(selection)} - onCancel={onCancel} - onClear={() => setSelection([])} + close()} + withoutCloseButton + width="calc(100% - 32px)" + height="80%" + contentOptions={{ + style: { + padding: 0, + maxWidth: 976, + background: cssVar('backgroundPrimaryColor'), + }, + }} > - - + close(selection)} + onCancel={close} + onClear={() => setSelection([])} + > + + + ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/ai-usage-panel.tsx similarity index 93% rename from packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/account-setting/ai-usage-panel.tsx index ac6d679cb8..af5b07a6f5 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/ai-usage-panel.tsx @@ -1,6 +1,5 @@ import { Button, ErrorMessage, Skeleton } from '@affine/component'; import { SettingRow } from '@affine/component/setting-components'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { ServerConfigService, SubscriptionService, @@ -11,15 +10,18 @@ import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; -import { useSetAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; import { AIResume, AISubscribe } from '../general-setting/plans/ai/actions'; +import type { SettingState } from '../types'; import * as styles from './storage-progress.css'; -export const AIUsagePanel = () => { +export const AIUsagePanel = ({ + onChangeSettingState, +}: { + onChangeSettingState?: (settingState: SettingState) => void; +}) => { const t = useI18n(); - const setOpenSettingModal = useSetAtom(openSettingModalAtom); const serverConfigService = useService(ServerConfigService); const hasPaymentFeature = useLiveData( serverConfigService.serverConfig.features$.map(f => f?.payment) @@ -44,12 +46,11 @@ export const AIUsagePanel = () => { const loadError = useLiveData(copilotQuotaService.copilotQuota.error$); const openBilling = useCallback(() => { - setOpenSettingModal({ - open: true, + onChangeSettingState?.({ activeTab: 'billing', }); track.$.settingsPanel.accountUsage.viewPlans({ plan: SubscriptionPlan.AI }); - }, [setOpenSettingModal]); + }, [onChangeSettingState]); if (loading) { if (loadError) { diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/index.tsx similarity index 90% rename from packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/account-setting/index.tsx index 31d59be2eb..ad437101d8 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/index.tsx @@ -5,8 +5,11 @@ import { } from '@affine/component/setting-components'; import { Avatar } from '@affine/component/ui/avatar'; import { Button } from '@affine/component/ui/button'; +import { authAtom } from '@affine/core/components/atoms'; +import { useSignOut } from '@affine/core/components/hooks/affine/use-sign-out'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; +import { Upload } from '@affine/core/components/pure/file-upload'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; @@ -18,16 +21,10 @@ import { useServices, } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; -import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { AuthService, ServerConfigService } from '../../../../modules/cloud'; -import { - authAtom, - openSettingModalAtom, - openSignOutModalAtom, -} from '../../../atoms'; -import { Upload } from '../../../pure/file-upload'; +import type { SettingState } from '../types'; import { AIUsagePanel } from './ai-usage-panel'; import { StorageProgress } from './storage-progress'; import * as styles from './style.css'; @@ -148,20 +145,22 @@ export const AvatarAndName = () => { ); }; -const StoragePanel = () => { +const StoragePanel = ({ + onChangeSettingState, +}: { + onChangeSettingState?: (settingState: SettingState) => void; +}) => { const t = useI18n(); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); const onUpgrade = useCallback(() => { track.$.settingsPanel.accountUsage.viewPlans({ plan: SubscriptionPlan.Pro, }); - setSettingModalAtom({ - open: true, + onChangeSettingState?.({ activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - }, [setSettingModalAtom]); + }, [onChangeSettingState]); return ( { ); }; -export const AccountSetting: FC = () => { +export const AccountSetting = ({ + onChangeSettingState, +}: { + onChangeSettingState?: (settingState: SettingState) => void; +}) => { const { authService, serverConfigService } = useServices({ AuthService, ServerConfigService, @@ -189,7 +192,7 @@ export const AccountSetting: FC = () => { }, [session]); const account = useEnsureLiveData(session.account$); const setAuthModal = useSetAtom(authAtom); - const setSignOutModal = useSetAtom(openSignOutModalAtom); + const openSignOutModal = useSignOut(); const onChangeEmail = useCallback(() => { setAuthModal({ @@ -211,10 +214,6 @@ export const AccountSetting: FC = () => { }); }, [account.email, account.info?.hasPassword, setAuthModal]); - const onOpenSignOutModal = useCallback(() => { - setSignOutModal(true); - }, [setSignOutModal]); - return ( <> { : t['com.affine.settings.password.action.set']()} - - {serverFeatures?.copilot && } + + {serverFeatures?.copilot && ( + + )} diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/storage-progress.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/account-setting/storage-progress.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.tsx b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/storage-progress.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/account-setting/storage-progress.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/account-setting/storage-progress.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/account-setting/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/account-setting/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/account-setting/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/config.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/config.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/about/config.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/config.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/icons.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/icons.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/about/icons.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/icons.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/index.tsx similarity index 99% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/index.tsx index c9a6bbdf4d..2710b346b0 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/index.tsx @@ -6,7 +6,7 @@ import { } from '@affine/component/setting-components'; import { useAppUpdater } from '@affine/core/components/hooks/use-app-updater'; import { UrlService } from '@affine/core/modules/url'; -import { appIconMap, appNames } from '@affine/core/utils'; +import { appIconMap, appNames } from '@affine/core/utils/channel'; import { useI18n } from '@affine/i18n'; import { mixpanel } from '@affine/track'; import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons/rc'; @@ -24,18 +24,18 @@ import { UpdateCheckSection } from './update-check-section'; export const AboutAffine = () => { const t = useI18n(); + const { appSettings, updateSettings } = useAppSettingHelper(); + const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater(); + const channel = BUILD_CONFIG.appBuildType; + const appIcon = appIconMap[channel]; + const appName = appNames[channel]; const { urlService, featureFlagService } = useServices({ UrlService, FeatureFlagService, }); - const { appSettings, updateSettings } = useAppSettingHelper(); - const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater(); const enableSnapshotImportExport = useLiveData( featureFlagService.flags.enable_snapshot_import_export.$ ); - const channel = BUILD_CONFIG.appBuildType; - const appIcon = appIconMap[channel]; - const appName = appNames[channel]; const onSwitchAutoCheck = useCallback( (checked: boolean) => { diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/about/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/about/update-check-section.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/update-check-section.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/about/update-check-section.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/about/update-check-section.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx similarity index 98% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx index f2e88b6240..d95ec04549 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/appearance/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/appearance/index.tsx @@ -5,6 +5,7 @@ import { SettingRow, SettingWrapper, } from '@affine/component/setting-components'; +import { LanguageMenu } from '@affine/core/components/affine/language-menu'; import { useI18n } from '@affine/i18n'; import { FeatureFlagService, @@ -15,7 +16,6 @@ import { useTheme } from 'next-themes'; import { useCallback, useMemo } from 'react'; import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper'; -import { LanguageMenu } from '../../../language-menu'; import { OpenInAppLinksMenu } from './links'; import { settingWrapper } from './style.css'; import { ThemeEditorSetting } from './theme-editor-setting'; @@ -118,6 +118,7 @@ export const AppearanceSettings = () => { ) : null} + {BUILD_CONFIG.isElectron ? ( { const open = useCallback(() => { if (desktopApi) { - desktopApi.handler.ui.openThemeEditor().catch(console.error); + desktopApi?.handler.ui.openThemeEditor().catch(console.error); } else if (BUILD_CONFIG.isMobileWeb || BUILD_CONFIG.isWeb) { urlService.openPopupWindow(location.origin + '/theme-editor'); } diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/index.tsx similarity index 97% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/index.tsx index 5138e59e14..b4e6ea086d 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/index.tsx @@ -29,14 +29,10 @@ import { track } from '@affine/track'; import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; -import { useSetAtom } from 'jotai'; import { useCallback, useEffect, useState } from 'react'; import { useMutation } from '../../../../../components/hooks/use-mutation'; -import { - openSettingModalAtom, - type PlansScrollAnchor, -} from '../../../../atoms'; +import type { SettingState } from '../../types'; import { CancelAction, ResumeAction } from '../plans/actions'; import { AICancel, AIResume, AISubscribe } from '../plans/ai/actions'; import { AIRedeemCodeButton } from '../plans/ai/actions/redeem'; @@ -64,7 +60,11 @@ const getMessageKey = ( return DescriptionI18NKey[recurring]; }; -export const BillingSettings = () => { +export const BillingSettings = ({ + onChangeSettingState, +}: { + onChangeSettingState: (state: SettingState) => void; +}) => { const t = useI18n(); return ( @@ -76,7 +76,7 @@ export const BillingSettings = () => { - + @@ -85,7 +85,11 @@ export const BillingSettings = () => { ); }; -const SubscriptionSettings = () => { +const SubscriptionSettings = ({ + onChangeSettingState, +}: { + onChangeSettingState: (state: SettingState) => void; +}) => { const t = useI18n(); const subscriptionService = useService(SubscriptionService); useEffect(() => { @@ -99,22 +103,20 @@ const SubscriptionSettings = () => { const isOnetime = useLiveData(subscriptionService.subscription.isOnetimeAI$); const [openCancelModal, setOpenCancelModal] = useState(false); - const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const currentPlan = proSubscription?.plan ?? SubscriptionPlan.Free; const currentRecurring = proSubscription?.recurring ?? SubscriptionRecurring.Monthly; const openPlans = useCallback( - (scrollAnchor?: PlansScrollAnchor) => { + (scrollAnchor?: string) => { track.$.settingsPanel.billing.viewPlans(); - setOpenSettingModalAtom({ - open: true, + onChangeSettingState({ activeTab: 'plans', scrollAnchor: scrollAnchor, }); }, - [setOpenSettingModalAtom] + [onChangeSettingState] ); const gotoCloudPlansSetting = useCallback( () => openPlans('cloudPricingPlan'), diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/style.css.ts similarity index 99% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/style.css.ts index c145251a5a..74cb0f927b 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/billing/style.css.ts @@ -21,7 +21,7 @@ export const currentPlan = style({ flex: '1 0 0', }); export const planAction = style({ - width: 'auto', + width: '100%', marginTop: '8px', }); export const planPrice = style({ diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/connector.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/connector.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/connector.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/connector.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/flow.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/flow.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/flow.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/flow.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/index.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/index.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/mindmap.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/mindmap.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/note.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/note.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/note.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/note.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/pen.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/pen.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/pen.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/pen.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/shape.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/shape.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/shape.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/shape.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/text.json b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/text.json similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/text.json rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/docs/text.json diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/edgeless.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/edgeless.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/edgeless.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/edgeless.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/general.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/general.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/general.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/general.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/index.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/index.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/index.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/index.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/mind-map.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/mind-map.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/note.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/note.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/note.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/note.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/pen.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/pen.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/pen.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/pen.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/point.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/point.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/point.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/point.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/shape.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/shape.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/snapshot.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/snapshot.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/text.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/text.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/text.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/text.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/utils.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/utils.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/utils.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/edgeless/utils.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/general.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/general.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/index.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/index.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/menu.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/menu.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/menu.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/menu.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/page.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/page.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/page.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/page.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/preferences.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/preferences.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/preferences.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/preferences.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/utils.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/utils.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/utils.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/editor/utils.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/arts.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/arts.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/arts.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/arts.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/arts.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/arts.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/arts.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/arts.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/dark-art-svg.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/dark-art-svg.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/dark-art-svg.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/dark-art-svg.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/index.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/light-art-svg.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/light-art-svg.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/light-art-svg.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/experimental-features/light-art-svg.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/icons.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/icons.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/icons.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/icons.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx similarity index 87% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx index 13f9197c15..72c18369be 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/index.tsx @@ -1,4 +1,5 @@ import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature'; +import type { SettingTab } from '@affine/core/modules/dialogs/constant'; import { useI18n } from '@affine/i18n'; import { AppearanceIcon, @@ -16,7 +17,7 @@ import type { ReactElement, SVGProps } from 'react'; import { useEffect } from 'react'; import { AuthService, ServerConfigService } from '../../../../modules/cloud'; -import type { GeneralSettingKey } from '../types'; +import type { SettingState } from '../types'; import { AboutAffine } from './about'; import { AppearanceSettings } from './appearance'; import { BillingSettings } from './billing'; @@ -27,7 +28,7 @@ import { AFFiNEPricingPlans } from './plans'; import { Shortcuts } from './shortcuts'; interface GeneralSettingListItem { - key: GeneralSettingKey; + key: SettingTab; title: string; icon: (props: SVGProps) => ReactElement; testId: string; @@ -118,11 +119,17 @@ export const useGeneralSettingList = (): GeneralSettingList => { }; interface GeneralSettingProps { - generalKey: GeneralSettingKey; + activeTab: SettingTab; + scrollAnchor?: string; + onChangeSettingState: (settingState: SettingState) => void; } -export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => { - switch (generalKey) { +export const GeneralSetting = ({ + activeTab, + scrollAnchor, + onChangeSettingState, +}: GeneralSettingProps) => { + switch (activeTab) { case 'shortcuts': return ; case 'editor': @@ -132,9 +139,9 @@ export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => { case 'about': return ; case 'plans': - return ; + return ; case 'billing': - return ; + return ; case 'experimental-features': return ; default: diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/actions.tsx similarity index 97% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/actions.tsx index 66fbe08c6a..e0fe87bd70 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/actions.tsx @@ -1,3 +1,4 @@ +import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; import { getDowngradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { SubscriptionPlan } from '@affine/graphql'; @@ -8,7 +9,6 @@ import type { PropsWithChildren } from 'react'; import { useEffect, useState } from 'react'; import { AuthService, SubscriptionService } from '../../../../../modules/cloud'; -import { useDowngradeNotify } from '../../../subscription-landing/notify'; import { ConfirmLoadingModal, DowngradeModal } from './modals'; /** diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/cancel.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/cancel.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/index.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/index.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/index.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/index.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/login.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/login.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/redeem.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/redeem.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/redeem.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/redeem.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/resume.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/resume.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/subscribe.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/subscribe.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/subscribe.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/actions/subscribe.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/ai-plan.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/ai-plan.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/ai-plan.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/ai-plan.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/benefits.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/benefits.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/layout.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/layout.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/layout.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/ai/layout.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/checkout-slot.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/checkout-slot.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/checkout-slot.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/checkout-slot.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/cloud-plans.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/cloud-plans.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/icons/bulled-list.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/icons/bulled-list.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/icons/bulled-list.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/icons/bulled-list.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/index.tsx similarity index 81% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/index.tsx index adab1ea611..d496a6b92a 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/index.tsx @@ -11,7 +11,7 @@ import { CloudPlanLayout, PlanLayout } from './layout'; import { PlansSkeleton } from './skeleton'; import * as styles from './style.css'; -const Settings = () => { +const Settings = ({ scrollAnchor }: { scrollAnchor?: string }) => { const subscriptionService = useService(SubscriptionService); const prices = useLiveData(subscriptionService.prices.prices$); @@ -24,13 +24,23 @@ const Settings = () => { return ; } - return } ai={} />; + return ( + } + ai={} + scrollAnchor={scrollAnchor} + /> + ); }; -export const AFFiNEPricingPlans = () => { +export const AFFiNEPricingPlans = ({ + scrollAnchor, +}: { + scrollAnchor?: string; +}) => { return ( - + ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/layout.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/layout.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/layout.tsx similarity index 57% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/layout.tsx index 9de011d541..4140dc4175 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/layout.tsx @@ -1,26 +1,19 @@ -import { Button, Divider, IconButton } from '@affine/component'; +import { Divider, IconButton } from '@affine/component'; import { SettingHeader } from '@affine/component/setting-components'; -import { - openSettingModalAtom, - type PlansScrollAnchor, -} from '@affine/core/components/atoms'; import { useI18n } from '@affine/i18n'; import { ArrowRightBigIcon, ArrowUpSmallIcon } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; import * as ScrollArea from '@radix-ui/react-scroll-area'; -import { useAtom, useAtomValue } from 'jotai'; import { type HtmlHTMLAttributes, type ReactNode, useCallback, - useEffect, useLayoutEffect, useRef, useState, } from 'react'; -import { createPortal, flushSync } from 'react-dom'; +import { flushSync } from 'react-dom'; -import { settingModalScrollContainerAtom } from '../../atoms'; import * as styles from './layout.css'; export const SeeAllLink = () => { @@ -44,6 +37,7 @@ interface PricingCollapsibleProps title?: ReactNode; caption?: ReactNode; } + export const PricingCollapsible = ({ title, caption, @@ -75,35 +69,12 @@ export const PricingCollapsible = ({ export interface PlanLayoutProps { cloud?: ReactNode; ai?: ReactNode; - cloudTip?: boolean; + scrollAnchor?: string; } -export const PlanLayout = ({ cloud, ai, cloudTip }: PlanLayoutProps) => { +export const PlanLayout = ({ cloud, ai, scrollAnchor }: PlanLayoutProps) => { const t = useI18n(); - const [modal, setOpenSettingModal] = useAtom(openSettingModalAtom); - const scrollAnchor = modal.activeTab === 'plans' ? modal.scrollAnchor : null; const plansRootRef = useRef(null); - const cloudScrollTipRef = useRef(null); - const settingModalScrollContainer = useAtomValue( - settingModalScrollContainerAtom - ); - - const updateCloudTipState = useCallback(() => { - if (!cloudTip) return; - const cloudContainer = - plansRootRef.current?.querySelector('#cloudPricingPlan'); - if (!settingModalScrollContainer || !cloudContainer) return; - - const minVisibleHeight = 30; - - const containerRect = settingModalScrollContainer.getBoundingClientRect(); - const cloudTop = - cloudContainer.getBoundingClientRect().top - containerRect.top; - const cloudIntoView = cloudTop < containerRect.height - minVisibleHeight; - if (cloudIntoView) { - settingModalScrollContainer.dataset.cloudVisible = ''; - } - }, [cloudTip, settingModalScrollContainer]); // TODO(@catsjuice): Need a better solution to handle this situation useLayoutEffect(() => { @@ -112,28 +83,9 @@ export const PlanLayout = ({ cloud, ai, cloudTip }: PlanLayoutProps) => { const target = plansRootRef.current?.querySelector(`#${scrollAnchor}`); if (target) { target.scrollIntoView(); - setOpenSettingModal(prev => ({ ...prev, scrollAnchor: undefined })); } }); - }, [scrollAnchor, setOpenSettingModal]); - - useEffect(() => { - if (!settingModalScrollContainer || !cloudScrollTipRef.current) return; - - settingModalScrollContainer.addEventListener('scroll', updateCloudTipState); - updateCloudTipState(); - return () => { - settingModalScrollContainer.removeEventListener( - 'scroll', - updateCloudTipState - ); - }; - }, [settingModalScrollContainer, updateCloudTipState]); - - const scrollToAnchor = useCallback((anchor: PlansScrollAnchor) => { - const target = plansRootRef.current?.querySelector(`#${anchor}`); - target && target.scrollIntoView({ behavior: 'smooth' }); - }, []); + }, [scrollAnchor]); return (
@@ -149,29 +101,6 @@ export const PlanLayout = ({ cloud, ai, cloudTip }: PlanLayoutProps) => { ) : null}
{cloud}
- - {cloudTip && settingModalScrollContainer - ? createPortal( -
-
-
- {t['com.affine.cloud-scroll-tip.title']()} -
-
- {t['com.affine.cloud-scroll-tip.caption']()} -
-
- -
, - settingModalScrollContainer, - 'aiScrollTip' - ) - : null}
); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/assets.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/assets.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/assets.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/assets.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/believer-card.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/believer-card.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/believer-card.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/believer-card.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/believer-card.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/believer-card.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/believer-card.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/believer-card.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/benefits.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/benefits.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/benefits.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/benefits.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/benefits.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/benefits.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/benefits.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/benefits.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/lifetime-plan.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/lifetime-plan.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/lifetime-plan.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/lifetime-plan.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/lifetime/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/lifetime/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/modals.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/modals.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/modals.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/modals.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/plan-card.tsx similarity index 99% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/plan-card.tsx index 3a6c0652f1..369a28d57c 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/plan-card.tsx @@ -1,5 +1,6 @@ import { Button, type ButtonProps } from '@affine/component/ui/button'; import { Tooltip } from '@affine/component/ui/tooltip'; +import { authAtom } from '@affine/core/components/atoms'; import { generateSubscriptionCallbackLink } from '@affine/core/components/hooks/affine/use-subscription-notify'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; @@ -22,7 +23,6 @@ import { nanoid } from 'nanoid'; import type { PropsWithChildren } from 'react'; import { useCallback, useMemo, useState } from 'react'; -import { authAtom } from '../../../../atoms/index'; import { CancelAction, ResumeAction } from './actions'; import { CheckoutSlot } from './checkout-slot'; import type { DynamicPrice, FixedPrice } from './cloud-plans'; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/skeleton.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/skeleton.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/skeleton.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/skeleton.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/shortcuts/index.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/shortcuts/index.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/shortcuts/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/shortcuts/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/general-setting/shortcuts/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/general-setting/shortcuts/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/index.tsx similarity index 64% rename from packages/frontend/core/src/components/affine/setting-modal/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/index.tsx index cf3a4d10fd..675b4ea9d4 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/index.tsx @@ -2,11 +2,12 @@ import { Loading, Scrollable } from '@affine/component'; import { WorkspaceDetailSkeleton } from '@affine/component/setting-components'; import type { ModalProps } from '@affine/component/ui/modal'; import { Modal } from '@affine/component/ui/modal'; -import { - openIssueFeedbackModalAtom, - openStarAFFiNEModalAtom, -} from '@affine/core/components/atoms'; import { AuthService } from '@affine/core/modules/cloud'; +import type { + DialogComponentProps, + GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import type { SettingTab } from '@affine/core/modules/dialogs/constant'; import { Trans } from '@affine/i18n'; import { ContactWithUsIcon } from '@blocksuite/icons/rc'; import { @@ -14,36 +15,32 @@ import { useService, type WorkspaceMetadata, } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { debounce } from 'lodash-es'; import { Suspense, useCallback, - useEffect, useLayoutEffect, useRef, + useState, } from 'react'; import { AccountSetting } from './account-setting'; -import { settingModalScrollContainerAtom } from './atoms'; import { GeneralSetting } from './general-setting'; +import { IssueFeedbackModal } from './issue-feedback-modal'; import { SettingSidebar } from './setting-sidebar'; +import { StarAFFiNEModal } from './star-affine-modal'; import * as style from './style.css'; -import type { ActiveTab, GeneralSettingKey, WorkspaceSubTab } from './types'; -import { GeneralSettingKeys } from './types'; +import type { SettingState } from './types'; import { WorkspaceSetting } from './workspace-setting'; -export interface SettingProps extends ModalProps { - activeTab: ActiveTab; +interface SettingProps extends ModalProps { + activeTab?: SettingTab; workspaceMetadata?: WorkspaceMetadata | null; - onSettingClick: (params: { - activeTab: ActiveTab; - workspaceMetadata: WorkspaceMetadata | null; - }) => void; + onCloseSetting: () => void; } -const isGeneralSetting = (key: string): key is GeneralSettingKey => - GeneralSettingKeys.includes(key as GeneralSettingKey); +const isWorkspaceSetting = (key: string): boolean => + key.startsWith('workspace:'); const CenteredLoading = () => { return ( @@ -54,21 +51,21 @@ const CenteredLoading = () => { }; const SettingModalInner = ({ - activeTab = 'appearance', - workspaceMetadata = null, - onSettingClick, - ...modalProps + activeTab: initialActiveTab = 'appearance', + workspaceMetadata: initialWorkspaceMetadata = null, + onCloseSetting, }: SettingProps) => { + const [settingState, setSettingState] = useState({ + activeTab: initialActiveTab, + activeWorkspaceMetadata: initialWorkspaceMetadata, + scrollAnchor: undefined, + }); const loginStatus = useLiveData(useService(AuthService).session.status$); const modalContentRef = useRef(null); const modalContentWrapperRef = useRef(null); - const setSettingModalScrollContainer = useSetAtom( - settingModalScrollContainerAtom - ); useLayoutEffect(() => { - if (!modalProps.open) return; let animationFrameId: number; const onResize = debounce(() => { cancelAnimationFrame(animationFrameId); @@ -106,23 +103,16 @@ const SettingModalInner = ({ cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', onResize); }; - }, [modalProps.open]); - - useEffect(() => { - setSettingModalScrollContainer(modalContentWrapperRef.current); - return () => { - setSettingModalScrollContainer(null); - }; - }, [setSettingModalScrollContainer]); + }, []); const onTabChange = useCallback( - (key: ActiveTab, meta: WorkspaceMetadata | null) => { - onSettingClick({ activeTab: key, workspaceMetadata: meta }); + (key: SettingTab, meta: WorkspaceMetadata | null) => { + setSettingState({ activeTab: key, activeWorkspaceMetadata: meta }); }, - [onSettingClick] + [setSettingState] ); - const setOpenIssueFeedbackModal = useSetAtom(openIssueFeedbackModalAtom); - const setOpenStarAFFiNEModal = useSetAtom(openStarAFFiNEModalAtom); + const [openIssueFeedbackModal, setOpenIssueFeedbackModal] = useState(false); + const [openStarAFFiNEModal, setOpenStarAFFiNEModal] = useState(false); const handleOpenIssueFeedbackModal = useCallback(() => { setOpenIssueFeedbackModal(true); @@ -135,9 +125,9 @@ const SettingModalInner = ({ return ( <>
}> - {activeTab.startsWith('workspace:') && workspaceMetadata ? ( + {} + {settingState.activeTab === 'account' && + loginStatus === 'authenticated' ? ( + + ) : isWorkspaceSetting(settingState.activeTab) && + settingState.activeWorkspaceMetadata ? ( + ) : !isWorkspaceSetting(settingState.activeTab) ? ( + - ) : null} - {isGeneralSetting(activeTab) ? ( - - ) : null} - {activeTab === 'account' && loginStatus === 'authenticated' ? ( - ) : null}
@@ -183,6 +179,14 @@ const SettingModalInner = ({ }} /> + +
@@ -191,12 +195,11 @@ const SettingModalInner = ({ ); }; -export const SettingModal = ({ - activeTab = 'appearance', - workspaceMetadata = null, - onSettingClick, - ...modalProps -}: SettingProps) => { +export const SettingDialog = ({ + close, + activeTab, + workspaceMetadata, +}: DialogComponentProps) => { return ( close()} > }> diff --git a/packages/frontend/core/src/components/affine/issue-feedback-modal/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/issue-feedback-modal/index.tsx similarity index 78% rename from packages/frontend/core/src/components/affine/issue-feedback-modal/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/issue-feedback-modal/index.tsx index f67e424a3a..60747b8bdb 100644 --- a/packages/frontend/core/src/components/affine/issue-feedback-modal/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/issue-feedback-modal/index.tsx @@ -1,11 +1,14 @@ import { OverlayModal } from '@affine/component'; -import { openIssueFeedbackModalAtom } from '@affine/core/components/atoms'; import { useI18n } from '@affine/i18n'; -import { useAtom } from 'jotai'; -export const IssueFeedbackModal = () => { +export const IssueFeedbackModal = ({ + open, + setOpen, +}: { + open: boolean; + setOpen: (open: boolean) => void; +}) => { const t = useI18n(); - const [open, setOpen] = useAtom(openIssueFeedbackModalAtom); return ( void; selectedWorkspaceId: string | null; @@ -126,7 +126,7 @@ export const SettingSidebar = ({ const tab = e.currentTarget.dataset.eventArg; if (!tab) return; track.$.settingsPanel.menu.openSettings({ to: tab }); - onTabChange(tab as ActiveTab, null); + onTabChange(tab as SettingTab, null); }, [onTabChange] ); @@ -135,12 +135,12 @@ export const SettingSidebar = ({ onTabChange('account', null); }, [onTabChange]); const onWorkspaceSettingClick = useCallback( - (subTab: WorkspaceSubTab, workspaceMetadata: WorkspaceMetadata) => { + (tab: SettingTab, workspaceMetadata: WorkspaceMetadata) => { track.$.settingsPanel.menu.openSettings({ to: 'workspace', - control: subTab, + control: tab, }); - onTabChange(`workspace:${subTab}`, workspaceMetadata); + onTabChange(tab, workspaceMetadata); }, [onTabChange] ); @@ -181,7 +181,7 @@ export const SettingSidebar = ({
@@ -204,14 +204,14 @@ export const SettingSidebar = ({ export const WorkspaceList = ({ onWorkspaceSettingClick, selectedWorkspaceId, - activeSubTab, + activeTab, }: { onWorkspaceSettingClick: ( - subTab: WorkspaceSubTab, + activeTab: SettingTab, workspaceMetadata: WorkspaceMetadata ) => void; selectedWorkspaceId: string | null; - activeSubTab: WorkspaceSubTab; + activeTab: SettingTab; }) => { const workspaces = useLiveData( useService(WorkspacesService).list.workspaces$ @@ -226,8 +226,8 @@ export const WorkspaceList = ({ onClick={subTab => { onWorkspaceSettingClick(subTab, workspace); }} - activeSubTab={ - workspace.id === selectedWorkspaceId ? activeSubTab : undefined + activeTab={ + workspace.id === selectedWorkspaceId ? activeTab : undefined } /> @@ -239,35 +239,37 @@ export const WorkspaceList = ({ const subTabConfigs = [ { - key: 'preference', + key: 'workspace:preference', title: 'com.affine.settings.workspace.preferences', }, { - key: 'properties', + key: 'workspace:properties', title: 'com.affine.settings.workspace.properties', }, ] satisfies { - key: WorkspaceSubTab; + key: SettingTab; title: keyof ReturnType; }[]; const WorkspaceListItem = ({ - activeSubTab, + activeTab, meta, onClick, }: { meta: WorkspaceMetadata; - activeSubTab?: WorkspaceSubTab; - onClick: (subTab: WorkspaceSubTab) => void; + activeTab?: SettingTab; + onClick: (activeTab: SettingTab) => void; }) => { - const { workspaceService, userFeatureService } = useServices({ - WorkspaceService, + const { globalContextService, userFeatureService } = useServices({ + GlobalContextService, UserFeatureService, }); const information = useWorkspaceInfo(meta); const name = information?.name ?? UNTITLED_WORKSPACE_NAME; - const currentWorkspace = workspaceService.workspace; - const isCurrent = currentWorkspace.id === meta.id; + const currentWorkspaceId = useLiveData( + globalContextService.globalContext.workspaceId.$ + ); + const isCurrent = currentWorkspaceId === meta.id; const t = useI18n(); useEffect(() => { @@ -275,7 +277,7 @@ const WorkspaceListItem = ({ }, [userFeatureService]); const onClickPreference = useCallback(() => { - onClick('preference'); + onClick('workspace:preference'); }, [onClick]); const subTabs = useMemo(() => { @@ -287,7 +289,7 @@ const WorkspaceListItem = ({ onClick(key); }} className={clsx(style.sidebarSelectSubItem, { - active: activeSubTab === key, + active: activeTab === key, })} key={key} > @@ -295,12 +297,12 @@ const WorkspaceListItem = ({ ); }); - }, [activeSubTab, onClick, t]); + }, [activeTab, onClick, t]); return ( <>
) : null}
- {activeSubTab && subTabs.length > 1 ? subTabs : null} + {activeTab && subTabs.length > 1 ? subTabs : null} ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/setting-sidebar/style.css.ts diff --git a/packages/frontend/core/src/components/affine/star-affine-modal/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/star-affine-modal/index.tsx similarity index 78% rename from packages/frontend/core/src/components/affine/star-affine-modal/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/star-affine-modal/index.tsx index 46fe87326c..a709735d20 100644 --- a/packages/frontend/core/src/components/affine/star-affine-modal/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/star-affine-modal/index.tsx @@ -1,11 +1,14 @@ import { OverlayModal } from '@affine/component'; -import { openStarAFFiNEModalAtom } from '@affine/core/components/atoms'; import { useI18n } from '@affine/i18n'; -import { useAtom } from 'jotai'; -export const StarAFFiNEModal = () => { +export const StarAFFiNEModal = ({ + open, + setOpen, +}: { + open: boolean; + setOpen: (open: boolean) => void; +}) => { const t = useI18n(); - const [open, setOpen] = useAtom(openStarAFFiNEModalAtom); return ( void; + onChangeSettingState: (settingState: SettingState) => void; +}) => { + switch (activeTab) { + case 'workspace:preference': + return ( + + ); + case 'workspace:properties': + return ( + + ); + } + return null; +}; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/delete/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx similarity index 93% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx index 359096e81c..b0cbd633c5 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/delete-leave-workspace/index.tsx @@ -12,17 +12,19 @@ import { WorkspaceService, WorkspacesService, } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { useCallback, useEffect, useState } from 'react'; import { RouteLogic, useNavigateHelper, } from '../../../../../../components/hooks/use-navigate-helper'; -import { openSettingModalAtom } from '../../../../../atoms'; import { WorkspaceDeleteModal } from './delete'; -export const DeleteLeaveWorkspace = () => { +export const DeleteLeaveWorkspace = ({ + onCloseSetting, +}: { + onCloseSetting?: () => void; +}) => { const { workspaceService, globalContextService, @@ -40,7 +42,6 @@ export const DeleteLeaveWorkspace = () => { // fixme: cloud regression const [showDelete, setShowDelete] = useState(false); const [showLeave, setShowLeave] = useState(false); - const setSettingModal = useSetAtom(openSettingModalAtom); const workspaceList = useLiveData(workspacesService.list.workspaces$); const currentWorkspaceId = useLiveData( @@ -63,7 +64,7 @@ export const DeleteLeaveWorkspace = () => { }, [isOwner]); const onDeleteConfirm = useAsyncCallback(async () => { - setSettingModal(prev => ({ ...prev, open: false, workspaceId: null })); + onCloseSetting?.(); if (currentWorkspaceId === workspace.id) { const backWorkspace = workspaceList.find( @@ -84,7 +85,7 @@ export const DeleteLeaveWorkspace = () => { } notify.success({ title: t['Successfully deleted']() }); }, [ - setSettingModal, + onCloseSetting, currentWorkspaceId, workspace.id, workspace.meta, diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx similarity index 83% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx index feb6fd1cd0..7578782ea9 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/enable-cloud.tsx @@ -10,16 +10,17 @@ import { type Workspace, WorkspaceService, } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { useCallback } from 'react'; -import { openSettingModalAtom } from '../../../../atoms'; - export interface PublishPanelProps { workspace: Workspace | null; } -export const EnableCloudPanel = () => { +export const EnableCloudPanel = ({ + onCloseSetting, +}: { + onCloseSetting?: () => void; +}) => { const t = useI18n(); const confirmEnableCloud = useEnableCloud(); @@ -27,16 +28,14 @@ export const EnableCloudPanel = () => { const name = useLiveData(workspace.name$); const flavour = workspace.flavour; - const setSettingModal = useSetAtom(openSettingModalAtom); - const confirmEnableCloudAndClose = useCallback(() => { if (!workspace) return; confirmEnableCloud(workspace, { onSuccess: () => { - setSettingModal(settings => ({ ...settings, open: false })); + onCloseSetting?.(); }, }); - }, [confirmEnableCloud, setSettingModal, workspace]); + }, [confirmEnableCloud, onCloseSetting, workspace]); if (flavour !== WorkspaceFlavour.LOCAL) { return null; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/export.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx similarity index 92% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/export.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx index e39ef4b1fe..2eab27cd1c 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/export.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/export.tsx @@ -3,7 +3,7 @@ import { SettingRow } from '@affine/component/setting-components'; import { Button } from '@affine/component/ui/button'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useSystemOnline } from '@affine/core/components/hooks/use-system-online'; -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; +import { DesktopApiService } from '@affine/core/modules/desktop-api'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { @@ -42,7 +42,7 @@ export const DesktopExportPanel = ({ await workspace.engine.blob.sync(); } - const result = await desktopApi.handler.dialog.saveDBFileAs(workspaceId); + const result = await desktopApi.handler?.dialog.saveDBFileAs(workspaceId); if (result?.error) { throw new Error(result.error); } else if (!result?.canceled) { @@ -53,7 +53,7 @@ export const DesktopExportPanel = ({ } finally { setSaving(false); } - }, [isOnline, saving, t, workspace, workspaceId, desktopApi]); + }, [desktopApi, isOnline, saving, t, workspace, workspaceId]); return ( diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/index.tsx similarity index 94% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/index.tsx index 9a423101a6..f683f39141 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/index.tsx @@ -22,6 +22,8 @@ import type { WorkspaceSettingDetailProps } from './types'; export const WorkspaceSettingDetail = ({ workspaceMetadata, + onCloseSetting, + onChangeSettingState, }: WorkspaceSettingDetailProps) => { const t = useI18n(); @@ -65,8 +67,8 @@ export const WorkspaceSettingDetail = ({ - - + + {BUILD_CONFIG.isElectron && ( diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/labels.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/labels.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/labels.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members.tsx similarity index 96% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members.tsx index f5f0ac675b..522efd9702 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members.tsx @@ -12,7 +12,6 @@ import { Loading } from '@affine/component/ui/loading'; import { Menu, MenuItem } from '@affine/component/ui/menu'; import { Tooltip } from '@affine/component/ui/tooltip'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { useInviteMember } from '@affine/core/components/hooks/affine/use-invite-member'; import { useRevokeMemberPermission } from '@affine/core/components/hooks/affine/use-revoke-member-permission'; import { @@ -34,7 +33,6 @@ import { } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import clsx from 'clsx'; -import { useSetAtom } from 'jotai'; import { clamp } from 'lodash-es'; import type { ReactElement } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -45,6 +43,7 @@ import { ServerConfigService, SubscriptionService, } from '../../../../../modules/cloud'; +import type { SettingState } from '../../types'; import * as style from './style.css'; type OnRevoke = (memberId: string) => void; @@ -61,7 +60,11 @@ const MembersPanelLocal = () => { ); }; -export const CloudWorkspaceMembersPanel = () => { +export const CloudWorkspaceMembersPanel = ({ + onChangeSettingState, +}: { + onChangeSettingState: (settingState: SettingState) => void; +}) => { const serverConfig = useService(ServerConfigService).serverConfig; const hasPaymentFeature = useLiveData( serverConfig.features$.map(f => f?.payment) @@ -119,17 +122,15 @@ export const CloudWorkspaceMembersPanel = () => { [invite, t] ); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); const handleUpgradeConfirm = useCallback(() => { - setSettingModalAtom({ - open: true, + onChangeSettingState({ activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); track.$.settingsPanel.workspace.viewPlans({ control: 'inviteMember', }); - }, [setSettingModalAtom]); + }, [onChangeSettingState]); const onRevoke = useCallback( async memberId => { @@ -403,14 +404,18 @@ const MemberItem = ({ ); }; -export const MembersPanel = (): ReactElement | null => { +export const MembersPanel = ({ + onChangeSettingState, +}: { + onChangeSettingState: (settingState: SettingState) => void; +}): ReactElement | null => { const workspace = useService(WorkspaceService).workspace; if (workspace.flavour === WorkspaceFlavour.LOCAL) { return ; } return ( - + ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/profile.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/profile.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/profile.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/sharing.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/sharing.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/sharing.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/style.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/types.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts similarity index 51% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/types.ts rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts index c8ea9267d4..e16ef68a5d 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/types.ts +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/types.ts @@ -1,5 +1,9 @@ import type { WorkspaceMetadata } from '@toeverything/infra'; +import type { SettingState } from '../../types'; + export interface WorkspaceSettingDetailProps { workspaceMetadata: WorkspaceMetadata; + onCloseSetting: () => void; + onChangeSettingState: (settingState: SettingState) => void; } diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/index.tsx diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/styles.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/styles.css.ts similarity index 100% rename from packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/styles.css.ts rename to packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/properties/styles.css.ts diff --git a/packages/frontend/core/src/desktop/pages/404.tsx b/packages/frontend/core/src/desktop/pages/404.tsx deleted file mode 100644 index ca351bd3ee..0000000000 --- a/packages/frontend/core/src/desktop/pages/404.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { - NoPermissionOrNotFound, - NotFoundPage, -} from '@affine/component/not-found-page'; -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; -import { - useLiveData, - useService, - useServiceOptional, -} from '@toeverything/infra'; -import type { ReactElement } from 'react'; -import { useCallback, useEffect, useState } from 'react'; - -import { SignOutModal } from '../../components/affine/sign-out-modal'; -import { - RouteLogic, - useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; -import { AuthService } from '../../modules/cloud'; -import { SignIn } from './auth/sign-in'; - -export const PageNotFound = ({ - noPermission, -}: { - noPermission?: boolean; -}): ReactElement => { - const authService = useService(AuthService); - const desktopApi = useServiceOptional(DesktopApiService); - const account = useLiveData(authService.session.account$); - const { jumpToIndex } = useNavigateHelper(); - const [open, setOpen] = useState(false); - - const handleBackButtonClick = useCallback( - () => jumpToIndex(RouteLogic.REPLACE), - [jumpToIndex] - ); - - const handleOpenSignOutModal = useCallback(() => { - setOpen(true); - }, [setOpen]); - - const onConfirmSignOut = useAsyncCallback(async () => { - setOpen(false); - await authService.signOut(); - }, [authService]); - - useEffect(() => { - desktopApi?.handler.ui.pingAppLayoutReady().catch(console.error); - }, [desktopApi]); - - // not using workbench location or router location deliberately - // strip the origin - const currentUrl = window.location.href.replace(window.location.origin, ''); - - return ( - <> - {noPermission ? ( - } - /> - ) : ( - - )} - - - - ); -}; - -export const Component = () => { - return ; -}; diff --git a/packages/frontend/core/src/desktop/pages/404/index.tsx b/packages/frontend/core/src/desktop/pages/404/index.tsx new file mode 100644 index 0000000000..13ea86db8c --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/404/index.tsx @@ -0,0 +1,67 @@ +import { + NoPermissionOrNotFound, + NotFoundPage, +} from '@affine/component/not-found-page'; +import { useSignOut } from '@affine/core/components/hooks/affine/use-sign-out'; +import { DesktopApiService } from '@affine/core/modules/desktop-api'; +import { + useLiveData, + useService, + useServiceOptional, +} from '@toeverything/infra'; +import type { ReactElement } from 'react'; +import { useCallback, useEffect } from 'react'; + +import { + RouteLogic, + useNavigateHelper, +} from '../../../components/hooks/use-navigate-helper'; +import { AuthService } from '../../../modules/cloud'; +import { SignIn } from '../auth/sign-in'; + +/** + * only for web, should not be used in electron + */ +export const PageNotFound = ({ + noPermission, +}: { + noPermission?: boolean; +}): ReactElement => { + const authService = useService(AuthService); + const desktopApi = useServiceOptional(DesktopApiService); + const account = useLiveData(authService.session.account$); + const { jumpToIndex } = useNavigateHelper(); + const openSignOutModal = useSignOut(); + + const handleBackButtonClick = useCallback( + () => jumpToIndex(RouteLogic.REPLACE), + [jumpToIndex] + ); + + useEffect(() => { + desktopApi?.handler.ui.pingAppLayoutReady().catch(console.error); + }, [desktopApi]); + + // not using workbench location or router location deliberately + // strip the origin + const currentUrl = window.location.href.replace(window.location.origin, ''); + + return noPermission ? ( + } + /> + ) : ( + + ); +}; + +export const Component = () => { + return ; +}; diff --git a/packages/frontend/core/src/desktop/pages/ai-upgrade-success.tsx b/packages/frontend/core/src/desktop/pages/ai-upgrade-success.tsx deleted file mode 100644 index 5f35deca00..0000000000 --- a/packages/frontend/core/src/desktop/pages/ai-upgrade-success.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { AIUpgradeSuccess } from '../../components/affine/subscription-landing'; - -export const Component = () => { - return ; -}; diff --git a/packages/frontend/core/src/components/affine/subscription-landing/index.tsx b/packages/frontend/core/src/desktop/pages/ai-upgrade-success/index.tsx similarity index 58% rename from packages/frontend/core/src/components/affine/subscription-landing/index.tsx rename to packages/frontend/core/src/desktop/pages/ai-upgrade-success/index.tsx index 4138a6388f..88a4f20967 100644 --- a/packages/frontend/core/src/components/affine/subscription-landing/index.tsx +++ b/packages/frontend/core/src/desktop/pages/ai-upgrade-success/index.tsx @@ -1,20 +1,18 @@ +import { Button } from '@affine/component'; import { AuthPageContainer } from '@affine/component/auth-components'; -import { Button } from '@affine/component/ui/button'; -import { useSubscriptionNotifyWriter } from '@affine/core/components/hooks/affine/use-subscription-notify'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { Trans, useI18n } from '@affine/i18n'; -import { type ReactNode, useCallback } from 'react'; +import { useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; import * as styles from './styles.css'; -const UpgradeSuccessLayout = ({ - title, - description, -}: { - title?: ReactNode; - description?: ReactNode; -}) => { +/** + * /ai-upgrade-success page + * + * only on web + */ +export const Component = () => { const t = useI18n(); const [params] = useSearchParams(); @@ -29,7 +27,7 @@ const UpgradeSuccessLayout = ({ const subtitle = (
- {description} + {t['com.affine.payment.ai-upgrade-success-page.text']()}
+ ); }; - -export const CloudUpgradeSuccess = () => { - const t = useI18n(); - useSubscriptionNotifyWriter(); - return ( - - ); -}; - -export const AIUpgradeSuccess = () => { - const t = useI18n(); - useSubscriptionNotifyWriter(); - return ( - - ); -}; diff --git a/packages/frontend/core/src/desktop/pages/ai-upgrade-success/styles.css.ts b/packages/frontend/core/src/desktop/pages/ai-upgrade-success/styles.css.ts new file mode 100644 index 0000000000..578ec70bef --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/ai-upgrade-success/styles.css.ts @@ -0,0 +1,15 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; +export const leftContentText = style({ + fontSize: cssVar('fontBase'), + fontWeight: 400, + lineHeight: '1.6', + maxWidth: '548px', +}); +export const mail = style({ + color: cssVar('linkColor'), + textDecoration: 'none', + ':visited': { + color: cssVar('linkColor'), + }, +}); diff --git a/packages/frontend/core/src/desktop/pages/expired.tsx b/packages/frontend/core/src/desktop/pages/expired/index.tsx similarity index 88% rename from packages/frontend/core/src/desktop/pages/expired.tsx rename to packages/frontend/core/src/desktop/pages/expired/index.tsx index 87e0a94c02..027cf24804 100644 --- a/packages/frontend/core/src/desktop/pages/expired.tsx +++ b/packages/frontend/core/src/desktop/pages/expired/index.tsx @@ -6,8 +6,13 @@ import { useCallback } from 'react'; import { RouteLogic, useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; +} from '../../../components/hooks/use-navigate-helper'; +/** + * /expired page + * + * only on web + */ export const Component = () => { const t = useI18n(); const { jumpToIndex } = useNavigateHelper(); diff --git a/packages/frontend/core/src/desktop/pages/import-template.tsx b/packages/frontend/core/src/desktop/pages/import-template/index.tsx similarity index 58% rename from packages/frontend/core/src/desktop/pages/import-template.tsx rename to packages/frontend/core/src/desktop/pages/import-template/index.tsx index 48b659c265..48f2634df6 100644 --- a/packages/frontend/core/src/desktop/pages/import-template.tsx +++ b/packages/frontend/core/src/desktop/pages/import-template/index.tsx @@ -1,22 +1,27 @@ +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import type { DocMode } from '@blocksuite/affine/blocks'; import { useService } from '@toeverything/infra'; import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useNavigateHelper } from '../../components/hooks/use-navigate-helper'; -import { ImportTemplateDialogService } from '../../modules/import-template'; +import { useNavigateHelper } from '../../../components/hooks/use-navigate-helper'; +/** + * /template/import page, only for web + * + * no ui for this route, just open the dialog + */ export const Component = () => { - const importTemplateDialogService = useService(ImportTemplateDialogService); + const globalDialogService = useService(GlobalDialogService); const [searchParams] = useSearchParams(); const { jumpToIndex } = useNavigateHelper(); useEffect(() => { - importTemplateDialogService.dialog.open({ + globalDialogService.open('import-template', { templateName: searchParams.get('name') ?? '', templateMode: (searchParams.get('mode') as DocMode) ?? 'page', snapshotUrl: searchParams.get('snapshotUrl') ?? '', }); - }, [importTemplateDialogService.dialog, jumpToIndex, searchParams]); + }, [globalDialogService, jumpToIndex, searchParams]); // no ui for this route, just open the dialog return null; }; diff --git a/packages/frontend/core/src/desktop/pages/index.tsx b/packages/frontend/core/src/desktop/pages/index/index.tsx similarity index 88% rename from packages/frontend/core/src/desktop/pages/index.tsx rename to packages/frontend/core/src/desktop/pages/index/index.tsx index 3b8132103d..e466ec17f9 100644 --- a/packages/frontend/core/src/desktop/pages/index.tsx +++ b/packages/frontend/core/src/desktop/pages/index/index.tsx @@ -1,4 +1,8 @@ -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; +import { DesktopApiService } from '@affine/core/modules/desktop-api'; +import { + buildShowcaseWorkspace, + createFirstAppData, +} from '@affine/core/utils/first-app-data'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useLiveData, @@ -13,24 +17,22 @@ import { useRef, useState, } from 'react'; -import { type LoaderFunction, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; -import { AppFallback } from '../../components/affine/app-container'; import { RouteLogic, useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; -import { WorkspaceNavigator } from '../../components/workspace-selector'; -import { AuthService } from '../../modules/cloud'; -import { - buildShowcaseWorkspace, - createFirstAppData, -} from '../../utils/first-app-data'; - -export const loader: LoaderFunction = async () => { - return null; -}; +} from '../../../components/hooks/use-navigate-helper'; +import { WorkspaceNavigator } from '../../../components/workspace-selector'; +import { AuthService } from '../../../modules/cloud'; +import { AppContainer } from '../../components/app-container'; +/** + * index page + * + * query string: + * - initCloud: boolean, if true, when user is logged in, create a cloud workspace + */ export const Component = ({ defaultIndexRoute = 'all', }: { @@ -149,7 +151,7 @@ export const Component = ({ }, [jumpToPage, openPage, workspacesService]); if (navigating || creating) { - return ; + return ; } // TODO(@eyhn): We need a no workspace page diff --git a/packages/frontend/core/src/desktop/pages/invite.tsx b/packages/frontend/core/src/desktop/pages/invite/index.tsx similarity index 90% rename from packages/frontend/core/src/desktop/pages/invite.tsx rename to packages/frontend/core/src/desktop/pages/invite/index.tsx index 9f2a2106aa..1866c13960 100644 --- a/packages/frontend/core/src/desktop/pages/invite.tsx +++ b/packages/frontend/core/src/desktop/pages/invite/index.tsx @@ -6,18 +6,21 @@ import { getInviteInfoQuery, } from '@affine/graphql'; import { useLiveData, useService } from '@toeverything/infra'; -import { useSetAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; import type { LoaderFunction } from 'react-router-dom'; import { redirect, useLoaderData } from 'react-router-dom'; -import { authAtom } from '../../components/atoms'; import { RouteLogic, useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; -import { AuthService } from '../../modules/cloud'; +} from '../../../components/hooks/use-navigate-helper'; +import { AuthService } from '../../../modules/cloud'; +/** + * /invite/:inviteId page + * + * only for web + */ export const loader: LoaderFunction = async args => { const inviteId = args.params.inviteId || ''; const res = await fetcher({ @@ -60,7 +63,6 @@ export const Component = () => { const { jumpToSignIn } = useNavigateHelper(); const { jumpToPage } = useNavigateHelper(); - const setAuthAtom = useSetAtom(authAtom); const { inviteInfo } = useLoaderData() as { inviteId: string; inviteInfo: GetInviteInfoQuery['getInviteInfo']; @@ -84,7 +86,6 @@ export const Component = () => { jumpToSignIn, loginStatus, openWorkspace, - setAuthAtom, ]); if (loginStatus === 'authenticated') { diff --git a/packages/frontend/core/src/desktop/pages/onboarding.tsx b/packages/frontend/core/src/desktop/pages/onboarding.tsx deleted file mode 100644 index 257ab3f5a0..0000000000 --- a/packages/frontend/core/src/desktop/pages/onboarding.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { DesktopApiService } from '@affine/core/modules/desktop-api/service'; -import { useServiceOptional } from '@toeverything/infra'; -import { useCallback } from 'react'; -import { redirect } from 'react-router-dom'; - -import { Onboarding } from '../../components/affine/onboarding/onboarding'; -import { - appConfigStorage, - useAppConfigStorage, -} from '../../components/hooks/use-app-config-storage'; -import { - RouteLogic, - useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; - -export const loader = () => { - if (!BUILD_CONFIG.isElectron && !appConfigStorage.get('onBoarding')) { - // onboarding is off, redirect to index - return redirect('/'); - } - - return null; -}; - -export const Component = () => { - const { jumpToIndex } = useNavigateHelper(); - const [, setOnboarding] = useAppConfigStorage('onBoarding'); - const desktopApi = useServiceOptional(DesktopApiService); - - const openApp = useCallback(() => { - if (BUILD_CONFIG.isElectron) { - desktopApi?.handler.ui.handleOpenMainApp().catch(err => { - console.log('failed to open main app', err); - }); - } else { - jumpToIndex(RouteLogic.REPLACE); - setOnboarding(false); - } - }, [jumpToIndex, setOnboarding, desktopApi]); - - return ; -}; diff --git a/packages/frontend/core/src/desktop/pages/onboarding/index.tsx b/packages/frontend/core/src/desktop/pages/onboarding/index.tsx new file mode 100644 index 0000000000..4ee128db31 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/onboarding/index.tsx @@ -0,0 +1,33 @@ +import { DesktopApiService } from '@affine/core/modules/desktop-api'; +import { useServiceOptional } from '@toeverything/infra'; +import { useCallback } from 'react'; +import { redirect } from 'react-router-dom'; + +import { Onboarding } from '../../../components/affine/onboarding/onboarding'; +import { appConfigStorage } from '../../../components/hooks/use-app-config-storage'; + +/** + * /onboarding page + * + * only for electron + */ +export const loader = () => { + if (!BUILD_CONFIG.isElectron && !appConfigStorage.get('onBoarding')) { + // onboarding is off, redirect to index + return redirect('/'); + } + + return null; +}; + +export const Component = () => { + const desktopApi = useServiceOptional(DesktopApiService); + + const openApp = useCallback(() => { + desktopApi?.handler.ui.handleOpenMainApp().catch(err => { + console.log('failed to open main app', err); + }); + }, [desktopApi]); + + return ; +}; diff --git a/packages/frontend/core/src/desktop/pages/open-app.tsx b/packages/frontend/core/src/desktop/pages/open-app/index.tsx similarity index 97% rename from packages/frontend/core/src/desktop/pages/open-app.tsx rename to packages/frontend/core/src/desktop/pages/open-app/index.tsx index e10deb4678..b0b2e82e1a 100644 --- a/packages/frontend/core/src/desktop/pages/open-app.tsx +++ b/packages/frontend/core/src/desktop/pages/open-app/index.tsx @@ -1,5 +1,5 @@ import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page'; -import { appSchemes } from '@affine/core/utils'; +import { appSchemes } from '@affine/core/utils/channel'; import type { GetCurrentUserQuery } from '@affine/graphql'; import { fetcher, getCurrentUserQuery } from '@affine/graphql'; import type { LoaderFunction } from 'react-router-dom'; diff --git a/packages/frontend/core/src/desktop/pages/open-app.css.ts b/packages/frontend/core/src/desktop/pages/open-app/open-app.css.ts similarity index 100% rename from packages/frontend/core/src/desktop/pages/open-app.css.ts rename to packages/frontend/core/src/desktop/pages/open-app/open-app.css.ts diff --git a/packages/frontend/core/src/desktop/pages/redirect.tsx b/packages/frontend/core/src/desktop/pages/redirect/index.tsx similarity index 95% rename from packages/frontend/core/src/desktop/pages/redirect.tsx rename to packages/frontend/core/src/desktop/pages/redirect/index.tsx index fad78a7944..3405d237d8 100644 --- a/packages/frontend/core/src/desktop/pages/redirect.tsx +++ b/packages/frontend/core/src/desktop/pages/redirect/index.tsx @@ -15,6 +15,11 @@ const trustedDomain = [ const logger = new DebugLogger('redirect_proxy'); +/** + * /redirect-proxy page + * + * only for web + */ export const loader: LoaderFunction = async ({ request }) => { const url = new URL(request.url); const searchParams = url.searchParams; diff --git a/packages/frontend/core/src/desktop/pages/root.tsx b/packages/frontend/core/src/desktop/pages/root.tsx deleted file mode 100644 index 2b48f64932..0000000000 --- a/packages/frontend/core/src/desktop/pages/root.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -import { AllWorkspaceModals } from '../../components/providers/modal-provider'; - -export const RootWrapper = () => { - return ( - <> - - - - ); -}; diff --git a/packages/frontend/core/src/modules/theme-editor/views/custom-theme.tsx b/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx similarity index 75% rename from packages/frontend/core/src/modules/theme-editor/views/custom-theme.tsx rename to packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx index c00ade73e2..9451ae7ff9 100644 --- a/packages/frontend/core/src/modules/theme-editor/views/custom-theme.tsx +++ b/packages/frontend/core/src/desktop/pages/root/custom-theme/index.tsx @@ -1,3 +1,4 @@ +import { ThemeEditorService } from '@affine/core/modules/theme-editor'; import { FeatureFlagService, useLiveData, @@ -6,11 +7,9 @@ import { import { useTheme } from 'next-themes'; import { useEffect } from 'react'; -import { ThemeEditorService } from '../services/theme-editor'; - let _provided = false; -export const useCustomTheme = (target: HTMLElement) => { +export const CustomThemeModifier = () => { const { themeEditorService, featureFlagService } = useServices({ ThemeEditorService, FeatureFlagService, @@ -34,12 +33,12 @@ export const useCustomTheme = (target: HTMLElement) => { // remove previous style // TOOD(@CatsJuice): find better way to remove previous style - target.style.cssText = ''; + document.documentElement.style.cssText = ''; // recover color scheme set by next-themes - target.style.colorScheme = mode; + document.documentElement.style.colorScheme = mode; Object.entries(valueMap).forEach(([key, value]) => { - value && target.style.setProperty(key, value); + value && document.documentElement.style.setProperty(key, value); }); }); @@ -47,11 +46,7 @@ export const useCustomTheme = (target: HTMLElement) => { _provided = false; sub.unsubscribe(); }; - }, [resolvedTheme, target.style, enableThemeEditor, themeEditorService]); -}; - -export const CustomThemeModifier = () => { - useCustomTheme(document.documentElement); + }, [resolvedTheme, enableThemeEditor, themeEditorService]); return null; }; diff --git a/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.css.ts b/packages/frontend/core/src/desktop/pages/root/find-in-page/find-in-page-modal.css.ts similarity index 100% rename from packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.css.ts rename to packages/frontend/core/src/desktop/pages/root/find-in-page/find-in-page-modal.css.ts diff --git a/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx b/packages/frontend/core/src/desktop/pages/root/find-in-page/find-in-page-modal.tsx similarity index 99% rename from packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx rename to packages/frontend/core/src/desktop/pages/root/find-in-page/find-in-page-modal.tsx index f9a310783c..550987abac 100644 --- a/packages/frontend/core/src/modules/find-in-page/view/find-in-page-modal.tsx +++ b/packages/frontend/core/src/desktop/pages/root/find-in-page/find-in-page-modal.tsx @@ -1,4 +1,5 @@ import { IconButton, observeResize, RowInput } from '@affine/component'; +import { FindInPageService } from '@affine/core/modules/find-in-page'; import { ArrowDownSmallIcon, ArrowUpSmallIcon, @@ -20,7 +21,6 @@ import { } from 'react'; import { useTransition } from 'react-transition-state'; -import { FindInPageService } from '../services/find-in-page'; import * as styles from './find-in-page-modal.css'; const animationTimeout = 120; diff --git a/packages/frontend/component/src/components/global-loading/index.css.ts b/packages/frontend/core/src/desktop/pages/root/global-loading/index.css.ts similarity index 100% rename from packages/frontend/component/src/components/global-loading/index.css.ts rename to packages/frontend/core/src/desktop/pages/root/global-loading/index.css.ts diff --git a/packages/frontend/core/src/desktop/pages/root/global-loading/index.tsx b/packages/frontend/core/src/desktop/pages/root/global-loading/index.tsx new file mode 100644 index 0000000000..8e11e1d31c --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/root/global-loading/index.tsx @@ -0,0 +1,28 @@ +import { Loading } from '@affine/component'; +import { globalLoadingEventsAtom } from '@affine/component/global-loading/index.jotai'; +import { useAtomValue } from 'jotai'; +import { type ReactNode, useEffect, useState } from 'react'; + +import * as styles from './index.css'; + +export function GlobalLoading(): ReactNode { + const globalLoadingEvents = useAtomValue(globalLoadingEventsAtom); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (globalLoadingEvents.length) { + setLoading(true); + } else { + setLoading(false); + } + }, [globalLoadingEvents]); + + if (!globalLoadingEvents.length) { + return null; + } + return ( +
+ +
+ ); +} diff --git a/packages/frontend/core/src/desktop/pages/root/index.tsx b/packages/frontend/core/src/desktop/pages/root/index.tsx new file mode 100644 index 0000000000..c7e61249e5 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/root/index.tsx @@ -0,0 +1,18 @@ +import { NotificationCenter } from '@affine/component'; +import { Outlet } from 'react-router-dom'; + +import { GlobalDialogs } from '../../dialogs'; +import { CustomThemeModifier } from './custom-theme'; +import { FindInPageModal } from './find-in-page/find-in-page-modal'; + +export const RootWrapper = () => { + return ( + <> + + + + + {BUILD_CONFIG.isElectron && } + + ); +}; diff --git a/packages/frontend/core/src/components/telemetry.tsx b/packages/frontend/core/src/desktop/pages/root/telemetry/index.tsx similarity index 100% rename from packages/frontend/core/src/components/telemetry.tsx rename to packages/frontend/core/src/desktop/pages/root/telemetry/index.tsx diff --git a/packages/frontend/core/src/desktop/pages/subscribe.tsx b/packages/frontend/core/src/desktop/pages/subscribe/index.tsx similarity index 96% rename from packages/frontend/core/src/desktop/pages/subscribe.tsx rename to packages/frontend/core/src/desktop/pages/subscribe/index.tsx index 0406b97b8e..3adf84515c 100644 --- a/packages/frontend/core/src/desktop/pages/subscribe.tsx +++ b/packages/frontend/core/src/desktop/pages/subscribe/index.tsx @@ -11,16 +11,16 @@ import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { EMPTY, mergeMap, switchMap } from 'rxjs'; -import { generateSubscriptionCallbackLink } from '../../components/hooks/affine/use-subscription-notify'; +import { generateSubscriptionCallbackLink } from '../../../components/hooks/affine/use-subscription-notify'; import { RouteLogic, useNavigateHelper, -} from '../../components/hooks/use-navigate-helper'; +} from '../../../components/hooks/use-navigate-helper'; import { AuthService, BackendError, SubscriptionService, -} from '../../modules/cloud'; +} from '../../../modules/cloud'; import { container } from './subscribe.css'; interface ProductTriple { diff --git a/packages/frontend/core/src/desktop/pages/subscribe.css.ts b/packages/frontend/core/src/desktop/pages/subscribe/subscribe.css.ts similarity index 100% rename from packages/frontend/core/src/desktop/pages/subscribe.css.ts rename to packages/frontend/core/src/desktop/pages/subscribe/subscribe.css.ts diff --git a/packages/frontend/core/src/desktop/pages/theme-editor.tsx b/packages/frontend/core/src/desktop/pages/theme-editor.tsx deleted file mode 100644 index 1d413c4128..0000000000 --- a/packages/frontend/core/src/desktop/pages/theme-editor.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ThemeEditor } from '../../modules/theme-editor'; - -export const Component = () => { - return ; -}; diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/color-cell.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/color-cell.tsx similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/color-cell.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/color-cell.tsx diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/empty.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/empty.tsx similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/empty.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/empty.tsx diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/simple-color-picker.css.ts b/packages/frontend/core/src/desktop/pages/theme-editor/components/simple-color-picker.css.ts similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/simple-color-picker.css.ts rename to packages/frontend/core/src/desktop/pages/theme-editor/components/simple-color-picker.css.ts diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/simple-color-picker.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/simple-color-picker.tsx similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/simple-color-picker.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/simple-color-picker.tsx diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/string-cell.css.ts b/packages/frontend/core/src/desktop/pages/theme-editor/components/string-cell.css.ts similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/string-cell.css.ts rename to packages/frontend/core/src/desktop/pages/theme-editor/components/string-cell.css.ts diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/string-cell.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/string-cell.tsx similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/string-cell.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/string-cell.tsx diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/tree-node.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/tree-node.tsx similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/components/tree-node.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/tree-node.tsx diff --git a/packages/frontend/core/src/modules/theme-editor/views/components/variable-list.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/components/variable-list.tsx similarity index 96% rename from packages/frontend/core/src/modules/theme-editor/views/components/variable-list.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/components/variable-list.tsx index 5d70686d81..658bd0dbd5 100644 --- a/packages/frontend/core/src/modules/theme-editor/views/components/variable-list.tsx +++ b/packages/frontend/core/src/desktop/pages/theme-editor/components/variable-list.tsx @@ -1,7 +1,7 @@ import { Scrollable } from '@affine/component'; +import { ThemeEditorService } from '@affine/core/modules/theme-editor'; import { useLiveData, useService } from '@toeverything/infra'; -import { ThemeEditorService } from '../../services/theme-editor'; import type { TreeNode } from '../resource'; import * as styles from '../theme-editor.css'; import { isColor } from '../utils'; diff --git a/packages/frontend/core/src/desktop/pages/theme-editor/index.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/index.tsx new file mode 100644 index 0000000000..0b19480cf7 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/theme-editor/index.tsx @@ -0,0 +1,8 @@ +import { ThemeEditor } from './theme-editor'; + +/** + * /theme-editor page + */ +export const Component = () => { + return ; +}; diff --git a/packages/frontend/core/src/modules/theme-editor/views/resource.ts b/packages/frontend/core/src/desktop/pages/theme-editor/resource.ts similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/resource.ts rename to packages/frontend/core/src/desktop/pages/theme-editor/resource.ts diff --git a/packages/frontend/core/src/modules/theme-editor/views/theme-editor.css.ts b/packages/frontend/core/src/desktop/pages/theme-editor/theme-editor.css.ts similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/theme-editor.css.ts rename to packages/frontend/core/src/desktop/pages/theme-editor/theme-editor.css.ts diff --git a/packages/frontend/core/src/modules/theme-editor/views/theme-editor.tsx b/packages/frontend/core/src/desktop/pages/theme-editor/theme-editor.tsx similarity index 97% rename from packages/frontend/core/src/modules/theme-editor/views/theme-editor.tsx rename to packages/frontend/core/src/desktop/pages/theme-editor/theme-editor.tsx index f805fcf779..d99b2a6dbf 100644 --- a/packages/frontend/core/src/modules/theme-editor/views/theme-editor.tsx +++ b/packages/frontend/core/src/desktop/pages/theme-editor/theme-editor.tsx @@ -1,8 +1,8 @@ import { RadioGroup, Scrollable } from '@affine/component'; +import { ThemeEditorService } from '@affine/core/modules/theme-editor'; import { useService } from '@toeverything/infra'; import { useCallback, useEffect, useState } from 'react'; -import { ThemeEditorService } from '../services/theme-editor'; import { ThemeEmpty } from './components/empty'; import { ThemeTreeNode } from './components/tree-node'; import { VariableList } from './components/variable-list'; diff --git a/packages/frontend/core/src/modules/theme-editor/views/utils.ts b/packages/frontend/core/src/desktop/pages/theme-editor/utils.ts similarity index 100% rename from packages/frontend/core/src/modules/theme-editor/views/utils.ts rename to packages/frontend/core/src/desktop/pages/theme-editor/utils.ts diff --git a/packages/frontend/core/src/desktop/pages/upgrade-success.tsx b/packages/frontend/core/src/desktop/pages/upgrade-success.tsx deleted file mode 100644 index 6353f1c9a6..0000000000 --- a/packages/frontend/core/src/desktop/pages/upgrade-success.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { CloudUpgradeSuccess } from '../../components/affine/subscription-landing'; - -export const Component = () => { - return ; -}; diff --git a/packages/frontend/core/src/desktop/pages/upgrade-success/index.tsx b/packages/frontend/core/src/desktop/pages/upgrade-success/index.tsx new file mode 100644 index 0000000000..db9b96951b --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/upgrade-success/index.tsx @@ -0,0 +1,57 @@ +import { Button } from '@affine/component'; +import { AuthPageContainer } from '@affine/component/auth-components'; +import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; +import { Trans, useI18n } from '@affine/i18n'; +import { useCallback } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +import * as styles from './styles.css'; + +/** + * /upgrade-success page + * + * only on web + */ +export const Component = () => { + const t = useI18n(); + const [params] = useSearchParams(); + + const { jumpToIndex, jumpToOpenInApp } = useNavigateHelper(); + const openAffine = useCallback(() => { + if (params.get('schema')) { + jumpToOpenInApp('bring-to-front'); + } else { + jumpToIndex(); + } + }, [jumpToIndex, jumpToOpenInApp, params]); + + const subtitle = ( +
+ {t['com.affine.payment.upgrade-success-page.text']()} +
+ + ), + }} + /> +
+
+ ); + + return ( + + + + ); +}; diff --git a/packages/frontend/core/src/desktop/pages/upgrade-success/styles.css.ts b/packages/frontend/core/src/desktop/pages/upgrade-success/styles.css.ts new file mode 100644 index 0000000000..578ec70bef --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/upgrade-success/styles.css.ts @@ -0,0 +1,15 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; +export const leftContentText = style({ + fontSize: cssVar('fontBase'), + fontWeight: 400, + lineHeight: '1.6', + maxWidth: '548px', +}); +export const mail = style({ + color: cssVar('linkColor'), + textDecoration: 'none', + ':visited': { + color: cssVar('linkColor'), + }, +}); diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.css.ts b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.css.ts index a5f5542709..d633e68a41 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.css.ts +++ b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.css.ts @@ -1,3 +1,4 @@ +import { cssVar } from '@toeverything/theme'; import { style } from '@vanilla-extract/css'; export const body = style({ @@ -7,3 +8,9 @@ export const body = style({ height: '100%', width: '100%', }); + +export const createTips = style({ + color: cssVar('textSecondaryColor'), + fontSize: 12, + lineHeight: '20px', +}); diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx index 39fc840f5d..d102027b17 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-collection/index.tsx @@ -1,9 +1,9 @@ +import { usePromptModal } from '@affine/component'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import type { CollectionMeta } from '@affine/core/components/page-list'; import { CollectionListHeader, createEmptyCollection, - useEditCollectionName, VirtualizedCollectionList, } from '@affine/core/components/page-list'; import { @@ -40,22 +40,38 @@ export const AllCollection = () => { }, [collections]); const navigateHelper = useNavigateHelper(); - const { open } = useEditCollectionName({ - title: t['com.affine.editCollection.createCollection'](), - showTips: true, - }); + const { openPromptModal } = usePromptModal(); const handleCreateCollection = useCallback(() => { - open('') - .then(name => { + openPromptModal({ + title: t['com.affine.editCollection.saveCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + children: ( +
+ {t['com.affine.editCollectionName.createTips']()} +
+ ), + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { const id = nanoid(); collectionService.addCollection(createEmptyCollection(id, { name })); navigateHelper.jumpToCollection(currentWorkspace.id, id); - }) - .catch(err => { - console.error(err); - }); - }, [collectionService, currentWorkspace, navigateHelper, open]); + }, + }); + }, [ + collectionService, + currentWorkspace.id, + navigateHelper, + openPromptModal, + t, + ]); return ( <> diff --git a/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx index 00b927aae8..61cf826f63 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/collection/index.tsx @@ -1,11 +1,8 @@ import { notify } from '@affine/component'; import { EmptyCollectionDetail } from '@affine/core/components/affine/empty/collection-detail'; -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; -import { - useEditCollection, - VirtualizedPageList, -} from '@affine/core/components/page-list'; +import { VirtualizedPageList } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { ViewLayersIcon } from '@blocksuite/icons/rc'; @@ -34,14 +31,16 @@ export const CollectionDetail = ({ }: { collection: Collection; }) => { - const { open } = useEditCollection(); - const collectionService = useService(CollectionService); + const { workspaceDialogService } = useServices({ + WorkspaceDialogService, + }); const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true); - const handleEditCollection = useAsyncCallback(async () => { - const ret = await open({ ...collection }, 'page'); - collectionService.updateCollection(ret.id, () => ret); - }, [collection, collectionService, open]); + const handleEditCollection = useCallback(() => { + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); + }, [collection, workspaceDialogService]); return ( <> diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx index 301104478a..d9b8917926 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal.tsx @@ -6,8 +6,8 @@ import { MenuItem, MenuSeparator, Scrollable, + useConfirmModal, } from '@affine/component'; -import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal'; import { MoveToTrash } from '@affine/core/components/page-list'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; @@ -310,17 +310,23 @@ const ConflictList = ({ const t = useI18n(); const currentDoc = useService(DocService).doc; const journalService = useService(JournalService); - const { setTrashModal } = useTrashModalHelper(); + const { openConfirmModal } = useConfirmModal(); const handleOpenTrashModal = useCallback( (docRecord: DocRecord) => { - setTrashModal({ - open: true, - pageIds: [docRecord.id], - pageTitles: [docRecord.title$.value], + openConfirmModal({ + title: t['com.affine.moveToTrash.confirmModal.title'](), + description: t['com.affine.moveToTrash.confirmModal.description']({ + title: docRecord.title$.value || t['Untitled'](), + }), + cancelText: t['com.affine.confirmModal.button.cancel'](), + confirmText: t.Delete(), + onConfirm: () => { + docRecord.moveToTrash(); + }, }); }, - [setTrashModal] + [openConfirmModal, t] ); const handleRemoveJournalMark = useCallback( (docId: string) => { diff --git a/packages/frontend/core/src/desktop/pages/workspace/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/index.tsx index 40441afa09..7e7da7d543 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/index.tsx @@ -1,5 +1,4 @@ import { AffineOtherPageLayout } from '@affine/component/affine-other-page-layout'; -import { AppFallback } from '@affine/core/components/affine/app-container'; import { workbenchRoutes } from '@affine/core/desktop/workbench-router'; import { ZipTransformer } from '@blocksuite/affine/blocks'; import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; @@ -15,9 +14,10 @@ import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { matchPath, useLocation, useParams } from 'react-router-dom'; import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary'; -import { WorkspaceLayout } from '../../../components/layouts/workspace-layout'; import { WorkbenchRoot } from '../../../modules/workbench'; +import { AppContainer } from '../../components/app-container'; import { PageNotFound } from '../404'; +import { WorkspaceLayout } from './layouts/workspace-layout'; import { SharePage } from './share/share-page'; declare global { @@ -112,7 +112,7 @@ export const Component = (): ReactElement => { ); } if (!meta) { - return ; + return ; } return ; @@ -198,7 +198,7 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => { if (!isRootDocReady) { return ( - + ); } diff --git a/packages/frontend/core/src/components/layouts/styles.css.ts b/packages/frontend/core/src/desktop/pages/workspace/layouts/styles.css.ts similarity index 100% rename from packages/frontend/core/src/components/layouts/styles.css.ts rename to packages/frontend/core/src/desktop/pages/workspace/layouts/styles.css.ts diff --git a/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx new file mode 100644 index 0000000000..37e385fbed --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx @@ -0,0 +1,77 @@ +import { WorkspaceAIOnboarding } from '@affine/core/components/affine/ai-onboarding'; +import { AiLoginRequiredModal } from '@affine/core/components/affine/auth/ai-login-required'; +import { + CloudQuotaModal, + LocalQuotaModal, +} from '@affine/core/components/affine/quota-reached-modal'; +import { SWRConfigProvider } from '@affine/core/components/providers/swr-config-provider'; +import { WorkspaceSideEffects } from '@affine/core/components/providers/workspace-side-effects'; +import { WorkspaceUpgrade } from '@affine/core/components/workspace-upgrade'; +import { AIIsland } from '@affine/core/desktop/components/ai-island'; +import { AppContainer } from '@affine/core/desktop/components/app-container'; +import { WorkspaceDialogs } from '@affine/core/desktop/dialogs'; +import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; +import { WorkbenchService } from '@affine/core/modules/workbench'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { + LiveData, + useLiveData, + useService, + WorkspaceService, +} from '@toeverything/infra'; +import type { PropsWithChildren } from 'react'; + +export const WorkspaceLayout = function WorkspaceLayout({ + children, +}: PropsWithChildren) { + const currentWorkspace = useService(WorkspaceService).workspace; + return ( + + + + {/* ---- some side-effect components ---- */} + {currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && ( + + )} + {currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && ( + + )} + + + + + {children} + {/* should show after workspace loaded */} + + + + ); +}; + +/** + * Wraps the workspace layout main router view + */ +const WorkspaceLayoutUIContainer = ({ children }: PropsWithChildren) => { + const workbench = useService(WorkbenchService).workbench; + const currentPath = useLiveData( + LiveData.computed(get => { + return get(workbench.basename$) + get(workbench.location$).pathname; + }) + ); + + return ( + {children} + ); +}; +const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => { + const workspace = useService(WorkspaceService).workspace; + + const upgrading = useLiveData(workspace.upgrade.upgrading$); + const needUpgrade = useLiveData(workspace.upgrade.needUpgrade$); + + return ( + + {needUpgrade || upgrading ? : children} + + ); +}; diff --git a/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx index 54e2465c5e..0e71a2bb1e 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx @@ -1,14 +1,11 @@ import { Scrollable } from '@affine/component'; -import { AppFallback } from '@affine/core/components/affine/app-container'; import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer'; import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor'; import { usePageDocumentTitle } from '@affine/core/components/hooks/use-global-state'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { PageDetailEditor } from '@affine/core/components/page-detail-editor'; import { SharePageNotFoundError } from '@affine/core/components/share-page-not-found-error'; -import { AppContainer, MainContainer } from '@affine/core/components/workspace'; -import { OpenInAppCard } from '@affine/core/modules/app-sidebar/views'; -import { AppTabsHeader } from '@affine/core/modules/app-tabs-header'; +import { AppContainer } from '@affine/core/desktop/components/app-container'; import { AuthService, FetchService } from '@affine/core/modules/cloud'; import { type Editor, @@ -18,8 +15,6 @@ import { } from '@affine/core/modules/editor'; import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; import { ShareReaderService } from '@affine/core/modules/share-doc'; -import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; -import { DesktopStateSynchronizer } from '@affine/core/modules/workbench/services/desktop-state-synchronizer'; import { CloudBlobStorage } from '@affine/core/modules/workspace-engine'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; @@ -39,18 +34,11 @@ import { ReadonlyDocStorage, useLiveData, useService, - useServiceOptional, useServices, WorkspacesService, } from '@toeverything/infra'; import clsx from 'clsx'; -import { - type PropsWithChildren, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { PageNotFound } from '../../404'; @@ -109,7 +97,7 @@ export const SharePage = ({ }, [shareReaderService, docId, workspaceId]); if (isLoading) { - return ; + return ; } if (error) { @@ -136,75 +124,6 @@ export const SharePage = ({ } }; -interface SharePageContainerProps { - pageId: string; - pageTitle?: string; - publishMode: DocMode; - isTemplate?: boolean; - templateName?: string; - templateSnapshotUrl?: string; -} - -const SharePageWebContainer = ({ - children, - pageId, - publishMode, - isTemplate, - templateName, - templateSnapshotUrl, -}: PropsWithChildren) => { - return ( - -
-
- - {children} - -
- -
-
- ); -}; - -const SharePageDesktopContainer = ({ - children, - pageId, - pageTitle, - publishMode, - isTemplate, - templateName, - templateSnapshotUrl, -}: PropsWithChildren) => { - useServiceOptional(DesktopStateSynchronizer); - return ( - - {/* share page does not have ViewRoot so the following does not work yet */} - - -
- -
- - {children} -
-
-
- ); -}; - const SharePageInner = ({ workspaceId, docId, @@ -354,45 +273,44 @@ const SharePageInner = ({ return; } - const Container = BUILD_CONFIG.isElectron - ? SharePageDesktopContainer - : SharePageWebContainer; - return ( - - - - - - - - - {publishMode === 'page' ? : null} - - - - - + + + + +
+
+ + + + + {publishMode === 'page' ? : null} + + + + + +
+
-
+
- +
); }; diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx index 40b56feb50..2426edbd69 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/collection/index.tsx @@ -1,9 +1,7 @@ import { MenuItem, notify } from '@affine/component'; -import { - filterPage, - useEditCollection, -} from '@affine/core/components/page-list'; +import { filterPage } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; @@ -40,11 +38,12 @@ export const ExplorerCollectionNode = ({ operations?: NodeOperation[]; }) => { const t = useI18n(); - const { globalContextService, collectionService } = useServices({ - GlobalContextService, - CollectionService, - }); - const { open: openEditCollectionModal } = useEditCollection(); + const { globalContextService, collectionService, workspaceDialogService } = + useServices({ + GlobalContextService, + CollectionService, + WorkspaceDialogService, + }); const active = useLiveData(globalContextService.globalContext.collectionId.$) === collectionId; @@ -60,17 +59,10 @@ export const ExplorerCollectionNode = ({ if (!collection) { return; } - openEditCollectionModal(collection) - .then(collection => { - return collectionService.updateCollection( - collection.id, - () => collection - ); - }) - .catch(err => { - console.error(err); - }); - }, [collection, collectionService, openEditCollectionModal]); + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); + }, [collection, workspaceDialogService]); const collectionOperations = useExplorerCollectionNodeOperationsMenu( collectionId, diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx index bb7611af27..e3a86ba0be 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/doc/index.tsx @@ -1,6 +1,6 @@ import { Loading } from '@affine/component'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; -import { DocInfoService } from '@affine/core/modules/doc-info'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { useI18n } from '@affine/i18n'; @@ -90,13 +90,13 @@ export const ExplorerDocNode = ({ ); }, [indexerLoading]); - const docInfoModal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const option = useMemo( () => ({ - openInfoModal: () => docInfoModal.open(docId), + openInfoModal: () => workspaceDialogService.open('doc-info', { docId }), openNodeCollapsed: () => setCollapsed(false), }), - [docId, docInfoModal] + [docId, workspaceDialogService] ); const operations = useExplorerDocNodeOperationsMenu(docId, option); const { handleAddLinkedPage } = useExplorerDocNodeOperations(docId, option); diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx index 577bf7ef45..5315ecfc2e 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/folder/index.tsx @@ -7,6 +7,7 @@ import { notify, } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { ExplorerTreeNodeIcon, NodeOperation, @@ -37,11 +38,6 @@ import { import { difference } from 'lodash-es'; import { useCallback, useMemo, useState } from 'react'; -import { - useSelectCollection, - useSelectDoc, - useSelectTag, -} from '../../../selector'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; import { ExplorerTreeNode } from '../../tree/node'; import { ExplorerCollectionNode } from '../collection'; @@ -124,14 +120,13 @@ const ExplorerFolderNodeFolder = ({ operations?: NodeOperation[]; }) => { const t = useI18n(); - const { workspaceService, featureFlagService } = useServices({ - WorkspaceService, - CompatibleFavoriteItemsAdapter, - FeatureFlagService, - }); - const openDocsSelector = useSelectDoc(); - const openTagsSelector = useSelectTag(); - const openCollectionsSelector = useSelectCollection(); + const { workspaceService, featureFlagService, workspaceDialogService } = + useServices({ + WorkspaceService, + CompatibleFavoriteItemsAdapter, + FeatureFlagService, + WorkspaceDialogService, + }); const name = useLiveData(node.name$); const enableEmojiIcon = useLiveData( featureFlagService.flags.enable_emoji_folder_icon.$ @@ -191,12 +186,19 @@ const ExplorerFolderNodeFolder = ({ .filter(Boolean) as string[]; const selector = type === 'doc' - ? openDocsSelector + ? 'doc-selector' : type === 'collection' - ? openCollectionsSelector - : openTagsSelector; - selector(initialIds, { type, where: 'folder' }) - .then(selectedIds => { + ? 'collection-selector' + : 'tag-selector'; + workspaceDialogService.open( + selector, + { + init: initialIds, + }, + selectedIds => { + if (selectedIds === undefined) { + return; + } const newItemIds = difference(selectedIds, initialIds); const removedItemIds = difference(initialIds, selectedIds); const removedItems = children.filter( @@ -210,22 +212,14 @@ const ExplorerFolderNodeFolder = ({ removedItems.forEach(node => node.delete()); const updated = newItemIds.length + removedItems.length; updated && setCollapsed(false); - }) - .catch(err => { - console.error(`Unexpected error while selecting ${type}`, err); - }); + } + ); track.$.navigationPanel.organize.createOrganizeItem({ type: 'link', target: type, }); }, - [ - children, - node, - openCollectionsSelector, - openDocsSelector, - openTagsSelector, - ] + [children, node, workspaceDialogService] ); const createSubTipRenderer = useCallback( diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx index 51f494c98d..e98793836e 100644 --- a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx @@ -1,6 +1,13 @@ -import { IconButton, MenuItem, MenuSeparator, toast } from '@affine/component'; +import { + IconButton, + MenuItem, + MenuSeparator, + toast, + useConfirmModal, +} from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { NodeOperation } from '@affine/core/modules/explorer'; import { FavoriteService } from '@affine/core/modules/favorite'; import { TagService } from '@affine/core/modules/tag'; @@ -16,6 +23,7 @@ import { import { DocsService, FeatureFlagService, + GlobalCacheService, useLiveData, useService, useServices, @@ -23,7 +31,6 @@ import { } from '@toeverything/infra'; import { useCallback, useMemo } from 'react'; -import { useSelectDoc } from '../../../selector'; import { TagRenameSubMenu } from './dialog'; export const useExplorerTagNodeOperations = ( @@ -35,15 +42,23 @@ export const useExplorerTagNodeOperations = ( } ) => { const t = useI18n(); - const openDocSelector = useSelectDoc(); - const { workbenchService, workspaceService, tagService, favoriteService } = - useServices({ - WorkbenchService, - WorkspaceService, - TagService, - DocsService, - FavoriteService, - }); + const { + workbenchService, + workspaceService, + tagService, + favoriteService, + workspaceDialogService, + globalCacheService, + } = useServices({ + WorkbenchService, + WorkspaceService, + TagService, + DocsService, + FavoriteService, + WorkspaceDialogService, + GlobalCacheService, + }); + const { openConfirmModal } = useConfirmModal(); const favorite = useLiveData( favoriteService.favoriteList.favorite$('tag', tagId) @@ -122,15 +137,57 @@ export const useExplorerTagNodeOperations = ( ); const handleOpenDocSelector = useCallback(() => { const initialIds = tagRecord?.pageIds$.value; - openDocSelector(initialIds, { where: 'tag', type: 'doc' }) - .then(selectedIds => { + workspaceDialogService.open( + 'doc-selector', + { + init: initialIds ?? [], + onBeforeConfirm(ids, cb) { + const hasRemoved = initialIds?.some(id => !ids?.includes(id)); + if ( + hasRemoved && + globalCacheService.globalCache.get( + 'mobile:tags:will-be-removed-warning-read' + ) !== true + ) { + openConfirmModal({ + title: t['com.affine.m.selector.remove-warning.title'](), + description: t['com.affine.m.selector.remove-warning.message']({ + type: t['com.affine.m.selector.type-doc'](), + where: t['com.affine.m.selector.where-tag'](), + }), + cancelText: t['com.affine.m.selector.remove-warning.cancel'](), + confirmText: t['com.affine.m.selector.remove-warning.confirm'](), + reverseFooter: true, + onConfirm: () => { + globalCacheService.globalCache.set( + 'mobile:tags:will-be-removed-warning-read', + true + ); + cb(); + }, + }); + } else { + cb(); + } + }, + }, + selectedIds => { + if (selectedIds === undefined) { + return; + } const newIds = selectedIds.filter(id => !initialIds?.includes(id)); const removedIds = initialIds?.filter(id => !selectedIds.includes(id)); newIds.forEach(id => tagRecord?.tag(id)); removedIds?.forEach(id => tagRecord?.untag(id)); - }) - .catch(console.error); - }, [openDocSelector, tagRecord]); + } + ); + }, [ + tagRecord, + workspaceDialogService, + globalCacheService.globalCache, + openConfirmModal, + t, + ]); return useMemo( () => ({ diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.css.ts b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.css.ts new file mode 100644 index 0000000000..f9dd1bc597 --- /dev/null +++ b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.css.ts @@ -0,0 +1,8 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + +export const createTips = style({ + color: cssVar('textSecondaryColor'), + fontSize: 12, + lineHeight: '20px', +}); diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx index 186ed64e52..2f8aea083a 100644 --- a/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx +++ b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx @@ -1,3 +1,4 @@ +import { usePromptModal } from '@affine/component'; import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager'; import { CollectionService } from '@affine/core/modules/collection'; import { ExplorerService } from '@affine/core/modules/explorer'; @@ -7,12 +8,12 @@ import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { useLiveData, useServices } from '@toeverything/infra'; import { nanoid } from 'nanoid'; -import { useCallback, useState } from 'react'; +import { useCallback } from 'react'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; import { CollapsibleSection } from '../../layouts/collapsible-section'; import { ExplorerCollectionNode } from '../../nodes/collection'; -import { CollectionRenameDialog } from '../../nodes/collection/dialog'; +import * as styles from './index.css'; export const ExplorerCollections = () => { const t = useI18n(); @@ -23,22 +24,42 @@ export const ExplorerCollections = () => { }); const explorerSection = explorerService.sections.collections; const collections = useLiveData(collectionService.collections$); - const [showCreateCollectionModal, setShowCreateCollectionModal] = - useState(false); + const { openPromptModal } = usePromptModal(); - const handleCreateCollection = useCallback( - (name: string) => { - setShowCreateCollectionModal(false); - const id = nanoid(); - collectionService.addCollection(createEmptyCollection(id, { name })); - track.$.navigationPanel.organize.createOrganizeItem({ - type: 'collection', - }); - workbenchService.workbench.openCollection(id); - explorerSection.setCollapsed(false); - }, - [collectionService, explorerSection, workbenchService.workbench] - ); + const handleCreateCollection = useCallback(() => { + openPromptModal({ + title: t['com.affine.editCollection.saveCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + children: ( +
+ {t['com.affine.editCollectionName.createTips']()} +
+ ), + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { + const id = nanoid(); + collectionService.addCollection(createEmptyCollection(id, { name })); + track.$.navigationPanel.organize.createOrganizeItem({ + type: 'collection', + }); + workbenchService.workbench.openCollection(id); + explorerSection.setCollapsed(false); + }, + }); + }, [ + collectionService, + explorerSection, + openPromptModal, + t, + workbenchService.workbench, + ]); return ( { setShowCreateCollectionModal(true)} - /> - handleCreateCollection()} /> diff --git a/packages/frontend/core/src/mobile/components/index.ts b/packages/frontend/core/src/mobile/components/index.ts index d7197cd78b..86dc58c0ef 100644 --- a/packages/frontend/core/src/mobile/components/index.ts +++ b/packages/frontend/core/src/mobile/components/index.ts @@ -4,7 +4,6 @@ export * from './page-header'; export * from './rename'; export * from './search-input'; export * from './search-result'; -export * from './selector'; export * from './skeletons'; export * from './user-plan-tag'; export * from './workspace-selector'; diff --git a/packages/frontend/core/src/mobile/components/selector/collection-selector.tsx b/packages/frontend/core/src/mobile/components/selector/collection-selector.tsx deleted file mode 100644 index 57947eb2f1..0000000000 --- a/packages/frontend/core/src/mobile/components/selector/collection-selector.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { BaseSelectorDialogProps } from '@affine/core/components/page-list/selector'; -import { CollectionService } from '@affine/core/modules/collection'; -import { ViewLayersIcon } from '@blocksuite/icons/rc'; -import { useLiveData, useService } from '@toeverything/infra'; -import { useMemo } from 'react'; - -import type { DocsSelectorProps } from './doc-selector'; -import { GenericSelector, type GenericSelectorProps } from './generic-selector'; - -export interface CollectionsSelectorProps - extends BaseSelectorDialogProps, - Pick {} - -export const CollectionsSelector = ({ - init = [], - onCancel, - onConfirm, - ...otherProps -}: DocsSelectorProps) => { - const collectionService = useService(CollectionService); - const collections = useLiveData(collectionService.collections$); - - const list = useMemo(() => { - return collections.map(collection => ({ - id: collection.id, - icon: , - label: collection.name, - })); - }, [collections]); - - return ( - - ); -}; diff --git a/packages/frontend/core/src/mobile/components/selector/index.tsx b/packages/frontend/core/src/mobile/components/selector/index.tsx deleted file mode 100644 index c1bfda8dc2..0000000000 --- a/packages/frontend/core/src/mobile/components/selector/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useSelectDialog } from '@affine/core/components/page-list/selector'; -import { cssVarV2 } from '@toeverything/theme/v2'; - -import { - CollectionsSelector, - type CollectionsSelectorProps, -} from './collection-selector'; -import { DocsSelector, type DocsSelectorProps } from './doc-selector'; -import { TagsSelector, type TagsSelectorProps } from './tag-selector'; - -const options: Parameters[2] = { - modalProps: { - fullScreen: true, - width: undefined, - height: undefined, - contentOptions: { - style: { - background: cssVarV2('layer/background/secondary'), - padding: 0, - }, - }, - }, -}; - -export const useSelectDoc = () => { - return useSelectDialog( - DocsSelector, - 'select-doc-dialog', - options - ); -}; - -export const useSelectCollection = () => { - return useSelectDialog( - CollectionsSelector, - 'select-collection-dialog', - options - ); -}; - -export const useSelectTag = () => { - return useSelectDialog( - TagsSelector, - 'select-tag-dialog', - options - ); -}; diff --git a/packages/frontend/core/src/mobile/dialogs/index.tsx b/packages/frontend/core/src/mobile/dialogs/index.tsx new file mode 100644 index 0000000000..e2fc222b18 --- /dev/null +++ b/packages/frontend/core/src/mobile/dialogs/index.tsx @@ -0,0 +1,90 @@ +import { AuthModal } from '@affine/core/components/affine/auth'; +import { + type DialogComponentProps, + type GLOBAL_DIALOG_SCHEMA, + GlobalDialogService, + WorkspaceDialogService, +} from '@affine/core/modules/dialogs'; +import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant'; +import { useLiveData, useService } from '@toeverything/infra'; + +import { CollectionSelectorDialog } from './selectors/collection-selector'; +import { DocSelectorDialog } from './selectors/doc-selector'; +import { TagSelectorDialog } from './selectors/tag-selector'; +import { SettingDialog } from './setting'; + +const GLOBAL_DIALOGS = { + // 'create-workspace': CreateWorkspaceDialog, + // 'import-workspace': ImportWorkspaceDialog, + // 'import-template': ImportTemplateDialog, + setting: SettingDialog, + // import: ImportDialog, +} satisfies { + [key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC< + DialogComponentProps + >; +}; + +const WORKSPACE_DIALOGS = { + // 'doc-info': DocInfoDialog, + // 'collection-editor': CollectionEditorDialog, + 'tag-selector': TagSelectorDialog, + 'doc-selector': DocSelectorDialog, + 'collection-selector': CollectionSelectorDialog, +} satisfies { + [key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC< + DialogComponentProps + >; +}; + +export const GlobalDialogs = () => { + const globalDialogService = useService(GlobalDialogService); + const dialogs = useLiveData(globalDialogService.dialogs$); + return ( + <> + {dialogs.map(dialog => { + const DialogComponent = + GLOBAL_DIALOGS[dialog.type as keyof typeof GLOBAL_DIALOGS]; + if (!DialogComponent) { + return null; + } + return ( + { + globalDialogService.close(dialog.id, result); + }} + /> + ); + })} + + + + ); +}; + +export const WorkspaceDialogs = () => { + const workspaceDialogService = useService(WorkspaceDialogService); + const dialogs = useLiveData(workspaceDialogService.dialogs$); + return ( + <> + {dialogs.map(dialog => { + const DialogComponent = + WORKSPACE_DIALOGS[dialog.type as keyof typeof WORKSPACE_DIALOGS]; + if (!DialogComponent) { + return null; + } + return ( + { + workspaceDialogService.close(dialog.id, result); + }} + /> + ); + })} + + ); +}; diff --git a/packages/frontend/core/src/mobile/dialogs/selectors/collection-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/collection-selector.tsx new file mode 100644 index 0000000000..23c5293614 --- /dev/null +++ b/packages/frontend/core/src/mobile/dialogs/selectors/collection-selector.tsx @@ -0,0 +1,55 @@ +import { Modal } from '@affine/component'; +import { CollectionService } from '@affine/core/modules/collection'; +import type { + DialogComponentProps, + WORKSPACE_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; +import { useI18n } from '@affine/i18n'; +import { ViewLayersIcon } from '@blocksuite/icons/rc'; +import { useLiveData, useService } from '@toeverything/infra'; +import { cssVarV2 } from '@toeverything/theme/v2'; +import { useMemo } from 'react'; + +import { GenericSelector } from './generic-selector'; + +export const CollectionSelectorDialog = ({ + close, + init, + onBeforeConfirm, +}: DialogComponentProps) => { + const t = useI18n(); + const collectionService = useService(CollectionService); + const collections = useLiveData(collectionService.collections$); + + const list = useMemo(() => { + return collections.map(collection => ({ + id: collection.id, + icon: , + label: collection.name, + })); + }, [collections]); + + return ( + close()} + withoutCloseButton + fullScreen + contentOptions={{ + style: { + background: cssVarV2('layer/background/secondary'), + padding: 0, + }, + }} + > + + + ); +}; diff --git a/packages/frontend/core/src/mobile/components/selector/doc-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx similarity index 56% rename from packages/frontend/core/src/mobile/components/selector/doc-selector.tsx rename to packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx index 6a15d0b44b..c294111297 100644 --- a/packages/frontend/core/src/mobile/components/selector/doc-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx @@ -1,14 +1,15 @@ -import type { BaseSelectorDialogProps } from '@affine/core/components/page-list/selector'; +import { Modal } from '@affine/component'; +import type { + DialogComponentProps, + WORKSPACE_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { useI18n } from '@affine/i18n'; import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { cssVarV2 } from '@toeverything/theme/v2'; import { useMemo } from 'react'; -import { GenericSelector, type GenericSelectorProps } from './generic-selector'; - -export interface DocsSelectorProps - extends BaseSelectorDialogProps, - Pick {} +import { GenericSelector } from './generic-selector'; const DocIcon = ({ docId }: { docId: string }) => { const docDisplayMetaService = useService(DocDisplayMetaService); @@ -25,12 +26,12 @@ const DocLabel = ({ docId }: { docId: string }) => { return typeof label === 'string' ? label : t[label.i18nKey](); }; -export const DocsSelector = ({ - init = [], - onCancel, - onConfirm, - ...otherProps -}: DocsSelectorProps) => { +export const DocSelectorDialog = ({ + close, + init, + onBeforeConfirm, +}: DialogComponentProps) => { + const t = useI18n(); const docsService = useService(DocsService); const docRecords = useLiveData(docsService.list.docs$); @@ -47,12 +48,26 @@ export const DocsSelector = ({ }, [docRecords]); return ( - + close()} + withoutCloseButton + fullScreen + contentOptions={{ + style: { + background: cssVarV2('layer/background/secondary'), + padding: 0, + }, + }} + > + + ); }; diff --git a/packages/frontend/core/src/mobile/components/selector/generic-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx similarity index 81% rename from packages/frontend/core/src/mobile/components/selector/generic-selector.tsx rename to packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx index 7003f555d9..82305ba508 100644 --- a/packages/frontend/core/src/mobile/components/selector/generic-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/generic-selector.tsx @@ -3,12 +3,10 @@ import { Checkbox, SafeArea, Scrollable, - useConfirmModal, useThemeColorMeta, } from '@affine/component'; import { useI18n } from '@affine/i18n'; import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; -import { GlobalCacheService, useService } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; import { type ReactNode, @@ -20,7 +18,7 @@ import { useState, } from 'react'; -import { PageHeader } from '../page-header'; +import { PageHeader } from '../../components/page-header'; import * as styles from './generic.css'; export interface GenericSelectorProps { @@ -34,10 +32,9 @@ export interface GenericSelectorProps { // changedRenderer?: (props: { added: number; removed: number }) => ReactNode; // removeWarningType?: string; // removeWarningWhere?: string; - type: 'doc' | 'tag' | 'collection'; - where: 'tag' | 'folder' | 'collection'; + typeName: string; + onBeforeConfirm?: (ids: string[], cb: () => void) => void; } -const WILL_BE_REMOVED_WARNING_KEY = 'willBeRemovedWarningRead'; const ChangedRenderer = ({ type, @@ -68,24 +65,18 @@ export const GenericSelector = ({ data, title, confirmText, - type, - where, + typeName, onBack, onConfirm, + onBeforeConfirm, }: GenericSelectorProps) => { const t = useI18n(); useThemeColorMeta(cssVarV2('layer/background/secondary')); const listRef = useRef(null); const quickScrollRef = useRef(null); - const globalCache = useService(GlobalCacheService).globalCache; - const { openConfirmModal } = useConfirmModal(); - const whereText = t[`com.affine.m.selector.where-${where}`](); - const typeText = t[`com.affine.m.selector.type-${type}`](); + const typeText = typeName; - const [willBeRemovedWarningRead] = useState( - globalCache.get(WILL_BE_REMOVED_WARNING_KEY) - ); // make sure "initial ids" exist in list const [initial] = useState( originalInitial.filter(id => data.some(el => el.id === id)) @@ -111,39 +102,15 @@ export const GenericSelector = ({ } }); }, []); - const handleReadWillBeRemovedWarning = useCallback(() => { - globalCache.set(WILL_BE_REMOVED_WARNING_KEY, true); - }, [globalCache]); const handleConfirm = useCallback(() => { - if (!willBeRemovedWarningRead && removed.length > 0) { - openConfirmModal({ - title: t['com.affine.m.selector.remove-warning.title'](), - description: t['com.affine.m.selector.remove-warning.message']({ - type: typeText, - where: whereText, - }), - cancelText: t['com.affine.m.selector.remove-warning.cancel'](), - confirmText: t['com.affine.m.selector.remove-warning.confirm'](), - reverseFooter: true, - onConfirm: () => { - handleReadWillBeRemovedWarning(); - onConfirm?.(selected); - }, + if (onBeforeConfirm) { + onBeforeConfirm(selected, () => { + onConfirm?.(selected); }); - return; + } else { + onConfirm?.(selected); } - onConfirm?.(selected); - }, [ - handleReadWillBeRemovedWarning, - onConfirm, - openConfirmModal, - removed, - selected, - t, - typeText, - whereText, - willBeRemovedWarningRead, - ]); + }, [onBeforeConfirm, onConfirm, selected]); // touch & move to select useEffect(() => { @@ -273,7 +240,6 @@ export const GenericSelector = ({
{t['com.affine.m.selector.info-total']({ total: initial.length + '', - where: whereText, })}
diff --git a/packages/frontend/core/src/mobile/components/selector/generic.css.ts b/packages/frontend/core/src/mobile/dialogs/selectors/generic.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/components/selector/generic.css.ts rename to packages/frontend/core/src/mobile/dialogs/selectors/generic.css.ts diff --git a/packages/frontend/core/src/mobile/components/selector/tag-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/tag-selector.tsx similarity index 53% rename from packages/frontend/core/src/mobile/components/selector/tag-selector.tsx rename to packages/frontend/core/src/mobile/dialogs/selectors/tag-selector.tsx index 224b13ff44..ef86ae07f7 100644 --- a/packages/frontend/core/src/mobile/components/selector/tag-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/tag-selector.tsx @@ -1,13 +1,15 @@ -import type { BaseSelectorDialogProps } from '@affine/core/components/page-list/selector'; +import { Modal } from '@affine/component'; +import type { + DialogComponentProps, + WORKSPACE_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; import { TagService } from '@affine/core/modules/tag'; +import { useI18n } from '@affine/i18n'; import { useLiveData, useService } from '@toeverything/infra'; +import { cssVarV2 } from '@toeverything/theme/v2'; import { useMemo } from 'react'; -import { GenericSelector, type GenericSelectorProps } from './generic-selector'; - -export interface TagsSelectorProps - extends BaseSelectorDialogProps, - Pick {} +import { GenericSelector } from './generic-selector'; const TagIcon = ({ tagId }: { tagId: string }) => { const tagService = useService(TagService); @@ -35,12 +37,12 @@ const TagLabel = ({ tagId }: { tagId: string }) => { return name; }; -export const TagsSelector = ({ - init = [], - onCancel, - onConfirm, - ...otherProps -}: TagsSelectorProps) => { +export const TagSelectorDialog = ({ + close, + init, + onBeforeConfirm, +}: DialogComponentProps) => { + const t = useI18n(); const tagService = useService(TagService); const tags = useLiveData(tagService.tagList.tags$); @@ -53,12 +55,26 @@ export const TagsSelector = ({ }, [tags]); return ( - + close()} + withoutCloseButton + fullScreen + contentOptions={{ + style: { + background: cssVarV2('layer/background/secondary'), + padding: 0, + }, + }} + > + + ); }; diff --git a/packages/frontend/core/src/mobile/views/settings/about/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/about/index.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/about/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/about/index.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/appearance/font.tsx b/packages/frontend/core/src/mobile/dialogs/setting/appearance/font.tsx similarity index 89% rename from packages/frontend/core/src/mobile/views/settings/appearance/font.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/appearance/font.tsx index 0d40a0b582..6683a563b2 100644 --- a/packages/frontend/core/src/mobile/views/settings/appearance/font.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/appearance/font.tsx @@ -1,4 +1,4 @@ -import { getBaseFontStyleOptions } from '@affine/core/components/affine/setting-modal/general-setting/editor/general'; +import { getBaseFontStyleOptions } from '@affine/core/desktop/dialogs/setting/general-setting/editor/general'; import { EditorSettingService, type FontFamily, diff --git a/packages/frontend/core/src/mobile/views/settings/appearance/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/appearance/index.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/appearance/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/appearance/index.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/appearance/language.tsx b/packages/frontend/core/src/mobile/dialogs/setting/appearance/language.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/appearance/language.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/appearance/language.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/appearance/theme.tsx b/packages/frontend/core/src/mobile/dialogs/setting/appearance/theme.tsx similarity index 84% rename from packages/frontend/core/src/mobile/views/settings/appearance/theme.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/appearance/theme.tsx index 2c4a90ac3c..a9fdedd335 100644 --- a/packages/frontend/core/src/mobile/views/settings/appearance/theme.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/appearance/theme.tsx @@ -1,4 +1,4 @@ -import { getThemeOptions } from '@affine/core/components/affine/setting-modal/general-setting/appearance'; +import { getThemeOptions } from '@affine/core/desktop/dialogs/setting/general-setting/appearance'; import { useI18n } from '@affine/i18n'; import { useTheme } from 'next-themes'; import { useMemo } from 'react'; diff --git a/packages/frontend/core/src/mobile/views/settings/dropdown-select.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/dropdown-select.css.ts rename to packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts diff --git a/packages/frontend/core/src/mobile/views/settings/dropdown-select.tsx b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/dropdown-select.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/group.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/group.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/group.css.ts rename to packages/frontend/core/src/mobile/dialogs/setting/group.css.ts diff --git a/packages/frontend/core/src/mobile/views/settings/group.tsx b/packages/frontend/core/src/mobile/dialogs/setting/group.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/group.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/group.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/index.tsx similarity index 72% rename from packages/frontend/core/src/mobile/views/settings/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/index.tsx index b9d3d15ad5..162860351d 100644 --- a/packages/frontend/core/src/mobile/views/settings/index.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/index.tsx @@ -1,11 +1,13 @@ import { Modal } from '@affine/component'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { AuthService } from '@affine/core/modules/cloud'; +import type { + DialogComponentProps, + GLOBAL_DIALOG_SCHEMA, +} from '@affine/core/modules/dialogs'; import { useI18n } from '@affine/i18n'; import { useService } from '@toeverything/infra'; import { cssVarV2 } from '@toeverything/theme/v2'; -import { useAtom } from 'jotai'; -import { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; import { PageHeader } from '../../components'; import { AboutGroup } from './about'; @@ -15,35 +17,6 @@ import * as styles from './style.css'; import { UserProfile } from './user-profile'; import { UserUsage } from './user-usage'; -export const MobileSettingModal = () => { - const [{ open }, setOpen] = useAtom(openSettingModalAtom); - - const onOpenChange = useCallback( - (open: boolean) => setOpen(prev => ({ ...prev, open })), - [setOpen] - ); - const closeModal = useCallback(() => onOpenChange(false), [onOpenChange]); - - return ( - - - - ); -}; - const MobileSetting = ({ onClose }: { onClose: () => void }) => { const t = useI18n(); const session = useService(AuthService).session; @@ -68,3 +41,26 @@ const MobileSetting = ({ onClose }: { onClose: () => void }) => { ); }; + +export const SettingDialog = ({ + close, +}: DialogComponentProps) => { + return ( + close()} + contentOptions={{ + style: { + padding: 0, + overflowY: 'auto', + backgroundColor: cssVarV2('layer/background/secondary'), + }, + }} + withoutCloseButton + > + + + ); +}; diff --git a/packages/frontend/core/src/mobile/views/settings/others/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/others/index.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/others/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/others/index.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/row.layout.tsx b/packages/frontend/core/src/mobile/dialogs/setting/row.layout.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/row.layout.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/row.layout.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/style.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/style.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/style.css.ts rename to packages/frontend/core/src/mobile/dialogs/setting/style.css.ts diff --git a/packages/frontend/core/src/mobile/views/settings/user-profile/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/user-profile/index.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/user-profile/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/user-profile/index.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/user-profile/style.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/user-profile/style.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/user-profile/style.css.ts rename to packages/frontend/core/src/mobile/dialogs/setting/user-profile/style.css.ts diff --git a/packages/frontend/core/src/mobile/views/settings/user-usage/index.tsx b/packages/frontend/core/src/mobile/dialogs/setting/user-usage/index.tsx similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/user-usage/index.tsx rename to packages/frontend/core/src/mobile/dialogs/setting/user-usage/index.tsx diff --git a/packages/frontend/core/src/mobile/views/settings/user-usage/style.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/user-usage/style.css.ts similarity index 100% rename from packages/frontend/core/src/mobile/views/settings/user-usage/style.css.ts rename to packages/frontend/core/src/mobile/dialogs/setting/user-usage/style.css.ts diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/sheets/doc-info.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/sheets/doc-info.tsx index 82bf78eebc..8a12e95ba7 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/sheets/doc-info.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/sheets/doc-info.tsx @@ -3,8 +3,8 @@ import { type DefaultOpenProperty, DocPropertiesTable, } from '@affine/core/components/doc-properties'; -import { LinksRow } from '@affine/core/components/doc-properties/info-modal/links-row'; -import { TimeRow } from '@affine/core/components/doc-properties/info-modal/time-row'; +import { LinksRow } from '@affine/core/desktop/dialogs/doc-info/links-row'; +import { TimeRow } from '@affine/core/desktop/dialogs/doc-info/time-row'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import { useI18n } from '@affine/i18n'; import { LiveData, useLiveData, useService } from '@toeverything/infra'; diff --git a/packages/frontend/core/src/mobile/pages/workspace/index.tsx b/packages/frontend/core/src/mobile/pages/workspace/index.tsx index c0173b532b..c8a000b32b 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/index.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/index.tsx @@ -1,6 +1,5 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback'; -import { AppFallback } from '@affine/core/components/affine/app-container'; import { PageNotFound } from '@affine/core/desktop/pages/404'; import { MobileWorkbenchRoot } from '@affine/core/desktop/pages/workspace/workbench-root'; import { workbenchRoutes } from '@affine/core/mobile/workbench-router'; @@ -136,7 +135,7 @@ export const Component = () => { return ; } if (!meta) { - return ; + return; } return ( diff --git a/packages/frontend/core/src/mobile/pages/workspace/layout.tsx b/packages/frontend/core/src/mobile/pages/workspace/layout.tsx index 16750c6264..7e5b038b7d 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/layout.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/layout.tsx @@ -1,6 +1,13 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; -import { WorkspaceLayoutProviders } from '@affine/core/components/layouts/workspace-layout'; +import { AiLoginRequiredModal } from '@affine/core/components/affine/auth/ai-login-required'; +import { + CloudQuotaModal, + LocalQuotaModal, +} from '@affine/core/components/affine/quota-reached-modal'; import { SWRConfigProvider } from '@affine/core/components/providers/swr-config-provider'; +import { WorkspaceSideEffects } from '@affine/core/components/providers/workspace-side-effects'; +import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; +import { WorkspaceFlavour } from '@affine/env/workspace'; import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; import { FrameworkScope, @@ -17,7 +24,7 @@ import { } from 'react'; import { AppFallback } from '../../components'; -import { MobileCurrentWorkspaceModals } from '../../provider/model-provider'; +import { WorkspaceDialogs } from '../../dialogs'; // TODO(@forehalo): reuse the global context with [core/electron] declare global { @@ -84,19 +91,24 @@ export const WorkspaceLayout = ({ } if (!isRootDocReady) { - return ( - - - - ); + return ; } return ( - - {children} + + + {/* ---- some side-effect components ---- */} + + {workspace?.flavour === WorkspaceFlavour.LOCAL && } + {workspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && ( + + )} + + + {children} diff --git a/packages/frontend/core/src/mobile/provider/model-provider.tsx b/packages/frontend/core/src/mobile/provider/model-provider.tsx deleted file mode 100644 index 8deac6441f..0000000000 --- a/packages/frontend/core/src/mobile/provider/model-provider.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { NotificationCenter } from '@affine/component'; -import { AiLoginRequiredModal } from '@affine/core/components/affine/auth/ai-login-required'; -import { HistoryTipsModal } from '@affine/core/components/affine/history-tips-modal'; -import { IssueFeedbackModal } from '@affine/core/components/affine/issue-feedback-modal'; -import { - CloudQuotaModal, - LocalQuotaModal, -} from '@affine/core/components/affine/quota-reached-modal'; -import { StarAFFiNEModal } from '@affine/core/components/affine/star-affine-modal'; -import { InfoModal } from '@affine/core/components/doc-properties'; -import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper'; -import { MoveToTrash } from '@affine/core/components/page-list'; -import { SignOutConfirmModal } from '@affine/core/components/providers/modal-provider'; -import { CreateWorkspaceDialogProvider } from '@affine/core/modules/create-workspace'; -import { PeekViewManagerModal } from '@affine/core/modules/peek-view'; -import { WorkspaceFlavour } from '@affine/env/workspace'; -import { useService, WorkspaceService } from '@toeverything/infra'; -import { useCallback } from 'react'; - -import { MobileSettingModal } from '../views'; -import { MobileSignInModal } from '../views/sign-in/modal'; - -export function MobileCurrentWorkspaceModals() { - const currentWorkspace = useService(WorkspaceService).workspace; - - const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper(); - const deletePageTitles = trashModal.pageTitles; - const trashConfirmOpen = trashModal.open; - const onTrashConfirmOpenChange = useCallback( - (open: boolean) => { - setTrashModal({ - ...trashModal, - open, - }); - }, - [trashModal, setTrashModal] - ); - - return ( - <> - - - {currentWorkspace ? : null} - {currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && ( - <> - - - - )} - {currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && ( - - )} - - - - {currentWorkspace ? : null} - - ); -} - -// I don't like the name, but let's keep it for now -export const AllWorkspaceModals = () => { - return ( - <> - - - - - - ); -}; diff --git a/packages/frontend/core/src/mobile/router.tsx b/packages/frontend/core/src/mobile/router.tsx index 2e2ff00185..78d2c4882a 100644 --- a/packages/frontend/core/src/mobile/router.tsx +++ b/packages/frontend/core/src/mobile/router.tsx @@ -1,3 +1,4 @@ +import { NotificationCenter } from '@affine/component'; import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper'; import { wrapCreateBrowserRouter } from '@sentry/react'; import { useEffect, useState } from 'react'; @@ -10,7 +11,8 @@ import { useNavigate, } from 'react-router-dom'; -import { AllWorkspaceModals } from './provider/model-provider'; +import { GlobalDialogs } from './dialogs'; +import { MobileSignInModal } from './views/sign-in/modal'; function RootRouter() { const navigate = useNavigate(); @@ -23,7 +25,9 @@ function RootRouter() { return ( ready && ( - + + + ) diff --git a/packages/frontend/core/src/mobile/views/home-header/index.tsx b/packages/frontend/core/src/mobile/views/home-header/index.tsx index 74a140e1ee..acd89923df 100644 --- a/packages/frontend/core/src/mobile/views/home-header/index.tsx +++ b/packages/frontend/core/src/mobile/views/home-header/index.tsx @@ -3,13 +3,12 @@ import { SafeArea, startScopedViewTransition, } from '@affine/component'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; +import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { SettingsIcon } from '@blocksuite/icons/rc'; import { useService } from '@toeverything/infra'; import clsx from 'clsx'; -import { useSetAtom } from 'jotai'; import { useCallback, useState } from 'react'; import { SearchInput, WorkspaceSelector } from '../../components'; @@ -26,7 +25,7 @@ import * as styles from './styles.css'; export const HomeHeader = () => { const t = useI18n(); const workbench = useService(WorkbenchService).workbench; - const openSetting = useSetAtom(openSettingModalAtom); + const globalDialogService = useService(GlobalDialogService); const [dense, setDense] = useState(false); @@ -53,7 +52,9 @@ export const HomeHeader = () => {
{ - openSetting({ open: true, activeTab: 'appearance' }); + globalDialogService.open('setting', { + activeTab: 'appearance', + }); }} size="24" style={{ padding: 10 }} diff --git a/packages/frontend/core/src/mobile/views/index.ts b/packages/frontend/core/src/mobile/views/index.ts index 85a42b5e19..7b9275ccbe 100644 --- a/packages/frontend/core/src/mobile/views/index.ts +++ b/packages/frontend/core/src/mobile/views/index.ts @@ -1,4 +1,3 @@ export * from './all-docs'; export * from './home-header'; export * from './recent-docs'; -export * from './settings'; diff --git a/packages/frontend/core/src/modules/create-workspace/entities/dialog.ts b/packages/frontend/core/src/modules/create-workspace/entities/dialog.ts deleted file mode 100644 index 6d17967a1b..0000000000 --- a/packages/frontend/core/src/modules/create-workspace/entities/dialog.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Entity, LiveData } from '@toeverything/infra'; - -import type { CreateWorkspaceCallbackPayload } from '../types'; - -export class CreateWorkspaceDialog extends Entity { - readonly mode$ = new LiveData<'new' | 'add'>('new'); - readonly isOpen$ = new LiveData(false); - readonly callback$ = new LiveData< - (data: CreateWorkspaceCallbackPayload | undefined) => void - >(() => {}); - - open( - mode: 'new' | 'add', - callback?: (data: CreateWorkspaceCallbackPayload | undefined) => void - ) { - this.callback(undefined); - this.mode$.next(mode); - this.isOpen$.next(true); - if (callback) { - this.callback$.next(callback); - } - } - - callback(payload: CreateWorkspaceCallbackPayload | undefined) { - this.callback$.value(payload); - this.callback$.next(() => {}); - } - - close() { - this.isOpen$.next(false); - this.callback(undefined); - } -} diff --git a/packages/frontend/core/src/modules/create-workspace/index.ts b/packages/frontend/core/src/modules/create-workspace/index.ts deleted file mode 100644 index e55eaa7cbc..0000000000 --- a/packages/frontend/core/src/modules/create-workspace/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Framework } from '@toeverything/infra'; - -import { CreateWorkspaceDialog } from './entities/dialog'; -import { CreateWorkspaceDialogService } from './services/dialog'; - -export { CreateWorkspaceDialogService } from './services/dialog'; -export type { CreateWorkspaceCallbackPayload } from './types'; -export { CreateWorkspaceDialogProvider } from './views/dialog'; - -export function configureCreateWorkspaceModule(framework: Framework) { - framework.service(CreateWorkspaceDialogService).entity(CreateWorkspaceDialog); -} diff --git a/packages/frontend/core/src/modules/create-workspace/services/dialog.ts b/packages/frontend/core/src/modules/create-workspace/services/dialog.ts deleted file mode 100644 index 78e067ce15..0000000000 --- a/packages/frontend/core/src/modules/create-workspace/services/dialog.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Service } from '@toeverything/infra'; - -import { CreateWorkspaceDialog } from '../entities/dialog'; - -export class CreateWorkspaceDialogService extends Service { - dialog = this.framework.createEntity(CreateWorkspaceDialog); -} diff --git a/packages/frontend/core/src/modules/create-workspace/types.ts b/packages/frontend/core/src/modules/create-workspace/types.ts deleted file mode 100644 index b6f94e01ca..0000000000 --- a/packages/frontend/core/src/modules/create-workspace/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { WorkspaceMetadata } from '@toeverything/infra'; - -export type CreateWorkspaceMode = 'add' | 'new'; -export type CreateWorkspaceCallbackPayload = { - meta: WorkspaceMetadata; - defaultDocId?: string; -}; diff --git a/packages/frontend/core/src/modules/desktop-api/index.ts b/packages/frontend/core/src/modules/desktop-api/index.ts index 92949e58a4..f7e36d6694 100644 --- a/packages/frontend/core/src/modules/desktop-api/index.ts +++ b/packages/frontend/core/src/modules/desktop-api/index.ts @@ -1,27 +1,16 @@ -import { - DocsService, - type Framework, - WorkspaceScope, -} from '@toeverything/infra'; +import { type Framework } from '@toeverything/infra'; -import { WorkbenchService } from '../workbench/services/workbench'; import { DesktopApi } from './entities/electron-api'; import { ElectronApiImpl } from './impl'; import { DesktopApiProvider } from './provider'; -import { DesktopApiService, WorkspaceDesktopApiService } from './service'; +import { DesktopApiService } from './service/desktop-api'; export function configureDesktopApiModule(framework: Framework) { framework .impl(DesktopApiProvider, ElectronApiImpl) .entity(DesktopApi, [DesktopApiProvider]) - .service(DesktopApiService, [DesktopApi]) - .scope(WorkspaceScope) - .service(WorkspaceDesktopApiService, [ - DesktopApiService, - DocsService, - WorkbenchService, - ]); + .service(DesktopApiService, [DesktopApi]); } -export * from './service'; +export { DesktopApiService } from './service/desktop-api'; export type { ClientEvents, TabViewsMetaSchema } from '@affine/electron-api'; diff --git a/packages/frontend/core/src/modules/desktop-api/service/index.ts b/packages/frontend/core/src/modules/desktop-api/service/index.ts deleted file mode 100644 index 3a215acbc9..0000000000 --- a/packages/frontend/core/src/modules/desktop-api/service/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './desktop-api'; -export * from './workspace-events'; diff --git a/packages/frontend/core/src/modules/desktop-api/service/workspace-events.ts b/packages/frontend/core/src/modules/desktop-api/service/workspace-events.ts deleted file mode 100644 index 12316d3c47..0000000000 --- a/packages/frontend/core/src/modules/desktop-api/service/workspace-events.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { DocsService } from '@toeverything/infra'; -import { OnEvent, Service, WorkspaceInitialized } from '@toeverything/infra'; - -import { EditorSettingService } from '../../editor-setting'; -import type { WorkbenchService } from '../../workbench'; -import type { DesktopApiService } from './desktop-api'; - -// setup desktop events for workspace scope -@OnEvent(WorkspaceInitialized, e => e.setupApplicationMenuEvents) -export class WorkspaceDesktopApiService extends Service { - constructor( - private readonly desktopApi: DesktopApiService, - private readonly docsService: DocsService, - private readonly workbenchService: WorkbenchService - ) { - super(); - } - - async setupApplicationMenuEvents() { - this.desktopApi.events.applicationMenu.onNewPageAction(() => { - const editorSetting = - this.framework.get(EditorSettingService).editorSetting; - - const docProps = { - note: editorSetting.get('affine:note'), - }; - this.desktopApi.handler.ui - .isActiveTab() - .then(isActive => { - if (!isActive) { - return; - } - const page = this.docsService.createDoc({ docProps }); - this.workbenchService.workbench.openDoc(page.id); - }) - .catch(err => { - console.error(err); - }); - }); - } -} diff --git a/packages/frontend/core/src/modules/dialogs/constant.ts b/packages/frontend/core/src/modules/dialogs/constant.ts new file mode 100644 index 0000000000..47d436b83a --- /dev/null +++ b/packages/frontend/core/src/modules/dialogs/constant.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import type { DocMode } from '@blocksuite/affine/blocks'; +import type { WorkspaceMetadata } from '@toeverything/infra'; + +export type SettingTab = + | 'shortcuts' + | 'appearance' + | 'about' + | 'plans' + | 'billing' + | 'experimental-features' + | 'editor' + | 'account' + | `workspace:${'preference' | 'properties'}`; + +export type GLOBAL_DIALOG_SCHEMA = { + 'create-workspace': () => { + metadata: WorkspaceMetadata; + defaultDocId?: string; + }; + 'import-workspace': () => { + workspace: WorkspaceMetadata; + }; + 'import-template': (props: { + templateName: string; + templateMode: DocMode; + snapshotUrl: string; + }) => void; + import: () => void; + setting: (props: { + activeTab?: SettingTab; + workspaceMetadata?: WorkspaceMetadata | null; + scrollAnchor?: string; + }) => void; +}; + +export type WORKSPACE_DIALOG_SCHEMA = { + 'doc-info': (props: { docId: string }) => void; + 'doc-selector': (props: { + init: string[]; + onBeforeConfirm?: (ids: string[], cb: () => void) => void; + }) => string[]; + 'collection-selector': (props: { + init: string[]; + onBeforeConfirm?: (ids: string[], cb: () => void) => void; + }) => string[]; + 'collection-editor': (props: { + collectionId: string; + mode?: 'page' | 'rule'; + }) => void; + 'tag-selector': (props: { + init: string[]; + onBeforeConfirm?: (ids: string[], cb: () => void) => void; + }) => string[]; +}; diff --git a/packages/frontend/core/src/modules/dialogs/index.ts b/packages/frontend/core/src/modules/dialogs/index.ts new file mode 100644 index 0000000000..a32db5bc59 --- /dev/null +++ b/packages/frontend/core/src/modules/dialogs/index.ts @@ -0,0 +1,17 @@ +import type { Framework } from '@toeverything/infra'; +import { WorkspaceScope } from '@toeverything/infra'; + +import { GlobalDialogService } from './services/dialog'; +import { WorkspaceDialogService } from './services/workspace-dialog'; + +export type { GLOBAL_DIALOG_SCHEMA, WORKSPACE_DIALOG_SCHEMA } from './constant'; +export { GlobalDialogService } from './services/dialog'; +export { WorkspaceDialogService } from './services/workspace-dialog'; +export type { DialogComponentProps } from './types'; + +export function configureDialogModule(framework: Framework) { + framework + .service(GlobalDialogService) + .scope(WorkspaceScope) + .service(WorkspaceDialogService); +} diff --git a/packages/frontend/core/src/modules/dialogs/services/dialog.ts b/packages/frontend/core/src/modules/dialogs/services/dialog.ts new file mode 100644 index 0000000000..05ff5122c6 --- /dev/null +++ b/packages/frontend/core/src/modules/dialogs/services/dialog.ts @@ -0,0 +1,41 @@ +import { LiveData, Service } from '@toeverything/infra'; +import { nanoid } from 'nanoid'; + +import type { GLOBAL_DIALOG_SCHEMA } from '../constant'; +import type { DialogProps, DialogResult, OpenedDialog } from '../types'; + +export class GlobalDialogService extends Service { + readonly dialogs$ = new LiveData[]>([]); + + open( + type: T, + props: DialogProps, + callback?: (result?: DialogResult) => void + ): string { + const id = nanoid(); + this.dialogs$.next([ + ...this.dialogs$.value, + { + type, + props, + callback, + id, + } as OpenedDialog, + ]); + return id; + } + + close(id: string, result?: unknown) { + this.dialogs$.next( + this.dialogs$.value.filter(dialog => { + if (dialog.id === id) { + if (dialog.callback) { + dialog.callback(result as any); + } + return false; + } + return true; + }) + ); + } +} diff --git a/packages/frontend/core/src/modules/dialogs/services/workspace-dialog.ts b/packages/frontend/core/src/modules/dialogs/services/workspace-dialog.ts new file mode 100644 index 0000000000..8dccb2109f --- /dev/null +++ b/packages/frontend/core/src/modules/dialogs/services/workspace-dialog.ts @@ -0,0 +1,41 @@ +import { LiveData, Service } from '@toeverything/infra'; +import { nanoid } from 'nanoid'; + +import type { WORKSPACE_DIALOG_SCHEMA } from '../constant'; +import type { DialogProps, DialogResult, OpenedDialog } from '../types'; + +export class WorkspaceDialogService extends Service { + readonly dialogs$ = new LiveData[]>([]); + + open( + type: T, + props: DialogProps, + callback?: (result?: DialogResult) => void + ): string { + const id = nanoid(); + this.dialogs$.next([ + ...this.dialogs$.value, + { + type, + props, + callback, + id, + } as OpenedDialog, + ]); + return id; + } + + close(id: string, result?: unknown) { + this.dialogs$.next( + this.dialogs$.value.filter(dialog => { + if (dialog.id === id) { + if (dialog.callback) { + dialog.callback(result as any); + } + return false; + } + return true; + }) + ); + } +} diff --git a/packages/frontend/core/src/modules/dialogs/types.ts b/packages/frontend/core/src/modules/dialogs/types.ts new file mode 100644 index 0000000000..6988aaa13b --- /dev/null +++ b/packages/frontend/core/src/modules/dialogs/types.ts @@ -0,0 +1,16 @@ +export type OpenedDialog = { + [key in keyof T]: { + type: key; + props: DialogProps; + callback: (result?: DialogResult) => void; + }; +}[keyof T] & { id: string }; + +export type DialogProps = T extends (props: infer P) => void ? P : undefined; +export type DialogResult = T extends (...args: any[]) => infer R + ? R + : undefined; + +export type DialogComponentProps = DialogProps & { + close: (result?: DialogResult) => void; +}; diff --git a/packages/frontend/core/src/modules/doc-info/entities/modal.ts b/packages/frontend/core/src/modules/doc-info/entities/modal.ts deleted file mode 100644 index c596847724..0000000000 --- a/packages/frontend/core/src/modules/doc-info/entities/modal.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Entity, LiveData } from '@toeverything/infra'; - -export class DocInfoModal extends Entity { - public readonly docId$ = new LiveData(null); - public readonly open$ = LiveData.computed(get => !!get(this.docId$)); - - public open(docId?: string) { - if (docId) { - this.docId$.next(docId); - } else { - this.docId$.next(null); - } - } - - public close() { - this.docId$.next(null); - } - - public onOpenChange(open: boolean) { - if (!open) this.docId$.next(null); - } -} diff --git a/packages/frontend/core/src/modules/doc-info/index.ts b/packages/frontend/core/src/modules/doc-info/index.ts index 01805077da..285155153a 100644 --- a/packages/frontend/core/src/modules/doc-info/index.ts +++ b/packages/frontend/core/src/modules/doc-info/index.ts @@ -5,18 +5,12 @@ import { } from '@toeverything/infra'; import { DocsSearchService } from '../docs-search'; -import { DocInfoModal } from './entities/modal'; import { DocDatabaseBacklinksService } from './services/doc-database-backlinks'; -import { DocInfoService } from './services/doc-info'; export { DocDatabaseBacklinkInfo } from './views/database-properties/doc-database-backlink-info'; -export { DocInfoService }; - export function configureDocInfoModule(framework: Framework) { framework .scope(WorkspaceScope) - .service(DocInfoService) - .service(DocDatabaseBacklinksService, [DocsService, DocsSearchService]) - .entity(DocInfoModal); + .service(DocDatabaseBacklinksService, [DocsService, DocsSearchService]); } diff --git a/packages/frontend/core/src/modules/doc-info/services/doc-info.ts b/packages/frontend/core/src/modules/doc-info/services/doc-info.ts deleted file mode 100644 index 1ff35f9b2a..0000000000 --- a/packages/frontend/core/src/modules/doc-info/services/doc-info.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Service } from '@toeverything/infra'; - -import { DocInfoModal } from '../entities/modal'; - -export class DocInfoService extends Service { - public readonly modal = this.framework.createEntity(DocInfoModal); -} diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx index 632dc47085..b289cebc89 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/collection/index.tsx @@ -5,11 +5,9 @@ import { MenuItem, toast, } from '@affine/component'; -import { - filterPage, - useEditCollection, -} from '@affine/core/components/page-list'; +import { filterPage } from '@affine/core/components/page-list'; import { CollectionService } from '@affine/core/modules/collection'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { ShareDocsListService } from '@affine/core/modules/share-doc'; import type { AffineDNDData } from '@affine/core/types/dnd'; @@ -59,10 +57,10 @@ export const ExplorerCollectionNode = ({ collectionId: string; } & GenericExplorerNode) => { const t = useI18n(); - const { globalContextService } = useServices({ + const { globalContextService, workspaceDialogService } = useServices({ GlobalContextService, + WorkspaceDialogService, }); - const { open: openEditCollectionModal } = useEditCollection(); const active = useLiveData(globalContextService.globalContext.collectionId.$) === collectionId; @@ -170,17 +168,10 @@ export const ExplorerCollectionNode = ({ if (!collection) { return; } - openEditCollectionModal(collection) - .then(collection => { - return collectionService.updateCollection( - collection.id, - () => collection - ); - }) - .catch(err => { - console.error(err); - }); - }, [collection, collectionService, openEditCollectionModal]); + workspaceDialogService.open('collection-editor', { + collectionId: collection.id, + }); + }, [collection, workspaceDialogService]); const collectionOperations = useExplorerCollectionNodeOperations( collectionId, diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx index 4df22ea9d1..08276dd4db 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx @@ -6,8 +6,8 @@ import { Tooltip, } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; -import { DocInfoService } from '@affine/core/modules/doc-info'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; @@ -187,15 +187,15 @@ export const ExplorerDocNode = ({ [canDrop] ); - const docInfoModal = useService(DocInfoService).modal; + const workspaceDialogService = useService(WorkspaceDialogService); const operations = useExplorerDocNodeOperations( docId, useMemo( () => ({ - openInfoModal: () => docInfoModal.open(docId), + openInfoModal: () => workspaceDialogService.open('doc-info', { docId }), openNodeCollapsed: () => setCollapsed(false), }), - [docId, docInfoModal] + [docId, workspaceDialogService] ) ); diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx index 7f71818c26..771f824e88 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/folder/index.tsx @@ -10,11 +10,7 @@ import { notify, } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; -import { - useSelectCollection, - useSelectDoc, - useSelectTag, -} from '@affine/core/components/page-list/selector'; +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { type FolderNode, @@ -186,14 +182,13 @@ const ExplorerFolderNodeFolder = ({ node: FolderNode; } & GenericExplorerNode) => { const t = useI18n(); - const { workspaceService, featureFlagService } = useServices({ - WorkspaceService, - CompatibleFavoriteItemsAdapter, - FeatureFlagService, - }); - const openDocsSelector = useSelectDoc(); - const openTagsSelector = useSelectTag(); - const openCollectionsSelector = useSelectCollection(); + const { workspaceService, featureFlagService, workspaceDialogService } = + useServices({ + WorkspaceService, + CompatibleFavoriteItemsAdapter, + FeatureFlagService, + WorkspaceDialogService, + }); const name = useLiveData(node.name$); const enableEmojiIcon = useLiveData( featureFlagService.flags.enable_emoji_folder_icon.$ @@ -575,12 +570,19 @@ const ExplorerFolderNodeFolder = ({ .filter(Boolean) as string[]; const selector = type === 'doc' - ? openDocsSelector + ? 'doc-selector' : type === 'collection' - ? openCollectionsSelector - : openTagsSelector; - selector(initialIds) - .then(selectedIds => { + ? 'collection-selector' + : 'tag-selector'; + workspaceDialogService.open( + selector, + { + init: initialIds, + }, + selectedIds => { + if (selectedIds === undefined) { + return; + } const newItemIds = difference(selectedIds, initialIds); const removedItemIds = difference(initialIds, selectedIds); const removedItems = children.filter( @@ -594,22 +596,14 @@ const ExplorerFolderNodeFolder = ({ removedItems.forEach(node => node.delete()); const updated = newItemIds.length + removedItems.length; updated && setCollapsed(false); - }) - .catch(err => { - console.error(`Unexpected error while selecting ${type}`, err); - }); + } + ); track.$.navigationPanel.organize.createOrganizeItem({ type: 'link', target: type, }); }, - [ - children, - node, - openCollectionsSelector, - openDocsSelector, - openTagsSelector, - ] + [children, node, workspaceDialogService] ); const folderOperations = useMemo(() => { diff --git a/packages/frontend/core/src/modules/explorer/views/sections/collections/index.css.ts b/packages/frontend/core/src/modules/explorer/views/sections/collections/index.css.ts new file mode 100644 index 0000000000..f9dd1bc597 --- /dev/null +++ b/packages/frontend/core/src/modules/explorer/views/sections/collections/index.css.ts @@ -0,0 +1,8 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + +export const createTips = style({ + color: cssVar('textSecondaryColor'), + fontSize: 12, + lineHeight: '20px', +}); diff --git a/packages/frontend/core/src/modules/explorer/views/sections/collections/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/collections/index.tsx index 81c77fa132..17de850dad 100644 --- a/packages/frontend/core/src/modules/explorer/views/sections/collections/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/sections/collections/index.tsx @@ -1,5 +1,4 @@ -import { IconButton } from '@affine/component'; -import { useEditCollectionName } from '@affine/core/components/page-list'; +import { IconButton, usePromptModal } from '@affine/component'; import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager'; import { CollectionService } from '@affine/core/modules/collection'; import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; @@ -15,6 +14,7 @@ import { ExplorerService } from '../../../services/explorer'; import { CollapsibleSection } from '../../layouts/collapsible-section'; import { ExplorerCollectionNode } from '../../nodes/collection'; import { RootEmpty } from './empty'; +import * as styles from './index.css'; export const ExplorerCollections = () => { const t = useI18n(); @@ -25,14 +25,26 @@ export const ExplorerCollections = () => { }); const explorerSection = explorerService.sections.collections; const collections = useLiveData(collectionService.collections$); - const { open: openCreateCollectionModel } = useEditCollectionName({ - title: t['com.affine.editCollection.createCollection'](), - showTips: true, - }); + const { openPromptModal } = usePromptModal(); const handleCreateCollection = useCallback(() => { - openCreateCollectionModel('') - .then(name => { + openPromptModal({ + title: t['com.affine.editCollection.saveCollection'](), + label: t['com.affine.editCollectionName.name'](), + inputOptions: { + placeholder: t['com.affine.editCollectionName.name.placeholder'](), + }, + children: ( +
+ {t['com.affine.editCollectionName.createTips']()} +
+ ), + confirmText: t['com.affine.editCollection.save'](), + cancelText: t['com.affine.editCollection.button.cancel'](), + confirmButtonOptions: { + variant: 'primary', + }, + onConfirm(name) { const id = nanoid(); collectionService.addCollection(createEmptyCollection(id, { name })); track.$.navigationPanel.organize.createOrganizeItem({ @@ -40,14 +52,13 @@ export const ExplorerCollections = () => { }); workbenchService.workbench.openCollection(id); explorerSection.setCollapsed(false); - }) - .catch(err => { - console.error(err); - }); + }, + }); }, [ collectionService, explorerSection, - openCreateCollectionModel, + openPromptModal, + t, workbenchService.workbench, ]); diff --git a/packages/frontend/core/src/modules/find-in-page/index.ts b/packages/frontend/core/src/modules/find-in-page/index.ts index 207dac4158..59f2d997f1 100644 --- a/packages/frontend/core/src/modules/find-in-page/index.ts +++ b/packages/frontend/core/src/modules/find-in-page/index.ts @@ -4,6 +4,8 @@ import { DesktopApiService } from '../desktop-api'; import { FindInPage } from './entities/find-in-page'; import { FindInPageService } from './services/find-in-page'; +export { FindInPageService } from './services/find-in-page'; + export function configureFindInPageModule(framework: Framework) { framework.service(FindInPageService).entity(FindInPage, [DesktopApiService]); } diff --git a/packages/frontend/core/src/modules/import-template/index.ts b/packages/frontend/core/src/modules/import-template/index.ts index be7ab4708e..a0d4c8e42d 100644 --- a/packages/frontend/core/src/modules/import-template/index.ts +++ b/packages/frontend/core/src/modules/import-template/index.ts @@ -3,17 +3,15 @@ import { type Framework, WorkspacesService } from '@toeverything/infra'; import { FetchService } from '../cloud'; import { ImportTemplateDialog } from './entities/dialog'; import { TemplateDownloader } from './entities/downloader'; -import { ImportTemplateDialogService } from './services/dialog'; import { TemplateDownloaderService } from './services/downloader'; import { ImportTemplateService } from './services/import'; import { TemplateDownloaderStore } from './store/downloader'; -export { ImportTemplateDialogService } from './services/dialog'; -export { ImportTemplateDialogProvider } from './views/dialog'; +export { TemplateDownloaderService } from './services/downloader'; +export { ImportTemplateService } from './services/import'; export function configureImportTemplateModule(framework: Framework) { framework - .service(ImportTemplateDialogService) .entity(ImportTemplateDialog) .service(TemplateDownloaderService) .entity(TemplateDownloader, [TemplateDownloaderStore]) diff --git a/packages/frontend/core/src/modules/import-template/services/dialog.ts b/packages/frontend/core/src/modules/import-template/services/dialog.ts deleted file mode 100644 index ea6960a4bf..0000000000 --- a/packages/frontend/core/src/modules/import-template/services/dialog.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Service } from '@toeverything/infra'; - -import { ImportTemplateDialog } from '../entities/dialog'; - -export class ImportTemplateDialogService extends Service { - dialog = this.framework.createEntity(ImportTemplateDialog); -} diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index 7e6486a987..51c9b9501e 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -4,7 +4,7 @@ import { configureInfraModules, type Framework } from '@toeverything/infra'; import { configureAppSidebarModule } from './app-sidebar'; import { configureCloudModule } from './cloud'; import { configureCollectionModule } from './collection'; -import { configureCreateWorkspaceModule } from './create-workspace'; +import { configureDialogModule } from './dialogs'; import { configureDocDisplayMetaModule } from './doc-display-meta'; import { configureDocInfoModule } from './doc-info'; import { configureDocLinksModule } from './doc-link'; @@ -17,6 +17,7 @@ import { configureI18nModule } from './i18n'; import { configureImportTemplateModule } from './import-template'; import { configureJournalModule } from './journal'; import { configureNavigationModule } from './navigation'; +import { configureOpenInApp } from './open-in-app'; import { configureOrganizeModule } from './organize'; import { configurePeekViewModule } from './peek-view'; import { configurePermissionsModule } from './permissions'; @@ -56,11 +57,12 @@ export function configureCommonModules(framework: Framework) { configureSystemFontFamilyModule(framework); configureEditorSettingModule(framework); configureImportTemplateModule(framework); - configureCreateWorkspaceModule(framework); configureUserspaceModule(framework); - configureDocInfoModule(framework); configureAppSidebarModule(framework); configureJournalModule(framework); configureUrlModule(framework); configureAppThemeModule(framework); + configureDialogModule(framework); + configureDocInfoModule(framework); + configureOpenInApp(framework); } diff --git a/packages/frontend/core/src/modules/open-in-app/index.ts b/packages/frontend/core/src/modules/open-in-app/index.ts index 7a1acc3781..f5312b4c45 100644 --- a/packages/frontend/core/src/modules/open-in-app/index.ts +++ b/packages/frontend/core/src/modules/open-in-app/index.ts @@ -6,7 +6,7 @@ import { import { OpenInAppService } from './services'; -export * from './services'; +export { OpenInAppService, OpenLinkMode } from './services'; export * from './utils'; export * from './views/open-in-app-guard'; diff --git a/packages/frontend/core/src/modules/open-in-app/views/open-in-app-page.tsx b/packages/frontend/core/src/modules/open-in-app/views/open-in-app-page.tsx index 1a972e1fed..6f86981e90 100644 --- a/packages/frontend/core/src/modules/open-in-app/views/open-in-app-page.tsx +++ b/packages/frontend/core/src/modules/open-in-app/views/open-in-app-page.tsx @@ -1,13 +1,13 @@ import { Button } from '@affine/component/ui/button'; -import { openSettingModalAtom } from '@affine/core/components/atoms'; import { resolveLinkToDoc } from '@affine/core/modules/navigation'; -import { appIconMap, appNames } from '@affine/core/utils'; +import { appIconMap, appNames } from '@affine/core/utils/channel'; import { Trans, useI18n } from '@affine/i18n'; import { Logo1Icon } from '@blocksuite/icons/rc'; -import { useSetAtom } from 'jotai'; +import { useService } from '@toeverything/infra'; import type { MouseEvent } from 'react'; import { useCallback } from 'react'; +import { GlobalDialogService } from '../../dialogs'; import { getOpenUrlInDesktopAppLink } from '../utils'; import * as styles from './open-in-app-page.css'; @@ -21,6 +21,7 @@ interface OpenAppProps { export const OpenInAppPage = ({ urlToOpen, openHereClicked }: OpenAppProps) => { // default to open the current page in desktop app urlToOpen ??= getOpenUrlInDesktopAppLink(window.location.href, true); + const globalDialogService = useService(GlobalDialogService); const t = useI18n(); const channel = BUILD_CONFIG.appBuildType; const openDownloadLink = useCallback(() => { @@ -45,17 +46,14 @@ export const OpenInAppPage = ({ urlToOpen, openHereClicked }: OpenAppProps) => { [maybeDocLink, openHereClicked] ); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); - const goToAppearanceSetting = useCallback( (e: MouseEvent) => { openHereClicked?.(e); - setSettingModalAtom({ - open: true, + globalDialogService.open('setting', { activeTab: 'appearance', }); }, - [openHereClicked, setSettingModalAtom] + [globalDialogService, openHereClicked] ); if (urlToOpen && lastOpened !== urlToOpen) { diff --git a/packages/frontend/core/src/modules/peek-view/view/modal-container.css.ts b/packages/frontend/core/src/modules/peek-view/view/modal-container.css.ts index bb98db96b8..09d336fbd9 100644 --- a/packages/frontend/core/src/modules/peek-view/view/modal-container.css.ts +++ b/packages/frontend/core/src/modules/peek-view/view/modal-container.css.ts @@ -30,21 +30,15 @@ export const modalContentContainer = style({ alignItems: 'center', justifyContent: 'center', borderRadius: 12, - '@media': { - // mobile: - 'screen and (width <= 640px)': { - selectors: { - [`${modalContentWrapper}:is([data-mode="max"], [data-mode="fit"]) &`]: { - height: '60%', - width: 'calc(100% - 32px)', - paddingRight: 0, - paddingBottom: 32, - alignSelf: 'flex-end', - }, - }, - }, - }, selectors: { + [`${modalContentWrapper}:is([data-mode="max"], [data-mode="fit"], [data-mobile]) &`]: + { + height: '60%', + width: 'calc(100% - 32px)', + paddingRight: 0, + paddingBottom: 32, + alignSelf: 'flex-end', + }, [`${modalContentWrapper}[data-mode="max"] &`]: { width: 'calc(100% - 64px)', height: 'calc(100% - 64px)', diff --git a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx index 22c720d3d1..89eea67b38 100644 --- a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx @@ -321,10 +321,12 @@ export const PeekViewModalContainer = forwardRef< data-mode={mode} data-peek-view-wrapper className={styles.modalContentWrapper} + data-mobile={BUILD_CONFIG.isMobileEdition ? '' : undefined} >
diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx index d08fd99202..01fbdfdc52 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx @@ -18,7 +18,7 @@ import { useMemo, } from 'react'; -import { DocInfoService } from '../../doc-info'; +import { WorkspaceDialogService } from '../../dialogs'; import { WorkbenchService } from '../../workbench'; import type { DocReferenceInfo } from '../entities/peek-view'; import { PeekViewService } from '../services/peek-view'; @@ -98,7 +98,7 @@ export const DocPeekViewControls = ({ const peekView = useService(PeekViewService).peekView; const workbench = useService(WorkbenchService).workbench; const t = useI18n(); - const docInfoService = useService(DocInfoService); + const workspaceDialogService = useService(WorkspaceDialogService); const controls = useMemo(() => { return [ { @@ -139,13 +139,11 @@ export const DocPeekViewControls = ({ nameKey: 'info', name: t['com.affine.peek-view-controls.open-info'](), onClick: () => { - docInfoService.modal.open( - typeof docRef === 'string' ? docRef : docRef.docId - ); + workspaceDialogService.open('doc-info', { docId: docRef.docId }); }, }, ].filter((opt): opt is ControlButtonProps => Boolean(opt)); - }, [t, peekView, workbench, docRef, docInfoService.modal]); + }, [t, peekView, workbench, docRef, workspaceDialogService]); return (
{controls.map(option => ( diff --git a/packages/frontend/core/src/modules/theme-editor/index.ts b/packages/frontend/core/src/modules/theme-editor/index.ts index 07fd082ca0..20ad54662e 100644 --- a/packages/frontend/core/src/modules/theme-editor/index.ts +++ b/packages/frontend/core/src/modules/theme-editor/index.ts @@ -2,8 +2,6 @@ import { type Framework, GlobalState } from '@toeverything/infra'; import { ThemeEditorService } from './services/theme-editor'; -export { CustomThemeModifier, useCustomTheme } from './views/custom-theme'; -export { ThemeEditor } from './views/theme-editor'; export { ThemeEditorService }; export function configureThemeEditorModule(framework: Framework) { diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx index 4207929568..6ff0011e0b 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx +++ b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx @@ -13,13 +13,13 @@ import { } from '@dnd-kit/sortable'; import { useService } from '@toeverything/infra'; import clsx from 'clsx'; -import type { HTMLAttributes, PropsWithChildren, RefObject } from 'react'; +import type { HTMLAttributes, RefObject } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import type { View } from '../../entities/view'; import { WorkbenchService } from '../../services/workbench'; -import { SplitViewPanel, SplitViewPanelContainer } from './panel'; +import { SplitViewPanel } from './panel'; import { ResizeHandle } from './resize-handle'; import * as styles from './split-view.css'; @@ -141,20 +141,3 @@ export const SplitView = ({
); }; - -export const SplitViewFallback = ({ - children, - className, -}: PropsWithChildren<{ className?: string }>) => { - const { appSettings } = useAppSettingHelper(); - - return ( -
- {/* todo: support multiple split views */} - {children} -
- ); -}; diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index 24ef0923c0..59836fcf5f 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -13,7 +13,7 @@ "ja": 95, "ko": 84, "pl": 0, - "pt-BR": 91, + "pt-BR": 92, "ru": 78, "sv-SE": 5, "ur": 3, diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 403d886afd..9c1fe9979f 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -42,7 +42,6 @@ "Export success": "Export success", "Export to HTML": "Export to HTML", "Export to Markdown": "Export to Markdown", - "Export to PDF": "Export to PDF", "Export to PNG": "Export to PNG", "FILE_ALREADY_EXISTS": "File already exists", "Favorite": "Favourite", @@ -1381,7 +1380,7 @@ "com.affine.m.selector.where-collection": "Collection", "com.affine.m.selector.confirm-default": "Apply", "com.affine.m.selector.title": "Manage {{type}}(s)", - "com.affine.m.selector.info-total": "{{total}} item(s) in this {{where}}", + "com.affine.m.selector.info-total": "{{total}} item(s)", "com.affine.m.selector.info-added": "Add {{count}} {{type}}(s)", "com.affine.m.selector.info-removed": "Remove {{count}} {{type}}(s)", "com.affine.m.selector.remove-warning.title": "Remove items", diff --git a/tests/affine-cloud/e2e/collaboration.spec.ts b/tests/affine-cloud/e2e/collaboration.spec.ts index 161ad5590b..2c32719648 100644 --- a/tests/affine-cloud/e2e/collaboration.spec.ts +++ b/tests/affine-cloud/e2e/collaboration.spec.ts @@ -119,10 +119,10 @@ test('can sync collections between different browser', async ({ ); await enableCloudWorkspace(page); await page.getByTestId('explorer-bar-add-collection-button').click(); - const title = page.getByTestId('input-collection-title'); + const title = page.getByTestId('prompt-modal-input'); await title.isVisible(); await title.fill('test collection'); - await page.getByTestId('save-collection').click(); + await page.getByTestId('prompt-modal-confirm').click(); { const context = await browser.newContext(); diff --git a/tests/affine-local/e2e/all-page.spec.ts b/tests/affine-local/e2e/all-page.spec.ts index 7c6f400162..c50ec39c89 100644 --- a/tests/affine-local/e2e/all-page.spec.ts +++ b/tests/affine-local/e2e/all-page.spec.ts @@ -396,9 +396,9 @@ test('create a collection and delete it', async ({ page }) => { // create a collection await page.getByTestId('all-collection-new-button').click(); - await expect(page.getByTestId('edit-collection-modal')).toBeVisible(); - await page.getByTestId('input-collection-title').fill('test collection'); - await page.getByTestId('save-collection').click(); + await expect(page.getByTestId('prompt-modal-input')).toBeVisible(); + await page.getByTestId('prompt-modal-input').fill('test collection'); + await page.getByTestId('prompt-modal-confirm').click(); // check the collection is created await clickSideBarAllPageButton(page); diff --git a/tests/affine-local/e2e/drag-page.spec.ts b/tests/affine-local/e2e/drag-page.spec.ts index 862079bfac..cff8b6a1a8 100644 --- a/tests/affine-local/e2e/drag-page.spec.ts +++ b/tests/affine-local/e2e/drag-page.spec.ts @@ -32,10 +32,10 @@ const dragToFavourites = async ( const createCollection = async (page: Page, name: string) => { await page.getByTestId('explorer-bar-add-collection-button').click(); - const input = page.getByTestId('input-collection-title'); + const input = page.getByTestId('prompt-modal-input'); await expect(input).toBeVisible(); await input.fill(name); - await page.getByTestId('save-collection').click(); + await page.getByTestId('prompt-modal-confirm').click(); const newCollectionId = getCurrentCollectionIdFromUrl(page); const collection = page.getByTestId(`explorer-collection-${newCollectionId}`); await expect(collection).toBeVisible(); diff --git a/tests/affine-local/e2e/local-first-collections-items.spec.ts b/tests/affine-local/e2e/local-first-collections-items.spec.ts index 46d7b7612c..9c47d86978 100644 --- a/tests/affine-local/e2e/local-first-collections-items.spec.ts +++ b/tests/affine-local/e2e/local-first-collections-items.spec.ts @@ -20,7 +20,7 @@ const removeOnboardingPages = async (page: Page) => { await page.getByTestId('page-list-header-selection-checkbox').click(); await page.getByTestId('list-toolbar-delete').click(); // confirm delete - await page.getByTestId('confirm-delete-page').click(); + await page.getByTestId('confirm-modal-confirm').click(); }; test.beforeEach(async ({ page }) => { @@ -57,10 +57,10 @@ const createAndPinCollection = async ( await page.getByTestId('save-as-collection').click({ delay: 200, }); - const title = page.getByTestId('input-collection-title'); + const title = page.getByTestId('prompt-modal-input'); await expect(title).toBeVisible(); await title.fill(options?.collectionName ?? 'test collection'); - await page.getByTestId('save-collection').click(); + await page.getByTestId('prompt-modal-confirm').click(); await page.waitForTimeout(100); }; @@ -159,10 +159,10 @@ test('add collection from sidebar', async ({ page }) => { ); await expect(nullCollection).toBeVisible(); await page.getByTestId('explorer-bar-add-collection-button').click(); - const title = page.getByTestId('input-collection-title'); + const title = page.getByTestId('prompt-modal-input'); await expect(title).toBeVisible(); await title.fill('test collection'); - await page.getByTestId('save-collection').click(); + await page.getByTestId('prompt-modal-confirm').click(); await page.waitForTimeout(100); const collections = page.getByTestId('explorer-collections'); const items = collections.locator('[data-testid^="explorer-collection-"]'); diff --git a/tests/affine-local/e2e/local-first-favorite-page.spec.ts b/tests/affine-local/e2e/local-first-favorite-page.spec.ts index 4d8b06cf3e..0b0fdbe451 100644 --- a/tests/affine-local/e2e/local-first-favorite-page.spec.ts +++ b/tests/affine-local/e2e/local-first-favorite-page.spec.ts @@ -7,7 +7,6 @@ import { getPageByTitle, waitForEditorLoad, } from '@affine-test/kit/utils/page-logic'; -import { waitForLogMessage } from '@affine-test/kit/utils/utils'; import { expect } from '@playwright/test'; test('New a page and open it, then favorite it', async ({ @@ -62,23 +61,6 @@ test('Export to html, markdown and png', async ({ page }) => { // } }); -test.skip('Export to pdf', async ({ page }) => { - const CheckedMessage = '[test] beforeprint event emitted'; - page.addInitScript(() => { - window.addEventListener('beforeprint', () => { - console.log(CheckedMessage); - }); - }); - await openHomePage(page); - await waitForEditorLoad(page); - { - await clickPageMoreActions(page); - await page.getByTestId('export-menu').click(); - await page.getByTestId('export-to-pdf').click(); - expect(waitForLogMessage(page, CheckedMessage)).toBeTruthy(); - } -}); - test('Cancel favorite', async ({ page, workspace }) => { await openHomePage(page); await waitForEditorLoad(page); diff --git a/tests/affine-local/e2e/quick-search.spec.ts b/tests/affine-local/e2e/quick-search.spec.ts index 2b9e144848..799e1d43bd 100644 --- a/tests/affine-local/e2e/quick-search.spec.ts +++ b/tests/affine-local/e2e/quick-search.spec.ts @@ -358,13 +358,12 @@ test('can use cmdk to delete page and restore it', async ({ page }) => { await getBlockSuiteEditorTitle(page).fill('this is a new page to delete'); await openQuickSearchByShortcut(page); await keyboardDownAndSelect(page, 'Move to trash'); - await page.getByTestId('confirm-delete-page').click(); + await page.getByTestId('confirm-modal-confirm').click(); const restoreButton = page.getByTestId('page-restore-button'); await expect(restoreButton).toBeVisible(); await page.waitForTimeout(100); await openQuickSearchByShortcut(page); expect(await commandsIsVisible(page, 'Move to trash')).toBe(false); - expect(await commandsIsVisible(page, 'Export to PDF')).toBe(false); expect(await commandsIsVisible(page, 'Restore from trash')).toBe(true); await keyboardDownAndSelect(page, 'Restore from trash'); await expect(restoreButton).not.toBeVisible(); diff --git a/tests/kit/utils/properties.ts b/tests/kit/utils/properties.ts index 5742f3208f..33cc058432 100644 --- a/tests/kit/utils/properties.ts +++ b/tests/kit/utils/properties.ts @@ -152,7 +152,7 @@ export const openWorkspaceProperties = async (page: Page) => { await page .locator('[data-testid="workspace-list-item"] .setting-name') .click(); - await page.getByTestId('workspace-list-item-properties').click(); + await page.getByTestId('workspace-list-item-workspace:properties').click(); }; export const selectVisibilitySelector = async (