feat: 代码格式化, 改进仪表盘, 新增多CSS支持

This commit is contained in:
2026-04-20 16:30:04 +08:00
parent 8677e828c7
commit 4ca9c65bea
43 changed files with 551 additions and 349 deletions

View File

@@ -35,6 +35,8 @@ class DashboardScreen(Screen):
("q", "go_back", "返回"),
]
CSS_PATH = Path(__file__).parent.parent / 'css' / "screens" / "dashboard.tcss"
def __init__(
self,
name: str | None = None,
@@ -42,97 +44,91 @@ class DashboardScreen(Screen):
classes: str | None = None,
) -> None:
super().__init__(name, id, classes)
self.repostat = {}
self.title2dirname = {}
self.title2repo = {}
self.dirname2repo = {}
self._load_data()
self.repolink = {}
def compose(self) -> ComposeResult:
"""组合界面组件"""
self._load_data()
yield Header(show_clock=True)
with ScrollableContainer():
yield Horizontal(
yield Horizontal( # 顶部的状态
Vertical(
Label(f'欢迎使用 "潜进" 版本 {version.ver}'),
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
Label(
f"当前 UNIX 日时间戳: {timer.get_daystamp()}"
f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"
),
Label(f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"),
Label(f"全局算法设置: {config_var.get()['interface']['global']['algorithm']}: {algorithms[config_var.get()['interface']['global']['algorithm']].desc}"),
classes="column infview",
Label(
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}"
),
classes="left",
),
Vertical(
Label(f"已加载 {len(self.repostat)} 个单元集", classes='dataview'),
Label(f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.get('unit_sum'), self.repostat.values()))} 个单元", classes='dataview'),
Label(f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.get('activated_sum'), self.repostat.values()))} 个单元", classes='dataview'),
Label(f"已加载 {len(self.repos)} 个单元集"),
Label(
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos))} 个单元"
),
Label(
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos))} 个单元"
),
Label(f""),
classes="column dataview",
classes="right",
),
id="dashboardtop"
id="header",
)
yield ListView(id="repo-list", classes="repo-list-view")
yield Label(f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}')
yield ListView(id="repo_list", classes="repo-list") # 单元集选择
yield Label(
f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}'
) # 版本信息
yield Footer()
def _load_data(self):
self.repo_dirs = Repo.probe_valid_repos_in_dir(
Path(config_var.get()['global']["paths"]["data"]) / "repo"
repo_dirs = Repo.probe_valid_repos_in_dir(
Path(config_var.get()["global"]["paths"]["repo"])
)
for repo_dir in self.repo_dirs:
repo = Repo.create_from_repodir(repo_dir)
self.repos = list(map(Repo.from_repodir, repo_dirs))
for repo in self.repos:
self._analyse_repo(repo)
def _analyse_repo(self, repo: Repo):
dirname = repo.source.name # type: ignore
title = repo.manifest["title"]
is_due = 0
unit_sum = len(repo)
activated_sum = 0
nextdate = float('inf')
for i in repo.ident_index:
nucleon = pt.Nucleon.create_on_nucleonic_data(
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
)
electron = pt.Electron.create_on_electonic_data(
electronic_data=repo.electronic_data_lict.get_itemic_unit(i),
algo_name=config_var.get()['repo'][repo.manifest['title']]['algorithm']
)
if electron.is_activated():
activated_sum += 1
if electron.is_due():
is_due = 1
nextdate = min(nextdate, electron.nextdate())
is_unfinished = unit_sum > activated_sum
if is_unfinished:
nextdate = min(nextdate, timer.get_daystamp())
need_to_study = is_due or is_unfinished
prompt = f"{title}\0\n 进度: {activated_sum}/{unit_sum} ({round(activated_sum/unit_sum*100)}%)\n {'需要学习' if need_to_study else '无需操作'}"
stat = {
"is_due": is_due,
"unit_sum": unit_sum,
"title": title,
"activated_sum": activated_sum,
"nextdate": nextdate,
"is_unfinished": is_unfinished,
"need_to_study": need_to_study,
"prompt": prompt,
"dirname": dirname,
# need_review: 需要/不需要学习
# nearest_review_time: 最近下次学习时间
# progress: 进度
# algotype: 算法类型
## initial_time: 起始时间
# package: 包名
# prompt: 最终呈现信息
repo.package = repo.manifest["package"]
repo.nearest_review_time = float("inf")
repo.progress = {
"total": repo.data_length,
"touched": 0,
}
self.repostat[dirname] = stat
self.title2dirname[title] = dirname
self.title2repo[title] = repo
self.dirname2repo[dirname] = repo
initial_time = float("inf")
for i in range(repo.data_length):
e = pt.Electron.from_data(repo.electronic_data_lict[i])
n = pt.Nucleon.from_data(repo.nucleonic_data_lict[i])
if e.is_activated():
repo.algotype = e.algoname
repo.progress["touched"] += 1
repo.nearest_review_time = min(repo.nearest_review_time, e.nextdate())
# initial_time = min(initial_time, e.)
repo.need_review = timer.get_daystamp() >= repo.nearest_review_time
repo.prompt = f"""{repo.manifest['title']} ({repo.algotype})
进度: {repo.progress['touched']}/{repo.progress['total']} ({round(repo.progress['touched']/repo.progress['total']*100, 1)}%)
{'需要学习' if repo.need_review else "无需操作"}
"""
def on_mount(self) -> None:
"""挂载组件时初始化"""
repo_list_widget = self.query_one("#repo-list", ListView)
repo_list_widget = self.query_one("#repo_list", ListView)
# 按下次复习时间排序
repodirs = sorted(
self.repo_dirs,
key=lambda f: self.repostat[f.name]["nextdate"],
self.repos,
key=lambda r: r.nearest_review_time,
reverse=True,
)
repotitles = map(lambda f: self.repostat[f.name]["title"], repodirs)
@@ -141,43 +137,44 @@ class DashboardScreen(Screen):
repo_list_widget.append(
ListItem(
Static(
"./data/repo/ 中未找到任何仓库.\n"
f"{config_var.get()['global']['paths']['repo']} 中未找到任何仓库.\n"
"请导入仓库后重启应用, 或者新建空的仓库."
)
),
id="not-found",
)
)
repo_list_widget.disabled = True
return
for repotitle in repotitles:
prompt = self.repostat[self.title2dirname[repotitle]]["prompt"]
list_item = ListItem(Label(prompt), Button(f"开始学习", flat=True, variant="primary", classes="repo_listitem_btn", id=f"launch_{self.repostat[self.title2dirname[repotitle]]['dirname']}"), classes="repo_listitem")
for r in self.repos:
self.repolink[str(id(r))] = r # 用于规避 ctype id 对象还原
list_item = ListItem(
Label(r.prompt),
Button(
f"开始学习",
flat=True,
variant="primary",
id=f"slaunch_repo_{id(r)}",
classes="repo-list-item-shortcut",
),
classes="repo-list-item",
id=f"launch_repo_{id(r)}",
)
repo_list_widget.append(list_item)
# if not self.stay_enabled[repodir]:
# list_item.disabled = True
def on_list_view_selected(self, event) -> None:
"""处理列表项选择事件"""
if not isinstance(event.item, ListItem):
return
selected_label = event.item.query_one(Label)
label_text = str(selected_label.render())
if "未找到任何仓库" in label_text:
if "not-found" == event.item.id:
return
# 提取文件名
selected_repotitle = label_text.partition("\0")[0].replace("*", "")
selected_repo = self.title2repo[label_text.partition("\0")[0].replace("*", "")]
# 还原对象
selected_repo = self.repolink[event.item.id.lstrip("launch_repo_")]
# 跳转到准备屏幕
self.app.push_screen(
PreparationScreen(
selected_repo, self.repostat[self.title2dirname[selected_repotitle]]
)
)
self.app.push_screen(PreparationScreen(selected_repo))
def action_quit_app(self) -> None:
"""退出应用程序"""
@@ -188,9 +185,10 @@ class DashboardScreen(Screen):
self.app.push_screen(NavigatorScreen())
def on_button_pressed(self, event: Button.Pressed) -> None:
logger.debug(f"event.button.id: {event.button.id}")
"""处理按钮点击事件"""
if str(event.button.id).startswith("launch_"): # type: ignore
logger.debug(f"event.button.id: {event.button.id}")
if event.button.id.startswith("slaunch_repo_"): # type: ignore
from .preparation import launch
launch(repo=self.dirname2repo[event.button.id[7:]], app=self.app, scheduled_num=-1) # type: ignore
launch(repo=self.repolink[event.button.id.lstrip("slaunch_repo_")], app=self.app, scheduled_num=-1) # type: ignore
# TODO: 这样启动的记忆实例的状态机无法绑定到 PreparationScreen 中