Extract event manager controller into module
This commit is contained in:
781
src/app.js
781
src/app.js
@@ -14,6 +14,7 @@ import { renderGuideView, renderOverlayPageView } from "./misc_views.js";
|
||||
import { getSessionsForEventHelper, getModeLabelHelper, normalizeStartModeHelper, getStartModeLabelHelper, getClassNameHelper, getEventNameHelper, renderAssignmentListView, renderSessionsTableView } from "./event_common.js";
|
||||
import { renderEventWorkspaceView } from "./event_workspace_controller.js";
|
||||
import { renderEventManagerMarkup } from "./event_manager_view.js";
|
||||
import { renderEventManagerView } from "./event_manager_controller.js";
|
||||
import { createDefaultAmmcConfigHelper, getManagedWsUrlHelper, loadAmmcConfigFromBackendHelper, saveAmmcConfigToBackendHelper, refreshAmmcStatusHelper, startManagedAmmcHelper, stopManagedAmmcHelper, applyPersistedStateHelper, hydrateFromBackendHelper, scheduleBackendSyncHelper, syncStateToBackendHelper, pingBackendHelper, checkAppVersionHelper, startAppVersionPollingHelper, startOverlaySyncHelper, startOverlayRotationHelper, startOverlayLiveRefreshHelper, ensureAudioContextHelper, playPassingBeepHelper, playFinishSirenHelper, playLeaderCueHelper, playStartCueHelper, playBestLapCueHelper, pushOverlayEventHelper, speakTextHelper, announcePassingHelper, announceRaceFinishedHelper, handleSessionTimerTickHelper, tickClockHelper } from "./runtime_services.js";
|
||||
import { connectDecoderHelper, disconnectDecoderHelper, processDecoderMessageHelper } from "./decoder_runtime.js";
|
||||
|
||||
@@ -2851,92 +2852,40 @@ function renderEventWorkspace(mode) {
|
||||
}
|
||||
|
||||
function renderEventManager(eventId) {
|
||||
const event = state.events.find((e) => e.id === eventId);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
const normalizedEvent = normalizeEvent(event);
|
||||
if (normalizedEvent !== event) {
|
||||
Object.assign(event, normalizedEvent);
|
||||
}
|
||||
ensureRaceParticipantsConfigured(event);
|
||||
|
||||
const sessions = getSessionsForEvent(eventId);
|
||||
const eventManageArea = document.getElementById("eventManageArea");
|
||||
if (!eventManageArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const driverOptions = state.drivers
|
||||
.map((d) => `<option value="${d.id}">${escapeHtml(d.name)}</option>`)
|
||||
.join("");
|
||||
const teamDriverPool = event.mode === "race" ? getTeamDriverPool(event) : { drivers: [], fallback: false };
|
||||
const raceDrivers = event.mode === "race" ? teamDriverPool.drivers : [];
|
||||
const raceTeams = event.mode === "race" ? getEventTeams(event) : [];
|
||||
if (selectedTeamEditId && !raceTeams.some((team) => team.id === selectedTeamEditId)) {
|
||||
selectedTeamEditId = null;
|
||||
}
|
||||
const editingTeam = event.mode === "race" ? raceTeams.find((team) => team.id === selectedTeamEditId) || null : null;
|
||||
const carOptions = state.cars
|
||||
.map((c) => `<option value="${c.id}">${escapeHtml(c.name)} (${escapeHtml(c.transponder)})</option>`)
|
||||
.join("");
|
||||
const branding = normalizeBrandingConfig(event.branding);
|
||||
const editingSession = sessions.find((session) => session.id === selectedSessionEditId) || null;
|
||||
const sessionTypeChoices = getSessionTypeChoices(event.mode);
|
||||
const sessionTypeHintKey = event.mode === "track" ? "events.session_type_hint_track" : "events.session_type_hint_race";
|
||||
const racePresets = getRaceFormatPresets();
|
||||
const selectedPreset = racePresets.find((preset) => preset.id === event.raceConfig.presetId) || racePresets[0];
|
||||
const isEndurancePreset = event.mode === "race" && selectedPreset?.id === "endurance";
|
||||
const showBasicQualifyingFields = raceFormatAdvanced || !isEndurancePreset;
|
||||
const showBasicFinalFields = raceFormatAdvanced || !isEndurancePreset;
|
||||
const selectedParticipantCount = event.mode === "race" ? (event.raceConfig.participantsConfigured ? (event.raceConfig.driverIds || []).length : raceDrivers.length) : 0;
|
||||
const raceSummaryItems = event.mode === "race" ? getRaceSummaryItems(event, sessions, raceDrivers, selectedPreset, { t, getStartModeLabel }) : [];
|
||||
const raceSummaryWarnings = event.mode === "race" ? getRaceSummaryWarnings(event, sessions, raceDrivers, raceTeams, selectedPreset, { t }) : [];
|
||||
const manageStatuses = event.mode === "race" ? getRaceManageStatuses(event, sessions, raceDrivers, raceTeams, selectedPreset) : null;
|
||||
const gridSessions = event.mode === "race" ? sessions.filter((session) => normalizeStartMode(session.startMode) === "position") : [];
|
||||
if (selectedGridSessionId && !gridSessions.some((session) => session.id === selectedGridSessionId)) {
|
||||
selectedGridSessionId = "";
|
||||
}
|
||||
const selectedGridSession =
|
||||
gridSessions.find((session) => session.id === selectedGridSessionId) || gridSessions[0] || null;
|
||||
if (!selectedGridSessionId && selectedGridSession) {
|
||||
selectedGridSessionId = selectedGridSession.id;
|
||||
}
|
||||
|
||||
eventManageArea.innerHTML = renderEventManagerMarkup({
|
||||
event,
|
||||
renderEventManagerView({
|
||||
eventId,
|
||||
state,
|
||||
t,
|
||||
escapeHtml,
|
||||
sessions,
|
||||
sessionTypeChoices,
|
||||
sessionTypeHintKey,
|
||||
raceTeams,
|
||||
normalizeEvent,
|
||||
ensureRaceParticipantsConfigured,
|
||||
getSessionsForEvent,
|
||||
getSelectedTeamEditId: () => selectedTeamEditId,
|
||||
setSelectedTeamEditId: (value) => { selectedTeamEditId = value; },
|
||||
getSelectedSessionEditId: () => selectedSessionEditId,
|
||||
setSelectedSessionEditId: (value) => { selectedSessionEditId = value; },
|
||||
getSelectedGridSessionId: () => selectedGridSessionId,
|
||||
setSelectedGridSessionId: (value) => { selectedGridSessionId = value; },
|
||||
getRaceFormatAdvanced: () => raceFormatAdvanced,
|
||||
setRaceFormatAdvanced: (value) => { raceFormatAdvanced = value; },
|
||||
getTeamDriverPool,
|
||||
getEventTeams,
|
||||
normalizeBrandingConfig,
|
||||
getSessionTypeChoices,
|
||||
getRaceFormatPresets,
|
||||
getRaceSummaryItems,
|
||||
getRaceSummaryWarnings,
|
||||
getRaceManageStatuses,
|
||||
normalizeStartMode,
|
||||
renderEventManagerMarkup,
|
||||
getSessionEntrants,
|
||||
getSessionTypeLabel,
|
||||
getStartModeLabel,
|
||||
getStatusLabel,
|
||||
normalizeStartMode,
|
||||
branding,
|
||||
driverOptions,
|
||||
carOptions,
|
||||
manageStatuses,
|
||||
renderManageStatusBadgeView,
|
||||
selectedParticipantCount,
|
||||
raceDrivers,
|
||||
teamDriverPool,
|
||||
state,
|
||||
getDriverDisplayById,
|
||||
raceFormatAdvanced,
|
||||
racePresets,
|
||||
selectedPreset,
|
||||
isEndurancePreset,
|
||||
showBasicQualifyingFields,
|
||||
showBasicFinalFields,
|
||||
renderRaceFormatContextCardView,
|
||||
renderRaceFormatFieldView,
|
||||
raceSummaryWarnings,
|
||||
raceSummaryItems,
|
||||
selectedGridSession,
|
||||
renderGridEditor,
|
||||
renderRaceStandingsTableView,
|
||||
buildPracticeStandings,
|
||||
@@ -2944,653 +2893,49 @@ function renderEventManager(eventId) {
|
||||
buildFinalStandings,
|
||||
renderTeamRaceStandings,
|
||||
renderFinalMatrix,
|
||||
editingTeam,
|
||||
editingSession,
|
||||
getModeLabel,
|
||||
renderTable,
|
||||
bindModalShell,
|
||||
setFormError,
|
||||
saveState,
|
||||
renderView,
|
||||
rerenderEventManager: renderEventManager,
|
||||
updateHeaderState,
|
||||
uid,
|
||||
normalizeSession,
|
||||
buildSessionHeatSheetHtml,
|
||||
openPrintWindow,
|
||||
exportSessionHeatSheet,
|
||||
exportSessionHeatSheetPdf,
|
||||
getSelectedAssignmentSessionId,
|
||||
autoAssignTrackSession,
|
||||
renderAssignmentList,
|
||||
normalizeRaceTeam,
|
||||
buildRaceFormatConfigFromForm,
|
||||
applyRaceFormatPreset,
|
||||
normalizeStoredRacePreset,
|
||||
generateQualifyingForRace,
|
||||
clearGeneratedQualifying,
|
||||
reseedUpcomingQualifying,
|
||||
generateFinalsForRace,
|
||||
clearGeneratedFinals,
|
||||
applyBumpsForRace,
|
||||
buildRacePackagePayload,
|
||||
downloadJsonFile,
|
||||
sanitizeFilenameSegment,
|
||||
importRacePackagePayload,
|
||||
buildRaceStartListsHtml,
|
||||
buildRaceResultsHtml,
|
||||
buildTeamRaceResultsHtml,
|
||||
exportRaceStartListsPdf,
|
||||
exportRaceResultsPdf,
|
||||
exportTeamRaceResultsPdf,
|
||||
ensureSessionDriverOrder,
|
||||
reorderList,
|
||||
getEventName,
|
||||
});
|
||||
|
||||
const bindManageJump = (node) => {
|
||||
const triggerJump = () => {
|
||||
const targetId = node.getAttribute("data-target") || "";
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
};
|
||||
node.addEventListener("click", triggerJump);
|
||||
node.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
triggerJump();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
eventManageArea.querySelectorAll(".summary-warning-link, .manage-step-card-link").forEach((node) => {
|
||||
bindManageJump(node);
|
||||
});
|
||||
|
||||
document.getElementById("eventBrandingForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
brandName: String(form.get("brandName") || "").trim(),
|
||||
brandTagline: String(form.get("brandTagline") || "").trim(),
|
||||
pdfFooter: String(form.get("pdfFooter") || "").trim(),
|
||||
pdfTheme: String(form.get("pdfTheme") || "").trim(),
|
||||
});
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("eventLogoUpload")?.addEventListener("change", (eventInput) => {
|
||||
const input = eventInput.currentTarget;
|
||||
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
logoDataUrl: typeof reader.result === "string" ? reader.result : "",
|
||||
});
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
document.getElementById("eventLogoClear")?.addEventListener("click", () => {
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
logoDataUrl: "",
|
||||
});
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
state.sessions.push(normalizeSession({
|
||||
id: uid("session"),
|
||||
eventId,
|
||||
name: String(form.get("name")).trim(),
|
||||
type: String(form.get("type")),
|
||||
durationMin: Number(form.get("durationMin")),
|
||||
followUpSec: Math.max(0, Number(form.get("followUpSec") || 0) || 0),
|
||||
startMode: String(form.get("startMode") || "mass"),
|
||||
seedBestLapCount: Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0),
|
||||
seedMethod: String(form.get("seedMethod") || "best_sum"),
|
||||
staggerGapSec: Math.max(0, Number(form.get("staggerGapSec") || 0) || 0),
|
||||
maxCars: Number(form.get("maxCars") || 0) || null,
|
||||
mode: event.mode,
|
||||
status: "ready",
|
||||
startedAt: null,
|
||||
endedAt: null,
|
||||
finishedByTimer: false,
|
||||
assignments: [],
|
||||
}));
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
updateHeaderState();
|
||||
});
|
||||
|
||||
sessions.forEach((s) => {
|
||||
document.getElementById(`session-edit-${s.id}`)?.addEventListener("click", () => {
|
||||
selectedSessionEditId = s.id;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`session-active-${s.id}`)?.addEventListener("click", () => {
|
||||
state.activeSessionId = s.id;
|
||||
saveState();
|
||||
updateHeaderState();
|
||||
renderView();
|
||||
});
|
||||
|
||||
document.getElementById(`session-delete-${s.id}`)?.addEventListener("click", () => {
|
||||
state.sessions = state.sessions.filter((x) => x.id !== s.id);
|
||||
delete state.resultsBySession[s.id];
|
||||
if (state.activeSessionId === s.id) {
|
||||
state.activeSessionId = null;
|
||||
}
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
updateHeaderState();
|
||||
});
|
||||
|
||||
document.getElementById(`session-grid-${s.id}`)?.addEventListener("click", () => {
|
||||
ensureSessionDriverOrder(s);
|
||||
selectedGridSessionId = s.id;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-print-${s.id}`)?.addEventListener("click", () => {
|
||||
openPrintWindow(`${getEventName(eventId)} - ${s.name}`, buildSessionHeatSheetHtml(s));
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-export-${s.id}`)?.addEventListener("click", () => {
|
||||
exportSessionHeatSheet(s);
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-pdf-${s.id}`)?.addEventListener("click", async () => {
|
||||
await exportSessionHeatSheetPdf(s);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditCancel")?.addEventListener("click", () => {
|
||||
selectedSessionEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditCancelFooter")?.addEventListener("click", () => {
|
||||
selectedSessionEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditModalOverlay")?.addEventListener("click", (event) => {
|
||||
if (event.target?.id === "sessionEditModalOverlay") {
|
||||
selectedSessionEditId = null;
|
||||
renderEventManager(eventId);
|
||||
}
|
||||
});
|
||||
|
||||
bindModalShell("sessionEditModalOverlay", () => {
|
||||
selectedSessionEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
if (!editingSession) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(event.currentTarget);
|
||||
const cleanedName = String(form.get("name") || "").trim();
|
||||
const cleanedDuration = Number(form.get("durationMin") || editingSession.durationMin || 5) || 0;
|
||||
if (!cleanedName) {
|
||||
setFormError("sessionEditError", t("validation.required_name"));
|
||||
return;
|
||||
}
|
||||
if (cleanedDuration < 1) {
|
||||
setFormError("sessionEditError", t("validation.required_duration"));
|
||||
return;
|
||||
}
|
||||
setFormError("sessionEditError", "");
|
||||
editingSession.name = cleanedName;
|
||||
editingSession.type = String(form.get("type") || editingSession.type);
|
||||
editingSession.durationMin = Math.max(1, cleanedDuration);
|
||||
editingSession.followUpSec = Math.max(0, Number(form.get("followUpSec") || 0) || 0);
|
||||
editingSession.startMode = normalizeStartMode(String(form.get("startMode") || editingSession.startMode || "mass"));
|
||||
editingSession.seedBestLapCount = Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0);
|
||||
editingSession.seedMethod = ["best_sum", "average", "consecutive"].includes(String(form.get("seedMethod") || "").toLowerCase())
|
||||
? String(form.get("seedMethod")).toLowerCase()
|
||||
: "best_sum";
|
||||
editingSession.staggerGapSec = Math.max(0, Number(form.get("staggerGapSec") || 0) || 0);
|
||||
editingSession.maxCars = Number(form.get("maxCars") || 0) || null;
|
||||
selectedSessionEditId = null;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
if (event.mode === "track") {
|
||||
document.getElementById("sponsorRoundsForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const qualificationRounds = Number(form.get("qualificationRounds") || 0);
|
||||
const heatRounds = Number(form.get("heatRounds") || 0);
|
||||
const finalRounds = Number(form.get("finalRounds") || 0);
|
||||
const durationMin = Number(form.get("roundDuration") || 5);
|
||||
|
||||
createSponsorRounds(eventId, {
|
||||
qualificationRounds,
|
||||
heatRounds,
|
||||
finalRounds,
|
||||
durationMin,
|
||||
});
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("assignForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const sessionId = String(form.get("sessionId"));
|
||||
const session = state.sessions.find((x) => x.id === sessionId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const driverId = String(form.get("driverId"));
|
||||
const carId = String(form.get("carId"));
|
||||
const car = state.cars.find((x) => x.id === carId);
|
||||
if (!car) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateCar = (session.assignments || []).find((a) => a.carId === carId);
|
||||
if (duplicateCar) {
|
||||
alert(t("events.duplicate_car"));
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateDriver = (session.assignments || []).find((a) => a.driverId === driverId);
|
||||
if (duplicateDriver) {
|
||||
alert(t("events.duplicate_driver"));
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateTp = (session.assignments || []).find((a) => {
|
||||
const existingCar = state.cars.find((x) => x.id === a.carId);
|
||||
return existingCar?.transponder && existingCar.transponder === car.transponder;
|
||||
});
|
||||
if (duplicateTp) {
|
||||
alert(t("events.duplicate_tp"));
|
||||
return;
|
||||
}
|
||||
|
||||
session.assignments = session.assignments || [];
|
||||
session.assignments.push({ id: uid("as"), driverId, carId });
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("autoAssignSession")?.addEventListener("click", () => {
|
||||
const sessionId = getSelectedAssignmentSessionId();
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
autoAssignTrackSession(event, sessionId);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("clearAssignSession")?.addEventListener("click", () => {
|
||||
const sessionId = getSelectedAssignmentSessionId();
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
const session = state.sessions.find((x) => x.id === sessionId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
session.assignments = [];
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
renderAssignmentList(eventId);
|
||||
}
|
||||
|
||||
if (event.mode === "race") {
|
||||
const persistRaceParticipants = () => {
|
||||
const selectedIds = Array.from(document.querySelectorAll(".race-participant:checked")).map((node) => node.value);
|
||||
event.raceConfig.driverIds = selectedIds;
|
||||
event.raceConfig.participantsConfigured = true;
|
||||
saveState();
|
||||
};
|
||||
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.addEventListener("change", persistRaceParticipants);
|
||||
});
|
||||
|
||||
document.getElementById("selectAllParticipants")?.addEventListener("click", () => {
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.checked = true;
|
||||
});
|
||||
persistRaceParticipants();
|
||||
});
|
||||
|
||||
document.getElementById("clearParticipants")?.addEventListener("click", () => {
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.checked = false;
|
||||
});
|
||||
persistRaceParticipants();
|
||||
});
|
||||
|
||||
document.getElementById("teamForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const name = String(form.get("teamName") || "").trim();
|
||||
const driverIds = form.getAll("teamDriverIds").map(String).filter(Boolean);
|
||||
const carIds = form.getAll("teamCarIds").map(String).filter(Boolean);
|
||||
if (!name || (!driverIds.length && !carIds.length)) {
|
||||
return;
|
||||
}
|
||||
const createdTeam = normalizeRaceTeam({ id: uid("team"), name, driverIds, carIds });
|
||||
event.raceConfig.teams = [...getEventTeams(event), createdTeam];
|
||||
selectedTeamEditId = createdTeam.id;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
raceTeams.forEach((team) => {
|
||||
document.getElementById(`team-edit-${team.id}`)?.addEventListener("click", () => {
|
||||
selectedTeamEditId = team.id;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`team-delete-${team.id}`)?.addEventListener("click", () => {
|
||||
event.raceConfig.teams = getEventTeams(event).filter((item) => item.id !== team.id);
|
||||
if (selectedTeamEditId === team.id) {
|
||||
selectedTeamEditId = null;
|
||||
}
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("teamEditCancel")?.addEventListener("click", () => {
|
||||
selectedTeamEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditCancelFooter")?.addEventListener("click", () => {
|
||||
selectedTeamEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditModalOverlay")?.addEventListener("click", (modalEvent) => {
|
||||
if (modalEvent.target?.id === "teamEditModalOverlay") {
|
||||
selectedTeamEditId = null;
|
||||
renderEventManager(eventId);
|
||||
}
|
||||
});
|
||||
|
||||
bindModalShell("teamEditModalOverlay", () => {
|
||||
selectedTeamEditId = null;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditForm")?.addEventListener("submit", (submitEvent) => {
|
||||
submitEvent.preventDefault();
|
||||
if (!editingTeam) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(submitEvent.currentTarget);
|
||||
const name = String(form.get("teamName") || "").trim();
|
||||
const driverIds = form.getAll("teamDriverIds").map(String).filter(Boolean);
|
||||
const carIds = form.getAll("teamCarIds").map(String).filter(Boolean);
|
||||
if (!name) {
|
||||
setFormError("teamEditError", t("validation.required_name"));
|
||||
return;
|
||||
}
|
||||
if (!driverIds.length && !carIds.length) {
|
||||
setFormError("teamEditError", t("validation.invalid_selection"));
|
||||
return;
|
||||
}
|
||||
setFormError("teamEditError", "");
|
||||
event.raceConfig.teams = getEventTeams(event).map((team) =>
|
||||
team.id === editingTeam.id ? normalizeRaceTeam({ ...team, name, driverIds, carIds }) : team
|
||||
);
|
||||
selectedTeamEditId = null;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatBasicToggle")?.addEventListener("click", () => {
|
||||
raceFormatAdvanced = false;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatAdvancedToggle")?.addEventListener("click", () => {
|
||||
raceFormatAdvanced = true;
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
event.raceConfig = buildRaceFormatConfigFromForm(form, event);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("applyRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
applyRaceFormatPreset(event, String(form.get("presetId") || "custom"));
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("saveRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
const presetName = String(form.get("presetName") || "").trim();
|
||||
if (!presetName) {
|
||||
return;
|
||||
}
|
||||
const config = buildRaceFormatConfigFromForm(form, event);
|
||||
const selectedPresetId = String(form.get("presetId") || "custom");
|
||||
const existingCustomPreset = (state.settings.racePresets || []).find((preset) => preset.id === selectedPresetId);
|
||||
const presetId = existingCustomPreset ? existingCustomPreset.id : uid("preset");
|
||||
const storedPreset = normalizeStoredRacePreset({
|
||||
id: presetId,
|
||||
name: presetName,
|
||||
values: {
|
||||
qualifyingScoring: config.qualifyingScoring,
|
||||
qualifyingRounds: config.qualifyingRounds,
|
||||
carsPerHeat: config.carsPerHeat,
|
||||
qualDurationMin: config.qualDurationMin,
|
||||
qualStartMode: config.qualStartMode,
|
||||
qualSeedLapCount: config.qualSeedLapCount,
|
||||
qualSeedMethod: config.qualSeedMethod,
|
||||
countedQualRounds: config.countedQualRounds,
|
||||
qualifyingPointsTable: config.qualifyingPointsTable,
|
||||
qualifyingTieBreak: config.qualifyingTieBreak,
|
||||
carsPerFinal: config.carsPerFinal,
|
||||
finalLegs: config.finalLegs,
|
||||
countedFinalLegs: config.countedFinalLegs,
|
||||
finalDurationMin: config.finalDurationMin,
|
||||
finalStartMode: config.finalStartMode,
|
||||
followUpSec: config.followUpSec,
|
||||
minLapMs: config.minLapMs,
|
||||
maxLapMs: config.maxLapMs,
|
||||
bumpCount: config.bumpCount,
|
||||
reserveBumpSlots: config.reserveBumpSlots,
|
||||
finalsSource: config.finalsSource,
|
||||
},
|
||||
});
|
||||
const otherPresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
|
||||
state.settings.racePresets = [...otherPresets, storedPreset];
|
||||
event.raceConfig = { ...config, presetId };
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("deleteRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
const presetId = String(form.get("presetId") || "custom");
|
||||
if (!(state.settings.racePresets || []).some((preset) => preset.id === presetId)) {
|
||||
return;
|
||||
}
|
||||
state.settings.racePresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
|
||||
event.raceConfig.presetId = "custom";
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("generateQualifying")?.addEventListener("click", () => {
|
||||
const created = generateQualifyingForRace(event);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
if (created > 0) {
|
||||
alert(t("events.generated_qualifying"));
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("clearGeneratedQualifying")?.addEventListener("click", () => {
|
||||
clearGeneratedQualifying(event.id);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("reseedQualifying")?.addEventListener("click", () => {
|
||||
const result = reseedUpcomingQualifying(event);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
const messages = [];
|
||||
if (result.updated > 0) {
|
||||
messages.push(t("events.reseed_done"));
|
||||
} else {
|
||||
messages.push(t("events.no_reseed_done"));
|
||||
}
|
||||
if (result.locked > 0) {
|
||||
messages.push(t("events.reseed_locked", { count: result.locked }));
|
||||
}
|
||||
alert(messages.join("\n"));
|
||||
});
|
||||
|
||||
document.getElementById("generateFinals")?.addEventListener("click", () => {
|
||||
const created = generateFinalsForRace(event);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
if (created > 0) {
|
||||
alert(t("events.finals_generated"));
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("clearGeneratedFinals")?.addEventListener("click", () => {
|
||||
clearGeneratedFinals(event.id);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("applyBumps")?.addEventListener("click", () => {
|
||||
const applied = applyBumpsForRace(event);
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
alert(t(applied > 0 ? "events.bumps_applied" : "events.no_bumps_applied"));
|
||||
});
|
||||
|
||||
document.getElementById("exportRacePackage")?.addEventListener("click", () => {
|
||||
const payload = buildRacePackagePayload(eventId);
|
||||
downloadJsonFile(`${sanitizeFilenameSegment(event.name)}_race_package.json`, payload);
|
||||
});
|
||||
|
||||
document.getElementById("importRacePackage")?.addEventListener("change", (importEvent) => {
|
||||
const input = importEvent.currentTarget;
|
||||
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(String(reader.result || "{}"));
|
||||
importRacePackagePayload(parsed);
|
||||
} catch (error) {
|
||||
alert(t("settings.import_failed", { msg: error instanceof Error ? error.message : String(error) }));
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
document.getElementById("printStartlists")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.start_lists")}`, buildRaceStartListsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("printResults")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.results_overview")}`, buildRaceResultsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("printTeamResults")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.team_report")}`, buildTeamRaceResultsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("pdfStartlists")?.addEventListener("click", async () => {
|
||||
await exportRaceStartListsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("pdfResults")?.addEventListener("click", async () => {
|
||||
await exportRaceResultsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("pdfTeamResults")?.addEventListener("click", async () => {
|
||||
await exportTeamRaceResultsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("gridResetOrder")?.addEventListener("click", () => {
|
||||
if (!selectedGridSession) {
|
||||
return;
|
||||
}
|
||||
selectedGridSession.driverIds = getSessionEntrants(selectedGridSession)
|
||||
.map((driver) => driver.id)
|
||||
.filter(Boolean);
|
||||
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
|
||||
selectedGridSession.gridCustomized = false;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("gridToggleLock")?.addEventListener("click", () => {
|
||||
if (!selectedGridSession) {
|
||||
return;
|
||||
}
|
||||
if (!selectedGridSession.gridCustomized) {
|
||||
selectedGridSession.manualGridIds = [...ensureSessionDriverOrder(selectedGridSession)];
|
||||
selectedGridSession.gridCustomized = true;
|
||||
} else {
|
||||
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
|
||||
selectedGridSession.gridCustomized = false;
|
||||
}
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
|
||||
let dragIndex = null;
|
||||
document.querySelectorAll("#gridDragList .drag-item").forEach((node) => {
|
||||
node.addEventListener("dragstart", () => {
|
||||
dragIndex = Number(node.dataset.index);
|
||||
node.classList.add("drag-item-active");
|
||||
});
|
||||
node.addEventListener("dragend", () => {
|
||||
dragIndex = null;
|
||||
node.classList.remove("drag-item-active");
|
||||
});
|
||||
node.addEventListener("dragover", (dragEvent) => {
|
||||
dragEvent.preventDefault();
|
||||
node.classList.add("drag-item-over");
|
||||
});
|
||||
node.addEventListener("dragleave", () => {
|
||||
node.classList.remove("drag-item-over");
|
||||
});
|
||||
node.addEventListener("drop", (dropEvent) => {
|
||||
dropEvent.preventDefault();
|
||||
node.classList.remove("drag-item-over");
|
||||
if (!selectedGridSession || dragIndex === null) {
|
||||
return;
|
||||
}
|
||||
const dropIndex = Number(node.dataset.index);
|
||||
if (Number.isNaN(dropIndex) || dropIndex === dragIndex) {
|
||||
return;
|
||||
}
|
||||
selectedGridSession.manualGridIds = reorderList(ensureSessionDriverOrder(selectedGridSession), dragIndex, dropIndex);
|
||||
selectedGridSession.gridCustomized = true;
|
||||
saveState();
|
||||
renderEventManager(eventId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getFreePracticeSessions(eventId) {
|
||||
return getSessionsForEvent(eventId).filter((session) => session.type === "free_practice");
|
||||
}
|
||||
|
||||
828
src/event_manager_controller.js
Normal file
828
src/event_manager_controller.js
Normal file
@@ -0,0 +1,828 @@
|
||||
export function renderEventManagerView(context) {
|
||||
const {
|
||||
eventId,
|
||||
state,
|
||||
t,
|
||||
escapeHtml,
|
||||
normalizeEvent,
|
||||
ensureRaceParticipantsConfigured,
|
||||
getSessionsForEvent,
|
||||
getSelectedTeamEditId,
|
||||
setSelectedTeamEditId,
|
||||
getSelectedSessionEditId,
|
||||
setSelectedSessionEditId,
|
||||
getSelectedGridSessionId,
|
||||
setSelectedGridSessionId,
|
||||
getRaceFormatAdvanced,
|
||||
setRaceFormatAdvanced,
|
||||
getTeamDriverPool,
|
||||
getEventTeams,
|
||||
normalizeBrandingConfig,
|
||||
getSessionTypeChoices,
|
||||
getRaceFormatPresets,
|
||||
getRaceSummaryItems,
|
||||
getRaceSummaryWarnings,
|
||||
getRaceManageStatuses,
|
||||
normalizeStartMode,
|
||||
renderEventManagerMarkup,
|
||||
getSessionEntrants,
|
||||
getSessionTypeLabel,
|
||||
getStartModeLabel,
|
||||
getStatusLabel,
|
||||
renderManageStatusBadgeView,
|
||||
getDriverDisplayById,
|
||||
renderRaceFormatContextCardView,
|
||||
renderRaceFormatFieldView,
|
||||
renderGridEditor,
|
||||
renderRaceStandingsTableView,
|
||||
buildPracticeStandings,
|
||||
buildQualifyingStandings,
|
||||
buildFinalStandings,
|
||||
renderTeamRaceStandings,
|
||||
renderFinalMatrix,
|
||||
getModeLabel,
|
||||
renderTable,
|
||||
bindModalShell,
|
||||
setFormError,
|
||||
saveState,
|
||||
renderView,
|
||||
rerenderEventManager,
|
||||
updateHeaderState,
|
||||
uid,
|
||||
normalizeSession,
|
||||
buildSessionHeatSheetHtml,
|
||||
openPrintWindow,
|
||||
exportSessionHeatSheet,
|
||||
exportSessionHeatSheetPdf,
|
||||
getSelectedAssignmentSessionId,
|
||||
autoAssignTrackSession,
|
||||
renderAssignmentList,
|
||||
normalizeRaceTeam,
|
||||
buildRaceFormatConfigFromForm,
|
||||
applyRaceFormatPreset,
|
||||
normalizeStoredRacePreset,
|
||||
generateQualifyingForRace,
|
||||
clearGeneratedQualifying,
|
||||
reseedUpcomingQualifying,
|
||||
generateFinalsForRace,
|
||||
clearGeneratedFinals,
|
||||
applyBumpsForRace,
|
||||
buildRacePackagePayload,
|
||||
downloadJsonFile,
|
||||
sanitizeFilenameSegment,
|
||||
importRacePackagePayload,
|
||||
buildRaceStartListsHtml,
|
||||
buildRaceResultsHtml,
|
||||
buildTeamRaceResultsHtml,
|
||||
exportRaceStartListsPdf,
|
||||
exportRaceResultsPdf,
|
||||
exportTeamRaceResultsPdf,
|
||||
ensureSessionDriverOrder,
|
||||
reorderList,
|
||||
getEventName,
|
||||
} = context;
|
||||
|
||||
const selectedTeamEditId = getSelectedTeamEditId();
|
||||
const selectedSessionEditId = getSelectedSessionEditId();
|
||||
const selectedGridSessionId = getSelectedGridSessionId();
|
||||
const raceFormatAdvanced = getRaceFormatAdvanced();
|
||||
|
||||
const event = state.events.find((e) => e.id === eventId);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
const normalizedEvent = normalizeEvent(event);
|
||||
if (normalizedEvent !== event) {
|
||||
Object.assign(event, normalizedEvent);
|
||||
}
|
||||
ensureRaceParticipantsConfigured(event);
|
||||
|
||||
const sessions = getSessionsForEvent(eventId);
|
||||
const eventManageArea = document.getElementById("eventManageArea");
|
||||
if (!eventManageArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const driverOptions = state.drivers
|
||||
.map((d) => `<option value="${d.id}">${escapeHtml(d.name)}</option>`)
|
||||
.join("");
|
||||
const teamDriverPool = event.mode === "race" ? getTeamDriverPool(event) : { drivers: [], fallback: false };
|
||||
const raceDrivers = event.mode === "race" ? teamDriverPool.drivers : [];
|
||||
const raceTeams = event.mode === "race" ? getEventTeams(event) : [];
|
||||
if (selectedTeamEditId && !raceTeams.some((team) => team.id === selectedTeamEditId)) {
|
||||
setSelectedTeamEditId(null);
|
||||
}
|
||||
const editingTeam = event.mode === "race" ? raceTeams.find((team) => team.id === selectedTeamEditId) || null : null;
|
||||
const carOptions = state.cars
|
||||
.map((c) => `<option value="${c.id}">${escapeHtml(c.name)} (${escapeHtml(c.transponder)})</option>`)
|
||||
.join("");
|
||||
const branding = normalizeBrandingConfig(event.branding);
|
||||
const editingSession = sessions.find((session) => session.id === selectedSessionEditId) || null;
|
||||
const sessionTypeChoices = getSessionTypeChoices(event.mode);
|
||||
const sessionTypeHintKey = event.mode === "track" ? "events.session_type_hint_track" : "events.session_type_hint_race";
|
||||
const racePresets = getRaceFormatPresets();
|
||||
const selectedPreset = racePresets.find((preset) => preset.id === event.raceConfig.presetId) || racePresets[0];
|
||||
const isEndurancePreset = event.mode === "race" && selectedPreset?.id === "endurance";
|
||||
const showBasicQualifyingFields = raceFormatAdvanced || !isEndurancePreset;
|
||||
const showBasicFinalFields = raceFormatAdvanced || !isEndurancePreset;
|
||||
const selectedParticipantCount = event.mode === "race" ? (event.raceConfig.participantsConfigured ? (event.raceConfig.driverIds || []).length : raceDrivers.length) : 0;
|
||||
const raceSummaryItems = event.mode === "race" ? getRaceSummaryItems(event, sessions, raceDrivers, selectedPreset, { t, getStartModeLabel }) : [];
|
||||
const raceSummaryWarnings = event.mode === "race" ? getRaceSummaryWarnings(event, sessions, raceDrivers, raceTeams, selectedPreset, { t }) : [];
|
||||
const manageStatuses = event.mode === "race" ? getRaceManageStatuses(event, sessions, raceDrivers, raceTeams, selectedPreset) : null;
|
||||
const gridSessions = event.mode === "race" ? sessions.filter((session) => normalizeStartMode(session.startMode) === "position") : [];
|
||||
if (selectedGridSessionId && !gridSessions.some((session) => session.id === selectedGridSessionId)) {
|
||||
setSelectedGridSessionId("");
|
||||
}
|
||||
const selectedGridSession =
|
||||
gridSessions.find((session) => session.id === selectedGridSessionId) || gridSessions[0] || null;
|
||||
if (!selectedGridSessionId && selectedGridSession) {
|
||||
setSelectedGridSessionId(selectedGridSession.id);
|
||||
}
|
||||
|
||||
eventManageArea.innerHTML = renderEventManagerMarkup({
|
||||
event,
|
||||
t,
|
||||
escapeHtml,
|
||||
sessions,
|
||||
sessionTypeChoices,
|
||||
sessionTypeHintKey,
|
||||
raceTeams,
|
||||
getSessionEntrants,
|
||||
getSessionTypeLabel,
|
||||
getStartModeLabel,
|
||||
getStatusLabel,
|
||||
normalizeStartMode,
|
||||
branding,
|
||||
driverOptions,
|
||||
carOptions,
|
||||
manageStatuses,
|
||||
renderManageStatusBadgeView,
|
||||
selectedParticipantCount,
|
||||
raceDrivers,
|
||||
teamDriverPool,
|
||||
state,
|
||||
getDriverDisplayById,
|
||||
raceFormatAdvanced,
|
||||
racePresets,
|
||||
selectedPreset,
|
||||
isEndurancePreset,
|
||||
showBasicQualifyingFields,
|
||||
showBasicFinalFields,
|
||||
renderRaceFormatContextCardView,
|
||||
renderRaceFormatFieldView,
|
||||
raceSummaryWarnings,
|
||||
raceSummaryItems,
|
||||
selectedGridSession,
|
||||
renderGridEditor,
|
||||
renderRaceStandingsTableView,
|
||||
buildPracticeStandings,
|
||||
buildQualifyingStandings,
|
||||
buildFinalStandings,
|
||||
renderTeamRaceStandings,
|
||||
renderFinalMatrix,
|
||||
editingTeam,
|
||||
editingSession,
|
||||
getModeLabel,
|
||||
renderTable,
|
||||
});
|
||||
|
||||
const bindManageJump = (node) => {
|
||||
const triggerJump = () => {
|
||||
const targetId = node.getAttribute("data-target") || "";
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
};
|
||||
node.addEventListener("click", triggerJump);
|
||||
node.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
triggerJump();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
eventManageArea.querySelectorAll(".summary-warning-link, .manage-step-card-link").forEach((node) => {
|
||||
bindManageJump(node);
|
||||
});
|
||||
|
||||
document.getElementById("eventBrandingForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
brandName: String(form.get("brandName") || "").trim(),
|
||||
brandTagline: String(form.get("brandTagline") || "").trim(),
|
||||
pdfFooter: String(form.get("pdfFooter") || "").trim(),
|
||||
pdfTheme: String(form.get("pdfTheme") || "").trim(),
|
||||
});
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("eventLogoUpload")?.addEventListener("change", (eventInput) => {
|
||||
const input = eventInput.currentTarget;
|
||||
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
logoDataUrl: typeof reader.result === "string" ? reader.result : "",
|
||||
});
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
document.getElementById("eventLogoClear")?.addEventListener("click", () => {
|
||||
event.branding = normalizeBrandingConfig({
|
||||
...event.branding,
|
||||
logoDataUrl: "",
|
||||
});
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
state.sessions.push(normalizeSession({
|
||||
id: uid("session"),
|
||||
eventId,
|
||||
name: String(form.get("name")).trim(),
|
||||
type: String(form.get("type")),
|
||||
durationMin: Number(form.get("durationMin")),
|
||||
followUpSec: Math.max(0, Number(form.get("followUpSec") || 0) || 0),
|
||||
startMode: String(form.get("startMode") || "mass"),
|
||||
seedBestLapCount: Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0),
|
||||
seedMethod: String(form.get("seedMethod") || "best_sum"),
|
||||
staggerGapSec: Math.max(0, Number(form.get("staggerGapSec") || 0) || 0),
|
||||
maxCars: Number(form.get("maxCars") || 0) || null,
|
||||
mode: event.mode,
|
||||
status: "ready",
|
||||
startedAt: null,
|
||||
endedAt: null,
|
||||
finishedByTimer: false,
|
||||
assignments: [],
|
||||
}));
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
updateHeaderState();
|
||||
});
|
||||
|
||||
sessions.forEach((s) => {
|
||||
document.getElementById(`session-edit-${s.id}`)?.addEventListener("click", () => {
|
||||
setSelectedSessionEditId(s.id);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`session-active-${s.id}`)?.addEventListener("click", () => {
|
||||
state.activeSessionId = s.id;
|
||||
saveState();
|
||||
updateHeaderState();
|
||||
renderView();
|
||||
});
|
||||
|
||||
document.getElementById(`session-delete-${s.id}`)?.addEventListener("click", () => {
|
||||
state.sessions = state.sessions.filter((x) => x.id !== s.id);
|
||||
delete state.resultsBySession[s.id];
|
||||
if (state.activeSessionId === s.id) {
|
||||
state.activeSessionId = null;
|
||||
}
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
updateHeaderState();
|
||||
});
|
||||
|
||||
document.getElementById(`session-grid-${s.id}`)?.addEventListener("click", () => {
|
||||
ensureSessionDriverOrder(s);
|
||||
setSelectedGridSessionId(s.id);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-print-${s.id}`)?.addEventListener("click", () => {
|
||||
openPrintWindow(`${getEventName(eventId)} - ${s.name}`, buildSessionHeatSheetHtml(s));
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-export-${s.id}`)?.addEventListener("click", () => {
|
||||
exportSessionHeatSheet(s);
|
||||
});
|
||||
|
||||
document.getElementById(`session-sheet-pdf-${s.id}`)?.addEventListener("click", async () => {
|
||||
await exportSessionHeatSheetPdf(s);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditCancel")?.addEventListener("click", () => {
|
||||
setSelectedSessionEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditCancelFooter")?.addEventListener("click", () => {
|
||||
setSelectedSessionEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditModalOverlay")?.addEventListener("click", (event) => {
|
||||
if (event.target?.id === "sessionEditModalOverlay") {
|
||||
setSelectedSessionEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
}
|
||||
});
|
||||
|
||||
bindModalShell("sessionEditModalOverlay", () => {
|
||||
setSelectedSessionEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("sessionEditForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
if (!editingSession) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(event.currentTarget);
|
||||
const cleanedName = String(form.get("name") || "").trim();
|
||||
const cleanedDuration = Number(form.get("durationMin") || editingSession.durationMin || 5) || 0;
|
||||
if (!cleanedName) {
|
||||
setFormError("sessionEditError", t("validation.required_name"));
|
||||
return;
|
||||
}
|
||||
if (cleanedDuration < 1) {
|
||||
setFormError("sessionEditError", t("validation.required_duration"));
|
||||
return;
|
||||
}
|
||||
setFormError("sessionEditError", "");
|
||||
editingSession.name = cleanedName;
|
||||
editingSession.type = String(form.get("type") || editingSession.type);
|
||||
editingSession.durationMin = Math.max(1, cleanedDuration);
|
||||
editingSession.followUpSec = Math.max(0, Number(form.get("followUpSec") || 0) || 0);
|
||||
editingSession.startMode = normalizeStartMode(String(form.get("startMode") || editingSession.startMode || "mass"));
|
||||
editingSession.seedBestLapCount = Math.max(0, Number(form.get("seedBestLapCount") || 0) || 0);
|
||||
editingSession.seedMethod = ["best_sum", "average", "consecutive"].includes(String(form.get("seedMethod") || "").toLowerCase())
|
||||
? String(form.get("seedMethod")).toLowerCase()
|
||||
: "best_sum";
|
||||
editingSession.staggerGapSec = Math.max(0, Number(form.get("staggerGapSec") || 0) || 0);
|
||||
editingSession.maxCars = Number(form.get("maxCars") || 0) || null;
|
||||
setSelectedSessionEditId(null);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
if (event.mode === "track") {
|
||||
document.getElementById("sponsorRoundsForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const qualificationRounds = Number(form.get("qualificationRounds") || 0);
|
||||
const heatRounds = Number(form.get("heatRounds") || 0);
|
||||
const finalRounds = Number(form.get("finalRounds") || 0);
|
||||
const durationMin = Number(form.get("roundDuration") || 5);
|
||||
|
||||
createSponsorRounds(eventId, {
|
||||
qualificationRounds,
|
||||
heatRounds,
|
||||
finalRounds,
|
||||
durationMin,
|
||||
});
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("assignForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const sessionId = String(form.get("sessionId"));
|
||||
const session = state.sessions.find((x) => x.id === sessionId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const driverId = String(form.get("driverId"));
|
||||
const carId = String(form.get("carId"));
|
||||
const car = state.cars.find((x) => x.id === carId);
|
||||
if (!car) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateCar = (session.assignments || []).find((a) => a.carId === carId);
|
||||
if (duplicateCar) {
|
||||
alert(t("events.duplicate_car"));
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateDriver = (session.assignments || []).find((a) => a.driverId === driverId);
|
||||
if (duplicateDriver) {
|
||||
alert(t("events.duplicate_driver"));
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateTp = (session.assignments || []).find((a) => {
|
||||
const existingCar = state.cars.find((x) => x.id === a.carId);
|
||||
return existingCar?.transponder && existingCar.transponder === car.transponder;
|
||||
});
|
||||
if (duplicateTp) {
|
||||
alert(t("events.duplicate_tp"));
|
||||
return;
|
||||
}
|
||||
|
||||
session.assignments = session.assignments || [];
|
||||
session.assignments.push({ id: uid("as"), driverId, carId });
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("autoAssignSession")?.addEventListener("click", () => {
|
||||
const sessionId = getSelectedAssignmentSessionId();
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
autoAssignTrackSession(event, sessionId);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("clearAssignSession")?.addEventListener("click", () => {
|
||||
const sessionId = getSelectedAssignmentSessionId();
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
const session = state.sessions.find((x) => x.id === sessionId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
session.assignments = [];
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
renderAssignmentList(eventId);
|
||||
}
|
||||
|
||||
if (event.mode === "race") {
|
||||
const persistRaceParticipants = () => {
|
||||
const selectedIds = Array.from(document.querySelectorAll(".race-participant:checked")).map((node) => node.value);
|
||||
event.raceConfig.driverIds = selectedIds;
|
||||
event.raceConfig.participantsConfigured = true;
|
||||
saveState();
|
||||
};
|
||||
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.addEventListener("change", persistRaceParticipants);
|
||||
});
|
||||
|
||||
document.getElementById("selectAllParticipants")?.addEventListener("click", () => {
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.checked = true;
|
||||
});
|
||||
persistRaceParticipants();
|
||||
});
|
||||
|
||||
document.getElementById("clearParticipants")?.addEventListener("click", () => {
|
||||
document.querySelectorAll(".race-participant").forEach((node) => {
|
||||
node.checked = false;
|
||||
});
|
||||
persistRaceParticipants();
|
||||
});
|
||||
|
||||
document.getElementById("teamForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const name = String(form.get("teamName") || "").trim();
|
||||
const driverIds = form.getAll("teamDriverIds").map(String).filter(Boolean);
|
||||
const carIds = form.getAll("teamCarIds").map(String).filter(Boolean);
|
||||
if (!name || (!driverIds.length && !carIds.length)) {
|
||||
return;
|
||||
}
|
||||
const createdTeam = normalizeRaceTeam({ id: uid("team"), name, driverIds, carIds });
|
||||
event.raceConfig.teams = [...getEventTeams(event), createdTeam];
|
||||
setSelectedTeamEditId(createdTeam.id);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
raceTeams.forEach((team) => {
|
||||
document.getElementById(`team-edit-${team.id}`)?.addEventListener("click", () => {
|
||||
setSelectedTeamEditId(team.id);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById(`team-delete-${team.id}`)?.addEventListener("click", () => {
|
||||
event.raceConfig.teams = getEventTeams(event).filter((item) => item.id !== team.id);
|
||||
if (selectedTeamEditId === team.id) {
|
||||
setSelectedTeamEditId(null);
|
||||
}
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("teamEditCancel")?.addEventListener("click", () => {
|
||||
setSelectedTeamEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditCancelFooter")?.addEventListener("click", () => {
|
||||
setSelectedTeamEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditModalOverlay")?.addEventListener("click", (modalEvent) => {
|
||||
if (modalEvent.target?.id === "teamEditModalOverlay") {
|
||||
setSelectedTeamEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
}
|
||||
});
|
||||
|
||||
bindModalShell("teamEditModalOverlay", () => {
|
||||
setSelectedTeamEditId(null);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("teamEditForm")?.addEventListener("submit", (submitEvent) => {
|
||||
submitEvent.preventDefault();
|
||||
if (!editingTeam) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(submitEvent.currentTarget);
|
||||
const name = String(form.get("teamName") || "").trim();
|
||||
const driverIds = form.getAll("teamDriverIds").map(String).filter(Boolean);
|
||||
const carIds = form.getAll("teamCarIds").map(String).filter(Boolean);
|
||||
if (!name) {
|
||||
setFormError("teamEditError", t("validation.required_name"));
|
||||
return;
|
||||
}
|
||||
if (!driverIds.length && !carIds.length) {
|
||||
setFormError("teamEditError", t("validation.invalid_selection"));
|
||||
return;
|
||||
}
|
||||
setFormError("teamEditError", "");
|
||||
event.raceConfig.teams = getEventTeams(event).map((team) =>
|
||||
team.id === editingTeam.id ? normalizeRaceTeam({ ...team, name, driverIds, carIds }) : team
|
||||
);
|
||||
setSelectedTeamEditId(null);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatBasicToggle")?.addEventListener("click", () => {
|
||||
setRaceFormatAdvanced(false);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatAdvancedToggle")?.addEventListener("click", () => {
|
||||
setRaceFormatAdvanced(true);
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("raceFormatForm")?.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
event.raceConfig = buildRaceFormatConfigFromForm(form, event);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("applyRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
applyRaceFormatPreset(event, String(form.get("presetId") || "custom"));
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("saveRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
const presetName = String(form.get("presetName") || "").trim();
|
||||
if (!presetName) {
|
||||
return;
|
||||
}
|
||||
const config = buildRaceFormatConfigFromForm(form, event);
|
||||
const selectedPresetId = String(form.get("presetId") || "custom");
|
||||
const existingCustomPreset = (state.settings.racePresets || []).find((preset) => preset.id === selectedPresetId);
|
||||
const presetId = existingCustomPreset ? existingCustomPreset.id : uid("preset");
|
||||
const storedPreset = normalizeStoredRacePreset({
|
||||
id: presetId,
|
||||
name: presetName,
|
||||
values: {
|
||||
qualifyingScoring: config.qualifyingScoring,
|
||||
qualifyingRounds: config.qualifyingRounds,
|
||||
carsPerHeat: config.carsPerHeat,
|
||||
qualDurationMin: config.qualDurationMin,
|
||||
qualStartMode: config.qualStartMode,
|
||||
qualSeedLapCount: config.qualSeedLapCount,
|
||||
qualSeedMethod: config.qualSeedMethod,
|
||||
countedQualRounds: config.countedQualRounds,
|
||||
qualifyingPointsTable: config.qualifyingPointsTable,
|
||||
qualifyingTieBreak: config.qualifyingTieBreak,
|
||||
carsPerFinal: config.carsPerFinal,
|
||||
finalLegs: config.finalLegs,
|
||||
countedFinalLegs: config.countedFinalLegs,
|
||||
finalDurationMin: config.finalDurationMin,
|
||||
finalStartMode: config.finalStartMode,
|
||||
followUpSec: config.followUpSec,
|
||||
minLapMs: config.minLapMs,
|
||||
maxLapMs: config.maxLapMs,
|
||||
bumpCount: config.bumpCount,
|
||||
reserveBumpSlots: config.reserveBumpSlots,
|
||||
finalsSource: config.finalsSource,
|
||||
},
|
||||
});
|
||||
const otherPresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
|
||||
state.settings.racePresets = [...otherPresets, storedPreset];
|
||||
event.raceConfig = { ...config, presetId };
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("deleteRacePreset")?.addEventListener("click", () => {
|
||||
const formElement = document.getElementById("raceFormatForm");
|
||||
if (!(formElement instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formElement);
|
||||
const presetId = String(form.get("presetId") || "custom");
|
||||
if (!(state.settings.racePresets || []).some((preset) => preset.id === presetId)) {
|
||||
return;
|
||||
}
|
||||
state.settings.racePresets = (state.settings.racePresets || []).filter((preset) => preset.id !== presetId);
|
||||
event.raceConfig.presetId = "custom";
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("generateQualifying")?.addEventListener("click", () => {
|
||||
const created = generateQualifyingForRace(event);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
if (created > 0) {
|
||||
alert(t("events.generated_qualifying"));
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("clearGeneratedQualifying")?.addEventListener("click", () => {
|
||||
clearGeneratedQualifying(event.id);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("reseedQualifying")?.addEventListener("click", () => {
|
||||
const result = reseedUpcomingQualifying(event);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
const messages = [];
|
||||
if (result.updated > 0) {
|
||||
messages.push(t("events.reseed_done"));
|
||||
} else {
|
||||
messages.push(t("events.no_reseed_done"));
|
||||
}
|
||||
if (result.locked > 0) {
|
||||
messages.push(t("events.reseed_locked", { count: result.locked }));
|
||||
}
|
||||
alert(messages.join("\n"));
|
||||
});
|
||||
|
||||
document.getElementById("generateFinals")?.addEventListener("click", () => {
|
||||
const created = generateFinalsForRace(event);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
if (created > 0) {
|
||||
alert(t("events.finals_generated"));
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("clearGeneratedFinals")?.addEventListener("click", () => {
|
||||
clearGeneratedFinals(event.id);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("applyBumps")?.addEventListener("click", () => {
|
||||
const applied = applyBumpsForRace(event);
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
alert(t(applied > 0 ? "events.bumps_applied" : "events.no_bumps_applied"));
|
||||
});
|
||||
|
||||
document.getElementById("exportRacePackage")?.addEventListener("click", () => {
|
||||
const payload = buildRacePackagePayload(eventId);
|
||||
downloadJsonFile(`${sanitizeFilenameSegment(event.name)}_race_package.json`, payload);
|
||||
});
|
||||
|
||||
document.getElementById("importRacePackage")?.addEventListener("change", (importEvent) => {
|
||||
const input = importEvent.currentTarget;
|
||||
const file = input instanceof HTMLInputElement ? input.files?.[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(String(reader.result || "{}"));
|
||||
importRacePackagePayload(parsed);
|
||||
} catch (error) {
|
||||
alert(t("settings.import_failed", { msg: error instanceof Error ? error.message : String(error) }));
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
document.getElementById("printStartlists")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.start_lists")}`, buildRaceStartListsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("printResults")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.results_overview")}`, buildRaceResultsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("printTeamResults")?.addEventListener("click", () => {
|
||||
openPrintWindow(`${event.name} - ${t("events.team_report")}`, buildTeamRaceResultsHtml(event));
|
||||
});
|
||||
|
||||
document.getElementById("pdfStartlists")?.addEventListener("click", async () => {
|
||||
await exportRaceStartListsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("pdfResults")?.addEventListener("click", async () => {
|
||||
await exportRaceResultsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("pdfTeamResults")?.addEventListener("click", async () => {
|
||||
await exportTeamRaceResultsPdf(event);
|
||||
});
|
||||
|
||||
document.getElementById("gridResetOrder")?.addEventListener("click", () => {
|
||||
if (!selectedGridSession) {
|
||||
return;
|
||||
}
|
||||
selectedGridSession.driverIds = getSessionEntrants(selectedGridSession)
|
||||
.map((driver) => driver.id)
|
||||
.filter(Boolean);
|
||||
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
|
||||
selectedGridSession.gridCustomized = false;
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
document.getElementById("gridToggleLock")?.addEventListener("click", () => {
|
||||
if (!selectedGridSession) {
|
||||
return;
|
||||
}
|
||||
if (!selectedGridSession.gridCustomized) {
|
||||
selectedGridSession.manualGridIds = [...ensureSessionDriverOrder(selectedGridSession)];
|
||||
selectedGridSession.gridCustomized = true;
|
||||
} else {
|
||||
selectedGridSession.manualGridIds = [...selectedGridSession.driverIds];
|
||||
selectedGridSession.gridCustomized = false;
|
||||
}
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
|
||||
let dragIndex = null;
|
||||
document.querySelectorAll("#gridDragList .drag-item").forEach((node) => {
|
||||
node.addEventListener("dragstart", () => {
|
||||
dragIndex = Number(node.dataset.index);
|
||||
node.classList.add("drag-item-active");
|
||||
});
|
||||
node.addEventListener("dragend", () => {
|
||||
dragIndex = null;
|
||||
node.classList.remove("drag-item-active");
|
||||
});
|
||||
node.addEventListener("dragover", (dragEvent) => {
|
||||
dragEvent.preventDefault();
|
||||
node.classList.add("drag-item-over");
|
||||
});
|
||||
node.addEventListener("dragleave", () => {
|
||||
node.classList.remove("drag-item-over");
|
||||
});
|
||||
node.addEventListener("drop", (dropEvent) => {
|
||||
dropEvent.preventDefault();
|
||||
node.classList.remove("drag-item-over");
|
||||
if (!selectedGridSession || dragIndex === null) {
|
||||
return;
|
||||
}
|
||||
const dropIndex = Number(node.dataset.index);
|
||||
if (Number.isNaN(dropIndex) || dropIndex === dragIndex) {
|
||||
return;
|
||||
}
|
||||
selectedGridSession.manualGridIds = reorderList(ensureSessionDriverOrder(selectedGridSession), dragIndex, dropIndex);
|
||||
selectedGridSession.gridCustomized = true;
|
||||
saveState();
|
||||
rerenderEventManager(eventId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user