Update media-server: Add backdrop click-to-close for dialogs
- Add dirty state tracking for script and callback forms - Add backdrop click event listeners to detect clicks outside dialogs - Add unsaved changes confirmation when closing dialogs with modifications - Reset dirty state when opening dialogs or after successful save - Add localized confirmation messages (EN/RU) for unsaved changes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -108,22 +108,30 @@
|
|||||||
fill: var(--text-primary);
|
fill: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#locale-toggle {
|
#locale-select {
|
||||||
background: none;
|
background: var(--bg-tertiary);
|
||||||
border: 2px solid var(--text-secondary);
|
border: 1px solid var(--border);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#locale-toggle:hover {
|
#locale-select:hover {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
color: var(--accent);
|
}
|
||||||
transform: scale(1.05);
|
|
||||||
|
#locale-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#locale-select option {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-container {
|
.player-container {
|
||||||
@@ -486,6 +494,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
margin: auto;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,13 +559,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-footer button {
|
.dialog-footer button {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.625rem 1.5rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
min-width: 100px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-footer .btn-primary {
|
.dialog-footer .btn-primary {
|
||||||
@@ -790,7 +801,10 @@
|
|||||||
<path d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"/>
|
<path d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button id="locale-toggle" onclick="toggleLocale()" data-i18n-title="player.locale" title="Change language">EN</button>
|
<select id="locale-select" onchange="changeLocale()" title="Change language">
|
||||||
|
<option value="en">English</option>
|
||||||
|
<option value="ru">Русский</option>
|
||||||
|
</select>
|
||||||
<div class="status-indicator">
|
<div class="status-indicator">
|
||||||
<span class="status-dot" id="status-dot"></span>
|
<span class="status-dot" id="status-dot"></span>
|
||||||
<span id="status-text" data-i18n="player.status.disconnected">Disconnected</span>
|
<span id="status-text" data-i18n="player.status.disconnected">Disconnected</span>
|
||||||
@@ -920,7 +934,7 @@
|
|||||||
<!-- Add/Edit Script Dialog -->
|
<!-- Add/Edit Script Dialog -->
|
||||||
<dialog id="scriptDialog">
|
<dialog id="scriptDialog">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<h3 id="dialogTitle">Add Script</h3>
|
<h3 id="dialogTitle" data-i18n="scripts.dialog.add">Add Script</h3>
|
||||||
</div>
|
</div>
|
||||||
<form id="scriptForm" onsubmit="saveScript(event)">
|
<form id="scriptForm" onsubmit="saveScript(event)">
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
@@ -928,39 +942,39 @@
|
|||||||
<input type="hidden" id="scriptIsEdit">
|
<input type="hidden" id="scriptIsEdit">
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Script Name *
|
<span data-i18n="scripts.field.name">Script Name *</span>
|
||||||
<input type="text" id="scriptName" required pattern="[a-zA-Z0-9_]+"
|
<input type="text" id="scriptName" required pattern="[a-zA-Z0-9_]+"
|
||||||
title="Only letters, numbers, and underscores allowed" maxlength="64">
|
data-i18n-title="scripts.placeholder.name" title="Only letters, numbers, and underscores allowed" maxlength="64">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Label
|
<span data-i18n="scripts.field.label">Label</span>
|
||||||
<input type="text" id="scriptLabel" placeholder="Human-readable name">
|
<input type="text" id="scriptLabel" data-i18n-placeholder="scripts.placeholder.label" placeholder="Human-readable name">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Command *
|
<span data-i18n="scripts.field.command">Command *</span>
|
||||||
<input type="text" id="scriptCommand" required placeholder="e.g., shutdown /s /t 0">
|
<input type="text" id="scriptCommand" required data-i18n-placeholder="scripts.placeholder.command" placeholder="e.g., shutdown /s /t 0">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Description
|
<span data-i18n="scripts.field.description">Description</span>
|
||||||
<textarea id="scriptDescription" placeholder="What does this script do?"></textarea>
|
<textarea id="scriptDescription" data-i18n-placeholder="scripts.placeholder.description" placeholder="What does this script do?"></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Icon (MDI)
|
<span data-i18n="scripts.field.icon">Icon (MDI)</span>
|
||||||
<input type="text" id="scriptIcon" placeholder="e.g., mdi:power">
|
<input type="text" id="scriptIcon" data-i18n-placeholder="scripts.placeholder.icon" placeholder="e.g., mdi:power">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Timeout (seconds)
|
<span data-i18n="scripts.field.timeout">Timeout (seconds)</span>
|
||||||
<input type="number" id="scriptTimeout" value="30" min="1" max="300">
|
<input type="number" id="scriptTimeout" value="30" min="1" max="300">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<button type="button" class="btn-secondary" onclick="closeScriptDialog()">Cancel</button>
|
<button type="button" class="btn-secondary" onclick="closeScriptDialog()" data-i18n="scripts.button.cancel">Cancel</button>
|
||||||
<button type="submit" class="btn-primary">Save</button>
|
<button type="submit" class="btn-primary" data-i18n="scripts.button.save">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
@@ -968,48 +982,48 @@
|
|||||||
<!-- Add/Edit Callback Dialog -->
|
<!-- Add/Edit Callback Dialog -->
|
||||||
<dialog id="callbackDialog">
|
<dialog id="callbackDialog">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<h3 id="callbackDialogTitle">Add Callback</h3>
|
<h3 id="callbackDialogTitle" data-i18n="callbacks.dialog.add">Add Callback</h3>
|
||||||
</div>
|
</div>
|
||||||
<form id="callbackForm" onsubmit="saveCallback(event)">
|
<form id="callbackForm" onsubmit="saveCallback(event)">
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
<input type="hidden" id="callbackIsEdit">
|
<input type="hidden" id="callbackIsEdit">
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Event *
|
<span data-i18n="callbacks.field.event">Event *</span>
|
||||||
<select id="callbackName" required>
|
<select id="callbackName" required>
|
||||||
<option value="">Select event...</option>
|
<option value="" data-i18n="callbacks.placeholder.event">Select event...</option>
|
||||||
<option value="on_play">on_play - After play succeeds</option>
|
<option value="on_play" data-i18n="callbacks.event.on_play">on_play - After play succeeds</option>
|
||||||
<option value="on_pause">on_pause - After pause succeeds</option>
|
<option value="on_pause" data-i18n="callbacks.event.on_pause">on_pause - After pause succeeds</option>
|
||||||
<option value="on_stop">on_stop - After stop succeeds</option>
|
<option value="on_stop" data-i18n="callbacks.event.on_stop">on_stop - After stop succeeds</option>
|
||||||
<option value="on_next">on_next - After next track succeeds</option>
|
<option value="on_next" data-i18n="callbacks.event.on_next">on_next - After next track succeeds</option>
|
||||||
<option value="on_previous">on_previous - After previous track succeeds</option>
|
<option value="on_previous" data-i18n="callbacks.event.on_previous">on_previous - After previous track succeeds</option>
|
||||||
<option value="on_volume">on_volume - After volume change</option>
|
<option value="on_volume" data-i18n="callbacks.event.on_volume">on_volume - After volume change</option>
|
||||||
<option value="on_mute">on_mute - After mute toggle</option>
|
<option value="on_mute" data-i18n="callbacks.event.on_mute">on_mute - After mute toggle</option>
|
||||||
<option value="on_seek">on_seek - After seek succeeds</option>
|
<option value="on_seek" data-i18n="callbacks.event.on_seek">on_seek - After seek succeeds</option>
|
||||||
<option value="on_turn_on">on_turn_on - Callback-only action</option>
|
<option value="on_turn_on" data-i18n="callbacks.event.on_turn_on">on_turn_on - Callback-only action</option>
|
||||||
<option value="on_turn_off">on_turn_off - Callback-only action</option>
|
<option value="on_turn_off" data-i18n="callbacks.event.on_turn_off">on_turn_off - Callback-only action</option>
|
||||||
<option value="on_toggle">on_toggle - Callback-only action</option>
|
<option value="on_toggle" data-i18n="callbacks.event.on_toggle">on_toggle - Callback-only action</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Command *
|
<span data-i18n="callbacks.field.command">Command *</span>
|
||||||
<input type="text" id="callbackCommand" required placeholder="e.g., shutdown /s /t 0">
|
<input type="text" id="callbackCommand" required data-i18n-placeholder="callbacks.placeholder.command" placeholder="e.g., shutdown /s /t 0">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Timeout (seconds)
|
<span data-i18n="callbacks.field.timeout">Timeout (seconds)</span>
|
||||||
<input type="number" id="callbackTimeout" value="30" min="1" max="300">
|
<input type="number" id="callbackTimeout" value="30" min="1" max="300">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Working Directory
|
<span data-i18n="callbacks.field.workdir">Working Directory</span>
|
||||||
<input type="text" id="callbackWorkingDir" placeholder="Optional">
|
<input type="text" id="callbackWorkingDir" data-i18n-placeholder="callbacks.placeholder.workdir" placeholder="Optional">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<button type="button" class="btn-secondary" onclick="closeCallbackDialog()">Cancel</button>
|
<button type="button" class="btn-secondary" onclick="closeCallbackDialog()" data-i18n="callbacks.button.cancel">Cancel</button>
|
||||||
<button type="submit" class="btn-primary">Save</button>
|
<button type="submit" class="btn-primary" data-i18n="callbacks.button.save">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
@@ -1049,7 +1063,10 @@
|
|||||||
// Locale management
|
// Locale management
|
||||||
let currentLocale = 'en';
|
let currentLocale = 'en';
|
||||||
let translations = {};
|
let translations = {};
|
||||||
const supportedLocales = ['en', 'ru'];
|
const supportedLocales = {
|
||||||
|
'en': 'English',
|
||||||
|
'ru': 'Русский'
|
||||||
|
};
|
||||||
|
|
||||||
// Minimal inline fallback for critical UI elements
|
// Minimal inline fallback for critical UI elements
|
||||||
const fallbackTranslations = {
|
const fallbackTranslations = {
|
||||||
@@ -1096,7 +1113,7 @@
|
|||||||
const langCode = browserLang.split('-')[0]; // 'en-US' -> 'en', 'ru-RU' -> 'ru'
|
const langCode = browserLang.split('-')[0]; // 'en-US' -> 'en', 'ru-RU' -> 'ru'
|
||||||
|
|
||||||
// Only return if we support it
|
// Only return if we support it
|
||||||
return supportedLocales.includes(langCode) ? langCode : 'en';
|
return supportedLocales[langCode] ? langCode : 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize locale
|
// Initialize locale
|
||||||
@@ -1107,7 +1124,7 @@
|
|||||||
|
|
||||||
// Set locale
|
// Set locale
|
||||||
async function setLocale(locale) {
|
async function setLocale(locale) {
|
||||||
if (!supportedLocales.includes(locale)) {
|
if (!supportedLocales[locale]) {
|
||||||
locale = 'en';
|
locale = 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1122,22 +1139,25 @@
|
|||||||
// Update all text
|
// Update all text
|
||||||
updateAllText();
|
updateAllText();
|
||||||
|
|
||||||
// Update locale toggle button (if visible)
|
// Update locale select dropdown (if visible)
|
||||||
updateLocaleToggle();
|
updateLocaleSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle between locales
|
// Change locale from dropdown
|
||||||
async function toggleLocale() {
|
function changeLocale() {
|
||||||
const newLocale = currentLocale === 'en' ? 'ru' : 'en';
|
const select = document.getElementById('locale-select');
|
||||||
await setLocale(newLocale);
|
const newLocale = select.value;
|
||||||
|
if (newLocale && newLocale !== currentLocale) {
|
||||||
|
localStorage.setItem('locale', newLocale);
|
||||||
|
setLocale(newLocale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update locale toggle button
|
// Update locale select dropdown
|
||||||
function updateLocaleToggle() {
|
function updateLocaleSelect() {
|
||||||
const localeButton = document.getElementById('locale-toggle');
|
const select = document.getElementById('locale-select');
|
||||||
if (localeButton) {
|
if (select) {
|
||||||
localeButton.textContent = currentLocale === 'en' ? 'RU' : 'EN';
|
select.value = currentLocale;
|
||||||
localeButton.title = t('player.locale');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1192,6 +1212,10 @@
|
|||||||
let scripts = [];
|
let scripts = [];
|
||||||
let lastStatus = null; // Store last status for locale switching
|
let lastStatus = null; // Store last status for locale switching
|
||||||
|
|
||||||
|
// Dialog dirty state tracking
|
||||||
|
let scriptFormDirty = false;
|
||||||
|
let callbackFormDirty = false;
|
||||||
|
|
||||||
// Position interpolation
|
// Position interpolation
|
||||||
let lastPositionUpdate = 0;
|
let lastPositionUpdate = 0;
|
||||||
let lastPositionValue = 0;
|
let lastPositionValue = 0;
|
||||||
@@ -1247,6 +1271,42 @@
|
|||||||
authenticate();
|
authenticate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Script form dirty state tracking
|
||||||
|
const scriptForm = document.getElementById('scriptForm');
|
||||||
|
scriptForm.addEventListener('input', () => {
|
||||||
|
scriptFormDirty = true;
|
||||||
|
});
|
||||||
|
scriptForm.addEventListener('change', () => {
|
||||||
|
scriptFormDirty = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback form dirty state tracking
|
||||||
|
const callbackForm = document.getElementById('callbackForm');
|
||||||
|
callbackForm.addEventListener('input', () => {
|
||||||
|
callbackFormDirty = true;
|
||||||
|
});
|
||||||
|
callbackForm.addEventListener('change', () => {
|
||||||
|
callbackFormDirty = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Script dialog backdrop click to close
|
||||||
|
const scriptDialog = document.getElementById('scriptDialog');
|
||||||
|
scriptDialog.addEventListener('click', (e) => {
|
||||||
|
// Check if click is on the backdrop (not the dialog content)
|
||||||
|
if (e.target === scriptDialog) {
|
||||||
|
closeScriptDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback dialog backdrop click to close
|
||||||
|
const callbackDialog = document.getElementById('callbackDialog');
|
||||||
|
callbackDialog.addEventListener('click', (e) => {
|
||||||
|
// Check if click is on the backdrop (not the dialog content)
|
||||||
|
if (e.target === callbackDialog) {
|
||||||
|
closeCallbackDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function showAuthForm(errorMessage = '') {
|
function showAuthForm(errorMessage = '') {
|
||||||
@@ -1707,7 +1767,10 @@
|
|||||||
document.getElementById('scriptOriginalName').value = '';
|
document.getElementById('scriptOriginalName').value = '';
|
||||||
document.getElementById('scriptIsEdit').value = 'false';
|
document.getElementById('scriptIsEdit').value = 'false';
|
||||||
document.getElementById('scriptName').disabled = false;
|
document.getElementById('scriptName').disabled = false;
|
||||||
title.textContent = 'Add Script';
|
title.textContent = t('scripts.dialog.add');
|
||||||
|
|
||||||
|
// Reset dirty state
|
||||||
|
scriptFormDirty = false;
|
||||||
|
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
}
|
}
|
||||||
@@ -1746,7 +1809,11 @@
|
|||||||
document.getElementById('scriptIcon').value = script.icon || '';
|
document.getElementById('scriptIcon').value = script.icon || '';
|
||||||
document.getElementById('scriptTimeout').value = script.timeout || 30;
|
document.getElementById('scriptTimeout').value = script.timeout || 30;
|
||||||
|
|
||||||
title.textContent = 'Edit Script';
|
title.textContent = t('scripts.dialog.edit');
|
||||||
|
|
||||||
|
// Reset dirty state
|
||||||
|
scriptFormDirty = false;
|
||||||
|
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading script for edit:', error);
|
console.error('Error loading script for edit:', error);
|
||||||
@@ -1755,7 +1822,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeScriptDialog() {
|
function closeScriptDialog() {
|
||||||
|
// Check if form has unsaved changes
|
||||||
|
if (scriptFormDirty) {
|
||||||
|
if (!confirm(t('scripts.confirm.unsaved'))) {
|
||||||
|
return; // User cancelled, don't close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const dialog = document.getElementById('scriptDialog');
|
const dialog = document.getElementById('scriptDialog');
|
||||||
|
scriptFormDirty = false; // Reset dirty state
|
||||||
dialog.close();
|
dialog.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1797,6 +1872,7 @@
|
|||||||
|
|
||||||
if (response.ok && result.success) {
|
if (response.ok && result.success) {
|
||||||
showToast(`Script ${isEdit ? 'updated' : 'created'} successfully`, 'success');
|
showToast(`Script ${isEdit ? 'updated' : 'created'} successfully`, 'success');
|
||||||
|
scriptFormDirty = false; // Reset dirty state before closing
|
||||||
closeScriptDialog();
|
closeScriptDialog();
|
||||||
// Don't reload manually - WebSocket will trigger it
|
// Don't reload manually - WebSocket will trigger it
|
||||||
} else {
|
} else {
|
||||||
@@ -1886,7 +1962,10 @@
|
|||||||
form.reset();
|
form.reset();
|
||||||
document.getElementById('callbackIsEdit').value = 'false';
|
document.getElementById('callbackIsEdit').value = 'false';
|
||||||
document.getElementById('callbackName').disabled = false;
|
document.getElementById('callbackName').disabled = false;
|
||||||
title.textContent = 'Add Callback';
|
title.textContent = t('callbacks.dialog.add');
|
||||||
|
|
||||||
|
// Reset dirty state
|
||||||
|
callbackFormDirty = false;
|
||||||
|
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
}
|
}
|
||||||
@@ -1922,7 +2001,11 @@
|
|||||||
document.getElementById('callbackTimeout').value = callback.timeout;
|
document.getElementById('callbackTimeout').value = callback.timeout;
|
||||||
document.getElementById('callbackWorkingDir').value = callback.working_dir || '';
|
document.getElementById('callbackWorkingDir').value = callback.working_dir || '';
|
||||||
|
|
||||||
title.textContent = 'Edit Callback';
|
title.textContent = t('callbacks.dialog.edit');
|
||||||
|
|
||||||
|
// Reset dirty state
|
||||||
|
callbackFormDirty = false;
|
||||||
|
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading callback for edit:', error);
|
console.error('Error loading callback for edit:', error);
|
||||||
@@ -1931,7 +2014,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeCallbackDialog() {
|
function closeCallbackDialog() {
|
||||||
|
// Check if form has unsaved changes
|
||||||
|
if (callbackFormDirty) {
|
||||||
|
if (!confirm(t('callbacks.confirm.unsaved'))) {
|
||||||
|
return; // User cancelled, don't close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const dialog = document.getElementById('callbackDialog');
|
const dialog = document.getElementById('callbackDialog');
|
||||||
|
callbackFormDirty = false; // Reset dirty state
|
||||||
dialog.close();
|
dialog.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1969,6 +2060,7 @@
|
|||||||
|
|
||||||
if (response.ok && result.success) {
|
if (response.ok && result.success) {
|
||||||
showToast(`Callback ${isEdit ? 'updated' : 'created'} successfully`, 'success');
|
showToast(`Callback ${isEdit ? 'updated' : 'created'} successfully`, 'success');
|
||||||
|
callbackFormDirty = false; // Reset dirty state before closing
|
||||||
closeCallbackDialog();
|
closeCallbackDialog();
|
||||||
loadCallbacksTable();
|
loadCallbacksTable();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"scripts.msg.load_failed": "Failed to load script details",
|
"scripts.msg.load_failed": "Failed to load script details",
|
||||||
"scripts.msg.list_failed": "Failed to load scripts",
|
"scripts.msg.list_failed": "Failed to load scripts",
|
||||||
"scripts.confirm.delete": "Are you sure you want to delete the script \"{name}\"?",
|
"scripts.confirm.delete": "Are you sure you want to delete the script \"{name}\"?",
|
||||||
|
"scripts.confirm.unsaved": "You have unsaved changes. Are you sure you want to discard them?",
|
||||||
"callbacks.management": "Callback Management",
|
"callbacks.management": "Callback Management",
|
||||||
"callbacks.description": "Callbacks are scripts triggered automatically by media control events (play, pause, stop, etc.)",
|
"callbacks.description": "Callbacks are scripts triggered automatically by media control events (play, pause, stop, etc.)",
|
||||||
"callbacks.add": "Add",
|
"callbacks.add": "Add",
|
||||||
@@ -105,5 +106,6 @@
|
|||||||
"callbacks.msg.not_found": "Callback not found",
|
"callbacks.msg.not_found": "Callback not found",
|
||||||
"callbacks.msg.load_failed": "Failed to load callback details",
|
"callbacks.msg.load_failed": "Failed to load callback details",
|
||||||
"callbacks.msg.list_failed": "Failed to load callbacks",
|
"callbacks.msg.list_failed": "Failed to load callbacks",
|
||||||
"callbacks.confirm.delete": "Are you sure you want to delete the callback \"{name}\"?"
|
"callbacks.confirm.delete": "Are you sure you want to delete the callback \"{name}\"?",
|
||||||
|
"callbacks.confirm.unsaved": "You have unsaved changes. Are you sure you want to discard them?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"scripts.msg.load_failed": "Не удалось загрузить данные скрипта",
|
"scripts.msg.load_failed": "Не удалось загрузить данные скрипта",
|
||||||
"scripts.msg.list_failed": "Не удалось загрузить скрипты",
|
"scripts.msg.list_failed": "Не удалось загрузить скрипты",
|
||||||
"scripts.confirm.delete": "Вы уверены, что хотите удалить скрипт \"{name}\"?",
|
"scripts.confirm.delete": "Вы уверены, что хотите удалить скрипт \"{name}\"?",
|
||||||
|
"scripts.confirm.unsaved": "У вас есть несохраненные изменения. Вы уверены, что хотите отменить их?",
|
||||||
"callbacks.management": "Управление Обратными Вызовами",
|
"callbacks.management": "Управление Обратными Вызовами",
|
||||||
"callbacks.description": "Обратные вызовы - это скрипты, автоматически запускаемые при событиях управления медиа (воспроизведение, пауза, остановка и т.д.)",
|
"callbacks.description": "Обратные вызовы - это скрипты, автоматически запускаемые при событиях управления медиа (воспроизведение, пауза, остановка и т.д.)",
|
||||||
"callbacks.add": "Добавить",
|
"callbacks.add": "Добавить",
|
||||||
@@ -105,5 +106,6 @@
|
|||||||
"callbacks.msg.not_found": "Обратный вызов не найден",
|
"callbacks.msg.not_found": "Обратный вызов не найден",
|
||||||
"callbacks.msg.load_failed": "Не удалось загрузить данные обратного вызова",
|
"callbacks.msg.load_failed": "Не удалось загрузить данные обратного вызова",
|
||||||
"callbacks.msg.list_failed": "Не удалось загрузить обратные вызовы",
|
"callbacks.msg.list_failed": "Не удалось загрузить обратные вызовы",
|
||||||
"callbacks.confirm.delete": "Вы уверены, что хотите удалить обратный вызов \"{name}\"?"
|
"callbacks.confirm.delete": "Вы уверены, что хотите удалить обратный вызов \"{name}\"?",
|
||||||
|
"callbacks.confirm.unsaved": "У вас есть несохраненные изменения. Вы уверены, что хотите отменить их?"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user