feat: refine database discovery and ISAC interactions

This commit is contained in:
JB
2026-07-26 13:24:30 -04:00
parent 2e868d86ff
commit e8ed35e35f
11 changed files with 636 additions and 50 deletions
+88 -2
View File
@@ -1,4 +1,5 @@
import { getDatabase } from '$lib/server/database';
import { getRegistrySearch } from '$lib/server/pagination';
import { databaseCategories } from '$lib/database/categories';
import type { PageServerLoad } from './$types';
@@ -13,14 +14,99 @@ const tables = {
'skill-chips': 'skill_chip_sets'
} as const;
export const load: PageServerLoad = async () => {
export const load: PageServerLoad = async ({ url }) => {
const database = await getDatabase();
const { search, pattern } = getRegistrySearch(url);
const counts = await Promise.all(databaseCategories.map(async (category) => {
const row = await (await database.prepare(`SELECT COUNT(*) AS count FROM ${tables[category.slug]} WHERE active=1`)).get();
return { ...category, count: Number(row?.count ?? 0) };
}));
const searchResults = search
? await (await database.prepare(`
SELECT * FROM (
SELECT 'gear' AS slug,'Gear' AS category,item.name,
item.slot || ' · T' || item.tier AS detail
FROM gear item
WHERE item.active=1 AND (item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.slot LIKE ? ESCAPE '\\' COLLATE NOCASE)
UNION ALL
SELECT 'weapons','Weapons',item.name,
item.category || ' · ' || item.damage_type || ' damage'
FROM weapons item
WHERE item.active=1 AND (
item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.category LIKE ? ESCAPE '\\' COLLATE NOCASE
OR item.damage_type LIKE ? ESCAPE '\\' COLLATE NOCASE
)
UNION ALL
SELECT 'weapon-mods','Weapon Mods',item.name,item.slot_type
FROM weapon_mods item
WHERE item.active=1 AND (item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.slot_type LIKE ? ESCAPE '\\' COLLATE NOCASE)
UNION ALL
SELECT 'gear-mods','Gear Mods',item.name,'Gear modification'
FROM gear_mods item
WHERE item.active=1 AND item.name LIKE ? ESCAPE '\\' COLLATE NOCASE
UNION ALL
SELECT 'os-protocols','OS Protocols',item.name,
item.quality || ' · ' || item.primary_attribute_type
FROM os_protocols item
LEFT JOIN os_protocol_talents talent ON talent.protocol_id=item.id
WHERE item.active=1 AND (
item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.quality LIKE ? ESCAPE '\\' COLLATE NOCASE
OR item.primary_attribute_type LIKE ? ESCAPE '\\' COLLATE NOCASE
OR talent.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR talent.description_template LIKE ? ESCAPE '\\' COLLATE NOCASE
)
UNION ALL
SELECT 'talents','Talents',item.name,item.category
FROM talents item
WHERE item.active=1 AND (
item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.category LIKE ? ESCAPE '\\' COLLATE NOCASE
OR EXISTS (
SELECT 1 FROM talent_tiers tier
WHERE tier.talent_id=item.id AND (
tier.description LIKE ? ESCAPE '\\' COLLATE NOCASE
OR tier.trigger_text LIKE ? ESCAPE '\\' COLLATE NOCASE
OR tier.effect_text LIKE ? ESCAPE '\\' COLLATE NOCASE
)
)
)
UNION ALL
SELECT 'brands','Brands',item.name,
COALESCE((
SELECT GROUP_CONCAT(attribute.name, ', ')
FROM brand_set_effects effect
JOIN stat_attributes attribute ON attribute.id=effect.stat_attribute_id
WHERE effect.brand_set_id=item.id
),'Brand Set bonuses')
FROM brand_sets item
WHERE item.active=1 AND (
item.name LIKE ? ESCAPE '\\' COLLATE NOCASE
OR EXISTS (
SELECT 1 FROM brand_set_effects effect
JOIN stat_attributes attribute ON attribute.id=effect.stat_attribute_id
WHERE effect.brand_set_id=item.id AND attribute.name LIKE ? ESCAPE '\\' COLLATE NOCASE
)
)
UNION ALL
SELECT 'skill-chips','Skill Chips',item.name,COALESCE(talent.name,'Skill Chip set')
FROM skill_chip_sets item
LEFT JOIN skill_chip_talents talent ON talent.skill_chip_set_id=item.id
WHERE item.active=1 AND (
item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR talent.name LIKE ? ESCAPE '\\' COLLATE NOCASE
OR talent.description_template LIKE ? ESCAPE '\\' COLLATE NOCASE
)
) matches
ORDER BY name COLLATE NOCASE
LIMIT 40
`)).all(...Array(23).fill(pattern))
: [];
return {
registries: counts,
total: counts.reduce((total, registry) => total + registry.count, 0)
total: counts.reduce((total, registry) => total + registry.count, 0),
search,
searchResults: searchResults.map((result) => ({
slug: String(result.slug),
category: String(result.category),
name: String(result.name),
detail: String(result.detail ?? '')
}))
};
};
+37 -10
View File
@@ -1,28 +1,55 @@
<script lang="ts">
let { data } = $props();
const onlineRegistries = $derived(data.registries.filter((registry) => registry.count > 0).length);
</script>
<svelte:head>
<title>Game database — SHD.build</title>
<title>The Division Resurgence database — SHD.build</title>
<meta name="description" content="Community-maintained The Division Resurgence attributes, talents, brands, gear, weapons, mods, protocols, and Skill Chip data."/>
</svelte:head>
<main class="page community-index-page">
<header class="catalogue-hero community-index-hero">
<div><p class="eyebrow"><span></span> ISAC PUBLIC ARCHIVE</p><h1>GAME<br/><em>DATABASE.</em></h1><p>Community-maintained Resurgence data powering SHD.build tools, loadouts, and reference material.</p></div>
<div class="catalogue-count"><strong>{data.total}</strong><span>ACTIVE RECORDS</span><a class="primary" href="/isac">CONTRIBUTE DATA →</a></div>
<div><p class="eyebrow"><span></span> ISAC PUBLIC ARCHIVE</p><h1>RESURGENCE <em>DATABASE.</em></h1><p>Explore equipment, talents, modifications, and gameplay effects from The Division Resurgence.</p></div>
<div class="catalogue-count"><strong>{onlineRegistries} REGISTRIES ONLINE</strong><span>{data.total} ACTIVE RECORDS</span><a class="secondary" href="/isac">CONTRIBUTE THROUGH ISAC →</a></div>
</header>
<section class="community-database-intro">
<div><span>DATABASE STATUS</span><strong>PUBLIC INDEX ONLINE</strong><small>Select a registry to inspect active, approved records and their resolved gameplay attributes.</small></div>
<div><span>DATA STEWARDSHIP</span><strong>COMMUNITY + OVERSIGHT</strong><small>Registered agents can inspect source data and propose verified corrections through ISAC.</small></div>
<section class="database-global-search" aria-labelledby="database-search-heading">
<div>
<span>DATABASE INDEX</span>
<strong id="database-search-heading">SELECT A REGISTRY OR SEARCH ALL DATA</strong>
</div>
<form method="GET" action="/database" role="search">
<label class="sr-only" for="database-global-query">Search all database categories</label>
<input id="database-global-query" name="q" type="search" value={data.search} placeholder="SEARCH ITEMS, TALENTS, ATTRIBUTES, OR EFFECTS" maxlength="80"/>
<button type="submit">SEARCH ALL DATA</button>
{#if data.search}<a href="/database" aria-label="Clear database search">CLEAR</a>{/if}
</form>
</section>
{#if data.search}
<section class="database-global-results" aria-live="polite">
<header><div><span>SEARCH RESULTS</span><strong>“{data.search}”</strong></div><b>{data.searchResults.length}{data.searchResults.length === 40 ? '+' : ''} MATCHES</b></header>
{#if data.searchResults.length}
<div>
{#each data.searchResults as result}
<a href={`/database/${result.slug}?q=${encodeURIComponent(result.name)}`}>
<small>{result.category}</small>
<strong>{result.name}</strong>
<span>{result.detail}</span>
<b>VIEW →</b>
</a>
{/each}
</div>
{:else}
<p>No database entries match this search. Try an item name, category, attribute, talent effect, or damage type.</p>
{/if}
</section>
{/if}
<section class="database-registry-grid">
{#each data.registries as registry, index}
{#each data.registries as registry}
<a href={`/database/${registry.slug}`}>
<header><small>{String(index + 1).padStart(2, '0')} // {registry.code}</small><b>{registry.count}</b></header>
<h2>{registry.label}</h2>
<header><h2>{registry.label}</h2><b>{registry.count}</b></header>
<p>{registry.description}</p>
<span>{registry.count ? 'OPEN ACTIVE DATA →' : 'AWAITING CONTRIBUTIONS →'}</span>
<footer><small>{registry.count ? `${registry.count} ENTRIES` : 'NO ENTRIES YET'}</small><span>OPEN REGISTRY →</span></footer>
</a>
{/each}
</section>
+5 -7
View File
@@ -1,31 +1,29 @@
<script lang="ts">
import DatabaseCategoryNav from '$lib/components/DatabaseCategoryNav.svelte';
import DatabaseItemCard from '$lib/components/DatabaseItemCard.svelte';
import DatabaseFacetFilters from '$lib/components/DatabaseFacetFilters.svelte';
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
import DatabaseDiscoveryConsole from '$lib/components/DatabaseDiscoveryConsole.svelte';
let { data } = $props();
</script>
<svelte:head>
<title>{data.category.label} database — SHD.build</title>
<title>{data.category.label} — The Division Resurgence database — SHD.build</title>
<meta name="description" content={`${data.category.description} Community-maintained data for The Division Resurgence.`}/>
</svelte:head>
<main class="page database-category-page">
<header class="database-category-command">
<div><p class="eyebrow"><span></span> ISAC PUBLIC ARCHIVE / {data.category.code}</p><h1>{data.category.label}</h1><p>{data.category.description}</p></div>
<div><p class="eyebrow"><span></span> ISAC PUBLIC ARCHIVE / DATABASE REGISTRY</p><h1>{data.category.label}</h1><p>{data.category.description}</p></div>
<div><strong>{data.pagination.total}</strong><span>ACTIVE RECORDS</span><a href="/database">DATABASE INDEX →</a></div>
</header>
<DatabaseCategoryNav active={data.category.slug}/>
{#if data.filters?.length}<DatabaseFacetFilters groups={data.filters}/>{/if}
<RegistryPagination pagination={data.pagination} search={data.search} searchPlaceholder={`SEARCH ${data.category.label.toUpperCase()} BY NAME`}/>
<DatabaseDiscoveryConsole filters={data.filters} pagination={data.pagination} search={data.search} searchPlaceholder={`SEARCH ${data.category.label.toUpperCase()} BY NAME`}/>
<section class="database-item-grid">
{#each data.items as item}
<DatabaseItemCard item={item as any}/>
{:else}
<div class="database-empty">
<span>◇</span><strong>{data.search ? 'NO MATCHING RECORDS' : 'NO ACTIVE RECORDS'}</strong>
<p>{data.search ? `No ${data.category.label.toLocaleLowerCase()} match “${data.search}”.` : `This registry is awaiting approved ${data.category.label.toLocaleLowerCase()} data.`}</p>
<p>{data.search ? `No ${data.category.label.toLocaleLowerCase()} match “${data.search}”.` : `No ${data.category.label.toLocaleLowerCase()} have been added yet.`}</p>
</div>
{/each}
</section>