refactor(aim): remove emoji buddy icon presets
The emoji-on-color presets were not period accurate. Everyone now shows the AIM running man in the buddy list, IM header and Get Info window. Drops users.buddyIcon from the schema and updateProfile.
- commit
- 7e3029fd5345f65c7bf9762246595deb239f118e
- author
- Kit · · 2026-09-27 14:06 UTC
- parent
- e2dff43
- signature
- SSH Unverified
SHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6TkThe key is unknown
10 files · +9 -125
M src/convex/aim.ts +2 -9
| @@ -39,7 +39,6 @@ export function toPublicUser(user: Doc<'users'>) { | |||
| 39 | 39 | avatarUrl: user.avatarUrl, | |
| 40 | 40 | lastSeen: user.lastSeen, | |
| 41 | 41 | awayMessage: status === 'away' ? user.awayMessage : undefined, | |
| 42 | - | buddyIcon: user.buddyIcon, | |
| 43 | 42 | createdAt: user.createdAt | |
| 44 | 43 | }; | |
| 45 | 44 | } | |
| @@ -176,8 +175,7 @@ export const getRoomMessages = authQuery({ | |||
| 176 | 175 | id: sender._id, | |
| 177 | 176 | nickname: sender.nickname, | |
| 178 | 177 | status: computeEffectiveStatus(sender.status, sender.lastSeen), | |
| 179 | - | avatarUrl: sender.avatarUrl, | |
| 180 | - | buddyIcon: sender.buddyIcon | |
| 178 | + | avatarUrl: sender.avatarUrl | |
| 181 | 179 | } | |
| 182 | 180 | : null | |
| 183 | 181 | }; | |
| @@ -214,7 +212,6 @@ export const getMe = authQuery({ | |||
| 214 | 212 | status: u.status, | |
| 215 | 213 | awayMessage: u.awayMessage, | |
| 216 | 214 | profile: u.profile, | |
| 217 | - | buddyIcon: u.buddyIcon, | |
| 218 | 215 | createdAt: u.createdAt | |
| 219 | 216 | }; | |
| 220 | 217 | } | |
| @@ -240,15 +237,11 @@ export const setAwayMessage = authMutation({ | |||
| 240 | 237 | }); | |
| 241 | 238 | ||
| 242 | 239 | export const updateProfile = authMutation({ | |
| 243 | - | args: { profile: v.optional(v.string()), buddyIcon: v.optional(v.string()) }, | |
| 240 | + | args: { profile: v.optional(v.string()) }, | |
| 244 | 241 | handler: async (ctx, args) => { | |
| 245 | 242 | const patch: Partial<Doc<'users'>> = {}; | |
| 246 | 243 | if (args.profile !== undefined) | |
| 247 | 244 | patch.profile = args.profile.trim().slice(0, MAX_PROFILE_LENGTH); | |
| 248 | - | if (args.buddyIcon !== undefined) { | |
| 249 | - | if (!/^[a-z0-9-]{1,32}$/.test(args.buddyIcon)) throw new Error('Invalid buddy icon'); | |
| 250 | - | patch.buddyIcon = args.buddyIcon; | |
| 251 | - | } | |
| 252 | 245 | await ctx.db.patch(ctx.user._id, patch); | |
| 253 | 246 | return { success: true }; | |
| 254 | 247 | } | |
| @@ -39,7 +39,6 @@ export function toPublicUser(user: Doc<'users'>) { | |||||
| 39 | avatarUrl: user.avatarUrl, | 39 | avatarUrl: user.avatarUrl, | ||
| 40 | lastSeen: user.lastSeen, | 40 | lastSeen: user.lastSeen, | ||
| 41 | awayMessage: status === 'away' ? user.awayMessage : undefined, | 41 | awayMessage: status === 'away' ? user.awayMessage : undefined, | ||
| 42 | - | buddyIcon: user.buddyIcon, | |||
| 43 | createdAt: user.createdAt | 42 | createdAt: user.createdAt | ||
| 44 | }; | 43 | }; | ||
| 45 | } | 44 | } | ||
| @@ -176,8 +175,7 @@ export const getRoomMessages = authQuery({ | |||||
| 176 | id: sender._id, | 175 | id: sender._id, | ||
| 177 | nickname: sender.nickname, | 176 | nickname: sender.nickname, | ||
| 178 | status: computeEffectiveStatus(sender.status, sender.lastSeen), | 177 | status: computeEffectiveStatus(sender.status, sender.lastSeen), | ||
| 179 | - | avatarUrl: sender.avatarUrl, | 178 | + | avatarUrl: sender.avatarUrl |
| 180 | - | buddyIcon: sender.buddyIcon | |||
| 181 | } | 179 | } | ||
| 182 | : null | 180 | : null | ||
| 183 | }; | 181 | }; | ||
| @@ -214,7 +212,6 @@ export const getMe = authQuery({ | |||||
| 214 | status: u.status, | 212 | status: u.status, | ||
| 215 | awayMessage: u.awayMessage, | 213 | awayMessage: u.awayMessage, | ||
| 216 | profile: u.profile, | 214 | profile: u.profile, | ||
| 217 | - | buddyIcon: u.buddyIcon, | |||
| 218 | createdAt: u.createdAt | 215 | createdAt: u.createdAt | ||
| 219 | }; | 216 | }; | ||
| 220 | } | 217 | } | ||
| @@ -240,15 +237,11 @@ export const setAwayMessage = authMutation({ | |||||
| 240 | }); | 237 | }); | ||
| 241 | 238 | ||||
| 242 | export const updateProfile = authMutation({ | 239 | export const updateProfile = authMutation({ | ||
| 243 | - | args: { profile: v.optional(v.string()), buddyIcon: v.optional(v.string()) }, | 240 | + | args: { profile: v.optional(v.string()) }, |
| 244 | handler: async (ctx, args) => { | 241 | handler: async (ctx, args) => { | ||
| 245 | const patch: Partial<Doc<'users'>> = {}; | 242 | const patch: Partial<Doc<'users'>> = {}; | ||
| 246 | if (args.profile !== undefined) | 243 | if (args.profile !== undefined) | ||
| 247 | patch.profile = args.profile.trim().slice(0, MAX_PROFILE_LENGTH); | 244 | patch.profile = args.profile.trim().slice(0, MAX_PROFILE_LENGTH); | ||
| 248 | - | if (args.buddyIcon !== undefined) { | |||
| 249 | - | if (!/^[a-z0-9-]{1,32}$/.test(args.buddyIcon)) throw new Error('Invalid buddy icon'); | |||
| 250 | - | patch.buddyIcon = args.buddyIcon; | |||
| 251 | - | } | |||
| 252 | await ctx.db.patch(ctx.user._id, patch); | 245 | await ctx.db.patch(ctx.user._id, patch); | ||
| 253 | return { success: true }; | 246 | return { success: true }; | ||
| 254 | } | 247 | } | ||
M src/convex/schema.ts +1 -2
| @@ -12,8 +12,7 @@ export default defineSchema({ | |||
| 12 | 12 | lastReadMentionTimestamp: v.optional(v.number()), // Timestamp of last read mention | |
| 13 | 13 | // AIM profile | |
| 14 | 14 | awayMessage: v.optional(v.string()), // Shown while status === 'away' | |
| 15 | - | profile: v.optional(v.string()), // Free text "Get Info" profile | |
| 16 | - | buddyIcon: v.optional(v.string()) // Preset icon id (see $lib/aim/buddy-icons) | |
| 15 | + | profile: v.optional(v.string()) // Free text "Get Info" profile | |
| 17 | 16 | }) | |
| 18 | 17 | .index('by_nickname', ['nickname']) | |
| 19 | 18 | .index('by_status', ['status']), | |
| @@ -12,8 +12,7 @@ export default defineSchema({ | |||||
| 12 | lastReadMentionTimestamp: v.optional(v.number()), // Timestamp of last read mention | 12 | lastReadMentionTimestamp: v.optional(v.number()), // Timestamp of last read mention | ||
| 13 | // AIM profile | 13 | // AIM profile | ||
| 14 | awayMessage: v.optional(v.string()), // Shown while status === 'away' | 14 | awayMessage: v.optional(v.string()), // Shown while status === 'away' | ||
| 15 | - | profile: v.optional(v.string()), // Free text "Get Info" profile | 15 | + | profile: v.optional(v.string()) // Free text "Get Info" profile |
| 16 | - | buddyIcon: v.optional(v.string()) // Preset icon id (see $lib/aim/buddy-icons) | |||
| 17 | }) | 16 | }) | ||
| 18 | .index('by_nickname', ['nickname']) | 17 | .index('by_nickname', ['nickname']) | ||
| 19 | .index('by_status', ['status']), | 18 | .index('by_status', ['status']), | ||
D src/lib/aim/buddy-icons.ts +0 -35
| @@ -1,35 +0,0 @@ | |||
| 1 | - | /** | |
| 2 | - | * Preset buddy icons (48x48 in the original AIM). Rendered as emoji on a | |
| 3 | - | * coloured tile so no image assets are needed. | |
| 4 | - | */ | |
| 5 | - | export interface BuddyIconPreset { | |
| 6 | - | id: string; | |
| 7 | - | emoji: string; | |
| 8 | - | label: string; | |
| 9 | - | bg: string; | |
| 10 | - | } | |
| 11 | - | ||
| 12 | - | export const BUDDY_ICONS: BuddyIconPreset[] = [ | |
| 13 | - | { id: 'runner', emoji: '🏃', label: 'Running man', bg: '#f6c700' }, | |
| 14 | - | { id: 'smiley', emoji: '🙂', label: 'Smiley', bg: '#ffe680' }, | |
| 15 | - | { id: 'cool', emoji: '😎', label: 'Cool', bg: '#8fd3ff' }, | |
| 16 | - | { id: 'cat', emoji: '🐱', label: 'Chat', bg: '#ffd1dc' }, | |
| 17 | - | { id: 'dog', emoji: '🐶', label: 'Chien', bg: '#d9c3a5' }, | |
| 18 | - | { id: 'alien', emoji: '👽', label: 'Alien', bg: '#b6ffb0' }, | |
| 19 | - | { id: 'skull', emoji: '💀', label: 'Crâne', bg: '#dcdcdc' }, | |
| 20 | - | { id: 'fire', emoji: '🔥', label: 'Feu', bg: '#ffb27a' }, | |
| 21 | - | { id: 'star', emoji: '⭐', label: 'Étoile', bg: '#3a4fbf' }, | |
| 22 | - | { id: 'heart', emoji: '💜', label: 'Cœur', bg: '#f7c6ff' }, | |
| 23 | - | { id: 'music', emoji: '🎵', label: 'Musique', bg: '#c4f0ff' }, | |
| 24 | - | { id: 'game', emoji: '🎮', label: 'Jeux', bg: '#c9ffd6' }, | |
| 25 | - | { id: 'pizza', emoji: '🍕', label: 'Pizza', bg: '#ffe0b3' }, | |
| 26 | - | { id: 'rocket', emoji: '🚀', label: 'Fusée', bg: '#1f2a5a' }, | |
| 27 | - | { id: 'ghost', emoji: '👻', label: 'Fantôme', bg: '#eaeaff' }, | |
| 28 | - | { id: 'robot', emoji: '🤖', label: 'Robot', bg: '#cfd8dc' } | |
| 29 | - | ]; | |
| 30 | - | ||
| 31 | - | export const DEFAULT_BUDDY_ICON = BUDDY_ICONS[0]; | |
| 32 | - | ||
| 33 | - | export function getBuddyIcon(id: string | null | undefined): BuddyIconPreset { | |
| 34 | - | return BUDDY_ICONS.find((i) => i.id === id) ?? DEFAULT_BUDDY_ICON; | |
| 35 | - | } | |
| @@ -1,35 +0,0 @@ | |||||
| 1 | - | /** | |||
| 2 | - | * Preset buddy icons (48x48 in the original AIM). Rendered as emoji on a | |||
| 3 | - | * coloured tile so no image assets are needed. | |||
| 4 | - | */ | |||
| 5 | - | export interface BuddyIconPreset { | |||
| 6 | - | id: string; | |||
| 7 | - | emoji: string; | |||
| 8 | - | label: string; | |||
| 9 | - | bg: string; | |||
| 10 | - | } | |||
| 11 | - | ||||
| 12 | - | export const BUDDY_ICONS: BuddyIconPreset[] = [ | |||
| 13 | - | { id: 'runner', emoji: '🏃', label: 'Running man', bg: '#f6c700' }, | |||
| 14 | - | { id: 'smiley', emoji: '🙂', label: 'Smiley', bg: '#ffe680' }, | |||
| 15 | - | { id: 'cool', emoji: '😎', label: 'Cool', bg: '#8fd3ff' }, | |||
| 16 | - | { id: 'cat', emoji: '🐱', label: 'Chat', bg: '#ffd1dc' }, | |||
| 17 | - | { id: 'dog', emoji: '🐶', label: 'Chien', bg: '#d9c3a5' }, | |||
| 18 | - | { id: 'alien', emoji: '👽', label: 'Alien', bg: '#b6ffb0' }, | |||
| 19 | - | { id: 'skull', emoji: '💀', label: 'Crâne', bg: '#dcdcdc' }, | |||
| 20 | - | { id: 'fire', emoji: '🔥', label: 'Feu', bg: '#ffb27a' }, | |||
| 21 | - | { id: 'star', emoji: '⭐', label: 'Étoile', bg: '#3a4fbf' }, | |||
| 22 | - | { id: 'heart', emoji: '💜', label: 'Cœur', bg: '#f7c6ff' }, | |||
| 23 | - | { id: 'music', emoji: '🎵', label: 'Musique', bg: '#c4f0ff' }, | |||
| 24 | - | { id: 'game', emoji: '🎮', label: 'Jeux', bg: '#c9ffd6' }, | |||
| 25 | - | { id: 'pizza', emoji: '🍕', label: 'Pizza', bg: '#ffe0b3' }, | |||
| 26 | - | { id: 'rocket', emoji: '🚀', label: 'Fusée', bg: '#1f2a5a' }, | |||
| 27 | - | { id: 'ghost', emoji: '👻', label: 'Fantôme', bg: '#eaeaff' }, | |||
| 28 | - | { id: 'robot', emoji: '🤖', label: 'Robot', bg: '#cfd8dc' } | |||
| 29 | - | ]; | |||
| 30 | - | ||||
| 31 | - | export const DEFAULT_BUDDY_ICON = BUDDY_ICONS[0]; | |||
| 32 | - | ||||
| 33 | - | export function getBuddyIcon(id: string | null | undefined): BuddyIconPreset { | |||
| 34 | - | return BUDDY_ICONS.find((i) => i.id === id) ?? DEFAULT_BUDDY_ICON; | |||
| 35 | - | } | |||
M src/lib/aim/client.ts +1 -1
| @@ -72,7 +72,7 @@ export function useAimClient() { | |||
| 72 | 72 | const { client, hash } = requireAuth(); | |
| 73 | 73 | return client.mutation(api.aim.setAwayMessage, { tokenHash: hash, awayMessage }); | |
| 74 | 74 | }, | |
| 75 | - | async updateProfile(patch: { profile?: string; buddyIcon?: string }) { | |
| 75 | + | async updateProfile(patch: { profile?: string }) { | |
| 76 | 76 | const { client, hash } = requireAuth(); | |
| 77 | 77 | return client.mutation(api.aim.updateProfile, { tokenHash: hash, ...patch }); | |
| 78 | 78 | }, | |
| @@ -72,7 +72,7 @@ export function useAimClient() { | |||||
| 72 | const { client, hash } = requireAuth(); | 72 | const { client, hash } = requireAuth(); | ||
| 73 | return client.mutation(api.aim.setAwayMessage, { tokenHash: hash, awayMessage }); | 73 | return client.mutation(api.aim.setAwayMessage, { tokenHash: hash, awayMessage }); | ||
| 74 | }, | 74 | }, | ||
| 75 | - | async updateProfile(patch: { profile?: string; buddyIcon?: string }) { | 75 | + | async updateProfile(patch: { profile?: string }) { |
| 76 | const { client, hash } = requireAuth(); | 76 | const { client, hash } = requireAuth(); | ||
| 77 | return client.mutation(api.aim.updateProfile, { tokenHash: hash, ...patch }); | 77 | return client.mutation(api.aim.updateProfile, { tokenHash: hash, ...patch }); | ||
| 78 | }, | 78 | }, | ||
M src/lib/components/chat-room.svelte +1 -2
| @@ -79,8 +79,7 @@ | |||
| 79 | 79 | status: u.status as SafeUser['status'], | |
| 80 | 80 | avatarUrl: u.avatarUrl ?? null, | |
| 81 | 81 | lastSeen: u.lastSeen ?? null, | |
| 82 | - | awayMessage: u.awayMessage, | |
| 83 | - | buddyIcon: u.buddyIcon | |
| 82 | + | awayMessage: u.awayMessage | |
| 84 | 83 | })) | |
| 85 | 84 | .sort((a, b) => { | |
| 86 | 85 | const statusOrder: Record<string, number> = { | |
| @@ -79,8 +79,7 @@ | |||||
| 79 | status: u.status as SafeUser['status'], | 79 | status: u.status as SafeUser['status'], | ||
| 80 | avatarUrl: u.avatarUrl ?? null, | 80 | avatarUrl: u.avatarUrl ?? null, | ||
| 81 | lastSeen: u.lastSeen ?? null, | 81 | lastSeen: u.lastSeen ?? null, | ||
| 82 | - | awayMessage: u.awayMessage, | 82 | + | awayMessage: u.awayMessage |
| 83 | - | buddyIcon: u.buddyIcon | |||
| 84 | })) | 83 | })) | ||
| 85 | .sort((a, b) => { | 84 | .sort((a, b) => { | ||
| 86 | const statusOrder: Record<string, number> = { | 85 | const statusOrder: Record<string, number> = { | ||
M src/lib/components/desktop/buddy-list.svelte +1 -10
| @@ -6,7 +6,6 @@ | |||
| 6 | 6 | import type { Id } from '../../../convex/_generated/dataModel'; | |
| 7 | 7 | import type { Buddy, AimStatus } from '$lib/types/aim'; | |
| 8 | 8 | import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim'; | |
| 9 | - | import { getBuddyIcon } from '$lib/aim/buddy-icons'; | |
| 10 | 9 | import { playSound } from '$lib/aim/sounds'; | |
| 11 | 10 | import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client'; | |
| 12 | 11 | import { chatState } from '$lib/states/chat.svelte'; | |
| @@ -327,9 +326,7 @@ | |||
| 327 | 326 | <div class="buddy-content"> | |
| 328 | 327 | <header class="identity"> | |
| 329 | 328 | {#if me} | |
| 330 | - | <div class="icon" style:background={getBuddyIcon(meQuery?.data?.buddyIcon).bg}> | |
| 331 | - | {getBuddyIcon(meQuery?.data?.buddyIcon).emoji} | |
| 332 | - | </div> | |
| 329 | + | <img class="icon" src="/aim/running-man-32.png" alt="" width="32" height="32" /> | |
| 333 | 330 | <div class="identity-text"> | |
| 334 | 331 | <strong | |
| 335 | 332 | ><img src="/aim/running-man-16.png" alt="" width="16" height="16" /> | |
| @@ -523,13 +520,7 @@ | |||
| 523 | 520 | margin: 3px 0 0; | |
| 524 | 521 | } | |
| 525 | 522 | .icon { | |
| 526 | - | display: grid; | |
| 527 | - | place-items: center; | |
| 528 | - | width: 32px; | |
| 529 | - | height: 32px; | |
| 530 | 523 | flex: none; | |
| 531 | - | border: 1px solid #9c8700; | |
| 532 | - | font-size: 22px; | |
| 533 | 524 | } | |
| 534 | 525 | .identity-text { | |
| 535 | 526 | min-width: 0; | |
| @@ -6,7 +6,6 @@ | |||||
| 6 | import type { Id } from '../../../convex/_generated/dataModel'; | 6 | import type { Id } from '../../../convex/_generated/dataModel'; | ||
| 7 | import type { Buddy, AimStatus } from '$lib/types/aim'; | 7 | import type { Buddy, AimStatus } from '$lib/types/aim'; | ||
| 8 | import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim'; | 8 | import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim'; | ||
| 9 | - | import { getBuddyIcon } from '$lib/aim/buddy-icons'; | |||
| 10 | import { playSound } from '$lib/aim/sounds'; | 9 | import { playSound } from '$lib/aim/sounds'; | ||
| 11 | import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client'; | 10 | import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client'; | ||
| 12 | import { chatState } from '$lib/states/chat.svelte'; | 11 | import { chatState } from '$lib/states/chat.svelte'; | ||
| @@ -327,9 +326,7 @@ | |||||
| 327 | <div class="buddy-content"> | 326 | <div class="buddy-content"> | ||
| 328 | <header class="identity"> | 327 | <header class="identity"> | ||
| 329 | {#if me} | 328 | {#if me} | ||
| 330 | - | <div class="icon" style:background={getBuddyIcon(meQuery?.data?.buddyIcon).bg}> | 329 | + | <img class="icon" src="/aim/running-man-32.png" alt="" width="32" height="32" /> |
| 331 | - | {getBuddyIcon(meQuery?.data?.buddyIcon).emoji} | |||
| 332 | - | </div> | |||
| 333 | <div class="identity-text"> | 330 | <div class="identity-text"> | ||
| 334 | <strong | 331 | <strong | ||
| 335 | ><img src="/aim/running-man-16.png" alt="" width="16" height="16" /> | 332 | ><img src="/aim/running-man-16.png" alt="" width="16" height="16" /> | ||
| @@ -523,13 +520,7 @@ | |||||
| 523 | margin: 3px 0 0; | 520 | margin: 3px 0 0; | ||
| 524 | } | 521 | } | ||
| 525 | .icon { | 522 | .icon { | ||
| 526 | - | display: grid; | |||
| 527 | - | place-items: center; | |||
| 528 | - | width: 32px; | |||
| 529 | - | height: 32px; | |||
| 530 | flex: none; | 523 | flex: none; | ||
| 531 | - | border: 1px solid #9c8700; | |||
| 532 | - | font-size: 22px; | |||
| 533 | } | 524 | } | ||
| 534 | .identity-text { | 525 | .identity-text { | ||
| 535 | min-width: 0; | 526 | min-width: 0; | ||
M src/lib/components/desktop/im-window.svelte +1 -16
| @@ -6,7 +6,6 @@ | |||
| 6 | 6 | import type { EnrichedMessage, SafeUser } from '$lib/types/chat'; | |
| 7 | 7 | import { authQuery, useAimClient } from '$lib/aim/client'; | |
| 8 | 8 | import { playSound } from '$lib/aim/sounds'; | |
| 9 | - | import { getBuddyIcon } from '$lib/aim/buddy-icons'; | |
| 10 | 9 | import { STATUS_LABELS_FR, type AimStatus } from '$lib/types/aim'; | |
| 11 | 10 | import { chatState } from '$lib/states/chat.svelte'; | |
| 12 | 11 | import { desktop } from '$lib/states/desktop.svelte'; | |
| @@ -31,7 +30,6 @@ | |||
| 31 | 30 | const profileQuery = useQuery(convexApi.aim.getUserProfile, () => ({ userId: otherUserId })); | |
| 32 | 31 | const currentUser = $derived(chatState.getCurrentUser()); | |
| 33 | 32 | const profile = $derived(profileQuery.data); | |
| 34 | - | const buddyIcon = $derived(getBuddyIcon(profile?.buddyIcon)); | |
| 35 | 33 | const status = $derived((profile?.status ?? 'offline') as AimStatus); | |
| 36 | 34 | const messages = $derived.by<EnrichedMessage[]>(() => | |
| 37 | 35 | (messagesQuery?.data ?? []).map((msg) => ({ | |
| @@ -262,14 +260,7 @@ | |||
| 262 | 260 | {/snippet} | |
| 263 | 261 | <div class="im-body"> | |
| 264 | 262 | <div class="header"> | |
| 265 | - | <div | |
| 266 | - | class="buddy-icon" | |
| 267 | - | style:background={buddyIcon.bg} | |
| 268 | - | role="img" | |
| 269 | - | aria-label={buddyIcon.label} | |
| 270 | - | > | |
| 271 | - | {buddyIcon.emoji} | |
| 272 | - | </div> | |
| 263 | + | <img class="buddy-icon" src="/aim/running-man-48.png" alt="" width="48" height="48" /> | |
| 273 | 264 | <div class="identity"> | |
| 274 | 265 | <strong>{profile?.nickname ?? otherNickname}</strong><span | |
| 275 | 266 | ><img src={`/aim/status-${status}.svg`} alt="" width="14" height="14" /> | |
| @@ -380,12 +371,6 @@ | |||
| 380 | 371 | padding: 2px 4px; | |
| 381 | 372 | } | |
| 382 | 373 | .buddy-icon { | |
| 383 | - | width: 48px; | |
| 384 | - | height: 48px; | |
| 385 | - | display: grid; | |
| 386 | - | place-items: center; | |
| 387 | - | font-size: 29px; | |
| 388 | - | border: 1px solid #aca899; | |
| 389 | 374 | flex: none; | |
| 390 | 375 | } | |
| 391 | 376 | .identity { | |
| @@ -6,7 +6,6 @@ | |||||
| 6 | import type { EnrichedMessage, SafeUser } from '$lib/types/chat'; | 6 | import type { EnrichedMessage, SafeUser } from '$lib/types/chat'; | ||
| 7 | import { authQuery, useAimClient } from '$lib/aim/client'; | 7 | import { authQuery, useAimClient } from '$lib/aim/client'; | ||
| 8 | import { playSound } from '$lib/aim/sounds'; | 8 | import { playSound } from '$lib/aim/sounds'; | ||
| 9 | - | import { getBuddyIcon } from '$lib/aim/buddy-icons'; | |||
| 10 | import { STATUS_LABELS_FR, type AimStatus } from '$lib/types/aim'; | 9 | import { STATUS_LABELS_FR, type AimStatus } from '$lib/types/aim'; | ||
| 11 | import { chatState } from '$lib/states/chat.svelte'; | 10 | import { chatState } from '$lib/states/chat.svelte'; | ||
| 12 | import { desktop } from '$lib/states/desktop.svelte'; | 11 | import { desktop } from '$lib/states/desktop.svelte'; | ||
| @@ -31,7 +30,6 @@ | |||||
| 31 | const profileQuery = useQuery(convexApi.aim.getUserProfile, () => ({ userId: otherUserId })); | 30 | const profileQuery = useQuery(convexApi.aim.getUserProfile, () => ({ userId: otherUserId })); | ||
| 32 | const currentUser = $derived(chatState.getCurrentUser()); | 31 | const currentUser = $derived(chatState.getCurrentUser()); | ||
| 33 | const profile = $derived(profileQuery.data); | 32 | const profile = $derived(profileQuery.data); | ||
| 34 | - | const buddyIcon = $derived(getBuddyIcon(profile?.buddyIcon)); | |||
| 35 | const status = $derived((profile?.status ?? 'offline') as AimStatus); | 33 | const status = $derived((profile?.status ?? 'offline') as AimStatus); | ||
| 36 | const messages = $derived.by<EnrichedMessage[]>(() => | 34 | const messages = $derived.by<EnrichedMessage[]>(() => | ||
| 37 | (messagesQuery?.data ?? []).map((msg) => ({ | 35 | (messagesQuery?.data ?? []).map((msg) => ({ | ||
| @@ -262,14 +260,7 @@ | |||||
| 262 | {/snippet} | 260 | {/snippet} | ||
| 263 | <div class="im-body"> | 261 | <div class="im-body"> | ||
| 264 | <div class="header"> | 262 | <div class="header"> | ||
| 265 | - | <div | 263 | + | <img class="buddy-icon" src="/aim/running-man-48.png" alt="" width="48" height="48" /> |
| 266 | - | class="buddy-icon" | |||
| 267 | - | style:background={buddyIcon.bg} | |||
| 268 | - | role="img" | |||
| 269 | - | aria-label={buddyIcon.label} | |||
| 270 | - | > | |||
| 271 | - | {buddyIcon.emoji} | |||
| 272 | - | </div> | |||
| 273 | <div class="identity"> | 264 | <div class="identity"> | ||
| 274 | <strong>{profile?.nickname ?? otherNickname}</strong><span | 265 | <strong>{profile?.nickname ?? otherNickname}</strong><span | ||
| 275 | ><img src={`/aim/status-${status}.svg`} alt="" width="14" height="14" /> | 266 | ><img src={`/aim/status-${status}.svg`} alt="" width="14" height="14" /> | ||
| @@ -380,12 +371,6 @@ | |||||
| 380 | padding: 2px 4px; | 371 | padding: 2px 4px; | ||
| 381 | } | 372 | } | ||
| 382 | .buddy-icon { | 373 | .buddy-icon { | ||
| 383 | - | width: 48px; | |||
| 384 | - | height: 48px; | |||
| 385 | - | display: grid; | |||
| 386 | - | place-items: center; | |||
| 387 | - | font-size: 29px; | |||
| 388 | - | border: 1px solid #aca899; | |||
| 389 | flex: none; | 374 | flex: none; | ||
| 390 | } | 375 | } | ||
| 391 | .identity { | 376 | .identity { | ||
M src/lib/components/desktop/profile-window.svelte +2 -48
| @@ -6,7 +6,6 @@ | |||
| 6 | 6 | import { desktop } from '$lib/states/desktop.svelte'; | |
| 7 | 7 | import { chatState } from '$lib/states/chat.svelte'; | |
| 8 | 8 | import { useAimClient } from '$lib/aim/client'; | |
| 9 | - | import { BUDDY_ICONS, getBuddyIcon } from '$lib/aim/buddy-icons'; | |
| 10 | 9 | import { STATUS_LABELS_FR } from '$lib/types/aim'; | |
| 11 | 10 | import { formatFrenchDateTime } from '$lib/utils/date-format'; | |
| 12 | 11 | ||
| @@ -17,7 +16,6 @@ | |||
| 17 | 16 | const profile = $derived(profileQuery.data); | |
| 18 | 17 | const own = $derived(userId === chatState.getCurrentUser()?.id); | |
| 19 | 18 | let text = $state(''); | |
| 20 | - | let icon = $state('runner'); | |
| 21 | 19 | let saved = $state(false); | |
| 22 | 20 | let saving = $state(false); | |
| 23 | 21 | let error = $state(''); | |
| @@ -25,7 +23,6 @@ | |||
| 25 | 23 | $effect(() => { | |
| 26 | 24 | if (profile && initializedFor !== userId) { | |
| 27 | 25 | text = profile.profile ?? ''; | |
| 28 | - | icon = profile.buddyIcon ?? 'runner'; | |
| 29 | 26 | initializedFor = userId; | |
| 30 | 27 | } | |
| 31 | 28 | }); | |
| @@ -35,7 +32,7 @@ | |||
| 35 | 32 | saved = false; | |
| 36 | 33 | error = ''; | |
| 37 | 34 | try { | |
| 38 | - | await client.updateProfile({ profile: text, buddyIcon: icon }); | |
| 35 | + | await client.updateProfile({ profile: text }); | |
| 39 | 36 | saved = true; | |
| 40 | 37 | } catch (err) { | |
| 41 | 38 | error = err instanceof Error ? err.message : 'Impossible d’enregistrer.'; | |
| @@ -49,13 +46,7 @@ | |||
| 49 | 46 | <div class="profile-content"> | |
| 50 | 47 | {#if profile} | |
| 51 | 48 | <div class="identity"> | |
| 52 | - | <div | |
| 53 | - | class="buddy-icon" | |
| 54 | - | style:background={getBuddyIcon(profile.buddyIcon).bg} | |
| 55 | - | aria-label={getBuddyIcon(profile.buddyIcon).label} | |
| 56 | - | > | |
| 57 | - | {getBuddyIcon(profile.buddyIcon).emoji} | |
| 58 | - | </div> | |
| 49 | + | <img class="buddy-icon" src="/aim/running-man-48.png" alt="" width="48" height="48" /> | |
| 59 | 50 | <div> | |
| 60 | 51 | <strong>{profile.nickname}</strong> | |
| 61 | 52 | <div class="status"> | |
| @@ -82,21 +73,6 @@ | |||
| 82 | 73 | oninput={() => (saved = false)} | |
| 83 | 74 | ></textarea> | |
| 84 | 75 | <small>{text.length}/1000</small> | |
| 85 | - | <div class="icon-grid" aria-label="Choisir une icône"> | |
| 86 | - | {#each BUDDY_ICONS as preset (preset.id)} | |
| 87 | - | <button | |
| 88 | - | class:selected={icon === preset.id} | |
| 89 | - | aria-label={preset.label} | |
| 90 | - | aria-pressed={icon === preset.id} | |
| 91 | - | title={preset.label} | |
| 92 | - | style:background={preset.bg} | |
| 93 | - | onclick={() => { | |
| 94 | - | icon = preset.id; | |
| 95 | - | saved = false; | |
| 96 | - | }}>{preset.emoji}</button | |
| 97 | - | > | |
| 98 | - | {/each} | |
| 99 | - | </div> | |
| 100 | 76 | {:else} | |
| 101 | 77 | <div id="profile-text" class="sunken-panel profile-text"> | |
| 102 | 78 | {profile.profile ?? 'Aucun profil pour le moment.'} | |
| @@ -136,13 +112,7 @@ | |||
| 136 | 112 | gap: 10px; | |
| 137 | 113 | } | |
| 138 | 114 | .buddy-icon { | |
| 139 | - | width: 48px; | |
| 140 | - | height: 48px; | |
| 141 | 115 | flex: 0 0 48px; | |
| 142 | - | display: grid; | |
| 143 | - | place-items: center; | |
| 144 | - | font-size: 28px; | |
| 145 | - | border: 1px solid #777; | |
| 146 | 116 | } | |
| 147 | 117 | .status { | |
| 148 | 118 | display: flex; | |
| @@ -172,22 +142,6 @@ | |||
| 172 | 142 | textarea { | |
| 173 | 143 | resize: vertical; | |
| 174 | 144 | } | |
| 175 | - | .icon-grid { | |
| 176 | - | display: grid; | |
| 177 | - | grid-template-columns: repeat(8, 1fr); | |
| 178 | - | gap: 3px; | |
| 179 | - | } | |
| 180 | - | .icon-grid button { | |
| 181 | - | padding: 0; | |
| 182 | - | min-width: 0; | |
| 183 | - | height: 30px; | |
| 184 | - | font-size: 18px; | |
| 185 | - | cursor: pointer; | |
| 186 | - | border: 2px solid transparent; | |
| 187 | - | } | |
| 188 | - | .icon-grid button.selected { | |
| 189 | - | border-color: #245edb; | |
| 190 | - | } | |
| 191 | 145 | .actions { | |
| 192 | 146 | margin-top: auto; | |
| 193 | 147 | display: flex; | |
| @@ -6,7 +6,6 @@ | |||||
| 6 | import { desktop } from '$lib/states/desktop.svelte'; | 6 | import { desktop } from '$lib/states/desktop.svelte'; | ||
| 7 | import { chatState } from '$lib/states/chat.svelte'; | 7 | import { chatState } from '$lib/states/chat.svelte'; | ||
| 8 | import { useAimClient } from '$lib/aim/client'; | 8 | import { useAimClient } from '$lib/aim/client'; | ||
| 9 | - | import { BUDDY_ICONS, getBuddyIcon } from '$lib/aim/buddy-icons'; | |||
| 10 | import { STATUS_LABELS_FR } from '$lib/types/aim'; | 9 | import { STATUS_LABELS_FR } from '$lib/types/aim'; | ||
| 11 | import { formatFrenchDateTime } from '$lib/utils/date-format'; | 10 | import { formatFrenchDateTime } from '$lib/utils/date-format'; | ||
| 12 | 11 | ||||
| @@ -17,7 +16,6 @@ | |||||
| 17 | const profile = $derived(profileQuery.data); | 16 | const profile = $derived(profileQuery.data); | ||
| 18 | const own = $derived(userId === chatState.getCurrentUser()?.id); | 17 | const own = $derived(userId === chatState.getCurrentUser()?.id); | ||
| 19 | let text = $state(''); | 18 | let text = $state(''); | ||
| 20 | - | let icon = $state('runner'); | |||
| 21 | let saved = $state(false); | 19 | let saved = $state(false); | ||
| 22 | let saving = $state(false); | 20 | let saving = $state(false); | ||
| 23 | let error = $state(''); | 21 | let error = $state(''); | ||
| @@ -25,7 +23,6 @@ | |||||
| 25 | $effect(() => { | 23 | $effect(() => { | ||
| 26 | if (profile && initializedFor !== userId) { | 24 | if (profile && initializedFor !== userId) { | ||
| 27 | text = profile.profile ?? ''; | 25 | text = profile.profile ?? ''; | ||
| 28 | - | icon = profile.buddyIcon ?? 'runner'; | |||
| 29 | initializedFor = userId; | 26 | initializedFor = userId; | ||
| 30 | } | 27 | } | ||
| 31 | }); | 28 | }); | ||
| @@ -35,7 +32,7 @@ | |||||
| 35 | saved = false; | 32 | saved = false; | ||
| 36 | error = ''; | 33 | error = ''; | ||
| 37 | try { | 34 | try { | ||
| 38 | - | await client.updateProfile({ profile: text, buddyIcon: icon }); | 35 | + | await client.updateProfile({ profile: text }); |
| 39 | saved = true; | 36 | saved = true; | ||
| 40 | } catch (err) { | 37 | } catch (err) { | ||
| 41 | error = err instanceof Error ? err.message : 'Impossible d’enregistrer.'; | 38 | error = err instanceof Error ? err.message : 'Impossible d’enregistrer.'; | ||
| @@ -49,13 +46,7 @@ | |||||
| 49 | <div class="profile-content"> | 46 | <div class="profile-content"> | ||
| 50 | {#if profile} | 47 | {#if profile} | ||
| 51 | <div class="identity"> | 48 | <div class="identity"> | ||
| 52 | - | <div | 49 | + | <img class="buddy-icon" src="/aim/running-man-48.png" alt="" width="48" height="48" /> |
| 53 | - | class="buddy-icon" | |||
| 54 | - | style:background={getBuddyIcon(profile.buddyIcon).bg} | |||
| 55 | - | aria-label={getBuddyIcon(profile.buddyIcon).label} | |||
| 56 | - | > | |||
| 57 | - | {getBuddyIcon(profile.buddyIcon).emoji} | |||
| 58 | - | </div> | |||
| 59 | <div> | 50 | <div> | ||
| 60 | <strong>{profile.nickname}</strong> | 51 | <strong>{profile.nickname}</strong> | ||
| 61 | <div class="status"> | 52 | <div class="status"> | ||
| @@ -82,21 +73,6 @@ | |||||
| 82 | oninput={() => (saved = false)} | 73 | oninput={() => (saved = false)} | ||
| 83 | ></textarea> | 74 | ></textarea> | ||
| 84 | <small>{text.length}/1000</small> | 75 | <small>{text.length}/1000</small> | ||
| 85 | - | <div class="icon-grid" aria-label="Choisir une icône"> | |||
| 86 | - | {#each BUDDY_ICONS as preset (preset.id)} | |||
| 87 | - | <button | |||
| 88 | - | class:selected={icon === preset.id} | |||
| 89 | - | aria-label={preset.label} | |||
| 90 | - | aria-pressed={icon === preset.id} | |||
| 91 | - | title={preset.label} | |||
| 92 | - | style:background={preset.bg} | |||
| 93 | - | onclick={() => { | |||
| 94 | - | icon = preset.id; | |||
| 95 | - | saved = false; | |||
| 96 | - | }}>{preset.emoji}</button | |||
| 97 | - | > | |||
| 98 | - | {/each} | |||
| 99 | - | </div> | |||
| 100 | {:else} | 76 | {:else} | ||
| 101 | <div id="profile-text" class="sunken-panel profile-text"> | 77 | <div id="profile-text" class="sunken-panel profile-text"> | ||
| 102 | {profile.profile ?? 'Aucun profil pour le moment.'} | 78 | {profile.profile ?? 'Aucun profil pour le moment.'} | ||
| @@ -136,13 +112,7 @@ | |||||
| 136 | gap: 10px; | 112 | gap: 10px; | ||
| 137 | } | 113 | } | ||
| 138 | .buddy-icon { | 114 | .buddy-icon { | ||
| 139 | - | width: 48px; | |||
| 140 | - | height: 48px; | |||
| 141 | flex: 0 0 48px; | 115 | flex: 0 0 48px; | ||
| 142 | - | display: grid; | |||
| 143 | - | place-items: center; | |||
| 144 | - | font-size: 28px; | |||
| 145 | - | border: 1px solid #777; | |||
| 146 | } | 116 | } | ||
| 147 | .status { | 117 | .status { | ||
| 148 | display: flex; | 118 | display: flex; | ||
| @@ -172,22 +142,6 @@ | |||||
| 172 | textarea { | 142 | textarea { | ||
| 173 | resize: vertical; | 143 | resize: vertical; | ||
| 174 | } | 144 | } | ||
| 175 | - | .icon-grid { | |||
| 176 | - | display: grid; | |||
| 177 | - | grid-template-columns: repeat(8, 1fr); | |||
| 178 | - | gap: 3px; | |||
| 179 | - | } | |||
| 180 | - | .icon-grid button { | |||
| 181 | - | padding: 0; | |||
| 182 | - | min-width: 0; | |||
| 183 | - | height: 30px; | |||
| 184 | - | font-size: 18px; | |||
| 185 | - | cursor: pointer; | |||
| 186 | - | border: 2px solid transparent; | |||
| 187 | - | } | |||
| 188 | - | .icon-grid button.selected { | |||
| 189 | - | border-color: #245edb; | |||
| 190 | - | } | |||
| 191 | .actions { | 145 | .actions { | ||
| 192 | margin-top: auto; | 146 | margin-top: auto; | ||
| 193 | display: flex; | 147 | display: flex; | ||
M src/lib/types/aim.ts +0 -1
| @@ -10,7 +10,6 @@ export interface AimUser { | |||
| 10 | 10 | avatarUrl?: string | null; | |
| 11 | 11 | lastSeen?: number | null; | |
| 12 | 12 | awayMessage?: string; | |
| 13 | - | buddyIcon?: string; | |
| 14 | 13 | createdAt?: number; | |
| 15 | 14 | } | |
| 16 | 15 | ||
| @@ -10,7 +10,6 @@ export interface AimUser { | |||||
| 10 | avatarUrl?: string | null; | 10 | avatarUrl?: string | null; | ||
| 11 | lastSeen?: number | null; | 11 | lastSeen?: number | null; | ||
| 12 | awayMessage?: string; | 12 | awayMessage?: string; | ||
| 13 | - | buddyIcon?: string; | |||
| 14 | createdAt?: number; | 13 | createdAt?: number; | ||
| 15 | } | 14 | } | ||
| 16 | 15 | ||||
M src/lib/types/chat.ts +0 -1
| @@ -9,7 +9,6 @@ export interface User { | |||
| 9 | 9 | lastSeen?: number | null; // Optional last active timestamp | |
| 10 | 10 | lastReadMentionTimestamp?: number | null; // Timestamp of last read mention | |
| 11 | 11 | awayMessage?: string; // AIM away message (only exposed while away) | |
| 12 | - | buddyIcon?: string; // Preset buddy icon id | |
| 13 | 12 | } | |
| 14 | 13 | ||
| 15 | 14 | export type UserStatus = 'offline' | 'online' | 'away' | string; | |
| @@ -9,7 +9,6 @@ export interface User { | |||||
| 9 | lastSeen?: number | null; // Optional last active timestamp | 9 | lastSeen?: number | null; // Optional last active timestamp | ||
| 10 | lastReadMentionTimestamp?: number | null; // Timestamp of last read mention | 10 | lastReadMentionTimestamp?: number | null; // Timestamp of last read mention | ||
| 11 | awayMessage?: string; // AIM away message (only exposed while away) | 11 | awayMessage?: string; // AIM away message (only exposed while away) | ||
| 12 | - | buddyIcon?: string; // Preset buddy icon id | |||
| 13 | } | 12 | } | ||
| 14 | 13 | ||||
| 15 | export type UserStatus = 'offline' | 'online' | 'away' | string; | 14 | export type UserStatus = 'offline' | 'online' | 'away' | string; | ||