263 lines
5.6 KiB
Python
263 lines
5.6 KiB
Python
# v4.0.0 settings.py
|
|
import json
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
|
|
def resolve_app_dir() -> Path:
|
|
if getattr(sys, "frozen", False):
|
|
return Path(
|
|
sys.executable
|
|
).resolve().parent
|
|
|
|
return (
|
|
Path(__file__)
|
|
.resolve()
|
|
.parent
|
|
.parent
|
|
)
|
|
|
|
|
|
APP_DIR: Final[Path] = (
|
|
resolve_app_dir()
|
|
)
|
|
|
|
APP_DIR.mkdir(
|
|
parents=True,
|
|
exist_ok=True,
|
|
)
|
|
|
|
SETTINGS_FILE: Final[Path] = (
|
|
APP_DIR / "settings.json"
|
|
)
|
|
|
|
STATE_FILE: Final[Path] = (
|
|
APP_DIR / "active.json"
|
|
)
|
|
|
|
MARKDOWN_FILE: Final[Path] = (
|
|
APP_DIR / "note.md"
|
|
)
|
|
|
|
|
|
DEFAULT_SETTINGS: Final[dict] = {
|
|
"active_profile": "default",
|
|
"profiles": {
|
|
"default": {
|
|
"theme": "dark",
|
|
"window": {
|
|
"width": 250,
|
|
"height": 260,
|
|
"min_width": 200,
|
|
"min_height": 100,
|
|
"layout_margin": 0,
|
|
"layout_spacing": 0,
|
|
"drag_handle_height": 0,
|
|
"resize_grip_size": 16,
|
|
"corner_radius": 8,
|
|
"startup_stabilize_ms": 80,
|
|
},
|
|
"appearance": {
|
|
"enable_mica": True,
|
|
"enable_acrylic": True,
|
|
"enable_glass_blur": True,
|
|
"enable_animations": True,
|
|
"enable_shadow": False,
|
|
"opacity_active": 0.97,
|
|
"opacity_inactive": 0.95,
|
|
"opacity_dragging": 0.89,
|
|
"focus_animation_ms": 180,
|
|
},
|
|
"editor": {
|
|
"font_family": [
|
|
"Inter",
|
|
"SF Pro Display",
|
|
"Helvetica",
|
|
"Arial",
|
|
"system-ui",
|
|
],
|
|
"font_size": 13,
|
|
"preview_delay_ms": 700,
|
|
"save_debounce_ms": 500,
|
|
"max_note_size": 2_000_000,
|
|
"enable_markdown_preview": True,
|
|
"enable_clickable_links": True,
|
|
"enable_antialiasing": True,
|
|
"enable_spellcheck": False,
|
|
"padding": 0,
|
|
},
|
|
"behavior": {
|
|
"enable_tray": True,
|
|
"enable_persistence": True,
|
|
"enable_window_memory": True,
|
|
"enable_auto_restore": True,
|
|
"high_contrast_inactive": False,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def atomic_write(
|
|
path: Path,
|
|
content: str,
|
|
) -> None:
|
|
path.parent.mkdir(
|
|
parents=True,
|
|
exist_ok=True,
|
|
)
|
|
|
|
temp = path.with_suffix(
|
|
f"{path.suffix}.tmp"
|
|
)
|
|
|
|
temp.write_text(
|
|
content,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
temp.replace(path)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class SettingsManager:
|
|
settings: dict
|
|
|
|
@classmethod
|
|
def load(cls) -> "SettingsManager":
|
|
SETTINGS_FILE.parent.mkdir(
|
|
parents=True,
|
|
exist_ok=True,
|
|
)
|
|
|
|
if not SETTINGS_FILE.exists():
|
|
atomic_write(
|
|
SETTINGS_FILE,
|
|
json.dumps(
|
|
DEFAULT_SETTINGS,
|
|
indent=2,
|
|
),
|
|
)
|
|
|
|
try:
|
|
raw = SETTINGS_FILE.read_text(
|
|
encoding="utf-8",
|
|
)
|
|
|
|
data = json.loads(raw)
|
|
|
|
merged = cls.merge_defaults(
|
|
DEFAULT_SETTINGS,
|
|
data,
|
|
)
|
|
|
|
manager = cls(merged)
|
|
|
|
manager.save()
|
|
|
|
return manager
|
|
|
|
except Exception:
|
|
atomic_write(
|
|
SETTINGS_FILE,
|
|
json.dumps(
|
|
DEFAULT_SETTINGS,
|
|
indent=2,
|
|
),
|
|
)
|
|
|
|
return cls(
|
|
DEFAULT_SETTINGS
|
|
)
|
|
|
|
@staticmethod
|
|
def merge_defaults(
|
|
defaults: dict,
|
|
current: dict,
|
|
) -> dict:
|
|
result = {}
|
|
|
|
for key, value in defaults.items():
|
|
result[key] = value
|
|
|
|
for key, value in current.items():
|
|
if (
|
|
key in result
|
|
and isinstance(
|
|
result[key],
|
|
dict,
|
|
)
|
|
and isinstance(
|
|
value,
|
|
dict,
|
|
)
|
|
):
|
|
result[key] = (
|
|
SettingsManager.merge_defaults(
|
|
result[key],
|
|
value,
|
|
)
|
|
)
|
|
|
|
else:
|
|
result[key] = value
|
|
|
|
return result
|
|
|
|
def save(self) -> None:
|
|
atomic_write(
|
|
SETTINGS_FILE,
|
|
json.dumps(
|
|
self.settings,
|
|
indent=2,
|
|
),
|
|
)
|
|
|
|
@property
|
|
def active_profile_name(
|
|
self,
|
|
) -> str:
|
|
return self.settings.get(
|
|
"active_profile",
|
|
"default",
|
|
)
|
|
|
|
@property
|
|
def profiles(self) -> dict:
|
|
return self.settings.get(
|
|
"profiles",
|
|
{},
|
|
)
|
|
|
|
@property
|
|
def profile(self) -> dict:
|
|
active = (
|
|
self.active_profile_name
|
|
)
|
|
|
|
profiles = self.profiles
|
|
|
|
if active not in profiles:
|
|
return profiles[
|
|
"default"
|
|
]
|
|
|
|
return profiles[active]
|
|
|
|
def reload(self) -> None:
|
|
loaded = (
|
|
SettingsManager.load()
|
|
)
|
|
|
|
self.settings = (
|
|
loaded.settings
|
|
)
|
|
|
|
def reset(self) -> None:
|
|
self.settings = (
|
|
DEFAULT_SETTINGS
|
|
)
|
|
|
|
self.save() |