mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-22 08:37:42 +00:00
Add: Dashboard on GUI
This commit is contained in:
parent
9e2445d5b1
commit
a087189e54
@ -59,4 +59,11 @@
|
||||
#pywebio-scope-waiting,
|
||||
#pywebio-scope-log {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[id^="pywebio-scope-dashboard-row-"] {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 4rem;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -152,4 +152,8 @@ pre.rich-traceback-code {
|
||||
*[style*="--arg-help--"],
|
||||
[id^="pywebio-scope-group_"] > p + p {
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
*[style*="--dashboard-time--"] {
|
||||
color: #adb5bd;
|
||||
}
|
@ -151,4 +151,8 @@ pre.rich-traceback-code {
|
||||
*[style*="--arg-help--"],
|
||||
[id^="pywebio-scope-group_"] > p + p {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
*[style*="--dashboard-time--"] {
|
||||
color: #777777;
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -52,6 +52,16 @@ Overview:
|
||||
Waiting:
|
||||
NoTask:
|
||||
|
||||
Dashboard:
|
||||
# From lang.readable_time()
|
||||
NoData:
|
||||
TimeError:
|
||||
JustNow:
|
||||
MinutesAgo:
|
||||
HoursAgo:
|
||||
DaysAgo:
|
||||
LongTimeAgo:
|
||||
|
||||
AddAlas:
|
||||
PopupTitle:
|
||||
NewName:
|
||||
|
84
module/config/argument/stored.json
Normal file
84
module/config/argument/stored.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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",
|
||||
|
@ -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": "コンフィグ名",
|
||||
|
@ -695,6 +695,15 @@
|
||||
"Waiting": "等待中",
|
||||
"NoTask": "无任务"
|
||||
},
|
||||
"Dashboard": {
|
||||
"NoData": "无数据",
|
||||
"TimeError": "时间错误",
|
||||
"JustNow": "刚刚",
|
||||
"MinutesAgo": "{time}分钟前",
|
||||
"HoursAgo": "{time}小时前",
|
||||
"DaysAgo": "{time}天前",
|
||||
"LongTimeAgo": "很久以前"
|
||||
},
|
||||
"AddAlas": {
|
||||
"PopupTitle": "添加新配置",
|
||||
"NewName": "新的配置文件名",
|
||||
|
@ -695,6 +695,15 @@
|
||||
"Waiting": "等待中",
|
||||
"NoTask": "無任務"
|
||||
},
|
||||
"Dashboard": {
|
||||
"NoData": "無數據",
|
||||
"TimeError": "時間錯誤",
|
||||
"JustNow": "剛剛",
|
||||
"MinutesAgo": "{time}分鐘前",
|
||||
"HoursAgo": "{time}小時前",
|
||||
"DaysAgo": "{time}天前",
|
||||
"LongTimeAgo": "很久以前"
|
||||
},
|
||||
"AddAlas": {
|
||||
"PopupTitle": "添加新的設定",
|
||||
"NewName": "新設定的檔案名",
|
||||
|
@ -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
|
||||
|
@ -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'<div><div class="dashboard-icon" style="background-color:{color}"></div>'),
|
||||
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('<hr class="hr-group">'),
|
||||
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:
|
||||
|
@ -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:
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user