push web
This commit is contained in:
178
web/theme_manager.py
Normal file
178
web/theme_manager.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""Менеджер тем и масштаба приложения с сохранением в localStorage браузера."""
|
||||
import flet as ft
|
||||
import designer as ds
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Темы: каждый цвет surface должен КОНТРАСТИРОВАТЬ с background,
|
||||
# чтобы карточки, поля и кнопки не сливались с фоном.
|
||||
# ---------------------------------------------------------------------------
|
||||
THEMES: dict[str, dict] = {
|
||||
"default": {
|
||||
"name": "ЦОПП (оранжевая)",
|
||||
# фон страницы
|
||||
"background": "#F4F6F8",
|
||||
# карточки / формы — светлее фона
|
||||
"surface": "#FFFFFF",
|
||||
# альтернативные блоки — чуть темнее фона
|
||||
"surface_alt": "#E4E8ED",
|
||||
},
|
||||
"warm": {
|
||||
"name": "Тёплая",
|
||||
"background": "#FFF3E6",
|
||||
"surface": "#FFFFFF",
|
||||
"surface_alt": "#FFD9B3",
|
||||
},
|
||||
"blue": {
|
||||
"name": "Синяя",
|
||||
"background": "#E3F2FD",
|
||||
"surface": "#FFFFFF",
|
||||
"surface_alt": "#B8D9F5",
|
||||
},
|
||||
"green": {
|
||||
"name": "Зелёная",
|
||||
"background": "#EDF7E7",
|
||||
"surface": "#FFFFFF",
|
||||
"surface_alt": "#BEE3B0",
|
||||
},
|
||||
"dark": {
|
||||
"name": "Тёмная",
|
||||
"background": "#1E2A3A",
|
||||
"surface": "#2D3F54",
|
||||
"surface_alt": "#3D5068",
|
||||
},
|
||||
}
|
||||
|
||||
_SCALE_KEY = "ui_scale"
|
||||
_THEME_KEY = "ui_theme"
|
||||
|
||||
# Допустимый диапазон масштаба
|
||||
SCALE_MIN = 0.8
|
||||
SCALE_MAX = 2.5
|
||||
SCALE_STEP = 0.1
|
||||
SCALE_DEFAULT = 1.7
|
||||
|
||||
# Автомасштаб по ширине экрана
|
||||
_MOBILE_BREAKPOINT = 600 # px: меньше — телефон, больше — ПК/планшет
|
||||
SCALE_MOBILE = 1.0
|
||||
SCALE_DESKTOP = 2.0
|
||||
|
||||
|
||||
def auto_scale(page: ft.Page) -> float:
|
||||
"""Вернуть масштаб на основе ширины viewport браузера."""
|
||||
try:
|
||||
w = page.width or 0
|
||||
return SCALE_MOBILE if w < _MOBILE_BREAKPOINT else SCALE_DESKTOP
|
||||
except Exception:
|
||||
return SCALE_DEFAULT
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Серверное хранилище настроек по user_id.
|
||||
# В памяти контейнера — переживает F5 в браузере, сбрасывается при рестарте.
|
||||
# ---------------------------------------------------------------------------
|
||||
_user_prefs: dict[str, dict] = {} # user_id -> {"theme": str, "scale": float}
|
||||
|
||||
|
||||
def _get_uid(page: ft.Page) -> str:
|
||||
return page.session.store.get("user_id") or ""
|
||||
|
||||
|
||||
def _prefs_for(user_id: str) -> dict:
|
||||
return _user_prefs.get(user_id, {"theme": "default", "scale": SCALE_DEFAULT})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Публичный API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def apply_theme(page: ft.Page, theme_id: str | None = None) -> None:
|
||||
"""Применить тему: обновить ds.colors и page.bgcolor."""
|
||||
if theme_id is None:
|
||||
uid = _get_uid(page)
|
||||
theme_id = _prefs_for(uid)["theme"] if uid else "default"
|
||||
if theme_id not in THEMES:
|
||||
theme_id = "default"
|
||||
|
||||
t = THEMES[theme_id]
|
||||
ds.colors.background = t["background"]
|
||||
ds.colors.surface = t["surface"]
|
||||
ds.colors.surface_alt = t["surface_alt"]
|
||||
|
||||
page.bgcolor = t["background"]
|
||||
# кешируем в session.store для быстрого доступа внутри сессии
|
||||
page.session.store.set(_THEME_KEY, theme_id)
|
||||
|
||||
|
||||
def apply_scale(page: ft.Page, scale: float | None = None) -> None:
|
||||
"""Применить масштаб: обновить ds.SCALE.
|
||||
|
||||
Если масштаб не задан явно и у пользователя нет сохранённого —
|
||||
определяется автоматически по ширине экрана (мобильный / ПК).
|
||||
"""
|
||||
if scale is None:
|
||||
uid = _get_uid(page)
|
||||
if uid and uid in _user_prefs:
|
||||
# есть явно сохранённая настройка пользователя
|
||||
scale = _user_prefs[uid]["scale"]
|
||||
else:
|
||||
# первый вход — автоопределение по ширине экрана
|
||||
scale = auto_scale(page)
|
||||
scale = max(SCALE_MIN, min(SCALE_MAX, float(scale)))
|
||||
ds.SCALE = scale
|
||||
page.session.store.set(_SCALE_KEY, str(scale))
|
||||
|
||||
|
||||
def save_preferences(page: ft.Page, theme_id: str, scale: float,
|
||||
user_id: str | None = None) -> None:
|
||||
"""Сохранить тему и масштаб. Хранятся в памяти сервера по user_id."""
|
||||
uid = user_id or _get_uid(page)
|
||||
if uid:
|
||||
_user_prefs[uid] = {"theme": theme_id, "scale": float(scale)}
|
||||
# также пишем в session.store — нужно для apply_* внутри текущей сессии
|
||||
page.session.store.set(_THEME_KEY, theme_id)
|
||||
page.session.store.set(_SCALE_KEY, str(scale))
|
||||
|
||||
|
||||
def load_theme_id(page: ft.Page) -> str:
|
||||
# сначала session.store (уже применено), потом serv dict
|
||||
v = page.session.store.get(_THEME_KEY)
|
||||
if v and v in THEMES:
|
||||
return v
|
||||
uid = _get_uid(page)
|
||||
v2 = _prefs_for(uid)["theme"] if uid else "default"
|
||||
return v2 if v2 in THEMES else "default"
|
||||
|
||||
|
||||
def load_scale(page: ft.Page) -> float:
|
||||
v = page.session.store.get(_SCALE_KEY)
|
||||
try:
|
||||
if v is not None:
|
||||
return float(v)
|
||||
except ValueError:
|
||||
pass
|
||||
uid = _get_uid(page)
|
||||
return _prefs_for(uid)["scale"] if uid else SCALE_DEFAULT
|
||||
|
||||
|
||||
def theme_list() -> list[dict]:
|
||||
return [{"id": k, "name": v["name"]} for k, v in THEMES.items()]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Устаревший алиас (оставлен для совместимости с роутером)
|
||||
# ---------------------------------------------------------------------------
|
||||
class ThemeManager:
|
||||
THEMES = THEMES
|
||||
|
||||
@staticmethod
|
||||
def get_theme_list():
|
||||
return theme_list()
|
||||
|
||||
@staticmethod
|
||||
def load_theme_preference(page):
|
||||
return load_theme_id(page)
|
||||
|
||||
@staticmethod
|
||||
def save_theme_preference(page, theme_id):
|
||||
save_preferences(page, theme_id, load_scale(page))
|
||||
Reference in New Issue
Block a user