@thomasfarineau/anvil 0.0.1

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.
@@ -0,0 +1,6 @@
1
+ // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ app_lib::run();
6
+ }
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ src-anvil/target/
3
+ src-anvil/gen/
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "enables the default permissions",
5
+ "windows": ["main"],
6
+ "permissions": ["core:default"]
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "node_modules/anvil/src/client/config.schema.json",
3
+ "identifier": "",
4
+ "target": "",
5
+ "app_name": "My Launcher",
6
+ "data_folder": ".my-launcher",
7
+ "java_version": 21,
8
+ "update_url": "",
9
+ "logo": "",
10
+ "session": "none",
11
+ "window_decorations": true,
12
+ "window_resizable": false,
13
+ "instances": [
14
+ {
15
+ "id": "survival",
16
+ "name": "Survival",
17
+ "mc_version": "1.21.4"
18
+ }
19
+ ]
20
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,43 @@
1
+ // tauri-mc-launcher client API
2
+ // Requires withGlobalTauri: true in tauri.conf.json (set by default).
3
+ // Import this file as an ES module: import { MC } from '/api.js';
4
+
5
+ const _invoke = window.__TAURI__.core.invoke;
6
+ const _listen = window.__TAURI__.event.listen;
7
+ const _window = window.__TAURI__.window;
8
+
9
+ export const MC = {
10
+ // ── Config & settings ──────────────────────────────────────
11
+ getConfig: () => _invoke('get_server_config'),
12
+ getSettings: () => _invoke('get_settings'),
13
+ saveSettings: (settings) => _invoke('save_settings', { settings }),
14
+ getDefaultDir: () => _invoke('get_default_launcher_dir'),
15
+
16
+ // ── Init / install ─────────────────────────────────────────
17
+ getInitStatus: () => _invoke('get_init_status'),
18
+ runSetup: () => _invoke('run_setup'),
19
+
20
+ // ── Game ───────────────────────────────────────────────────
21
+ verify: (instanceId) => _invoke('verify_game', { instanceId }),
22
+ play: (instanceId) => _invoke('launch_game', { instanceId }),
23
+
24
+ // ── Updater ────────────────────────────────────────────────
25
+ checkUpdate: () => _invoke('check_update'),
26
+ doUpdate: (url) => _invoke('do_update', { url }),
27
+
28
+ // ── Session ────────────────────────────────────────────────
29
+ setSession: (session) => _invoke('set_custom_session', { session }),
30
+ clearSession: () => _invoke('set_custom_session', { session: null }),
31
+
32
+ // ── Window ─────────────────────────────────────────────────
33
+ close: () => _window.getCurrentWindow().close(),
34
+
35
+ // ── Events ─────────────────────────────────────────────────
36
+ on: {
37
+ setupProgress: (cb) => _listen('setup:progress', (e) => cb(e.payload)),
38
+ setupDone: (cb) => _listen('setup:done', cb),
39
+ gameStarting: (cb) => _listen('game:starting', (e) => cb(e.payload)),
40
+ gameOutput: (cb) => _listen('game:output', (e) => cb(e.payload)),
41
+ gameExit: (cb) => _listen('game:exit', (e) => cb(e.payload)),
42
+ },
43
+ };
@@ -0,0 +1,399 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Launcher</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+ body {
14
+ font-family:
15
+ system-ui,
16
+ -apple-system,
17
+ sans-serif;
18
+ background: #0d0d0d;
19
+ color: #e8e8e8;
20
+ height: 100vh;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ user-select: none;
25
+ }
26
+ .card {
27
+ background: #111;
28
+ border: 1px solid #222;
29
+ border-radius: 12px;
30
+ padding: 28px 32px;
31
+ width: 360px;
32
+ }
33
+ h1 {
34
+ font-size: 1.4rem;
35
+ margin-bottom: 4px;
36
+ color: #4caf50;
37
+ }
38
+ p {
39
+ font-size: 0.8rem;
40
+ color: #555;
41
+ margin-bottom: 20px;
42
+ }
43
+ label {
44
+ display: block;
45
+ font-size: 0.75rem;
46
+ color: #666;
47
+ margin-bottom: 6px;
48
+ text-transform: uppercase;
49
+ letter-spacing: 0.05em;
50
+ }
51
+ input[type='text'] {
52
+ width: 100%;
53
+ padding: 8px 12px;
54
+ border-radius: 6px;
55
+ background: #0a0a0a;
56
+ border: 1px solid #333;
57
+ color: #e8e8e8;
58
+ font-size: 0.9rem;
59
+ outline: none;
60
+ margin-bottom: 16px;
61
+ }
62
+ input[type='text']:focus {
63
+ border-color: #4caf50;
64
+ }
65
+ .instance-btn {
66
+ width: 100%;
67
+ padding: 10px;
68
+ border-radius: 8px;
69
+ border: none;
70
+ background: #1a2e1a;
71
+ color: #4caf50;
72
+ font-size: 0.9rem;
73
+ font-weight: 600;
74
+ cursor: pointer;
75
+ margin-bottom: 8px;
76
+ transition: background 0.15s;
77
+ }
78
+ .instance-btn:hover:not(:disabled) {
79
+ background: #4caf50;
80
+ color: #fff;
81
+ }
82
+ .instance-btn:disabled {
83
+ opacity: 0.4;
84
+ cursor: not-allowed;
85
+ }
86
+ .status {
87
+ font-size: 0.75rem;
88
+ color: #555;
89
+ margin-top: 12px;
90
+ min-height: 1rem;
91
+ }
92
+
93
+ /* ── Setup overlay ─────────────────────────────────────── */
94
+ #setup-overlay {
95
+ position: fixed;
96
+ inset: 0;
97
+ background: #080808;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ }
102
+ #setup-overlay .card {
103
+ width: 400px;
104
+ }
105
+ #setup-overlay h1 {
106
+ margin-bottom: 20px;
107
+ }
108
+ .dir-row {
109
+ display: flex;
110
+ gap: 8px;
111
+ margin-bottom: 16px;
112
+ }
113
+ .dir-row input {
114
+ flex: 1;
115
+ margin: 0;
116
+ }
117
+ .dir-row button {
118
+ padding: 8px 12px;
119
+ border-radius: 6px;
120
+ border: 1px solid #333;
121
+ background: #111;
122
+ color: #888;
123
+ font-size: 0.8rem;
124
+ cursor: pointer;
125
+ }
126
+ .step {
127
+ margin-bottom: 10px;
128
+ }
129
+ .step-name {
130
+ font-size: 0.85rem;
131
+ margin-bottom: 4px;
132
+ }
133
+ .bar-track {
134
+ height: 4px;
135
+ background: #222;
136
+ border-radius: 2px;
137
+ overflow: hidden;
138
+ }
139
+ .bar-fill {
140
+ height: 4px;
141
+ background: #4caf50;
142
+ transition: width 0.2s;
143
+ }
144
+ .step-label {
145
+ font-size: 0.7rem;
146
+ color: #555;
147
+ margin-top: 2px;
148
+ }
149
+ .install-btn {
150
+ width: 100%;
151
+ padding: 10px;
152
+ border-radius: 8px;
153
+ border: none;
154
+ background: #1a2e1a;
155
+ color: #4caf50;
156
+ font-weight: 700;
157
+ font-size: 0.9rem;
158
+ cursor: pointer;
159
+ margin-top: 16px;
160
+ transition: background 0.15s;
161
+ }
162
+ .install-btn:hover:not(:disabled) {
163
+ background: #4caf50;
164
+ color: #fff;
165
+ }
166
+ .install-btn:disabled {
167
+ opacity: 0.4;
168
+ cursor: not-allowed;
169
+ }
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <!-- ── Setup overlay (shown when not installed) ── -->
174
+ <div id="setup-overlay" style="display: none">
175
+ <div class="card">
176
+ <h1 id="setup-title">Initial setup</h1>
177
+
178
+ <label>Install folder</label>
179
+ <div class="dir-row">
180
+ <input id="dir-input" type="text" placeholder="C:\Users\..." />
181
+ <button id="dir-default-btn">Default</button>
182
+ </div>
183
+
184
+ <div id="steps-container"></div>
185
+
186
+ <button id="install-btn" class="install-btn">Install</button>
187
+ <p id="setup-error" class="status" style="color: #ef5350"></p>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- ── Main launcher ── -->
192
+ <div id="main-view" style="display: none">
193
+ <div class="card">
194
+ <h1 id="server-name">Launcher</h1>
195
+ <p id="server-sub"></p>
196
+
197
+ <label>Username</label>
198
+ <input
199
+ id="username-input"
200
+ type="text"
201
+ maxlength="16"
202
+ placeholder="Steve" />
203
+
204
+ <div id="instances-container"></div>
205
+ <p id="game-status" class="status"></p>
206
+ </div>
207
+ </div>
208
+
209
+ <script type="module">
210
+ import { MC } from '/api.js';
211
+
212
+ // ── Boot ────────────────────────────────────────────────────
213
+ const [config, settings, initStatus] = await Promise.all([
214
+ MC.getConfig(),
215
+ MC.getSettings(),
216
+ MC.getInitStatus(),
217
+ ]);
218
+
219
+ if (settings.username) {
220
+ document.getElementById('username-input').value = settings.username;
221
+ }
222
+
223
+ document.title = config.app_name;
224
+
225
+ const allInstalled =
226
+ initStatus.java_ok && initStatus.instances.every((i) => i.installed);
227
+
228
+ if (allInstalled) {
229
+ showMain(config, settings, initStatus);
230
+ } else {
231
+ showSetup(config, settings, initStatus);
232
+ }
233
+
234
+ // ── Main page ────────────────────────────────────────────────
235
+ function showMain(config, settings, initStatus) {
236
+ document.getElementById('main-view').style.display = 'flex';
237
+ document.getElementById('server-name').textContent = config.app_name;
238
+
239
+ const container = document.getElementById('instances-container');
240
+ for (const inst of config.instances) {
241
+ const btn = document.createElement('button');
242
+ btn.className = 'instance-btn';
243
+ btn.id = `play-${inst.id}`;
244
+ btn.textContent = `▶ ${inst.name}`;
245
+ btn.disabled = true;
246
+ btn.addEventListener('click', () => onPlay(inst.id));
247
+ container.appendChild(btn);
248
+ }
249
+
250
+ document
251
+ .getElementById('username-input')
252
+ .addEventListener('input', updateButtons);
253
+ updateButtons();
254
+
255
+ MC.on.gameStarting((id) => {
256
+ document.getElementById(`play-${id}`).textContent = 'EN JEU';
257
+ document.getElementById(`play-${id}`).disabled = true;
258
+ });
259
+ MC.on.gameExit(({ instance_id, code }) => {
260
+ document.getElementById(`play-${instance_id}`).textContent =
261
+ `▶ ${config.instances.find((i) => i.id === instance_id)?.name ?? instance_id}`;
262
+ updateButtons();
263
+ document.getElementById('game-status').textContent =
264
+ code === 0 ? 'Session ended.' : `Exited with code ${code}.`;
265
+ });
266
+
267
+ // Check for updates silently
268
+ MC.checkUpdate()
269
+ .then((info) => {
270
+ if (info) {
271
+ const ok = confirm(
272
+ `Update ${info.version} available.\n\n${info.notes}\n\nInstall now?`,
273
+ );
274
+ if (ok) MC.doUpdate(info.url);
275
+ }
276
+ })
277
+ .catch(() => {});
278
+ }
279
+
280
+ function updateButtons() {
281
+ const username = document.getElementById('username-input').value.trim();
282
+ document.querySelectorAll('.instance-btn').forEach((btn) => {
283
+ if (!btn.textContent.includes('EN JEU')) btn.disabled = !username;
284
+ });
285
+ }
286
+
287
+ async function onPlay(instanceId) {
288
+ const username = document.getElementById('username-input').value.trim();
289
+ if (!username) return;
290
+ await MC.saveSettings({
291
+ username,
292
+ launcher_dir: null,
293
+ max_memory: 2048,
294
+ });
295
+ try {
296
+ await MC.verify(instanceId);
297
+ await MC.play(instanceId);
298
+ } catch (e) {
299
+ alert(`Error: ${e}`);
300
+ }
301
+ }
302
+
303
+ // ── Setup page ───────────────────────────────────────────────
304
+ function showSetup(config, settings, initStatus) {
305
+ document.getElementById('setup-overlay').style.display = 'flex';
306
+ document.getElementById('setup-title').textContent =
307
+ `Setup — ${config.app_name}`;
308
+ document.getElementById('dir-input').value = initStatus.launcher_dir;
309
+
310
+ document
311
+ .getElementById('dir-default-btn')
312
+ .addEventListener('click', async () => {
313
+ document.getElementById('dir-input').value =
314
+ await MC.getDefaultDir();
315
+ });
316
+
317
+ renderSteps(initStatus, config);
318
+
319
+ document
320
+ .getElementById('install-btn')
321
+ .addEventListener('click', onInstall);
322
+
323
+ // Live progress
324
+ MC.on.setupProgress(({ step, current, total, label, error }) => {
325
+ const pct = total > 0 ? Math.round((current * 100) / total) : 0;
326
+ const fill = document.getElementById(`bar-${step}`);
327
+ const lbl = document.getElementById(`lbl-${step}`);
328
+ if (fill) {
329
+ fill.style.width = `${pct}%`;
330
+ fill.style.background = error ? '#ef5350' : '#4caf50';
331
+ }
332
+ if (lbl) lbl.textContent = label;
333
+ });
334
+
335
+ MC.on.setupDone(async () => {
336
+ const status = await MC.getInitStatus();
337
+ if (status.java_ok && status.instances.every((i) => i.installed)) {
338
+ document.getElementById('setup-overlay').style.display = 'none';
339
+ showMain(config, settings, status);
340
+ } else {
341
+ const btn = document.getElementById('install-btn');
342
+ btn.textContent = 'Retry';
343
+ btn.disabled = false;
344
+ }
345
+ });
346
+ }
347
+
348
+ function renderSteps(initStatus, config) {
349
+ const container = document.getElementById('steps-container');
350
+ const steps = [
351
+ { id: 'java', name: 'Java JRE', installed: initStatus.java_ok },
352
+ ...config.instances.map((inst) => ({
353
+ id: inst.id,
354
+ name: inst.name,
355
+ installed:
356
+ initStatus.instances.find((i) => i.id === inst.id)?.installed ??
357
+ false,
358
+ })),
359
+ ];
360
+ container.innerHTML = steps
361
+ .map(
362
+ (s) => `
363
+ <div class="step">
364
+ <div class="step-name" style="color:${s.installed ? '#4caf50' : '#aaa'}">
365
+ ${s.installed ? '✓' : '○'} ${s.name}
366
+ </div>
367
+ <div class="bar-track"><div class="bar-fill" id="bar-${s.id}" style="width:${s.installed ? 100 : 0}%"></div></div>
368
+ <div class="step-label" id="lbl-${s.id}">${s.installed ? 'Installed' : ''}</div>
369
+ </div>
370
+ `,
371
+ )
372
+ .join('');
373
+ }
374
+
375
+ async function onInstall() {
376
+ const dir = document.getElementById('dir-input').value.trim();
377
+ if (!dir) {
378
+ document.getElementById('setup-error').textContent =
379
+ 'Choose an install folder.';
380
+ return;
381
+ }
382
+ const btn = document.getElementById('install-btn');
383
+ btn.disabled = true;
384
+ btn.textContent = 'Installing…';
385
+ document.getElementById('setup-error').textContent = '';
386
+
387
+ try {
388
+ const settings = await MC.getSettings();
389
+ await MC.saveSettings({ ...settings, launcher_dir: dir });
390
+ await MC.runSetup();
391
+ } catch (e) {
392
+ document.getElementById('setup-error').textContent = `Error: ${e}`;
393
+ btn.disabled = false;
394
+ btn.textContent = 'Retry';
395
+ }
396
+ }
397
+ </script>
398
+ </body>
399
+ </html>
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://schema.tauri.app/config/2",
3
+ "productName": "{{name}}",
4
+ "version": "1.0.0",
5
+ "identifier": "{{identifier}}",
6
+ "build": {
7
+ "frontendDist": "../src"
8
+ },
9
+ "app": {
10
+ "withGlobalTauri": true,
11
+ "windows": [
12
+ {
13
+ "title": "Launcher",
14
+ "width": 1000,
15
+ "height": 660,
16
+ "minWidth": 800,
17
+ "minHeight": 520,
18
+ "resizable": true,
19
+ "fullscreen": false,
20
+ "decorations": true
21
+ }
22
+ ],
23
+ "security": {
24
+ "csp": null
25
+ }
26
+ },
27
+ "bundle": {
28
+ "active": true,
29
+ "targets": ["nsis", "appimage", "dmg"],
30
+ "resources": {
31
+ "../config.json": "config.json"
32
+ },
33
+ "icon": [
34
+ "icons/32x32.png",
35
+ "icons/128x128.png",
36
+ "icons/128x128@2x.png",
37
+ "icons/icon.icns",
38
+ "icons/icon.ico"
39
+ ]
40
+ }
41
+ }