feat(ms-ai-architect): playground v3 home surface + project list [skip-docs]
Step 6/17 av Playground v3-leveransen (Session 2, Wave 2). Hjem-skjerm med 3-track entry-pattern (.tracks__card--guided/explore/expert): - Onboard / Re-onboard - Nytt prosjekt - Command-katalog Prosjekt-liste under tracks: .fleet-grid med .fleet-tile per prosjekt (navn + scenario-chip + meter med rapport-fremdrift). Tom-state vises som .guide-panel--info med 'Opprett første prosjekt'-knapp. Topbar (renderTopbar) med brand + nav + eksport/import-knapper synlig på home/catalog/project. Onboarding holdes uten topbar for full-fokus første-flyt. import-input change-handler ruter via window.__importState fra Step 3 og kjører scheduleRender etter import. Verifisert via vm sandbox: - 21 tracks__card-treff (3 cards med modifier-klasser) - guided/explore/expert-modifiers alle til stede - empty-state guide-panel--info når projects=[] - fleet-grid suppressed når projects=[] Stub-actions for new-project (Step 7 erstatter med modal-åpning). README/CLAUDE.md-update deferred til Step 17 (Session 5).
This commit is contained in:
parent
6b2ac8250e
commit
ff99a51d1d
1 changed files with 208 additions and 5 deletions
|
|
@ -1179,22 +1179,174 @@
|
|||
scheduleRender();
|
||||
}
|
||||
|
||||
// Stubber for surfaces som fylles i Steps 6, 7, 9. Holder renderActive
|
||||
// total uten å kreve at de finnes.
|
||||
// Topbar — gjenbrukes på home, catalog, project. Onboarding viser ingen topbar
|
||||
// (full-fokus førstegangs-flyt). Eksport/import-knapper wires opp til
|
||||
// __exportState/__importState fra Step 3.
|
||||
function renderTopbar(crumb) {
|
||||
const orgName = (store.state.shared.organization && store.state.shared.organization.name) || '';
|
||||
const crumbHtml = (orgName || crumb)
|
||||
? '<span class="topbar__crumb">' + (orgName ? escapeHtml(orgName) : '') + (orgName && crumb ? ' · ' : '') + (crumb || '') + '</span>'
|
||||
: '';
|
||||
return (
|
||||
'<header class="topbar">' +
|
||||
'<div class="topbar__brand">' +
|
||||
'<span class="topbar__brand-mark" aria-hidden="true">M</span>' +
|
||||
'<span>ms-ai-architect</span>' +
|
||||
crumbHtml +
|
||||
'</div>' +
|
||||
'<nav class="topbar__nav" aria-label="Hovednavigasjon">' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-home">Hjem</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-catalog">Katalog</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-onboarding">Re-onboard</button>' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="export-state" aria-label="Eksporter state til JSON">Eksporter</button>' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="import-state" aria-label="Importer state fra JSON">Importer</button>' +
|
||||
'<input type="file" accept="application/json,.json" data-import-input hidden>' +
|
||||
'</nav>' +
|
||||
'</header>'
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// HOME SURFACE (Step 6)
|
||||
// ============================================================
|
||||
//
|
||||
// 3 entry-tracks (.tracks med .tracks__card--guided/explore/expert) som
|
||||
// første-valg på home. Under: prosjekt-liste i .fleet-grid med .fleet-tile
|
||||
// per prosjekt. Tom-state: .guide-panel--info. "Nytt prosjekt"-knapp
|
||||
// åpner modal (modal-handler i Step 7 — Step 6 har stub).
|
||||
|
||||
function projectReportCount(p) {
|
||||
if (!p || !p.reports) return 0;
|
||||
let count = 0;
|
||||
for (const k in p.reports) {
|
||||
if (p.reports[k] && p.reports[k].parsed) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function projectMeterBand(filled, total) {
|
||||
if (total === 0) return '4'; // tom = "krever oppmerksomhet"
|
||||
const pct = filled / total;
|
||||
if (pct >= 0.8) return '1';
|
||||
if (pct >= 0.5) return '2';
|
||||
if (pct >= 0.2) return '3';
|
||||
return '4';
|
||||
}
|
||||
|
||||
function renderHomeSurface() {
|
||||
const root = getSurfaceEl('home');
|
||||
if (!root) return;
|
||||
root.innerHTML = '<div class="app-shell"><p>Hjem-skjerm fylles i Step 6.</p></div>';
|
||||
|
||||
const projects = store.state.projects || [];
|
||||
const reportTotal = CATALOG.commands.filter(function (c) { return c.produces_report; }).length;
|
||||
|
||||
const tracksHtml = (
|
||||
'<div class="tracks">' +
|
||||
'<button type="button" class="tracks__card tracks__card--guided" data-action="goto-onboarding">' +
|
||||
'<span class="tracks__card-icon" aria-hidden="true">⚙︎</span>' +
|
||||
'<h3 class="tracks__card-title">Onboard / Re-onboard</h3>' +
|
||||
'<p class="tracks__card-desc">Oppdater de 18 felles feltene som forhåndsutfyller alle command-skjemaer.</p>' +
|
||||
'<span class="tracks__card-meta"><span>Felles state</span><span class="tracks__card-cta">Åpne →</span></span>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="tracks__card tracks__card--explore" data-action="new-project">' +
|
||||
'<span class="tracks__card-icon" aria-hidden="true">+</span>' +
|
||||
'<h3 class="tracks__card-title">Nytt prosjekt</h3>' +
|
||||
'<p class="tracks__card-desc">Start et nytt arkitektur-prosjekt. Hvert prosjekt holder sine egne ROS, DPIA, AI Act-klassifisering osv.</p>' +
|
||||
'<span class="tracks__card-meta"><span>Per-prosjekt state</span><span class="tracks__card-cta">Opprett →</span></span>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="tracks__card tracks__card--expert" data-action="goto-catalog">' +
|
||||
'<span class="tracks__card-icon" aria-hidden="true">◇</span>' +
|
||||
'<h3 class="tracks__card-title">Command-katalog</h3>' +
|
||||
'<p class="tracks__card-desc">Bla i alle 24 commands gruppert på kategori. Generer pipeline-strenger uten et prosjekt.</p>' +
|
||||
'<span class="tracks__card-meta"><span>' + CATALOG.commands.length + ' commands</span><span class="tracks__card-cta">Bla →</span></span>' +
|
||||
'</button>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
const projectListHtml = (function () {
|
||||
if (projects.length === 0) {
|
||||
return (
|
||||
'<div class="guide-panel guide-panel--info">' +
|
||||
'<div class="guide-panel__icon" aria-hidden="true">i</div>' +
|
||||
'<div class="guide-panel__body">' +
|
||||
'<h3 class="guide-panel__title">Du har ingen prosjekter ennå</h3>' +
|
||||
'<p class="guide-panel__text">Opprett ditt første for å starte ROS-, DPIA- og AI Act-arbeid. Felles felter du fylte ut i onboarding gjenbrukes automatisk.</p>' +
|
||||
'<div class="guide-panel__action">' +
|
||||
'<button type="button" class="btn btn--primary" data-action="new-project">Opprett første prosjekt</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
const tiles = projects.map(function (p) {
|
||||
const filled = projectReportCount(p);
|
||||
const band = projectMeterBand(filled, reportTotal);
|
||||
const pct = reportTotal ? Math.round(100 * filled / reportTotal) : 0;
|
||||
const scenarios = Array.isArray(p.scenarios) ? p.scenarios : [];
|
||||
const chip = scenarios.length > 0
|
||||
? '<span class="fleet-tile__chip">' + escapeHtml(scenarios[0]) + (scenarios.length > 1 ? ' +' + (scenarios.length - 1) : '') + '</span>'
|
||||
: '<span class="fleet-tile__chip">Uten scenario</span>';
|
||||
return (
|
||||
'<button type="button" class="fleet-tile" data-action="open-project" data-project-id="' + escapeAttr(p.id) + '">' +
|
||||
'<div class="fleet-tile__row">' +
|
||||
'<span class="fleet-tile__name" title="' + escapeAttr(p.name) + '">' + escapeHtml(p.name) + '</span>' +
|
||||
chip +
|
||||
'</div>' +
|
||||
'<div class="fleet-tile__meter" aria-label="Rapport-fremdrift">' +
|
||||
'<span class="fleet-tile__meter-fill" data-band="' + band + '" style="width:' + Math.max(pct, 4) + '%"></span>' +
|
||||
'</div>' +
|
||||
'<div class="fleet-tile__meta">' +
|
||||
'<span>' + filled + '/' + reportTotal + ' rapporter</span>' +
|
||||
'<span class="fleet-tile__trend--stable">' + pct + '%</span>' +
|
||||
'</div>' +
|
||||
'</button>'
|
||||
);
|
||||
}).join('');
|
||||
return '<div class="fleet-grid">' + tiles + '</div>';
|
||||
})();
|
||||
|
||||
const orgName = (store.state.shared.organization && store.state.shared.organization.name) || '';
|
||||
const heroHtml = (
|
||||
'<section class="home-hero">' +
|
||||
'<h1>' + (orgName ? 'Hei, ' + escapeHtml(orgName) : 'ms-ai-architect') + '</h1>' +
|
||||
'<p>' + (orgName
|
||||
? 'Velg hvor du vil starte. Felles state er aktiv og forhåndsutfyller skjemaer.'
|
||||
: 'Single-file arkitektur-rådgivning for Microsoft AI-stakken. Start med onboarding for å aktivere felles state.'
|
||||
) + '</p>' +
|
||||
'</section>'
|
||||
);
|
||||
|
||||
const projectsSection = (
|
||||
'<section class="home-projects">' +
|
||||
'<div class="home-section-head">' +
|
||||
'<h2>Mine prosjekter</h2>' +
|
||||
'<span class="home-section-meta">' + projects.length + ' prosjekt' + (projects.length === 1 ? '' : 'er') + ' · maks ' + reportTotal + ' rapporter per prosjekt</span>' +
|
||||
'</div>' +
|
||||
projectListHtml +
|
||||
(projects.length > 0 ? '<div class="onboarding-actions" style="margin-top: var(--space-4);"><button type="button" class="btn btn--primary" data-action="new-project">Nytt prosjekt</button></div>' : '') +
|
||||
'</section>'
|
||||
);
|
||||
|
||||
root.innerHTML = (
|
||||
renderTopbar('Hjem') +
|
||||
'<div class="app-shell">' +
|
||||
heroHtml +
|
||||
tracksHtml +
|
||||
projectsSection +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
// Stub for project + catalog som fylles i Steps 7, 9.
|
||||
function renderProjectSurface() {
|
||||
const root = getSurfaceEl('project');
|
||||
if (!root) return;
|
||||
root.innerHTML = '<div class="app-shell"><p>Prosjekt-overflate fylles i Step 7.</p></div>';
|
||||
root.innerHTML = renderTopbar('Prosjekt') + '<div class="app-shell"><p>Prosjekt-detaljvisning fylles i Step 7.</p></div>';
|
||||
}
|
||||
function renderCatalogStub() {
|
||||
const root = getSurfaceEl('catalog');
|
||||
if (!root) return;
|
||||
root.innerHTML = '<div class="app-shell"><p>Command-katalog fylles i Step 9.</p></div>';
|
||||
root.innerHTML = renderTopbar('Katalog') + '<div class="app-shell"><p>Command-katalog fylles i Step 9.</p></div>';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -1593,6 +1745,57 @@
|
|||
if (input) input.focus();
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// NAV + EXPORT/IMPORT ACTIONS (Step 6)
|
||||
// ============================================================
|
||||
|
||||
ACTIONS['goto-home'] = function () { navigate('home'); };
|
||||
ACTIONS['goto-catalog'] = function () { navigate('catalog'); };
|
||||
ACTIONS['goto-onboarding'] = function () { navigate('onboarding'); };
|
||||
|
||||
ACTIONS['open-project'] = function (ev, el) {
|
||||
const id = el.dataset.projectId;
|
||||
if (!id) return;
|
||||
store.state.activeProjectId = id;
|
||||
navigate('project');
|
||||
};
|
||||
|
||||
// Stub — Step 7 erstatter med modal-åpning.
|
||||
ACTIONS['new-project'] = function () {
|
||||
console.log('[playground v3] new-project: modal kommer i Step 7');
|
||||
};
|
||||
|
||||
ACTIONS['export-state'] = function () {
|
||||
try { exportState(); }
|
||||
catch (err) { console.error('[playground v3] export feilet:', err); alert('Eksport feilet: ' + err.message); }
|
||||
};
|
||||
|
||||
ACTIONS['import-state'] = function (ev, el) {
|
||||
const topbar = el.closest('.topbar');
|
||||
if (!topbar) return;
|
||||
const input = topbar.querySelector('[data-import-input]');
|
||||
if (!input) return;
|
||||
input.value = ''; // tillat samme fil to ganger
|
||||
input.click();
|
||||
};
|
||||
|
||||
// File-input change handler (én gang for hele dokumentet — input genereres
|
||||
// fortløpende via renderTopbar, men endringen bobler).
|
||||
document.addEventListener('change', function (ev) {
|
||||
if (!ev.target.matches || !ev.target.matches('[data-import-input]')) return;
|
||||
const file = ev.target.files && ev.target.files[0];
|
||||
if (!file) return;
|
||||
importState(file)
|
||||
.then(function () {
|
||||
scheduleRender();
|
||||
alert('Import fullført. Nåværende state er erstattet av filens innhold.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('[playground v3] import feilet:', err);
|
||||
alert('Import feilet: ' + err.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Eksponer for Verify-asserts og Steps 6-9.
|
||||
window.__navigate = navigate;
|
||||
window.__scheduleRender = scheduleRender;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue