From 0702eef39bee4f0495ada2a759a7c0cb8748b798 Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 17:59:42 +0900
Subject: [PATCH 01/15] Add export and import buttons for badge presets
---
index.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/index.php b/index.php
index 948977816..f595513b1 100644
--- a/index.php
+++ b/index.php
@@ -1167,6 +1167,8 @@ class="joystickBase" />
@@ -2279,6 +2281,7 @@ class="joystickBase" />
+
From f64f91a36e595f64b559737f3cc9dc98aee38155 Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:00:06 +0900
Subject: [PATCH 02/15] Add badge preset I/O functionality
---
badgepresetio.js | 356 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 356 insertions(+)
create mode 100644 badgepresetio.js
diff --git a/badgepresetio.js b/badgepresetio.js
new file mode 100644
index 000000000..e25f46a43
--- /dev/null
+++ b/badgepresetio.js
@@ -0,0 +1,356 @@
+const PRESET_FILE_EXT = '.badge-preset';
+const PRESET_FILE_ACCEPT = '.badge-preset,.json';
+const PRESET_FILE_MAX_BYTES = 64 * 1024;
+const BADGE_ID_PATTERN = /^[a-zA-Z0-9_]+$/;
+const SLOT_SET_CONCURRENCY = 8;
+
+function getPresetIoMessage(key, fallback, replacements) {
+ let message = fallback;
+ const i18nextInstance = /** @type {any} */ (window).i18next;
+ if (i18nextInstance)
+ message = i18nextInstance.t(`modal.badgePreset.io.${key}`, fallback);
+ if (replacements) {
+ for (const replacementKey of Object.keys(replacements))
+ message = message.replace(`{${replacementKey}}`, `${replacements[replacementKey]}`);
+ }
+ return message;
+}
+
+function getErrorMessage(error) {
+ if (error instanceof Error)
+ return error.message;
+ return `${error}`;
+}
+
+function showPresetIoSuccess(message) {
+ if (typeof showToastMessage === 'function')
+ showToastMessage(getMassagedLabel(message, true), 'info', true);
+}
+
+function getCurrentGridDimensions(fallbackSlots) {
+ const rows = typeof badgeSlotRows === 'number' && badgeSlotRows > 0
+ ? badgeSlotRows
+ : (fallbackSlots?.length || 1);
+ const cols = typeof badgeSlotCols === 'number' && badgeSlotCols > 0
+ ? badgeSlotCols
+ : (fallbackSlots?.[0]?.length || 3);
+ return { rows, cols };
+}
+
+function validateBadgeSlots(badgeSlots, maxRows, maxCols) {
+ if (!Array.isArray(badgeSlots))
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+
+ if (maxRows && badgeSlots.length > maxRows)
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+
+ for (const row of badgeSlots) {
+ if (!Array.isArray(row))
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ if (maxCols && row.length > maxCols)
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+
+ for (const badgeId of row) {
+ if (badgeId === null || badgeId === 'null')
+ continue;
+ if (typeof badgeId !== 'string')
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ if (!BADGE_ID_PATTERN.test(badgeId)) {
+ throw new Error(getPresetIoMessage(
+ 'importInvalidBadgeId',
+ 'Invalid badge ID in preset: {ID}',
+ { ID: badgeId }
+ ));
+ }
+ }
+ }
+}
+
+function parsePresetFile(rawText, maxRows, maxCols) {
+ const parsed = JSON.parse(rawText);
+
+ let badgeSlots;
+ if (Array.isArray(parsed))
+ badgeSlots = parsed;
+ else if (parsed && Array.isArray(parsed.badgeSlots))
+ badgeSlots = parsed.badgeSlots;
+ else
+ throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+
+ validateBadgeSlots(badgeSlots, maxRows, maxCols);
+ return badgeSlots;
+}
+
+function expandBadgeSlotsToGrid(badgeSlots, rows, cols) {
+ const expanded = [];
+ for (let r = 0; r < rows; r++) {
+ const expandedRow = [];
+ for (let c = 0; c < cols; c++) {
+ const badgeId = badgeSlots?.[r]?.[c];
+ if (badgeId === null || badgeId === 'null' || typeof badgeId === 'undefined')
+ expandedRow.push('null');
+ else
+ expandedRow.push(badgeId);
+ }
+ expanded.push(expandedRow);
+ }
+ return expanded;
+}
+
+function computeChanges(targetSlots, currentSlots, rows, cols) {
+ const changes = [];
+ for (let r = 0; r < rows; r++) {
+ for (let c = 0; c < cols; c++) {
+ const targetId = targetSlots[r]?.[c] ?? 'null';
+ const currentId = currentSlots[r]?.[c] ?? 'null';
+ if (targetId !== currentId)
+ changes.push({ r, c, targetId, currentId });
+ }
+ }
+ return changes;
+}
+
+function fetchSlotSet(badgeId, row, col) {
+ const encodedBadgeId = encodeURIComponent(badgeId);
+ return apiFetch(`badge?command=slotSet&id=${encodedBadgeId}&row=${row}&col=${col}`);
+}
+
+async function clearChangedSlotsFromChanges(changes) {
+ const tasks = [];
+ for (const { r, c, currentId } of changes) {
+ if (currentId === 'null')
+ continue;
+
+ tasks.push(
+ fetchSlotSet('null', r + 1, c + 1)
+ .then(response => ({
+ ok: response.ok,
+ row: r + 1,
+ col: c + 1,
+ id: 'null'
+ }))
+ .catch(error => ({
+ ok: false,
+ row: r + 1,
+ col: c + 1,
+ id: 'null',
+ error: error?.message || `${error}`
+ }))
+ );
+ }
+
+ return Promise.all(tasks);
+}
+
+async function placeNonNullSlotsConcurrent(changes, concurrency) {
+ const tasks = changes
+ .filter(change => change.targetId !== 'null')
+ .map(({ r, c, targetId }) => async () => {
+ try {
+ const response = await fetchSlotSet(targetId, r + 1, c + 1);
+ return {
+ ok: response.ok,
+ row: r + 1,
+ col: c + 1,
+ id: targetId
+ };
+ } catch (error) {
+ return {
+ ok: false,
+ row: r + 1,
+ col: c + 1,
+ id: targetId,
+ error: getErrorMessage(error)
+ };
+ }
+ });
+
+ const results = [];
+ for (let index = 0; index < tasks.length; index += concurrency) {
+ const batch = tasks.slice(index, index + concurrency).map(task => task());
+ results.push(...await Promise.all(batch));
+ }
+ return results;
+}
+
+async function rollbackSlots(backupSlots) {
+ if (!Array.isArray(backupSlots))
+ return;
+
+ const promises = [];
+ for (let r = 0; r < backupSlots.length; r++) {
+ for (let c = 0; c < backupSlots[r].length; c++) {
+ const badgeId = backupSlots[r]?.[c] || 'null';
+ promises.push(fetchSlotSet(badgeId, r + 1, c + 1).catch(() => null));
+ }
+ }
+ await Promise.all(promises);
+}
+
+async function getPresetData(presetId) {
+ const response = await apiFetch(`badge?command=presetGet&preset=${presetId}`);
+ if (!response.ok)
+ throw new Error(getPresetIoMessage('exportFetchFailed', 'Failed to fetch preset from server'));
+ return response.json();
+}
+
+function formatExportFilename(presetIndex) {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = `${now.getMonth() + 1}`.padStart(2, '0');
+ const day = `${now.getDate()}`.padStart(2, '0');
+ const hour = `${now.getHours()}`.padStart(2, '0');
+ const minute = `${now.getMinutes()}`.padStart(2, '0');
+ const second = `${now.getSeconds()}`.padStart(2, '0');
+ const formattedDate = `${year}-${month}-${day}-${hour}h${minute}m${second}s`;
+ const presetNumber = `${(Number.parseInt(presetIndex, 10) || 0) + 1}`.padStart(2, '0');
+ return `badge_preset_${presetNumber}-${formattedDate}${PRESET_FILE_EXT}`;
+}
+
+function downloadJSON(data, filename) {
+ const json = JSON.stringify(data, null, 2);
+ const blob = new Blob([json], { type: 'application/json' });
+ const blobUrl = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = blobUrl;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(blobUrl);
+}
+
+async function handleExport() {
+ try {
+ const presetSelection = document.getElementById('badgePresetSelection');
+ if (!presetSelection)
+ throw new Error(getPresetIoMessage('importRequiredElementsNotFound', 'Required elements not found'));
+
+ const presetId = presetSelection.value;
+ const presetSlots = await getPresetData(presetId);
+ if (isEmptyBadgeSlots(presetSlots)) {
+ alert(getPresetIoMessage('exportEmpty', 'The badge preset to export is empty.'));
+ return;
+ }
+
+ const { rows, cols } = getCurrentGridDimensions(presetSlots);
+ const fullGridSlots = expandBadgeSlotsToGrid(presetSlots, rows, cols);
+ downloadJSON({ badgeSlots: fullGridSlots }, formatExportFilename(presetId));
+ showPresetIoSuccess(getPresetIoMessage('exportSuccess', 'Badge preset exported successfully.'));
+ } catch (error) {
+ alert(`${getPresetIoMessage('exportFailed', 'Export failed: ')}${getErrorMessage(error)}`);
+ }
+}
+
+async function applyPresetToSlot(badgeSlots) {
+ const presetModal = document.getElementById('badgePresetModal');
+ const presetSelection = document.getElementById('badgePresetSelection');
+
+ if (!presetSelection || typeof apiFetch !== 'function')
+ throw new Error(getPresetIoMessage('importRequiredElementsNotFound', 'Required elements not found'));
+
+ let backupSlots = null;
+ let changedServerSlots = false;
+
+ try {
+ if (presetModal && typeof addLoader === 'function')
+ addLoader(presetModal, true);
+
+ const backupResponse = await apiFetch('badge?command=slotList');
+ if (!backupResponse.ok)
+ throw new Error(getPresetIoMessage('importFetchSlotsFailed', 'Failed to fetch current slots'));
+ backupSlots = await backupResponse.json();
+
+ const playerRows = backupSlots.length;
+ const playerCols = backupSlots[0]?.length || 0;
+ validateBadgeSlots(badgeSlots, playerRows, playerCols);
+ const normalizedSlots = expandBadgeSlotsToGrid(badgeSlots, playerRows, playerCols);
+
+ const changes = computeChanges(normalizedSlots, backupSlots, playerRows, playerCols);
+ const clearResults = await clearChangedSlotsFromChanges(changes);
+ const placeResults = await placeNonNullSlotsConcurrent(changes, SLOT_SET_CONCURRENCY);
+ changedServerSlots = clearResults.length > 0 || placeResults.length > 0;
+
+ const allResults = [...clearResults, ...placeResults];
+ const failures = allResults.filter(result => !result.ok);
+ if (failures.length > 0) {
+ throw new Error(getPresetIoMessage(
+ 'importSetSlotsFailed',
+ 'Failed to set slots ({FAIL}/{TOTAL}).',
+ { FAIL: failures.length, TOTAL: allResults.length }
+ ));
+ }
+
+ const saveResponse = await apiFetch(`badge?command=presetSave&preset=${presetSelection.value}`);
+ if (!saveResponse.ok)
+ throw new Error(getPresetIoMessage('importSaveFailed', 'Failed to save the preset.'));
+
+ if (typeof initBadgePresetModal === 'function')
+ initBadgePresetModal();
+ showPresetIoSuccess(getPresetIoMessage('importSuccess', 'Badge preset imported successfully.'));
+ } finally {
+ try {
+ if (changedServerSlots && backupSlots)
+ await rollbackSlots(backupSlots);
+ } finally {
+ if (presetModal && typeof removeLoader === 'function')
+ removeLoader(presetModal);
+ }
+ }
+}
+
+function handleImport() {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = PRESET_FILE_ACCEPT;
+ input.onchange = (event) => {
+ const fileInput = /** @type {HTMLInputElement} */ (event.target);
+ const file = fileInput?.files?.[0];
+ if (!file)
+ return;
+
+ if (file.size > PRESET_FILE_MAX_BYTES) {
+ alert(getPresetIoMessage('importFileTooLarge', 'File is too large.'));
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = async (loadEvent) => {
+ try {
+ const rawText = `${loadEvent.target?.result || ''}`;
+ const maxRows = typeof maxBadgeSlotRows === 'number' ? maxBadgeSlotRows : 8;
+ const maxCols = typeof maxBadgeSlotCols === 'number' ? maxBadgeSlotCols : 7;
+ const badgeSlots = parsePresetFile(rawText, maxRows, maxCols);
+
+ if (isEmptyBadgeSlots(badgeSlots)) {
+ alert(getPresetIoMessage('importEmpty', 'The badge preset to import is empty.'));
+ return;
+ }
+
+ await applyPresetToSlot(badgeSlots);
+ } catch (error) {
+ alert(`${getPresetIoMessage('importPresetFailed', 'Failed to import the badge preset: ')}${getErrorMessage(error)}`);
+ }
+ };
+ reader.onerror = () => {
+ alert(getPresetIoMessage('importFileReadFailed', 'Failed to read the file.'));
+ };
+ reader.readAsText(file);
+ };
+ input.click();
+}
+
+function initBadgePresetIO() {
+ const exportButton = document.getElementById('badgePresetExport');
+ const importButton = document.getElementById('badgePresetImport');
+ if (!exportButton || !importButton)
+ return;
+ if (exportButton.dataset.initialized === 'true')
+ return;
+
+ exportButton.onclick = handleExport;
+ importButton.onclick = handleImport;
+ exportButton.dataset.initialized = 'true';
+}
+
+window.initBadgePresetIO = initBadgePresetIO;
From 9c4c2f99ef921f4e0c6f5890eb06c8573c75a374 Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:00:24 +0900
Subject: [PATCH 03/15] Init badge preset I/O function
---
badges.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/badges.js b/badges.js
index c565b0f09..63325c0c6 100644
--- a/badges.js
+++ b/badges.js
@@ -730,6 +730,8 @@ function initBadgeControls() {
};
document.getElementById('badgePresetSelection').onchange = initBadgePresetModal;
+ if (typeof initBadgePresetIO === 'function')
+ initBadgePresetIO();
document.getElementById('badgePresetSave').onclick = () => {
apiFetch(`badge?command=presetSave&preset=${document.getElementById('badgePresetSelection').value}`)
From c7d55717a62903dcd778eabda31e738c5e808018 Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:16:49 +0900
Subject: [PATCH 04/15] Add I/O messages for badge presets export and import
---
lang/en.json | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/lang/en.json b/lang/en.json
index 720eed6e8..d07a57c0a 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -420,7 +420,17 @@
"badgePreset": {
"title": "Manage Badge Presets",
"selectPreset": "Select Preset",
- "presetName": "Preset {{index}}"
+ "presetName": "Preset {{index}}",
+ "export": "Export",
+ "import": "Import",
+ "io": {
+ "exportSuccess": "Badge preset exported successfully.",
+ "importSuccess": "Badge preset imported successfully.",
+ "empty": "This preset is empty.",
+ "invalidFile": "Invalid preset file.",
+ "exportFailed": "Export failed.",
+ "importFailed": "Import failed."
+ }
},
"save": {
"title": "Manage Save Data",
@@ -1238,4 +1248,4 @@
}
}
}
-}
+}
\ No newline at end of file
From c05eab4998646764436caf56df7eb9a2e89a1298 Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:19:02 +0900
Subject: [PATCH 05/15] Update error handling and delete debug code
---
badgepresetio.js | 143 ++++++++++++++++++++---------------------------
1 file changed, 62 insertions(+), 81 deletions(-)
diff --git a/badgepresetio.js b/badgepresetio.js
index e25f46a43..d834c846d 100644
--- a/badgepresetio.js
+++ b/badgepresetio.js
@@ -4,22 +4,11 @@ const PRESET_FILE_MAX_BYTES = 64 * 1024;
const BADGE_ID_PATTERN = /^[a-zA-Z0-9_]+$/;
const SLOT_SET_CONCURRENCY = 8;
-function getPresetIoMessage(key, fallback, replacements) {
- let message = fallback;
+function getPresetIoMessage(key, fallback) {
const i18nextInstance = /** @type {any} */ (window).i18next;
if (i18nextInstance)
- message = i18nextInstance.t(`modal.badgePreset.io.${key}`, fallback);
- if (replacements) {
- for (const replacementKey of Object.keys(replacements))
- message = message.replace(`{${replacementKey}}`, `${replacements[replacementKey]}`);
- }
- return message;
-}
-
-function getErrorMessage(error) {
- if (error instanceof Error)
- return error.message;
- return `${error}`;
+ return i18nextInstance.t(`modal.badgePreset.io.${key}`, fallback);
+ return fallback;
}
function showPresetIoSuccess(message) {
@@ -39,31 +28,28 @@ function getCurrentGridDimensions(fallbackSlots) {
function validateBadgeSlots(badgeSlots, maxRows, maxCols) {
if (!Array.isArray(badgeSlots))
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ return false;
if (maxRows && badgeSlots.length > maxRows)
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ return false;
for (const row of badgeSlots) {
if (!Array.isArray(row))
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ return false;
if (maxCols && row.length > maxCols)
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ return false;
for (const badgeId of row) {
if (badgeId === null || badgeId === 'null')
continue;
if (typeof badgeId !== 'string')
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
- if (!BADGE_ID_PATTERN.test(badgeId)) {
- throw new Error(getPresetIoMessage(
- 'importInvalidBadgeId',
- 'Invalid badge ID in preset: {ID}',
- { ID: badgeId }
- ));
- }
+ return false;
+ if (!BADGE_ID_PATTERN.test(badgeId))
+ return false;
}
}
+
+ return true;
}
function parsePresetFile(rawText, maxRows, maxCols) {
@@ -75,9 +61,11 @@ function parsePresetFile(rawText, maxRows, maxCols) {
else if (parsed && Array.isArray(parsed.badgeSlots))
badgeSlots = parsed.badgeSlots;
else
- throw new Error(getPresetIoMessage('importInvalidFormat', 'Invalid preset format'));
+ return null;
+
+ if (!validateBadgeSlots(badgeSlots, maxRows, maxCols))
+ return null;
- validateBadgeSlots(badgeSlots, maxRows, maxCols);
return badgeSlots;
}
@@ -123,19 +111,8 @@ async function clearChangedSlotsFromChanges(changes) {
tasks.push(
fetchSlotSet('null', r + 1, c + 1)
- .then(response => ({
- ok: response.ok,
- row: r + 1,
- col: c + 1,
- id: 'null'
- }))
- .catch(error => ({
- ok: false,
- row: r + 1,
- col: c + 1,
- id: 'null',
- error: error?.message || `${error}`
- }))
+ .then(response => ({ ok: response.ok }))
+ .catch(() => ({ ok: false }))
);
}
@@ -148,20 +125,9 @@ async function placeNonNullSlotsConcurrent(changes, concurrency) {
.map(({ r, c, targetId }) => async () => {
try {
const response = await fetchSlotSet(targetId, r + 1, c + 1);
- return {
- ok: response.ok,
- row: r + 1,
- col: c + 1,
- id: targetId
- };
- } catch (error) {
- return {
- ok: false,
- row: r + 1,
- col: c + 1,
- id: targetId,
- error: getErrorMessage(error)
- };
+ return { ok: response.ok };
+ } catch {
+ return { ok: false };
}
});
@@ -190,7 +156,7 @@ async function rollbackSlots(backupSlots) {
async function getPresetData(presetId) {
const response = await apiFetch(`badge?command=presetGet&preset=${presetId}`);
if (!response.ok)
- throw new Error(getPresetIoMessage('exportFetchFailed', 'Failed to fetch preset from server'));
+ return null;
return response.json();
}
@@ -221,15 +187,22 @@ function downloadJSON(data, filename) {
}
async function handleExport() {
- try {
- const presetSelection = document.getElementById('badgePresetSelection');
- if (!presetSelection)
- throw new Error(getPresetIoMessage('importRequiredElementsNotFound', 'Required elements not found'));
+ const presetSelection = document.getElementById('badgePresetSelection');
+ if (!presetSelection) {
+ alert(getPresetIoMessage('exportFailed', 'Export failed.'));
+ return;
+ }
+ try {
const presetId = presetSelection.value;
const presetSlots = await getPresetData(presetId);
+ if (!presetSlots) {
+ alert(getPresetIoMessage('exportFailed', 'Export failed.'));
+ return;
+ }
+
if (isEmptyBadgeSlots(presetSlots)) {
- alert(getPresetIoMessage('exportEmpty', 'The badge preset to export is empty.'));
+ alert(getPresetIoMessage('empty', 'This preset is empty.'));
return;
}
@@ -238,7 +211,8 @@ async function handleExport() {
downloadJSON({ badgeSlots: fullGridSlots }, formatExportFilename(presetId));
showPresetIoSuccess(getPresetIoMessage('exportSuccess', 'Badge preset exported successfully.'));
} catch (error) {
- alert(`${getPresetIoMessage('exportFailed', 'Export failed: ')}${getErrorMessage(error)}`);
+ console.error('Export failed:', error);
+ alert(getPresetIoMessage('exportFailed', 'Export failed.'));
}
}
@@ -247,10 +221,11 @@ async function applyPresetToSlot(badgeSlots) {
const presetSelection = document.getElementById('badgePresetSelection');
if (!presetSelection || typeof apiFetch !== 'function')
- throw new Error(getPresetIoMessage('importRequiredElementsNotFound', 'Required elements not found'));
+ return false;
let backupSlots = null;
let changedServerSlots = false;
+ let success = false;
try {
if (presetModal && typeof addLoader === 'function')
@@ -258,36 +233,32 @@ async function applyPresetToSlot(badgeSlots) {
const backupResponse = await apiFetch('badge?command=slotList');
if (!backupResponse.ok)
- throw new Error(getPresetIoMessage('importFetchSlotsFailed', 'Failed to fetch current slots'));
+ return false;
backupSlots = await backupResponse.json();
const playerRows = backupSlots.length;
const playerCols = backupSlots[0]?.length || 0;
- validateBadgeSlots(badgeSlots, playerRows, playerCols);
- const normalizedSlots = expandBadgeSlotsToGrid(badgeSlots, playerRows, playerCols);
+ if (!validateBadgeSlots(badgeSlots, playerRows, playerCols))
+ return false;
+ const normalizedSlots = expandBadgeSlotsToGrid(badgeSlots, playerRows, playerCols);
const changes = computeChanges(normalizedSlots, backupSlots, playerRows, playerCols);
const clearResults = await clearChangedSlotsFromChanges(changes);
const placeResults = await placeNonNullSlotsConcurrent(changes, SLOT_SET_CONCURRENCY);
changedServerSlots = clearResults.length > 0 || placeResults.length > 0;
const allResults = [...clearResults, ...placeResults];
- const failures = allResults.filter(result => !result.ok);
- if (failures.length > 0) {
- throw new Error(getPresetIoMessage(
- 'importSetSlotsFailed',
- 'Failed to set slots ({FAIL}/{TOTAL}).',
- { FAIL: failures.length, TOTAL: allResults.length }
- ));
- }
+ if (allResults.some(result => !result.ok))
+ return false;
const saveResponse = await apiFetch(`badge?command=presetSave&preset=${presetSelection.value}`);
if (!saveResponse.ok)
- throw new Error(getPresetIoMessage('importSaveFailed', 'Failed to save the preset.'));
+ return false;
if (typeof initBadgePresetModal === 'function')
initBadgePresetModal();
- showPresetIoSuccess(getPresetIoMessage('importSuccess', 'Badge preset imported successfully.'));
+ success = true;
+ return true;
} finally {
try {
if (changedServerSlots && backupSlots)
@@ -296,6 +267,8 @@ async function applyPresetToSlot(badgeSlots) {
if (presetModal && typeof removeLoader === 'function')
removeLoader(presetModal);
}
+ if (success)
+ showPresetIoSuccess(getPresetIoMessage('importSuccess', 'Badge preset imported successfully.'));
}
}
@@ -310,7 +283,7 @@ function handleImport() {
return;
if (file.size > PRESET_FILE_MAX_BYTES) {
- alert(getPresetIoMessage('importFileTooLarge', 'File is too large.'));
+ alert(getPresetIoMessage('invalidFile', 'Invalid preset file.'));
return;
}
@@ -322,18 +295,26 @@ function handleImport() {
const maxCols = typeof maxBadgeSlotCols === 'number' ? maxBadgeSlotCols : 7;
const badgeSlots = parsePresetFile(rawText, maxRows, maxCols);
+ if (!badgeSlots) {
+ alert(getPresetIoMessage('invalidFile', 'Invalid preset file.'));
+ return;
+ }
+
if (isEmptyBadgeSlots(badgeSlots)) {
- alert(getPresetIoMessage('importEmpty', 'The badge preset to import is empty.'));
+ alert(getPresetIoMessage('empty', 'This preset is empty.'));
return;
}
- await applyPresetToSlot(badgeSlots);
+ const imported = await applyPresetToSlot(badgeSlots);
+ if (!imported)
+ alert(getPresetIoMessage('importFailed', 'Import failed.'));
} catch (error) {
- alert(`${getPresetIoMessage('importPresetFailed', 'Failed to import the badge preset: ')}${getErrorMessage(error)}`);
+ console.error('Import failed:', error);
+ alert(getPresetIoMessage('importFailed', 'Import failed.'));
}
};
reader.onerror = () => {
- alert(getPresetIoMessage('importFileReadFailed', 'Failed to read the file.'));
+ alert(getPresetIoMessage('invalidFile', 'Invalid preset file.'));
};
reader.readAsText(file);
};
From 20a7a5f30877943fd3b4d74424f722bc4f620a2b Mon Sep 17 00:00:00 2001
From: zebraed <30438415+zebraed@users.noreply.github.com>
Date: Sun, 7 Jun 2026 18:23:46 +0900
Subject: [PATCH 06/15] Add modal footer
---
index.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.php b/index.php
index f595513b1..7e0f888e7 100644
--- a/index.php
+++ b/index.php
@@ -1164,7 +1164,7 @@ class="joystickBase" />
-