decksmith 0.9.1__py3-none-any.whl → 0.9.3__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
- document.addEventListener('DOMContentLoaded', function() {
1
+ document.addEventListener('DOMContentLoaded', function () {
2
2
  // --- Initialization ---
3
-
3
+
4
4
  // Initialize Split.js
5
5
  Split(['#left-pane', '#right-pane'], {
6
6
  sizes: [50, 50],
@@ -48,7 +48,7 @@ document.addEventListener('DOMContentLoaded', function() {
48
48
  statusIcon: document.getElementById('status-icon'),
49
49
  statusLine: document.getElementById('status-line'),
50
50
  toastContainer: document.getElementById('toast-container'),
51
-
51
+
52
52
  // Project controls
53
53
  currentProjectPath: document.getElementById('current-project-path'),
54
54
  openProjectBtn: document.getElementById('open-project-btn'),
@@ -62,7 +62,7 @@ document.addEventListener('DOMContentLoaded', function() {
62
62
  projectNameInput: document.getElementById('project-name-input'),
63
63
  modalCancelBtn: document.getElementById('modal-cancel-btn'),
64
64
  modalConfirmBtn: document.getElementById('modal-confirm-btn'),
65
-
65
+
66
66
  // Welcome screen & Browse
67
67
  welcomeScreen: document.getElementById('welcome-screen'),
68
68
  welcomeOpenBtn: document.getElementById('welcome-open-btn'),
@@ -93,9 +93,30 @@ document.addEventListener('DOMContentLoaded', function() {
93
93
  modalMode: 'open', // 'open' or 'new'
94
94
  isPreviewing: false,
95
95
  pendingPreview: false,
96
- isProjectOpen: false
96
+ isProjectOpen: false,
97
+ hasSyntaxError: false
97
98
  };
98
99
 
100
+ // --- Annotation Handler ---
101
+ function checkAnnotations() {
102
+ // Wait slightly for Ace to update annotations
103
+ setTimeout(() => {
104
+ const annotations = yamlEditor.getSession().getAnnotations();
105
+ const errors = annotations.filter(a => a.type === 'error');
106
+
107
+ if (errors.length > 0) {
108
+ state.hasSyntaxError = true;
109
+ // Use the first error message
110
+ setStatus(`YAML Syntax Error: ${errors[0].text} (Line ${errors[0].row + 1})`, 'error');
111
+ } else {
112
+ if (state.hasSyntaxError) {
113
+ state.hasSyntaxError = false;
114
+ setStatus('Ready'); // Clear error status
115
+ }
116
+ }
117
+ }, 100);
118
+ }
119
+
99
120
  // --- UI Helpers ---
100
121
 
101
122
  function showShutdownScreen(reason) {
@@ -107,9 +128,9 @@ document.addEventListener('DOMContentLoaded', function() {
107
128
  const toast = document.createElement('div');
108
129
  toast.className = `toast ${type}`;
109
130
  toast.textContent = message;
110
-
131
+
111
132
  elements.toastContainer.appendChild(toast);
112
-
133
+
113
134
  requestAnimationFrame(() => {
114
135
  toast.classList.add('show');
115
136
  });
@@ -131,12 +152,15 @@ document.addEventListener('DOMContentLoaded', function() {
131
152
 
132
153
  function setStatus(message, type = null) {
133
154
  elements.statusText.textContent = message;
134
-
155
+
135
156
  // Reset status bar state
136
- elements.statusBar.classList.remove('processing', 'success');
157
+ elements.statusBar.classList.remove('processing', 'success', 'error');
137
158
  elements.statusSpinner.classList.add('hidden');
138
159
  elements.statusIcon.classList.remove('hidden');
139
160
 
161
+ // Reset icon to default
162
+ elements.statusIcon.className = 'fa-solid fa-circle-info fa-fw';
163
+
140
164
  if (type === 'processing') {
141
165
  elements.statusBar.classList.add('processing');
142
166
  elements.statusSpinner.classList.remove('hidden');
@@ -145,6 +169,11 @@ document.addEventListener('DOMContentLoaded', function() {
145
169
  } else if (type === 'success') {
146
170
  elements.statusBar.classList.add('success');
147
171
  updateStatusLine('success');
172
+ } else if (type === 'error') {
173
+ elements.statusBar.classList.add('error');
174
+ updateStatusLine('error');
175
+ // Change icon to danger
176
+ elements.statusIcon.className = 'fa-solid fa-triangle-exclamation fa-fw';
148
177
  } else {
149
178
  if (type) updateStatusLine(type);
150
179
  }
@@ -194,7 +223,7 @@ document.addEventListener('DOMContentLoaded', function() {
194
223
  option.textContent = `${index + 1}: ${label}`;
195
224
  elements.cardSelector.appendChild(option);
196
225
  });
197
-
226
+
198
227
  if (state.currentCardIndex >= 0 && state.currentCardIndex < cards.length) {
199
228
  elements.cardSelector.value = state.currentCardIndex;
200
229
  } else if (cards.length > 0) {
@@ -222,7 +251,7 @@ document.addEventListener('DOMContentLoaded', function() {
222
251
  state.currentCardIndex = index;
223
252
  state.isPreviewing = true;
224
253
  state.pendingPreview = false;
225
-
254
+
226
255
  elements.loadingIndicator.classList.remove('hidden');
227
256
  elements.placeholderText.classList.add('hidden');
228
257
  setStatus('Generating preview...', 'loading');
@@ -237,33 +266,33 @@ document.addEventListener('DOMContentLoaded', function() {
237
266
  headers: { 'Content-Type': 'application/json' },
238
267
  body: JSON.stringify(payload)
239
268
  })
240
- .then(response => {
241
- if (!response.ok) {
242
- return response.json().then(err => { throw new Error(err.error || 'Preview failed'); });
243
- }
244
- return response.blob();
245
- })
246
- .then(blob => {
247
- const url = URL.createObjectURL(blob);
248
- elements.previewImage.src = url;
249
- elements.previewImage.onload = () => {
269
+ .then(response => {
270
+ if (!response.ok) {
271
+ return response.json().then(err => { throw new Error(err.error || 'Preview failed'); });
272
+ }
273
+ return response.blob();
274
+ })
275
+ .then(blob => {
276
+ const url = URL.createObjectURL(blob);
277
+ elements.previewImage.src = url;
278
+ elements.previewImage.onload = () => {
279
+ elements.loadingIndicator.classList.add('hidden');
280
+ elements.previewImage.classList.remove('hidden');
281
+ setStatus('Preview updated');
282
+ updateStatusLine('success');
283
+ };
284
+ })
285
+ .catch(err => {
250
286
  elements.loadingIndicator.classList.add('hidden');
251
- elements.previewImage.classList.remove('hidden');
252
- setStatus('Preview updated');
253
- updateStatusLine('success');
254
- };
255
- })
256
- .catch(err => {
257
- elements.loadingIndicator.classList.add('hidden');
258
- setStatus(`Error: ${err.message}`, 'error');
259
- console.error(err);
260
- })
261
- .finally(() => {
262
- state.isPreviewing = false;
263
- if (state.pendingPreview) {
264
- updatePreview();
265
- }
266
- });
287
+ setStatus(`Error: ${err.message}`, 'error');
288
+ console.error(err);
289
+ })
290
+ .finally(() => {
291
+ state.isPreviewing = false;
292
+ if (state.pendingPreview) {
293
+ updatePreview();
294
+ }
295
+ });
267
296
  }
268
297
 
269
298
  function autoSave() {
@@ -272,7 +301,7 @@ document.addEventListener('DOMContentLoaded', function() {
272
301
  if (!elements.statusText.textContent.includes('preview')) {
273
302
  setStatus('Saving...');
274
303
  }
275
-
304
+
276
305
  const payload = {
277
306
  yaml: yamlEditor.getValue(),
278
307
  csv: csvEditor.getValue()
@@ -283,17 +312,17 @@ document.addEventListener('DOMContentLoaded', function() {
283
312
  headers: { 'Content-Type': 'application/json' },
284
313
  body: JSON.stringify(payload)
285
314
  })
286
- .then(response => response.json())
287
- .then(data => {
288
- if (!elements.statusText.textContent.includes('preview')) {
289
- setStatus('Saved');
290
- }
291
- loadCards();
292
- })
293
- .catch(err => {
294
- setStatus('Error saving');
295
- console.error(err);
296
- });
315
+ .then(response => response.json())
316
+ .then(data => {
317
+ // if (!elements.statusText.textContent.includes('preview')) {
318
+ // setStatus('Saved');
319
+ // }
320
+ loadCards();
321
+ })
322
+ .catch(err => {
323
+ setStatus('Error saving');
324
+ console.error(err);
325
+ });
297
326
  }
298
327
 
299
328
  function buildDeck() {
@@ -302,21 +331,24 @@ document.addEventListener('DOMContentLoaded', function() {
302
331
  yaml: yamlEditor.getValue(),
303
332
  csv: csvEditor.getValue()
304
333
  };
305
-
306
- fetch('/api/build', {
334
+
335
+ return fetch('/api/build', {
307
336
  method: 'POST',
308
337
  headers: { 'Content-Type': 'application/json' },
309
338
  body: JSON.stringify(payload)
310
339
  })
311
- .then(response => response.json())
312
- .then(data => {
313
- if (data.error) throw new Error(data.error);
314
- setStatus(data.message, 'success');
315
- })
316
- .catch(err => {
317
- setStatus('Build failed', 'error');
318
- showNotification('Build failed: ' + err.message, 'error');
319
- });
340
+ .then(response => response.json())
341
+ .then(data => {
342
+ if (data.error) throw new Error(data.error);
343
+ setStatus(data.message, 'success');
344
+ showNotification(data.message, 'success');
345
+ return data;
346
+ })
347
+ .catch(err => {
348
+ setStatus('Build failed: ' + err.message, 'error');
349
+ showNotification('Build failed: ' + err.message, 'error');
350
+ throw err;
351
+ });
320
352
  }
321
353
 
322
354
  function showExportModal() {
@@ -339,7 +371,16 @@ document.addEventListener('DOMContentLoaded', function() {
339
371
  };
340
372
 
341
373
  hideExportModal();
342
- exportPdf(params);
374
+
375
+ // Chain build then export
376
+ buildDeck()
377
+ .then(() => {
378
+ exportPdf(params);
379
+ })
380
+ .catch(() => {
381
+ // Build failed, error already handled by buildDeck UI updates
382
+ console.log('Export cancelled due to build failure');
383
+ });
343
384
  }
344
385
 
345
386
  function exportPdf(params) {
@@ -349,15 +390,16 @@ document.addEventListener('DOMContentLoaded', function() {
349
390
  headers: { 'Content-Type': 'application/json' },
350
391
  body: JSON.stringify(params)
351
392
  })
352
- .then(response => response.json())
353
- .then(data => {
354
- if (data.error) throw new Error(data.error);
355
- setStatus(data.message, 'success');
356
- })
357
- .catch(err => {
358
- setStatus('Export failed', 'error');
359
- showNotification('Export failed: ' + err.message, 'error');
360
- });
393
+ .then(response => response.json())
394
+ .then(data => {
395
+ if (data.error) throw new Error(data.error);
396
+ setStatus(data.message, 'success');
397
+ showNotification(data.message, 'success');
398
+ })
399
+ .catch(err => {
400
+ setStatus('Export failed: ' + err.message, 'error');
401
+ showNotification('Export failed: ' + err.message, 'error');
402
+ });
361
403
  }
362
404
 
363
405
  // --- Project Management ---
@@ -367,6 +409,8 @@ document.addEventListener('DOMContentLoaded', function() {
367
409
  elements.modalTitle.textContent = mode === 'open' ? 'Open Project' : 'New Project';
368
410
  elements.projectPathInput.value = '';
369
411
  elements.projectNameInput.value = '';
412
+ elements.projectPathInput.classList.remove('input-error');
413
+ elements.projectNameInput.classList.remove('input-error');
370
414
  elements.pathModal.classList.remove('hidden');
371
415
 
372
416
  if (mode === 'new') {
@@ -414,24 +458,23 @@ document.addEventListener('DOMContentLoaded', function() {
414
458
  headers: { 'Content-Type': 'application/json' },
415
459
  body: JSON.stringify({ path: path })
416
460
  })
417
- .then(response => response.json())
418
- .then(data => {
419
- if (data.error) throw new Error(data.error);
420
-
421
- state.isProjectOpen = true;
422
- elements.currentProjectPath.textContent = data.path;
423
- elements.currentProjectPath.title = data.path;
424
-
425
- // showNotification('Project opened', 'success');
426
- setStatus('Ready');
427
-
428
- loadInitialData();
429
- elements.welcomeScreen.classList.add('hidden');
430
- })
431
- .catch(err => {
432
- showNotification(err.message, 'error');
433
- setStatus('Error', 'error');
434
- });
461
+ .then(response => response.json())
462
+ .then(data => {
463
+ if (data.error) throw new Error(data.error);
464
+
465
+ state.isProjectOpen = true;
466
+ elements.currentProjectPath.textContent = data.path;
467
+ elements.currentProjectPath.title = data.path;
468
+
469
+ // showNotification('Project opened', 'success');
470
+ setStatus('Ready');
471
+
472
+ loadInitialData();
473
+ elements.welcomeScreen.classList.add('hidden');
474
+ })
475
+ .catch(err => {
476
+ setStatus('Error: ' + err.message, 'error');
477
+ });
435
478
  }
436
479
 
437
480
  function handleDirectOpen() {
@@ -445,17 +488,28 @@ document.addEventListener('DOMContentLoaded', function() {
445
488
  .catch(err => console.error('Error browsing:', err));
446
489
  }
447
490
 
491
+ // Clear error on input
492
+ elements.projectPathInput.addEventListener('input', () => {
493
+ elements.projectPathInput.classList.remove('input-error');
494
+ });
495
+
496
+ elements.projectNameInput.addEventListener('input', () => {
497
+ elements.projectNameInput.classList.remove('input-error');
498
+ });
499
+
448
500
  function handleProjectAction() {
449
501
  let path = elements.projectPathInput.value.trim();
450
502
  if (!path) {
451
- showNotification('Please enter a path', 'error');
503
+ elements.projectPathInput.classList.add('input-error');
504
+ // showNotification('Please enter a path', 'error');
452
505
  return;
453
506
  }
454
507
 
455
508
  if (state.modalMode === 'new') {
456
509
  const name = elements.projectNameInput.value.trim();
457
510
  if (!name) {
458
- showNotification('Please enter a project name', 'error');
511
+ elements.projectNameInput.classList.add('input-error');
512
+ // showNotification('Please enter a project name', 'error');
459
513
  return;
460
514
  }
461
515
  const separator = path.includes('\\') ? '\\' : '/';
@@ -463,7 +517,7 @@ document.addEventListener('DOMContentLoaded', function() {
463
517
  }
464
518
 
465
519
  const endpoint = state.modalMode === 'open' ? '/api/project/select' : '/api/project/create';
466
-
520
+
467
521
  setStatus(state.modalMode === 'open' ? 'Opening project...' : 'Creating project...');
468
522
 
469
523
  fetch(endpoint, {
@@ -471,26 +525,25 @@ document.addEventListener('DOMContentLoaded', function() {
471
525
  headers: { 'Content-Type': 'application/json' },
472
526
  body: JSON.stringify({ path: path })
473
527
  })
474
- .then(response => response.json())
475
- .then(data => {
476
- if (data.error) throw new Error(data.error);
477
-
478
- hideModal();
479
- state.isProjectOpen = true;
480
- elements.currentProjectPath.textContent = data.path;
481
- elements.currentProjectPath.title = data.path;
482
-
483
- // showNotification(state.modalMode === 'open' ? 'Project opened' : 'Project created', 'success');
484
- setStatus('Ready');
485
-
486
- // Reload data
487
- loadInitialData();
488
- elements.welcomeScreen.classList.add('hidden');
489
- })
490
- .catch(err => {
491
- showNotification(err.message, 'error');
492
- setStatus('Error', 'error');
493
- });
528
+ .then(response => response.json())
529
+ .then(data => {
530
+ if (data.error) throw new Error(data.error);
531
+
532
+ hideModal();
533
+ state.isProjectOpen = true;
534
+ elements.currentProjectPath.textContent = data.path;
535
+ elements.currentProjectPath.title = data.path;
536
+
537
+ // showNotification(state.modalMode === 'open' ? 'Project opened' : 'Project created', 'success');
538
+ setStatus('Ready');
539
+
540
+ // Reload data
541
+ loadInitialData();
542
+ elements.welcomeScreen.classList.add('hidden');
543
+ })
544
+ .catch(err => {
545
+ setStatus('Error: ' + err.message, 'error');
546
+ });
494
547
  }
495
548
 
496
549
  function closeProject() {
@@ -500,16 +553,16 @@ document.addEventListener('DOMContentLoaded', function() {
500
553
  state.isProjectOpen = false;
501
554
  elements.currentProjectPath.textContent = 'No project selected';
502
555
  elements.currentProjectPath.title = '';
503
-
556
+
504
557
  // Clear editors
505
558
  yamlEditor.setValue('', -1);
506
559
  csvEditor.setValue('', -1);
507
560
  state.isDirty = false; // Reset dirty flag after clearing editors
508
-
561
+
509
562
  elements.cardSelector.innerHTML = '<option value="-1">Select a card...</option>';
510
563
  elements.previewImage.classList.add('hidden');
511
564
  elements.placeholderText.classList.remove('hidden');
512
-
565
+
513
566
  elements.welcomeScreen.classList.remove('hidden');
514
567
  // showNotification('Project closed', 'info');
515
568
  })
@@ -542,6 +595,7 @@ document.addEventListener('DOMContentLoaded', function() {
542
595
  };
543
596
 
544
597
  yamlEditor.session.on('change', markDirty);
598
+ yamlEditor.session.on('changeAnnotation', checkAnnotations);
545
599
  csvEditor.session.on('change', markDirty);
546
600
 
547
601
  // Auto-save loop
@@ -569,7 +623,7 @@ document.addEventListener('DOMContentLoaded', function() {
569
623
  evtSource.onmessage = (e) => {
570
624
  // Ignore keepalive
571
625
  if (e.data === ': keepalive') return;
572
-
626
+
573
627
  try {
574
628
  const data = JSON.parse(e.data);
575
629
  if (data.type === 'shutdown') {