This commit is contained in:
2026-05-23 21:31:34 +02:00
parent 51b5ddc6f2
commit 641bb10479
6155 changed files with 3775717 additions and 291 deletions

View File

@@ -0,0 +1,6 @@
Backup Startpanel WebView-Video 20260520_203142
Rollback (PowerShell):
cd "C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026"
Copy-Item -Force "backup_start_panel_webview_video_20260520_203142\aza_start_panel.py" "aza_start_panel.py"
Copy-Item -Force "backup_start_panel_webview_video_20260520_203142\web\aza_start_panel.html" "web\aza_start_panel.html"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -0,0 +1,388 @@
# -*- coding: utf-8 -*-
"""
AzA Einheitliches Startpanel (lokal).
Zwei sichtbare Aktionen:
* AzA Office -> basis14.py / spaeter aza_desktop.exe
* MiniChat -> aza_empfang_webview.py mit ?minichat=1 / spaeter AZA_EmpfangShell.exe
UI: bevorzugt pywebview + web/aza_start_panel.html (MP4-Hintergrund).
Fallback: Tkinter + assets/matternhorn.png.
"""
from __future__ import annotations
import os
import subprocess
import sys
import tkinter as tk
from pathlib import Path
from tkinter import messagebox
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
try:
from PIL import Image, ImageEnhance, ImageFilter, ImageTk
_HAS_PIL = True
except Exception:
_HAS_PIL = False
_ROOT = Path(__file__).resolve().parent
# +30 % gegenueber 720x480
_W, _H = 940, 625
# Karte -30 % gegenueber 420px
_CARD_W = 294
_MP4 = _ROOT / "assets" / "matternhorn-aza-2.mp4"
_PNG = _ROOT / "assets" / "matternhorn.png"
_HTML = _ROOT / "web" / "aza_start_panel.html"
_FALLBACK_BG = "#d4e3ef"
_CARD_BG = "#f5f8fb"
_ACCENT = "#1a5f8a"
_ACCENT_HOVER = "#164f73"
_TEXT = "#1a2a3a"
_SUBTLE = "#5a6d7d"
_FONT_TITLE = ("Segoe UI", 20, "bold")
_FONT_SUB = ("Segoe UI", 11)
_FONT_BTN = ("Segoe UI", 11, "bold")
_FONT_FOOT = ("Segoe UI", 9)
def _center_window(win: tk.Tk, width: int, height: int) -> None:
win.update_idletasks()
sw = win.winfo_screenwidth()
sh = win.winfo_screenheight()
x = max(0, (sw - width) // 2)
y = max(0, (sh - height) // 2)
win.geometry(f"{width}x{height}+{x}+{y}")
def _minichat_url() -> str:
try:
from aza_empfang_webview import _build_default_empfang_chat_shell_url
base = _build_default_empfang_chat_shell_url()
except Exception:
base = (
os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or ""
).strip() or (
"https://empfang.aza-medwork.ch/empfang/"
"?empfang_chat_shell=1&shell_source=empfang_chat_shell"
)
try:
p = urlparse(base)
qs = parse_qs(p.query, keep_blank_values=True)
qs["minichat"] = ["1"]
return urlunparse(p._replace(query=urlencode(qs, doseq=True)))
except Exception:
sep = "&" if "?" in base else "?"
return f"{base}{sep}minichat=1"
def _resolve_office_executable() -> Path | None:
if getattr(sys, "frozen", False):
exe_dir = Path(sys.executable).resolve().parent
for name in ("aza_desktop.exe", "AzA.exe", "AzA_Start.exe"):
p = exe_dir / name
if p.is_file():
return p
meipass = getattr(sys, "_MEIPASS", "")
if meipass:
for name in ("aza_desktop.exe",):
p = Path(meipass) / name
if p.is_file():
return p
for p in (_ROOT / "aza_desktop.exe", _ROOT / "dist" / "aza_desktop" / "aza_desktop.exe"):
if p.is_file():
return p
return None
def _resolve_empfang_shell_executable() -> Path | None:
if getattr(sys, "frozen", False):
exe_dir = Path(sys.executable).resolve().parent
for name in ("AZA_EmpfangShell.exe",):
p = exe_dir / name
if p.is_file():
return p
meipass = getattr(sys, "_MEIPASS", "")
if meipass:
p = Path(meipass) / "AZA_EmpfangShell.exe"
if p.is_file():
return p
for p in (_ROOT / "AZA_EmpfangShell.exe", _ROOT / "dist" / "AZA_EmpfangShell.exe"):
if p.is_file():
return p
return None
def start_office() -> tuple[bool, str]:
"""Office starten ohne basis14.py zu aendern."""
exe = _resolve_office_executable()
try:
if exe is not None:
subprocess.Popen(
[str(exe)],
cwd=str(exe.parent),
close_fds=(sys.platform != "win32"),
)
return True, f"Gestartet: {exe.name}"
script = _ROOT / "basis14.py"
if not script.is_file():
return False, "basis14.py und aza_desktop.exe wurden nicht gefunden."
subprocess.Popen(
[sys.executable, str(script)],
cwd=str(_ROOT),
close_fds=(sys.platform != "win32"),
)
return True, "AzA Office wird gestartet (Entwicklungsmodus)."
except Exception as exc:
return False, f"Office konnte nicht gestartet werden: {exc}"
def start_minichat() -> tuple[bool, str]:
"""MiniChat ueber bestehenden WebView-Starter (keine HTML-Aenderung an empfang.html)."""
url = _minichat_url()
mode_arg = "--shell-mode=empfang_chat_shell"
w, h = "350", "700"
exe = _resolve_empfang_shell_executable()
try:
if exe is not None:
subprocess.Popen(
[str(exe), mode_arg, url, w, h],
cwd=str(exe.parent),
close_fds=(sys.platform != "win32"),
)
return True, f"MiniChat wird gestartet ({exe.name})."
script = _ROOT / "aza_empfang_webview.py"
if not script.is_file():
return (
False,
"aza_empfang_webview.py und AZA_EmpfangShell.exe wurden nicht gefunden.",
)
subprocess.Popen(
[sys.executable, str(script), mode_arg, url, w, h],
cwd=str(_ROOT),
close_fds=(sys.platform != "win32"),
)
return True, "MiniChat wird gestartet (Entwicklungsmodus)."
except Exception as exc:
return False, f"MiniChat konnte nicht gestartet werden: {exc}"
class StartPanelApi:
"""pywebview JS-API fuer web/aza_start_panel.html."""
def __init__(self) -> None:
self._window = None
def bind_window(self, window) -> None:
self._window = window
def _close_on_success(self, ok: bool) -> None:
if ok and self._window is not None:
try:
self._window.destroy()
except Exception:
pass
def launch_office(self) -> dict:
ok, msg = start_office()
self._close_on_success(ok)
return {"ok": ok, "message": msg}
def launch_minichat(self) -> dict:
ok, msg = start_minichat()
self._close_on_success(ok)
return {"ok": ok, "message": msg}
def _icon_path() -> str | None:
ico = _ROOT / "logo.ico"
return str(ico) if ico.is_file() else None
def _can_use_webview_panel() -> bool:
if os.environ.get("AZA_START_PANEL_FORCE_TK", "").strip() in ("1", "true", "yes"):
return False
if not _HTML.is_file():
return False
if not _PNG.is_file() and not _MP4.is_file():
return False
try:
import webview # noqa: F401
return True
except ImportError:
return False
def run_webview_panel() -> int:
import webview
api = StartPanelApi()
url = _HTML.resolve().as_uri()
kw: dict = {
"width": _W,
"height": _H,
"resizable": False,
"js_api": api,
}
ico = _icon_path()
if ico:
kw["icon"] = ico
win = webview.create_window("AzA", url, **kw)
api.bind_window(win)
webview.start(private_mode=True)
return 0
def _load_logo_photo(master: tk.Misc, size: int = 44) -> tk.PhotoImage | None:
logo = _ROOT / "logo.png"
if not logo.is_file():
return None
try:
if _HAS_PIL:
img = Image.open(logo).convert("RGBA")
img = img.resize((size, size), Image.Resampling.LANCZOS)
return ImageTk.PhotoImage(img, master=master)
return tk.PhotoImage(file=str(logo), master=master)
except Exception:
return None
def _build_background_photo(master: tk.Misc) -> tk.PhotoImage | None:
if not _PNG.is_file() or not _HAS_PIL:
return None
try:
img = Image.open(_PNG).convert("RGB")
img = ImageEnhance.Brightness(img).enhance(1.08)
img = img.resize((_W, _H), Image.Resampling.LANCZOS)
img = img.filter(ImageFilter.GaussianBlur(radius=1.6))
fog = Image.new("RGBA", (_W, _H), (245, 248, 252, 155))
img = Image.alpha_composite(img.convert("RGBA"), fog).convert("RGB")
return ImageTk.PhotoImage(img, master=master)
except Exception:
return None
class AzAStartPanelTk:
"""Tkinter-Fallback wenn pywebview/Video nicht nutzbar."""
def __init__(self) -> None:
self.root = tk.Tk()
self.root.title("AzA")
self.root.resizable(False, False)
self.root.configure(bg=_FALLBACK_BG)
_center_window(self.root, _W, _H)
self._bg_photo: tk.PhotoImage | None = None
self._logo_photo: tk.PhotoImage | None = None
self.canvas = tk.Canvas(
self.root, width=_W, height=_H, highlightthickness=0, bd=0
)
self.canvas.pack(fill="both", expand=True)
self._bg_photo = _build_background_photo(self.root)
if self._bg_photo is not None:
self.canvas.create_image(0, 0, anchor="nw", image=self._bg_photo)
else:
self.canvas.configure(bg=_FALLBACK_BG)
card = tk.Frame(
self.canvas,
bg=_CARD_BG,
highlightbackground="#c8d6e4",
highlightthickness=1,
)
self.canvas.create_window(_W // 2, _H // 2, window=card, width=_CARD_W)
header = tk.Frame(card, bg=_CARD_BG)
header.pack(pady=(20, 6))
self._logo_photo = _load_logo_photo(card, 44)
if self._logo_photo is not None:
tk.Label(header, image=self._logo_photo, bg=_CARD_BG).pack()
tk.Label(header, text="AzA", font=_FONT_TITLE, fg=_TEXT, bg=_CARD_BG).pack(
pady=(8, 2)
)
tk.Label(
header,
text="Was möchten Sie öffnen?",
font=_FONT_SUB,
fg=_SUBTLE,
bg=_CARD_BG,
).pack(pady=(0, 14))
btn_frame = tk.Frame(card, bg=_CARD_BG)
btn_frame.pack(padx=22, pady=(0, 6))
self._mk_btn(btn_frame, "AzA Office", self._on_office).pack(
fill="x", pady=(0, 10)
)
self._mk_btn(btn_frame, "MiniChat", self._on_minichat).pack(fill="x")
tk.Label(
card, text="AzA Medwork", font=_FONT_FOOT, fg=_SUBTLE, bg=_CARD_BG
).pack(pady=(16, 16))
if sys.platform == "win32":
ico = _icon_path()
if ico:
try:
self.root.iconbitmap(ico)
except Exception:
pass
def _mk_btn(self, parent: tk.Misc, text: str, cmd) -> tk.Button:
btn = tk.Button(
parent,
text=text,
font=_FONT_BTN,
fg="#ffffff",
bg=_ACCENT,
activebackground=_ACCENT_HOVER,
activeforeground="#ffffff",
relief="flat",
bd=0,
padx=18,
pady=10,
cursor="hand2",
command=cmd,
)
btn.bind("<Enter>", lambda _e: btn.configure(bg=_ACCENT_HOVER))
btn.bind("<Leave>", lambda _e: btn.configure(bg=_ACCENT))
return btn
def _on_office(self) -> None:
ok, msg = start_office()
if not ok:
messagebox.showerror("AzA Office", msg, parent=self.root)
else:
self.root.destroy()
def _on_minichat(self) -> None:
ok, msg = start_minichat()
if not ok:
messagebox.showerror("MiniChat", msg, parent=self.root)
else:
self.root.destroy()
def run(self) -> None:
self.root.mainloop()
def main() -> int:
if _can_use_webview_panel():
try:
return run_webview_panel()
except Exception as exc:
print(f"[AzA Startpanel] WebView-Fallback: {exc}", file=sys.stderr)
AzAStartPanelTk().run()
return 0
if __name__ == "__main__":
raise SystemExit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>AzA</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
width: 100%; height: 100%; overflow: hidden;
font-family: "Segoe UI", system-ui, sans-serif;
background: #c5d4e2;
}
.bg-video, .bg-fallback {
position: fixed; inset: 0; width: 100%; height: 100%;
object-fit: cover; z-index: 0;
}
.bg-fallback { display: none; }
body.no-video .bg-video { display: none; }
body.no-video .bg-fallback { display: block; }
.fog {
position: fixed; inset: 0; z-index: 1;
background: linear-gradient(
180deg,
rgba(240, 246, 252, 0.35) 0%,
rgba(230, 240, 250, 0.55) 45%,
rgba(220, 232, 244, 0.65) 100%
);
pointer-events: none;
}
.stage {
position: fixed; inset: 0; z-index: 2;
display: flex; align-items: center; justify-content: center;
padding: 24px;
}
.card {
width: min(294px, 88vw);
padding: 22px 22px 18px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(255, 255, 255, 0.85);
box-shadow: 0 12px 40px rgba(26, 42, 58, 0.12);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
text-align: center;
}
.logo {
width: 48px; height: 48px; object-fit: contain;
margin: 0 auto 10px; display: block;
}
h1 {
font-size: 1.55rem; font-weight: 700;
color: #1a2a3a; letter-spacing: 0.02em;
}
.sub {
margin-top: 6px; margin-bottom: 18px;
font-size: 0.92rem; color: #5a6d7d;
}
.btn {
display: block; width: 100%;
margin-bottom: 10px;
padding: 12px 16px;
border: none; border-radius: 10px;
font-size: 0.95rem; font-weight: 600;
color: #fff; background: #1a5f8a;
cursor: pointer;
transition: background 0.15s ease, transform 0.1s ease;
box-shadow: 0 4px 14px rgba(26, 95, 138, 0.25);
}
.btn:last-of-type { margin-bottom: 0; }
.btn:hover { background: #164f73; }
.btn:active { transform: scale(0.98); }
.btn:disabled { opacity: 0.65; cursor: wait; }
.foot {
margin-top: 16px; font-size: 0.72rem; color: #7a8d9c;
}
.err {
display: none; margin-top: 10px;
font-size: 0.78rem; color: #b42318;
}
.err.show { display: block; }
</style>
</head>
<body>
<video class="bg-video" autoplay muted loop playsinline
poster="../assets/matternhorn.png">
<source src="../assets/matternhorn-aza-2.mp4" type="video/mp4"/>
</video>
<img class="bg-fallback" src="../assets/matternhorn.png" alt=""/>
<div class="fog"></div>
<div class="stage">
<div class="card">
<img class="logo" src="../logo.png" alt="" onerror="this.style.display='none'"/>
<h1>AzA</h1>
<p class="sub">Was möchten Sie öffnen?</p>
<button type="button" class="btn" id="btnOffice">AzA Office</button>
<button type="button" class="btn" id="btnMiniChat">MiniChat</button>
<p class="err" id="errBox"></p>
<p class="foot">AzA Medwork</p>
</div>
</div>
<script>
(function () {
var v = document.querySelector(".bg-video");
function useFallback() {
document.body.classList.add("no-video");
}
if (v) {
v.addEventListener("error", useFallback);
v.addEventListener("stalled", function () {
if (v.readyState < 2) useFallback();
});
setTimeout(function () {
if (v.readyState < 2 && v.error) useFallback();
}, 2500);
} else {
useFallback();
}
function showErr(msg) {
var el = document.getElementById("errBox");
el.textContent = msg || "";
el.classList.toggle("show", !!msg);
}
function setBusy(busy) {
document.getElementById("btnOffice").disabled = busy;
document.getElementById("btnMiniChat").disabled = busy;
}
async function callApi(method) {
if (!window.pywebview || !pywebview.api || !pywebview.api[method]) {
showErr("Start-API nicht verfügbar.");
return;
}
setBusy(true);
showErr("");
try {
var r = await pywebview.api[method]();
if (r && r.ok) return;
showErr((r && r.message) ? r.message : "Start fehlgeschlagen.");
} catch (e) {
showErr(String(e && e.message ? e.message : e));
} finally {
setBusy(false);
}
}
document.getElementById("btnOffice").addEventListener("click", function () {
callApi("launch_office");
});
document.getElementById("btnMiniChat").addEventListener("click", function () {
callApi("launch_minichat");
});
})();
</script>
</body>
</html>