Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
/* Dev-scoped Dark/Light theme toggle for the theme lab.
|
|
*
|
|
* Flips `document.documentElement.dataset.theme` and persists to
|
|
* **sessionStorage** intentionally — the lab is a tab-scoped
|
|
* exploration tool. Global persistence (across reloads, all users)
|
|
* is the user-facing Config toggle that ships in Task 6. */
|
|
|
|
export const THEME_SESSION_KEY = 'decnet_theme_lab';
|
|
|
|
export type Theme = 'dark' | 'light';
|
|
|
|
export function readLabTheme(): Theme {
|
|
try {
|
|
const v = sessionStorage.getItem(THEME_SESSION_KEY);
|
|
return v === 'light' ? 'light' : 'dark';
|
|
} catch {
|
|
return 'dark';
|
|
}
|
|
}
|
|
|
|
export function applyTheme(theme: Theme): void {
|
|
document.documentElement.dataset.theme = theme;
|
|
}
|
|
|
|
const ThemeToggle: React.FC = () => {
|
|
const [theme, setTheme] = useState<Theme>(() => readLabTheme());
|
|
|
|
useEffect(() => {
|
|
applyTheme(theme);
|
|
try { sessionStorage.setItem(THEME_SESSION_KEY, theme); } catch { /* ignore */ }
|
|
}, [theme]);
|
|
|
|
return (
|
|
<div className="lab-theme-toggle" role="group" aria-label="Theme">
|
|
<button
|
|
type="button"
|
|
className={`btn small ${theme === 'dark' ? '' : 'ghost'}`}
|
|
onClick={() => setTheme('dark')}
|
|
aria-pressed={theme === 'dark'}
|
|
>
|
|
DARK
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`btn small ${theme === 'light' ? '' : 'ghost'}`}
|
|
onClick={() => setTheme('light')}
|
|
aria-pressed={theme === 'light'}
|
|
>
|
|
LIGHT
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ThemeToggle;
|