Skip to content

Commit b2bb412

Browse files
committed
number input to text
1 parent e072b8d commit b2bb412

11 files changed

Lines changed: 251 additions & 110 deletions

File tree

DESKTOP_APP_README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ Download from [GitHub Releases](https://github.com/LiaScript/LiaScript-Exporter/
1212

1313
**Windows:**
1414
- `.exe` - NSIS installer with start menu shortcuts
15-
- `-portable.exe` - Portable executable (no installation)
1615
- `-win.zip` - Portable archive
1716

1817
**macOS:**

dist/server/public/app.js

Lines changed: 113 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,70 @@ let currentSourceType = 'upload'
44
let currentExportTab = 'presets'
55
let presetsConfig = null
66

7+
// Initialize number inputs with validation (for Windows Electron compatibility)
8+
// Using type="text" with inputmode instead of type="number" to avoid Electron issues
9+
function initializeNumberInputs() {
10+
const numberInputs = [
11+
{ id: 'masteryScore', min: 0, max: 100, isInteger: true, value: 70 },
12+
{ id: 'xapiMasteryScore', min: 0, max: 1, isInteger: false, value: null },
13+
{ id: 'xapiProgressThreshold', min: 0, max: 1, isInteger: false, value: null },
14+
{ id: 'pdfScale', min: 0.1, max: 2, isInteger: false, value: 1 },
15+
{ id: 'pdfTimeout', min: 1000, max: null, isInteger: true, value: 60000 }
16+
]
17+
18+
numberInputs.forEach(config => {
19+
const input = document.getElementById(config.id)
20+
if (input) {
21+
if (config.value !== null) {
22+
input.value = config.value
23+
}
24+
25+
input.addEventListener('blur', () => {
26+
let value = input.value.trim()
27+
if (value === '') return
28+
29+
const num = config.isInteger ? parseInt(value, 10) : parseFloat(value)
30+
31+
if (isNaN(num)) {
32+
input.value = config.value !== null ? config.value : ''
33+
return
34+
}
35+
36+
let clampedValue = num
37+
if (config.min !== null && num < config.min) {
38+
clampedValue = config.min
39+
}
40+
if (config.max !== null && num > config.max) {
41+
clampedValue = config.max
42+
}
43+
44+
input.value = clampedValue
45+
})
46+
47+
// Prevent non-numeric input
48+
input.addEventListener('beforeinput', (e) => {
49+
const data = e.data
50+
if (!data) return
51+
52+
const currentValue = input.value
53+
54+
// Allow digits
55+
if (/^\d+$/.test(data)) return
56+
57+
// Allow decimal point for non-integer fields (only one)
58+
if (!config.isInteger && data === '.' && !currentValue.includes('.')) {
59+
return
60+
}
61+
62+
e.preventDefault()
63+
})
64+
}
65+
})
66+
}
67+
768
// Initialize app
869
document.addEventListener('DOMContentLoaded', async () => {
70+
initializeNumberInputs()
971
await loadPresets()
1072
initializeTabs()
1173
initializeExportTabs()
@@ -23,48 +85,52 @@ async function loadPresets() {
2385
const response = await fetch('/api/presets')
2486
const data = await response.json()
2587
presetsConfig = data.presets
88+
renderPresets()
89+
} catch (error) {
90+
console.error('Failed to load presets:', error)
91+
}
92+
}
2693

27-
const presetsGrid = document.getElementById('presets-grid')
28-
presetsGrid.innerHTML = ''
94+
// Render presets with current language
95+
function renderPresets() {
96+
const presetsGrid = document.getElementById('presets-grid')
97+
presetsGrid.innerHTML = ''
2998

30-
presetsConfig.forEach((preset, index) => {
31-
const label = document.createElement('label')
32-
label.className = 'preset-tile'
99+
presetsConfig.forEach((preset, index) => {
100+
const label = document.createElement('label')
101+
label.className = 'preset-tile'
33102

34-
const input = document.createElement('input')
35-
input.type = 'radio'
36-
input.name = 'preset'
37-
input.value = preset.id
38-
input.dataset.description = preset.description
39-
input.dataset.presetOptions = JSON.stringify(preset.options)
40-
if (index === 0) input.checked = true
103+
const input = document.createElement('input')
104+
input.type = 'radio'
105+
input.name = 'preset'
106+
input.value = preset.id
107+
input.dataset.descriptionKey = `presets.${preset.id}.description`
108+
input.dataset.presetOptions = JSON.stringify(preset.options)
109+
if (index === 0) input.checked = true
41110

42-
const content = document.createElement('div')
43-
content.className = 'preset-content'
111+
const content = document.createElement('div')
112+
content.className = 'preset-content'
44113

45-
const logo = document.createElement('div')
46-
logo.style.fontSize = '2rem'
47-
logo.style.marginBottom = '0.5rem'
48-
logo.textContent = preset.logo
114+
const logo = document.createElement('div')
115+
logo.style.fontSize = '2rem'
116+
logo.style.marginBottom = '0.5rem'
117+
logo.textContent = preset.logo
49118

50-
const title = document.createElement('h3')
51-
title.textContent = preset.name
119+
const title = document.createElement('h3')
120+
title.textContent = preset.name
52121

53-
const subtitle = document.createElement('p')
54-
subtitle.textContent = preset.subtitle
122+
const subtitle = document.createElement('p')
123+
subtitle.textContent = preset.subtitle
55124

56-
content.appendChild(logo)
57-
content.appendChild(title)
58-
content.appendChild(subtitle)
125+
content.appendChild(logo)
126+
content.appendChild(title)
127+
content.appendChild(subtitle)
59128

60-
label.appendChild(input)
61-
label.appendChild(content)
129+
label.appendChild(input)
130+
label.appendChild(content)
62131

63-
presetsGrid.appendChild(label)
64-
})
65-
} catch (error) {
66-
console.error('Failed to load presets:', error)
67-
}
132+
presetsGrid.appendChild(label)
133+
})
68134
}
69135

70136
// Tab switching
@@ -215,13 +281,15 @@ function updateFileList() {
215281
return
216282
}
217283

284+
const removeTitle = window.i18n ? window.i18n.t('files.remove') : 'Remove'
285+
218286
fileList.innerHTML = selectedFiles
219287
.map(
220288
(file, index) => `
221289
<div class="file-item">
222290
<span class="file-name">${escapeHtml(file.name)}</span>
223291
<span class="file-size">${formatFileSize(file.size)}</span>
224-
<button type="button" class="remove-file" data-index="${index}" title="Entfernen">×</button>
292+
<button type="button" class="remove-file" data-index="${index}" title="${removeTitle}">×</button>
225293
</div>
226294
`,
227295
)
@@ -565,7 +633,10 @@ function initializePresetDescription() {
565633
if (e.target.name === 'preset') {
566634
const descriptionBox = document.getElementById('preset-description')
567635
const descriptionText = descriptionBox.querySelector('p')
568-
const description = e.target.dataset.description
636+
637+
// Get translation key and translate
638+
const descriptionKey = e.target.dataset.descriptionKey
639+
const description = window.i18n ? window.i18n.t(descriptionKey) : ''
569640

570641
if (description) {
571642
descriptionText.innerHTML = description
@@ -579,11 +650,16 @@ function initializePresetDescription() {
579650
// Show description for initially checked preset
580651
setTimeout(() => {
581652
const checkedPreset = document.querySelector('input[name="preset"]:checked')
582-
if (checkedPreset && checkedPreset.dataset.description) {
653+
if (checkedPreset) {
583654
const descriptionBox = document.getElementById('preset-description')
584655
const descriptionText = descriptionBox.querySelector('p')
585-
descriptionText.innerHTML = checkedPreset.dataset.description
586-
descriptionBox.style.display = 'block'
656+
const descriptionKey = checkedPreset.dataset.descriptionKey
657+
const description = window.i18n ? window.i18n.t(descriptionKey) : ''
658+
659+
if (description) {
660+
descriptionText.innerHTML = description
661+
descriptionBox.style.display = 'block'
662+
}
587663
}
588664
}, 100)
589665
}

dist/server/public/i18n.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class I18n {
3939
localStorage.setItem('language', lang)
4040
this.updatePageTranslations()
4141
this.updateLanguageSelector()
42+
this.updateDynamicContent()
4243
}
4344

4445
t(key, fallback = '') {
@@ -49,8 +50,8 @@ class I18n {
4950
return translation
5051
}
5152

53+
// Update all elements with data-i18n attribute
5254
updatePageTranslations() {
53-
// Update all elements with data-i18n attribute
5455
document.querySelectorAll('[data-i18n]').forEach(element => {
5556
const key = element.getAttribute('data-i18n')
5657
const translation = this.t(key)
@@ -84,6 +85,43 @@ class I18n {
8485
})
8586
}
8687

88+
updateDynamicContent() {
89+
// Update preset description if one is currently selected
90+
const checkedPreset = document.querySelector('input[name="preset"]:checked')
91+
if (checkedPreset && checkedPreset.dataset.descriptionKey) {
92+
const descriptionBox = document.getElementById('preset-description')
93+
if (descriptionBox && descriptionBox.style.display !== 'none') {
94+
const descriptionText = descriptionBox.querySelector('p')
95+
if (descriptionText) {
96+
const description = this.t(checkedPreset.dataset.descriptionKey)
97+
descriptionText.innerHTML = description
98+
}
99+
}
100+
}
101+
102+
// Update format description if one is currently selected
103+
const checkedFormat = document.querySelector('input[name="format"]:checked')
104+
if (checkedFormat && checkedFormat.dataset.description) {
105+
const descriptionBox = document.getElementById('format-description')
106+
if (descriptionBox && descriptionBox.style.display !== 'none') {
107+
const descriptionText = descriptionBox.querySelector('p')
108+
if (descriptionText) {
109+
const description = checkedFormat.dataset.description
110+
descriptionText.innerHTML = description
111+
}
112+
}
113+
}
114+
115+
// Update file list remove button titles
116+
const removeButtons = document.querySelectorAll('.remove-file')
117+
if (removeButtons.length > 0) {
118+
const removeTitle = this.t('files.remove')
119+
removeButtons.forEach(btn => {
120+
btn.title = removeTitle
121+
})
122+
}
123+
}
124+
87125
updateLanguageSelector() {
88126
const selector = document.getElementById('language-selector')
89127
if (selector) {

dist/server/public/index.html

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,10 @@ <h3 class="settings-heading" data-i18n="settings.scorm.title">SCORM Settings</h3
360360
<div class="form-group">
361361
<label for="masteryScore" data-i18n="settings.scorm.masteryScore">Mastery Score</label>
362362
<input
363-
type="number"
363+
type="text"
364+
inputmode="numeric"
364365
id="masteryScore"
365366
name="option_masteryScore"
366-
min="0"
367-
max="100"
368-
value="70"
369367
placeholder="70"
370368
/>
371369
<span class="hint" data-i18n="settings.scorm.masteryScoreHint"
@@ -500,13 +498,11 @@ <h3 class="settings-heading" data-i18n="settings.xapi.title">xAPI Settings</h3>
500498
<div class="form-group">
501499
<label for="xapiMasteryScore" data-i18n="settings.xapi.masteryScore">Mastery Score</label>
502500
<input
503-
type="number"
501+
type="text"
502+
inputmode="decimal"
504503
id="xapiMasteryScore"
505504
name="option_xapi-mastery-threshold"
506505
placeholder="0.8"
507-
min="0"
508-
max="1"
509-
step="0.01"
510506
/>
511507
<span class="hint" data-i18n="settings.xapi.masteryScoreHint"
512508
>Required score to pass (0.0-1.0, default: 0.8)</span
@@ -516,13 +512,11 @@ <h3 class="settings-heading" data-i18n="settings.xapi.title">xAPI Settings</h3>
516512
<div class="form-group">
517513
<label for="xapiProgressThreshold" data-i18n="settings.xapi.progressThreshold">Progress Threshold</label>
518514
<input
519-
type="number"
515+
type="text"
516+
inputmode="decimal"
520517
id="xapiProgressThreshold"
521518
name="option_xapi-progress-threshold"
522519
placeholder="0.9"
523-
min="0"
524-
max="1"
525-
step="0.01"
526520
/>
527521
<span class="hint" data-i18n="settings.xapi.progressThresholdHint"
528522
>Required slide progress for completion (0.0-1.0, default: 0.9)</span
@@ -649,13 +643,10 @@ <h3 class="settings-heading" data-i18n="settings.pdf.title">PDF Settings</h3>
649643
<div class="form-group">
650644
<label for="pdfScale" data-i18n="settings.pdf.scale">Scale</label>
651645
<input
652-
type="number"
646+
type="text"
647+
inputmode="decimal"
653648
id="pdfScale"
654649
name="option_pdf-scale"
655-
min="0.1"
656-
max="2"
657-
step="0.1"
658-
value="1"
659650
placeholder="1.0"
660651
/>
661652
<span class="hint" data-i18n="settings.pdf.scaleHint">Page scale (0.1 - 2.0)</span>
@@ -816,12 +807,10 @@ <h3 class="settings-heading" data-i18n="settings.pdf.title">PDF Settings</h3>
816807
<div class="form-group">
817808
<label for="pdfTimeout" data-i18n="settings.pdf.timeout">Timeout (ms)</label>
818809
<input
819-
type="number"
810+
type="text"
811+
inputmode="numeric"
820812
id="pdfTimeout"
821813
name="option_pdf-timeout"
822-
min="1000"
823-
step="1000"
824-
value="60000"
825814
placeholder="60000"
826815
/>
827816
<span class="hint" data-i18n="settings.pdf.timeoutHint"

dist/server/public/locales/de.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"tabs.gitRepository": "Git-Repository",
1111
"upload.dropzone": "Dateien hier ablegen oder klicken zum Auswählen",
1212
"upload.hint": "ZIP-Dateien oder mehrere Dateien (max. 100MB)",
13+
"files.remove": "Entfernen",
1314
"git.repoUrl": "Repository-URL",
1415
"git.repoUrlRequired": "Repository-URL *",
1516
"git.branch": "Branch / Tag",
@@ -198,5 +199,11 @@
198199
"status.autoRefresh": "Seite wird automatisch aktualisiert",
199200
"status.refreshNow": "Jetzt aktualisieren",
200201
"status.error": "Fehler",
201-
"status.errorMessage": "Fehler beim Laden des Status"
202+
"status.errorMessage": "Fehler beim Laden des Status",
203+
"presets.moodle4x-scorm2004.description": "Moodle ist das weltweit am häufigsten verwendete Open-Source-Lernmanagementsystem. Diese Konfiguration verwendet SCORM 2004 für maximale Kompatibilität mit Moodle 3.x und 4.x. <a href='https://moodle.org' target='_blank'>Mehr erfahren</a>",
204+
"presets.ilias.description": "ILIAS ist ein leistungsstarkes Open-Source-LMS aus Deutschland. Diese Konfiguration nutzt SCORM 1.2 für bestmögliche Kompatibilität mit ILIAS-Versionen. <a href='https://www.ilias.de' target='_blank'>Mehr erfahren</a>",
205+
"presets.opal.description": "OPAL (Online-Plattform für Akademisches Lehren und Lernen) ist das zentrale LMS für sächsische Hochschulen. Optimiert für SCORM 2004. <a href='https://bildungsportal.sachsen.de/opal' target='_blank'>Mehr erfahren</a>",
206+
"presets.generic.description": "Universelle SCORM 2004 Konfiguration für beliebige Lernmanagementsysteme, die den SCORM 2004 Standard unterstützen. Funktioniert mit den meisten modernen LMS-Plattformen.",
207+
"presets.openolat.description": "OpenOlat ist eine Open-Source E-Learning-Plattform aus der Schweiz. Diese Konfiguration nutzt SCORM 2004 für volle Funktionalität. <a href='https://www.openolat.com' target='_blank'>Mehr erfahren</a>",
208+
"presets.openedx.description": "Open edX ist die Open-Source-Plattform hinter edX.org und wird weltweit für MOOCs eingesetzt. Verwendet SCORM 2004 über das SCORM XBlock. <a href='https://openedx.org' target='_blank'>Mehr erfahren</a>"
202209
}

dist/server/public/locales/en.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"tabs.gitRepository": "Git Repository",
1111
"upload.dropzone": "Drop files here or click to select",
1212
"upload.hint": "ZIP files or multiple files (max. 100MB)",
13+
"files.remove": "Remove",
1314
"git.repoUrl": "Repository URL",
1415
"git.repoUrlRequired": "Repository URL *",
1516
"git.branch": "Branch / Tag",
@@ -198,5 +199,11 @@
198199
"status.autoRefresh": "Page will refresh automatically",
199200
"status.refreshNow": "Refresh Now",
200201
"status.error": "Error",
201-
"status.errorMessage": "Error loading status"
202+
"status.errorMessage": "Error loading status",
203+
"presets.moodle4x-scorm2004.description": "Moodle is the world's most widely used open-source learning management system. This configuration uses SCORM 2004 for maximum compatibility with Moodle 3.x and 4.x. <a href='https://moodle.org' target='_blank'>Learn more</a>",
204+
"presets.ilias.description": "ILIAS is a powerful open-source LMS from Germany. This configuration uses SCORM 1.2 for best compatibility with ILIAS versions. <a href='https://www.ilias.de' target='_blank'>Learn more</a>",
205+
"presets.opal.description": "OPAL (Online Platform for Academic Teaching and Learning) is the central LMS for Saxon universities. Optimized for SCORM 2004. <a href='https://bildungsportal.sachsen.de/opal' target='_blank'>Learn more</a>",
206+
"presets.generic.description": "Universal SCORM 2004 configuration for any learning management systems that support the SCORM 2004 standard. Works with most modern LMS platforms.",
207+
"presets.openolat.description": "OpenOlat is an open-source e-learning platform from Switzerland. This configuration uses SCORM 2004 for full functionality. <a href='https://www.openolat.com' target='_blank'>Learn more</a>",
208+
"presets.openedx.description": "Open edX is the open-source platform behind edX.org and is used worldwide for MOOCs. Uses SCORM 2004 via the SCORM XBlock. <a href='https://openedx.org' target='_blank'>Learn more</a>"
202209
}

0 commit comments

Comments
 (0)