update
This commit is contained in:
@@ -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.
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
@@ -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 |
@@ -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>
|
||||
Reference in New Issue
Block a user