diff --git a/launch.html b/launch.html index b35402e..9bdde4c 100644 --- a/launch.html +++ b/launch.html @@ -120,6 +120,49 @@ color: #900; } + .form-group { + margin-bottom: 16px; + text-align: left; + } + + .form-group label { + display: block; + margin-bottom: 4px; + font-weight: 500; + color: #333; + } + + .form-group input, + .form-group textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + box-sizing: border-box; + } + + .form-group textarea { + min-height: 80px; + resize: vertical; + } + + .form-actions { + margin-top: 24px; + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; + } + + .success-message { + margin-top: 24px; + padding: 16px; + border-radius: 8px; + background: #f0f8ff; + color: #0066cc; + } + @keyframes spin { to { transform: rotate(360deg); @@ -157,6 +200,18 @@ border-color: #555; border-top-color: #ddd; } + + .form-group input, + .form-group textarea { + background: #303030; + border-color: #555; + color: #eee; + } + + .success-message { + background: #1a3a5a; + color: #99ccff; + } } @@ -184,6 +239,10 @@ + + @@ -288,6 +347,56 @@ parameter. + +
+ +
+ + + + @@ -308,8 +417,7 @@ /* * Your Jitsi Domain. */ - jitsiHost: "", - + jitsiHost: "meet.bnvb-ingenieure.de", /* * Timeout trying to start the jitsi-meet-electron client */ @@ -384,6 +492,25 @@ title: "No meeting specified", description: "Please specify a meeting using the", descriptionSuffix: "parameter." + }, + + creator: { + title: "Create New Meeting", + meetingNameLabel: "Meeting Name", + meetingDescriptionLabel: "Description", + meetingStartLabel: "Start Date & Time", + meetingEndLabel: "End Date & Time", + meetingOrganizerLabel: "Organizer Email", + createButton: "Create Meeting", + cancelButton: "Cancel", + successMessage: "Meeting created! Download your calendar file.", + downloadButton: "Download ICS File", + createNewButton: "Create New Meeting", + meetingNameRequired: "Please enter a meeting name", + startTimeRequired: "Please select a start time", + endTimeRequired: "Please select an end time", + endAfterStart: "End time must be after start time", + organizerRequired: "Please enter an organizer email" } }, @@ -443,6 +570,25 @@ description: "Bitte gib ein Meeting über den Parameter", descriptionSuffix: "an." + }, + + creator: { + title: "Neues Meeting erstellen", + meetingNameLabel: "Meeting-Name", + meetingDescriptionLabel: "Beschreibung", + meetingStartLabel: "Start Datum & Uhrzeit", + meetingEndLabel: "Ende Datum & Uhrzeit", + meetingOrganizerLabel: "Organisator E-Mail", + createButton: "Meeting erstellen", + cancelButton: "Abbrechen", + successMessage: "Meeting erstellt! Lade deine Kalenderdatei herunter.", + downloadButton: "ICS-Datei herunterladen", + createNewButton: "Neues Meeting erstellen", + meetingNameRequired: "Bitte gib einen Meeting-Namen ein", + startTimeRequired: "Bitte wähle eine Startzeit", + endTimeRequired: "Bitte wähle eine Endzeit", + endAfterStart: "Die Endzeit muss nach der Startzeit liegen", + organizerRequired: "Bitte gib eine Organisator-E-Mail ein" } }, @@ -501,6 +647,25 @@ title: "Aucune réunion spécifiée", description: "Veuillez spécifier une réunion à l'aide du paramètre", descriptionSuffix: "." + }, + + creator: { + title: "Créer une nouvelle réunion", + meetingNameLabel: "Nom de la réunion", + meetingDescriptionLabel: "Description", + meetingStartLabel: "Date et heure de début", + meetingEndLabel: "Date et heure de fin", + meetingOrganizerLabel: "Email de l'organisateur", + createButton: "Créer la réunion", + cancelButton: "Annuler", + successMessage: "Réunion créée ! Téléchargez votre fichier calendrier.", + downloadButton: "Télécharger le fichier ICS", + createNewButton: "Créer une nouvelle réunion", + meetingNameRequired: "Veuillez entrer un nom de réunion", + startTimeRequired: "Veuillez sélectionner une heure de début", + endTimeRequired: "Veuillez sélectionner une heure de fin", + endAfterStart: "L'heure de fin doit être après l'heure de début", + organizerRequired: "Veuillez entrer un email d'organisateur" } } }; @@ -597,6 +762,7 @@ const starting = document.getElementById("starting"); const fallback = document.getElementById("fallback"); const invalidRoom = document.getElementById("invalidRoom"); + const meetingCreator = document.getElementById("meetingCreator"); const roomName = document.getElementById("roomName"); const openButton = document.getElementById("openButton"); const retryButton = document.getElementById("retryButton"); @@ -606,6 +772,19 @@ const otherDownload = document.getElementById("otherDownload"); const windowsLink = document.getElementById("windowsLink"); const macosLink = document.getElementById("macosLink"); + const createNewMeetingButton = document.getElementById("createNewMeetingButton"); + const createNewFromLauncherButton = document.getElementById("createNewFromLauncherButton"); + + /* Meeting Creator DOM elements */ + const meetingNameInput = document.getElementById("meetingName"); + const meetingDescriptionInput = document.getElementById("meetingDescription"); + const meetingStartInput = document.getElementById("meetingStart"); + const meetingEndInput = document.getElementById("meetingEnd"); + const meetingOrganizerInput = document.getElementById("meetingOrganizer"); + const createMeetingButton = document.getElementById("createMeetingButton"); + const cancelCreateButton = document.getElementById("cancelCreateButton"); + const icsSuccess = document.getElementById("icsSuccess"); + const downloadIcsLink = document.getElementById("downloadIcsLink"); /* ============================================================ * GET ROOM FROM QUERY-PARAMETER @@ -947,6 +1126,161 @@ }, CONFIG.launchTimeout); } + /* ============================================================ + * ICS FILE GENERATION + * ============================================================ + */ + + function generateICS(meetingName, description, startTime, endTime, organizer) { + /* Generate a unique room name from the meeting name */ + const roomName = meetingName.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); + const meetingUrl = createBrowserUrl(roomName); + + /* Format dates for ICS */ + const formatICSDate = (date) => { + const pad = (num) => num.toString().padStart(2, '0'); + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + const seconds = pad(date.getSeconds()); + return `${year}${month}${day}T${hours}${minutes}${seconds}`; + }; + + const uid = `jitsi-${Date.now()}@${CONFIG.jitsiHost}`; + const dtStamp = formatICSDate(new Date()); + const dtStart = formatICSDate(startTime); + const dtEnd = formatICSDate(endTime); + + /* Build ICS content */ + const icsContent = `BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//BNVB//Jitsi Meeting//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VEVENT +UID:${uid} +DTSTAMP:${dtStamp} +DTSTART:${dtStart} +DTEND:${dtEnd} +SUMMARY:${meetingName.replace(/,/g, '\\,')} +DESCRIPTION:${description.replace(/,/g, '\\,').replace(/\n/g, '\\n')} +LOCATION:${meetingUrl} +ORGANIZER:MAILTO:${organizer} +STATUS:CONFIRMED +URL:${meetingUrl} +SEQUENCE:0 +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:Reminder +TRIGGER:-PT15M +END:VALARM +END:VEVENT +END:VCALENDAR`; + + return { + content: icsContent, + roomName: roomName, + url: meetingUrl + }; + } + + function downloadICS(content, filename) { + const blob = new Blob([content], { type: 'text/calendar;charset=utf-8' }); + const url = URL.createObjectURL(blob); + return url; + } + + function showMeetingCreator() { + /* Hide all other views */ + launcher.classList.add("hidden"); + starting.classList.add("hidden"); + fallback.classList.add("hidden"); + invalidRoom.classList.add("hidden"); + + /* Show meeting creator */ + meetingCreator.classList.remove("hidden"); + + /* Reset form */ + meetingNameInput.value = ''; + meetingDescriptionInput.value = ''; + meetingStartInput.value = ''; + meetingEndInput.value = ''; + meetingOrganizerInput.value = ''; + icsSuccess.classList.add("hidden"); + + /* Set default start and end times */ + const now = new Date(); + const inOneHour = new Date(now.getTime() + 60 * 60 * 1000); + + meetingStartInput.value = now.toISOString().slice(0, 16); + meetingEndInput.value = inOneHour.toISOString().slice(0, 16); + } + + function createMeeting() { + const meetingName = meetingNameInput.value.trim(); + const description = meetingDescriptionInput.value.trim(); + const startTime = new Date(meetingStartInput.value); + const endTime = new Date(meetingEndInput.value); + const organizer = meetingOrganizerInput.value.trim(); + + if (!meetingName) { + alert(getTranslation('creator.meetingNameRequired') || 'Please enter a meeting name'); + return; + } + + if (!meetingStartInput.value) { + alert(getTranslation('creator.startTimeRequired') || 'Please select a start time'); + return; + } + + if (!meetingEndInput.value) { + alert(getTranslation('creator.endTimeRequired') || 'Please select an end time'); + return; + } + + if (startTime >= endTime) { + alert(getTranslation('creator.endAfterStart') || 'End time must be after start time'); + return; + } + + if (!organizer) { + alert(getTranslation('creator.organizerRequired') || 'Please enter an organizer email'); + return; + } + + /* Generate ICS */ + const icsData = generateICS(meetingName, description || meetingName, startTime, endTime, organizer); + + /* Create download link */ + const blob = new Blob([icsData.content], { type: 'text/calendar;charset=utf-8' }); + const url = URL.createObjectURL(blob); + downloadIcsLink.href = url; + downloadIcsLink.download = `${icsData.roomName}.ics`; + + /* Show success message */ + icsSuccess.classList.remove("hidden"); + + /* Scroll to success message */ + icsSuccess.scrollIntoView({ behavior: 'smooth' }); + } + + function cancelCreateMeeting() { + meetingCreator.classList.add("hidden"); + + /* If there was a room parameter, show the launcher */ + const room = getRoom(); + if (isValidRoom(room)) { + roomName.textContent = room; + browserLink.href = createBrowserUrl(room); + launcher.classList.remove("hidden"); + } else { + invalidRoom.classList.remove("hidden"); + } + } + /* ============================================================ * INITIALISIERUNG * ============================================================ @@ -1009,6 +1343,27 @@ launchJitsi ); + /* Meeting Creator Events */ + createNewMeetingButton.addEventListener( + "click", + showMeetingCreator + ); + + createNewFromLauncherButton.addEventListener( + "click", + showMeetingCreator + ); + + createMeetingButton.addEventListener( + "click", + createMeeting + ); + + cancelCreateButton.addEventListener( + "click", + cancelCreateMeeting + ); + /* ============================================================ * START * ============================================================