feat: add platform browse history tab updates
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
victo
2026-04-15 18:32:04 +08:00
parent 6363267bca
commit 00dfb78b00
3 changed files with 311 additions and 50 deletions

View File

@@ -0,0 +1,133 @@
import type { CustomWorldGalleryCard } from '../../packages/shared/src/contracts/runtime';
import type { AuthUser } from './authService';
export type PlatformBrowseHistoryEntry = {
ownerUserId: string;
profileId: string;
worldName: string;
subtitle: string;
summaryText: string;
coverImageSrc: string | null;
themeMode: CustomWorldGalleryCard['themeMode'];
authorDisplayName: string;
visitedAt: string;
};
const HISTORY_STORAGE_KEY_PREFIX = 'genarrative.platform.browse-history.v1';
const MAX_HISTORY_ENTRIES = 20;
function canUseLocalStorage() {
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
}
function buildHistoryStorageKey(user: AuthUser | null | undefined) {
const accountId = user?.id?.trim() || user?.username?.trim() || 'guest';
return `${HISTORY_STORAGE_KEY_PREFIX}:${accountId}`;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function readString(value: unknown) {
return typeof value === 'string' ? value.trim() : '';
}
function normalizeHistoryEntry(value: unknown): PlatformBrowseHistoryEntry | null {
if (!isRecord(value)) {
return null;
}
const ownerUserId = readString(value.ownerUserId);
const profileId = readString(value.profileId);
const worldName = readString(value.worldName);
const visitedAt = readString(value.visitedAt);
if (!ownerUserId || !profileId || !worldName || !visitedAt) {
return null;
}
const themeMode = readString(value.themeMode) as PlatformBrowseHistoryEntry['themeMode'];
return {
ownerUserId,
profileId,
worldName,
subtitle: readString(value.subtitle),
summaryText: readString(value.summaryText),
coverImageSrc: readString(value.coverImageSrc) || null,
themeMode: themeMode || 'mythic',
authorDisplayName: readString(value.authorDisplayName) || '玩家',
visitedAt,
};
}
function sortHistoryEntries(entries: PlatformBrowseHistoryEntry[]) {
return [...entries].sort((left, right) => {
return (
new Date(right.visitedAt).getTime() - new Date(left.visitedAt).getTime()
);
});
}
export function readPlatformBrowseHistory(user: AuthUser | null | undefined) {
if (!canUseLocalStorage()) {
return [] as PlatformBrowseHistoryEntry[];
}
const raw = window.localStorage.getItem(buildHistoryStorageKey(user));
if (!raw?.trim()) {
return [] as PlatformBrowseHistoryEntry[];
}
try {
const parsed = JSON.parse(raw) as unknown[];
if (!Array.isArray(parsed)) {
return [] as PlatformBrowseHistoryEntry[];
}
return sortHistoryEntries(
parsed
.map((entry) => normalizeHistoryEntry(entry))
.filter((entry): entry is PlatformBrowseHistoryEntry => Boolean(entry)),
).slice(0, MAX_HISTORY_ENTRIES);
} catch {
return [] as PlatformBrowseHistoryEntry[];
}
}
export function writePlatformBrowseHistory(
user: AuthUser | null | undefined,
entry: Omit<PlatformBrowseHistoryEntry, 'visitedAt'> & {
visitedAt?: string;
},
) {
if (!canUseLocalStorage()) {
return [] as PlatformBrowseHistoryEntry[];
}
const nextEntry: PlatformBrowseHistoryEntry = {
...entry,
subtitle: entry.subtitle?.trim() || '',
summaryText: entry.summaryText?.trim() || '',
coverImageSrc: entry.coverImageSrc?.trim() || null,
authorDisplayName: entry.authorDisplayName?.trim() || '玩家',
visitedAt: entry.visitedAt?.trim() || new Date().toISOString(),
};
const deduped = readPlatformBrowseHistory(user).filter(
(current) =>
!(
current.ownerUserId === nextEntry.ownerUserId &&
current.profileId === nextEntry.profileId
),
);
const nextEntries = sortHistoryEntries([nextEntry, ...deduped]).slice(
0,
MAX_HISTORY_ENTRIES,
);
window.localStorage.setItem(
buildHistoryStorageKey(user),
JSON.stringify(nextEntries),
);
return nextEntries;
}