mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Update emojis and icons with the new structure
This commit is contained in:
@@ -104,6 +104,7 @@
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-pattern": "^5.5.0",
|
||||
"unzipper": "^0.12.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -120,6 +121,7 @@
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/unzipper": "^0.10.10",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
128
apps/desktop/src/main/asset-manager.ts
Normal file
128
apps/desktop/src/main/asset-manager.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { app, net } from 'electron';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import unzipper from 'unzipper';
|
||||
import { EmojiData } from '@/types/emojis';
|
||||
import { IconData } from '@/types/icons';
|
||||
|
||||
type AssetsVersion = {
|
||||
date: string;
|
||||
emojis: string;
|
||||
icons: string;
|
||||
};
|
||||
|
||||
const EMOJIS_VERSION = '1.0.0';
|
||||
const ICONS_VERSION = '1.0.0';
|
||||
|
||||
class AssetManager {
|
||||
private readonly assetsDir: string;
|
||||
|
||||
constructor() {
|
||||
this.assetsDir = path.join(app.getPath('userData'), 'assets');
|
||||
}
|
||||
|
||||
public async handleAssetRequest(request: Request): Promise<Response> {
|
||||
const url = request.url.replace('asset://', '');
|
||||
const assetPath = this.getAssetPath(url);
|
||||
const localFileUrl = `file://${assetPath}`;
|
||||
return net.fetch(localFileUrl);
|
||||
}
|
||||
|
||||
private getAssetPath(url: string): string {
|
||||
if (url.includes('emojis') || url.includes('icons')) {
|
||||
return path.join(this.assetsDir, url);
|
||||
}
|
||||
|
||||
return path.join(__dirname, 'assets', url);
|
||||
}
|
||||
|
||||
public getEmojiData(): EmojiData {
|
||||
const emojisDir = path.join(this.assetsDir, 'emojis');
|
||||
const emojisMetadataPath = path.join(emojisDir, 'emojis.json');
|
||||
return JSON.parse(fs.readFileSync(emojisMetadataPath, 'utf8'));
|
||||
}
|
||||
|
||||
public getIconData(): IconData {
|
||||
const iconsDir = path.join(this.assetsDir, 'icons');
|
||||
const iconsMetadataPath = path.join(iconsDir, 'icons.json');
|
||||
return JSON.parse(fs.readFileSync(iconsMetadataPath, 'utf8'));
|
||||
}
|
||||
|
||||
public async checkAssets(): Promise<void> {
|
||||
if (!this.shouldUpdateAssets()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateAssets();
|
||||
}
|
||||
|
||||
private async updateAssets(): Promise<void> {
|
||||
await this.updateEmojis();
|
||||
await this.updateIcons();
|
||||
|
||||
this.writeAssetsVersion();
|
||||
}
|
||||
|
||||
private async updateEmojis(): Promise<void> {
|
||||
const emojisZipPath = path.join(this.assetsDir, 'emojis.zip');
|
||||
const emojisDir = path.join(this.assetsDir, 'emojis');
|
||||
if (fs.existsSync(emojisDir)) {
|
||||
fs.rmSync(emojisDir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.mkdirSync(emojisDir, { recursive: true });
|
||||
await fs
|
||||
.createReadStream(emojisZipPath)
|
||||
.pipe(unzipper.Extract({ path: emojisDir }))
|
||||
.promise();
|
||||
}
|
||||
|
||||
private async updateIcons(): Promise<void> {
|
||||
const iconsZipPath = path.join(this.assetsDir, 'icons.zip');
|
||||
const iconsDir = path.join(this.assetsDir, 'icons');
|
||||
if (fs.existsSync(iconsDir)) {
|
||||
fs.rmSync(iconsDir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.mkdirSync(iconsDir, { recursive: true });
|
||||
await fs
|
||||
.createReadStream(iconsZipPath)
|
||||
.pipe(unzipper.Extract({ path: iconsDir }))
|
||||
.promise();
|
||||
}
|
||||
|
||||
private shouldUpdateAssets(): boolean {
|
||||
const assetsVersion = this.readAssetsVersion();
|
||||
if (!assetsVersion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
assetsVersion.emojis !== EMOJIS_VERSION ||
|
||||
assetsVersion.icons !== ICONS_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
private readAssetsVersion(): AssetsVersion | null {
|
||||
const assetsVersionPath = path.join(this.assetsDir, 'version.json');
|
||||
if (!fs.existsSync(assetsVersionPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(fs.readFileSync(assetsVersionPath, 'utf8'));
|
||||
}
|
||||
|
||||
private writeAssetsVersion(): void {
|
||||
const assetsVersionPath = path.join(this.assetsDir, 'version.json');
|
||||
fs.writeFileSync(
|
||||
assetsVersionPath,
|
||||
JSON.stringify({
|
||||
date: new Date().toISOString(),
|
||||
emojis: EMOJIS_VERSION,
|
||||
icons: ICONS_VERSION,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const assetManager = new AssetManager();
|
||||
33
apps/desktop/src/main/handlers/queries/emojis-get.ts
Normal file
33
apps/desktop/src/main/handlers/queries/emojis-get.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { assetManager } from '@/main/asset-manager';
|
||||
import {
|
||||
MutationChange,
|
||||
ChangeCheckResult,
|
||||
QueryHandler,
|
||||
QueryResult,
|
||||
} from '@/main/types';
|
||||
import { EmojisGetQueryInput } from '@/operations/queries/emojis-get';
|
||||
|
||||
export class EmojisGetQueryHandler
|
||||
implements QueryHandler<EmojisGetQueryInput>
|
||||
{
|
||||
public async handleQuery(
|
||||
_: EmojisGetQueryInput
|
||||
): Promise<QueryResult<EmojisGetQueryInput>> {
|
||||
const data = assetManager.getEmojiData();
|
||||
|
||||
return {
|
||||
output: data,
|
||||
state: {},
|
||||
};
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
_: MutationChange[],
|
||||
__: EmojisGetQueryInput,
|
||||
___: Record<string, any>
|
||||
): Promise<ChangeCheckResult<EmojisGetQueryInput>> {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
33
apps/desktop/src/main/handlers/queries/icons-get.ts
Normal file
33
apps/desktop/src/main/handlers/queries/icons-get.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { assetManager } from '@/main/asset-manager';
|
||||
import {
|
||||
MutationChange,
|
||||
ChangeCheckResult,
|
||||
QueryHandler,
|
||||
QueryResult,
|
||||
} from '@/main/types';
|
||||
import { IconsGetQueryInput } from '@/operations/queries/icons-get';
|
||||
|
||||
export class IconsGetQueryHandler implements QueryHandler<IconsGetQueryInput> {
|
||||
public async handleQuery(
|
||||
_: IconsGetQueryInput
|
||||
): Promise<QueryResult<IconsGetQueryInput>> {
|
||||
console.time('icons_get');
|
||||
const data = assetManager.getIconData();
|
||||
console.timeEnd('icons_get');
|
||||
|
||||
return {
|
||||
output: data,
|
||||
state: {},
|
||||
};
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
_: MutationChange[],
|
||||
__: IconsGetQueryInput,
|
||||
___: Record<string, any>
|
||||
): Promise<ChangeCheckResult<IconsGetQueryInput>> {
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import { ChatGetQueryHandler } from '@/main/handlers/queries/chat-get';
|
||||
import { FileListQueryHandler } from '@/main/handlers/queries/file-list';
|
||||
import { FileGetQueryHandler } from '@/main/handlers/queries/file-get';
|
||||
import { DocumentGetQueryHandler } from '@/main/handlers/queries/document-get';
|
||||
import { EmojisGetQueryHandler } from '@/main/handlers/queries/emojis-get';
|
||||
import { IconsGetQueryHandler } from '@/main/handlers/queries/icons-get';
|
||||
|
||||
type QueryHandlerMap = {
|
||||
[K in keyof QueryMap]: QueryHandler<QueryMap[K]['input']>;
|
||||
@@ -40,4 +42,6 @@ export const queryHandlerMap: QueryHandlerMap = {
|
||||
file_list: new FileListQueryHandler(),
|
||||
file_get: new FileGetQueryHandler(),
|
||||
document_get: new DocumentGetQueryHandler(),
|
||||
emojis_get: new EmojisGetQueryHandler(),
|
||||
icons_get: new IconsGetQueryHandler(),
|
||||
};
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
app,
|
||||
shell,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
protocol,
|
||||
net,
|
||||
dialog,
|
||||
} from 'electron';
|
||||
import { app, shell, BrowserWindow, ipcMain, protocol, dialog } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
|
||||
import { eventBus } from '@/lib/event-bus';
|
||||
@@ -19,12 +11,14 @@ import { mediator } from '@/main/mediator';
|
||||
import { FileMetadata } from '@/types/files';
|
||||
import { MutationInput, MutationMap } from '@/operations/mutations';
|
||||
import { QueryInput, QueryMap } from '@/operations/queries';
|
||||
import { assetManager } from '@/main/asset-manager';
|
||||
|
||||
let subscriptionId: string | null = null;
|
||||
const icon = join(__dirname, '../assets/icon.png');
|
||||
|
||||
const createWindow = async (): Promise<void> => {
|
||||
await databaseManager.init();
|
||||
assetManager.checkAssets();
|
||||
socketManager.init();
|
||||
synchronizer.init();
|
||||
|
||||
@@ -84,10 +78,7 @@ const createWindow = async (): Promise<void> => {
|
||||
|
||||
if (!protocol.isProtocolHandled('asset')) {
|
||||
protocol.handle('asset', (request) => {
|
||||
const url = request.url.replace('asset://', '');
|
||||
const filePath = join(__dirname, 'assets', url);
|
||||
const localFileUrl = `file://${filePath}`;
|
||||
return net.fetch(localFileUrl);
|
||||
return assetManager.handleAssetRequest(request);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
14
apps/desktop/src/operations/queries/emojis-get.ts
Normal file
14
apps/desktop/src/operations/queries/emojis-get.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { EmojiData } from '@/types/emojis';
|
||||
|
||||
export type EmojisGetQueryInput = {
|
||||
type: 'emojis_get';
|
||||
};
|
||||
|
||||
declare module '@/operations/queries' {
|
||||
interface QueryMap {
|
||||
emojis_get: {
|
||||
input: EmojisGetQueryInput;
|
||||
output: EmojiData;
|
||||
};
|
||||
}
|
||||
}
|
||||
14
apps/desktop/src/operations/queries/icons-get.ts
Normal file
14
apps/desktop/src/operations/queries/icons-get.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IconData } from '@/types/icons';
|
||||
|
||||
export type IconsGetQueryInput = {
|
||||
type: 'icons_get';
|
||||
};
|
||||
|
||||
declare module '@/operations/queries' {
|
||||
interface QueryMap {
|
||||
icons_get: {
|
||||
input: IconsGetQueryInput;
|
||||
output: IconData;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ export const AvatarPicker = ({ onPick }: AvatarPickerProps) => {
|
||||
</TabsList>
|
||||
<TabsContent value="emojis">
|
||||
<EmojiPicker
|
||||
onPick={(emoji) => {
|
||||
onPick(emoji.id);
|
||||
onPick={(emoji, skinTone) => {
|
||||
onPick(emoji.skins[skinTone].id);
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { EmojiPickerRowData } from '@/lib/emojis';
|
||||
import { EmojiPickerRowData } from '@/types/emojis';
|
||||
import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item';
|
||||
|
||||
interface EmojiPickerBrowserRowProps {
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import { EmojiPickerRowData, categories, emojis } from '@/lib/emojis';
|
||||
import { EmojiPickerRowData } from '@/types/emojis';
|
||||
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';
|
||||
|
||||
const emojisPerRow = 10;
|
||||
|
||||
export const EmojiPickerBrowser = () => {
|
||||
const { data } = useEmojiPicker();
|
||||
|
||||
const rowDataArray = React.useMemo<EmojiPickerRowData[]>(() => {
|
||||
const rows: EmojiPickerRowData[] = [];
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
for (let i = 0; i < data.categories.length; i++) {
|
||||
const category = data.categories[i];
|
||||
// Add the category label
|
||||
rows.push({
|
||||
type: 'label',
|
||||
category: categories[i],
|
||||
category: category.name,
|
||||
});
|
||||
|
||||
const numEmojis = emojis[i].length;
|
||||
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 emojisInRow = emojis[i].slice(start, end);
|
||||
const emojisIds = category.emojis.slice(start, end);
|
||||
const emojisInRow = emojisIds.map((id) => data.emojis[id]);
|
||||
|
||||
rows.push({
|
||||
type: 'emoji',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Emoji } from '@/lib/emojis';
|
||||
import { Emoji } from '@/types/emojis';
|
||||
import { EmojiElement } from '@/renderer/components/emojis/emoji-element';
|
||||
import { useEmojiPicker } from '@/renderer/contexts/emoji-picker';
|
||||
|
||||
@@ -9,14 +9,9 @@ interface EmojiPickerItemProps {
|
||||
export const EmojiPickerItem = ({ emoji }: EmojiPickerItemProps) => {
|
||||
const { skinTone, onPick: onEmojiClick } = useEmojiPicker();
|
||||
|
||||
let id = emoji.id;
|
||||
if (
|
||||
emoji.skins &&
|
||||
emoji.skins.length > 0 &&
|
||||
skinTone !== 0 &&
|
||||
skinTone < emoji.skins.length
|
||||
) {
|
||||
id = emoji.skins[skinTone];
|
||||
let id = emoji.skins[0].id;
|
||||
if (skinTone !== 0 && skinTone < emoji.skins.length) {
|
||||
id = emoji.skins[skinTone].id;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import { searchEmojis } from '@/lib/emojis';
|
||||
import { EmojiPickerItem } from '@/renderer/components/emojis/emoji-picker-item';
|
||||
import { useEmojiPicker } from '@/renderer/contexts/emoji-picker';
|
||||
|
||||
interface EmojiPickerSearchProps {
|
||||
query: string;
|
||||
}
|
||||
|
||||
export const EmojiPickerSearch = ({ query }: EmojiPickerSearchProps) => {
|
||||
const { data } = useEmojiPicker();
|
||||
|
||||
const filteredEmojis = React.useMemo(() => {
|
||||
return searchEmojis(query);
|
||||
return searchEmojis(query, data);
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
import React from 'react';
|
||||
import { EmojiSkinToneSelector } from '@/renderer/components/emojis/emoji-skin-tone-selector';
|
||||
import { Emoji } from '@/lib/emojis';
|
||||
import { Emoji } from '@/types/emojis';
|
||||
import { EmojiPickerContext } from '@/renderer/contexts/emoji-picker';
|
||||
import { EmojiPickerBrowser } from '@/renderer/components/emojis/emoji-picker-browser';
|
||||
import { EmojiPickerSearch } from '@/renderer/components/emojis/emoji-picker-search';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
|
||||
interface EmojiPickerProps {
|
||||
onPick: (emoji: Emoji) => void;
|
||||
onPick: (emoji: Emoji, skinTone: number) => void;
|
||||
}
|
||||
|
||||
export const EmojiPicker = ({ onPick }: EmojiPickerProps) => {
|
||||
const [query, setQuery] = React.useState('');
|
||||
const [skinTone, setSkinTone] = React.useState(0);
|
||||
const { data, isPending } = useQuery({ type: 'emojis_get' });
|
||||
|
||||
if (isPending || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EmojiPickerContext.Provider value={{ skinTone, onPick }}>
|
||||
<EmojiPickerContext.Provider
|
||||
value={{ data, skinTone, onPick: (emoji) => onPick(emoji, skinTone) }}
|
||||
>
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<input
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { IconPickerRowData } from '@/lib/icons';
|
||||
import { IconPickerRowData } from '@/types/icons';
|
||||
import { IconPickerItem } from '@/renderer/components/icons/icon-picker-item';
|
||||
|
||||
interface IconPickerBrowserRowProps {
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
import React from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { IconPickerRowData, iconCategories, icons } from '@/lib/icons';
|
||||
import { IconPickerRowData } from '@/types/icons';
|
||||
import { IconPickerBrowserRow } from '@/renderer/components/icons/icon-picker-browser-row';
|
||||
import { useIconPicker } from '@/renderer/contexts/icon-picker';
|
||||
|
||||
const iconsPerRow = 10;
|
||||
|
||||
export const IconPickerBrowser = () => {
|
||||
const { data } = useIconPicker();
|
||||
const rowDataArray = React.useMemo<IconPickerRowData[]>(() => {
|
||||
const rows: IconPickerRowData[] = [];
|
||||
|
||||
for (let i = 0; i < iconCategories.length; i++) {
|
||||
for (let i = 0; i < data.categories.length; i++) {
|
||||
const category = data.categories[i];
|
||||
// Add the category label
|
||||
rows.push({
|
||||
type: 'label',
|
||||
category: iconCategories[i],
|
||||
category: category.name,
|
||||
});
|
||||
|
||||
const numIcons = icons[i].length;
|
||||
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 iconsInRow = icons[i].slice(start, end);
|
||||
const iconIds = category.icons.slice(start, end);
|
||||
const iconsInRow = iconIds.map((id) => data.icons[id]);
|
||||
|
||||
rows.push({
|
||||
type: 'icon',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Icon } from '@/lib/icons';
|
||||
import { Icon } from '@/types/icons';
|
||||
import { useIconPicker } from '@/renderer/contexts/icon-picker';
|
||||
import { IconElement } from '@/renderer/components/icons/icon-element';
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import { IconPickerItem } from '@/renderer/components/icons/icon-picker-item';
|
||||
import { searchIcons } from '@/lib/icons';
|
||||
import { useIconPicker } from '@/renderer/contexts/icon-picker';
|
||||
|
||||
interface IconPickerSearchProps {
|
||||
query: string;
|
||||
}
|
||||
|
||||
export const IconPickerSearch = ({ query }: IconPickerSearchProps) => {
|
||||
const { data } = useIconPicker();
|
||||
|
||||
const filteredIcons = React.useMemo(() => {
|
||||
return searchIcons(query);
|
||||
return searchIcons(query, data);
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@/lib/icons';
|
||||
import { Icon } from '@/types/icons';
|
||||
import { IconPickerContext } from '@/renderer/contexts/icon-picker';
|
||||
import { IconPickerSearch } from '@/renderer/components/icons/icon-picker-search';
|
||||
import { IconPickerBrowser } from '@/renderer/components/icons/icon-picker-browser';
|
||||
import { useQuery } from '@/renderer/hooks/use-query';
|
||||
|
||||
interface IconPickerProps {
|
||||
onPick: (icon: Icon) => void;
|
||||
@@ -10,9 +11,14 @@ interface IconPickerProps {
|
||||
|
||||
export const IconPicker = ({ onPick }: IconPickerProps) => {
|
||||
const [query, setQuery] = React.useState('');
|
||||
const { data, isPending } = useQuery({ type: 'icons_get' });
|
||||
|
||||
if (isPending || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<IconPickerContext.Provider value={{ onPick }}>
|
||||
<IconPickerContext.Provider value={{ data, onPick }}>
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -23,8 +23,8 @@ export const MessageReactionCreatePopover = ({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-max p-0" align="end">
|
||||
<EmojiPicker
|
||||
onPick={(emoji) => {
|
||||
onReactionClick(emoji.id);
|
||||
onPick={(emoji, skinTone) => {
|
||||
onReactionClick(emoji.skins[skinTone].id);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Emoji } from '@/lib/emojis';
|
||||
import { EmojiData, Emoji } from '@/types/emojis';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface EmojiPickerContextProps {
|
||||
data: EmojiData;
|
||||
skinTone: number;
|
||||
onPick: (emoji: Emoji) => void;
|
||||
}
|
||||
|
||||
export const EmojiPickerContext = createContext<EmojiPickerContextProps>(
|
||||
{} as EmojiPickerContextProps,
|
||||
{} as EmojiPickerContextProps
|
||||
);
|
||||
|
||||
export const useEmojiPicker = () => useContext(EmojiPickerContext);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Icon } from '@/lib/icons';
|
||||
import { IconData, Icon } from '@/types/icons';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface IconPickerContextProps {
|
||||
data: IconData;
|
||||
onPick: (icon: Icon) => void;
|
||||
}
|
||||
|
||||
export const IconPickerContext = createContext<IconPickerContextProps>(
|
||||
{} as IconPickerContextProps,
|
||||
{} as IconPickerContextProps
|
||||
);
|
||||
|
||||
export const useIconPicker = () => useContext(IconPickerContext);
|
||||
|
||||
36
apps/desktop/src/types/emojis.ts
Normal file
36
apps/desktop/src/types/emojis.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export type EmojiData = {
|
||||
categories: EmojiCategory[];
|
||||
emojis: Record<string, Emoji>;
|
||||
};
|
||||
|
||||
export type Emoji = {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
tags: string[];
|
||||
emoticons: string[] | undefined;
|
||||
skins: EmojiSkin[];
|
||||
};
|
||||
|
||||
export type EmojiCategory = {
|
||||
id: string;
|
||||
name: string;
|
||||
emojis: string[];
|
||||
};
|
||||
|
||||
export type EmojiSkin = {
|
||||
id: string;
|
||||
unified: string;
|
||||
};
|
||||
|
||||
export type EmojiPickerRowData = EmojiPickerLabelRow | EmojiPickerEmojiRow;
|
||||
|
||||
export type EmojiPickerLabelRow = {
|
||||
type: 'label';
|
||||
category: string;
|
||||
};
|
||||
|
||||
export type EmojiPickerEmojiRow = {
|
||||
type: 'emoji';
|
||||
emojis: Emoji[];
|
||||
};
|
||||
29
apps/desktop/src/types/icons.ts
Normal file
29
apps/desktop/src/types/icons.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type IconData = {
|
||||
categories: IconCategory[];
|
||||
icons: Record<string, Icon>;
|
||||
};
|
||||
|
||||
export type Icon = {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export type IconCategory = {
|
||||
id: string;
|
||||
name: string;
|
||||
icons: string[];
|
||||
};
|
||||
|
||||
export type IconPickerRowData = IconPickerLabelRow | IconPickerEmojiRow;
|
||||
|
||||
export type IconPickerLabelRow = {
|
||||
type: 'label';
|
||||
category: string;
|
||||
};
|
||||
|
||||
export type IconPickerEmojiRow = {
|
||||
type: 'icon';
|
||||
icons: Icon[];
|
||||
};
|
||||
Reference in New Issue
Block a user