Implement the 030-bulk-import-sources feature that adds a one-click bulk import button to load all bestiary sources at once, with real-time progress feedback in the side panel and a toast notification when the panel is closed, plus completion/failure reporting with auto-dismiss on success and persistent display on partial failure, while also hardening the bestiary normalizer to handle variable stat blocks (spell summons with special AC/HP/CR) and skip malformed monster entries gracefully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,21 +33,61 @@ function mapCreature(c: CompactCreature): BestiaryIndexEntry {
|
||||
};
|
||||
}
|
||||
|
||||
// Source codes whose filename on the remote differs from a simple lowercase.
|
||||
// Plane Shift sources use a hyphen: PSA -> ps-a, etc.
|
||||
const FILENAME_OVERRIDES: Record<string, string> = {
|
||||
PSA: "ps-a",
|
||||
PSD: "ps-d",
|
||||
PSI: "ps-i",
|
||||
PSK: "ps-k",
|
||||
PSX: "ps-x",
|
||||
PSZ: "ps-z",
|
||||
};
|
||||
|
||||
// Source codes with no corresponding remote bestiary file.
|
||||
// Excluded from the index entirely so creatures aren't searchable
|
||||
// without a fetchable source.
|
||||
const EXCLUDED_SOURCES = new Set<string>([]);
|
||||
|
||||
let cachedIndex: BestiaryIndex | undefined;
|
||||
|
||||
export function loadBestiaryIndex(): BestiaryIndex {
|
||||
if (cachedIndex) return cachedIndex;
|
||||
|
||||
const compact = rawIndex as unknown as CompactIndex;
|
||||
const sources = Object.fromEntries(
|
||||
Object.entries(compact.sources).filter(
|
||||
([code]) => !EXCLUDED_SOURCES.has(code),
|
||||
),
|
||||
);
|
||||
cachedIndex = {
|
||||
sources: compact.sources,
|
||||
creatures: compact.creatures.map(mapCreature),
|
||||
sources,
|
||||
creatures: compact.creatures
|
||||
.filter((c) => !EXCLUDED_SOURCES.has(c.s))
|
||||
.map(mapCreature),
|
||||
};
|
||||
return cachedIndex;
|
||||
}
|
||||
|
||||
export function getDefaultFetchUrl(sourceCode: string): string {
|
||||
return `https://raw.githubusercontent.com/5etools-mirror-3/5etools-src/main/data/bestiary/bestiary-${sourceCode.toLowerCase()}.json`;
|
||||
export function getAllSourceCodes(): string[] {
|
||||
const index = loadBestiaryIndex();
|
||||
return Object.keys(index.sources).filter((c) => !EXCLUDED_SOURCES.has(c));
|
||||
}
|
||||
|
||||
function sourceCodeToFilename(sourceCode: string): string {
|
||||
return FILENAME_OVERRIDES[sourceCode] ?? sourceCode.toLowerCase();
|
||||
}
|
||||
|
||||
export function getDefaultFetchUrl(
|
||||
sourceCode: string,
|
||||
baseUrl?: string,
|
||||
): string {
|
||||
const filename = `bestiary-${sourceCodeToFilename(sourceCode)}.json`;
|
||||
if (baseUrl !== undefined) {
|
||||
const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
||||
return `${normalized}${filename}`;
|
||||
}
|
||||
return `https://raw.githubusercontent.com/5etools-mirror-3/5etools-src/main/data/bestiary/${filename}`;
|
||||
}
|
||||
|
||||
export function getSourceDisplayName(sourceCode: string): string {
|
||||
|
||||
Reference in New Issue
Block a user