cct-cli 0.7.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Customization — Responsive Professional Redesign (v0.8.3)
|
|
3
|
+
No hover-auto-start: every change is explicit click / Apply / Toggle.
|
|
4
|
+
Card layout, lightweight per-panel editing for performance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
if __name__ == "__main__":
|
|
8
|
+
print("This is a library file and is not meant to be run directly.")
|
|
9
|
+
import sys
|
|
10
|
+
sys.exit(1)
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
TEXTUAL_AVAILABLE = True
|
|
15
|
+
try:
|
|
16
|
+
from textual.screen import Screen
|
|
17
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
18
|
+
from textual.widgets import Static, Button, Input, Switch, Select
|
|
19
|
+
from textual.binding import Binding
|
|
20
|
+
try:
|
|
21
|
+
from textual.widgets import TextArea
|
|
22
|
+
_HAS_TEXTAREA = True
|
|
23
|
+
except Exception:
|
|
24
|
+
TextArea = None
|
|
25
|
+
_HAS_TEXTAREA = False
|
|
26
|
+
except Exception:
|
|
27
|
+
TEXTUAL_AVAILABLE = False
|
|
28
|
+
_HAS_TEXTAREA = False
|
|
29
|
+
|
|
30
|
+
if TEXTUAL_AVAILABLE:
|
|
31
|
+
try:
|
|
32
|
+
from .. import customization as cust
|
|
33
|
+
from ..customization import COMPONENT_CONSTRAINTS
|
|
34
|
+
except Exception:
|
|
35
|
+
cust = None
|
|
36
|
+
COMPONENT_CONSTRAINTS = {}
|
|
37
|
+
try:
|
|
38
|
+
from . import theme_css
|
|
39
|
+
if hasattr(theme_css, "_patch_rich_divide_line"):
|
|
40
|
+
theme_css._patch_rich_divide_line()
|
|
41
|
+
except Exception:
|
|
42
|
+
theme_css = None
|
|
43
|
+
|
|
44
|
+
def _hex(role):
|
|
45
|
+
try:
|
|
46
|
+
return theme_css.current_hex(role) if theme_css else "#888888"
|
|
47
|
+
except Exception:
|
|
48
|
+
return "#888888"
|
|
49
|
+
|
|
50
|
+
_NAV_SECTIONS = [
|
|
51
|
+
("OVERVIEW", [("overview", "◎ Overview")]),
|
|
52
|
+
("LAYOUT", [("layout", "▦ Order"), ("position", "↔ Position"), ("size", "⧉ Size"), ("docking", "⚓ Docking")]),
|
|
53
|
+
("APPEARANCE", [("appearance", "✎ Appearance"), ("buttons", "⬢ Buttons"), ("icons", "◇ Icons"), ("header", "⬔ Header"), ("menu", "☰ Menu"), ("spacing", "⟷ Spacing")]),
|
|
54
|
+
("BEHAVIOR", [("visibility", "◉ Visibility")]),
|
|
55
|
+
("WORKSPACE", [("profiles", "★ Profiles"), ("editor", "⬙ Editor"), ("advanced", "⌘ Advanced"), ("reset", "↺ Reset")]),
|
|
56
|
+
]
|
|
57
|
+
_HINTS = {
|
|
58
|
+
"overview": "Overview · select panel below then edit in its section",
|
|
59
|
+
"layout": "Order defines visual sequence · use ↑ ↓",
|
|
60
|
+
"position": "Choose panel then click position · explicit Apply",
|
|
61
|
+
"size": "Choose panel then set W/H · Apply",
|
|
62
|
+
"docking": "Docked inline · Floating overlay",
|
|
63
|
+
"appearance": "Appearance overview · jump to details",
|
|
64
|
+
"buttons": "Shape · size · radius · padding",
|
|
65
|
+
"icons": "Size · visibility",
|
|
66
|
+
"header": "Height · logo · title",
|
|
67
|
+
"menu": "Menu order & visibility",
|
|
68
|
+
"spacing": "Padding · margin · borders",
|
|
69
|
+
"visibility": "Toggle any region",
|
|
70
|
+
"profiles": "Save / load layouts",
|
|
71
|
+
"editor": "Visual preview · click to cycle",
|
|
72
|
+
"advanced": "Raw JSON · Save to apply",
|
|
73
|
+
"reset": "Restore defaults",
|
|
74
|
+
}
|
|
75
|
+
_PANEL_IDS = ["sidebar", "chat", "code_editor", "terminal", "file_explorer", "header", "preview", "activity_bar", "secondary_panel", "editor"]
|
|
76
|
+
_POSITION_OPTS = ["left", "right", "center", "top", "bottom", "floating", "docked"]
|
|
77
|
+
_BUTTON_SHAPES = ["rounded", "pill", "square", "minimal", "ghost", "outlined", "filled", "compact", "large"]
|
|
78
|
+
_BUTTON_SIZES = ["small", "medium", "large", "compact"]
|
|
79
|
+
_ICON_SIZES = ["small", "medium", "large"]
|
|
80
|
+
|
|
81
|
+
def _safe_query(widget, selector, typ=None):
|
|
82
|
+
try:
|
|
83
|
+
if typ:
|
|
84
|
+
return widget.query_one(selector, typ)
|
|
85
|
+
return widget.query_one(selector)
|
|
86
|
+
except Exception:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
class _Preview(Static):
|
|
90
|
+
allow_select = False
|
|
91
|
+
|
|
92
|
+
def __init__(self, manager, selected="sidebar"):
|
|
93
|
+
super().__init__("", id="cust-preview")
|
|
94
|
+
self._manager = manager
|
|
95
|
+
self._selected = selected or "sidebar"
|
|
96
|
+
self.allow_select = False
|
|
97
|
+
|
|
98
|
+
def on_mount(self):
|
|
99
|
+
self.refresh_preview()
|
|
100
|
+
|
|
101
|
+
def on_mouse_down(self, event):
|
|
102
|
+
try:
|
|
103
|
+
event.stop()
|
|
104
|
+
event.prevent_default()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def refresh_preview(self):
|
|
109
|
+
try:
|
|
110
|
+
layout = self._manager.get_layout()
|
|
111
|
+
w = 50
|
|
112
|
+
lines = []
|
|
113
|
+
header = layout.get("header", {})
|
|
114
|
+
if header.get("visible", True):
|
|
115
|
+
sel = "● " if self._selected == "header" else " "
|
|
116
|
+
h_h = header.get("height", 3)
|
|
117
|
+
lines.append("┌" + "─" * w + "┐")
|
|
118
|
+
lines.append("│" + f"{sel}HEADER (height: {h_h})".center(w) + "│")
|
|
119
|
+
|
|
120
|
+
panes = []
|
|
121
|
+
seen = set()
|
|
122
|
+
order_list = sorted([v for v in layout.values() if v.get("visible", True)], key=lambda c: c.get("order", 0))
|
|
123
|
+
for p in order_list:
|
|
124
|
+
pid = p["id"]
|
|
125
|
+
if pid in ("header", "terminal"):
|
|
126
|
+
continue
|
|
127
|
+
canon = "code_editor" if pid in ("editor", "code_editor") else ("sidebar" if pid in ("sidebar", "file_explorer") else pid)
|
|
128
|
+
if canon in seen:
|
|
129
|
+
continue
|
|
130
|
+
seen.add(canon)
|
|
131
|
+
panes.append(p)
|
|
132
|
+
|
|
133
|
+
label_map = {"sidebar": "SIDEBAR", "chat": "CHAT", "code_editor": "EDITOR", "file_explorer": "FILES"}
|
|
134
|
+
n = len(panes)
|
|
135
|
+
if n > 0:
|
|
136
|
+
lines.append("├" + "─" * w + "┤")
|
|
137
|
+
col_w = (w - (n - 1)) // n
|
|
138
|
+
rem = (w - (n - 1)) % n
|
|
139
|
+
widths = [col_w + (1 if i < rem else 0) for i in range(n)]
|
|
140
|
+
|
|
141
|
+
row1_parts = []
|
|
142
|
+
row2_parts = []
|
|
143
|
+
for i, p in enumerate(panes):
|
|
144
|
+
pw = widths[i]
|
|
145
|
+
lab = label_map.get(p["id"], p["id"][:8].upper())
|
|
146
|
+
is_sel = (p["id"] == self._selected or ("code_editor" in (p["id"], self._selected) and "editor" in (p["id"], self._selected)) or ("sidebar" in (p["id"], self._selected) and "file_explorer" in (p["id"], self._selected)))
|
|
147
|
+
sel_mark = "● " if is_sel else ""
|
|
148
|
+
pos_str = f"({p.get('position', '?')})"
|
|
149
|
+
row1_parts.append(f"{sel_mark}{lab}".center(pw))
|
|
150
|
+
row2_parts.append(pos_str.center(pw))
|
|
151
|
+
|
|
152
|
+
lines.append("│" + "│".join(row1_parts) + "│")
|
|
153
|
+
lines.append("│" + "│".join(row2_parts) + "│")
|
|
154
|
+
|
|
155
|
+
term = layout.get("terminal", {})
|
|
156
|
+
if term.get("visible", True):
|
|
157
|
+
sel = "● " if self._selected == "terminal" else " "
|
|
158
|
+
lines.append("├" + "─" * w + "┤")
|
|
159
|
+
lines.append("│" + f"{sel}TERMINAL ({term.get('position', 'bottom')})".center(w) + "│")
|
|
160
|
+
lines.append("└" + "─" * w + "┘")
|
|
161
|
+
self.update("\n".join(lines))
|
|
162
|
+
except Exception:
|
|
163
|
+
self.update("Preview unavailable")
|
|
164
|
+
|
|
165
|
+
def on_click(self, event):
|
|
166
|
+
try:
|
|
167
|
+
event.stop()
|
|
168
|
+
event.prevent_default()
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
try:
|
|
172
|
+
canonical_ids = ["sidebar", "chat", "code_editor", "terminal", "header"]
|
|
173
|
+
cur = self._selected
|
|
174
|
+
if cur in ("editor", "code_editor"):
|
|
175
|
+
cur = "code_editor"
|
|
176
|
+
elif cur in ("file_explorer", "sidebar"):
|
|
177
|
+
cur = "sidebar"
|
|
178
|
+
if cur in canonical_ids:
|
|
179
|
+
idx = canonical_ids.index(cur)
|
|
180
|
+
self._selected = canonical_ids[(idx + 1) % len(canonical_ids)]
|
|
181
|
+
else:
|
|
182
|
+
self._selected = "sidebar"
|
|
183
|
+
self.refresh_preview()
|
|
184
|
+
try:
|
|
185
|
+
if hasattr(self.screen, "_on_preview_select"):
|
|
186
|
+
self.screen._on_preview_select(self._selected)
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
class CustomizationPanel(Screen):
|
|
193
|
+
allow_select = False
|
|
194
|
+
CSS = """
|
|
195
|
+
CustomizationPanel { align: center middle; background: $app-background 70%; }
|
|
196
|
+
#cust-box { width: 98; max-width: 98%; height: 92%; max-height: 96%; background: $surface; border: round $border; padding: 0; layout: vertical; overflow: hidden; }
|
|
197
|
+
#cust-box Button { min-height: 3; height: 3; min-width: 10; padding: 0 1; }
|
|
198
|
+
#cust-box Button.-style-default, #cust-box Button:ansi.-style-default { min-height: 3; height: 3; min-width: 10; padding: 0 1; }
|
|
199
|
+
#cust-titlebar { height: 3; min-height: 3; padding: 0 2; border-bottom: solid $border; layout: horizontal; align: center middle; }
|
|
200
|
+
#cust-title { width: 1fr; text-style: bold; color: $text; }
|
|
201
|
+
#cust-subtitle { width: auto; color: $text-faint; padding-right: 1; }
|
|
202
|
+
#cust-close { min-width: 3; width: 3; height: 1; min-height: 1; }
|
|
203
|
+
#cust-disabled { height: auto; padding: 2 2; align: center middle; }
|
|
204
|
+
.cust-disabled-msg { color: $warning; text-style: bold; height: auto; text-align: center; }
|
|
205
|
+
.cust-disabled-hint { color: $text-faint; height: auto; text-align: center; padding-top: 1; }
|
|
206
|
+
#cust-disabled-actions { height: auto; padding-top: 1; layout: horizontal; align: center middle; }
|
|
207
|
+
#cust-disabled-actions Button { margin: 0 1; min-width: 10; padding: 0 1; }
|
|
208
|
+
#cust-body { height: 1fr; min-height: 0; layout: horizontal; }
|
|
209
|
+
#cust-sidebar { width: 28; min-width: 24; height: 100%; border-right: solid $border; layout: vertical; background: $surface; }
|
|
210
|
+
#cust-search { height: 3; min-height: 3; padding: 0 1; border-bottom: solid $border; }
|
|
211
|
+
#cust-search Input { width: 1fr; height: 3; background: $app-background; border: round $border; }
|
|
212
|
+
#cust-search Input:focus { border: round $accent; }
|
|
213
|
+
#cust-sidebar-inner { width: 100%; height: 1fr; overflow-y: auto; scrollbar-gutter: stable; }
|
|
214
|
+
.cust-nav-section { color: $text-faint; height: 1; padding: 1 1 0 1; text-style: bold; }
|
|
215
|
+
.cust-nav-btn { width: 100%; height: 1; min-height: 1; padding: 0 1; text-align: left; background: transparent; border: none; color: $text-muted; }
|
|
216
|
+
.cust-nav-btn-sel { background: $accent 14%; color: $accent; text-style: bold; border-left: thick $accent; }
|
|
217
|
+
#cust-content { width: 1fr; height: 1fr; min-height: 10; padding: 1 2; overflow-y: auto; overflow-x: auto; scrollbar-gutter: stable; background: $surface; }
|
|
218
|
+
.cust-card { background: $surface; border: round $border; padding: 1 2; margin-bottom: 1; width: 100%; height: auto; min-height: 6; }
|
|
219
|
+
.cust-card-title { color: $text; text-style: bold; height: 1; }
|
|
220
|
+
.cust-card-desc { color: $text-faint; height: auto; padding-bottom: 1; }
|
|
221
|
+
.cust-label { color: $text-muted; height: 1; min-height: 1; width: auto; min-width: 6; padding-right: 1; }
|
|
222
|
+
.cust-hint { color: $text-faint; height: auto; min-height: 1; padding: 0 0 1 0; }
|
|
223
|
+
.cust-row { height: auto; min-height: 3; layout: horizontal; align: center middle; padding: 0; margin-bottom: 1; width: 100%; overflow-x: auto; }
|
|
224
|
+
.cust-row Button { margin-right: 1; min-width: 10; width: auto; height: 3; min-height: 3; padding: 0 1; }
|
|
225
|
+
.cust-row Input { width: 10; min-width: 6; height: 3; min-height: 3; margin-right: 1; }
|
|
226
|
+
.cust-row Select { width: 18; min-width: 12; height: 3; min-height: 3; margin-right: 1; }
|
|
227
|
+
.cust-field-row { height: auto; min-height: 3; layout: horizontal; align: center middle; padding: 0; margin-bottom: 1; width: 100%; }
|
|
228
|
+
.cust-field-row Input { width: 1fr; min-width: 8; height: 3; min-height: 3; margin-right: 1; }
|
|
229
|
+
.cust-field-row Select { width: 1fr; min-width: 10; height: 3; min-height: 3; margin-right: 1; }
|
|
230
|
+
.cust-field-row Button { height: 3; min-height: 3; min-width: 10; padding: 0 1; }
|
|
231
|
+
.cust-visibility-row { height: auto; min-height: 2; layout: horizontal; align: center middle; border-bottom: solid $border 12%; padding: 1 0; }
|
|
232
|
+
.cust-visibility-label { width: 1fr; color: $text; }
|
|
233
|
+
.cust-drag-row { height: auto; min-height: 3; layout: horizontal; align: center middle; border-bottom: solid $border 60%; padding: 0 1; margin-bottom: 1; }
|
|
234
|
+
.cust-drag-label { width: 1fr; color: $text; }
|
|
235
|
+
#cust-preview { width: 100%; height: auto; padding: 0; background: $surface; color: $text-muted; margin-bottom: 1; }
|
|
236
|
+
#cust-actions { height: 4; min-height: 4; padding: 0 2; border-top: solid $border; layout: horizontal; align: center middle; background: $surface; }
|
|
237
|
+
#cust-actions-left { width: 1fr; layout: horizontal; }
|
|
238
|
+
#cust-actions-left Button { margin-right: 1; min-width: 12; height: 3; }
|
|
239
|
+
#cust-actions-right Button { margin-left: 1; min-width: 10; height: 3; }
|
|
240
|
+
#cust-status { height: 1; color: $text-faint; padding: 0 2; }
|
|
241
|
+
#cust-error { height: auto; color: $error; padding: 1 2; display: none; background: $surface; border: round $error; margin: 0 2; }
|
|
242
|
+
#cust-error.show { display: block; }
|
|
243
|
+
#cust-hint { color: $text-faint; height: 1; padding: 0 2; }
|
|
244
|
+
.cct-compact #cust-body { layout: vertical; }
|
|
245
|
+
.cct-compact #cust-sidebar { width: 100%; height: auto; max-height: 12; border-right: none; border-bottom: solid $border; }
|
|
246
|
+
.cct-compact #cust-content { width: 100%; height: 1fr; min-height: 14; }
|
|
247
|
+
.cct-compact .cust-row { layout: vertical; height: auto; }
|
|
248
|
+
.cct-compact .cust-row Button { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
249
|
+
.cct-compact .cust-row Input { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
250
|
+
.cct-compact .cust-row Select { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
251
|
+
.cct-compact .cust-field-row { layout: vertical; height: auto; }
|
|
252
|
+
.cct-compact .cust-field-row Input { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
253
|
+
.cct-compact .cust-field-row Select { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
254
|
+
.cct-compact .cust-field-row Button { width: 100%; margin-right: 0; margin-bottom: 1; }
|
|
255
|
+
.cct-compact .cust-visibility-row { layout: vertical; height: auto; }
|
|
256
|
+
.cct-compact .cust-visibility-row Switch { margin-top: 1; }
|
|
257
|
+
.cct-compact .cust-drag-row { layout: vertical; height: auto; padding: 1 1; }
|
|
258
|
+
.cct-compact .cust-drag-row Button { width: 100%; margin: 1 0 0 0; }
|
|
259
|
+
.cct-compact .cust-drag-label { width: 100%; margin-bottom: 1; }
|
|
260
|
+
.cct-compact #cust-actions { layout: vertical; height: auto; padding: 1 2; }
|
|
261
|
+
.cct-compact #cust-actions-left { width: 100%; layout: vertical; }
|
|
262
|
+
.cct-compact #cust-actions-left Button { width: 100%; margin: 0 0 1 0; }
|
|
263
|
+
.cct-compact #cust-actions-right { width: 100%; layout: vertical; margin-top: 1; }
|
|
264
|
+
.cct-compact #cust-actions-right Button { width: 100%; margin: 0; }
|
|
265
|
+
.cct-compact #cust-preview { height: auto; min-height: 8; }
|
|
266
|
+
"""
|
|
267
|
+
BINDINGS = [Binding("escape", "cancel", "Cancel")]
|
|
268
|
+
|
|
269
|
+
def __init__(self):
|
|
270
|
+
super().__init__()
|
|
271
|
+
self._manager = cust.get_manager() if cust else None
|
|
272
|
+
self._selected_panel = "sidebar"
|
|
273
|
+
self._nav_state = "overview"
|
|
274
|
+
self._search_query = ""
|
|
275
|
+
self._last_search_query = ""
|
|
276
|
+
self._status_timer = None
|
|
277
|
+
self._size_panel = "sidebar"
|
|
278
|
+
self._pos_panel = "sidebar"
|
|
279
|
+
self._dock_panel = "sidebar"
|
|
280
|
+
|
|
281
|
+
def compose(self):
|
|
282
|
+
try:
|
|
283
|
+
from .. import extensions as _ext
|
|
284
|
+
if not _ext.is_enabled("customization"):
|
|
285
|
+
with Vertical(id="cust-box"):
|
|
286
|
+
with Horizontal(id="cust-titlebar"):
|
|
287
|
+
yield Static("CAT Customization", id="cust-title")
|
|
288
|
+
yield Button("✕", id="cust-close", classes="cct-ctrl")
|
|
289
|
+
with Vertical(id="cust-disabled"):
|
|
290
|
+
yield Static("Customization is disabled.", classes="cust-disabled-msg")
|
|
291
|
+
yield Static("Enable CAT Customization in Extensions to edit layout & appearance.", classes="cust-disabled-hint")
|
|
292
|
+
with Horizontal(id="cust-disabled-actions"):
|
|
293
|
+
yield Button("Open Extensions", id="cust-open-ext", variant="primary")
|
|
294
|
+
yield Button("Close", id="cust-close2")
|
|
295
|
+
return
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
with Vertical(id="cust-box"):
|
|
299
|
+
with Horizontal(id="cust-titlebar"):
|
|
300
|
+
yield Static("CAT Customization", id="cust-title")
|
|
301
|
+
yield Static("explicit apply", id="cust-subtitle")
|
|
302
|
+
yield Button("✕", id="cust-close", classes="cct-ctrl")
|
|
303
|
+
yield Static("", id="cust-error")
|
|
304
|
+
with Horizontal(id="cust-body"):
|
|
305
|
+
with Vertical(id="cust-sidebar"):
|
|
306
|
+
with Horizontal(id="cust-search"):
|
|
307
|
+
yield Input(placeholder="Filter…", id="cust-search-input")
|
|
308
|
+
yield ScrollableContainer(id="cust-sidebar-inner")
|
|
309
|
+
with ScrollableContainer(id="cust-content"):
|
|
310
|
+
pass
|
|
311
|
+
yield Static("", id="cust-status")
|
|
312
|
+
with Horizontal(id="cust-actions"):
|
|
313
|
+
with Horizontal(id="cust-actions-left"):
|
|
314
|
+
yield Button("Reset Current", id="cust-reset-current", classes="cct-btn-sm")
|
|
315
|
+
yield Button("Reset All", id="cust-reset-all", variant="error", classes="cct-btn-sm")
|
|
316
|
+
with Horizontal(id="cust-actions-right"):
|
|
317
|
+
yield Button("Close", id="cust-close3", variant="primary", classes="cct-btn-sm")
|
|
318
|
+
yield Static("", id="cust-hint")
|
|
319
|
+
|
|
320
|
+
def on_mount(self):
|
|
321
|
+
if self.size.width < 88 or self.size.height < 28:
|
|
322
|
+
try:
|
|
323
|
+
self.query_one("#cust-box").add_class("cct-compact")
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
self.call_after_refresh(self._rebuild_nav)
|
|
327
|
+
self.call_after_refresh(self._render_section_view)
|
|
328
|
+
try:
|
|
329
|
+
self.query_one("#cust-search-input", Input).focus()
|
|
330
|
+
except Exception:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
def on_resize(self, event):
|
|
334
|
+
try:
|
|
335
|
+
want = self.size.width < 88 or self.size.height < 28
|
|
336
|
+
box = self.query_one("#cust-box")
|
|
337
|
+
if want != box.has_class("cct-compact"):
|
|
338
|
+
box.set_class(want, "cct-compact")
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
def _rebuild_nav(self):
|
|
343
|
+
try:
|
|
344
|
+
nav = self.query_one("#cust-sidebar-inner", ScrollableContainer)
|
|
345
|
+
# lightweight: update existing buttons' selected state without full rebuild if possible
|
|
346
|
+
# If nav is empty (first mount), do full rebuild
|
|
347
|
+
if len(list(nav.children)) == 0:
|
|
348
|
+
q = self._search_query.lower().strip()
|
|
349
|
+
for sec, items in _NAV_SECTIONS:
|
|
350
|
+
vis = [(k,l) for k,l in items if not q or q in k.lower() or q in l.lower()]
|
|
351
|
+
if not vis:
|
|
352
|
+
continue
|
|
353
|
+
try:
|
|
354
|
+
nav.mount(Static(sec, classes="cust-nav-section"))
|
|
355
|
+
except Exception:
|
|
356
|
+
pass
|
|
357
|
+
for k,l in vis:
|
|
358
|
+
btn = Button(l, id=f"cust-nav-{k}", classes="cust-nav-btn")
|
|
359
|
+
if k==self._nav_state:
|
|
360
|
+
btn.add_class("cust-nav-btn-sel")
|
|
361
|
+
try:
|
|
362
|
+
nav.mount(btn)
|
|
363
|
+
except Exception:
|
|
364
|
+
pass
|
|
365
|
+
else:
|
|
366
|
+
# update selected state only
|
|
367
|
+
for btn in nav.query(Button):
|
|
368
|
+
if btn.id and btn.id.startswith("cust-nav-"):
|
|
369
|
+
key = btn.id.replace("cust-nav-","")
|
|
370
|
+
btn.set_class(key==self._nav_state, "cust-nav-btn-sel")
|
|
371
|
+
# handle search filtering: only rebuild if search query changed and is active, or was cleared
|
|
372
|
+
cur_q = self._search_query.lower().strip()
|
|
373
|
+
last_q = getattr(self, "_last_search_query", "")
|
|
374
|
+
if cur_q != last_q and (cur_q or last_q):
|
|
375
|
+
self._last_search_query = cur_q
|
|
376
|
+
nav.remove_children()
|
|
377
|
+
for sec, items in _NAV_SECTIONS:
|
|
378
|
+
vis = [(k,l) for k,l in items if not cur_q or cur_q in k.lower() or cur_q in l.lower()]
|
|
379
|
+
if not vis:
|
|
380
|
+
continue
|
|
381
|
+
nav.mount(Static(sec, classes="cust-nav-section"))
|
|
382
|
+
for k,l in vis:
|
|
383
|
+
btn = Button(l, id=f"cust-nav-{k}", classes="cust-nav-btn")
|
|
384
|
+
if k==self._nav_state:
|
|
385
|
+
btn.add_class("cust-nav-btn-sel")
|
|
386
|
+
nav.mount(btn)
|
|
387
|
+
except Exception:
|
|
388
|
+
return
|
|
389
|
+
try:
|
|
390
|
+
self.query_one("#cust-hint", Static).update(_HINTS.get(self._nav_state, "Esc to close"))
|
|
391
|
+
except Exception:
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
def _set_nav(self, key):
|
|
395
|
+
self._nav_state = key
|
|
396
|
+
self._rebuild_nav()
|
|
397
|
+
self._render_section_view()
|
|
398
|
+
|
|
399
|
+
def _render_section_view(self):
|
|
400
|
+
try:
|
|
401
|
+
c = self.query_one("#cust-content", ScrollableContainer)
|
|
402
|
+
c.remove_children()
|
|
403
|
+
except Exception:
|
|
404
|
+
return
|
|
405
|
+
h = getattr(self, f"_render_{self._nav_state}", None)
|
|
406
|
+
if h:
|
|
407
|
+
try:
|
|
408
|
+
h(c)
|
|
409
|
+
except Exception as e:
|
|
410
|
+
try:
|
|
411
|
+
c.mount(Static(f"Error: {e}", classes="cust-hint"))
|
|
412
|
+
except Exception:
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
def _show_status(self, msg, style="success"):
|
|
416
|
+
try:
|
|
417
|
+
lbl = self.query_one("#cust-status", Static)
|
|
418
|
+
col = _hex(style) if style in ("success","warning","error") else _hex("success")
|
|
419
|
+
lbl.update(f"[{col}]{msg}[/]")
|
|
420
|
+
except Exception:
|
|
421
|
+
pass
|
|
422
|
+
def _show_error(self, msg):
|
|
423
|
+
try:
|
|
424
|
+
lbl = self.query_one("#cust-error", Static)
|
|
425
|
+
lbl.update(msg)
|
|
426
|
+
lbl.add_class("show")
|
|
427
|
+
self.set_timer(4, lambda: lbl.remove_class("show"))
|
|
428
|
+
except Exception:
|
|
429
|
+
pass
|
|
430
|
+
def _refresh_all(self):
|
|
431
|
+
self.call_after_refresh(self._render_section_view)
|
|
432
|
+
try:
|
|
433
|
+
for p in self.query(_Preview):
|
|
434
|
+
p.refresh_preview()
|
|
435
|
+
except Exception:
|
|
436
|
+
pass
|
|
437
|
+
def _on_preview_select(self, comp_id):
|
|
438
|
+
self._selected_panel = comp_id
|
|
439
|
+
self._pos_panel = comp_id
|
|
440
|
+
self._size_panel = comp_id
|
|
441
|
+
self._dock_panel = comp_id
|
|
442
|
+
|
|
443
|
+
# In-place updates for preview & overview/editor without tearing down the DOM:
|
|
444
|
+
try:
|
|
445
|
+
for hint in self.query(".cust-hint"):
|
|
446
|
+
if self._manager:
|
|
447
|
+
layout = self._manager.get_layout()
|
|
448
|
+
sel = layout.get(comp_id, {})
|
|
449
|
+
if self._nav_state == "editor":
|
|
450
|
+
hint.update(f"Selected: [b]{comp_id}[/]")
|
|
451
|
+
else:
|
|
452
|
+
hint.update(f"Selected: [b]{comp_id}[/] · {sel.get('position','?')} · {'Visible' if sel.get('visible',True) else 'Hidden'} · w:{sel.get('width','auto')} h:{sel.get('height','auto')}")
|
|
453
|
+
except Exception:
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
for pid in ("sidebar", "chat", "code_editor", "terminal"):
|
|
458
|
+
b = _safe_query(self, f"#cust-sel-{pid}", Button)
|
|
459
|
+
if b:
|
|
460
|
+
is_sel = (pid == comp_id or (pid == "code_editor" and comp_id == "editor") or (pid == "sidebar" and comp_id == "file_explorer"))
|
|
461
|
+
b.variant = "primary" if is_sel else "default"
|
|
462
|
+
except Exception:
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
try:
|
|
466
|
+
for p in self.query(_Preview):
|
|
467
|
+
if p._selected != comp_id:
|
|
468
|
+
p._selected = comp_id
|
|
469
|
+
p.refresh_preview()
|
|
470
|
+
except Exception:
|
|
471
|
+
pass
|
|
472
|
+
|
|
473
|
+
if self._nav_state in ("layout", "position", "size"):
|
|
474
|
+
self.call_after_refresh(self._render_section_view)
|
|
475
|
+
|
|
476
|
+
def _live_apply(self):
|
|
477
|
+
# Refresh internal preview
|
|
478
|
+
try:
|
|
479
|
+
for p in self.query(_Preview):
|
|
480
|
+
p.refresh_preview()
|
|
481
|
+
except Exception:
|
|
482
|
+
pass
|
|
483
|
+
# Immediately update live UI underneath (live preview)
|
|
484
|
+
try:
|
|
485
|
+
if hasattr(self.app, "_apply_customization_layout"):
|
|
486
|
+
self.app._apply_customization_layout()
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
489
|
+
|
|
490
|
+
def _card(self, parent, title, desc=None):
|
|
491
|
+
card = Vertical(classes="cust-card")
|
|
492
|
+
parent.mount(card)
|
|
493
|
+
card.mount(Static(title, classes="cust-card-title"))
|
|
494
|
+
if desc:
|
|
495
|
+
card.mount(Static(desc, classes="cust-card-desc"))
|
|
496
|
+
return card
|
|
497
|
+
|
|
498
|
+
def _render_overview(self, container):
|
|
499
|
+
card = self._card(container, "Overview", "Select a panel then edit in its section · explicit Apply only")
|
|
500
|
+
if self._manager:
|
|
501
|
+
layout = self._manager.get_layout()
|
|
502
|
+
sel = layout.get(self._selected_panel, {})
|
|
503
|
+
card.mount(Static(f"Selected: [b]{self._selected_panel}[/] · {sel.get('position','?')} · {'Visible' if sel.get('visible',True) else 'Hidden'} · w:{sel.get('width','auto')} h:{sel.get('height','auto')}", classes="cust-hint"))
|
|
504
|
+
card.mount(_Preview(self._manager, selected=self._selected_panel))
|
|
505
|
+
row = Horizontal(classes="cust-row")
|
|
506
|
+
card.mount(row)
|
|
507
|
+
for pid, label in [("sidebar", "Sidebar"), ("chat", "Chat"), ("code_editor", "Editor"), ("terminal", "Terminal")]:
|
|
508
|
+
b = Button(label, id=f"cust-sel-{pid}", classes="cct-btn-sm")
|
|
509
|
+
if pid == self._selected_panel or (pid == "code_editor" and self._selected_panel == "editor") or (pid == "sidebar" and self._selected_panel == "file_explorer"):
|
|
510
|
+
b.variant = "primary"
|
|
511
|
+
row.mount(b)
|
|
512
|
+
|
|
513
|
+
def _render_layout(self, container):
|
|
514
|
+
card = self._card(container, "Order", "Use ↑ ↓ to reorder · click to apply")
|
|
515
|
+
if self._manager:
|
|
516
|
+
ordered = self._manager.get_ordered_components()
|
|
517
|
+
seen = set()
|
|
518
|
+
idx = 0
|
|
519
|
+
for c in ordered:
|
|
520
|
+
cid = c["id"]
|
|
521
|
+
canon = "code_editor" if cid in ("editor", "code_editor") else ("sidebar" if cid in ("sidebar", "file_explorer") else cid)
|
|
522
|
+
if canon in seen:
|
|
523
|
+
continue
|
|
524
|
+
seen.add(canon)
|
|
525
|
+
idx += 1
|
|
526
|
+
r = Horizontal(classes="cust-drag-row")
|
|
527
|
+
card.mount(r)
|
|
528
|
+
disp_name = cid.replace("_", " ").title()
|
|
529
|
+
r.mount(Static(f"{idx}. {disp_name} ({c.get('position','?')})", classes="cust-drag-label"))
|
|
530
|
+
r.mount(Button("↑", id=f"cust-order-up-{cid}", classes="cct-btn-sm"))
|
|
531
|
+
r.mount(Button("↓", id=f"cust-order-down-{cid}", classes="cct-btn-sm"))
|
|
532
|
+
r.mount(Button("Hide" if c.get("visible",True) else "Show", id=f"cust-order-vis-{cid}", classes="cct-btn-sm"))
|
|
533
|
+
|
|
534
|
+
def _render_position(self, container):
|
|
535
|
+
card = self._card(container, "Position", "Choose panel then choose position · click to apply (no hover)")
|
|
536
|
+
row = Horizontal(classes="cust-row")
|
|
537
|
+
card.mount(row)
|
|
538
|
+
row.mount(Static("Panel:", classes="cust-label"))
|
|
539
|
+
for pid, label in [("sidebar", "Sidebar"), ("chat", "Chat"), ("code_editor", "Editor"), ("terminal", "Terminal")]:
|
|
540
|
+
b = Button(label, id=f"cust-pospick-{pid}", classes="cct-btn-sm")
|
|
541
|
+
if pid == self._pos_panel or (pid == "code_editor" and self._pos_panel == "editor") or (pid == "sidebar" and self._pos_panel == "file_explorer"):
|
|
542
|
+
b.variant = "primary"
|
|
543
|
+
row.mount(b)
|
|
544
|
+
cur = self._manager.get_layout().get(self._pos_panel, {}).get("position","left") if self._manager else "left"
|
|
545
|
+
card.mount(Static(f"Current: [b]{cur}[/] · Selected: [b]{self._pos_panel}[/]", classes="cust-hint"))
|
|
546
|
+
row2 = Horizontal(classes="cust-row")
|
|
547
|
+
card.mount(row2)
|
|
548
|
+
for pos in _POSITION_OPTS:
|
|
549
|
+
b = Button(pos.title(), id=f"cust-pos-{pos}", classes="cct-btn-sm")
|
|
550
|
+
if pos == cur:
|
|
551
|
+
b.variant = "primary"
|
|
552
|
+
row2.mount(b)
|
|
553
|
+
|
|
554
|
+
# header extra
|
|
555
|
+
card2 = self._card(container, "Header Alignment", "")
|
|
556
|
+
hdr = self._manager.get_layout().get("header", {}) if self._manager else {}
|
|
557
|
+
for key,opts in [("logo_position",["left","center","right"]), ("title_position",["left","center","right"])]:
|
|
558
|
+
cur2 = hdr.get(key,"left")
|
|
559
|
+
card2.mount(Static(key.replace("_"," ").title(), classes="cust-label"))
|
|
560
|
+
r = Horizontal(classes="cust-row")
|
|
561
|
+
card2.mount(r)
|
|
562
|
+
for opt in opts:
|
|
563
|
+
b = Button(opt.title(), id=f"cust-hdr-{key}-{opt}", classes="cct-btn-sm")
|
|
564
|
+
if opt == cur2:
|
|
565
|
+
b.variant = "primary"
|
|
566
|
+
r.mount(b)
|
|
567
|
+
|
|
568
|
+
def _render_size(self, container):
|
|
569
|
+
card = self._card(container, "Size", "Choose panel then set W/H · Apply to save")
|
|
570
|
+
row = Horizontal(classes="cust-row")
|
|
571
|
+
card.mount(row)
|
|
572
|
+
row.mount(Static("Panel:", classes="cust-label"))
|
|
573
|
+
for pid, label in [("sidebar", "Sidebar"), ("chat", "Chat"), ("code_editor", "Editor"), ("terminal", "Terminal")]:
|
|
574
|
+
b = Button(label, id=f"cust-sizepick-{pid}", classes="cct-btn-sm")
|
|
575
|
+
if pid == self._size_panel or (pid == "code_editor" and self._size_panel == "editor") or (pid == "sidebar" and self._size_panel == "file_explorer"):
|
|
576
|
+
b.variant = "primary"
|
|
577
|
+
row.mount(b)
|
|
578
|
+
if self._manager:
|
|
579
|
+
comp = self._manager.get_layout().get(self._size_panel, {})
|
|
580
|
+
constr = COMPONENT_CONSTRAINTS.get(self._size_panel, {})
|
|
581
|
+
card.mount(Static(f"Range {constr.get('min_width',12)}–{constr.get('max_width',80)} cols", classes="cust-hint"))
|
|
582
|
+
row2 = Horizontal(classes="cust-row")
|
|
583
|
+
card.mount(row2)
|
|
584
|
+
row2.mount(Static("W:", classes="cust-label"))
|
|
585
|
+
row2.mount(Input(value=str(comp.get("width") or ""), placeholder="auto", id=f"cust-w-{self._size_panel}"))
|
|
586
|
+
row2.mount(Static("H:", classes="cust-label"))
|
|
587
|
+
row2.mount(Input(value=str(comp.get("height") or ""), placeholder="auto", id=f"cust-h-{self._size_panel}"))
|
|
588
|
+
row2.mount(Button("Apply", id=f"cust-size-apply-{self._size_panel}", variant="primary", classes="cct-btn-sm"))
|
|
589
|
+
quick = Horizontal(classes="cust-row")
|
|
590
|
+
card.mount(quick)
|
|
591
|
+
quick.mount(Static("Quick:", classes="cust-label"))
|
|
592
|
+
for pct in [20,30,40]:
|
|
593
|
+
quick.mount(Button(f"{pct}%", id=f"cust-size-pct-{self._size_panel}-{pct}", classes="cct-btn-sm"))
|
|
594
|
+
quick.mount(Button("Auto", id=f"cust-size-auto-{self._size_panel}", classes="cct-btn-sm"))
|
|
595
|
+
|
|
596
|
+
def _render_docking(self, container):
|
|
597
|
+
card = self._card(container, "Docking", "Explicit click to set dock state")
|
|
598
|
+
row = Horizontal(classes="cust-row")
|
|
599
|
+
card.mount(row)
|
|
600
|
+
row.mount(Static("Panel:", classes="cust-label"))
|
|
601
|
+
for pid, label in [("sidebar", "Sidebar"), ("chat", "Chat"), ("code_editor", "Editor"), ("terminal", "Terminal")]:
|
|
602
|
+
b = Button(label, id=f"cust-dockpick-{pid}", classes="cct-btn-sm")
|
|
603
|
+
if pid == self._dock_panel or (pid == "code_editor" and self._dock_panel == "editor") or (pid == "sidebar" and self._dock_panel == "file_explorer"):
|
|
604
|
+
b.variant = "primary"
|
|
605
|
+
row.mount(b)
|
|
606
|
+
cur = self._manager.get_layout().get(self._dock_panel, {}).get("dock_state","docked") if self._manager else "docked"
|
|
607
|
+
card.mount(Static(f"Current: [b]{cur}[/]", classes="cust-hint"))
|
|
608
|
+
row2 = Horizontal(classes="cust-row")
|
|
609
|
+
card.mount(row2)
|
|
610
|
+
for opt in ["docked","floating"]:
|
|
611
|
+
b = Button(opt.title(), id=f"cust-dock-{self._dock_panel}-{opt}", classes="cct-btn-sm")
|
|
612
|
+
if opt == cur:
|
|
613
|
+
b.variant = "primary"
|
|
614
|
+
row2.mount(b)
|
|
615
|
+
|
|
616
|
+
def _render_appearance(self, container):
|
|
617
|
+
card = self._card(container, "Appearance", "Jump to sections · all edits require click")
|
|
618
|
+
row = Horizontal(classes="cust-row")
|
|
619
|
+
card.mount(row)
|
|
620
|
+
for sec,label in [("buttons","Buttons"),("icons","Icons"),("header","Header"),("menu","Menu"),("spacing","Spacing")]:
|
|
621
|
+
row.mount(Button(label, id=f"cust-jump-{sec}", classes="cct-btn-sm"))
|
|
622
|
+
|
|
623
|
+
def _render_buttons(self, container):
|
|
624
|
+
if not self._manager: return
|
|
625
|
+
btn = self._manager.get_styles().get("buttons", {})
|
|
626
|
+
card = self._card(container, "Buttons", f"Current {btn.get('shape','rounded')} · {btn.get('size','medium')}")
|
|
627
|
+
card.mount(Static("Shape", classes="cust-label"))
|
|
628
|
+
row = Horizontal(classes="cust-row")
|
|
629
|
+
card.mount(row)
|
|
630
|
+
for s in _BUTTON_SHAPES[:5]:
|
|
631
|
+
b = Button(s.title(), id=f"cust-btn-shape-{s}", classes="cct-btn-sm")
|
|
632
|
+
if s == btn.get("shape"): b.variant = "primary"
|
|
633
|
+
row.mount(b)
|
|
634
|
+
row_b = Horizontal(classes="cust-row")
|
|
635
|
+
card.mount(row_b)
|
|
636
|
+
for s in _BUTTON_SHAPES[5:]:
|
|
637
|
+
b = Button(s.title(), id=f"cust-btn-shape-{s}", classes="cct-btn-sm")
|
|
638
|
+
if s == btn.get("shape"): b.variant = "primary"
|
|
639
|
+
row_b.mount(b)
|
|
640
|
+
card.mount(Static("Size", classes="cust-label"))
|
|
641
|
+
row2 = Horizontal(classes="cust-row")
|
|
642
|
+
card.mount(row2)
|
|
643
|
+
for sz in _BUTTON_SIZES:
|
|
644
|
+
b = Button(sz.title(), id=f"cust-btn-size-{sz}", classes="cct-btn-sm")
|
|
645
|
+
if sz == btn.get("size"): b.variant = "primary"
|
|
646
|
+
row2.mount(b)
|
|
647
|
+
row3 = Horizontal(classes="cust-row")
|
|
648
|
+
card.mount(row3)
|
|
649
|
+
row3.mount(Static("Radius:", classes="cust-label")); row3.mount(Input(value=str(btn.get("border_radius",4)), id="cust-btn-radius"))
|
|
650
|
+
row3.mount(Static("Padding:", classes="cust-label")); row3.mount(Input(value=str(btn.get("padding",1)), id="cust-btn-padding"))
|
|
651
|
+
row3.mount(Button("Apply", id="cust-btn-apply", variant="primary", classes="cct-btn-sm"))
|
|
652
|
+
|
|
653
|
+
def _render_icons(self, container):
|
|
654
|
+
if not self._manager: return
|
|
655
|
+
icons = self._manager.get_styles().get("icons", {})
|
|
656
|
+
card = self._card(container, "Icons", f"Current {icons.get('size','medium')}")
|
|
657
|
+
row = Horizontal(classes="cust-row")
|
|
658
|
+
card.mount(row)
|
|
659
|
+
for sz in _ICON_SIZES:
|
|
660
|
+
b = Button(sz.title(), id=f"cust-icon-size-{sz}", classes="cct-btn-sm")
|
|
661
|
+
if sz == icons.get("size"): b.variant = "primary"
|
|
662
|
+
row.mount(b)
|
|
663
|
+
row2 = Horizontal(classes="cust-row")
|
|
664
|
+
card.mount(row2)
|
|
665
|
+
row2.mount(Button("Toggle Visibility", id="cust-icon-vis", classes="cct-btn-sm"))
|
|
666
|
+
row2.mount(Button("Reset", id="cust-icon-reset", classes="cct-btn-sm"))
|
|
667
|
+
|
|
668
|
+
def _render_header(self, container):
|
|
669
|
+
if not self._manager: return
|
|
670
|
+
hdr = self._manager.get_layout().get("header", {})
|
|
671
|
+
card = self._card(container, "Header", f"Height: {hdr.get('height',3)} lines")
|
|
672
|
+
row = Horizontal(classes="cust-row")
|
|
673
|
+
card.mount(row)
|
|
674
|
+
row.mount(Static("Height:", classes="cust-label")); row.mount(Input(value=str(hdr.get("height",3)), id="cust-header-height"))
|
|
675
|
+
row.mount(Switch(value=bool(hdr.get("logo_visible",True)), id="cust-header-logo-vis"))
|
|
676
|
+
row.mount(Static("Logo visible", classes="cust-label"))
|
|
677
|
+
row.mount(Button("Apply", id="cust-header-apply", variant="primary", classes="cct-btn-sm"))
|
|
678
|
+
|
|
679
|
+
def _render_menu(self, container):
|
|
680
|
+
if not self._manager: return
|
|
681
|
+
mm = self._manager.get_main_menu()
|
|
682
|
+
card = self._card(container, "Main Menu", "Order & toggles")
|
|
683
|
+
for idx,item in enumerate(mm.get("order", [])):
|
|
684
|
+
r = Horizontal(classes="cust-drag-row")
|
|
685
|
+
card.mount(r)
|
|
686
|
+
r.mount(Static(f"{idx+1}. {item}", classes="cust-drag-label"))
|
|
687
|
+
r.mount(Button("↑", id=f"cust-menu-up-{item}", classes="cct-btn-sm"))
|
|
688
|
+
r.mount(Button("↓", id=f"cust-menu-down-{item}", classes="cct-btn-sm"))
|
|
689
|
+
r.mount(Switch(value=mm.get("visibility", {}).get(item,True), id=f"cust-mmvis-{item}"))
|
|
690
|
+
|
|
691
|
+
def _render_spacing(self, container):
|
|
692
|
+
if not self._manager: return
|
|
693
|
+
styles = self._manager.get_styles()
|
|
694
|
+
card = self._card(container, "Spacing & Borders", "Padding · margin · borders")
|
|
695
|
+
spacing = styles.get("spacing", {})
|
|
696
|
+
row = Horizontal(classes="cust-row")
|
|
697
|
+
card.mount(row)
|
|
698
|
+
row.mount(Static("Padding:", classes="cust-label")); row.mount(Input(value=str(spacing.get("padding",1)), id="cust-spacing-pad"))
|
|
699
|
+
row.mount(Static("Margin:", classes="cust-label")); row.mount(Input(value=str(spacing.get("margin",0)), id="cust-spacing-margin"))
|
|
700
|
+
row.mount(Static("Gap:", classes="cust-label")); row.mount(Input(value=str(spacing.get("gap",1)), id="cust-spacing-gap"))
|
|
701
|
+
row.mount(Button("Apply Spacing", id="cust-spacing-apply", variant="primary", classes="cct-btn-sm"))
|
|
702
|
+
borders = styles.get("borders", {})
|
|
703
|
+
row2 = Horizontal(classes="cust-row")
|
|
704
|
+
card.mount(row2)
|
|
705
|
+
row2.mount(Static("Radius:", classes="cust-label")); row2.mount(Input(value=str(borders.get("radius",0)), id="cust-border-radius"))
|
|
706
|
+
border_opts = [("Solid", "solid"), ("Round", "round"), ("Heavy", "heavy"), ("None", "none")]
|
|
707
|
+
cur_style = str(borders.get("style", "solid")).lower()
|
|
708
|
+
valid_styles = [opt[1] for opt in border_opts]
|
|
709
|
+
if cur_style not in valid_styles:
|
|
710
|
+
cur_style = "solid"
|
|
711
|
+
row2.mount(Select(border_opts, value=cur_style, id="cust-border-style", allow_blank=False))
|
|
712
|
+
row2.mount(Button("Apply Borders", id="cust-border-apply", variant="primary", classes="cct-btn-sm"))
|
|
713
|
+
|
|
714
|
+
def _render_visibility(self, container):
|
|
715
|
+
card = self._card(container, "Visibility", "Toggle any region · explicit switch")
|
|
716
|
+
if not self._manager: return
|
|
717
|
+
layout = self._manager.get_layout()
|
|
718
|
+
panels_to_show = [
|
|
719
|
+
("sidebar", "Sidebar"),
|
|
720
|
+
("chat", "Chat"),
|
|
721
|
+
("code_editor", "Code Editor"),
|
|
722
|
+
("terminal", "Terminal"),
|
|
723
|
+
("header", "Header"),
|
|
724
|
+
]
|
|
725
|
+
for pid, label in panels_to_show:
|
|
726
|
+
comp = layout.get(pid)
|
|
727
|
+
if not comp: continue
|
|
728
|
+
r = Horizontal(classes="cust-visibility-row")
|
|
729
|
+
card.mount(r)
|
|
730
|
+
pos = comp.get("position", "?")
|
|
731
|
+
r.mount(Static(f"{label} ({pos})", classes="cust-visibility-label"))
|
|
732
|
+
r.mount(Switch(value=bool(comp.get("visible",True)), id=f"cust-vis-{pid}"))
|
|
733
|
+
|
|
734
|
+
def _render_profiles(self, container):
|
|
735
|
+
card = self._card(container, "Profiles", "Save / load layouts")
|
|
736
|
+
if not self._manager: return
|
|
737
|
+
pm = self._manager.profile_manager
|
|
738
|
+
active = self._manager.get_config().get("active_profile")
|
|
739
|
+
for name in pm.list_profiles():
|
|
740
|
+
prof = pm.get_profile(name)
|
|
741
|
+
desc = prof.get("description","") if prof else ""
|
|
742
|
+
mark = "★ " if name == active else " "
|
|
743
|
+
r = Horizontal(classes="cust-drag-row")
|
|
744
|
+
card.mount(r)
|
|
745
|
+
r.mount(Static(f"{mark}{name} — {desc}", classes="cust-drag-label"))
|
|
746
|
+
r.mount(Button("Load", id=f"cust-prof-load-{name}", variant="primary" if name!=active else None, classes="cct-btn-sm"))
|
|
747
|
+
r.mount(Button("Delete", id=f"cust-prof-del-{name}", variant="error", classes="cct-btn-sm"))
|
|
748
|
+
row = Horizontal(classes="cust-field-row")
|
|
749
|
+
card.mount(row)
|
|
750
|
+
row.mount(Input(placeholder="New Profile Name", id="cust-prof-new-name"))
|
|
751
|
+
row.mount(Button("Save", id="cust-prof-save", variant="primary", classes="cct-btn-sm"))
|
|
752
|
+
row2 = Horizontal(classes="cust-field-row")
|
|
753
|
+
card.mount(row2)
|
|
754
|
+
row2.mount(Input(placeholder="Old name", id="cust-prof-rename-old"))
|
|
755
|
+
row2.mount(Input(placeholder="New name", id="cust-prof-rename-new"))
|
|
756
|
+
row2.mount(Button("Rename", id="cust-prof-rename", classes="cct-btn-sm"))
|
|
757
|
+
|
|
758
|
+
def _render_editor(self, container):
|
|
759
|
+
card = self._card(container, "Editor", "Visual preview · click preview to cycle")
|
|
760
|
+
if self._manager:
|
|
761
|
+
card.mount(_Preview(self._manager, selected=self._selected_panel))
|
|
762
|
+
card.mount(Static(f"Selected: [b]{self._selected_panel}[/]", classes="cust-hint"))
|
|
763
|
+
row = Horizontal(classes="cust-row")
|
|
764
|
+
for pos in ["left","right","top","bottom","floating","center"]:
|
|
765
|
+
row.mount(Button(pos.title(), id=f"cust-edit-{pos}", classes="cct-btn-sm"))
|
|
766
|
+
card.mount(row)
|
|
767
|
+
row2 = Horizontal(classes="cust-row")
|
|
768
|
+
card.mount(row2)
|
|
769
|
+
row2.mount(Button("Hide", id="cust-edit-hide", classes="cct-btn-sm"))
|
|
770
|
+
row2.mount(Button("Show", id="cust-edit-show", classes="cct-btn-sm"))
|
|
771
|
+
row2.mount(Button("Docked", id="cust-edit-docked", classes="cct-btn-sm"))
|
|
772
|
+
row2.mount(Button("Floating", id="cust-edit-floating", classes="cct-btn-sm"))
|
|
773
|
+
|
|
774
|
+
def _render_advanced(self, container):
|
|
775
|
+
card = self._card(container, "Advanced JSON", "Edit directly · Validate then Save")
|
|
776
|
+
if not self._manager: return
|
|
777
|
+
import json as _json
|
|
778
|
+
slim = {"layout": self._manager.get_config().get("layout"), "styles": self._manager.get_config().get("styles")}
|
|
779
|
+
txt = _json.dumps(slim, indent=2)
|
|
780
|
+
if _HAS_TEXTAREA:
|
|
781
|
+
ta = TextArea(text=txt[:4000], id="cust-adv-json")
|
|
782
|
+
ta.styles.height = 16; ta.styles.min_height = 8
|
|
783
|
+
card.mount(ta)
|
|
784
|
+
else:
|
|
785
|
+
card.mount(Static(txt[:1500], classes="cust-hint"))
|
|
786
|
+
row = Horizontal(classes="cust-row")
|
|
787
|
+
card.mount(row)
|
|
788
|
+
row.mount(Button("Validate", id="cust-adv-validate", classes="cct-btn-sm"))
|
|
789
|
+
row.mount(Button("Save JSON", id="cust-adv-save", variant="primary", classes="cct-btn-sm"))
|
|
790
|
+
|
|
791
|
+
def _render_reset(self, container):
|
|
792
|
+
card = self._card(container, "Reset", "Restore defaults")
|
|
793
|
+
row = Horizontal(classes="cust-row")
|
|
794
|
+
card.mount(row)
|
|
795
|
+
row.mount(Button("Reset Layout", id="cust-reset-layout", classes="cct-btn-sm"))
|
|
796
|
+
row.mount(Button("Reset Panes", id="cust-reset-panes", classes="cct-btn-sm"))
|
|
797
|
+
row2 = Horizontal(classes="cust-row")
|
|
798
|
+
card.mount(row2)
|
|
799
|
+
row2.mount(Button("Reset Appearance", id="cust-reset-appearance", classes="cct-btn-sm"))
|
|
800
|
+
row2.mount(Button("Reset Buttons", id="cust-reset-buttons", classes="cct-btn-sm"))
|
|
801
|
+
row3 = Horizontal(classes="cust-row")
|
|
802
|
+
card.mount(row3)
|
|
803
|
+
row3.mount(Button("Reset Everything", id="cust-reset-everything", variant="error", classes="cct-btn-sm"))
|
|
804
|
+
|
|
805
|
+
def _jump(self, sec):
|
|
806
|
+
self._nav_state = sec
|
|
807
|
+
self._rebuild_nav()
|
|
808
|
+
self._render_section_view()
|
|
809
|
+
|
|
810
|
+
def on_input_changed(self, event):
|
|
811
|
+
if getattr(event.input, "id", "") == "cust-search-input":
|
|
812
|
+
self._search_query = event.value
|
|
813
|
+
self._rebuild_nav()
|
|
814
|
+
def on_input_submitted(self, event):
|
|
815
|
+
if getattr(event.input, "id", "") == "cust-prof-new-name":
|
|
816
|
+
name = event.value.strip()
|
|
817
|
+
if name:
|
|
818
|
+
ok,msg = self._manager.profile_manager.save_profile(name)
|
|
819
|
+
self._show_status(msg, "success" if ok else "error")
|
|
820
|
+
self._refresh_all()
|
|
821
|
+
event.input.value = ""
|
|
822
|
+
def on_button_pressed(self, event):
|
|
823
|
+
bid = event.button.id or ""
|
|
824
|
+
if bid in ("cust-close","cust-close2","cust-close3"):
|
|
825
|
+
self.dismiss(None); return
|
|
826
|
+
if bid=="cust-open-ext":
|
|
827
|
+
try:
|
|
828
|
+
self.dismiss(None)
|
|
829
|
+
from .extensions_panel import ExtensionsPanel
|
|
830
|
+
self.app.push_screen(ExtensionsPanel())
|
|
831
|
+
except Exception:
|
|
832
|
+
self.dismiss(None)
|
|
833
|
+
return
|
|
834
|
+
if bid.startswith("cust-nav-"):
|
|
835
|
+
self._set_nav(bid.replace("cust-nav-","")); return
|
|
836
|
+
if bid.startswith("cust-jump-"):
|
|
837
|
+
self._jump(bid.replace("cust-jump-","")); return
|
|
838
|
+
if bid.startswith("cust-sel-"):
|
|
839
|
+
self._selected_panel = bid.replace("cust-sel-", "")
|
|
840
|
+
self._pos_panel = self._selected_panel
|
|
841
|
+
self._size_panel = self._selected_panel
|
|
842
|
+
self._dock_panel = self._selected_panel
|
|
843
|
+
self._on_preview_select(self._selected_panel)
|
|
844
|
+
return
|
|
845
|
+
if bid.startswith("cust-order-up-"):
|
|
846
|
+
cid = bid.replace("cust-order-up-","")
|
|
847
|
+
try:
|
|
848
|
+
all_ordered = [c["id"] for c in self._manager.get_ordered_components()]
|
|
849
|
+
seen = set()
|
|
850
|
+
unique_ordered = []
|
|
851
|
+
for k in all_ordered:
|
|
852
|
+
canon = "code_editor" if k in ("editor", "code_editor") else ("sidebar" if k in ("sidebar", "file_explorer") else k)
|
|
853
|
+
if canon not in seen:
|
|
854
|
+
seen.add(canon)
|
|
855
|
+
unique_ordered.append(k)
|
|
856
|
+
idx = unique_ordered.index(cid)
|
|
857
|
+
if idx > 0:
|
|
858
|
+
unique_ordered[idx], unique_ordered[idx-1] = unique_ordered[idx-1], unique_ordered[idx]
|
|
859
|
+
ok, msg = self._manager.set_order(unique_ordered)
|
|
860
|
+
self._show_status(msg, "success" if ok else "error")
|
|
861
|
+
self._live_apply()
|
|
862
|
+
self.call_after_refresh(self._render_section_view)
|
|
863
|
+
except Exception as e:
|
|
864
|
+
self._show_error(str(e))
|
|
865
|
+
return
|
|
866
|
+
if bid.startswith("cust-order-down-"):
|
|
867
|
+
cid = bid.replace("cust-order-down-","")
|
|
868
|
+
try:
|
|
869
|
+
all_ordered = [c["id"] for c in self._manager.get_ordered_components()]
|
|
870
|
+
seen = set()
|
|
871
|
+
unique_ordered = []
|
|
872
|
+
for k in all_ordered:
|
|
873
|
+
canon = "code_editor" if k in ("editor", "code_editor") else ("sidebar" if k in ("sidebar", "file_explorer") else k)
|
|
874
|
+
if canon not in seen:
|
|
875
|
+
seen.add(canon)
|
|
876
|
+
unique_ordered.append(k)
|
|
877
|
+
idx = unique_ordered.index(cid)
|
|
878
|
+
if idx < len(unique_ordered) - 1:
|
|
879
|
+
unique_ordered[idx], unique_ordered[idx+1] = unique_ordered[idx+1], unique_ordered[idx]
|
|
880
|
+
ok, msg = self._manager.set_order(unique_ordered)
|
|
881
|
+
self._show_status(msg, "success" if ok else "error")
|
|
882
|
+
self._live_apply()
|
|
883
|
+
self.call_after_refresh(self._render_section_view)
|
|
884
|
+
except Exception as e:
|
|
885
|
+
self._show_error(str(e))
|
|
886
|
+
return
|
|
887
|
+
if bid.startswith("cust-order-vis-"):
|
|
888
|
+
cid = bid.replace("cust-order-vis-","")
|
|
889
|
+
try:
|
|
890
|
+
comp = self._manager.get_component(cid)
|
|
891
|
+
cur_vis = comp.get("visible", True) if comp else True
|
|
892
|
+
ok, msg = self._manager.set_visibility(cid, not cur_vis)
|
|
893
|
+
self._show_status(msg, "success" if ok else "error")
|
|
894
|
+
self._live_apply()
|
|
895
|
+
self.call_after_refresh(self._render_section_view)
|
|
896
|
+
except Exception as e:
|
|
897
|
+
self._show_error(str(e))
|
|
898
|
+
return
|
|
899
|
+
if bid.startswith("cust-pospick-"):
|
|
900
|
+
pid = bid.replace("cust-pospick-","")
|
|
901
|
+
self._pos_panel = pid
|
|
902
|
+
self._show_status(f"Selected {pid}", "success")
|
|
903
|
+
self.call_after_refresh(self._render_section_view)
|
|
904
|
+
return
|
|
905
|
+
if bid.startswith("cust-pos-"):
|
|
906
|
+
try:
|
|
907
|
+
pos = bid.replace("cust-pos-","")
|
|
908
|
+
pid = self._pos_panel
|
|
909
|
+
if pos in _POSITION_OPTS:
|
|
910
|
+
ok,msg=self._manager.set_position(pid,pos)
|
|
911
|
+
self._show_status(msg, "success" if ok else "error")
|
|
912
|
+
try:
|
|
913
|
+
for p in self.query(_Preview):
|
|
914
|
+
p.refresh_preview()
|
|
915
|
+
except Exception:
|
|
916
|
+
pass
|
|
917
|
+
except Exception as e:
|
|
918
|
+
self._show_error(str(e))
|
|
919
|
+
return
|
|
920
|
+
if bid.startswith("cust-hdr-"):
|
|
921
|
+
try:
|
|
922
|
+
rest=bid.replace("cust-hdr-","")
|
|
923
|
+
for key in ["logo_position","title_position","alignment"]:
|
|
924
|
+
if rest.startswith(key+"-"):
|
|
925
|
+
opt=rest[len(key)+1:]
|
|
926
|
+
ok,msg=self._manager.set_header_config(**{key:opt})
|
|
927
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); break
|
|
928
|
+
except Exception as e:
|
|
929
|
+
self._show_error(str(e))
|
|
930
|
+
return
|
|
931
|
+
if bid.startswith("cust-dockpick-"):
|
|
932
|
+
pid = bid.replace("cust-dockpick-","")
|
|
933
|
+
self._dock_panel = pid
|
|
934
|
+
self._show_status(f"Selected {pid}", "success")
|
|
935
|
+
self.call_after_refresh(self._render_section_view)
|
|
936
|
+
return
|
|
937
|
+
if bid.startswith("cust-dock-"):
|
|
938
|
+
try:
|
|
939
|
+
rest=bid.replace("cust-dock-","")
|
|
940
|
+
for pid in _PANEL_IDS+["header"]:
|
|
941
|
+
if rest.startswith(pid+"-"):
|
|
942
|
+
state=rest[len(pid)+1:]
|
|
943
|
+
ok,msg=self._manager.set_dock_state(pid,state)
|
|
944
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); break
|
|
945
|
+
except Exception as e:
|
|
946
|
+
self._show_error(str(e))
|
|
947
|
+
return
|
|
948
|
+
if bid.startswith("cust-sizepick-"):
|
|
949
|
+
pid = bid.replace("cust-sizepick-","")
|
|
950
|
+
self._size_panel = pid
|
|
951
|
+
self._show_status(f"Selected {pid}", "success")
|
|
952
|
+
self.call_after_refresh(self._render_section_view)
|
|
953
|
+
return
|
|
954
|
+
if bid.startswith("cust-size-apply-"):
|
|
955
|
+
pid=bid.replace("cust-size-apply-","")
|
|
956
|
+
try:
|
|
957
|
+
w=_safe_query(self, f"#cust-w-{pid}", Input)
|
|
958
|
+
h=_safe_query(self, f"#cust-h-{pid}", Input)
|
|
959
|
+
ok,msg=self._manager.set_size(pid, (w.value.strip() if w and w.value.strip() else None), (h.value.strip() if h and h.value.strip() else None))
|
|
960
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
961
|
+
except Exception as e:
|
|
962
|
+
self._show_error(str(e))
|
|
963
|
+
return
|
|
964
|
+
if bid.startswith("cust-size-pct-"):
|
|
965
|
+
try:
|
|
966
|
+
rest=bid.replace("cust-size-pct-","")
|
|
967
|
+
for pid in _PANEL_IDS:
|
|
968
|
+
if rest.startswith(pid+"-"):
|
|
969
|
+
pct=rest[len(pid)+1:]
|
|
970
|
+
ok,msg=self._manager.set_size(pid, width=int(pct))
|
|
971
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); break
|
|
972
|
+
except Exception as e:
|
|
973
|
+
self._show_error(str(e))
|
|
974
|
+
return
|
|
975
|
+
if bid.startswith("cust-size-auto-"):
|
|
976
|
+
pid=bid.replace("cust-size-auto-","")
|
|
977
|
+
ok,msg=self._manager.set_size(pid, width="auto")
|
|
978
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
979
|
+
if bid.startswith("cust-btn-shape-"):
|
|
980
|
+
ok,msg=self._manager.style_manager.set_button_style(shape=bid.replace("cust-btn-shape-",""))
|
|
981
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
982
|
+
if bid.startswith("cust-btn-size-"):
|
|
983
|
+
ok,msg=self._manager.style_manager.set_button_style(size=bid.replace("cust-btn-size-",""))
|
|
984
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
985
|
+
if bid=="cust-btn-apply":
|
|
986
|
+
try:
|
|
987
|
+
kwargs={}
|
|
988
|
+
for fid,key in [("cust-btn-radius","border_radius"),("cust-btn-padding","padding")]:
|
|
989
|
+
w=_safe_query(self, f"#{fid}", Input)
|
|
990
|
+
if w and w.value.strip():
|
|
991
|
+
kwargs[key]=int(w.value.strip())
|
|
992
|
+
if kwargs:
|
|
993
|
+
ok,msg=self._manager.style_manager.set_button_style(**kwargs)
|
|
994
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
995
|
+
except Exception as e:
|
|
996
|
+
self._show_error(str(e))
|
|
997
|
+
return
|
|
998
|
+
if bid.startswith("cust-icon-size-"):
|
|
999
|
+
ok,msg=self._manager.style_manager.set_icon_style(size=bid.replace("cust-icon-size-",""))
|
|
1000
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1001
|
+
if bid=="cust-icon-vis":
|
|
1002
|
+
cur=self._manager.get_styles().get("icons", {}).get("visible",True)
|
|
1003
|
+
ok,msg=self._manager.style_manager.set_icon_style(visible=not cur)
|
|
1004
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1005
|
+
if bid=="cust-icon-reset":
|
|
1006
|
+
ok,msg=self._manager.style_manager.set_icon_style(size="medium", visible=True)
|
|
1007
|
+
self._show_status("Icons reset", "success" if ok else "error"); self._live_apply(); return
|
|
1008
|
+
if bid=="cust-header-apply":
|
|
1009
|
+
try:
|
|
1010
|
+
kwargs={}
|
|
1011
|
+
w=_safe_query(self, "#cust-header-height", Input)
|
|
1012
|
+
if w and w.value.strip(): kwargs["height"]=int(w.value.strip())
|
|
1013
|
+
w=_safe_query(self, "#cust-header-logo-vis", Switch)
|
|
1014
|
+
if w is not None: kwargs["logo_visible"]=bool(w.value)
|
|
1015
|
+
ok,msg=self._manager.set_header_config(**kwargs)
|
|
1016
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1017
|
+
except Exception as e:
|
|
1018
|
+
self._show_error(str(e))
|
|
1019
|
+
return
|
|
1020
|
+
if bid.startswith("cust-menu-up-"):
|
|
1021
|
+
item=bid.replace("cust-menu-up-","")
|
|
1022
|
+
try:
|
|
1023
|
+
mm=self._manager.get_main_menu(); order=list(mm.get("order",[])); idx=order.index(item)
|
|
1024
|
+
if idx>0:
|
|
1025
|
+
order[idx],order[idx-1]=order[idx-1],order[idx]
|
|
1026
|
+
ok,msg=self._manager.set_main_menu_config(order=order)
|
|
1027
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1028
|
+
except Exception as e:
|
|
1029
|
+
self._show_error(str(e))
|
|
1030
|
+
return
|
|
1031
|
+
if bid.startswith("cust-menu-down-"):
|
|
1032
|
+
item=bid.replace("cust-menu-down-","")
|
|
1033
|
+
try:
|
|
1034
|
+
mm=self._manager.get_main_menu(); order=list(mm.get("order",[])); idx=order.index(item)
|
|
1035
|
+
if idx < len(order)-1:
|
|
1036
|
+
order[idx],order[idx+1]=order[idx+1],order[idx]
|
|
1037
|
+
ok,msg=self._manager.set_main_menu_config(order=order)
|
|
1038
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1039
|
+
except Exception as e:
|
|
1040
|
+
self._show_error(str(e))
|
|
1041
|
+
return
|
|
1042
|
+
if bid=="cust-spacing-apply":
|
|
1043
|
+
try:
|
|
1044
|
+
kwargs={}
|
|
1045
|
+
for fid,key in [("cust-spacing-pad","padding"),("cust-spacing-margin","margin"),("cust-spacing-gap","gap")]:
|
|
1046
|
+
w=_safe_query(self, f"#{fid}", Input)
|
|
1047
|
+
if w and w.value.strip():
|
|
1048
|
+
kwargs[key]=int(w.value.strip())
|
|
1049
|
+
if kwargs:
|
|
1050
|
+
ok,msg=self._manager.style_manager.set_spacing(**kwargs)
|
|
1051
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1052
|
+
except Exception as e:
|
|
1053
|
+
self._show_error(str(e))
|
|
1054
|
+
return
|
|
1055
|
+
if bid=="cust-border-apply":
|
|
1056
|
+
try:
|
|
1057
|
+
kwargs={}
|
|
1058
|
+
w=_safe_query(self, "#cust-border-radius", Input)
|
|
1059
|
+
if w and w.value.strip(): kwargs["radius"]=int(w.value.strip())
|
|
1060
|
+
w=_safe_query(self, "#cust-border-style", Select)
|
|
1061
|
+
if w and w.value: kwargs["style"]=w.value
|
|
1062
|
+
ok,msg=self._manager.style_manager.set_borders(**kwargs)
|
|
1063
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1064
|
+
except Exception as e:
|
|
1065
|
+
self._show_error(str(e))
|
|
1066
|
+
return
|
|
1067
|
+
if bid.startswith("cust-prof-load-"):
|
|
1068
|
+
ok,msg=self._manager.profile_manager.load_profile(bid.replace("cust-prof-load-",""))
|
|
1069
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1070
|
+
if bid.startswith("cust-prof-del-"):
|
|
1071
|
+
name=bid.replace("cust-prof-del-","")
|
|
1072
|
+
self.app.push_screen(_ConfirmScreen(f"Delete '{name}'?", f"Delete profile '{name}' permanently."), lambda c: self._on_prof_delete(name,c))
|
|
1073
|
+
return
|
|
1074
|
+
if bid=="cust-prof-save":
|
|
1075
|
+
try:
|
|
1076
|
+
inp=_safe_query(self, "#cust-prof-new-name", Input)
|
|
1077
|
+
name=inp.value.strip() if inp else ""
|
|
1078
|
+
if not name:
|
|
1079
|
+
self._show_error("Enter name"); return
|
|
1080
|
+
ok,msg=self._manager.profile_manager.save_profile(name)
|
|
1081
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1082
|
+
if inp: inp.value=""
|
|
1083
|
+
except Exception as e:
|
|
1084
|
+
self._show_error(str(e))
|
|
1085
|
+
return
|
|
1086
|
+
if bid=="cust-prof-rename":
|
|
1087
|
+
try:
|
|
1088
|
+
o=_safe_query(self, "#cust-prof-rename-old", Input)
|
|
1089
|
+
n=_safe_query(self, "#cust-prof-rename-new", Input)
|
|
1090
|
+
old=o.value.strip() if o else ""; new=n.value.strip() if n else ""
|
|
1091
|
+
if not old or not new:
|
|
1092
|
+
self._show_error("Enter both names"); return
|
|
1093
|
+
ok,msg=self._manager.profile_manager.rename_profile(old,new)
|
|
1094
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1095
|
+
except Exception as e:
|
|
1096
|
+
self._show_error(str(e))
|
|
1097
|
+
return
|
|
1098
|
+
if bid.startswith("cust-edit-"):
|
|
1099
|
+
act=bid.replace("cust-edit-","")
|
|
1100
|
+
pid=self._selected_panel
|
|
1101
|
+
if act in ("left","right","top","bottom","floating","center","docked"):
|
|
1102
|
+
ok,msg=self._manager.set_position(pid,act)
|
|
1103
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1104
|
+
elif act=="hide":
|
|
1105
|
+
ok,msg=self._manager.set_visibility(pid,False)
|
|
1106
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1107
|
+
elif act=="show":
|
|
1108
|
+
ok,msg=self._manager.set_visibility(pid,True)
|
|
1109
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1110
|
+
return
|
|
1111
|
+
if bid=="cust-adv-validate":
|
|
1112
|
+
try:
|
|
1113
|
+
ta=_safe_query(self, "#cust-adv-json", TextArea) if _HAS_TEXTAREA else None
|
|
1114
|
+
if ta:
|
|
1115
|
+
import json as _json
|
|
1116
|
+
_json.loads(ta.text)
|
|
1117
|
+
self._show_status("JSON valid", "success")
|
|
1118
|
+
else:
|
|
1119
|
+
self._show_error("Editor not available")
|
|
1120
|
+
except Exception as e:
|
|
1121
|
+
self._show_error(f"Invalid: {e}")
|
|
1122
|
+
return
|
|
1123
|
+
if bid=="cust-adv-save":
|
|
1124
|
+
try:
|
|
1125
|
+
ta=_safe_query(self, "#cust-adv-json", TextArea) if _HAS_TEXTAREA else None
|
|
1126
|
+
if not ta:
|
|
1127
|
+
self._show_error("Editor not available"); return
|
|
1128
|
+
import json as _json
|
|
1129
|
+
data=_json.loads(ta.text)
|
|
1130
|
+
cfg=self._manager.get_config()
|
|
1131
|
+
if "layout" in data: cfg["layout"].update(data["layout"])
|
|
1132
|
+
if "styles" in data:
|
|
1133
|
+
for k,v in data["styles"].items():
|
|
1134
|
+
if isinstance(v,dict) and k in cfg["styles"]: cfg["styles"][k].update(v)
|
|
1135
|
+
else: cfg["styles"][k]=v
|
|
1136
|
+
ok=self._manager._persist(cfg)
|
|
1137
|
+
self._show_status("Saved" if ok else "Failed", "success" if ok else "error"); self._live_apply()
|
|
1138
|
+
except Exception as e:
|
|
1139
|
+
self._show_error(str(e))
|
|
1140
|
+
return
|
|
1141
|
+
if bid in ("cust-reset-layout","cust-reset-current"):
|
|
1142
|
+
ok,msg=self._manager.reset_current_layout()
|
|
1143
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1144
|
+
if bid=="cust-reset-panes":
|
|
1145
|
+
ok,msg=self._manager.reset_pane_positions()
|
|
1146
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1147
|
+
if bid=="cust-reset-appearance":
|
|
1148
|
+
ok,msg=self._manager.reset_appearance()
|
|
1149
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1150
|
+
if bid=="cust-reset-buttons":
|
|
1151
|
+
ok,msg=self._manager.reset_buttons()
|
|
1152
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply(); return
|
|
1153
|
+
if bid=="cust-reset-everything" or bid=="cust-reset-all":
|
|
1154
|
+
self.app.push_screen(_ConfirmScreen("Reset Everything?", "Reset ALL customization to defaults? Cannot be undone.", True), self._on_reset_everything)
|
|
1155
|
+
return
|
|
1156
|
+
|
|
1157
|
+
def _on_prof_delete(self, name, confirmed):
|
|
1158
|
+
if not confirmed: return
|
|
1159
|
+
ok,msg=self._manager.profile_manager.delete_profile(name)
|
|
1160
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1161
|
+
def _on_reset_everything(self, confirmed):
|
|
1162
|
+
if not confirmed: return
|
|
1163
|
+
ok,msg=self._manager.reset_everything()
|
|
1164
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1165
|
+
|
|
1166
|
+
def on_select_changed(self, event):
|
|
1167
|
+
sid = getattr(event.select, "id", "") or ""
|
|
1168
|
+
if sid == "cust-pos-select":
|
|
1169
|
+
self._pos_panel = event.value
|
|
1170
|
+
self.call_after_refresh(self._render_section_view)
|
|
1171
|
+
elif sid == "cust-size-select":
|
|
1172
|
+
self._size_panel = event.value
|
|
1173
|
+
self.call_after_refresh(self._render_section_view)
|
|
1174
|
+
elif sid == "cust-dock-select":
|
|
1175
|
+
self._dock_panel = event.value
|
|
1176
|
+
self.call_after_refresh(self._render_section_view)
|
|
1177
|
+
def on_switch_changed(self, event):
|
|
1178
|
+
sid=getattr(event.switch, "id", "") or ""
|
|
1179
|
+
if sid.startswith("cust-vis-"):
|
|
1180
|
+
pid=sid.replace("cust-vis-","")
|
|
1181
|
+
ok,msg=self._manager.set_visibility(pid, bool(event.value))
|
|
1182
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1183
|
+
elif sid.startswith("cust-mmvis-"):
|
|
1184
|
+
item=sid.replace("cust-mmvis-","")
|
|
1185
|
+
try:
|
|
1186
|
+
mm=self._manager.get_main_menu()
|
|
1187
|
+
vis=dict(mm.get("visibility",{}))
|
|
1188
|
+
vis[item]=bool(event.value)
|
|
1189
|
+
ok,msg=self._manager.set_main_menu_config(visibility=vis)
|
|
1190
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1191
|
+
except Exception as e:
|
|
1192
|
+
self._show_error(str(e))
|
|
1193
|
+
elif sid=="cust-header-logo-vis":
|
|
1194
|
+
ok,msg=self._manager.set_header_config(logo_visible=bool(event.value))
|
|
1195
|
+
self._show_status(msg, "success" if ok else "error"); self._live_apply()
|
|
1196
|
+
def action_cancel(self):
|
|
1197
|
+
self.dismiss(None)
|
|
1198
|
+
def on_key(self, event):
|
|
1199
|
+
if event.key=="escape":
|
|
1200
|
+
self.dismiss(None)
|
|
1201
|
+
|
|
1202
|
+
class _ConfirmScreen(Screen):
|
|
1203
|
+
CSS = """
|
|
1204
|
+
_ConfirmScreen { align: center middle; background: $app-background 70%; }
|
|
1205
|
+
#confirm-box { width: 60; max-width: 96%; height: auto; background: $surface; border: round $border; padding: 1 2; }
|
|
1206
|
+
#confirm-title { color: $warning; text-style: bold; height: 1; }
|
|
1207
|
+
#confirm-msg { color: $text; height: auto; padding: 1 0; }
|
|
1208
|
+
#confirm-btns { height: 3; align: center middle; }
|
|
1209
|
+
#confirm-btns Button { margin: 0 1; min-width: 12; }
|
|
1210
|
+
"""
|
|
1211
|
+
def __init__(self, title, msg, is_destructive=False):
|
|
1212
|
+
super().__init__()
|
|
1213
|
+
self._title=title; self._msg=msg; self._destructive=is_destructive
|
|
1214
|
+
def compose(self):
|
|
1215
|
+
with Vertical(id="confirm-box"):
|
|
1216
|
+
yield Static(self._title, id="confirm-title")
|
|
1217
|
+
yield Static(self._msg, id="confirm-msg")
|
|
1218
|
+
with Horizontal(id="confirm-btns"):
|
|
1219
|
+
yield Button("Cancel", id="confirm-cancel")
|
|
1220
|
+
yield Button("Confirm", id="confirm-ok", variant="error" if self._destructive else "primary")
|
|
1221
|
+
def on_button_pressed(self, event):
|
|
1222
|
+
self.dismiss(event.button.id=="confirm-ok")
|
|
1223
|
+
def on_key(self, event):
|
|
1224
|
+
if event.key=="escape":
|
|
1225
|
+
self.dismiss(False)
|
|
1226
|
+
|
|
1227
|
+
else:
|
|
1228
|
+
CustomizationPanel=None
|
|
1229
|
+
_ConfirmScreen=None
|