diff --git a/index.html b/index.html index 4ebd98a..0e5a030 100644 --- a/index.html +++ b/index.html @@ -588,6 +588,98 @@ border-radius: 0; } + /* ── Wizard ────────────────────────────────────────── */ + #wizard-overlay { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgba(12, 18, 33, 0.97); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + } + #wizard-overlay[hidden] { display: none; } + #wizard-card { + width: 100%; + max-width: 420px; + display: flex; + flex-direction: column; + align-items: center; + } + .wizard-logo { + width: 72px; height: 72px; + border-radius: 18px; + overflow: hidden; + margin-bottom: 18px; + box-shadow: 0 0 32px rgba(34,211,238,0.3); + } + .wizard-logo img { width: 100%; height: 100%; object-fit: cover; } + .wizard-title { + font-size: 22px; + font-weight: 600; + color: var(--text); + margin: 0 0 6px; + letter-spacing: -0.03em; + } + .wizard-subtitle { + font-size: 13px; + color: var(--text3); + margin: 0 0 32px; + } + .wizard-options { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + margin-bottom: 20px; + } + .wizard-option { + display: flex; + align-items: center; + gap: 14px; + text-align: left; + padding: 14px 16px; + border-radius: 12px; + background: rgba(255,255,255,0.03); + border: 1px solid var(--border); + cursor: pointer; + transition: background 0.15s, border-color 0.15s; + width: 100%; + color: var(--text); + } + .wizard-option:hover { + background: rgba(34,211,238,0.08); + border-color: rgba(34,211,238,0.3); + } + .wizard-option-icon { + width: 38px; height: 38px; + border-radius: 10px; + background: rgba(34,211,238,0.1); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--accent); + } + .wizard-option-body { flex: 1; } + .wizard-option-title { + font-size: 13px; + font-weight: 500; + margin-bottom: 2px; + } + .wizard-option-desc { + font-size: 11px; + color: var(--text3); + } + .wizard-hint { + font-size: 11px; + color: var(--text3); + margin: 0; + } + /* ── Edge label tooltip ────────────────────────────── */ #edge-tooltip { position: fixed; @@ -630,13 +722,17 @@
データ
+
@@ -834,6 +930,53 @@
+ + + + + +
@@ -880,9 +1023,7 @@ async function loadData() { if (saved) { try { atlasData = JSON.parse(saved); return; } catch (e) {} } - const res = await fetch('/atlas.json'); - atlasData = await res.json(); - saveData(); + // No saved data — wizard will handle initialization } function saveData() { @@ -1397,16 +1538,42 @@ function bindEvents() { .then(() => showToast('クリップボードにコピーしました')); }); + document.getElementById('btnImportJson').addEventListener('click', () => { + document.getElementById('fileInput').click(); + }); document.getElementById('btnExportJson').addEventListener('click', () => { exportJson(); showToast('エクスポートしました'); }); document.getElementById('btnResetData').addEventListener('click', () => { - if (!confirm('データをデフォルトに戻しますか?')) return; + if (!confirm('サンプルデータに戻しますか?現在のデータは失われます。')) return; localStorage.removeItem(STORAGE_KEY); location.reload(); }); + // File input (wizard + settings import) + document.getElementById('fileInput').addEventListener('change', e => { + const isWizard = !document.getElementById('wizard-overlay').hasAttribute('hidden'); + handleImportFile(e.target.files[0], isWizard); + }); + + // Wizard buttons + document.getElementById('w-sample').addEventListener('click', async () => { + const res = await fetch('/atlas.json'); + const data = await res.json(); + finishWizard(data); + }); + document.getElementById('w-empty').addEventListener('click', () => { + finishWizard({ + meta: { owner: '', description: 'My infrastructure', updated: new Date().toISOString().slice(0, 10), version: '1' }, + nodes: [], + edges: [], + }); + }); + document.getElementById('w-import').addEventListener('click', () => { + document.getElementById('fileInput').click(); + }); + window.addEventListener('resize', () => { if (simulation) { const svg = d3.select('#graph-svg'); @@ -1424,12 +1591,62 @@ if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } -// ── Init ─────────────────────────────────────────────────────── -loadData().then(() => { +// ── Wizard ───────────────────────────────────────────────────── +function showWizard() { + const overlay = document.getElementById('wizard-overlay'); + overlay.removeAttribute('hidden'); + if (window.lucide) lucide.createIcons({ nodes: [overlay] }); +} + +function finishWizard(data) { + atlasData = data; + saveData(); + document.getElementById('wizard-overlay').setAttribute('hidden', ''); buildFilterBar(); initGraph(); - bindEvents(); setTimeout(fitGraph, 800); + if (atlasData.nodes.length === 0) { + setTimeout(openAddModal, 700); + } +} + +// ── File import (shared: wizard + settings) ──────────────────── +function handleImportFile(file, isWizard) { + if (!file) return; + const reader = new FileReader(); + reader.onload = evt => { + try { + const data = JSON.parse(evt.target.result); + if (!Array.isArray(data.nodes) || !Array.isArray(data.edges)) throw new Error(); + if (isWizard) { + finishWizard(data); + } else { + atlasData = data; + saveData(); + closeDetail(); + buildFilterBar(); + initGraph(); + setTimeout(fitGraph, 500); + showToast('インポートしました'); + } + } catch (e) { + showToast('atlas.json の形式が正しくありません'); + } + document.getElementById('fileInput').value = ''; + }; + reader.readAsText(file); +} + +// ── Init ─────────────────────────────────────────────────────── +loadData().then(() => { + bindEvents(); + if (!atlasData) { + showWizard(); + } else { + buildFilterBar(); + initGraph(); + setTimeout(fitGraph, 800); + } });