feat: add platform browse history tab updates
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
133
src/services/platformBrowseHistory.ts
Normal file
133
src/services/platformBrowseHistory.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user