From 1d8bb8760b1f2817d5316275c91fb3647ab7609d Mon Sep 17 00:00:00 2001 From: Hakan Shehu Date: Sun, 19 Jan 2025 15:41:39 +0100 Subject: [PATCH] Use sqlite for emojis and icons --- .gitignore | 10 +- apps/desktop/package.json | 4 +- .../src/main/databases/emojis/index.ts | 1 + .../src/main/databases/emojis/schema.ts | 52 + .../desktop/src/main/databases/icons/index.ts | 1 + .../src/main/databases/icons/schema.ts | 49 + apps/desktop/src/main/lib/assets.ts | 34 +- apps/desktop/src/main/lib/mappers.ts | 26 + apps/desktop/src/main/lib/protocols.ts | 47 +- .../queries/emojis/emoji-category-list.ts | 26 + .../src/main/queries/emojis/emoji-get.ts | 33 + .../src/main/queries/emojis/emoji-list.ts | 34 + .../src/main/queries/emojis/emoji-search.ts | 33 + .../src/main/queries/emojis/emojis-get.ts | 24 - .../main/queries/icons/icon-category-list.ts | 26 + .../src/main/queries/icons/icon-list.ts | 32 + .../src/main/queries/icons/icon-search.ts | 33 + .../src/main/queries/icons/icons-get.ts | 23 - apps/desktop/src/main/queries/index.ts | 18 +- .../renderer/components/avatars/avatar.tsx | 3 +- .../emojis/emoji-browser-category.tsx | 20 + .../emojis/emoji-browser-emojis.tsx | 26 + .../components/emojis/emoji-browser-row.tsx | 31 + .../components/emojis/emoji-browser.tsx | 58 + .../components/emojis/emoji-element.tsx | 2 +- .../emojis/emoji-picker-browser-row.tsx | 43 - .../emojis/emoji-picker-browser.tsx | 71 - .../components/emojis/emoji-picker-search.tsx | 28 - .../components/emojis/emoji-picker.tsx | 29 +- .../components/emojis/emoji-search.tsx | 27 + .../emojis/emoji-skin-tone-selector.tsx | 27 +- .../icons/icon-browser-category.tsx | 20 + .../components/icons/icon-browser-icons.tsx | 26 + .../components/icons/icon-browser-row.tsx | 31 + .../components/icons/icon-browser.tsx | 58 + .../components/icons/icon-element.tsx | 2 +- .../icons/icon-picker-browser-row.tsx | 43 - .../components/icons/icon-picker-browser.tsx | 71 - .../renderer/components/icons/icon-picker.tsx | 22 +- ...icon-picker-search.tsx => icon-search.tsx} | 21 +- .../components/messages/message-actions.tsx | 36 +- .../messages/message-quick-reaction.tsx | 30 + .../src/renderer/contexts/emoji-picker.ts | 3 +- .../src/renderer/contexts/icon-picker.ts | 3 +- apps/desktop/src/shared/lib/assets.ts | 31 + apps/desktop/src/shared/lib/avatars.ts | 14 +- apps/desktop/src/shared/lib/emojis.ts | 31 - apps/desktop/src/shared/lib/icons.ts | 27 - .../queries/emojis/emoji-category-list.ts | 14 + .../src/shared/queries/emojis/emoji-get.ts | 15 + .../src/shared/queries/emojis/emoji-list.ts | 17 + .../src/shared/queries/emojis/emoji-search.ts | 16 + .../src/shared/queries/emojis/emojis-get.ts | 14 - .../queries/icons/icon-category-list.ts | 14 + .../src/shared/queries/icons/icon-list.ts | 17 + .../src/shared/queries/icons/icon-search.ts | 16 + .../src/shared/queries/icons/icons-get.ts | 14 - apps/desktop/src/shared/types/emojis.ts | 13 +- apps/desktop/src/shared/types/icons.ts | 17 +- package-lock.json | 79 +- packages/core/src/lib/id.ts | 1 + scripts/package.json | 2 + scripts/src/emojis/README.md | 22 +- scripts/src/emojis/emojis.db | Bin 0 -> 10850304 bytes scripts/src/emojis/emojis.json | 39679 ------------- scripts/src/emojis/emojis.zip | Bin 3819678 -> 0 bytes scripts/src/emojis/index.ts | 453 +- scripts/src/icons/README.md | 31 +- scripts/src/icons/icons.db | Bin 0 -> 8650752 bytes scripts/src/icons/icons.json | 47506 ---------------- scripts/src/icons/icons.zip | Bin 3589939 -> 0 bytes scripts/src/icons/index.ts | 455 +- scripts/src/postinstall/README.md | 18 +- scripts/src/postinstall/index.ts | 96 +- scripts/src/seed/node-generator.ts | 28 +- 75 files changed, 1710 insertions(+), 88167 deletions(-) create mode 100644 apps/desktop/src/main/databases/emojis/index.ts create mode 100644 apps/desktop/src/main/databases/emojis/schema.ts create mode 100644 apps/desktop/src/main/databases/icons/index.ts create mode 100644 apps/desktop/src/main/databases/icons/schema.ts create mode 100644 apps/desktop/src/main/queries/emojis/emoji-category-list.ts create mode 100644 apps/desktop/src/main/queries/emojis/emoji-get.ts create mode 100644 apps/desktop/src/main/queries/emojis/emoji-list.ts create mode 100644 apps/desktop/src/main/queries/emojis/emoji-search.ts delete mode 100644 apps/desktop/src/main/queries/emojis/emojis-get.ts create mode 100644 apps/desktop/src/main/queries/icons/icon-category-list.ts create mode 100644 apps/desktop/src/main/queries/icons/icon-list.ts create mode 100644 apps/desktop/src/main/queries/icons/icon-search.ts delete mode 100644 apps/desktop/src/main/queries/icons/icons-get.ts create mode 100644 apps/desktop/src/renderer/components/emojis/emoji-browser-category.tsx create mode 100644 apps/desktop/src/renderer/components/emojis/emoji-browser-emojis.tsx create mode 100644 apps/desktop/src/renderer/components/emojis/emoji-browser-row.tsx create mode 100644 apps/desktop/src/renderer/components/emojis/emoji-browser.tsx delete mode 100644 apps/desktop/src/renderer/components/emojis/emoji-picker-browser-row.tsx delete mode 100644 apps/desktop/src/renderer/components/emojis/emoji-picker-browser.tsx delete mode 100644 apps/desktop/src/renderer/components/emojis/emoji-picker-search.tsx create mode 100644 apps/desktop/src/renderer/components/emojis/emoji-search.tsx create mode 100644 apps/desktop/src/renderer/components/icons/icon-browser-category.tsx create mode 100644 apps/desktop/src/renderer/components/icons/icon-browser-icons.tsx create mode 100644 apps/desktop/src/renderer/components/icons/icon-browser-row.tsx create mode 100644 apps/desktop/src/renderer/components/icons/icon-browser.tsx delete mode 100644 apps/desktop/src/renderer/components/icons/icon-picker-browser-row.tsx delete mode 100644 apps/desktop/src/renderer/components/icons/icon-picker-browser.tsx rename apps/desktop/src/renderer/components/icons/{icon-picker-search.tsx => icon-search.tsx} (50%) create mode 100644 apps/desktop/src/renderer/components/messages/message-quick-reaction.tsx create mode 100644 apps/desktop/src/shared/lib/assets.ts delete mode 100644 apps/desktop/src/shared/lib/emojis.ts delete mode 100644 apps/desktop/src/shared/lib/icons.ts create mode 100644 apps/desktop/src/shared/queries/emojis/emoji-category-list.ts create mode 100644 apps/desktop/src/shared/queries/emojis/emoji-get.ts create mode 100644 apps/desktop/src/shared/queries/emojis/emoji-list.ts create mode 100644 apps/desktop/src/shared/queries/emojis/emoji-search.ts delete mode 100644 apps/desktop/src/shared/queries/emojis/emojis-get.ts create mode 100644 apps/desktop/src/shared/queries/icons/icon-category-list.ts create mode 100644 apps/desktop/src/shared/queries/icons/icon-list.ts create mode 100644 apps/desktop/src/shared/queries/icons/icon-search.ts delete mode 100644 apps/desktop/src/shared/queries/icons/icons-get.ts create mode 100644 scripts/src/emojis/emojis.db delete mode 100644 scripts/src/emojis/emojis.json delete mode 100644 scripts/src/emojis/emojis.zip create mode 100644 scripts/src/icons/icons.db delete mode 100644 scripts/src/icons/icons.json delete mode 100644 scripts/src/icons/icons.zip diff --git a/.gitignore b/.gitignore index a5c7ef26..bf929b31 100644 --- a/.gitignore +++ b/.gitignore @@ -151,10 +151,6 @@ dist/ src/scripts/emojis/temp/ src/scripts/icons/temp/ -# Ignore desktop assets directories -apps/desktop/assets/emojis/ -apps/desktop/assets/icons/ - -# Ignore specific JSON files in desktop assets -apps/desktop/assets/emojis.json -apps/desktop/assets/icons.json +# Ignore desktop emojis and icons databases +apps/desktop/assets/emojis.db +apps/desktop/assets/icons.db diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ffdedc34..95e86d39 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -35,7 +35,7 @@ "@types/react-window": "^1.8.8", "@types/unzipper": "^0.10.10", "autoprefixer": "^10.4.20", - "electron": "^33.3.1", + "electron": "^34.0.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "vite": "^6.0.7" @@ -84,7 +84,7 @@ "@tiptap/extension-underline": "^2.11.2", "@tiptap/react": "^2.11.2", "@tiptap/suggestion": "^2.11.2", - "better-sqlite3": "^11.7.2", + "better-sqlite3": "^11.8.1", "bufferutil": "^4.0.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/apps/desktop/src/main/databases/emojis/index.ts b/apps/desktop/src/main/databases/emojis/index.ts new file mode 100644 index 00000000..e27a6e2f --- /dev/null +++ b/apps/desktop/src/main/databases/emojis/index.ts @@ -0,0 +1 @@ +export * from './schema'; diff --git a/apps/desktop/src/main/databases/emojis/schema.ts b/apps/desktop/src/main/databases/emojis/schema.ts new file mode 100644 index 00000000..d513117c --- /dev/null +++ b/apps/desktop/src/main/databases/emojis/schema.ts @@ -0,0 +1,52 @@ +import { ColumnType, Insertable, Selectable, Updateable } from 'kysely'; + +interface CategoriesTable { + id: ColumnType; + name: ColumnType; + count: ColumnType; + display_order: ColumnType; +} + +export type SelectCategory = Selectable; +export type CreateCategory = Insertable; +export type UpdateCategory = Updateable; + +interface EmojisTable { + id: ColumnType; + category_id: ColumnType; + code: ColumnType; + name: ColumnType; + tags: ColumnType; + emoticons: ColumnType; + skins: ColumnType; +} + +export type SelectEmoji = Selectable; +export type CreateEmoji = Insertable; +export type UpdateEmoji = Updateable; + +interface EmojiSvgsTable { + skin_id: ColumnType; + emoji_id: ColumnType; + svg: ColumnType; +} + +export type SelectEmojiSvg = Selectable; +export type CreateEmojiSvg = Insertable; +export type UpdateEmojiSvg = Updateable; + +interface EmojiSearchTable { + id: ColumnType; + text: ColumnType; +} + +export type SelectEmojiSearch = Selectable; +export type CreateEmojiSearch = Insertable; +export type UpdateEmojiSearch = Updateable; + +export interface EmojiDatabaseSchema { + emojis: EmojisTable; + categories: CategoriesTable; + emoji_search: EmojiSearchTable; + emoji_svgs: EmojiSvgsTable; +} diff --git a/apps/desktop/src/main/databases/icons/index.ts b/apps/desktop/src/main/databases/icons/index.ts new file mode 100644 index 00000000..e27a6e2f --- /dev/null +++ b/apps/desktop/src/main/databases/icons/index.ts @@ -0,0 +1 @@ +export * from './schema'; diff --git a/apps/desktop/src/main/databases/icons/schema.ts b/apps/desktop/src/main/databases/icons/schema.ts new file mode 100644 index 00000000..f502cce5 --- /dev/null +++ b/apps/desktop/src/main/databases/icons/schema.ts @@ -0,0 +1,49 @@ +import { ColumnType, Insertable, Selectable, Updateable } from 'kysely'; + +interface CategoriesTable { + id: ColumnType; + name: ColumnType; + count: ColumnType; + display_order: ColumnType; +} + +export type SelectCategory = Selectable; +export type CreateCategory = Insertable; +export type UpdateCategory = Updateable; + +interface IconsTable { + id: ColumnType; + category_id: ColumnType; + code: ColumnType; + name: ColumnType; + tags: ColumnType; +} + +export type SelectIcon = Selectable; +export type CreateIcon = Insertable; +export type UpdateIcon = Updateable; + +interface IconSvgsTable { + id: ColumnType; + svg: ColumnType; +} + +export type SelectIconSvg = Selectable; +export type CreateIconSvg = Insertable; +export type UpdateIconSvg = Updateable; + +interface IconSearchTable { + id: ColumnType; + text: ColumnType; +} + +export type SelectIconSearch = Selectable; +export type CreateIconSearch = Insertable; +export type UpdateIconSearch = Updateable; + +export interface IconDatabaseSchema { + icons: IconsTable; + categories: CategoriesTable; + icon_search: IconSearchTable; + icon_svgs: IconSvgsTable; +} diff --git a/apps/desktop/src/main/lib/assets.ts b/apps/desktop/src/main/lib/assets.ts index 24d5213c..67b6c89a 100644 --- a/apps/desktop/src/main/lib/assets.ts +++ b/apps/desktop/src/main/lib/assets.ts @@ -1,24 +1,20 @@ -import fs from 'fs'; +import { Kysely, SqliteDialect } from 'kysely'; +import SQLite from 'better-sqlite3'; + import path from 'path'; +import { EmojiDatabaseSchema } from '@/main/databases/emojis'; +import { IconDatabaseSchema } from '@/main/databases/icons'; import { getAssetsSourcePath } from '@/main/lib/utils'; -import { EmojiData } from '@/shared/types/emojis'; -import { IconData } from '@/shared/types/icons'; -export const getEmojiData = (): EmojiData => { - const emojisMetadataPath = path.join( - getAssetsSourcePath(), - 'emojis', - 'emojis.json' - ); - return JSON.parse(fs.readFileSync(emojisMetadataPath, 'utf8')); -}; +export const emojiDatabase = new Kysely({ + dialect: new SqliteDialect({ + database: new SQLite(path.join(getAssetsSourcePath(), 'emojis.db')), + }), +}); -export const getIconData = (): IconData => { - const iconsMetadataPath = path.join( - getAssetsSourcePath(), - 'icons', - 'icons.json' - ); - return JSON.parse(fs.readFileSync(iconsMetadataPath, 'utf8')); -}; +export const iconDatabase = new Kysely({ + dialect: new SqliteDialect({ + database: new SQLite(path.join(getAssetsSourcePath(), 'icons.db')), + }), +}); diff --git a/apps/desktop/src/main/lib/mappers.ts b/apps/desktop/src/main/lib/mappers.ts index d07f67ea..a182ab77 100644 --- a/apps/desktop/src/main/lib/mappers.ts +++ b/apps/desktop/src/main/lib/mappers.ts @@ -7,6 +7,8 @@ import { import { encodeState } from '@colanode/crdt'; import { SelectAccount, SelectServer } from '@/main/databases/app'; +import { SelectEmoji } from '@/main/databases/emojis'; +import { SelectIcon } from '@/main/databases/icons'; import { SelectWorkspace } from '@/main/databases/account'; import { SelectFile, @@ -32,6 +34,8 @@ import { MessageReaction, } from '@/shared/types/messages'; import { EntryInteraction } from '@/shared/types/entries'; +import { Emoji } from '@/shared/types/emojis'; +import { Icon } from '@/shared/types/icons'; export const mapUser = (row: SelectUser): User => { return { @@ -259,3 +263,25 @@ export const mapEntryInteraction = ( version: row.version, }; }; + +export const mapEmoji = (row: SelectEmoji): Emoji => { + return { + id: row.id, + code: row.code, + name: row.name, + categoryId: row.category_id, + tags: row.tags ? JSON.parse(row.tags) : [], + emoticons: row.emoticons ? JSON.parse(row.emoticons) : [], + skins: row.skins ? JSON.parse(row.skins) : [], + }; +}; + +export const mapIcon = (row: SelectIcon): Icon => { + return { + id: row.id, + name: row.name, + categoryId: row.category_id, + code: row.code, + tags: row.tags ? JSON.parse(row.tags) : [], + }; +}; diff --git a/apps/desktop/src/main/lib/protocols.ts b/apps/desktop/src/main/lib/protocols.ts index c269cd11..cc7199d6 100644 --- a/apps/desktop/src/main/lib/protocols.ts +++ b/apps/desktop/src/main/lib/protocols.ts @@ -4,16 +4,53 @@ import path from 'path'; import { getAccountAvatarsDirectoryPath, - getAssetsSourcePath, getWorkspaceFilesDirectoryPath, } from '@/main/lib/utils'; import { appService } from '@/main/services/app-service'; +import { emojiDatabase, iconDatabase } from '@/main/lib/assets'; -export const handleAssetRequest = (request: Request): Promise => { +export const handleAssetRequest = async ( + request: Request +): Promise => { const url = request.url.replace('asset://', ''); - const assetPath = path.join(getAssetsSourcePath(), url); - const localFileUrl = `file://${assetPath}`; - return net.fetch(localFileUrl); + const [type, id] = url.split('/'); + if (!type || !id) { + return new Response(null, { status: 400 }); + } + + if (type === 'emojis') { + const emoji = await emojiDatabase + .selectFrom('emoji_svgs') + .selectAll() + .where('skin_id', '=', id) + .executeTakeFirst(); + + if (emoji) { + return new Response(emoji.svg, { + headers: { + 'Content-Type': 'image/svg+xml', + }, + }); + } + } + + if (type === 'icons') { + const icon = await iconDatabase + .selectFrom('icon_svgs') + .selectAll() + .where('id', '=', id) + .executeTakeFirst(); + + if (icon) { + return new Response(icon.svg, { + headers: { + 'Content-Type': 'image/svg+xml', + }, + }); + } + } + + return new Response(null, { status: 404 }); }; export const handleAvatarRequest = async ( diff --git a/apps/desktop/src/main/queries/emojis/emoji-category-list.ts b/apps/desktop/src/main/queries/emojis/emoji-category-list.ts new file mode 100644 index 00000000..78851c64 --- /dev/null +++ b/apps/desktop/src/main/queries/emojis/emoji-category-list.ts @@ -0,0 +1,26 @@ +import { emojiDatabase } from '@/main/lib/assets'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { EmojiCategoryListQueryInput } from '@/shared/queries/emojis/emoji-category-list'; +import { EmojiCategory } from '@/shared/types/emojis'; +import { Event } from '@/shared/types/events'; + +export class EmojiCategoryListQueryHandler + implements QueryHandler +{ + public async handleQuery( + _: EmojiCategoryListQueryInput + ): Promise { + const data = emojiDatabase.selectFrom('categories').selectAll().execute(); + return data; + } + + public async checkForChanges( + _: Event, + __: EmojiCategoryListQueryInput, + ___: EmojiCategory[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/emojis/emoji-get.ts b/apps/desktop/src/main/queries/emojis/emoji-get.ts new file mode 100644 index 00000000..4cf9f88f --- /dev/null +++ b/apps/desktop/src/main/queries/emojis/emoji-get.ts @@ -0,0 +1,33 @@ +import { emojiDatabase } from '@/main/lib/assets'; +import { mapEmoji } from '@/main/lib/mappers'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { EmojiGetQueryInput } from '@/shared/queries/emojis/emoji-get'; +import { Emoji } from '@/shared/types/emojis'; +import { Event } from '@/shared/types/events'; + +export class EmojiGetQueryHandler implements QueryHandler { + public async handleQuery(input: EmojiGetQueryInput): Promise { + const data = await emojiDatabase + .selectFrom('emojis') + .selectAll() + .where('id', '=', input.id) + .executeTakeFirst(); + + if (!data) { + throw new Error('Emoji not found'); + } + + const emoji = mapEmoji(data); + return emoji; + } + + public async checkForChanges( + _: Event, + __: EmojiGetQueryInput, + ___: Emoji + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/emojis/emoji-list.ts b/apps/desktop/src/main/queries/emojis/emoji-list.ts new file mode 100644 index 00000000..254a5708 --- /dev/null +++ b/apps/desktop/src/main/queries/emojis/emoji-list.ts @@ -0,0 +1,34 @@ +import { emojiDatabase } from '@/main/lib/assets'; +import { mapEmoji } from '@/main/lib/mappers'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { EmojiListQueryInput } from '@/shared/queries/emojis/emoji-list'; +import { Emoji } from '@/shared/types/emojis'; +import { Event } from '@/shared/types/events'; + +export class EmojiListQueryHandler + implements QueryHandler +{ + public async handleQuery(input: EmojiListQueryInput): Promise { + const offset = input.page * input.count; + const data = await emojiDatabase + .selectFrom('emojis') + .selectAll() + .where('category_id', '=', input.category) + .limit(input.count) + .offset(offset) + .execute(); + + const emojis: Emoji[] = data.map(mapEmoji); + return emojis; + } + + public async checkForChanges( + _: Event, + __: EmojiListQueryInput, + ___: Emoji[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/emojis/emoji-search.ts b/apps/desktop/src/main/queries/emojis/emoji-search.ts new file mode 100644 index 00000000..50016598 --- /dev/null +++ b/apps/desktop/src/main/queries/emojis/emoji-search.ts @@ -0,0 +1,33 @@ +import { emojiDatabase } from '@/main/lib/assets'; +import { mapEmoji } from '@/main/lib/mappers'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { EmojiSearchQueryInput } from '@/shared/queries/emojis/emoji-search'; +import { Emoji } from '@/shared/types/emojis'; +import { Event } from '@/shared/types/events'; + +export class EmojiSearchQueryHandler + implements QueryHandler +{ + public async handleQuery(input: EmojiSearchQueryInput): Promise { + const data = await emojiDatabase + .selectFrom('emojis') + .innerJoin('emoji_search', 'emojis.id', 'emoji_search.id') + .selectAll('emojis') + .where('emoji_search.text', 'match', `${input.query}*`) + .limit(input.count) + .execute(); + + const emojis: Emoji[] = data.map(mapEmoji); + return emojis; + } + + public async checkForChanges( + _: Event, + __: EmojiSearchQueryInput, + ___: Emoji[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/emojis/emojis-get.ts b/apps/desktop/src/main/queries/emojis/emojis-get.ts deleted file mode 100644 index 034a9879..00000000 --- a/apps/desktop/src/main/queries/emojis/emojis-get.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getEmojiData } from '@/main/lib/assets'; -import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; -import { EmojisGetQueryInput } from '@/shared/queries/emojis/emojis-get'; -import { EmojiData } from '@/shared/types/emojis'; -import { Event } from '@/shared/types/events'; - -export class EmojisGetQueryHandler - implements QueryHandler -{ - public async handleQuery(_: EmojisGetQueryInput): Promise { - const data = getEmojiData(); - return data; - } - - public async checkForChanges( - _: Event, - __: EmojisGetQueryInput, - ___: EmojiData - ): Promise> { - return { - hasChanges: false, - }; - } -} diff --git a/apps/desktop/src/main/queries/icons/icon-category-list.ts b/apps/desktop/src/main/queries/icons/icon-category-list.ts new file mode 100644 index 00000000..35efb73c --- /dev/null +++ b/apps/desktop/src/main/queries/icons/icon-category-list.ts @@ -0,0 +1,26 @@ +import { iconDatabase } from '@/main/lib/assets'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { IconCategoryListQueryInput } from '@/shared/queries/icons/icon-category-list'; +import { IconCategory } from '@/shared/types/icons'; +import { Event } from '@/shared/types/events'; + +export class IconCategoryListQueryHandler + implements QueryHandler +{ + public async handleQuery( + _: IconCategoryListQueryInput + ): Promise { + const data = iconDatabase.selectFrom('categories').selectAll().execute(); + return data; + } + + public async checkForChanges( + _: Event, + __: IconCategoryListQueryInput, + ___: IconCategory[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/icons/icon-list.ts b/apps/desktop/src/main/queries/icons/icon-list.ts new file mode 100644 index 00000000..4eca8585 --- /dev/null +++ b/apps/desktop/src/main/queries/icons/icon-list.ts @@ -0,0 +1,32 @@ +import { iconDatabase } from '@/main/lib/assets'; +import { mapIcon } from '@/main/lib/mappers'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { IconListQueryInput } from '@/shared/queries/icons/icon-list'; +import { Icon } from '@/shared/types/icons'; +import { Event } from '@/shared/types/events'; + +export class IconListQueryHandler implements QueryHandler { + public async handleQuery(input: IconListQueryInput): Promise { + const offset = input.page * input.count; + const data = await iconDatabase + .selectFrom('icons') + .selectAll() + .where('category_id', '=', input.category) + .limit(input.count) + .offset(offset) + .execute(); + + const icons: Icon[] = data.map(mapIcon); + return icons; + } + + public async checkForChanges( + _: Event, + __: IconListQueryInput, + ___: Icon[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/icons/icon-search.ts b/apps/desktop/src/main/queries/icons/icon-search.ts new file mode 100644 index 00000000..3eb6e647 --- /dev/null +++ b/apps/desktop/src/main/queries/icons/icon-search.ts @@ -0,0 +1,33 @@ +import { iconDatabase } from '@/main/lib/assets'; +import { mapIcon } from '@/main/lib/mappers'; +import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; +import { IconSearchQueryInput } from '@/shared/queries/icons/icon-search'; +import { Icon } from '@/shared/types/icons'; +import { Event } from '@/shared/types/events'; + +export class IconSearchQueryHandler + implements QueryHandler +{ + public async handleQuery(input: IconSearchQueryInput): Promise { + const data = await iconDatabase + .selectFrom('icons') + .innerJoin('icon_search', 'icons.id', 'icon_search.id') + .selectAll('icons') + .where('icon_search.text', 'match', `${input.query}*`) + .limit(input.count) + .execute(); + + const icons: Icon[] = data.map(mapIcon); + return icons; + } + + public async checkForChanges( + _: Event, + __: IconSearchQueryInput, + ___: Icon[] + ): Promise> { + return { + hasChanges: false, + }; + } +} diff --git a/apps/desktop/src/main/queries/icons/icons-get.ts b/apps/desktop/src/main/queries/icons/icons-get.ts deleted file mode 100644 index 1e6fae8d..00000000 --- a/apps/desktop/src/main/queries/icons/icons-get.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getIconData } from '@/main/lib/assets'; -import { ChangeCheckResult, QueryHandler } from '@/main/lib/types'; -import { IconsGetQueryInput } from '@/shared/queries/icons/icons-get'; -import { Event } from '@/shared/types/events'; -import { IconData } from '@/shared/types/icons'; - -export class IconsGetQueryHandler implements QueryHandler { - public async handleQuery(_: IconsGetQueryInput): Promise { - const data = getIconData(); - - return data; - } - - public async checkForChanges( - _: Event, - __: IconsGetQueryInput, - ___: IconData - ): Promise> { - return { - hasChanges: false, - }; - } -} diff --git a/apps/desktop/src/main/queries/index.ts b/apps/desktop/src/main/queries/index.ts index 52309994..dd024978 100644 --- a/apps/desktop/src/main/queries/index.ts +++ b/apps/desktop/src/main/queries/index.ts @@ -1,10 +1,15 @@ import { AccountGetQueryHandler } from '@/main/queries/accounts/account-get'; import { AccountListQueryHandler } from '@/main/queries/accounts/accounts-list'; -import { EmojisGetQueryHandler } from '@/main/queries/emojis/emojis-get'; +import { EmojiGetQueryHandler } from '@/main/queries/emojis/emoji-get'; +import { EmojiListQueryHandler } from '@/main/queries/emojis/emoji-list'; +import { EmojiCategoryListQueryHandler } from '@/main/queries/emojis/emoji-category-list'; +import { EmojiSearchQueryHandler } from '@/main/queries/emojis/emoji-search'; import { FileListQueryHandler } from '@/main/queries/files/file-list'; import { FileGetQueryHandler } from '@/main/queries/files/file-get'; import { FileMetadataGetQueryHandler } from '@/main/queries/files/file-metadata-get'; -import { IconsGetQueryHandler } from '@/main/queries/icons/icons-get'; +import { IconListQueryHandler } from '@/main/queries/icons/icon-list'; +import { IconSearchQueryHandler } from '@/main/queries/icons/icon-search'; +import { IconCategoryListQueryHandler } from '@/main/queries/icons/icon-category-list'; import { MessageGetQueryHandler } from '@/main/queries/messages/message-get'; import { MessageListQueryHandler } from '@/main/queries/messages/message-list'; import { MessageReactionsGetQueryHandler } from '@/main/queries/messages/message-reactions-get'; @@ -42,8 +47,13 @@ export const queryHandlerMap: QueryHandlerMap = { workspace_list: new WorkspaceListQueryHandler(), user_list: new UserListQueryHandler(), file_list: new FileListQueryHandler(), - emojis_get: new EmojisGetQueryHandler(), - icons_get: new IconsGetQueryHandler(), + emoji_list: new EmojiListQueryHandler(), + emoji_get: new EmojiGetQueryHandler(), + emoji_category_list: new EmojiCategoryListQueryHandler(), + emoji_search: new EmojiSearchQueryHandler(), + icon_list: new IconListQueryHandler(), + icon_search: new IconSearchQueryHandler(), + icon_category_list: new IconCategoryListQueryHandler(), entry_tree_get: new EntryTreeGetQueryHandler(), entry_children_get: new EntryChildrenGetQueryHandler(), radar_data_get: new RadarDataGetQueryHandler(), diff --git a/apps/desktop/src/renderer/components/avatars/avatar.tsx b/apps/desktop/src/renderer/components/avatars/avatar.tsx index 59e9f0c7..2d0921b5 100644 --- a/apps/desktop/src/renderer/components/avatars/avatar.tsx +++ b/apps/desktop/src/renderer/components/avatars/avatar.tsx @@ -8,8 +8,7 @@ import { getColorForId, getDefaultNodeAvatar, } from '@/shared/lib/avatars'; -import { getEmojiUrl } from '@/shared/lib/emojis'; -import { getIconUrl } from '@/shared/lib/icons'; +import { getEmojiUrl, getIconUrl } from '@/shared/lib/assets'; import { cn } from '@/shared/lib/utils'; interface AvatarProps { diff --git a/apps/desktop/src/renderer/components/emojis/emoji-browser-category.tsx b/apps/desktop/src/renderer/components/emojis/emoji-browser-category.tsx new file mode 100644 index 00000000..f4846863 --- /dev/null +++ b/apps/desktop/src/renderer/components/emojis/emoji-browser-category.tsx @@ -0,0 +1,20 @@ +import { EmojiPickerLabelRow } from '@/shared/types/emojis'; + +interface EmojiBrowserCategoryProps { + row: EmojiPickerLabelRow; + style: React.CSSProperties; +} + +export const EmojiBrowserCategory = ({ + row, + style, +}: EmojiBrowserCategoryProps) => { + return ( +
+

{row.category}

+
+ ); +}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-browser-emojis.tsx b/apps/desktop/src/renderer/components/emojis/emoji-browser-emojis.tsx new file mode 100644 index 00000000..67a7df25 --- /dev/null +++ b/apps/desktop/src/renderer/components/emojis/emoji-browser-emojis.tsx @@ -0,0 +1,26 @@ +import { EmojiPickerEmojiRow } from '@/shared/types/emojis'; +import { useQuery } from '@/renderer/hooks/use-query'; +import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item'; + +interface EmojiBrowserEmojisProps { + row: EmojiPickerEmojiRow; + style: React.CSSProperties; +} + +export const EmojiBrowserEmojis = ({ row, style }: EmojiBrowserEmojisProps) => { + const { data } = useQuery({ + type: 'emoji_list', + category: row.category, + page: row.page, + count: row.count, + }); + + const emojis = data ?? []; + return ( +
+ {emojis.map((emoji) => ( + + ))} +
+ ); +}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-browser-row.tsx b/apps/desktop/src/renderer/components/emojis/emoji-browser-row.tsx new file mode 100644 index 00000000..ad60a64b --- /dev/null +++ b/apps/desktop/src/renderer/components/emojis/emoji-browser-row.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { EmojiPickerRowData } from '@/shared/types/emojis'; +import { EmojiBrowserEmojis } from '@/renderer/components/emojis/emoji-browser-emojis'; +import { EmojiBrowserCategory } from '@/renderer/components/emojis/emoji-browser-category'; + +interface EmojiBrowserRowProps { + index: number; + style: React.CSSProperties; + data: { + rows: EmojiPickerRowData[]; + }; +} + +export const EmojiBrowserRow = ({ + index, + data, + style, +}: EmojiBrowserRowProps) => { + const rowData = data.rows[index]; + + if (!rowData) { + return null; + } + + if (rowData.type === 'label') { + return ; + } + + return ; +}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-browser.tsx b/apps/desktop/src/renderer/components/emojis/emoji-browser.tsx new file mode 100644 index 00000000..30478a72 --- /dev/null +++ b/apps/desktop/src/renderer/components/emojis/emoji-browser.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList } from 'react-window'; + +import { EmojiPickerRowData } from '@/shared/types/emojis'; +import { EmojiBrowserRow } from '@/renderer/components/emojis/emoji-browser-row'; +import { useQuery } from '@/renderer/hooks/use-query'; + +const EMOJIS_PER_ROW = 10; + +export const EmojiBrowser = () => { + const { data } = useQuery({ + type: 'emoji_category_list', + }); + + const categories = data ?? []; + const rowDataArray = React.useMemo(() => { + const rows: EmojiPickerRowData[] = []; + + for (const category of categories) { + rows.push({ + type: 'label', + category: category.name, + }); + + const numEmojis = category.count; + const numRowsInCategory = Math.ceil(numEmojis / EMOJIS_PER_ROW); + + for (let i = 0; i < numRowsInCategory; i++) { + rows.push({ + type: 'emoji', + category: category.id, + page: i, + count: EMOJIS_PER_ROW, + }); + } + } + + return rows; + }, [categories]); + + return ( + + {({ width, height }: { width: number; height: number }) => ( + + {EmojiBrowserRow} + + )} + + ); +}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-element.tsx b/apps/desktop/src/renderer/components/emojis/emoji-element.tsx index 1c5abf47..d8f69f9c 100644 --- a/apps/desktop/src/renderer/components/emojis/emoji-element.tsx +++ b/apps/desktop/src/renderer/components/emojis/emoji-element.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { getEmojiUrl } from '@/shared/lib/emojis'; +import { getEmojiUrl } from '@/shared/lib/assets'; interface Props extends React.ImgHTMLAttributes { id: string; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-picker-browser-row.tsx b/apps/desktop/src/renderer/components/emojis/emoji-picker-browser-row.tsx deleted file mode 100644 index bf932112..00000000 --- a/apps/desktop/src/renderer/components/emojis/emoji-picker-browser-row.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item'; -import { EmojiPickerRowData } from '@/shared/types/emojis'; - -interface EmojiPickerBrowserRowProps { - index: number; - style: React.CSSProperties; - data: { - rows: EmojiPickerRowData[]; - }; -} - -export const EmojiPickerBrowserRow = ({ - index, - style, - data, -}: EmojiPickerBrowserRowProps) => { - const rowData = data.rows[index]; - - if (!rowData) { - return null; - } - - if (rowData.type === 'label') { - return ( -
-

{rowData.category}

-
- ); - } - - return ( -
- {rowData.emojis.map((emoji) => ( - - ))} -
- ); -}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-picker-browser.tsx b/apps/desktop/src/renderer/components/emojis/emoji-picker-browser.tsx deleted file mode 100644 index cea4afa7..00000000 --- a/apps/desktop/src/renderer/components/emojis/emoji-picker-browser.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList } from 'react-window'; - -import { EmojiPickerBrowserRow } from '@/renderer/components/emojis/emoji-picker-browser-row'; -import { useEmojiPicker } from '@/renderer/contexts/emoji-picker'; -import { Emoji, EmojiPickerRowData } from '@/shared/types/emojis'; - -const emojisPerRow = 10; - -export const EmojiPickerBrowser = () => { - const { data } = useEmojiPicker(); - - const rowDataArray = React.useMemo(() => { - const rows: EmojiPickerRowData[] = []; - - for (let i = 0; i < data.categories.length; i++) { - const category = data.categories[i]; - if (!category) { - continue; - } - - // Add the category label - rows.push({ - type: 'label', - category: category.name, - }); - - const numEmojis = category.emojis.length; - const numRowsInCategory = Math.ceil(numEmojis / emojisPerRow); - - for (let rowIndex = 0; rowIndex < numRowsInCategory; rowIndex++) { - const start = rowIndex * emojisPerRow; - const end = start + emojisPerRow; - const emojisIds = category.emojis.slice(start, end); - const emojisInRow: Emoji[] = []; - for (const id of emojisIds) { - const emoji = data.emojis[id]; - if (!emoji) { - continue; - } - emojisInRow.push(emoji); - } - - rows.push({ - type: 'emoji', - emojis: emojisInRow, - }); - } - } - - return rows; - }, []); - - return ( - - {({ width, height }: { width: number; height: number }) => ( - - {EmojiPickerBrowserRow} - - )} - - ); -}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-picker-search.tsx b/apps/desktop/src/renderer/components/emojis/emoji-picker-search.tsx deleted file mode 100644 index fd6a5eb8..00000000 --- a/apps/desktop/src/renderer/components/emojis/emoji-picker-search.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item'; -import { useEmojiPicker } from '@/renderer/contexts/emoji-picker'; -import { searchEmojis } from '@/shared/lib/emojis'; - -interface EmojiPickerSearchProps { - query: string; -} - -export const EmojiPickerSearch = ({ query }: EmojiPickerSearchProps) => { - const { data } = useEmojiPicker(); - - const filteredEmojis = React.useMemo(() => { - return searchEmojis(query, data); - }, [query]); - - return ( -
-
-

Search results for "{query}"

-
- {filteredEmojis.map((emoji) => ( - - ))} -
- ); -}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-picker.tsx b/apps/desktop/src/renderer/components/emojis/emoji-picker.tsx index 3564deb2..6c8e7dd0 100644 --- a/apps/desktop/src/renderer/components/emojis/emoji-picker.tsx +++ b/apps/desktop/src/renderer/components/emojis/emoji-picker.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { EmojiPickerBrowser } from '@/renderer/components/emojis/emoji-picker-browser'; -import { EmojiPickerSearch } from '@/renderer/components/emojis/emoji-picker-search'; +import { EmojiBrowser } from '@/renderer/components/emojis/emoji-browser'; +import { EmojiSearch } from '@/renderer/components/emojis/emoji-search'; import { EmojiSkinToneSelector } from '@/renderer/components/emojis/emoji-skin-tone-selector'; import { EmojiPickerContext } from '@/renderer/contexts/emoji-picker'; -import { useQuery } from '@/renderer/hooks/use-query'; import { Emoji } from '@/shared/types/emojis'; interface EmojiPickerProps { @@ -14,7 +13,6 @@ interface EmojiPickerProps { export const EmojiPicker = ({ onPick }: EmojiPickerProps) => { const [query, setQuery] = React.useState(''); const [skinTone, setSkinTone] = React.useState(0); - const { data, isPending } = useQuery({ type: 'emojis_get' }); return (
@@ -32,21 +30,14 @@ export const EmojiPicker = ({ onPick }: EmojiPickerProps) => { />
- {!isPending && data && ( - onPick(emoji, skinTone), - }} - > - {query.length > 2 ? ( - - ) : ( - - )} - - )} + onPick(emoji, skinTone), + }} + > + {query.length > 2 ? : } +
); diff --git a/apps/desktop/src/renderer/components/emojis/emoji-search.tsx b/apps/desktop/src/renderer/components/emojis/emoji-search.tsx new file mode 100644 index 00000000..297b7a20 --- /dev/null +++ b/apps/desktop/src/renderer/components/emojis/emoji-search.tsx @@ -0,0 +1,27 @@ +import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item'; +import { useQuery } from '@/renderer/hooks/use-query'; + +interface EmojiSearchProps { + query: string; +} + +export const EmojiSearch = ({ query }: EmojiSearchProps) => { + const { data } = useQuery({ + type: 'emoji_search', + query, + count: 100, + }); + + const emojis = data ?? []; + + return ( +
+
+

Search results for "{query}"

+
+ {emojis.map((emoji) => ( + + ))} +
+ ); +}; diff --git a/apps/desktop/src/renderer/components/emojis/emoji-skin-tone-selector.tsx b/apps/desktop/src/renderer/components/emojis/emoji-skin-tone-selector.tsx index 0feb9414..9eff9409 100644 --- a/apps/desktop/src/renderer/components/emojis/emoji-skin-tone-selector.tsx +++ b/apps/desktop/src/renderer/components/emojis/emoji-skin-tone-selector.tsx @@ -6,16 +6,8 @@ import { PopoverContent, PopoverTrigger, } from '@/renderer/components/ui/popover'; -import { getEmojiUrl } from '@/shared/lib/emojis'; - -const skins: string[] = [ - '01je8kh1j8qdp2tbc4b8b8rgj7em', - '01je8kh1j8qdp2tbc4b8b8rgj8em', - '01je8kh1j8qdp2tbc4b8b8rgj9em', - '01je8kh1j8qdp2tbc4b8b8rgjaem', - '01je8kh1j8qdp2tbc4b8b8rgjbem', - '01je8kh1j9q3pjaxx25w2ysct2em', -]; +import { defaultEmojis, getEmojiUrl } from '@/shared/lib/assets'; +import { useQuery } from '@/renderer/hooks/use-query'; interface EmojiSkinToneSelectorProps { skinTone: number; @@ -28,11 +20,20 @@ export const EmojiSkinToneSelector = ({ }: EmojiSkinToneSelectorProps) => { const [open, setOpen] = React.useState(false); + const { data } = useQuery({ + type: 'emoji_get', + id: defaultEmojis.hand, + }); + const handleSkinToneSelection = (skinTone: number) => { setOpen(false); onSkinToneChange?.(skinTone); }; + if (!data) { + return null; + } + return ( @@ -42,13 +43,13 @@ export const EmojiSkinToneSelector = ({ }`} > - {skins.map((skin, idx) => ( + {data.skins.map((skin, idx) => ( ))} diff --git a/apps/desktop/src/renderer/components/icons/icon-browser-category.tsx b/apps/desktop/src/renderer/components/icons/icon-browser-category.tsx new file mode 100644 index 00000000..b11ea25e --- /dev/null +++ b/apps/desktop/src/renderer/components/icons/icon-browser-category.tsx @@ -0,0 +1,20 @@ +import { IconPickerLabelRow } from '@/shared/types/icons'; + +interface IconBrowserCategoryProps { + row: IconPickerLabelRow; + style: React.CSSProperties; +} + +export const IconBrowserCategory = ({ + row, + style, +}: IconBrowserCategoryProps) => { + return ( +
+

{row.category}

+
+ ); +}; diff --git a/apps/desktop/src/renderer/components/icons/icon-browser-icons.tsx b/apps/desktop/src/renderer/components/icons/icon-browser-icons.tsx new file mode 100644 index 00000000..09ef865c --- /dev/null +++ b/apps/desktop/src/renderer/components/icons/icon-browser-icons.tsx @@ -0,0 +1,26 @@ +import { IconPickerIconRow } from '@/shared/types/icons'; +import { useQuery } from '@/renderer/hooks/use-query'; +import { IconPickerItem } from '@/renderer/components/icons/icon-picker-item'; + +interface IconBrowserIconsProps { + row: IconPickerIconRow; + style: React.CSSProperties; +} + +export const IconBrowserIcons = ({ row, style }: IconBrowserIconsProps) => { + const { data } = useQuery({ + type: 'icon_list', + category: row.category, + page: row.page, + count: row.count, + }); + + const icons = data ?? []; + return ( +
+ {icons.map((icon) => ( + + ))} +
+ ); +}; diff --git a/apps/desktop/src/renderer/components/icons/icon-browser-row.tsx b/apps/desktop/src/renderer/components/icons/icon-browser-row.tsx new file mode 100644 index 00000000..783f503a --- /dev/null +++ b/apps/desktop/src/renderer/components/icons/icon-browser-row.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { IconBrowserCategory } from '@/renderer/components/icons/icon-browser-category'; +import { IconBrowserIcons } from '@/renderer/components/icons/icon-browser-icons'; +import { IconPickerRowData } from '@/shared/types/icons'; + +interface IconPickerIconRowProps { + index: number; + style: React.CSSProperties; + data: { + rows: IconPickerRowData[]; + }; +} + +export const IconPickerIconRow = ({ + index, + style, + data, +}: IconPickerIconRowProps) => { + const rowData = data.rows[index]; + + if (!rowData) { + return null; + } + + if (rowData.type === 'label') { + return ; + } + + return ; +}; diff --git a/apps/desktop/src/renderer/components/icons/icon-browser.tsx b/apps/desktop/src/renderer/components/icons/icon-browser.tsx new file mode 100644 index 00000000..48fa131f --- /dev/null +++ b/apps/desktop/src/renderer/components/icons/icon-browser.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList } from 'react-window'; + +import { IconPickerRowData } from '@/shared/types/icons'; +import { IconPickerIconRow } from '@/renderer/components/icons/icon-browser-row'; +import { useQuery } from '@/renderer/hooks/use-query'; + +const ICONS_PER_ROW = 10; + +export const IconBrowser = () => { + const { data } = useQuery({ + type: 'icon_category_list', + }); + + const categories = data ?? []; + const rowDataArray = React.useMemo(() => { + const rows: IconPickerRowData[] = []; + + for (const category of categories) { + rows.push({ + type: 'label', + category: category.name, + }); + + const numIcons = category.count; + const numRowsInCategory = Math.ceil(numIcons / ICONS_PER_ROW); + + for (let i = 0; i < numRowsInCategory; i++) { + rows.push({ + type: 'icon', + category: category.id, + page: i, + count: ICONS_PER_ROW, + }); + } + } + + return rows; + }, [categories]); + + return ( + + {({ width, height }: { width: number; height: number }) => ( + + {IconPickerIconRow} + + )} + + ); +}; diff --git a/apps/desktop/src/renderer/components/icons/icon-element.tsx b/apps/desktop/src/renderer/components/icons/icon-element.tsx index ab5e2ff0..c3b9309b 100644 --- a/apps/desktop/src/renderer/components/icons/icon-element.tsx +++ b/apps/desktop/src/renderer/components/icons/icon-element.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { getIconUrl } from '@/shared/lib/icons'; +import { getIconUrl } from '@/shared/lib/assets'; interface Props extends React.ImgHTMLAttributes { id: string; diff --git a/apps/desktop/src/renderer/components/icons/icon-picker-browser-row.tsx b/apps/desktop/src/renderer/components/icons/icon-picker-browser-row.tsx deleted file mode 100644 index c663e4e0..00000000 --- a/apps/desktop/src/renderer/components/icons/icon-picker-browser-row.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -import { IconPickerItem } from '@/renderer/components/icons/icon-picker-item'; -import { IconPickerRowData } from '@/shared/types/icons'; - -interface IconPickerBrowserRowProps { - index: number; - style: React.CSSProperties; - data: { - rows: IconPickerRowData[]; - }; -} - -export const IconPickerBrowserRow = ({ - index, - style, - data, -}: IconPickerBrowserRowProps) => { - const rowData = data.rows[index]; - - if (!rowData) { - return null; - } - - if (rowData.type === 'label') { - return ( -
-

{rowData.category}

-
- ); - } - - return ( -
- {rowData.icons.map((icon) => ( - - ))} -
- ); -}; diff --git a/apps/desktop/src/renderer/components/icons/icon-picker-browser.tsx b/apps/desktop/src/renderer/components/icons/icon-picker-browser.tsx deleted file mode 100644 index 4e6dfbf4..00000000 --- a/apps/desktop/src/renderer/components/icons/icon-picker-browser.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList } from 'react-window'; - -import { IconPickerBrowserRow } from '@/renderer/components/icons/icon-picker-browser-row'; -import { useIconPicker } from '@/renderer/contexts/icon-picker'; -import { Icon, IconPickerRowData } from '@/shared/types/icons'; - -const iconsPerRow = 10; - -export const IconPickerBrowser = () => { - const { data } = useIconPicker(); - const rowDataArray = React.useMemo(() => { - const rows: IconPickerRowData[] = []; - - for (let i = 0; i < data.categories.length; i++) { - const category = data.categories[i]; - - if (!category) { - continue; - } - - // Add the category label - rows.push({ - type: 'label', - category: category.name, - }); - - const numIcons = category.icons.length; - const numRowsInCategory = Math.ceil(numIcons / iconsPerRow); - - for (let rowIndex = 0; rowIndex < numRowsInCategory; rowIndex++) { - const start = rowIndex * iconsPerRow; - const end = start + iconsPerRow; - const iconIds = category.icons.slice(start, end); - const iconsInRow: Icon[] = []; - for (const id of iconIds) { - const icon = data.icons[id]; - if (!icon) { - continue; - } - iconsInRow.push(icon); - } - - rows.push({ - type: 'icon', - icons: iconsInRow, - }); - } - } - - return rows; - }, []); - - return ( - - {({ width, height }: { width: number; height: number }) => ( - - {IconPickerBrowserRow} - - )} - - ); -}; diff --git a/apps/desktop/src/renderer/components/icons/icon-picker.tsx b/apps/desktop/src/renderer/components/icons/icon-picker.tsx index 2361ddf6..2cc8dd80 100644 --- a/apps/desktop/src/renderer/components/icons/icon-picker.tsx +++ b/apps/desktop/src/renderer/components/icons/icon-picker.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { IconPickerBrowser } from '@/renderer/components/icons/icon-picker-browser'; -import { IconPickerSearch } from '@/renderer/components/icons/icon-picker-search'; +import { IconBrowser } from '@/renderer/components/icons/icon-browser'; +import { IconSearch } from '@/renderer/components/icons/icon-search'; import { IconPickerContext } from '@/renderer/contexts/icon-picker'; -import { useQuery } from '@/renderer/hooks/use-query'; import { Icon } from '@/shared/types/icons'; interface IconPickerProps { @@ -12,14 +11,9 @@ interface IconPickerProps { export const IconPicker = ({ onPick }: IconPickerProps) => { const [query, setQuery] = React.useState(''); - const { data, isPending } = useQuery({ type: 'icons_get' }); - - if (!data) { - return null; - } return ( - +
{ className="w-full rounded-md bg-gray-100 p-2 text-xs focus:outline-none" />
- {!isPending && data && ( - - {query.length > 2 ? ( - - ) : ( - - )} - - )} + {query.length > 2 ? : }
diff --git a/apps/desktop/src/renderer/components/icons/icon-picker-search.tsx b/apps/desktop/src/renderer/components/icons/icon-search.tsx similarity index 50% rename from apps/desktop/src/renderer/components/icons/icon-picker-search.tsx rename to apps/desktop/src/renderer/components/icons/icon-search.tsx index 293fd6e2..c4b742b6 100644 --- a/apps/desktop/src/renderer/components/icons/icon-picker-search.tsx +++ b/apps/desktop/src/renderer/components/icons/icon-search.tsx @@ -1,26 +1,25 @@ -import React from 'react'; - import { IconPickerItem } from '@/renderer/components/icons/icon-picker-item'; -import { useIconPicker } from '@/renderer/contexts/icon-picker'; -import { searchIcons } from '@/shared/lib/icons'; +import { useQuery } from '@/renderer/hooks/use-query'; -interface IconPickerSearchProps { +interface IconSearchProps { query: string; } -export const IconPickerSearch = ({ query }: IconPickerSearchProps) => { - const { data } = useIconPicker(); +export const IconSearch = ({ query }: IconSearchProps) => { + const { data } = useQuery({ + type: 'icon_search', + query, + count: 100, + }); - const filteredIcons = React.useMemo(() => { - return searchIcons(query, data); - }, [query]); + const icons = data ?? []; return (

Search results for "{query}"

- {filteredIcons.map((icon) => ( + {icons.map((icon) => ( ))}
diff --git a/apps/desktop/src/renderer/components/messages/message-actions.tsx b/apps/desktop/src/renderer/components/messages/message-actions.tsx index 1f2eb3a1..d04d6739 100644 --- a/apps/desktop/src/renderer/components/messages/message-actions.tsx +++ b/apps/desktop/src/renderer/components/messages/message-actions.tsx @@ -1,15 +1,16 @@ import { MessagesSquare, Reply } from 'lucide-react'; import React from 'react'; -import { EmojiElement } from '@/renderer/components/emojis/emoji-element'; import { MessageDeleteButton } from '@/renderer/components/messages/message-delete-button'; import { MessageReactionCreatePopover } from '@/renderer/components/messages/message-reaction-create-popover'; +import { MessageQuickReaction } from '@/renderer/components/messages/message-quick-reaction'; import { Separator } from '@/renderer/components/ui/separator'; import { useConversation } from '@/renderer/contexts/conversation'; import { useWorkspace } from '@/renderer/contexts/workspace'; import { useMutation } from '@/renderer/hooks/use-mutation'; import { toast } from '@/renderer/hooks/use-toast'; import { MessageNode } from '@/shared/types/messages'; +import { defaultEmojis } from '@/shared/lib/assets'; const MessageAction = ({ children }: { children: React.ReactNode }) => { return ( @@ -23,12 +24,6 @@ interface MessageActionsProps { message: MessageNode; } -const quickReactions = [ - '01je8kh1jw8hm4s8pgb1j7ha3jem', - '01je8kh1j23gc6jt9cbjfsam7dem', - '01je8kh228xbs1hv5gn6ff0y90em', -]; - export const MessageActions = ({ message }: MessageActionsProps) => { const workspace = useWorkspace(); const conversation = useConversation(); @@ -66,15 +61,24 @@ export const MessageActions = ({ message }: MessageActionsProps) => { return (