3a7340975f
Campaign CRUD API with cascading delete across all campaign-scoped tables, a systems endpoint to seed the create-campaign form, active campaign state wired to the sidebar chaos factor, and a campaign management view for creating/switching/deleting campaigns.
148 lines
4.3 KiB
JavaScript
148 lines
4.3 KiB
JavaScript
// Mythic Oracle — entry, sidebar routing, campaign context
|
|
|
|
import { getCampaign, updateCampaign } from './api.js';
|
|
|
|
const STORAGE_KEYS = {
|
|
sidebarCollapsed: 'mo-sidebar-collapsed',
|
|
activeView: 'mo-active-view',
|
|
theme: 'mo-theme',
|
|
activeCampaignId: 'mo-active-campaign-id',
|
|
};
|
|
|
|
const CHAOS_MIN = 1;
|
|
const CHAOS_MAX = 9;
|
|
|
|
let activeCampaign = null;
|
|
let navigateToView = null;
|
|
|
|
function initSidebarCollapse() {
|
|
const sidebar = document.getElementById('sidebar');
|
|
const toggle = document.getElementById('collapseToggle');
|
|
|
|
const collapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true';
|
|
sidebar.classList.toggle('collapsed', collapsed);
|
|
|
|
toggle.addEventListener('click', () => {
|
|
const isCollapsed = sidebar.classList.toggle('collapsed');
|
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, isCollapsed);
|
|
});
|
|
}
|
|
|
|
function initNavRouting() {
|
|
const navItems = document.querySelectorAll('.nav-item[data-view]');
|
|
const views = document.querySelectorAll('.view');
|
|
|
|
function activateView(viewId) {
|
|
navItems.forEach((item) => {
|
|
item.classList.toggle('active', item.dataset.view === viewId);
|
|
});
|
|
views.forEach((view) => {
|
|
view.classList.toggle('active', view.id === `view-${viewId}`);
|
|
});
|
|
localStorage.setItem(STORAGE_KEYS.activeView, viewId);
|
|
}
|
|
|
|
navItems.forEach((item) => {
|
|
item.addEventListener('click', () => activateView(item.dataset.view));
|
|
});
|
|
|
|
navigateToView = activateView;
|
|
|
|
const savedView = localStorage.getItem(STORAGE_KEYS.activeView);
|
|
const validView = savedView && document.getElementById(`view-${savedView}`);
|
|
activateView(validView ? savedView : 'oracle');
|
|
}
|
|
|
|
function initThemeToggle() {
|
|
const toggle = document.getElementById('themeToggle');
|
|
const root = document.documentElement;
|
|
|
|
const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) || 'dark';
|
|
root.setAttribute('data-theme', savedTheme);
|
|
|
|
toggle.addEventListener('click', () => {
|
|
const nextTheme = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
root.setAttribute('data-theme', nextTheme);
|
|
localStorage.setItem(STORAGE_KEYS.theme, nextTheme);
|
|
});
|
|
}
|
|
|
|
function renderChaosFactor() {
|
|
const value = document.getElementById('chaosValue');
|
|
value.textContent = activeCampaign ? activeCampaign.chaos_factor : '—';
|
|
}
|
|
|
|
function renderActiveCampaignName() {
|
|
const el = document.getElementById('activeCampaignName');
|
|
el.textContent = activeCampaign ? activeCampaign.name : 'No active campaign';
|
|
}
|
|
|
|
function initChaosFactorControls() {
|
|
const minus = document.getElementById('chaosMinus');
|
|
const plus = document.getElementById('chaosPlus');
|
|
|
|
async function adjust(delta) {
|
|
if (!activeCampaign) return;
|
|
|
|
const next = Math.min(CHAOS_MAX, Math.max(CHAOS_MIN, activeCampaign.chaos_factor + delta));
|
|
if (next === activeCampaign.chaos_factor) return;
|
|
|
|
const updated = await updateCampaign(activeCampaign.id, { chaos_factor: next });
|
|
activeCampaign = updated;
|
|
renderChaosFactor();
|
|
}
|
|
|
|
minus.addEventListener('click', () => adjust(-1));
|
|
plus.addEventListener('click', () => adjust(1));
|
|
}
|
|
|
|
export async function setActiveCampaign(campaign) {
|
|
if (!campaign) {
|
|
activeCampaign = null;
|
|
localStorage.removeItem(STORAGE_KEYS.activeCampaignId);
|
|
renderChaosFactor();
|
|
renderActiveCampaignName();
|
|
return;
|
|
}
|
|
|
|
// Always re-fetch: the passed-in object (e.g. from a cached list) may be
|
|
// stale if the chaos factor was changed elsewhere since it was loaded.
|
|
const fresh = await getCampaign(campaign.id);
|
|
activeCampaign = fresh;
|
|
localStorage.setItem(STORAGE_KEYS.activeCampaignId, fresh.id);
|
|
renderChaosFactor();
|
|
renderActiveCampaignName();
|
|
}
|
|
|
|
export function getActiveCampaign() {
|
|
return activeCampaign;
|
|
}
|
|
|
|
async function initActiveCampaign() {
|
|
const savedId = localStorage.getItem(STORAGE_KEYS.activeCampaignId);
|
|
|
|
if (!savedId) {
|
|
renderChaosFactor();
|
|
renderActiveCampaignName();
|
|
navigateToView('campaign-management');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await setActiveCampaign({ id: savedId });
|
|
} catch {
|
|
await setActiveCampaign(null);
|
|
navigateToView('campaign-management');
|
|
}
|
|
}
|
|
|
|
async function init() {
|
|
initSidebarCollapse();
|
|
initNavRouting();
|
|
initThemeToggle();
|
|
initChaosFactorControls();
|
|
await initActiveCampaign();
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|