refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -10,6 +10,7 @@ and a second machine running the desktop app obeys the same list.
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from fnmatch import fnmatch
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func, select
|
||||
@@ -21,27 +22,73 @@ from app.settings_schema import AppSettings
|
||||
from app.timeutil import ensure_utc, in_window, local_date, local_time
|
||||
|
||||
|
||||
def is_allowed(host: str, allow_masks: list[str]) -> bool:
|
||||
"""Whether an allow-list entry (plain or masked) covers this hostname."""
|
||||
return any(fnmatch(host, mask) for mask in allow_masks)
|
||||
|
||||
|
||||
def expand_entry(raw: str, subdomains: list[str]) -> tuple[list[str], str | None]:
|
||||
"""Turn one blocklist entry into concrete hostnames.
|
||||
|
||||
A hosts file has no wildcards, so a mask has to be expanded into real names:
|
||||
|
||||
* `youtube.com` → `youtube.com`, `www.youtube.com`, `m.youtube.com`
|
||||
* `*.youtube.com` → the bare domain plus every configured subdomain
|
||||
* `*gram*` → nothing expandable; returned as a leftover pattern so the
|
||||
UI can say so instead of silently dropping it
|
||||
|
||||
Returns `(hostnames, unexpandable_pattern)`.
|
||||
"""
|
||||
entry = raw.strip().lower().lstrip(".")
|
||||
if not entry:
|
||||
return [], None
|
||||
|
||||
if entry.startswith("*."):
|
||||
base = entry[2:]
|
||||
if not base or "*" in base:
|
||||
return [], entry
|
||||
return [base, *[f"{prefix}.{base}" for prefix in subdomains]], None
|
||||
|
||||
if "*" in entry:
|
||||
return [], entry
|
||||
|
||||
return [entry, f"www.{entry}", f"m.{entry}"], None
|
||||
|
||||
|
||||
def expand_domains(settings: AppSettings) -> list[str]:
|
||||
"""Every hostname the desktop app should point at the redirect IP.
|
||||
|
||||
A rule like `youtube.com` is meant as "that site", so `www.` and the common
|
||||
subdomains come along; anything in `allowed_domains` is removed again, which
|
||||
is how `youtube.com` blocked + `music.youtube.com` allowed works.
|
||||
`youtube.com` means "that site", so `www.` and `m.` come along; masks are
|
||||
expanded as described above; and anything the allow list covers — itself
|
||||
maskable — is taken back out, which is how `*.google.com` blocked plus
|
||||
`*.docs.google.com` allowed works.
|
||||
"""
|
||||
blocker = settings.blocker
|
||||
allowed = {entry.strip().lower().lstrip(".") for entry in blocker.allowed_domains if entry.strip()}
|
||||
allow_masks = [
|
||||
entry.strip().lower().lstrip(".") for entry in blocker.allowed_domains if entry.strip()
|
||||
]
|
||||
subdomains = [part.strip().lower() for part in blocker.subdomain_expansions if part.strip()]
|
||||
|
||||
hosts: list[str] = []
|
||||
for raw in blocker.blocked_domains:
|
||||
domain = raw.strip().lower().lstrip(".")
|
||||
if not domain or domain in allowed:
|
||||
continue
|
||||
for candidate in (domain, f"www.{domain}", f"m.{domain}"):
|
||||
if candidate not in allowed and candidate not in hosts:
|
||||
hosts.append(candidate)
|
||||
expanded, _ = expand_entry(raw, subdomains)
|
||||
for host in expanded:
|
||||
if host not in hosts and not is_allowed(host, allow_masks):
|
||||
hosts.append(host)
|
||||
return hosts
|
||||
|
||||
|
||||
def unexpandable_masks(settings: AppSettings) -> list[str]:
|
||||
"""Blocklist entries a hosts file cannot express, so the UI can flag them."""
|
||||
subdomains = [part.strip().lower() for part in settings.blocker.subdomain_expansions if part.strip()]
|
||||
leftovers: list[str] = []
|
||||
for raw in settings.blocker.blocked_domains:
|
||||
_, pattern = expand_entry(raw, subdomains)
|
||||
if pattern and pattern not in leftovers:
|
||||
leftovers.append(pattern)
|
||||
return leftovers
|
||||
|
||||
|
||||
def schedule_active(settings: AppSettings, moment: datetime | None = None) -> bool:
|
||||
now_time = local_time(settings.timezone, moment)
|
||||
weekday = (moment or datetime.now()).weekday()
|
||||
@@ -86,6 +133,7 @@ def policy(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
"redirect_ip": blocker.redirect_ip,
|
||||
"domains": expand_domains(settings) if active else [],
|
||||
"apps": [app.strip() for app in blocker.blocked_apps if app.strip()] if active else [],
|
||||
"unexpandable": unexpandable_masks(settings),
|
||||
"kill_apps": blocker.kill_blocked_apps,
|
||||
"notify_on_block": blocker.notify_on_block,
|
||||
"poll_seconds": blocker.app_poll_seconds,
|
||||
@@ -148,10 +196,10 @@ def events_for_day(session: Session, settings: AppSettings, day: date | None = N
|
||||
|
||||
|
||||
DEFAULT_DOMAINS = [
|
||||
"youtube.com",
|
||||
"*.youtube.com",
|
||||
"twitter.com",
|
||||
"x.com",
|
||||
"instagram.com",
|
||||
"*.instagram.com",
|
||||
"tiktok.com",
|
||||
"reddit.com",
|
||||
"facebook.com",
|
||||
@@ -162,4 +210,4 @@ DEFAULT_DOMAINS = [
|
||||
"pikabu.ru",
|
||||
]
|
||||
|
||||
DEFAULT_APPS = ["steam", "discord", "telegram-desktop", "spotify"]
|
||||
DEFAULT_APPS = ["steam*", "discord", "telegram-desktop", "spotify"]
|
||||
Reference in new issue
Block a user