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.
+
+
+
+
+
+
+
+
+
Create New Meeting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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
* ============================================================