create-magelight-view 0.28.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Severause
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # create-magelight-view
2
+
3
+ ```bash
4
+ npm create magelight-view MyMod
5
+ cd MyMod && npm install && npm run build
6
+ ```
7
+
8
+ Produces a manifest mod (`manifest.json` + `views/config/index.html`) whose page is a React app
9
+ on `@magelight/react`. Copy the folder to `Data/Magelight/MyMod/`; F10 opens it in game. Drive it
10
+ from a DLL (`RegisterMod("MyMod")` adopts it) or from Papyrus (`Magelight.RegisterMod("MyMod")`).
package/index.js ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // npm create magelight-view MyMod → ./MyMod/ (manifest + React page)
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here = path.dirname(fileURLToPath(import.meta.url));
8
+ const args = process.argv.slice(2);
9
+ const local = args.includes('--local');
10
+ const modId = args.find(a => !a.startsWith('--'));
11
+ // Same slug rules as the host's ValidSlug: [A-Za-z0-9_.-], not dot-only,
12
+ // not dot-ended, not a Windows device name.
13
+ const valid = modId && /^[A-Za-z0-9_.-]{1,32}$/.test(modId) && /[A-Za-z0-9]/.test(modId) &&
14
+ !modId.endsWith('.') && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/i.test(modId);
15
+ if (!valid) {
16
+ console.error('usage: npm create magelight-view <ModId> (slug: [A-Za-z0-9_.-], 1..32 chars, not dot-only/dot-ended/a device name)');
17
+ process.exit(1);
18
+ }
19
+ const dst = path.resolve(process.cwd(), modId);
20
+ if (fs.existsSync(dst)) {
21
+ console.error(`${dst} already exists`);
22
+ process.exit(1);
23
+ }
24
+ const tpl = path.join(here, 'template');
25
+ for (const f of fs.readdirSync(tpl, { recursive: true })) {
26
+ const src = path.join(tpl, f);
27
+ if (!fs.statSync(src).isFile()) continue;
28
+ const to = path.join(dst, f.replace(/^_/, '.'));
29
+ fs.mkdirSync(path.dirname(to), { recursive: true });
30
+ fs.writeFileSync(to, fs.readFileSync(src, 'utf8').replaceAll('__MODID__', modId));
31
+ }
32
+ // --local: point the @magelight/* deps at the cloned packages (file:) so the
33
+ // scaffold builds WITHOUT the packages being published to npm. Requires the
34
+ // packages to be built first (npm install && npm run build at the repo root).
35
+ if (local) {
36
+ const pkgPath = path.join(dst, 'package.json');
37
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
38
+ const rel = { '@magelight/sdk': 'sdk', '@magelight/react': 'react', '@magelight/vite-plugin': 'vite-plugin' };
39
+ for (const grp of ['dependencies', 'devDependencies']) {
40
+ for (const dep of Object.keys(pkg[grp] || {})) {
41
+ if (rel[dep]) pkg[grp][dep] = 'file:' + path.join(here, '..', rel[dep]).split(path.sep).join('/');
42
+ }
43
+ }
44
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
45
+ console.log(`created ${modId}/ (--local: @magelight/* point at the cloned packages)
46
+ build the packages ONCE: (repo root) npm install && npm run build
47
+ then: cd ${modId} && npm install && npm run build → ${modId}/views/config/index.html`);
48
+ } else {
49
+ console.log(`created ${modId}/
50
+ npm install && npm run build → ${modId}/views/config/index.html
51
+ (the @magelight/* packages must be published to npm; if you cloned the repo, re-run with --local)
52
+ copy the folder to Data/Magelight/${modId}/ (a mod manager mod with Magelight/${modId}/ inside works too)
53
+ in game: the manifest's hotkey opens the page. To hot-reload page edits, set
54
+ \"devMode\": true in My Games/Skyrim Special Edition/SKSE/Magelight.json`);
55
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "create-magelight-view",
3
+ "version": "0.28.6",
4
+ "description": "Scaffold a Magelight UI mod: manifest.json + a React config page wired to @magelight/react.",
5
+ "license": "MIT",
6
+ "repository": { "type": "git", "url": "git+https://github.com/Severause/MagelightUI.git", "directory": "packages/create-magelight-view" },
7
+ "type": "module",
8
+ "bin": { "create-magelight-view": "index.js" },
9
+ "files": ["index.js", "template", "README.md"],
10
+ "engines": { "node": ">=20" }
11
+ }
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ views/
4
+ *.log
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>__MODID__</title>
6
+ <style>html, body { margin: 0; background: transparent; }</style>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,15 @@
1
+ {
2
+ "modId": "__MODID__",
3
+ "name": "__MODID__",
4
+ "version": "0.1.0",
5
+ "minHost": "0.16.0",
6
+ "views": {
7
+ "config": {
8
+ "path": "views/config/index.html",
9
+ "layer": "panel",
10
+ "anchor": "top-left",
11
+ "x": 120, "y": 120, "w": 560, "h": 380,
12
+ "hotkey": "F10"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "__MODID__-ui",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build"
9
+ },
10
+ "dependencies": {
11
+ "@magelight/react": "^0.28.6",
12
+ "@magelight/sdk": "^0.28.6",
13
+ "react": "^19.2.0",
14
+ "react-dom": "^19.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@magelight/vite-plugin": "^0.28.6",
18
+ "@types/react": "^19.2.7",
19
+ "@types/react-dom": "^19.2.3",
20
+ "@vitejs/plugin-react": "^5.1.1",
21
+ "typescript": "~5.9.3",
22
+ "vite": "^7.3.1"
23
+ }
24
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useChannel, useHost, useSend, useUIMode, HostGate } from '@magelight/react';
3
+
4
+ // Host → page: your DLL/Papyrus sends InteropCall(view, 'state', json).
5
+ // Page → host: send('save', {...}) reaches the listener you registered as 'save'.
6
+ interface State { volume: number; subtitles: boolean }
7
+
8
+ const panel: React.CSSProperties = {
9
+ boxSizing: 'border-box', width: 560, height: 380, padding: '18px 22px',
10
+ background: '#1e1913', border: '1px solid #5a4a2e', borderRadius: 6,
11
+ color: '#e8dcc4', font: '14px Georgia, serif',
12
+ };
13
+
14
+ export function App() {
15
+ const host = useHost();
16
+ const hostState = useChannel<State>('state', { volume: 50, subtitles: true });
17
+ const focused = useUIMode();
18
+ const send = useSend();
19
+ // Local state so the page is usable as a manifest-only mod (nothing echoes
20
+ // 'state' back yet): the control moves immediately, and whatever the host
21
+ // sends on the 'state' channel still wins when it arrives.
22
+ const [state, setState] = useState<State>(hostState);
23
+ useEffect(() => { setState(hostState); }, [hostState]);
24
+ const update = (next: State) => { setState(next); send('save', next); };
25
+
26
+ return (
27
+ <HostGate fallback={<div style={panel}>Open this page inside Skyrim (or use the mock panel, bottom right).</div>}>
28
+ <div style={panel}>
29
+ <h1 style={{ margin: '0 0 8px', fontSize: 18, color: '#cba560', fontWeight: 'normal' }}>__MODID__</h1>
30
+ <p style={{ color: '#8a7a5a', fontSize: 12, margin: '0 0 14px' }}>
31
+ Magelight {host.version} · view {host.viewName} · {focused ? 'keyboard is yours' : 'HUD mode'}
32
+ </p>
33
+ <label style={{ display: 'block', margin: '10px 0' }}>
34
+ Volume {state.volume}
35
+ <input type="range" min={0} max={100} value={state.volume} style={{ width: '100%' }}
36
+ onChange={(e) => update({ ...state, volume: +e.target.value })} />
37
+ </label>
38
+ <label style={{ display: 'block', margin: '10px 0' }}>
39
+ <input type="checkbox" checked={state.subtitles}
40
+ onChange={(e) => update({ ...state, subtitles: e.target.checked })} /> Subtitles
41
+ </label>
42
+ <p style={{ color: '#8a7a5a', fontSize: 12 }}>F10 or Esc closes this page.</p>
43
+ </div>
44
+ </HostGate>
45
+ );
46
+ }
@@ -0,0 +1,4 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import { App } from './App';
3
+
4
+ createRoot(document.getElementById('root')!).render(<App />);
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2020", "DOM"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { magelight } from '@magelight/vite-plugin';
4
+
5
+ export default defineConfig({
6
+ plugins: [react(), magelight({ entries: { config: 'index.html' } })],
7
+ });