refactor(core): desktop project struct (#8334)

This commit is contained in:
EYHN
2024-11-05 11:00:33 +08:00
committed by GitHub
parent 89d09fd5e9
commit 902635e60f
343 changed files with 3846 additions and 3508 deletions

View File

@@ -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,

View File

@@ -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);

View File

@@ -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(

View File

@@ -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(
() => ({

View File

@@ -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',
});

View File

@@ -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: (
<div className={styles.createTips}>
{t['com.affine.editCollectionName.createTips']()}
</div>
),
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 (
<CollapsibleSection
@@ -56,13 +77,7 @@ export const ExplorerCollections = () => {
<AddItemPlaceholder
data-testid="explorer-bar-add-collection-button"
label={t['com.affine.rootAppSidebar.collection.new']()}
onClick={() => setShowCreateCollectionModal(true)}
/>
<CollectionRenameDialog
title={t['com.affine.m.explorer.collection.new-dialog-title']()}
open={showCreateCollectionModal}
onOpenChange={setShowCreateCollectionModal}
onConfirm={handleCreateCollection}
onClick={() => handleCreateCollection()}
/>
</ExplorerTreeRoot>
</CollapsibleSection>

View File

@@ -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';

View File

@@ -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<string[]>,
Pick<GenericSelectorProps, 'where' | 'type'> {}
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: <ViewLayersIcon />,
label: collection.name,
}));
}, [collections]);
return (
<GenericSelector
onBack={onCancel}
onConfirm={onConfirm}
initial={init}
data={list}
{...otherProps}
/>
);
};

View File

@@ -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<typeof useSelectDialog>[2] = {
modalProps: {
fullScreen: true,
width: undefined,
height: undefined,
contentOptions: {
style: {
background: cssVarV2('layer/background/secondary'),
padding: 0,
},
},
},
};
export const useSelectDoc = () => {
return useSelectDialog<string[], DocsSelectorProps>(
DocsSelector,
'select-doc-dialog',
options
);
};
export const useSelectCollection = () => {
return useSelectDialog<string[], CollectionsSelectorProps>(
CollectionsSelector,
'select-collection-dialog',
options
);
};
export const useSelectTag = () => {
return useSelectDialog<string[], TagsSelectorProps>(
TagsSelector,
'select-tag-dialog',
options
);
};

View File

@@ -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<GLOBAL_DIALOG_SCHEMA[key]>
>;
};
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<WORKSPACE_DIALOG_SCHEMA[key]>
>;
};
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 (
<DialogComponent
key={dialog.id}
{...(dialog.props as any)}
close={(result?: unknown) => {
globalDialogService.close(dialog.id, result);
}}
/>
);
})}
<AuthModal />
</>
);
};
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 (
<DialogComponent
key={dialog.id}
{...(dialog.props as any)}
close={(result?: unknown) => {
workspaceDialogService.close(dialog.id, result);
}}
/>
);
})}
</>
);
};

View File

@@ -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<WORKSPACE_DIALOG_SCHEMA['collection-selector']>) => {
const t = useI18n();
const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections$);
const list = useMemo(() => {
return collections.map(collection => ({
id: collection.id,
icon: <ViewLayersIcon />,
label: collection.name,
}));
}, [collections]);
return (
<Modal
open
onOpenChange={() => close()}
withoutCloseButton
fullScreen
contentOptions={{
style: {
background: cssVarV2('layer/background/secondary'),
padding: 0,
},
}}
>
<GenericSelector
onBack={close}
onConfirm={close}
onBeforeConfirm={onBeforeConfirm}
initial={init}
data={list}
typeName={t[`com.affine.m.selector.type-collection`]()}
/>
</Modal>
);
};

View File

@@ -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<string[]>,
Pick<GenericSelectorProps, 'where' | 'type'> {}
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<WORKSPACE_DIALOG_SCHEMA['doc-selector']>) => {
const t = useI18n();
const docsService = useService(DocsService);
const docRecords = useLiveData(docsService.list.docs$);
@@ -47,12 +48,26 @@ export const DocsSelector = ({
}, [docRecords]);
return (
<GenericSelector
onBack={onCancel}
onConfirm={onConfirm}
initial={init}
data={list}
{...otherProps}
/>
<Modal
open
onOpenChange={() => close()}
withoutCloseButton
fullScreen
contentOptions={{
style: {
background: cssVarV2('layer/background/secondary'),
padding: 0,
},
}}
>
<GenericSelector
onBack={close}
onConfirm={close}
onBeforeConfirm={onBeforeConfirm}
initial={init}
data={list}
typeName={t[`com.affine.m.selector.type-doc`]()}
/>
</Modal>
);
};

View File

@@ -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<HTMLUListElement>(null);
const quickScrollRef = useRef<HTMLDivElement>(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<boolean>(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 = ({
<div className={styles.totalInfo}>
{t['com.affine.m.selector.info-total']({
total: initial.length + '',
where: whereText,
})}
</div>
</div>

View File

@@ -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<string[]>,
Pick<GenericSelectorProps, 'where' | 'type'> {}
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<WORKSPACE_DIALOG_SCHEMA['tag-selector']>) => {
const t = useI18n();
const tagService = useService(TagService);
const tags = useLiveData(tagService.tagList.tags$);
@@ -53,12 +55,26 @@ export const TagsSelector = ({
}, [tags]);
return (
<GenericSelector
onBack={onCancel}
onConfirm={onConfirm}
initial={init}
data={list}
{...otherProps}
/>
<Modal
open
onOpenChange={() => close()}
withoutCloseButton
fullScreen
contentOptions={{
style: {
background: cssVarV2('layer/background/secondary'),
padding: 0,
},
}}
>
<GenericSelector
onBack={close}
onConfirm={close}
onBeforeConfirm={onBeforeConfirm}
initial={init}
data={list}
typeName={t[`com.affine.m.selector.type-tag`]()}
/>
</Modal>
);
};

View File

@@ -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,

View File

@@ -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';

View File

@@ -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 (
<Modal
fullScreen
animation="slideBottom"
open={open}
onOpenChange={onOpenChange}
contentOptions={{
style: {
padding: 0,
overflowY: 'auto',
backgroundColor: cssVarV2('layer/background/secondary'),
},
}}
withoutCloseButton
>
<MobileSetting onClose={closeModal} />
</Modal>
);
};
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<GLOBAL_DIALOG_SCHEMA['setting']>) => {
return (
<Modal
fullScreen
animation="slideBottom"
open
onOpenChange={() => close()}
contentOptions={{
style: {
padding: 0,
overflowY: 'auto',
backgroundColor: cssVarV2('layer/background/secondary'),
},
}}
withoutCloseButton
>
<MobileSetting onClose={close} />
</Modal>
);
};

View File

@@ -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';

View File

@@ -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 <PageNotFound noPermission />;
}
if (!meta) {
return <AppFallback key="workspaceLoading" />;
return;
}
return (
<WorkspaceLayout meta={meta}>

View File

@@ -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 (
<FrameworkScope scope={workspace.scope}>
<AppFallback />
</FrameworkScope>
);
return <AppFallback />;
}
return (
<FrameworkScope scope={workspace.scope}>
<AffineErrorBoundary height="100dvh">
<SWRConfigProvider>
<MobileCurrentWorkspaceModals />
<WorkspaceLayoutProviders>{children}</WorkspaceLayoutProviders>
<WorkspaceDialogs />
{/* ---- some side-effect components ---- */}
<PeekViewManagerModal />
{workspace?.flavour === WorkspaceFlavour.LOCAL && <LocalQuotaModal />}
{workspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && (
<CloudQuotaModal />
)}
<AiLoginRequiredModal />
<WorkspaceSideEffects />
{children}
</SWRConfigProvider>
</AffineErrorBoundary>
</FrameworkScope>

View File

@@ -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 (
<>
<StarAFFiNEModal />
<IssueFeedbackModal />
{currentWorkspace ? <MobileSettingModal /> : null}
{currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && (
<>
<LocalQuotaModal />
<HistoryTipsModal />
</>
)}
{currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && (
<CloudQuotaModal />
)}
<AiLoginRequiredModal />
<PeekViewManagerModal />
<MoveToTrash.ConfirmModal
open={trashConfirmOpen}
onConfirm={handleOnConfirm}
onOpenChange={onTrashConfirmOpenChange}
titles={deletePageTitles}
/>
{currentWorkspace ? <InfoModal /> : null}
</>
);
}
// I don't like the name, but let's keep it for now
export const AllWorkspaceModals = () => {
return (
<>
<NotificationCenter />
<CreateWorkspaceDialogProvider />
<MobileSignInModal />
<SignOutConfirmModal />
</>
);
};

View File

@@ -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 && (
<NavigateContext.Provider value={navigate}>
<AllWorkspaceModals />
<GlobalDialogs />
<NotificationCenter />
<MobileSignInModal />
<Outlet />
</NavigateContext.Provider>
)

View File

@@ -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 = () => {
<div className={styles.settingWrapper}>
<IconButton
onClick={() => {
openSetting({ open: true, activeTab: 'appearance' });
globalDialogService.open('setting', {
activeTab: 'appearance',
});
}}
size="24"
style={{ padding: 10 }}

View File

@@ -1,4 +1,3 @@
export * from './all-docs';
export * from './home-header';
export * from './recent-docs';
export * from './settings';