Skip to content

Commit c2973f3

Browse files
committed
ready to test
1 parent cb40ca5 commit c2973f3

1 file changed

Lines changed: 107 additions & 34 deletions

File tree

src/BasisPM.Server/wwwroot/index.html

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,23 @@ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">Bun
125125
<section class="rounded-2xl border border-gray-200 bg-white/40 p-4 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/[0.03] sm:p-5">
126126
<div class="mb-4 flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-white/10">
127127
<div id="count" class="text-sm text-gray-600 dark:text-gray-300"></div>
128-
<select id="sort" class="rounded-xl border border-gray-200 bg-white/70 px-3 py-2 text-sm text-gray-800 outline-none focus:border-brand dark:border-white/10 dark:bg-white/10 dark:text-gray-100">
129-
<option value="popular">Sort: Popular</option>
130-
<option value="stars">Most stars</option>
131-
<option value="forks">Most forks</option>
132-
<option value="updated">Recently updated</option>
133-
<option value="name">Name (A–Z)</option>
134-
</select>
128+
<div class="flex items-center gap-3">
129+
<select id="sort" class="rounded-xl border border-gray-200 bg-white/70 px-3 py-2 text-sm text-gray-800 outline-none focus:border-brand dark:border-white/10 dark:bg-white/10 dark:text-gray-100">
130+
<option value="popular">Sort: Popular</option>
131+
<option value="stars">Most stars</option>
132+
<option value="forks">Most forks</option>
133+
<option value="updated">Recently updated</option>
134+
<option value="name">Name (A–Z)</option>
135+
</select>
136+
<div class="inline-flex rounded-xl border border-gray-200 bg-white/70 p-1 shadow-sm dark:border-white/10 dark:bg-white/5">
137+
<button id="layoutGrid" type="button" title="Grid view" aria-label="Grid view" class="rounded-lg px-2.5 py-1.5">
138+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
139+
</button>
140+
<button id="layoutList" type="button" title="List view" aria-label="List view" class="rounded-lg px-2.5 py-1.5">
141+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="4" cy="6" r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/></svg>
142+
</button>
143+
</div>
144+
</div>
135145
</div>
136146
<div id="grid" class="grid gap-4 grid-cols-[repeat(auto-fill,minmax(18rem,1fr))]"></div>
137147
</section>
@@ -148,7 +158,8 @@ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">Bun
148158
<div id="toast" class="pointer-events-none fixed bottom-6 left-1/2 z-50 -translate-x-1/2 rounded-2xl border border-gray-200 bg-white px-5 py-3 text-sm opacity-0 shadow-lg transition dark:border-white/10 dark:bg-basisbg"></div>
149159

150160
<script>
151-
const state = { search:"", source:"all", category:"all", sort:"popular", view:"packages" };
161+
const state = { search:"", source:"all", category:"all", sort:"popular", view:"packages", layout:"grid" };
162+
try { const saved = localStorage.getItem('pkgLayout'); if(saved==='list'||saved==='grid') state.layout = saved; } catch {}
152163
const REPO = "BasisVR/BasisPackageManager";
153164
let RAW = [], ALL = [], RAWB = [], bundlesLoaded = false;
154165

@@ -257,6 +268,21 @@ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">Bun
257268
document.getElementById('tabBundles').className = state.view==='bundles' ? on : off;
258269
}
259270

271+
function layoutStyle(){
272+
const on = 'rounded-lg px-2.5 py-1.5 bg-gradient-to-r from-brand to-purple-600 text-white shadow';
273+
const off = 'rounded-lg px-2.5 py-1.5 text-gray-500 dark:text-gray-300 hover:text-brand';
274+
document.getElementById('layoutGrid').className = state.layout==='grid' ? on : off;
275+
document.getElementById('layoutList').className = state.layout==='list' ? on : off;
276+
}
277+
278+
function setLayout(layout){
279+
if(state.layout===layout) return;
280+
state.layout = layout;
281+
try { localStorage.setItem('pkgLayout', layout); } catch {}
282+
layoutStyle();
283+
renderGrid(); // ALL is already filtered/sorted — just re-render the current view in the new layout
284+
}
285+
260286
async function switchView(view){
261287
if(state.view===view) return;
262288
state.view = view;
@@ -333,12 +359,13 @@ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">Bun
333359
document.querySelectorAll('[data-category]').forEach(el=>el.onclick=()=>{state.category=el.dataset.category;buildFilters();load();});
334360
}
335361

336-
function renderGrid(){
337-
if(state.view==='bundles') return renderBundles();
338-
const grid = document.getElementById('grid');
339-
document.getElementById('count').innerHTML = `<b class="text-gray-900 dark:text-white">${ALL.length}</b> package${ALL.length===1?'':'s'}`;
340-
if(!ALL.length){ grid.innerHTML = `<div class="col-span-full py-20 text-center text-gray-500 dark:text-gray-400"><p class="text-lg font-semibold text-gray-900 dark:text-white">No packages found</p><p class="mt-1">Try a different search or filter.</p></div>`; return; }
341-
grid.innerHTML = ALL.map(p=>`
362+
// --- Results layout (grid cards vs. list rows), shared by packages and bundles ---
363+
const GRID_CLS = 'grid gap-4 grid-cols-[repeat(auto-fill,minmax(18rem,1fr))]';
364+
const LIST_CLS = 'flex flex-col gap-3';
365+
const gridCls = () => state.layout==='list' ? LIST_CLS : GRID_CLS;
366+
const emptyState = (title, sub) => `<div class="col-span-full w-full py-20 text-center text-gray-500 dark:text-gray-400"><p class="text-lg font-semibold text-gray-900 dark:text-white">${title}</p><p class="mt-1">${sub}</p></div>`;
367+
368+
const pkgCard = p => `
342369
<div data-id="${esc(p.id)}" class="group flex cursor-pointer flex-col rounded-2xl border border-gray-200 bg-white/70 p-5 shadow-sm backdrop-blur transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-brand/20 dark:border-white/10 dark:bg-white/5">
343370
<div class="flex items-start gap-3">
344371
<div class="inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gray-100 text-xl ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">${imgOrIcon(p)}</div>
@@ -357,7 +384,66 @@ <h3 class="flex flex-wrap items-center gap-2 font-semibold">${esc(p.name)}
357384
${p.license?`<span title="License">⚖ ${esc(p.license)}</span>`:''}
358385
<button class="ml-auto rounded-xl bg-gradient-to-r from-brand to-purple-600 px-3 py-1.5 text-xs font-semibold text-white">Install</button>
359386
</div>
360-
</div>`).join('');
387+
</div>`;
388+
389+
const pkgRow = p => `
390+
<div data-id="${esc(p.id)}" class="group flex cursor-pointer items-center gap-4 rounded-2xl border border-gray-200 bg-white/70 p-4 shadow-sm backdrop-blur transition hover:shadow-lg hover:shadow-brand/20 dark:border-white/10 dark:bg-white/5">
391+
<div class="inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gray-100 text-xl ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">${imgOrIcon(p)}</div>
392+
<div class="min-w-0 flex-1">
393+
<h3 class="flex flex-wrap items-center gap-2 font-semibold">${esc(p.name)}
394+
<span class="rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide ${badgeCls(p.source)}">${esc(p.source)}</span></h3>
395+
<p class="truncate text-xs text-gray-500 dark:text-gray-400">by ${esc(p.author)}${hostOf(p)?' · '+hostOf(p):''}${p.basisVersion?' · Basis '+esc(p.basisVersion):''}</p>
396+
<p class="mt-0.5 line-clamp-1 text-sm text-gray-600 dark:text-gray-300">${esc(p.description)}</p>
397+
</div>
398+
<div class="hidden shrink-0 items-center gap-3 text-xs text-gray-500 dark:text-gray-400 sm:flex">
399+
<span>⭐ ${fmtNum(p.stars)}</span>
400+
<span>⑂ ${fmtNum(p.forks)}</span>
401+
<span>v${esc(p.version)}</span>
402+
${p.license?`<span title="License">⚖ ${esc(p.license)}</span>`:''}
403+
</div>
404+
<button class="shrink-0 rounded-xl bg-gradient-to-r from-brand to-purple-600 px-3 py-1.5 text-xs font-semibold text-white">Install</button>
405+
</div>`;
406+
407+
const bundleCard = b => {
408+
const n = (b.packages||[]).length;
409+
const basis = b.basisBranch ? `${esc(b.basisBranch)}${b.basisCommit?'@'+esc(b.basisCommit):''}` : '';
410+
return `
411+
<div data-bundle="${esc(b.id)}" class="group flex cursor-pointer flex-col rounded-2xl border border-gray-200 bg-white/70 p-5 shadow-sm backdrop-blur transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-brand/20 dark:border-white/10 dark:bg-white/5">
412+
<div class="flex items-start gap-3">
413+
<div class="inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gray-100 text-xl ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">${imgOrIcon({image:b.image,icon:b.icon||'🧩'})}</div>
414+
<div class="min-w-0">
415+
<h3 class="flex flex-wrap items-center gap-2 font-semibold">${esc(b.name)}
416+
<span class="rounded-full bg-brand/15 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-brand ring-1 ring-brand/30">${n} pkg${n===1?'':'s'}</span></h3>
417+
<p class="text-xs text-gray-500 dark:text-gray-400">by ${esc(b.author||'—')}${basis?' · Basis '+basis:''}</p>
418+
</div>
419+
</div>
420+
<p class="mt-3 line-clamp-2 text-sm text-gray-600 dark:text-gray-300">${esc(b.description||'')}</p>
421+
<div class="mt-3 flex flex-wrap gap-1.5">${(b.tags||[]).slice(0,4).map(t=>`<span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-white/5 dark:text-gray-300">${esc(t)}</span>`).join('')}</div>
422+
</div>`;
423+
};
424+
425+
const bundleRow = b => {
426+
const n = (b.packages||[]).length;
427+
const basis = b.basisBranch ? `${esc(b.basisBranch)}${b.basisCommit?'@'+esc(b.basisCommit):''}` : '';
428+
return `
429+
<div data-bundle="${esc(b.id)}" class="group flex cursor-pointer items-center gap-4 rounded-2xl border border-gray-200 bg-white/70 p-4 shadow-sm backdrop-blur transition hover:shadow-lg hover:shadow-brand/20 dark:border-white/10 dark:bg-white/5">
430+
<div class="inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gray-100 text-xl ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">${imgOrIcon({image:b.image,icon:b.icon||'🧩'})}</div>
431+
<div class="min-w-0 flex-1">
432+
<h3 class="flex flex-wrap items-center gap-2 font-semibold">${esc(b.name)}
433+
<span class="rounded-full bg-brand/15 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-brand ring-1 ring-brand/30">${n} pkg${n===1?'':'s'}</span></h3>
434+
<p class="truncate text-xs text-gray-500 dark:text-gray-400">by ${esc(b.author||'—')}${basis?' · Basis '+basis:''}</p>
435+
<p class="mt-0.5 line-clamp-1 text-sm text-gray-600 dark:text-gray-300">${esc(b.description||'')}</p>
436+
</div>
437+
</div>`;
438+
};
439+
440+
function renderGrid(){
441+
if(state.view==='bundles') return renderBundles();
442+
const grid = document.getElementById('grid');
443+
grid.className = gridCls();
444+
document.getElementById('count').innerHTML = `<b class="text-gray-900 dark:text-white">${ALL.length}</b> package${ALL.length===1?'':'s'}`;
445+
if(!ALL.length){ grid.innerHTML = emptyState('No packages found','Try a different search or filter.'); return; }
446+
grid.innerHTML = ALL.map(p => state.layout==='list' ? pkgRow(p) : pkgCard(p)).join('');
361447
grid.querySelectorAll('[data-id]').forEach(c=>c.onclick=()=>openDetail(c.dataset.id));
362448
}
363449

@@ -431,25 +517,10 @@ <h2 class="flex flex-wrap items-center gap-2 text-xl font-bold">${esc(p.name)} <
431517

432518
function renderBundles(){
433519
const grid = document.getElementById('grid');
520+
grid.className = gridCls();
434521
document.getElementById('count').innerHTML = `<b class="text-gray-900 dark:text-white">${ALL.length}</b> bundle${ALL.length===1?'':'s'}`;
435-
if(!ALL.length){ grid.innerHTML = `<div class="col-span-full py-20 text-center text-gray-500 dark:text-gray-400"><p class="text-lg font-semibold text-gray-900 dark:text-white">No bundles yet</p><p class="mt-1">Build one from the Basis Package Manager and submit it.</p></div>`; return; }
436-
grid.innerHTML = ALL.map(b=>{
437-
const n = (b.packages||[]).length;
438-
const basis = b.basisBranch ? `${esc(b.basisBranch)}${b.basisCommit?'@'+esc(b.basisCommit):''}` : '';
439-
return `
440-
<div data-bundle="${esc(b.id)}" class="group flex cursor-pointer flex-col rounded-2xl border border-gray-200 bg-white/70 p-5 shadow-sm backdrop-blur transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-brand/20 dark:border-white/10 dark:bg-white/5">
441-
<div class="flex items-start gap-3">
442-
<div class="inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gray-100 text-xl ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">${imgOrIcon({image:b.image,icon:b.icon||'🧩'})}</div>
443-
<div class="min-w-0">
444-
<h3 class="flex flex-wrap items-center gap-2 font-semibold">${esc(b.name)}
445-
<span class="rounded-full bg-brand/15 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-brand ring-1 ring-brand/30">${n} pkg${n===1?'':'s'}</span></h3>
446-
<p class="text-xs text-gray-500 dark:text-gray-400">by ${esc(b.author||'—')}${basis?' · Basis '+basis:''}</p>
447-
</div>
448-
</div>
449-
<p class="mt-3 line-clamp-2 text-sm text-gray-600 dark:text-gray-300">${esc(b.description||'')}</p>
450-
<div class="mt-3 flex flex-wrap gap-1.5">${(b.tags||[]).slice(0,4).map(t=>`<span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-white/5 dark:text-gray-300">${esc(t)}</span>`).join('')}</div>
451-
</div>`;
452-
}).join('');
522+
if(!ALL.length){ grid.innerHTML = emptyState('No bundles yet','Build one from the Basis Package Manager and submit it.'); return; }
523+
grid.innerHTML = ALL.map(b => state.layout==='list' ? bundleRow(b) : bundleCard(b)).join('');
453524
grid.querySelectorAll('[data-bundle]').forEach(c=>c.onclick=()=>openBundle(c.dataset.bundle));
454525
}
455526

@@ -649,12 +720,14 @@ <h2 class="text-xl font-bold">${esc(b.name)}</h2>
649720
document.getElementById('submitBtn').onclick = openSubmit;
650721
document.getElementById('tabPackages').onclick = ()=>switchView('packages');
651722
document.getElementById('tabBundles').onclick = ()=>switchView('bundles');
723+
document.getElementById('layoutGrid').onclick = ()=>setLayout('grid');
724+
document.getElementById('layoutList').onclick = ()=>setLayout('list');
652725

653726
let searchTimer;
654727
document.getElementById('search').oninput = e=>{ clearTimeout(searchTimer); state.search=e.target.value; searchTimer=setTimeout(load,160); };
655728
document.getElementById('sort').onchange = e=>{ state.sort=e.target.value; load(); };
656729

657-
(async function init(){ tabStyle(); await fetchAll(); buildFilters(); load(); if(location.hash==='#bundles') switchView('bundles'); })();
730+
(async function init(){ tabStyle(); layoutStyle(); await fetchAll(); buildFilters(); load(); if(location.hash==='#bundles') switchView('bundles'); })();
658731
window.addEventListener('hashchange', ()=>switchView(location.hash==='#bundles'?'bundles':'packages'));
659732
</script>
660733

0 commit comments

Comments
 (0)