mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
feat(core): replace emoji-mart with affine icon picker (#13644)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Unified icon picker with consistent rendering across the app. - Picker can auto-close after selection. - “Remove” now clears the icon selection. - Refactor - Icon handling consolidated across editors, navigation, and document titles for consistent behavior. - Picker now opens on the Emoji panel by default. - Style - Adjusted line-height and selectors for icon picker visuals. - Chores - Removed unused emoji-mart dependencies. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 : (
|
||||
<div className={styles.docIconPickerTrigger} data-icon-type={icon?.type}>
|
||||
<IconRenderer iconType={icon.type} icon={icon.icon} />
|
||||
<div
|
||||
className={styles.docIconPickerTrigger}
|
||||
data-icon-type={icon?.icon?.type}
|
||||
>
|
||||
<IconRenderer data={icon.icon} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -53,14 +56,12 @@ export const DocIconPicker = ({
|
||||
return (
|
||||
<TitleContainer isPlaceholder={isPlaceholder}>
|
||||
<IconEditor
|
||||
iconType={icon?.type}
|
||||
icon={icon?.icon}
|
||||
onIconChange={(type, icon) => {
|
||||
onIconChange={data => {
|
||||
explorerIconService.setIcon({
|
||||
where: 'doc',
|
||||
id: docId,
|
||||
type,
|
||||
icon,
|
||||
icon: data,
|
||||
});
|
||||
}}
|
||||
closeAfterSelect={true}
|
||||
|
||||
@@ -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 = ({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.iconContainer}>
|
||||
{/* Only emoji icon is supported for now */}
|
||||
{explorerIcon && explorerIcon.type === 'emoji'
|
||||
? explorerIcon.icon
|
||||
: fallbackIcon}
|
||||
<IconRenderer data={explorerIcon?.icon} fallback={fallbackIcon} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<IconData>(),
|
||||
},
|
||||
} as const satisfies DBSchemaBuilder;
|
||||
export type AFFiNEWorkspaceDbSchema = typeof AFFiNE_WORKSPACE_DB_SCHEMA;
|
||||
@@ -61,10 +61,6 @@ export type DocProperties = ORMEntity<AFFiNEWorkspaceDbSchema['docProperties']>;
|
||||
export type DocCustomPropertyInfo = ORMEntity<
|
||||
AFFiNEWorkspaceDbSchema['docCustomPropertyInfo']
|
||||
>;
|
||||
export type ExplorerIcon = ORMEntity<AFFiNEWorkspaceDbSchema['explorerIcon']>;
|
||||
export type ExplorerIconType = ORMEntity<
|
||||
AFFiNEWorkspaceDbSchema['explorerIcon']
|
||||
>['type'];
|
||||
|
||||
export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = {
|
||||
favorite: {
|
||||
|
||||
@@ -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<T extends IconType> {
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { type IconData, IconRenderer } from '@affine/component';
|
||||
|
||||
export const getDocIconComponent = (icon: IconData) => {
|
||||
const Icon = () => <IconRenderer data={icon} />;
|
||||
Icon.displayName = 'DocIcon';
|
||||
return Icon;
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user