feat: first-run wizard + atlas.json import
This commit is contained in:
parent
8c7bf566e1
commit
a61ebf7a02
233
index.html
233
index.html
|
|
@ -588,6 +588,98 @@
|
||||||
border-radius: 0;
|
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 label tooltip ────────────────────────────── */
|
||||||
#edge-tooltip {
|
#edge-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -630,13 +722,17 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="settings-group-label">データ</div>
|
<div class="settings-group-label">データ</div>
|
||||||
<div class="settings-item" style="flex-direction:column;align-items:flex-start;gap:8px">
|
<div class="settings-item" style="flex-direction:column;align-items:flex-start;gap:8px">
|
||||||
|
<button class="btn btn-secondary" id="btnImportJson" style="width:100%;font-size:12px">
|
||||||
|
<i data-lucide="upload" style="width:13px;height:13px;stroke-width:1.75"></i>
|
||||||
|
atlas.json をインポート
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" id="btnExportJson" style="width:100%;font-size:12px">
|
<button class="btn btn-secondary" id="btnExportJson" style="width:100%;font-size:12px">
|
||||||
<i data-lucide="download" style="width:13px;height:13px;stroke-width:1.75"></i>
|
<i data-lucide="download" style="width:13px;height:13px;stroke-width:1.75"></i>
|
||||||
atlas.json をエクスポート
|
atlas.json をエクスポート
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary" id="btnResetData" style="width:100%;font-size:12px;color:var(--text3)">
|
<button class="btn btn-secondary" id="btnResetData" style="width:100%;font-size:12px;color:var(--text3)">
|
||||||
<i data-lucide="rotate-ccw" style="width:13px;height:13px;stroke-width:1.75"></i>
|
<i data-lucide="rotate-ccw" style="width:13px;height:13px;stroke-width:1.75"></i>
|
||||||
デフォルトに戻す
|
サンプルデータに戻す
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -834,6 +930,53 @@
|
||||||
<!-- Edge label tooltip -->
|
<!-- Edge label tooltip -->
|
||||||
<div id="edge-tooltip"></div>
|
<div id="edge-tooltip"></div>
|
||||||
|
|
||||||
|
<!-- Wizard (first-run) -->
|
||||||
|
<div id="wizard-overlay" hidden>
|
||||||
|
<div id="wizard-card">
|
||||||
|
<div class="wizard-logo">
|
||||||
|
<img src="/logo.png" alt="Atlas" width="72" height="72">
|
||||||
|
</div>
|
||||||
|
<h1 class="wizard-title">Atlas へようこそ</h1>
|
||||||
|
<p class="wizard-subtitle">インフラ構成図・サービス依存マップ</p>
|
||||||
|
<div class="wizard-options">
|
||||||
|
<button class="wizard-option" id="w-sample">
|
||||||
|
<div class="wizard-option-icon">
|
||||||
|
<i data-lucide="layout-dashboard" style="width:18px;height:18px;stroke-width:1.5"></i>
|
||||||
|
</div>
|
||||||
|
<div class="wizard-option-body">
|
||||||
|
<div class="wizard-option-title">サンプルを使う</div>
|
||||||
|
<div class="wizard-option-desc">一般的な開発環境のデータで試してみる</div>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="chevron-right" style="width:16px;height:16px;stroke-width:1.5;color:var(--text3)"></i>
|
||||||
|
</button>
|
||||||
|
<button class="wizard-option" id="w-empty">
|
||||||
|
<div class="wizard-option-icon">
|
||||||
|
<i data-lucide="plus-circle" style="width:18px;height:18px;stroke-width:1.5"></i>
|
||||||
|
</div>
|
||||||
|
<div class="wizard-option-body">
|
||||||
|
<div class="wizard-option-title">空から始める</div>
|
||||||
|
<div class="wizard-option-desc">自分の環境をゼロから追加する</div>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="chevron-right" style="width:16px;height:16px;stroke-width:1.5;color:var(--text3)"></i>
|
||||||
|
</button>
|
||||||
|
<button class="wizard-option" id="w-import">
|
||||||
|
<div class="wizard-option-icon">
|
||||||
|
<i data-lucide="upload" style="width:18px;height:18px;stroke-width:1.5"></i>
|
||||||
|
</div>
|
||||||
|
<div class="wizard-option-body">
|
||||||
|
<div class="wizard-option-title">ファイルから読み込む</div>
|
||||||
|
<div class="wizard-option-desc">既存の atlas.json をアップロード</div>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="chevron-right" style="width:16px;height:16px;stroke-width:1.5;color:var(--text3)"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="wizard-hint">ノードは後からいつでも追加・編集できます</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden file input (shared by wizard + settings import) -->
|
||||||
|
<input type="file" id="fileInput" accept=".json" style="display:none">
|
||||||
|
|
||||||
<div id="toast" role="status" aria-live="polite"></div>
|
<div id="toast" role="status" aria-live="polite"></div>
|
||||||
|
|
||||||
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
||||||
|
|
@ -880,9 +1023,7 @@ async function loadData() {
|
||||||
if (saved) {
|
if (saved) {
|
||||||
try { atlasData = JSON.parse(saved); return; } catch (e) {}
|
try { atlasData = JSON.parse(saved); return; } catch (e) {}
|
||||||
}
|
}
|
||||||
const res = await fetch('/atlas.json');
|
// No saved data — wizard will handle initialization
|
||||||
atlasData = await res.json();
|
|
||||||
saveData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveData() {
|
function saveData() {
|
||||||
|
|
@ -1397,16 +1538,42 @@ function bindEvents() {
|
||||||
.then(() => showToast('クリップボードにコピーしました'));
|
.then(() => showToast('クリップボードにコピーしました'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnImportJson').addEventListener('click', () => {
|
||||||
|
document.getElementById('fileInput').click();
|
||||||
|
});
|
||||||
document.getElementById('btnExportJson').addEventListener('click', () => {
|
document.getElementById('btnExportJson').addEventListener('click', () => {
|
||||||
exportJson();
|
exportJson();
|
||||||
showToast('エクスポートしました');
|
showToast('エクスポートしました');
|
||||||
});
|
});
|
||||||
document.getElementById('btnResetData').addEventListener('click', () => {
|
document.getElementById('btnResetData').addEventListener('click', () => {
|
||||||
if (!confirm('データをデフォルトに戻しますか?')) return;
|
if (!confirm('サンプルデータに戻しますか?現在のデータは失われます。')) return;
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
location.reload();
|
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', () => {
|
window.addEventListener('resize', () => {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
const svg = d3.select('#graph-svg');
|
const svg = d3.select('#graph-svg');
|
||||||
|
|
@ -1424,12 +1591,62 @@ if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/sw.js');
|
navigator.serviceWorker.register('/sw.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Init ───────────────────────────────────────────────────────
|
// ── Wizard ─────────────────────────────────────────────────────
|
||||||
loadData().then(() => {
|
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();
|
buildFilterBar();
|
||||||
initGraph();
|
initGraph();
|
||||||
bindEvents();
|
|
||||||
setTimeout(fitGraph, 800);
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue