feat(app): redesign discovery and align skeletons with rendered UI

This commit is contained in:
Hampus authored and GitHub committed 2026-08-16 19:17:28 +02:00
1 parent e054aa96be
commit 98c40c6979
380 files changed
+50869 -32207

No files matched your search

@@ -54,10 +54,10 @@
.stat {
display: inline-flex;
align-items: center;
gap: var(--count-indicator-gap);
}
.statDot {
margin-right: 0.3rem;
height: 0.5rem;
width: 0.5rem;
border-radius: 624.9375rem;
@@ -139,7 +139,10 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
backend={HTML5Backend}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.dnd-provider"
>
<RoleDragAutoScroll listRef={listRef} />
<RoleDragAutoScroll
listRef={listRef}
data-flx="guild.guild-tabs.guild-roles-tab.role-sidebar.role-drag-auto-scroll"
/>
{hoistedRoles.map((role, index) => {
const roleWithUpdates = applyRoleUpdate(role, roleUpdates.get(role.id));
return (
@@ -232,7 +235,10 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
backend={HTML5Backend}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.dnd-provider--2"
>
<RoleDragAutoScroll listRef={listRef} />
<RoleDragAutoScroll
listRef={listRef}
data-flx="guild.guild-tabs.guild-roles-tab.role-sidebar.role-drag-auto-scroll--2"
/>
{roles.map((role: GuildRole, index) => {
const roleWithUpdates = applyRoleUpdate(role, roleUpdates.get(role.id));
return (
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import GatewayConnection from '@app/features/gateway/transport/GatewayConnection';
import {makeAutoObservable} from 'mobx';
import {makeAutoObservable, observable} from 'mobx';
export interface GuildCounts {
memberCount: number;
@@ -21,10 +21,9 @@ const FRESH_TTL_MS = 60 * 1000;
const REQUEST_DEBOUNCE_MS = 100;
class GuildCount {
private cache: Map<string, GuildCounts> = new Map();
private readonly cache = observable.map<string, GuildCounts>(undefined, {deep: false});
private pending: Set<string> = new Set();
private flushTimer: NodeJS.Timeout | null = null;
private cacheVersion = 0;
constructor() {
makeAutoObservable<GuildCount, 'cache' | 'pending' | 'flushTimer'>(
@@ -35,7 +34,6 @@ class GuildCount {
}
getCounts(guildId: string): GuildCounts | null {
void this.cacheVersion;
return this.cache.get(guildId) ?? null;
}
@@ -86,13 +84,10 @@ class GuildCount {
});
this.pending.delete(entry.guild_id);
}
this.cacheVersion += 1;
}
handleGuildDelete(guildId: string): void {
if (this.cache.delete(guildId)) {
this.cacheVersion += 1;
}
this.cache.delete(guildId);
this.pending.delete(guildId);
}
@@ -103,12 +98,10 @@ class GuildCount {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
this.cacheVersion += 1;
}
private hydrate(guilds: ReadonlyArray<HydratableGuild>): void {
const now = Date.now();
let updated = false;
for (const guild of guilds) {
if (!guild || typeof guild.id !== 'string') continue;
if (guild.unavailable) continue;
@@ -118,10 +111,6 @@ class GuildCount {
onlineCount: guild.online_count,
fetchedAt: now,
});
updated = true;
}
if (updated) {
this.cacheVersion += 1;
}
}
@@ -105,16 +105,23 @@ class GuildList {
const guildMap = new Map(this.guilds.map((guild) => [guild.id, guild]));
const result: Array<OrganizedItem> = [];
const placedGuildIds = new Set<string>();
const emittedFolderIds = new Set<number | null>();
for (const folder of guildFolders) {
const folderGuilds = folder.guildIds
.map((guildId) => guildMap.get(guildId))
.filter((guild): guild is Guild => guild !== undefined);
for (const guild of folderGuilds) {
placedGuildIds.add(guild.id);
if (folder.id !== UNCATEGORIZED_FOLDER_ID && emittedFolderIds.has(folder.id)) {
continue;
}
const folderGuilds: Array<Guild> = [];
for (const guildId of folder.guildIds) {
if (placedGuildIds.has(guildId)) continue;
const guild = guildMap.get(guildId);
if (guild === undefined) continue;
placedGuildIds.add(guildId);
folderGuilds.push(guild);
}
if (folderGuilds.length === 0) {
continue;
}
emittedFolderIds.add(folder.id);
if (folder.id === UNCATEGORIZED_FOLDER_ID) {
for (const guild of folderGuilds) {
result.push({type: 'guild', guild});
@@ -2,6 +2,22 @@
const GUILD_ICON_INITIALS_MAX_LENGTH = 4;
export const GuildInitialsLength = Object.freeze({
SHORT: 'short',
MEDIUM: 'medium',
LONG: 'long',
} as const);
export type GuildInitialsLength = (typeof GuildInitialsLength)[keyof typeof GuildInitialsLength];
export function getInitialsFromName(name: string): string {
const words = name.split(/\s+/u).filter(Boolean);
return words
.map((word) => Array.from(word)[0])
.filter(Boolean)
.join('');
}
export function truncateInitials(initials: string, maxLength: number): string {
if (maxLength <= 0) return '';
return Array.from(initials).slice(0, maxLength).join('');
@@ -11,9 +27,9 @@ export function getGuildIconDisplayInitials(initials: string): string {
return truncateInitials(initials, GUILD_ICON_INITIALS_MAX_LENGTH);
}
export function getInitialsLength(initials: string): 'short' | 'medium' | 'long' {
export function getInitialsLength(initials: string): GuildInitialsLength {
const length = Array.from(initials).length;
if (length <= 2) return 'short';
if (length <= 4) return 'medium';
return 'long';
if (length <= 2) return GuildInitialsLength.SHORT;
if (length <= 4) return GuildInitialsLength.MEDIUM;
return GuildInitialsLength.LONG;
}