diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index c9e9efd997..744746c480 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -28,8 +28,6 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.4.0", "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", "@blocksuite/icons": "^2.2.17", - "@emoji-mart/data": "^1.2.1", - "@emoji-mart/react": "^1.1.1", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@radix-ui/react-avatar": "^1.1.2", diff --git a/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.css.ts b/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.css.ts index 01431acbca..89aa208ad0 100644 --- a/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.css.ts +++ b/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.css.ts @@ -15,6 +15,7 @@ export const contentRoot = style({ export const iconPicker = style({ padding: 0, + lineHeight: 1, }); globalStyle(`${iconPicker} span:has(svg)`, { lineHeight: 0, diff --git a/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.stories.tsx b/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.stories.tsx index 56f2f696f4..c63a8bda65 100644 --- a/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.stories.tsx +++ b/packages/frontend/component/src/ui/icon-name-editor/icon-name-editor.stories.tsx @@ -2,12 +2,12 @@ import type { Meta, StoryFn } from '@storybook/react'; import { useCallback, useState } from 'react'; import { Button } from '../button'; +import { type IconData, IconType } from '../icon-picker'; import { ResizePanel } from '../resize-panel/resize-panel'; import { IconAndNameEditorMenu, type IconAndNameEditorMenuProps, IconEditor, - type IconType, } from './icon-name-editor'; export default { @@ -16,10 +16,13 @@ export default { } satisfies Meta; export const Basic: StoryFn = () => { - const [icon, setIcon] = useState('👋'); + const [icon, setIcon] = useState({ + type: IconType.Emoji, + unicode: '👋', + }); const [name, setName] = useState('Hello'); - const handleIconChange = useCallback((_?: IconType, icon?: string) => { + const handleIconChange = useCallback((icon?: IconData) => { setIcon(icon); }, []); const handleNameChange = useCallback((name: string) => { @@ -28,7 +31,7 @@ export const Basic: StoryFn = () => { return (
-

Icon: {icon}

+

Icon: {JSON.stringify(icon)}

Name: {name}

= () => { }} > void; + onIconChange?: (data?: IconData) => void; triggerClassName?: string; } @@ -38,25 +34,7 @@ export interface IconAndNameEditorMenuProps skipIfNotChanged?: boolean; } -export const IconRenderer = ({ - iconType, - icon, - fallback, -}: { - iconType: IconType; - icon: string; - fallback?: ReactNode; -}) => { - switch (iconType) { - case 'emoji': - return
{icon ?? fallback}
; - default: - return
{fallback}
; - } -}; - export const IconEditor = ({ - iconType, icon, closeAfterSelect, iconPlaceholder, @@ -71,16 +49,17 @@ export const IconEditor = ({ triggerVariant?: ButtonProps['variant']; }) => { const [isPickerOpen, setIsPickerOpen] = useState(false); - const { resolvedTheme } = useTheme(); - const handleEmojiClick = useCallback( - (emoji: any) => { - onIconChange?.('emoji', emoji.native); + + const handleSelect = useCallback( + (data?: IconData) => { + onIconChange?.(data); if (closeAfterSelect) { setIsPickerOpen(false); } }, [closeAfterSelect, onIconChange] ); + return ( e.stopPropagation()}> - +
} > ); @@ -160,7 +127,6 @@ export const IconAndNameEditorMenu = ({ open, onOpenChange, width = 300, - iconType: initialIconType, icon: initialIcon, name: initialName, onIconChange, @@ -169,15 +135,15 @@ export const IconAndNameEditorMenu = ({ iconPlaceholder, skipIfNotChanged = true, inputTestId, + closeAfterSelect, ...menuProps }: IconAndNameEditorMenuProps) => { - const [iconType, setIconType] = useState(initialIconType); const [icon, setIcon] = useState(initialIcon); const [name, setName] = useState(initialName); const commit = useCallback(() => { - if (iconType !== initialIconType || icon !== initialIcon) { - onIconChange?.(iconType, icon); + if (icon !== initialIcon) { + onIconChange?.(icon); } if (skipIfNotChanged) { if (name !== initialName) onNameChange?.(name); @@ -186,9 +152,7 @@ export const IconAndNameEditorMenu = ({ } }, [ icon, - iconType, initialIcon, - initialIconType, initialName, name, onIconChange, @@ -196,13 +160,11 @@ export const IconAndNameEditorMenu = ({ skipIfNotChanged, ]); const abort = useCallback(() => { - setIconType(initialIconType); setIcon(initialIcon); setName(initialName); - }, [initialIcon, initialIconType, initialName]); - const handleIconChange = useCallback((type?: IconType, icon?: string) => { - setIconType(type); - setIcon(icon); + }, [initialIcon, initialName]); + const handleIconChange = useCallback((data?: IconData) => { + setIcon(data); }, []); const handleNameChange = useCallback((name: string) => { setName(name); @@ -210,13 +172,12 @@ export const IconAndNameEditorMenu = ({ const handleMenuOpenChange = useCallback( (open: boolean) => { if (open) { - setIconType(initialIconType); setIcon(initialIcon); setName(initialName); } onOpenChange?.(open); }, - [initialIcon, initialIconType, initialName, onOpenChange] + [initialIcon, initialName, onOpenChange] ); return ( @@ -243,10 +204,10 @@ export const IconAndNameEditorMenu = ({ {...menuProps} items={ = [ { value: 'Emoji', className: styles.headerNavItem }, @@ -16,17 +17,11 @@ const panels: Array = [ export const IconPicker = ({ className, style, -}: HTMLAttributes & { - onSelect?: ( - type: 'emoji' | 'affine-icon', - data: { icon?: string; color?: string } - ) => void; + onSelect, +}: Omit, 'onSelect'> & { + onSelect?: (data?: IconData) => void; }) => { - const [activePanel, setActivePanel] = useState('Icons'); - - // const ActivePanel = panels.find( - // panel => panel.value === activePanel - // )?.component; + const [activePanel, setActivePanel] = useState('Emoji'); return (
@@ -53,7 +48,7 @@ export const IconPicker = ({ @@ -61,9 +56,17 @@ export const IconPicker = ({
{activePanel === 'Emoji' ? ( - + { + onSelect?.({ type: IconType.Emoji, unicode: emoji }); + }} + /> ) : activePanel === 'Icons' ? ( - + { + onSelect?.({ type: IconType.AffineIcon, name: icon, color }); + }} + /> ) : null}
diff --git a/packages/frontend/component/src/ui/icon-picker/index.ts b/packages/frontend/component/src/ui/icon-picker/index.ts index 1fd5d61e59..d4da1beb36 100644 --- a/packages/frontend/component/src/ui/icon-picker/index.ts +++ b/packages/frontend/component/src/ui/icon-picker/index.ts @@ -1 +1,3 @@ export * from './icon-picker'; +export * from './renderer'; +export * from './type'; diff --git a/packages/frontend/component/src/ui/icon-picker/renderer.tsx b/packages/frontend/component/src/ui/icon-picker/renderer.tsx index f8a93cc23d..3410798565 100644 --- a/packages/frontend/component/src/ui/icon-picker/renderer.tsx +++ b/packages/frontend/component/src/ui/icon-picker/renderer.tsx @@ -1,18 +1,29 @@ +import type { ReactNode } from 'react'; + import { AffineIconRenderer } from './renderer/affine-icon'; +import { type IconData, IconType } from './type'; export const IconRenderer = ({ - iconType, - icon, + data, + fallback, }: { - iconType: 'emoji' | 'affine-icon'; - icon: string; + data?: IconData; + fallback?: ReactNode; }) => { - if (iconType === 'emoji') { - return icon; - } - if (iconType === 'affine-icon') { - return ; + if (!data) { + return fallback ?? null; } - return null; + if (data.type === IconType.Emoji && data.unicode) { + return data.unicode; + } + if (data.type === IconType.AffineIcon && data.name) { + return ; + } + if (data.type === IconType.Blob) { + // Not supported yet + return null; + } + + return fallback ?? null; }; diff --git a/packages/frontend/component/src/ui/icon-picker/type.ts b/packages/frontend/component/src/ui/icon-picker/type.ts new file mode 100644 index 0000000000..ba089983a7 --- /dev/null +++ b/packages/frontend/component/src/ui/icon-picker/type.ts @@ -0,0 +1,20 @@ +export enum IconType { + Emoji = 'emoji', + AffineIcon = 'affine-icon', + Blob = 'blob', +} + +export type IconData = + | { + type: IconType.Emoji; + unicode: string; + } + | { + type: IconType.AffineIcon; + name: string; + color: string; + } + | { + type: IconType.Blob; + blob: Blob; + }; diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.css.ts b/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.css.ts index 3edfb9695a..6711032de6 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.css.ts +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.css.ts @@ -6,7 +6,7 @@ export const docIconPickerTrigger = style({ height: 64, padding: 2, selectors: { - '&[data-icon-type="emoji"]': { + '&[data-icon-type="emoji"], &[data-icon-type="affine-icon"]': { fontSize: 60, lineHeight: 1, }, diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.tsx index 97da80543f..4ced934af2 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/doc-icon-picker.tsx @@ -40,12 +40,15 @@ export const DocIconPicker = ({ const icon = useLiveData(explorerIconService.icon$('doc', docId)); - const isPlaceholder = !icon?.type || !icon?.icon; + const isPlaceholder = !icon?.icon; if (readonly) { return isPlaceholder ? null : ( -
- +
+
); } @@ -53,14 +56,12 @@ export const DocIconPicker = ({ return ( { + onIconChange={data => { explorerIconService.setIcon({ where: 'doc', id: docId, - type, - icon, + icon: data, }); }} closeAfterSelect={true} diff --git a/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx b/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx index c3ee0adb57..299bfcc828 100644 --- a/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx +++ b/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx @@ -6,6 +6,8 @@ import { type DropTargetTreeInstruction, IconAndNameEditorMenu, IconButton, + type IconData, + IconRenderer, Menu, MenuItem, useDraggable, @@ -13,7 +15,6 @@ import { } from '@affine/component'; import { Guard } from '@affine/core/components/guard'; import { AppSidebarService } from '@affine/core/modules/app-sidebar'; -import type { ExplorerIconType } from '@affine/core/modules/db/schema/schema'; import { ExplorerIconService } from '@affine/core/modules/explorer-icon/services/explorer-icon'; import type { ExplorerType } from '@affine/core/modules/explorer-icon/store/explorer-icon'; import type { DocPermissionActions } from '@affine/core/modules/permissions'; @@ -142,13 +143,12 @@ export const NavigationPanelTreeNodeRenameModal = ({ ); const onIconChange = useCallback( - (type?: ExplorerIconType, icon?: string) => { + (data?: IconData) => { if (!explorerIconConfig) return; explorerIconService.setIcon({ where: explorerIconConfig.where, id: explorerIconConfig.id, - type, - icon, + icon: data, }); }, [explorerIconConfig, explorerIconService] @@ -161,8 +161,7 @@ export const NavigationPanelTreeNodeRenameModal = ({ onIconChange={onIconChange} onNameChange={handleRename} name={rawName ?? ''} - iconType={explorerIcon?.type ?? 'emoji'} - icon={explorerIcon?.icon ?? ''} + icon={explorerIcon?.icon} width={sidebarWidth - 16} contentOptions={{ sideOffset: 36, @@ -461,10 +460,7 @@ export const NavigationPanelTreeNode = ({ />
- {/* Only emoji icon is supported for now */} - {explorerIcon && explorerIcon.type === 'emoji' - ? explorerIcon.icon - : fallbackIcon} +
diff --git a/packages/frontend/core/src/modules/db/schema/schema.ts b/packages/frontend/core/src/modules/db/schema/schema.ts index 44fb5246ad..eff152b2cb 100644 --- a/packages/frontend/core/src/modules/db/schema/schema.ts +++ b/packages/frontend/core/src/modules/db/schema/schema.ts @@ -1,3 +1,4 @@ +import type { IconData } from '@affine/component'; import { type DBSchemaBuilder, f, @@ -51,8 +52,7 @@ export const AFFiNE_WORKSPACE_DB_SCHEMA = { * ${doc|collection|folder|tag}:${id} */ id: f.string().primaryKey(), - type: f.enum('emoji', 'affine-icon', 'blob'), - icon: f.string(), + icon: f.json(), }, } as const satisfies DBSchemaBuilder; export type AFFiNEWorkspaceDbSchema = typeof AFFiNE_WORKSPACE_DB_SCHEMA; @@ -61,10 +61,6 @@ export type DocProperties = ORMEntity; export type DocCustomPropertyInfo = ORMEntity< AFFiNEWorkspaceDbSchema['docCustomPropertyInfo'] >; -export type ExplorerIcon = ORMEntity; -export type ExplorerIconType = ORMEntity< - AFFiNEWorkspaceDbSchema['explorerIcon'] ->['type']; export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = { favorite: { diff --git a/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts index 8a70874a63..f0fca2a56a 100644 --- a/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts +++ b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts @@ -29,6 +29,7 @@ import type { DocRecord, DocsService } from '../../doc'; import type { ExplorerIconService } from '../../explorer-icon/services/explorer-icon'; import type { I18nService } from '../../i18n'; import type { JournalService } from '../../journal'; +import { getDocIconComponent } from './icon'; type IconType = 'rc' | 'lit'; interface DocDisplayIconOptions { @@ -149,8 +150,10 @@ export class DocDisplayMetaService extends Service { if (enableEmojiIcon) { // const { emoji } = extractEmojiIcon(title); // if (emoji) return () => emoji; - const icon = get(this.explorerIconService.icon$('doc', docId)); - if (icon && icon.type === 'emoji') return () => icon.icon; + const icon = get(this.explorerIconService.icon$('doc', docId))?.icon; + if (icon) { + return getDocIconComponent(icon); + } } // title alias diff --git a/packages/frontend/core/src/modules/doc-display-meta/services/icon.tsx b/packages/frontend/core/src/modules/doc-display-meta/services/icon.tsx new file mode 100644 index 0000000000..f65367056e --- /dev/null +++ b/packages/frontend/core/src/modules/doc-display-meta/services/icon.tsx @@ -0,0 +1,7 @@ +import { type IconData, IconRenderer } from '@affine/component'; + +export const getDocIconComponent = (icon: IconData) => { + const Icon = () => ; + Icon.displayName = 'DocIcon'; + return Icon; +}; diff --git a/packages/frontend/core/src/modules/explorer-icon/store/explorer-icon.ts b/packages/frontend/core/src/modules/explorer-icon/store/explorer-icon.ts index fd5619ef59..db46883924 100644 --- a/packages/frontend/core/src/modules/explorer-icon/store/explorer-icon.ts +++ b/packages/frontend/core/src/modules/explorer-icon/store/explorer-icon.ts @@ -1,7 +1,7 @@ +import type { IconData } from '@affine/component'; import { Store } from '@toeverything/infra'; import type { WorkspaceDBService } from '../../db'; -import type { ExplorerIconType } from '../../db/schema/schema'; export type ExplorerType = 'doc' | 'collection' | 'folder' | 'tag'; @@ -18,21 +18,15 @@ export class ExplorerIconStore extends Store { return this.dbService.db.explorerIcon.get(`${type}:${id}`); } - setIcon(options: { - where: ExplorerType; - id: string; - type?: ExplorerIconType; - icon?: string; - }) { - const { where, id, type, icon } = options; + setIcon(options: { where: ExplorerType; id: string; icon?: IconData }) { + const { where, id, icon } = options; // remove icon - if (!type || !icon) { + if (!icon) { return this.dbService.db.explorerIcon.delete(`${where}:${id}`); } // upsert icon return this.dbService.db.explorerIcon.create({ id: `${where}:${id}`, - type, icon, }); } diff --git a/yarn.lock b/yarn.lock index dbeb6a867a..e57230b70c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -314,8 +314,6 @@ __metadata: "@blocksuite/affine": "workspace:*" "@blocksuite/icons": "npm:^2.2.17" "@chromatic-com/storybook": "npm:^4.0.0" - "@emoji-mart/data": "npm:^1.2.1" - "@emoji-mart/react": "npm:^1.1.1" "@emotion/react": "npm:^11.14.0" "@emotion/styled": "npm:^11.14.0" "@radix-ui/react-avatar": "npm:^1.1.2" @@ -5532,16 +5530,6 @@ __metadata: languageName: node linkType: hard -"@emoji-mart/react@npm:^1.1.1": - version: 1.1.1 - resolution: "@emoji-mart/react@npm:1.1.1" - peerDependencies: - emoji-mart: ^5.2 - react: ^16.8 || ^17 || ^18 - checksum: 10/def4dddaa01ce88c396510d84d1878b881fe0f9c484a836a50e3db784a91ab98edf94816cff503b4d1cab7b00400a01c87e32d19dee2d950f64ef9243f79101e - languageName: node - linkType: hard - "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5"