feat(interface): 增加智能单元集排序
This commit is contained in:
@@ -4,8 +4,15 @@ import pathlib
|
|||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
from textual.containers import ScrollableContainer
|
from textual.containers import ScrollableContainer
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import (Button, Footer, Header, Label, ListItem, ListView,
|
from textual.widgets import (
|
||||||
Static)
|
Button,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Label,
|
||||||
|
ListItem,
|
||||||
|
ListView,
|
||||||
|
Static,
|
||||||
|
)
|
||||||
|
|
||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
import heurams.services.version as version
|
import heurams.services.version as version
|
||||||
@@ -20,129 +27,190 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DashboardScreen(Screen):
|
class DashboardScreen(Screen):
|
||||||
|
"""主仪表盘屏幕"""
|
||||||
|
|
||||||
SUB_TITLE = "仪表盘"
|
SUB_TITLE = "仪表盘"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(name, id, classes)
|
||||||
|
self.nextdates = {}
|
||||||
|
self.texts = {}
|
||||||
|
self.stay_enabled = {}
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
"""组合界面组件"""
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
yield ScrollableContainer(
|
yield ScrollableContainer(
|
||||||
Label(f'欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
|
Label('欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
|
||||||
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
|
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
|
||||||
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
|
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
|
||||||
Label(f"使用算法: {config_var.get()['algorithm']['default']}"),
|
Label(f"使用算法: {config_var.get()['algorithm']['default']}"),
|
||||||
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
|
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
|
||||||
ListView(id="union-list", classes="union-list-view"),
|
ListView(id="union-list", classes="union-list-view"),
|
||||||
Label(
|
Label(
|
||||||
f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} {version.codename.capitalize()} 2025'
|
f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} '
|
||||||
|
f'{version.codename.capitalize()} 2025'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
def item_desc_generator(self, filename) -> dict:
|
def analyser(self, filename: str) -> dict:
|
||||||
"""简单分析以生成项目项显示文本
|
"""分析文件状态以生成显示文本
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: 要分析的文件名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: 以数字为列表, 分别呈现单行字符串
|
dict: 包含显示文本的字典,键为行号
|
||||||
"""
|
"""
|
||||||
res = dict()
|
from heurams.kernel.particles.loader import load_electron, load_nucleon
|
||||||
|
|
||||||
|
result = {}
|
||||||
filestem = pathlib.Path(filename).stem
|
filestem = pathlib.Path(filename).stem
|
||||||
res[0] = f"{filename}\0"
|
|
||||||
import heurams.kernel.particles as pt
|
# 构建电子文件路径
|
||||||
from heurams.kernel.particles.loader import load_electron
|
electron_dir = config_var.get()["paths"]["electron_dir"]
|
||||||
|
electron_file_path = pathlib.Path(electron_dir) / f"{filestem}.json"
|
||||||
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
|
|
||||||
filestem + ".json"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"电子文件路径: {electron_file_path}")
|
logger.debug(f"电子文件路径: {electron_file_path}")
|
||||||
|
|
||||||
if electron_file_path.exists(): # 未找到则创建电子文件 (json)
|
# 确保电子文件存在
|
||||||
pass
|
if not electron_file_path.exists():
|
||||||
else:
|
|
||||||
electron_file_path.touch()
|
electron_file_path.touch()
|
||||||
with open(electron_file_path, "w") as f:
|
electron_file_path.write_text("{}")
|
||||||
f.write("{}")
|
|
||||||
electron_dict = load_electron(path=electron_file_path) # TODO: 取消硬编码扩展名
|
# 加载电子数据
|
||||||
logger.debug(electron_dict)
|
electron_dict = load_electron(path=electron_file_path)
|
||||||
|
logger.debug(f"电子数据: {electron_dict}")
|
||||||
|
|
||||||
|
# 分析电子状态
|
||||||
is_due = 0
|
is_due = 0
|
||||||
is_activated = 0
|
is_activated = 0
|
||||||
nextdate = 0x3F3F3F3F
|
nextdate = 0x3F3F3F3F
|
||||||
for i in electron_dict.values():
|
|
||||||
i: pt.Electron
|
for electron in electron_dict.values():
|
||||||
logger.debug(i, i.is_due())
|
logger.debug(f"{electron}, 是否到期: {electron.is_due()}")
|
||||||
if i.is_due():
|
|
||||||
|
if electron.is_due():
|
||||||
is_due = 1
|
is_due = 1
|
||||||
if i.is_activated():
|
if electron.is_activated():
|
||||||
is_activated = 1
|
is_activated = 1
|
||||||
nextdate = min(nextdate, i.nextdate())
|
nextdate = min(nextdate, electron.nextdate())
|
||||||
res[1] = f"下一次复习: {nextdate}\n"
|
|
||||||
res[1] += f"{"需要复习" if is_due else "当前无需复习"}"
|
# 检查是否需要更多复习
|
||||||
|
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
|
||||||
|
nucleon_path = pathlib.Path(nucleon_dir) / f"{filestem}.toml"
|
||||||
|
nucleon_count = len(load_nucleon(nucleon_path))
|
||||||
|
electron_count = len(electron_dict)
|
||||||
|
is_more = not (electron_count >= nucleon_count)
|
||||||
|
|
||||||
|
logger.debug(f"是否需要更多复习: {is_more}")
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
self.nextdates[filename] = nextdate
|
||||||
|
self.stay_enabled[filename] = (is_due or is_more)
|
||||||
|
|
||||||
|
# 构建返回结果
|
||||||
|
result[0] = f"{filename}\0"
|
||||||
|
|
||||||
if not is_activated:
|
if not is_activated:
|
||||||
res[1] = " 尚未激活"
|
result[1] = " 尚未激活"
|
||||||
return res
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
|
||||||
union_list_widget = self.query_one("#union-list", ListView)
|
|
||||||
|
|
||||||
probe = probe_all(0)
|
|
||||||
|
|
||||||
if len(probe["nucleon"]):
|
|
||||||
for file in probe["nucleon"]:
|
|
||||||
text = self.item_desc_generator(file)
|
|
||||||
union_list_widget.append(
|
|
||||||
ListItem(
|
|
||||||
Label(text[0] + "\n" + text[1]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
|
status_text = "需要复习" if is_due else "当前无需复习"
|
||||||
|
result[1] = f"下一次复习: {nextdate}\n{status_text}"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
"""挂载组件时初始化"""
|
||||||
|
union_list_widget = self.query_one("#union-list", ListView)
|
||||||
|
probe = probe_all(0)
|
||||||
|
|
||||||
|
# 分析所有文件
|
||||||
|
for file in probe["nucleon"]:
|
||||||
|
self.texts[file] = self.analyser(file)
|
||||||
|
|
||||||
|
# 按下次复习时间排序
|
||||||
|
nucleon_files = sorted(
|
||||||
|
probe["nucleon"],
|
||||||
|
key=lambda f: self.nextdates[f],
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 填充列表
|
||||||
|
if not probe["nucleon"]:
|
||||||
union_list_widget.append(
|
union_list_widget.append(
|
||||||
ListItem(
|
ListItem(
|
||||||
Static(
|
Static(
|
||||||
"在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."
|
"在 ./nucleon/ 中未找到任何内容源数据文件。\n"
|
||||||
|
"请放置文件后重启应用,或者新建空的单元集。"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
union_list_widget.disabled = True
|
union_list_widget.disabled = True
|
||||||
|
return
|
||||||
|
|
||||||
|
for file in nucleon_files:
|
||||||
|
text = self.texts[file]
|
||||||
|
list_item = ListItem(
|
||||||
|
Label(f"{text[0]}\n{text[1]}")
|
||||||
|
)
|
||||||
|
union_list_widget.append(list_item)
|
||||||
|
|
||||||
|
if not self.stay_enabled[file]:
|
||||||
|
list_item.disabled = True
|
||||||
|
|
||||||
def on_list_view_selected(self, event) -> None:
|
def on_list_view_selected(self, event) -> None:
|
||||||
|
"""处理列表项选择事件"""
|
||||||
if not isinstance(event.item, ListItem):
|
if not isinstance(event.item, ListItem):
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_label = event.item.query_one(Label)
|
selected_label = event.item.query_one(Label)
|
||||||
if "未找到任何 .toml 文件" in str(selected_label.renderable): # type: ignore
|
label_text = str(selected_label.renderable)
|
||||||
|
|
||||||
|
if "未找到任何 .toml 文件" in label_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 提取文件名
|
||||||
selected_filename = pathlib.Path(
|
selected_filename = pathlib.Path(
|
||||||
str(selected_label.renderable)
|
label_text.partition("\0")[0].replace("*", "")
|
||||||
.partition("\0")[0] # 文件名末尾截断, 保留文件名
|
|
||||||
.replace("*", "")
|
|
||||||
) # 去除markdown加粗
|
|
||||||
|
|
||||||
nucleon_file_path = (
|
|
||||||
pathlib.Path(config_var.get()["paths"]["nucleon_dir"]) / selected_filename
|
|
||||||
)
|
)
|
||||||
electron_file_path = pathlib.Path(config_var.get()["paths"]["electron_dir"]) / (
|
|
||||||
str(selected_filename.stem) + ".json"
|
# 构建文件路径
|
||||||
|
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
|
||||||
|
electron_dir = config_var.get()["paths"]["electron_dir"]
|
||||||
|
|
||||||
|
nucleon_file_path = pathlib.Path(nucleon_dir) / selected_filename
|
||||||
|
electron_file_path = pathlib.Path(electron_dir) / f"{selected_filename.stem}.json"
|
||||||
|
|
||||||
|
# 跳转到准备屏幕
|
||||||
|
self.app.push_screen(
|
||||||
|
PreparationScreen(nucleon_file_path, electron_file_path)
|
||||||
)
|
)
|
||||||
self.app.push_screen(PreparationScreen(nucleon_file_path, electron_file_path))
|
|
||||||
|
|
||||||
def on_button_pressed(self, event) -> None:
|
def on_button_pressed(self, event) -> None:
|
||||||
if event.button.id == "new_nucleon_button":
|
"""处理按钮点击事件"""
|
||||||
# 切换到创建单元
|
button_id = event.button.id
|
||||||
|
|
||||||
|
if button_id == "new_nucleon_button":
|
||||||
from .nucreator import NucleonCreatorScreen
|
from .nucreator import NucleonCreatorScreen
|
||||||
|
new_screen = NucleonCreatorScreen()
|
||||||
newscr = NucleonCreatorScreen()
|
self.app.push_screen(new_screen)
|
||||||
self.app.push_screen(newscr)
|
|
||||||
elif event.button.id == "precache_all_button":
|
elif button_id == "precache_all_button":
|
||||||
# 切换到缓存管理器
|
|
||||||
from .precache import PrecachingScreen
|
from .precache import PrecachingScreen
|
||||||
|
|
||||||
precache_screen = PrecachingScreen()
|
precache_screen = PrecachingScreen()
|
||||||
self.app.push_screen(precache_screen)
|
self.app.push_screen(precache_screen)
|
||||||
elif event.button.id == "about_button":
|
|
||||||
from .about import AboutScreen
|
elif button_id == "about_button":
|
||||||
|
|
||||||
about_screen = AboutScreen()
|
about_screen = AboutScreen()
|
||||||
self.app.push_screen(about_screen)
|
self.app.push_screen(about_screen)
|
||||||
|
|
||||||
def action_quit_app(self) -> None:
|
def action_quit_app(self) -> None:
|
||||||
self.app.exit()
|
"""退出应用程序"""
|
||||||
|
self.app.exit()
|
||||||
@@ -89,7 +89,7 @@ class TestDashboardScreenUnit(unittest.TestCase):
|
|||||||
screen = DashboardScreen()
|
screen = DashboardScreen()
|
||||||
# 模拟一个文件名
|
# 模拟一个文件名
|
||||||
filename = "test.toml"
|
filename = "test.toml"
|
||||||
result = screen.item_desc_generator(filename)
|
result = screen.analyser(filename)
|
||||||
self.assertIsInstance(result, dict)
|
self.assertIsInstance(result, dict)
|
||||||
self.assertIn(0, result)
|
self.assertIn(0, result)
|
||||||
self.assertIn(1, result)
|
self.assertIn(1, result)
|
||||||
|
|||||||
Reference in New Issue
Block a user