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 1/3] 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'
'), + put_scope(f"dashboard-content-{arg}", [ + put_scope(f"dashboard-value-{arg}", set_value(arg_dict)), + put_scope(f"dashboard-time-{arg}", [ + put_text(f"{name} - {lang.readable_time(config.get('time', ''))}").style("--dashboard-time--"), + ]) + ]) + @use_scope("content", clear=True) def alas_overview(self) -> None: self.init_menu(name="Overview") @@ -374,20 +405,19 @@ class AlasGUI(Frame): log = RichLog("log") with use_scope("logs"): - put_scope( - "log-bar", - [ - put_text(t("Gui.Overview.Log")).style( - "font-size: 1.25rem; margin: auto .5rem auto;" - ), - put_scope( - "log-bar-btns", - [ - put_scope("log_scroll_btn"), - ], - ), - ], - ), + put_scope("log-bar", [ + put_scope("log-title", [ + put_text(t("Gui.Overview.Log")).style("font-size: 1.25rem; margin: auto .5rem auto;"), + put_scope("log-title-btns", [ + put_scope("log_scroll_btn"), + ]), + ]), + put_html('
'), + put_scope("dashboard", [ + # Empty dashboard, values will be updated in alas_update_overview_task() + put_scope(f"dashboard-row-{arg}", []) for arg in self.ALAS_STORED.keys() + ]) + ]) put_scope("log", [put_html("")]) log.console.width = log.get_width() @@ -501,6 +531,7 @@ class AlasGUI(Frame): self.alas_config.load() self.alas_config.get_next_task() + alive = self.alas.alive if len(self.alas_config.pending_task) >= 1: if self.alas.alive: running = self.alas_config.pending_task[:1] @@ -528,27 +559,39 @@ class AlasGUI(Frame): color="off", ) - clear("running_tasks") - clear("pending_tasks") - clear("waiting_tasks") - with use_scope("running_tasks"): - if running: - for task in running: - put_task(task) - else: - put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") - with use_scope("pending_tasks"): - if pending: - for task in pending: - put_task(task) - else: - put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") - with use_scope("waiting_tasks"): - if waiting: - for task in waiting: - put_task(task) - else: - put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") + if self.scope_expired("scheduler_alive", alive) \ + or self.scope_expired("pending_task", self.alas_config.pending_task): + clear("running_tasks") + clear("pending_tasks") + clear("waiting_tasks") + with use_scope("running_tasks"): + if running: + for task in running: + put_task(task) + else: + put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") + with use_scope("pending_tasks"): + if pending: + for task in pending: + put_task(task) + else: + put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") + with use_scope("waiting_tasks"): + if waiting: + for task in waiting: + put_task(task) + else: + put_text(t("Gui.Overview.NoTask")).style("--overview-notask-text--") + self.scope_add("scheduler_alive", alive) + self.scope_add("pending_task", self.alas_config.pending_task) + + for arg, arg_dict in self.ALAS_STORED.items(): + path = arg_dict["path"] + if self.scope_expired_then_add( + key=f"dashboard-time-value-{arg}", + value=lang.readable_time(deep_get(self.alas_config.data, keys=f"{path}.time")) + ): + self.set_dashboard(arg, arg_dict, deep_get(self.alas_config.data, keys=path, default={})) @use_scope("content", clear=True) def alas_daemon_overview(self, task: str) -> None: diff --git a/module/webui/base.py b/module/webui/base.py index 0f43cbeba..29619551b 100644 --- a/module/webui/base.py +++ b/module/webui/base.py @@ -1,3 +1,5 @@ +from typing import Any, Dict + from pywebio.output import clear, put_html, put_scope, put_text, use_scope from pywebio.session import defer_call, info, run_js @@ -13,12 +15,34 @@ class Base: self.is_mobile = info.user_agent.is_mobile # Task handler self.task_handler = WebIOTaskHandler() + # Record scopes to reduce data transfer to frontend + # Key: scope name, value: last update time + self.scope: Dict[str, Any] = {} defer_call(self.stop) def stop(self) -> None: self.alive = False self.task_handler.stop() + def scope_clear(self): + self.scope = {} + + def scope_add(self, key, value): + self.scope[key] = value + + def scope_expired(self, key, value) -> bool: + try: + return self.scope[key] != value + except KeyError: + return True + + def scope_expired_then_add(self, key, value) -> bool: + if self.scope_expired(key, value): + self.scope_add(key, value) + return True + else: + return False + class Frame(Base): def __init__(self) -> None: @@ -33,6 +57,7 @@ class Frame(Base): name: button name(label) to be highlight """ self.visible = True + self.scope_clear() self.task_handler.remove_pending_task() clear("menu") if expand_menu: @@ -50,6 +75,7 @@ class Frame(Base): """ self.visible = True self.page = name + self.scope_clear() self.task_handler.remove_pending_task() clear("content") if collapse_menu: diff --git a/module/webui/lang.py b/module/webui/lang.py index 06c5c5dd5..c1134f034 100644 --- a/module/webui/lang.py +++ b/module/webui/lang.py @@ -1,8 +1,9 @@ +import time from typing import Dict from module.config.utils import * -from module.webui.setting import State from module.webui.fake import list_mod +from module.webui.setting import State LANG = "zh-CN" TRANSLATE_MODE = False @@ -67,3 +68,33 @@ def reload(): for key in dic_lang["ja-JP"].keys(): if dic_lang["ja-JP"][key] == key: dic_lang["ja-JP"][key] = dic_lang["en-US"][key] + + +def readable_time(before: str) -> str: + """ + Convert "2023-08-29 12:30:53" to "3 Minutes Ago" + """ + if not before: + return t("Gui.Dashboard.NoData") + try: + ti = datetime.fromisoformat(before) + except ValueError: + return t("Gui.Dashboard.TimeError") + if ti == DEFAULT_TIME: + return t("Gui.Dashboard.NoData") + + diff = time.time() - ti.timestamp() + if diff < -1: + return t("Gui.Dashboard.TimeError") + elif diff < 60: + # < 1 min + return t("Gui.Dashboard.JustNow") + elif diff < 3600: + return t("Gui.Dashboard.MinutesAgo", time=int(diff // 60)) + elif diff < 86400: + return t("Gui.Dashboard.HoursAgo", time=int(diff // 3600)) + elif diff < 129600: + return t("Gui.Dashboard.DaysAgo", time=int(diff // 86400)) + else: + # > 15 days + return t("Gui.Dashboard.LongTimeAgo") From 246c039dc8bfe9cb894ab48108b49aa22e176dbc Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:14:56 +0800 Subject: [PATCH 2/3] Add: Assigment and Sim.Uni. in dashboard --- assets/gui/css/alas-pc.css | 10 +++++++ assets/gui/css/alas.css | 27 ++++++++++--------- assets/share/dungeon/ui/OCR_SIMUNI_POINT.png | Bin 0 -> 21261 bytes config/template.json | 6 +++-- module/config/argument/args.json | 16 +++++++++++ module/config/argument/argument.yaml | 10 ++++++- module/config/config_generated.py | 2 ++ module/config/i18n/en-US.json | 8 ++++++ module/config/i18n/ja-JP.json | 8 ++++++ module/config/i18n/zh-CN.json | 8 ++++++ module/config/i18n/zh-TW.json | 8 ++++++ module/config/stored/classes.py | 8 ++++++ module/config/stored/stored_generated.py | 4 +++ module/webui/app.py | 3 ++- tasks/assignment/ui.py | 9 ++++++- tasks/dungeon/assets/assets_dungeon_ui.py | 10 +++++++ tasks/dungeon/dungeon.py | 3 +++ tasks/dungeon/ui.py | 22 ++++++++++++++- 18 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 assets/share/dungeon/ui/OCR_SIMUNI_POINT.png diff --git a/assets/gui/css/alas-pc.css b/assets/gui/css/alas-pc.css index 0adfa2591..a3873f49e 100644 --- a/assets/gui/css/alas-pc.css +++ b/assets/gui/css/alas-pc.css @@ -26,6 +26,16 @@ overflow-y: auto; } +#pywebio-scope-log-bar { + display: flex; + flex-direction: column; + height: 11.5rem; +} + +#pywebio-scope-dashboard { + flex: 1; +} + #pywebio-scope-log-bar, #pywebio-scope-log, #pywebio-scope-daemon-overview #pywebio-scope-groups { diff --git a/assets/gui/css/alas.css b/assets/gui/css/alas.css index 5fc57f5d4..b7128f4ae 100644 --- a/assets/gui/css/alas.css +++ b/assets/gui/css/alas.css @@ -394,15 +394,25 @@ pre.rich-traceback-code { grid-auto-flow: column; } -#pywebio-scope-log-bar { - height: 11.5rem; -} - #pywebio-scope-dashboard { display: flex; - align-content: flex-start; + align-content: space-between; + justify-content: flex-start; flex-flow: row wrap; overflow: auto; + margin-top: .5rem; +} + +#pywebio-scope-dashboard > i { + flex-grow: 1; + align-self: flex-end; + width: 10rem; +} + +[id^="pywebio-scope-dashboard-row-"] { + display: flex; + flex-grow: 1; + width: 10rem; } .dashboard-icon { @@ -412,13 +422,6 @@ pre.rich-traceback-code { 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; diff --git a/assets/share/dungeon/ui/OCR_SIMUNI_POINT.png b/assets/share/dungeon/ui/OCR_SIMUNI_POINT.png new file mode 100644 index 0000000000000000000000000000000000000000..f67fa87cb941e7072972339390e1000ce0e9c923 GIT binary patch literal 21261 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 literal 0 HcmV?d00001 diff --git a/config/template.json b/config/template.json index 6ec1415f7..c05b72df5 100644 --- a/config/template.json +++ b/config/template.json @@ -57,7 +57,8 @@ }, "DungeonStorage": { "TrailblazePower": {}, - "DungeonDouble": {} + "DungeonDouble": {}, + "SimulatedUniverse": {} } }, "DailyQuest": { @@ -119,7 +120,8 @@ "Name_2": "Akashic_Records", "Name_3": "The_Invisible_Hand", "Name_4": "Nine_Billion_Names", - "Duration": 20 + "Duration": 20, + "Assignment": {} } } } \ No newline at end of file diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 97694e31c..4d3f4208e 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -373,6 +373,14 @@ "value": {}, "display": "hide", "stored": "StoredDungeonDouble" + }, + "SimulatedUniverse": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredSimulatedUniverse", + "order": 5, + "color": "#8fb5fe" } } }, @@ -935,6 +943,14 @@ 12, 20 ] + }, + "Assignment": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredAssignment", + "order": 3, + "color": "#deba95" } } } diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 7d0c54b74..383436c54 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -115,6 +115,10 @@ DungeonStorage: color: "#eb8efe" DungeonDouble: stored: StoredDungeonDouble + SimulatedUniverse: + stored: StoredSimulatedUniverse + order: 5 + color: "#8fb5fe" AchievableQuest: # Quests will be injected in config updater @@ -147,4 +151,8 @@ Assignment: option: [ Nameless_Land_Nameless_People, ] Duration: value: 20 - option: [ 4, 8, 12, 20 ] \ No newline at end of file + option: [ 4, 8, 12, 20 ] + Assignment: + stored: StoredAssignment + order: 3 + color: "#deba95" diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 34647ddbe..bdba746c7 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -57,6 +57,7 @@ class GeneratedConfig: # Group `DungeonStorage` DungeonStorage_TrailblazePower = {} DungeonStorage_DungeonDouble = {} + DungeonStorage_SimulatedUniverse = {} # Group `AchievableQuest` AchievableQuest_Complete_1_Daily_Mission = 'achievable' # achievable, not_set, not_supported @@ -95,3 +96,4 @@ class GeneratedConfig: Assignment_Name_3 = 'The_Invisible_Hand' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm Assignment_Duration = 20 # 4, 8, 12, 20 + Assignment_Assignment = {} diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index c3eba38d1..31ba90310 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "Dungeon Double", "help": "" + }, + "SimulatedUniverse": { + "name": "Sim.Uni.", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8 Hours", "12": "12 Hours", "20": "20 Hours" + }, + "Assignment": { + "name": "Assignment", + "help": "" } }, "Gui": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 7e6199cc9..e5d789860 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "DungeonStorage.DungeonDouble.name", "help": "DungeonStorage.DungeonDouble.help" + }, + "SimulatedUniverse": { + "name": "DungeonStorage.SimulatedUniverse.name", + "help": "DungeonStorage.SimulatedUniverse.help" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8", "12": "12", "20": "20" + }, + "Assignment": { + "name": "Assignment.Assignment.name", + "help": "Assignment.Assignment.help" } }, "Gui": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index c7ecf1396..ddcf77838 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "副本双倍", "help": "" + }, + "SimulatedUniverse": { + "name": "模拟宇宙", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8小时", "12": "12小时", "20": "20小时" + }, + "Assignment": { + "name": "委托", + "help": "" } }, "Gui": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index aa2a45450..49272650a 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "副本雙倍", "help": "" + }, + "SimulatedUniverse": { + "name": "模擬宇宙", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8小時", "12": "12小時", "20": "20小時" + }, + "Assignment": { + "name": "委託", + "help": "" } }, "Gui": { diff --git a/module/config/stored/classes.py b/module/config/stored/classes.py index c1df02fb9..a56e86a49 100644 --- a/module/config/stored/classes.py +++ b/module/config/stored/classes.py @@ -169,6 +169,14 @@ class StoredTrailblazePower(StoredCounter): FIXED_TOTAL = 180 +class StoredSimulatedUniverse(StoredCounter, StoredExpiredAt0400): + pass + + +class StoredAssignment(StoredCounter): + pass + + class StoredDaily(StoredExpiredAt0400): quest1 = '' quest2 = '' diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index 95462b4aa..18f8fe087 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -1,4 +1,5 @@ from module.config.stored.classes import ( + StoredAssignment, StoredBase, StoredCounter, StoredDaily, @@ -6,6 +7,7 @@ from module.config.stored.classes import ( StoredDungeonDouble, StoredExpiredAt0400, StoredInt, + StoredSimulatedUniverse, StoredTrailblazePower, ) @@ -16,5 +18,7 @@ from module.config.stored.classes import ( class StoredGenerated: TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower") DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble") + SimulatedUniverse = StoredSimulatedUniverse("Dungeon.DungeonStorage.SimulatedUniverse") DailyActivity = StoredDailyActivity("DailyQuest.DailyStorage.DailyActivity") DailyQuest = StoredDaily("DailyQuest.DailyStorage.DailyQuest") + Assignment = StoredAssignment("Assignment.Assignment.Assignment") diff --git a/module/webui/app.py b/module/webui/app.py index 5c25cdd05..d3d07d68f 100644 --- a/module/webui/app.py +++ b/module/webui/app.py @@ -416,7 +416,8 @@ class AlasGUI(Frame): put_scope("dashboard", [ # Empty dashboard, values will be updated in alas_update_overview_task() put_scope(f"dashboard-row-{arg}", []) for arg in self.ALAS_STORED.keys() - ]) + # Empty content to left-align last row + ] + [put_html("")] * len(self.ALAS_STORED)) ]) put_scope("log", [put_html("")]) diff --git a/tasks/assignment/ui.py b/tasks/assignment/ui.py index ae57bc05e..cadd5d19a 100644 --- a/tasks/assignment/ui.py +++ b/tasks/assignment/ui.py @@ -145,7 +145,14 @@ class AssignmentUI(UI): @property def _limit_status(self) -> tuple[int, int, int]: self.device.screenshot() - return DigitCounter(OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) + current, remain, total = DigitCounter(OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) + if total and current <= total: + logger.attr('Assignment', f'{current}/{total}') + self.config.stored.Assignment.set(current, total) + else: + logger.warning(f'Invalid assignment limit: {current}/{total}') + self.config.stored.Assignment.set(0, 0) + return current, remain, total def _iter_groups(self) -> Iterator[AssignmentGroup]: for state in ASSIGNMENT_TOP_SWITCH.state_list: diff --git a/tasks/dungeon/assets/assets_dungeon_ui.py b/tasks/dungeon/assets/assets_dungeon_ui.py index 1a1d5adfe..5cf5b0695 100644 --- a/tasks/dungeon/assets/assets_dungeon_ui.py +++ b/tasks/dungeon/assets/assets_dungeon_ui.py @@ -53,6 +53,16 @@ OCR_DUNGEON_NAV = ButtonWrapper( button=(108, 132, 428, 613), ), ) +OCR_SIMUNI_POINT = ButtonWrapper( + name='OCR_SIMUNI_POINT', + share=Button( + file='./assets/share/dungeon/ui/OCR_SIMUNI_POINT.png', + area=(580, 190, 880, 235), + search=(560, 170, 900, 255), + color=(158, 164, 252), + button=(580, 190, 880, 235), + ), +) OPERATION_BRIEFING_CHECK = ButtonWrapper( name='OPERATION_BRIEFING_CHECK', share=Button( diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index 0463f464e..7185ba774 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -143,6 +143,9 @@ class Dungeon(DungeonUI, DungeonEvent, Combat): with self.config.multi_set(): self.config.stored.DungeonDouble.calyx = calyx self.config.stored.DungeonDouble.relic = relic + # Update SimulatedUniverse points + logger.info('Get simulated universe points') + self.dungeon_get_simuni_point() # Run double events ran_calyx_golden = False diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py index ab4c8e388..f06481bd7 100644 --- a/tasks/dungeon/ui.py +++ b/tasks/dungeon/ui.py @@ -5,7 +5,7 @@ from module.base.button import ClickButton from module.base.timer import Timer from module.base.utils import get_color from module.logger import logger -from module.ocr.ocr import Ocr, OcrResultButton +from module.ocr.ocr import DigitCounter, Ocr, OcrResultButton from module.ocr.utils import split_and_pair_button_attr from module.ui.draggable_list import DraggableList from module.ui.switch import Switch @@ -74,6 +74,10 @@ class OcrDungeonList(Ocr): return result +class OcrSimUniPoint(DigitCounter): + merge_thres_x = 50 + + class OcrDungeonListLimitEntrance(OcrDungeonList): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -219,6 +223,22 @@ class DungeonUI(UI): logger.info('DungeonNav row Forgotten_Hall stabled') return True + def dungeon_get_simuni_point(self) -> int: + """ + Page: + in: page_guide, Survival_Index, Simulated_Universe + """ + ocr = OcrSimUniPoint(OCR_SIMUNI_POINT) + value, _, total = ocr.ocr_single_line(self.device.image) + if total and value <= total: + logger.attr('SimulatedUniverse', f'{value}/{total}') + self.config.stored.SimulatedUniverse.set(value, total) + return value + else: + logger.warning(f'Invalid SimulatedUniverse points: {value}/{total}') + self.config.stored.SimulatedUniverse.set(0, 0) + return 0 + def _dungeon_nav_goto(self, dungeon: DungeonList, skip_first_screenshot=True): """ Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)` From eb28485d5df22c20c8bfc9c7df5c4e6ef193152a Mon Sep 17 00:00:00 2001 From: X-Zero-L <98764734+X-Zero-L@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:58:32 +0800 Subject: [PATCH 3/3] Fix: support (#64) * Fix: support * Upd: support * Upd: support * Upd: support * Upd: support * Upd: support * Upd: support * Upd: support --------- Co-authored-by: LmeSzinc --- assets/support/selected_character_arrow.png | Bin 0 -> 668 bytes tasks/combat/support.py | 170 ++++++++++++++++++-- 2 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 assets/support/selected_character_arrow.png diff --git a/assets/support/selected_character_arrow.png b/assets/support/selected_character_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8e079c820c33a370a4cffc69aef0c4dde00c0cc2 GIT binary patch literal 668 zcmV;N0%QG&P)}>`&0007GNklzu9h;NR^W0iTo-|HSA=0fh_aHwz97ky$p_KNY_ z?N-Om=X1GUuhV$EM-<8o)=eQNjE{LXK~Mk!6ex(Sr35IUir6p4Pp8wzKI{?mYPjmC z@<(UR3E+(pMcB`j^)X0_$LCuJ`9ze;LXH~ldZ9-+3NNcX)>IqHLT?bZt?&(D5fvIi zN>xA)o{3GeOkGw)l7nz>k*?83ULZ<{M#qO0E#rw0_hJ^_cv}sf z^V%Hn5~Q}|gk#DNNq$sHl6Y(K%!KKfX4YU7?*l&`MIu1U%T~iPuebDGy5C5M7V#oy z!v$+s)dKT08cRF7mco$UGsx#%Pi)jW-}U?w#(T=FX=}4F&U=plqHSrEyq$UNi?@O^ zsx^B}w$_YIx3)yD%y5pMMstokzLp9!<*_13OD`yY zg}F8~yFc{c6bxP&i*%A?W~s0Ek`lCm(-}z3j 0.75 + else None + ) + + +class NextSupportCharacter: + _arrow = ArrowWrapper( + name="NextSupportCharacterArrow", + share=Button( + file='./assets/support/selected_character_arrow.png', + area=None, + search=None, + color=None, + button=None, + ) + ) + _crop_area = (290, 115, 435, 634) + + def __init__(self, screenshot): + self.name = "SupportCharacterArrow" + self.screenshot = crop(screenshot, NextSupportCharacter._crop_area) + self.arrow_center = self._find_center() + self.button = self._get_next_support_character_button() + + def __bool__(self): + return self.button is not None + + def _find_center(self): + center = NextSupportCharacter._arrow.find_center(self.screenshot) + center = get_position_in_original_image( + center, NextSupportCharacter._crop_area) if center else None + return center + + def _get_next_support_character_button(self): + area = (self.arrow_center[0] - 200, min(self.arrow_center[1] + 65, 615), self.arrow_center[0] + 10, min( + self.arrow_center[1] + 80, 620)) if self.arrow_center and self.arrow_center[1] < 510 else None + return ButtonWrapper( + name="NextSupportCharacterButton", + share=Button( + file='./assets/support/selected_character_arrow.png', + area=area, + search=area, + # if next support was selected, the average color of the button will larger than 220 + color=(220, 220, 220), + button=area, + ) + ) if self.arrow_center and self.arrow_center[1] < 510 else None + + class SupportListScroll(Scroll): def cal_position(self, main): """ @@ -130,11 +205,18 @@ class CombatSupport(UI): return True # Click - if self.appear(COMBAT_TEAM_SUPPORT, interval=2): + if self.appear(COMBAT_TEAM_SUPPORT, interval=1): self.device.click(COMBAT_TEAM_SUPPORT) self.interval_reset(COMBAT_TEAM_SUPPORT) continue - if self.appear(COMBAT_SUPPORT_LIST, interval=2): + if self.appear(CANCEL_POPUP, interval=1): + logger.warning( + "selected identical character, trying select another") + self._cancel_popup() + self._select_next_support() + self.interval_reset(CANCEL_POPUP) + continue + if self.appear(COMBAT_SUPPORT_LIST, interval=1): if support_character_name != "FirstCharacter": self._search_support( support_character_name) # Search support @@ -167,7 +249,6 @@ class CombatSupport(UI): logger.info("Searching support") skip_first_screenshot = False - character = None while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -175,7 +256,8 @@ class CombatSupport(UI): self.device.screenshot() if not support_character_name.startswith("Trailblazer"): - character = SupportCharacter(support_character_name, self.device.image) + character = SupportCharacter( + support_character_name, self.device.image) else: character = SupportCharacter(f"Stelle{support_character_name[11:]}", self.device.image) or SupportCharacter( @@ -223,3 +305,73 @@ class CombatSupport(UI): self.device.click(character) interval.reset() continue + + def _cancel_popup(self): + """ + Pages: + in: CANCEL_POPUP + out: COMBAT_SUPPORT_LIST + """ + logger.hr("Combat support cancel popup") + skip_first_screenshot = True + + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if self.appear(COMBAT_SUPPORT_LIST): + logger.info("Popup canceled") + return + + if self.appear(CANCEL_POPUP): + self.device.click(CANCEL_POPUP) + continue + + def _select_next_support(self): + """ + Pages: + in: COMBAT_SUPPORT_LIST + out: COMBAT_SUPPORT_LIST + """ + skip_first_screenshot = True + scroll = SupportListScroll(area=COMBAT_SUPPORT_LIST_SCROLL.area, color=(194, 196, 205), + name=COMBAT_SUPPORT_LIST_SCROLL.name) + interval = Timer(1) + next_support = None + if scroll.appear(main=self): + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if next_support and self._next_support_selected(next_support): + return + + if interval.reached(): + next_support = NextSupportCharacter(self.device.image) + if next_support: + logger.info("Next support found, clicking") + self.device.click(next_support.button) + elif not scroll.at_bottom(main=self): + scroll.next_page(main=self, page=0.4) + else: + logger.warning("No more support") + return + + interval.reset() + continue + + def _next_support_selected(self, next_support: NextSupportCharacter): + """ + Returns: + bool: True if selected else False + """ + if self.match_color(next_support.button, threshold=20): + logger.info("Next support selected") + return True + return False