From a087189e54c949eda97d481c3f85bf90ef4236e9 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 29 Aug 2023 22:35:13 +0800 Subject: [PATCH] Add: Dashboard on GUI --- assets/gui/css/alas-mobile.css | 9 ++- assets/gui/css/alas-pc.css | 3 +- assets/gui/css/alas.css | 52 +++++++++++- assets/gui/css/dark-alas.css | 4 + assets/gui/css/light-alas.css | 4 + module/config/argument/args.json | 8 +- module/config/argument/argument.yaml | 4 + module/config/argument/gui.yaml | 10 +++ module/config/argument/stored.json | 84 ++++++++++++++++++++ module/config/config_updater.py | 28 +++++++ module/config/i18n/en-US.json | 9 +++ module/config/i18n/ja-JP.json | 9 +++ module/config/i18n/zh-CN.json | 9 +++ module/config/i18n/zh-TW.json | 9 +++ module/config/stored/classes.py | 35 +++------ module/webui/app.py | 113 ++++++++++++++++++--------- module/webui/base.py | 26 ++++++ module/webui/lang.py | 33 +++++++- 18 files changed, 380 insertions(+), 69 deletions(-) create mode 100644 module/config/argument/stored.json diff --git a/assets/gui/css/alas-mobile.css b/assets/gui/css/alas-mobile.css index 9acad6eb7..3e3d6f75c 100644 --- a/assets/gui/css/alas-mobile.css +++ b/assets/gui/css/alas-mobile.css @@ -59,4 +59,11 @@ #pywebio-scope-waiting, #pywebio-scope-log { overflow-y: auto; -} \ No newline at end of file +} + +[id^="pywebio-scope-dashboard-row-"] { + display: flex; + flex-grow: 1; + min-width: 4rem; + max-width: 50%; +} diff --git a/assets/gui/css/alas-pc.css b/assets/gui/css/alas-pc.css index 57d95088e..0adfa2591 100644 --- a/assets/gui/css/alas-pc.css +++ b/assets/gui/css/alas-pc.css @@ -26,7 +26,6 @@ overflow-y: auto; } -#pywebio-scope-scheduler-bar, #pywebio-scope-log-bar, #pywebio-scope-log, #pywebio-scope-daemon-overview #pywebio-scope-groups { @@ -45,4 +44,4 @@ display: grid; grid-auto-flow: column; grid-template-columns: auto auto; -} \ No newline at end of file +} diff --git a/assets/gui/css/alas.css b/assets/gui/css/alas.css index 735dc2012..5fc57f5d4 100644 --- a/assets/gui/css/alas.css +++ b/assets/gui/css/alas.css @@ -383,17 +383,65 @@ pre.rich-traceback-code { } #pywebio-scope-scheduler-bar, -#pywebio-scope-log-bar { +#pywebio-scope-log-title { display: flex; align-items: center; justify-content: space-between; } -#pywebio-scope-log-bar-btns { +#pywebio-scope-log-title-btns { display: grid; grid-auto-flow: column; } +#pywebio-scope-log-bar { + height: 11.5rem; +} + +#pywebio-scope-dashboard { + display: flex; + align-content: flex-start; + flex-flow: row wrap; + overflow: auto; +} + +.dashboard-icon { + margin: .6rem .8rem 0 .6rem; + width: .5rem; + height: .5rem; + border-radius: 50%; +} + +[id^="pywebio-scope-dashboard-row-"] { + display: flex; + flex-grow: 1; + min-width: 4rem; + max-width: 10rem; +} + +*[style*="--dashboard-value--"] { + font-size: 1rem; + font-weight: 500; + overflow-wrap: break-word; +} + +*[style*="--dashboard-time--"] { + font-size: 0.8rem; + font-weight: 400; + overflow-wrap: break-word; +} + +[id^="pywebio-scope-dashboard-row-"] p { + margin-bottom: 0; +} + +[id^="pywebio-scope-dashboard-value-"] { + display: flex; + align-items: flex-end; + height: 1.5rem; +} + + #pywebio-scope-log { line-height: 1.2; font-size: 0.85rem; diff --git a/assets/gui/css/dark-alas.css b/assets/gui/css/dark-alas.css index c36fa31b0..c2d76e7bc 100644 --- a/assets/gui/css/dark-alas.css +++ b/assets/gui/css/dark-alas.css @@ -152,4 +152,8 @@ pre.rich-traceback-code { *[style*="--arg-help--"], [id^="pywebio-scope-group_"] > p + p { color: #adb5bd; +} + +*[style*="--dashboard-time--"] { + color: #adb5bd; } \ No newline at end of file diff --git a/assets/gui/css/light-alas.css b/assets/gui/css/light-alas.css index b65067f11..171d565af 100644 --- a/assets/gui/css/light-alas.css +++ b/assets/gui/css/light-alas.css @@ -151,4 +151,8 @@ pre.rich-traceback-code { *[style*="--arg-help--"], [id^="pywebio-scope-group_"] > p + p { color: #777777; +} + +*[style*="--dashboard-time--"] { + color: #777777; } \ No newline at end of file diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 5afb63928..97694e31c 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -364,7 +364,9 @@ "type": "stored", "value": {}, "display": "hide", - "stored": "StoredTrailblazePower" + "stored": "StoredTrailblazePower", + "order": 1, + "color": "#eb8efe" }, "DungeonDouble": { "type": "stored", @@ -782,7 +784,9 @@ "type": "stored", "value": {}, "display": "hide", - "stored": "StoredDailyActivity" + "stored": "StoredDailyActivity", + "order": 2, + "color": "#ffcf70" }, "DailyQuest": { "type": "stored", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 2be96a989..7d0c54b74 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -111,6 +111,8 @@ DungeonSupport: DungeonStorage: TrailblazePower: stored: StoredTrailblazePower + order: 1 + color: "#eb8efe" DungeonDouble: stored: StoredDungeonDouble @@ -124,6 +126,8 @@ AchievableQuest: DailyStorage: DailyActivity: stored: StoredDailyActivity + order: 2 + color: "#ffcf70" DailyQuest: stored: StoredDaily diff --git a/module/config/argument/gui.yaml b/module/config/argument/gui.yaml index 5866dd269..419167b0c 100644 --- a/module/config/argument/gui.yaml +++ b/module/config/argument/gui.yaml @@ -52,6 +52,16 @@ Overview: Waiting: NoTask: +Dashboard: + # From lang.readable_time() + NoData: + TimeError: + JustNow: + MinutesAgo: + HoursAgo: + DaysAgo: + LongTimeAgo: + AddAlas: PopupTitle: NewName: diff --git a/module/config/argument/stored.json b/module/config/argument/stored.json new file mode 100644 index 000000000..a42f73750 --- /dev/null +++ b/module/config/argument/stored.json @@ -0,0 +1,84 @@ +{ + "TrailblazePower": { + "name": "TrailblazePower", + "path": "Dungeon.DungeonStorage.TrailblazePower", + "i18n": "DungeonStorage.TrailblazePower.name", + "stored": "StoredTrailblazePower", + "attrs": { + "time": "2020-01-01 00:00:00", + "total": 180, + "value": 0 + }, + "order": 1, + "color": "#eb8efe" + }, + "DailyActivity": { + "name": "DailyActivity", + "path": "DailyQuest.DailyStorage.DailyActivity", + "i18n": "DailyStorage.DailyActivity.name", + "stored": "StoredDailyActivity", + "attrs": { + "time": "2020-01-01 00:00:00", + "total": 500, + "value": 0 + }, + "order": 2, + "color": "#ffcf70" + }, + "Assignment": { + "name": "Assignment", + "path": "Assignment.Assignment.Assignment", + "i18n": "Assignment.Assignment.name", + "stored": "StoredAssignment", + "attrs": { + "time": "2020-01-01 00:00:00", + "total": 0, + "value": 0 + }, + "order": 3, + "color": "#deba95" + }, + "SimulatedUniverse": { + "name": "SimulatedUniverse", + "path": "Dungeon.DungeonStorage.SimulatedUniverse", + "i18n": "DungeonStorage.SimulatedUniverse.name", + "stored": "StoredSimulatedUniverse", + "attrs": { + "time": "2020-01-01 00:00:00", + "total": 0, + "value": 0 + }, + "order": 5, + "color": "#8fb5fe" + }, + "DungeonDouble": { + "name": "DungeonDouble", + "path": "Dungeon.DungeonStorage.DungeonDouble", + "i18n": "DungeonStorage.DungeonDouble.name", + "stored": "StoredDungeonDouble", + "attrs": { + "time": "2020-01-01 00:00:00", + "calyx": 0, + "relic": 0 + }, + "order": 0, + "color": "#777777" + }, + "DailyQuest": { + "name": "DailyQuest", + "path": "DailyQuest.DailyStorage.DailyQuest", + "i18n": "DailyStorage.DailyQuest.name", + "stored": "StoredDaily", + "attrs": { + "time": "2020-01-01 00:00:00", + "quest1": "", + "quest2": "", + "quest3": "", + "quest4": "", + "quest5": "", + "quest6": "" + }, + "order": 0, + "color": "#777777" + } +} \ No newline at end of file diff --git a/module/config/config_updater.py b/module/config/config_updater.py index 3b0dbeea9..99d49603d 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -441,6 +441,32 @@ class ConfigGenerator: return data + @cached_property + def stored(self): + import module.config.stored.classes as classes + data = {} + for path, value in deep_iter(self.args, depth=3): + if value.get('type') != 'stored': + continue + name = path[-1] + stored = value.get('stored') + stored_class = getattr(classes, stored) + row = { + 'name': name, + 'path': '.'.join(path), + 'i18n': f'{path[1]}.{path[2]}.name', + 'stored': stored, + 'attrs': stored_class('')._attrs, + 'order': value.get('order', 0), + 'color': value.get('color', '#777777') + } + data[name] = row + + # sort by `order` ascending, but `order`==0 at last + data = sorted(data.items(), key=lambda kv: (kv[1]['order'] == 0, kv[1]['order'])) + data = {k: v for k, v in data} + return data + @staticmethod def generate_deploy_template(): template = poor_yaml_read(DEPLOY_TEMPLATE) @@ -495,12 +521,14 @@ class ConfigGenerator: def generate(self): _ = self.args _ = self.menu + _ = self.stored # _ = self.event self.insert_assignment() self.insert_package() # self.insert_server() write_file(filepath_args(), self.args) write_file(filepath_args('menu'), self.menu) + write_file(filepath_args('stored'), self.stored) self.generate_code() self.generate_stored() for lang in LANGUAGES: diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index b4fad33df..c3eba38d1 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -695,6 +695,15 @@ "Waiting": "Waiting", "NoTask": "No Task" }, + "Dashboard": { + "NoData": "no data", + "TimeError": "time error", + "JustNow": "just now", + "MinutesAgo": "{time}min ago", + "HoursAgo": "{time}h ago", + "DaysAgo": "{time}d ago", + "LongTimeAgo": "long time ago" + }, "AddAlas": { "PopupTitle": "Add new config", "NewName": "New name", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 01c0bb4ba..7e6199cc9 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -695,6 +695,15 @@ "Waiting": "Waiting", "NoTask": "No Task" }, + "Dashboard": { + "NoData": "Gui.Dashboard.NoData", + "TimeError": "Gui.Dashboard.TimeError", + "JustNow": "Gui.Dashboard.JustNow", + "MinutesAgo": "Gui.Dashboard.MinutesAgo", + "HoursAgo": "Gui.Dashboard.HoursAgo", + "DaysAgo": "Gui.Dashboard.DaysAgo", + "LongTimeAgo": "Gui.Dashboard.LongTimeAgo" + }, "AddAlas": { "PopupTitle": "新しいコンフィグを追加", "NewName": "コンフィグ名", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 2d352e35f..c7ecf1396 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -695,6 +695,15 @@ "Waiting": "等待中", "NoTask": "无任务" }, + "Dashboard": { + "NoData": "无数据", + "TimeError": "时间错误", + "JustNow": "刚刚", + "MinutesAgo": "{time}分钟前", + "HoursAgo": "{time}小时前", + "DaysAgo": "{time}天前", + "LongTimeAgo": "很久以前" + }, "AddAlas": { "PopupTitle": "添加新配置", "NewName": "新的配置文件名", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 300bf11ca..aa2a45450 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -695,6 +695,15 @@ "Waiting": "等待中", "NoTask": "無任務" }, + "Dashboard": { + "NoData": "無數據", + "TimeError": "時間錯誤", + "JustNow": "剛剛", + "MinutesAgo": "{time}分鐘前", + "HoursAgo": "{time}小時前", + "DaysAgo": "{time}天前", + "LongTimeAgo": "很久以前" + }, "AddAlas": { "PopupTitle": "添加新的設定", "NewName": "新設定的檔案名", diff --git a/module/config/stored/classes.py b/module/config/stored/classes.py index 227fb3a00..c1df02fb9 100644 --- a/module/config/stored/classes.py +++ b/module/config/stored/classes.py @@ -1,4 +1,3 @@ -import time from datetime import datetime from functools import cached_property as functools_cached_property @@ -111,29 +110,6 @@ class StoredBase: from module.logger import logger logger.attr(self._name, self._stored) - def dashboard(self) -> str: - """ - Return a string to show on GUI - """ - return 'None' - - def readable_time(self): - diff = self.time.timestamp() - time.time() - if diff < -1: - return '', 'TimeError' - elif diff < 60: - # < 1 min - return '', 'JustNow' - elif diff < 3600: - return str(int(diff // 60)), 'MinutesAgo' - elif diff < 86400: - return str(int(diff // 86400)), 'HoursAgo' - elif diff < 129600: - return str(int(diff // 129600)), 'DaysAgo' - else: - # > 15 days - return '', 'LongTimeAgo' - class StoredExpiredAt0400(StoredBase): def is_expired(self): @@ -154,11 +130,11 @@ class StoredCounter(StoredBase): FIXED_TOTAL = 0 - def set(self, current, total=0): + def set(self, value, total=0): if self.FIXED_TOTAL: total = self.FIXED_TOTAL with self._config.multi_set(): - self.value = current + self.value = value self.total = total def to_counter(self) -> str: @@ -170,6 +146,13 @@ class StoredCounter(StoredBase): def get_remain(self) -> int: return self.total - self.value + @cached_property + def _attrs(self) -> dict: + attrs = super()._attrs + if self.FIXED_TOTAL: + attrs['total'] = self.FIXED_TOTAL + return attrs + @functools_cached_property def _stored(self): stored = super()._stored diff --git a/module/webui/app.py b/module/webui/app.py index 181d966f0..5c25cdd05 100644 --- a/module/webui/app.py +++ b/module/webui/app.py @@ -88,11 +88,13 @@ task_handler = TaskHandler() class AlasGUI(Frame): ALAS_MENU: Dict[str, Dict[str, List[str]]] ALAS_ARGS: Dict[str, Dict[str, Dict[str, Dict[str, str]]]] + ALAS_STORED: Dict[str, Dict[str, Dict[str, str]]] theme = "default" def initial(self) -> None: self.ALAS_MENU = read_file(filepath_args("menu", self.alas_mod)) self.ALAS_ARGS = read_file(filepath_args("args", self.alas_mod)) + self.ALAS_STORED = read_file(filepath_args("stored", self.alas_mod)) self._init_alas_config_watcher() def __init__(self) -> None: @@ -318,6 +320,35 @@ class AlasGUI(Frame): color="navigator", ) + def set_dashboard(self, arg, arg_dict, config): + i18n = arg_dict.get('i18n') + if i18n: + name = t(i18n) + else: + name = arg + color = arg_dict.get("color", "#777777") + nodata = t("Gui.Dashboard.NoData") + + def set_value(dic): + if "total" in dic.get("attrs", []) and config.get("total") is not None: + return [ + put_text(config.get("value", nodata)).style("--dashboard-value--"), + put_text(f' / {config.get("total", "")}').style("--dashboard-time--"), + ] + else: + return [ + put_text(config.get("value", nodata)).style("--dashboard-value--"), + ] + + with use_scope(f"dashboard-row-{arg}", clear=True): + put_html(f'