145 lines
6.9 KiB
HTML
145 lines
6.9 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8"/>
|
|||
|
|
<title>MeshiTrack</title>
|
|||
|
|
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|||
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
|||
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|||
|
|
<link rel="stylesheet" href="styles/tokens.css"/>
|
|||
|
|
<link rel="stylesheet" href="styles/shell.css"/>
|
|||
|
|
<link rel="stylesheet" href="styles/cabinet.css"/>
|
|||
|
|
<link rel="stylesheet" href="styles/dashboard.css"/>
|
|||
|
|
<link rel="stylesheet" href="styles/schedule.css?v=2"/>
|
|||
|
|
<link rel="stylesheet" href="styles/other.css?v=2"/>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div id="root"></div>
|
|||
|
|
|
|||
|
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|||
|
|
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|||
|
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|||
|
|
|
|||
|
|
<script type="text/babel" src="src/data.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/Icon.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/Shell.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/Cabinet.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/Dashboard.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/Schedule.jsx"></script>
|
|||
|
|
<script type="text/babel" src="src/OtherPages.jsx"></script>
|
|||
|
|
|
|||
|
|
<script type="text/babel">
|
|||
|
|
const { useState, useEffect } = React;
|
|||
|
|
|
|||
|
|
const TWEAKS = /*EDITMODE-BEGIN*/{
|
|||
|
|
"theme": "light",
|
|||
|
|
"accent": "sage"
|
|||
|
|
}/*EDITMODE-END*/;
|
|||
|
|
|
|||
|
|
const ACCENTS = {
|
|||
|
|
sage: { brand: '#2f6b4a', deep: '#1e4a32', soft: '#e6efe8' },
|
|||
|
|
cobalt: { brand: '#2e5aa8', deep: '#1d3d75', soft: '#e4eaf5' },
|
|||
|
|
terracotta: { brand: '#b55438', deep: '#7d3825', soft: '#f6e6de' },
|
|||
|
|
graphite: { brand: '#2c2c28', deep: '#000', soft: '#e8e6df' },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const App = () => {
|
|||
|
|
const [route, setRoute] = useState('dashboard');
|
|||
|
|
const [theme, setTheme] = useState(TWEAKS.theme || 'light');
|
|||
|
|
const [accent, setAccent] = useState(TWEAKS.accent || 'sage');
|
|||
|
|
const [tweaksOpen, setTweaksOpen] = useState(false);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|||
|
|
const a = ACCENTS[accent] || ACCENTS.sage;
|
|||
|
|
document.documentElement.style.setProperty('--brand', a.brand);
|
|||
|
|
document.documentElement.style.setProperty('--brand-deep', a.deep);
|
|||
|
|
document.documentElement.style.setProperty('--brand-soft', a.soft);
|
|||
|
|
document.documentElement.style.setProperty('--brand-soft-ink', a.deep);
|
|||
|
|
document.documentElement.style.setProperty('--viz-1', a.brand);
|
|||
|
|
}, [theme, accent]);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const onMsg = (e) => {
|
|||
|
|
if (!e.data || typeof e.data !== 'object') return;
|
|||
|
|
if (e.data.type === '__activate_edit_mode') setTweaksOpen(true);
|
|||
|
|
if (e.data.type === '__deactivate_edit_mode') setTweaksOpen(false);
|
|||
|
|
};
|
|||
|
|
window.addEventListener('message', onMsg);
|
|||
|
|
window.parent.postMessage({ type: '__edit_mode_available' }, '*');
|
|||
|
|
return () => window.removeEventListener('message', onMsg);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const setTweak = (key, val) => {
|
|||
|
|
window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const { Sidebar, Topbar, NAV } = window.Shell;
|
|||
|
|
const current = NAV.find(n => n.id === route);
|
|||
|
|
|
|||
|
|
const pages = {
|
|||
|
|
dashboard: { title: 'Dashboard', subtitle: 'Household: Red Panda Den', Page: window.DashboardPage },
|
|||
|
|
cabinet: { title: 'Medicine Cabinet', subtitle: 'Everything on hand, with days of supply', Page: window.CabinetPage },
|
|||
|
|
schedule: { title: 'Schedule & Log', subtitle: 'Today and this week', Page: window.SchedulePage },
|
|||
|
|
regimens: { title: 'Regimens', subtitle: 'Daily medication schedules', crumbs: ['Medicines', 'Regimens'], Page: window.RegimensPage },
|
|||
|
|
organizer: { title: 'Pill Organizer', subtitle: 'Fill a week of pills at once', crumbs: ['Medicines', 'Organizer'], Page: window.OrganizerPage },
|
|||
|
|
activity: { title: 'Activity', subtitle: 'Spending and cabinet changes', crumbs: ['Medicines', 'Activity'], Page: window.ActivityPage },
|
|||
|
|
library: { title: 'Library', subtitle: 'All known medicines', crumbs: ['Medicines', 'Library'], Page: window.LibraryPage },
|
|||
|
|
refills: { title: 'Refills', subtitle: 'Running-low alerts and shopping lists', Page: window.RefillsPage },
|
|||
|
|
purchases: { title: 'Purchases', subtitle: 'Order history and pending arrivals', Page: window.PurchasesPage },
|
|||
|
|
prices: { title: 'Prices', subtitle: 'Track & compare across stores', Page: window.PricesPage },
|
|||
|
|
stores: { title: 'Stores', subtitle: 'Pharmacies and vendors', Page: window.StoresPage },
|
|||
|
|
settings: { title: 'Settings', subtitle: 'Household and account', Page: window.SettingsPage },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const pg = pages[route];
|
|||
|
|
const Page = pg.Page;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="mt-app">
|
|||
|
|
<Sidebar route={route} onNav={setRoute} />
|
|||
|
|
<div className="mt-main" data-screen-label={'MT / ' + pg.title}>
|
|||
|
|
<Topbar
|
|||
|
|
title={pg.title}
|
|||
|
|
subtitle={pg.subtitle}
|
|||
|
|
crumbs={pg.crumbs}
|
|||
|
|
theme={theme}
|
|||
|
|
onToggleTheme={() => { const t = theme === 'light' ? 'dark' : 'light'; setTheme(t); setTweak('theme', t); }}
|
|||
|
|
actions={route === 'cabinet' ? <button className="mt-btn mt-btn--primary"><span>+</span> Add to cabinet</button> : null}
|
|||
|
|
/>
|
|||
|
|
<Page onNav={setRoute} />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className={'mt-tweaks' + (tweaksOpen ? ' is-open' : '')}>
|
|||
|
|
<div className="mt-tweaks__title">
|
|||
|
|
<span>Tweaks</span>
|
|||
|
|
<button className="mt-btn mt-btn--subtle" onClick={() => setTweaksOpen(false)} style={{padding:'2px 6px'}}>×</button>
|
|||
|
|
</div>
|
|||
|
|
<div className="mt-tweaks__row">
|
|||
|
|
<span>Theme</span>
|
|||
|
|
<div className="mt-seg">
|
|||
|
|
<button className={theme==='light'?'is-active':''} onClick={()=>{setTheme('light'); setTweak('theme','light');}}>Light</button>
|
|||
|
|
<button className={theme==='dark'?'is-active':''} onClick={()=>{setTheme('dark'); setTweak('theme','dark');}}>Dark</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="mt-tweaks__row">
|
|||
|
|
<span>Accent</span>
|
|||
|
|
<div className="mt-seg">
|
|||
|
|
{Object.keys(ACCENTS).map(k => (
|
|||
|
|
<button key={k} className={accent===k?'is-active':''} onClick={()=>{setAccent(k); setTweak('accent',k);}} style={{textTransform:'capitalize'}}>{k}</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div style={{fontSize: 10, color: 'var(--ink-faint)', marginTop: 8, lineHeight: 1.5}}>
|
|||
|
|
See alt-directions canvas for 3 novel cabinet layouts.
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|