(() => { document.querySelectorAll("[data-auto-submit='true']").forEach((element) => { element.addEventListener("change", () => { if (element.form) element.form.submit(); }); }); })(); (() => { const usage = document.getElementById("Usage"); const redirect = document.getElementById("RedirectUris"); const clientType = document.getElementById("ClientType"); if (!usage || !redirect || !clientType) return; const syncRedirectInputState = () => { const usageValue = usage.value; const needsRedirect = usageValue === "web_login" || usageValue === "webhook_outbound"; const requiresConfidential = usageValue === "tenant_api" || usageValue === "send_api" || usageValue === "platform_service" || usageValue === "file_api"; redirect.disabled = !needsRedirect; if (!needsRedirect) redirect.value = ""; const publicOption = clientType.querySelector('option[value="public"]'); if (publicOption) publicOption.disabled = requiresConfidential; if (requiresConfidential) clientType.value = "confidential"; }; usage.addEventListener("change", syncRedirectInputState); syncRedirectInputState(); })(); (() => { const forms = document.querySelectorAll("[data-avatar-upload]"); if (!forms.length) return; const targetSize = 256; const maxOutputBytes = 512 * 1024; const readFileAsImage = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const image = new Image(); image.onload = () => resolve(image); image.onerror = () => reject(new Error("Invalid image.")); image.src = reader.result; }; reader.onerror = () => reject(new Error("Invalid image.")); reader.readAsDataURL(file); }); const canvasToBlob = (canvas, type, quality) => new Promise((resolve) => { canvas.toBlob(resolve, type, quality); }); const blobToDataUrl = (blob) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); const submitForm = (form) => { HTMLFormElement.prototype.submit.call(form); }; forms.forEach((form) => { const fileInput = form.querySelector("[data-avatar-file]"); const dataUrlInput = form.querySelector("[data-avatar-data-url]"); const contentTypeInput = form.querySelector("[data-avatar-content-type]"); const widthInput = form.querySelector("[data-avatar-width]"); const heightInput = form.querySelector("[data-avatar-height]"); if (!fileInput || !dataUrlInput || !contentTypeInput || !widthInput || !heightInput) return; fileInput.addEventListener("change", async () => { const file = fileInput.files && fileInput.files[0]; if (!file) return; try { const image = await readFileAsImage(file); openCropDialog(form, image, () => { fileInput.value = ""; }, async (cropState) => { const canvas = document.createElement("canvas"); canvas.width = targetSize; canvas.height = targetSize; const context = canvas.getContext("2d"); if (!context) throw new Error("Canvas is not available."); context.drawImage( image, (targetSize - cropState.renderWidth) / 2 + cropState.offsetX, (targetSize - cropState.renderHeight) / 2 + cropState.offsetY, cropState.renderWidth, cropState.renderHeight); let blob = await canvasToBlob(canvas, "image/webp", 0.82); if (!blob) blob = await canvasToBlob(canvas, "image/png"); if (!blob || blob.size > maxOutputBytes) { throw new Error("Image output is too large."); } dataUrlInput.value = await blobToDataUrl(blob); contentTypeInput.value = blob.type || "image/png"; widthInput.value = String(targetSize); heightInput.value = String(targetSize); submitForm(form); }); } catch { fileInput.value = ""; } }); }); function openCropDialog(form, image, onCancel, onSave) { const strings = { title: form.dataset.cropTitle || "Adjust profile image", zoom: form.dataset.cropZoom || "Zoom", save: form.dataset.cropSave || "Save", cancel: form.dataset.cropCancel || "Cancel", error: form.dataset.cropError || "Unable to process this image." }; const dialog = document.createElement("div"); dialog.className = "avatar-crop-modal"; dialog.innerHTML = `