415 lines
14 KiB
JavaScript
415 lines
14 KiB
JavaScript
const application_state = {
|
|
websocket_connection: null,
|
|
pending_administrator_form: null,
|
|
stopwatch_interval_identifier: null
|
|
};
|
|
|
|
/* --- Deterministische Funktionen ("ehrliche" Funktionen) --- */
|
|
|
|
function calculate_time_difference(start_time_milliseconds, current_time_milliseconds) {
|
|
return current_time_milliseconds - start_time_milliseconds;
|
|
}
|
|
|
|
function format_stopwatch_string(difference_in_milliseconds) {
|
|
if (difference_in_milliseconds < 0) {
|
|
return '00:00 Minuten';
|
|
}
|
|
|
|
const total_seconds = Math.floor(difference_in_milliseconds / 1000);
|
|
const days_passed = Math.floor(total_seconds / 86400);
|
|
const hours_passed = Math.floor((total_seconds % 86400) / 3600);
|
|
const minutes_passed = Math.floor((total_seconds % 3600) / 60);
|
|
const seconds_passed = total_seconds % 60;
|
|
|
|
let output_string = '';
|
|
|
|
if (days_passed === 1) {
|
|
output_string += '1 Tag und ';
|
|
} else if (days_passed > 1) {
|
|
output_string += days_passed + ' Tage und ';
|
|
}
|
|
|
|
const padded_minutes = String(minutes_passed).padStart(2, '0');
|
|
const padded_seconds = String(seconds_passed).padStart(2, '0');
|
|
|
|
if (hours_passed === 1) {
|
|
output_string += '1:' + padded_minutes + ':' + padded_seconds + ' Stunde';
|
|
} else if (hours_passed > 1) {
|
|
output_string += hours_passed + ':' + padded_minutes + ':' + padded_seconds + ' Stunden';
|
|
} else {
|
|
output_string += padded_minutes + ':' + padded_seconds + ' Minuten';
|
|
}
|
|
|
|
return output_string;
|
|
}
|
|
|
|
function synchronize_element_nodes(current_node, updated_node) {
|
|
if (current_node.nodeType !== updated_node.nodeType) {
|
|
current_node.replaceWith(updated_node.cloneNode(true));
|
|
return;
|
|
}
|
|
if (current_node.nodeType === Node.TEXT_NODE) {
|
|
if (current_node.nodeValue !== updated_node.nodeValue) {
|
|
current_node.nodeValue = updated_node.nodeValue;
|
|
}
|
|
return;
|
|
}
|
|
if (current_node.nodeType !== Node.ELEMENT_NODE) {
|
|
return;
|
|
}
|
|
|
|
if (current_node.tagName !== updated_node.tagName) {
|
|
current_node.replaceWith(updated_node.cloneNode(true));
|
|
return;
|
|
}
|
|
|
|
if (current_node.namespaceURI !== updated_node.namespaceURI) {
|
|
current_node.replaceWith(updated_node.cloneNode(true));
|
|
return;
|
|
}
|
|
|
|
const is_active_input = document.activeElement === current_node;
|
|
const is_input_element = ['INPUT', 'TEXTAREA', 'SELECT'].includes(current_node.tagName);
|
|
|
|
if (is_input_element && !is_active_input) {
|
|
if (current_node.value !== updated_node.value) {
|
|
current_node.value = updated_node.value;
|
|
}
|
|
}
|
|
|
|
const current_attributes = current_node.attributes;
|
|
const updated_attributes = updated_node.attributes;
|
|
|
|
for (let index = 0; index < updated_attributes.length; index++) {
|
|
const attribute = updated_attributes[index];
|
|
if (current_node.getAttribute(attribute.name) !== attribute.value) {
|
|
if (!(is_input_element && is_active_input && attribute.name === 'value')) {
|
|
current_node.setAttribute(attribute.name, attribute.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let index = current_attributes.length - 1; index >= 0; index--) {
|
|
const attribute = current_attributes[index];
|
|
if (!updated_node.hasAttribute(attribute.name)) {
|
|
current_node.removeAttribute(attribute.name);
|
|
}
|
|
}
|
|
|
|
const current_children = Array.from(current_node.childNodes);
|
|
const updated_children = Array.from(updated_node.childNodes);
|
|
const max_length = Math.max(current_children.length, updated_children.length);
|
|
|
|
for (let index = 0; index < max_length; index++) {
|
|
const current_child = current_children[index];
|
|
const updated_child = updated_children[index];
|
|
|
|
if (!current_child && updated_child) {
|
|
current_node.appendChild(updated_child.cloneNode(true));
|
|
} else if (current_child && !updated_child) {
|
|
current_node.removeChild(current_child);
|
|
} else if (current_child && updated_child) {
|
|
synchronize_element_nodes(current_child, updated_child);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- Architektur & State-Management ("unehrliche" Funktionen) --- */
|
|
|
|
function trigger_dom_update(markup_text) {
|
|
const document_parser = new DOMParser();
|
|
const parsed_document = document_parser.parseFromString(markup_text, 'text/html');
|
|
const current_container = document.querySelector('.application-content');
|
|
const new_container = parsed_document.querySelector('.application-content');
|
|
|
|
if (!current_container || !new_container) {
|
|
return;
|
|
}
|
|
|
|
const current_active_tab = document.querySelector('.active-tab');
|
|
const active_tab_target = current_active_tab ? current_active_tab.getAttribute('data-target') : null;
|
|
|
|
const active_modals = [];
|
|
document.querySelectorAll('.active-modal').forEach(function(modal) {
|
|
active_modals.push(modal.id);
|
|
});
|
|
|
|
synchronize_element_nodes(current_container, new_container);
|
|
|
|
if (active_tab_target) {
|
|
document.querySelectorAll('.tab-button').forEach(function(button) { button.classList.remove('active-tab'); });
|
|
document.querySelectorAll('.tab-content').forEach(function(content) { content.classList.remove('active-content'); });
|
|
|
|
const restored_tab = document.querySelector('.tab-button[data-target="' + active_tab_target + '"]');
|
|
const restored_content = document.getElementById(active_tab_target);
|
|
if (restored_tab && restored_content) {
|
|
restored_tab.classList.add('active-tab');
|
|
restored_content.classList.add('active-content');
|
|
}
|
|
}
|
|
|
|
active_modals.forEach(function(modal_identifier) {
|
|
const restored_modal = document.getElementById(modal_identifier);
|
|
if (restored_modal) {
|
|
restored_modal.classList.add('active-modal');
|
|
}
|
|
});
|
|
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
function fetch_and_synchronize_dom() {
|
|
fetch(window.location.href)
|
|
.then(function(network_response) {
|
|
if (!network_response.ok) {
|
|
throw new Error('Network response failure');
|
|
}
|
|
return network_response.text();
|
|
})
|
|
.then(function(markup_text) {
|
|
trigger_dom_update(markup_text);
|
|
})
|
|
.catch(function(error_object) {
|
|
console.error('DOM Synchronization failed:', error_object);
|
|
});
|
|
}
|
|
|
|
function execute_asynchronous_form_submission(target_form) {
|
|
fetch(target_form.action, {
|
|
method: target_form.method,
|
|
body: new FormData(target_form)
|
|
})
|
|
.then(function(network_response) {
|
|
if (!network_response.ok) {
|
|
throw new Error('Form submission failure');
|
|
}
|
|
return network_response.text();
|
|
})
|
|
.then(function(markup_text) {
|
|
trigger_dom_update(markup_text);
|
|
})
|
|
.catch(function(error_object) {
|
|
console.error('Form execution failed:', error_object);
|
|
});
|
|
}
|
|
|
|
function update_game_stopwatch_ui() {
|
|
const stopwatch_element = document.getElementById('game-stopwatch');
|
|
if (!stopwatch_element) {
|
|
return;
|
|
}
|
|
|
|
const start_time_string = stopwatch_element.getAttribute('data-start-time');
|
|
if (!start_time_string) {
|
|
return;
|
|
}
|
|
|
|
const start_date = new Date(start_time_string);
|
|
if (isNaN(start_date.getTime())) {
|
|
return;
|
|
}
|
|
|
|
const current_date = new Date();
|
|
const difference = calculate_time_difference(start_date.getTime(), current_date.getTime());
|
|
stopwatch_element.textContent = format_stopwatch_string(difference);
|
|
}
|
|
|
|
function modify_modal_state(modal_identifier, is_active) {
|
|
const modal = document.getElementById(modal_identifier);
|
|
if (modal) {
|
|
if (is_active) {
|
|
modal.classList.add('active-modal');
|
|
} else {
|
|
modal.classList.remove('active-modal');
|
|
}
|
|
}
|
|
}
|
|
|
|
function write_text_to_clipboard(text_payload) {
|
|
if (!navigator.clipboard) {
|
|
console.error('Clipboard API unvailable.');
|
|
return;
|
|
}
|
|
navigator.clipboard.writeText(text_payload).catch(function(error_object) {
|
|
console.error('Clipboard action failed:', error_object);
|
|
});
|
|
}
|
|
|
|
/* --- Globale Event-Delegation --- */
|
|
|
|
function handle_global_clicks(mouse_event) {
|
|
const target_element = mouse_event.target;
|
|
|
|
const tab_button = target_element.closest('.tab-button');
|
|
if (tab_button) {
|
|
const target_identifier = tab_button.getAttribute('data-target');
|
|
const parent_container = tab_button.closest('.application-content');
|
|
|
|
parent_container.querySelectorAll('.tab-button').forEach(function(button) {
|
|
button.classList.remove('active-tab');
|
|
});
|
|
parent_container.querySelectorAll('.tab-content').forEach(function(content) {
|
|
content.classList.remove('active-content');
|
|
});
|
|
|
|
tab_button.classList.add('active-tab');
|
|
const target_panel = document.getElementById(target_identifier);
|
|
if (target_panel) {
|
|
target_panel.classList.add('active-content');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const action_mapping = {
|
|
'trigger-elimination': function() { modify_modal_state('elimination-confirm-modal', true); },
|
|
'cancel-elimination': function() { modify_modal_state('elimination-confirm-modal', false); },
|
|
'proceed-elimination': function() {
|
|
if (application_state.websocket_connection) {
|
|
application_state.websocket_connection.emit('request_elimination');
|
|
}
|
|
modify_modal_state('elimination-confirm-modal', false);
|
|
},
|
|
'trigger-revoke-elimination': function() {
|
|
if (application_state.websocket_connection) {
|
|
application_state.websocket_connection.emit('revoke_elimination_request');
|
|
}
|
|
},
|
|
'confirm-elimination': function() {
|
|
if (application_state.websocket_connection) {
|
|
application_state.websocket_connection.emit('confirm_elimination');
|
|
}
|
|
modify_modal_state('verification-modal', false);
|
|
},
|
|
'reject-elimination': function() {
|
|
if (application_state.websocket_connection) {
|
|
application_state.websocket_connection.emit('reject_elimination');
|
|
}
|
|
modify_modal_state('verification-modal', false);
|
|
},
|
|
'acknowledge-rejection': function() { modify_modal_state('rejection-modal', false); },
|
|
'trigger-name-change-modal': function() { modify_modal_state('name-change-modal', true); },
|
|
'cancel-name-change-modal': function() { modify_modal_state('name-change-modal', false); },
|
|
'trigger-help-modal': function() { modify_modal_state('help-modal', true); },
|
|
'close-help-modal': function() { modify_modal_state('help-modal', false); },
|
|
'copy-identifier-button': function(button_element) {
|
|
const identifier = button_element.getAttribute('data-identifier');
|
|
write_text_to_clipboard(identifier);
|
|
const icon = button_element.querySelector('i, svg');
|
|
if (icon) {
|
|
icon.setAttribute('data-lucide', 'check');
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
},
|
|
'confirm-admin-action': function() {
|
|
if (application_state.pending_administrator_form) {
|
|
const time_input = document.getElementById('modal_execution_time');
|
|
if (time_input && time_input.value) {
|
|
let hidden_input = application_state.pending_administrator_form.querySelector('input[name="execution_time"]');
|
|
if (!hidden_input) {
|
|
hidden_input = document.createElement('input');
|
|
hidden_input.type = 'hidden';
|
|
hidden_input.name = 'execution_time';
|
|
application_state.pending_administrator_form.appendChild(hidden_input);
|
|
}
|
|
hidden_input.value = time_input.value;
|
|
} else {
|
|
const hidden_input = application_state.pending_administrator_form.querySelector('input[name="execution_time"]');
|
|
if (hidden_input) {
|
|
hidden_input.remove();
|
|
}
|
|
}
|
|
execute_asynchronous_form_submission(application_state.pending_administrator_form);
|
|
application_state.pending_administrator_form = null;
|
|
modify_modal_state('admin-confirmation-modal', false);
|
|
}
|
|
},
|
|
'cancel-admin-action': function() {
|
|
application_state.pending_administrator_form = null;
|
|
modify_modal_state('admin-confirmation-modal', false);
|
|
}
|
|
};
|
|
|
|
for (const [identifier, action_function] of Object.entries(action_mapping)) {
|
|
const button = target_element.closest('#' + identifier);
|
|
if (button) {
|
|
action_function(button);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function handle_global_submits(submit_event) {
|
|
const target_form = submit_event.target;
|
|
|
|
if (target_form.classList.contains('asynchronous-form')) {
|
|
submit_event.preventDefault();
|
|
execute_asynchronous_form_submission(target_form);
|
|
} else if (target_form.classList.contains('admin-asynchronous-form')) {
|
|
submit_event.preventDefault();
|
|
application_state.pending_administrator_form = target_form;
|
|
const time_input = document.getElementById('modal_execution_time');
|
|
if (time_input) {
|
|
time_input.value = '';
|
|
}
|
|
modify_modal_state('admin-confirmation-modal', true);
|
|
}
|
|
}
|
|
|
|
/* --- Initialisierung --- */
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.register('/service_worker.js').catch(function(error_object) {
|
|
console.error('Service Worker Registration failed:', error_object);
|
|
});
|
|
}
|
|
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
|
|
if (application_state.stopwatch_interval_identifier) {
|
|
clearInterval(application_state.stopwatch_interval_identifier);
|
|
}
|
|
application_state.stopwatch_interval_identifier = setInterval(update_game_stopwatch_ui, 1000);
|
|
update_game_stopwatch_ui();
|
|
|
|
document.addEventListener('click', handle_global_clicks);
|
|
document.addEventListener('submit', handle_global_submits);
|
|
|
|
if (typeof io !== 'undefined') {
|
|
application_state.websocket_connection = io();
|
|
|
|
application_state.websocket_connection.on('game_state_changed', function() {
|
|
if (document.querySelector('.game-section') || document.querySelector('.administrator-section')) {
|
|
fetch_and_synchronize_dom();
|
|
}
|
|
});
|
|
|
|
application_state.websocket_connection.on('admin_state_changed', function() {
|
|
if (document.querySelector('.administrator-section')) {
|
|
fetch_and_synchronize_dom();
|
|
}
|
|
});
|
|
|
|
application_state.websocket_connection.on('elimination_verification_requested', function() {
|
|
fetch_and_synchronize_dom();
|
|
setTimeout(function() {
|
|
modify_modal_state('verification-modal', true);
|
|
}, 50);
|
|
});
|
|
|
|
application_state.websocket_connection.on('elimination_rejected', function() {
|
|
modify_modal_state('rejection-modal', true);
|
|
});
|
|
|
|
application_state.websocket_connection.on('elimination_revoked', function() {
|
|
modify_modal_state('verification-modal', false);
|
|
fetch_and_synchronize_dom();
|
|
});
|
|
}
|
|
}); |