Added ldap and pam authentication

This commit is contained in:
Martino Ferrari
2026-06-23 17:59:40 +02:00
parent ac24011487
commit 11120bedca
36 changed files with 1935 additions and 66 deletions
+76 -5
View File
@@ -1,24 +1,95 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import { useEffect, useState } from 'preact/hooks';
const LS_KEY = 'uopi:ui-zoom';
const ZOOM_EVENT = 'uopi:zoomchange';
const STEPS = [0.5, 0.6, 0.75, 0.85, 1.0, 1.15, 1.3, 1.5, 1.75, 2.0, 2.5];
// serverDefaultZoom is the base scale configured server-side ([ui] default_zoom),
// pushed in once /me resolves. It is the fallback when this browser has no saved
// per-machine override, so a fresh client on a HiDPI screen starts enlarged
// without per-browser setup while a user's local A+/A choice still wins.
let serverDefaultZoom = 1;
// hasStoredZoom reports whether this browser has an explicit saved zoom override.
export function hasStoredZoom(): boolean {
return localStorage.getItem(LS_KEY) !== null;
}
// setServerDefaultZoom records the server default and, unless the user has a saved
// override, applies it immediately (without persisting, so a later server change
// still propagates).
export function setServerDefaultZoom(z: number): void {
serverDefaultZoom = isFinite(z) && z > 0 ? z : 1;
if (!hasStoredZoom()) {
applyZoom(serverDefaultZoom, false);
}
}
export function getStoredZoom(): number {
const v = parseFloat(localStorage.getItem(LS_KEY) ?? '1');
const raw = localStorage.getItem(LS_KEY);
const v = raw === null ? serverDefaultZoom : parseFloat(raw);
return isFinite(v) ? Math.max(STEPS[0], Math.min(STEPS[STEPS.length - 1], v)) : 1;
}
export function applyZoom(z: number): number {
// dprFactor returns the device-pixel-ratio scaling applied on top of the manual
// zoom so the UI is proportionally larger on high-DPI displays by default.
// Firefox only honours CSS rem scaling (it does not auto-enlarge the root px the
// way Chrome does on some setups), so the ratio must be folded into the root
// font-size explicitly rather than left to the browser.
export function dprFactor(): number {
const dpr = window.devicePixelRatio;
return isFinite(dpr) && dpr > 0 ? dpr : 1;
}
// applyZoom sets the root font-size to scale the whole UI. When persist is true
// (a deliberate user action) the choice is saved as this browser's override;
// pass false to apply the server default transiently. A ZOOM_EVENT is dispatched
// so the ZoomControl's displayed percentage stays in sync regardless of source.
export function applyZoom(z: number, persist = true): number {
const clamped = Math.max(STEPS[0], Math.min(STEPS[STEPS.length - 1], z));
localStorage.setItem(LS_KEY, String(clamped));
document.documentElement.style.fontSize = `${Math.round(16 * clamped)}px`;
if (persist) {
localStorage.setItem(LS_KEY, String(clamped));
}
const dpr = dprFactor();
document.documentElement.style.setProperty('--dpr', String(dpr));
document.documentElement.style.fontSize = `${Math.round(16 * clamped * dpr)}px`;
window.dispatchEvent(new CustomEvent(ZOOM_EVENT, { detail: clamped }));
return clamped;
}
// watchDpr re-applies the current zoom whenever the device-pixel-ratio changes
// (e.g. the window moves to a monitor with different scaling). matchMedia fires
// once per resolution change; the listener is re-armed each time. Returns an
// unsubscribe function.
export function watchDpr(): () => void {
let mql: MediaQueryList | null = null;
const onChange = () => {
// Re-apply at the new DPR; only persist if this browser actually has an
// override (otherwise a monitor change would freeze the server default).
applyZoom(getStoredZoom(), hasStoredZoom());
arm();
};
const arm = () => {
mql?.removeEventListener('change', onChange);
mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
mql.addEventListener('change', onChange);
};
arm();
return () => mql?.removeEventListener('change', onChange);
}
export default function ZoomControl() {
const [zoom, setZoom] = useState(getStoredZoom);
// Keep the displayed percentage in sync when zoom is applied from elsewhere
// (e.g. the server default arriving via /me, or a DPR change).
useEffect(() => {
const onZoom = (e: Event) => setZoom((e as CustomEvent<number>).detail);
window.addEventListener(ZOOM_EVENT, onZoom);
return () => window.removeEventListener(ZOOM_EVENT, onZoom);
}, []);
function step(dir: number) {
const idx = STEPS.findIndex(s => s >= zoom - 0.01);
const nextIdx = Math.max(0, Math.min(STEPS.length - 1, idx + dir));