Opt: Validate value before save into config

This commit is contained in:
18870 2021-11-14 23:57:28 +08:00
parent 1921ffe0fa
commit e84f7c3153
16 changed files with 379 additions and 148 deletions

View File

@ -6,11 +6,16 @@
display: none;
}
.container-log {
font-size: .75rem !important;
}
*[style*="container-content"] {
padding: .25rem;
margin: 0
}
.container-log {
font-size: .75rem !important;
}
*[style*="container-args-row"],
*[style*="container-large.args"] {
grid-template-columns: 1fr 8rem !important;
}

View File

@ -0,0 +1,4 @@
*[style*="container-args-row"],
*[style*="container-large.args"] {
grid-template-columns: 1fr 13rem !important;
}

View File

@ -145,11 +145,19 @@ select {
background-size: 1rem;
}
select.form-control {
padding-right: 1rem;
}
select.form-control.is-invalid {
padding-right: 3rem !important;
background-position: right 1.5rem center;
}
.invalid-feedback {
margin-top: 0;
}
.CodeMirror {
height: auto !important;
}
@ -203,7 +211,7 @@ select.form-control.is-invalid {
}
*[style*="title-icon-alas"] {
margin: .25rem 2rem .25rem 1rem;
margin: .25rem auto .25rem;
border-radius: 1.5rem;
}
@ -236,7 +244,7 @@ select.form-control.is-invalid {
*[style*="container-title"] {
z-index: 100;
grid-template-columns: 5.6rem 3.75rem 8rem minmax(5rem, 1fr) !important;
grid-template-columns: 4.4rem 4rem auto 1fr !important;
}
*[style*="aside-icon-setting"] {
@ -291,7 +299,8 @@ select.form-control.is-invalid {
height: 100%;
}
*[style*="container-args"] {
*[style*="container-args-column"],
*[style*="container-args-row"] {
margin: .125rem 0 .125rem 0
}

82
gui.py
View File

@ -5,12 +5,17 @@ import time
from datetime import datetime
from multiprocessing import Manager, Process
from multiprocessing.managers import SyncManager
from typing import Generator
from typing import Dict, Generator, List
from filelock import FileLock
from pywebio import config as webconfig
from pywebio.exceptions import SessionClosedException, SessionNotFoundException
from pywebio.session import go_app, info, register_thread, set_env
from pywebio.output import (clear, close_popup, output, popup, put_button,
put_buttons, put_collapse, put_column, put_error,
put_html, put_loading, put_markdown, put_row,
put_text, toast)
from pywebio.pin import pin, pin_wait_change
from pywebio.session import go_app, info, register_thread, run_js, set_env
# This must be the first to import
from module.logger import logger # Change folder
@ -23,12 +28,13 @@ from module.config.utils import (alas_instance, deep_get, deep_iter, deep_set,
read_file, write_file)
from module.webui.base import Frame
from module.webui.lang import _t, t
from module.webui.pin import put_input, put_select
from module.webui.translate import translate
from module.webui.utils import (Icon, QueueHandler, Task, Thread, add_css,
filepath_css, get_output,
get_window_visibility_state, login,
parse_pin_value)
from module.webui.widgets import *
parse_pin_value, re_fullmatch)
from module.webui.widgets import ScrollableCode, put_group, put_icon_buttons
config_updater = ConfigUpdater()
@ -152,7 +158,7 @@ class AlasGUI(Frame):
self.modified_config_queue = queue.Queue()
# alas config name
self.alas_name = ''
self.alas_config = AzurLaneConfig('template', '')
self.alas_config = AzurLaneConfig('template')
self.alas_logs = ScrollableCode()
self.alas_running = output().style("container-overview-task")
self.alas_pending = output().style("container-overview-task")
@ -314,10 +320,8 @@ class AlasGUI(Frame):
if arg_help == "" or not arg_help:
arg_help = None
if self.is_mobile:
width = '8rem'
else:
width = '13rem'
# Invalid feedback
invalid_feedback = t("Gui.Text.InvalidFeedBack").format(d['value'])
list_arg.append(get_output(
arg_type=arg_type,
@ -326,7 +330,7 @@ class AlasGUI(Frame):
arg_help=arg_help,
value=value,
options=option,
width=width,
invalid_feedback=invalid_feedback,
))
if list_arg:
group_area.append(arg_group)
@ -463,6 +467,8 @@ class AlasGUI(Frame):
def _alas_thread_update_config(self) -> None:
modified = {}
valid = []
invalid = []
while self.alive:
try:
d = self.modified_config_queue.get(timeout=10)
@ -476,13 +482,28 @@ class AlasGUI(Frame):
modified[self.idx_to_path[d['name']]] = parse_pin_value(d['value'])
except queue.Empty:
config = read_file(filepath_config(config_name))
for k in modified.keys():
deep_set(config, k, modified[k])
for k, v in modified.copy().items():
validate = deep_get(self.ALAS_ARGS, k + '.validate')
if not validate or re_fullmatch(validate, v):
deep_set(config, k, v)
valid.append(self.path_to_idx[k])
else:
modified.pop(k)
invalid.append(self.path_to_idx[k])
logger.warning(f'Invalid value {v} for key {k}, skip saving.')
# toast(t("Gui.Toast.InvalidConfigValue").format(
# t('.'.join(k.split('.')[1:] + ['name']))),
# duration=0, position='right', color='warn')
logger.info(f'Save config {filepath_config(config_name)}, {dict_to_kv(modified)}')
write_file(filepath_config(config_name), config)
toast(t("Gui.Toast.ConfigSaved"),
duration=1, position='right', color='success')
modified = {}
self.pin_remove_invalid_mark(valid)
self.pin_set_invalid_mark(invalid)
if len(modified):
toast(t("Gui.Toast.ConfigSaved"),
duration=1, position='right', color='success')
modified.clear()
valid.clear()
invalid.clear()
break
def alas_update_status(self) -> None:
@ -663,11 +684,6 @@ class AlasGUI(Frame):
if arg_help == "" or not arg_help:
arg_help = None
if self.is_mobile:
width = '8rem'
else:
width = '13rem'
list_arg.append(get_output(
arg_type=arg_type,
name=self.path_to_idx[f"{task}.{group}.{arg}"],
@ -675,7 +691,6 @@ class AlasGUI(Frame):
arg_help=arg_help,
value=value,
options=option,
width=width,
))
if list_arg:
setting.append(*list_arg)
@ -683,6 +698,7 @@ class AlasGUI(Frame):
self.task_handler.add(self.alas_put_log(), 0.2, True)
# Develop
def dev_set_menu(self) -> None:
self.init_menu(collapse_menu=False, name="Develop")
@ -715,6 +731,9 @@ class AlasGUI(Frame):
self.title.reset(f"{t('Gui.Aside.Develop')}")
self.dev_set_menu()
self.alas_name = ''
if hasattr(self, 'alas'):
del self.alas
self.set_status(0)
def ui_alas(self, config_name: str) -> None:
if config_name == self.alas_name:
@ -849,11 +868,13 @@ class AlasGUI(Frame):
add_css(filepath_css('alas'))
if self.is_mobile:
add_css(filepath_css('alas-mobile'))
else:
add_css(filepath_css('alas-pc'))
if self.theme == 'default':
add_css(filepath_css('light-alas'))
elif self.theme == 'dark':
if self.theme == 'dark':
add_css(filepath_css('dark-alas'))
else:
add_css(filepath_css('light-alas'))
self.show()
@ -895,6 +916,19 @@ class AlasGUI(Frame):
self.task_handler.start()
def debug():
"""For interactive python.
$ python3
>>> from gui import *
>>> debug()
>>>
"""
AlasManager.sync_manager = Manager()
AlasGUI.shorten_path()
lang.reload()
AlasGUI().run()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Alas web service')
parser.add_argument("-d", "--debug", action="store_true",

View File

@ -163,7 +163,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -191,7 +192,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -432,7 +434,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -463,7 +466,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -532,7 +536,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -815,7 +820,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -846,7 +852,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -915,7 +922,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -1043,7 +1051,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -1074,7 +1083,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -1109,7 +1119,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -1328,7 +1339,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -1359,7 +1371,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -1394,7 +1407,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -1613,7 +1627,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -1644,7 +1659,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -1713,7 +1729,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -1966,7 +1983,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -1997,7 +2015,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -2066,7 +2085,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2337,7 +2357,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -2368,7 +2389,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -2437,7 +2459,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2726,7 +2749,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -2757,7 +2781,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -2792,7 +2817,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2830,7 +2856,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2864,7 +2891,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2920,7 +2948,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -2962,7 +2991,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3004,7 +3034,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3060,7 +3091,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3110,7 +3142,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3152,7 +3185,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3329,7 +3363,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3371,7 +3406,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3432,7 +3468,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3460,7 +3497,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3592,7 +3630,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3634,7 +3673,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3686,7 +3726,8 @@
},
"OpponentRefreshRecord": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
}
}
},
@ -3698,7 +3739,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -3955,7 +3997,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -3986,7 +4029,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -4055,7 +4099,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -4348,7 +4393,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -4379,7 +4425,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -4448,7 +4495,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -4741,7 +4789,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -4772,7 +4821,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -4841,7 +4891,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5124,7 +5175,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -5155,7 +5207,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -5224,7 +5277,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5343,7 +5397,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -5374,7 +5429,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -5409,7 +5465,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5662,7 +5719,8 @@
},
"Fleet1Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet1Control": {
"type": "select",
@ -5693,7 +5751,8 @@
},
"Fleet2Record": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Fleet2Control": {
"type": "select",
@ -5790,7 +5849,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5824,7 +5884,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5862,7 +5923,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5900,7 +5962,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",
@ -5942,7 +6005,8 @@
},
"NextRun": {
"type": "input",
"value": "2020-01-01 00:00:00"
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "disable",

View File

@ -6,7 +6,9 @@
Scheduler:
Enable: false
NextRun: 2020-01-01 00:00:00
NextRun:
value: 2020-01-01 00:00:00
validate: datetime
Command: Alas
SuccessInterval: 0
FailureInterval: 120
@ -126,7 +128,9 @@ Emotion:
CalculateEmotion: true
IgnoreLowEmotionWarn: false
Fleet1Value: 119
Fleet1Record: 2020-01-01 00:00:00
Fleet1Record:
value: 2020-01-01 00:00:00
validate: datetime
Fleet1Control:
value: prevent_yellow_face
option: [keep_exp_bonus, prevent_green_face, prevent_yellow_face, prevent_red_face]
@ -135,7 +139,9 @@ Emotion:
option: [not_in_dormitory, dormitory_floor_1, dormitory_floor_2]
Fleet1Oath: false
Fleet2Value: 119
Fleet2Record: 2020-01-01 00:00:00
Fleet2Record:
value: 2020-01-01 00:00:00
validate: datetime
Fleet2Control:
value: prevent_yellow_face
option: [keep_exp_bonus, prevent_green_face, prevent_yellow_face, prevent_red_face]
@ -369,7 +375,9 @@ Exercise:
LowHpThreshold: 0.4
LowHpConfirmWait: 0.1
OpponentRefreshValue: 0
OpponentRefreshRecord: 2020-01-01 00:00:00
OpponentRefreshRecord:
value: 2020-01-01 00:00:00
validate: datetime
Sos:
Chapter:
value: 3

View File

@ -47,4 +47,7 @@ AddAlas:
NewName:
CopyFrom:
Confirm:
FileExist:
FileExist:
Text:
InvalidFeedBack:

View File

@ -1610,6 +1610,9 @@
"CopyFrom": "Copy from existing config",
"Confirm": "Add",
"FileExist": "A config with the same name exists, please choose another one"
},
"Text": {
"InvalidFeedBack": "Invalid format. Example: {0}"
}
}
}

View File

@ -1610,6 +1610,9 @@
"CopyFrom": "Gui.AddAlas.CopyFrom",
"Confirm": "Gui.AddAlas.Confirm",
"FileExist": "Gui.AddAlas.FileExist"
},
"Text": {
"InvalidFeedBack": "Gui.Text.InvalidFeedBack"
}
}
}

View File

@ -1610,6 +1610,9 @@
"CopyFrom": "从现有的配置中复制",
"Confirm": "添加",
"FileExist": "存在同名的配置文件,请重新输入一个"
},
"Text": {
"InvalidFeedBack": "格式错误。 示例:{0}"
}
}
}

View File

@ -1610,6 +1610,9 @@
"CopyFrom": "從現有的設定中複製",
"Confirm": "添加",
"FileExist": "存在同名的設定檔案,請重新命名"
},
"Text": {
"InvalidFeedBack": "格式錯誤。 示例:{0}"
}
}
}

View File

@ -2,6 +2,7 @@ from module.webui.lang import t
from module.webui.utils import Icon, TaskHandler
from module.webui.widgets import put_icon_buttons
from pywebio.output import output, put_column, put_html, put_row, put_text
from pywebio.pin import pin_update
from pywebio.session import defer_call, info, run_js
@ -116,3 +117,24 @@ class Frame(Base):
$("button.btn-{position}").removeClass("btn-{position}-active");
$("div[style*='--{position}-{value}--']>button").addClass("btn-{position}-active");
""")
@staticmethod
def pin_set_invalid_mark(keys) -> None:
if isinstance(keys, str):
keys = [keys]
js = ''.join([f"""$(".form-control[name='{key}']").addClass('is-invalid');""" for key in keys])
if js:
run_js(js)
# for key in keys:
# pin_update(key, valid_status=False)
@staticmethod
def pin_remove_invalid_mark(keys) -> None:
if isinstance(keys, str):
keys = [keys]
js = ''.join(
[f"""$(".form-control[name='{key}']").removeClass('is-invalid');""" for key in keys])
if js:
run_js(js)
# for key in keys:
# pin_update(key, valid_status=0)

48
module/webui/pin.py Normal file
View File

@ -0,0 +1,48 @@
"""
Copy from pywebio.pin
Add **other_html_attrs to put_xxx()
"""
from pywebio.io_ctrl import Output
from pywebio.output import OutputPosition
from pywebio.pin import check_name, _pin_output
def put_input(name, type='text', *, label='', value=None, placeholder=None, readonly=None, datalist=None,
help_text=None, scope=None, position=OutputPosition.BOTTOM, **other_html_attrs) -> Output:
"""Output an input widget. Refer to: `pywebio.input.input()`"""
from pywebio.input import input
check_name(name)
single_input_return = input(name=name, label=label, value=value, type=type, placeholder=placeholder,
readonly=readonly, datalist=datalist, help_text=help_text, **other_html_attrs)
return _pin_output(single_input_return, scope, position)
def put_textarea(name, *, label='', rows=6, code=None, maxlength=None, minlength=None, value=None, placeholder=None,
readonly=None, help_text=None, scope=None, position=OutputPosition.BOTTOM, **other_html_attrs) -> Output:
"""Output a textarea widget. Refer to: `pywebio.input.textarea()`"""
from pywebio.input import textarea
check_name(name)
single_input_return = textarea(
name=name, label=label, rows=rows, code=code, maxlength=maxlength,
minlength=minlength, value=value, placeholder=placeholder, readonly=readonly, help_text=help_text, **other_html_attrs)
return _pin_output(single_input_return, scope, position)
def put_select(name, options=None, *, label='', multiple=None, value=None, help_text=None,
scope=None, position=OutputPosition.BOTTOM, **other_html_attrs) -> Output:
"""Output a select widget. Refer to: `pywebio.input.select()`"""
from pywebio.input import select
check_name(name)
single_input_return = select(name=name, options=options, label=label, multiple=multiple,
value=value, help_text=help_text, **other_html_attrs)
return _pin_output(single_input_return, scope, position)
def put_checkbox(name, options=None, *, label='', inline=None, value=None, help_text=None,
scope=None, position=OutputPosition.BOTTOM, **other_html_attrs) -> Output:
"""Output a checkbox widget. Refer to: `pywebio.input.checkbox()`"""
from pywebio.input import checkbox
check_name(name)
single_input_return = checkbox(name=name, options=options, label=label, inline=inline, value=value,
help_text=help_text, **other_html_attrs)
return _pin_output(single_input_return, scope, position)

View File

@ -1,5 +1,6 @@
import ctypes
import operator
import re
import threading
import time
from typing import Generator
@ -10,6 +11,11 @@ from pywebio.input import PASSWORD, input
from pywebio.session import eval_js, register_thread, run_js
RE_DATETIME = r'(\d{2}|\d{4})(?:\-)?([0]{1}\d{1}|[1]{1}[0-2]{1})(?:\-)?' + \
r'([0-2]{1}\d{1}|[3]{1}[0-1]{1})(?:\s)?([0-1]{1}\d{1}|[2]' + \
r'{1}[0-3]{1})(?::)?([0-5]{1}\d{1})(?::)?([0-5]{1}\d{1})'
class QueueHandler:
def __init__(self, q) -> None:
self.queue = q
@ -204,17 +210,17 @@ class Icon:
ADD = _read(filepath_icon('add'))
def get_output(arg_type, name, title, arg_help=None, value=None, options=None, width="12rem"):
def get_output(arg_type, name, title, arg_help=None, value=None, options=None, **other_html_attrs):
if arg_type == 'input':
return put_input_(name, title, arg_help, value, width)
return put_input_(name, title, arg_help, value, **other_html_attrs)
elif arg_type == 'select':
return put_select_(name, title, arg_help, options, width)
return put_select_(name, title, arg_help, options, **other_html_attrs)
elif arg_type == 'textarea':
return put_textarea_(name, title, arg_help, value)
return put_textarea_(name, title, arg_help, value, **other_html_attrs)
elif arg_type == 'checkbox':
return put_checkbox_(name, title, arg_help, value, width)
return put_checkbox_(name, title, arg_help, value, **other_html_attrs)
elif arg_type == 'disable':
return put_input_(name, title, arg_help, value, width, readonly=True)
return put_input_(name, title, arg_help, value, readonly=True, **other_html_attrs)
def parse_pin_value(val):
@ -244,7 +250,8 @@ def parse_pin_value(val):
def login(password):
if get_localstorage('password') == password:
return True
pwd = input(label='Please login below.', type=PASSWORD, placeholder='PASSWORD')
pwd = input(label='Please login below.',
type=PASSWORD, placeholder='PASSWORD')
if pwd == password:
set_localstorage('password', pwd)
return True
@ -252,16 +259,26 @@ def login(password):
toast('Wrong password!', color='error')
return False
def get_window_visibility_state():
ret = eval_js("document.visibilityState")
return False if ret == "hidden" else True
# https://pywebio.readthedocs.io/zh_CN/latest/cookbook.html#cookie-and-localstorage-manipulation
set_localstorage = lambda key, value: run_js("localStorage.setItem(key, value)", key=key, value=value)
get_localstorage = lambda key: eval_js("localStorage.getItem(key)", key=key)
# set_localstorage('hello', 'world')
# val = get_localstorage('hello')
def set_localstorage(key, value):
return run_js("localStorage.setItem(key, value)", key=key, value=value)
def get_localstorage(key):
return eval_js("localStorage.getItem(key)", key=key)
def re_fullmatch(pattern, string):
if pattern == 'datetime':
pattern = RE_DATETIME
# elif:
return re.fullmatch(pattern=pattern, string=string)
if __name__ == '__main__':

View File

@ -2,8 +2,8 @@ import random
import string
from typing import Callable, Dict, List, Union
from module.webui.pin import put_checkbox, put_input, put_select, put_textarea
from pywebio.output import *
from pywebio.pin import *
from pywebio.session import run_js
@ -19,7 +19,8 @@ class ScrollableCode:
for _ in range(10))
self.html = """<pre id="%s" class="container-log"><code style="white-space:break-spaces;"></code></pre>"""\
% self.id
self.output = output(put_html(self.html)).style("display: grid; overflow-y: auto;")
self.output = output(put_html(self.html)).style(
"display: grid; overflow-y: auto;")
def append(self, text: str) -> None:
if text:
@ -41,8 +42,8 @@ class ScrollableCode:
# aside buttons
def put_icon_buttons(icon_html: str,
buttons: List[Dict[str, str]],
def put_icon_buttons(icon_html: str,
buttons: List[Dict[str, str]],
onclick: Union[List[Callable[[], None]], Callable[[], None]]):
value = buttons[0]['value']
return put_column([
@ -55,32 +56,33 @@ def put_icon_buttons(icon_html: str,
# args input widget
def put_input_(
name: str,
title: str,
help: str = None,
value: str = '',
width: str = "12rem",
readonly: bool = None
title: str,
help: str = None,
value: str = '',
readonly: bool = None,
**other_html_attrs
):
if help:
left = put_column([
put_text(title).style("arg-title"),
put_text(help).style("arg-help"),
], size="auto auto")
], size="auto 1fr")
else:
left = put_text(title).style("arg-title")
return put_row([
left,
put_input(name, value=value, readonly=readonly).style("input-input"),
], size=f"1fr {width}").style("container-args")
put_input(name, value=value, readonly=readonly, **
other_html_attrs).style("input-input"),
]).style("container-args-row")
def put_select_(
name: str,
title: str,
name: str,
title: str,
help: str = None,
options: List[str] = None,
width: str = "12rem"
**other_html_attrs
):
if options is None:
options = []
@ -88,61 +90,64 @@ def put_select_(
left = put_column([
put_text(title).style("arg-title"),
put_text(help).style("arg-help"),
], size="auto auto")
], size="auto 1fr")
else:
left = put_text(title).style("arg-title")
return put_row([
left,
put_select(name, options=options).style("input-input"),
], size=f"1fr {width}").style("container-args")
put_select(name, options=options, **
other_html_attrs).style("input-input"),
]).style("container-args-row")
def put_textarea_(
name: str,
title: str,
help: str = None,
value: str = '',
readonly: bool = None
name: str,
title: str,
help: str = None,
value: str = '',
readonly: bool = None,
**other_html_attrs
):
if help:
return put_column([
put_text(title).style("arg-title"),
put_text(help).style("arg-help"),
put_textarea(name, value=value, readonly=readonly, code={
"lineWrapping": True, "lineNumbers": False})
], size="auto auto auto").style("container-args")
"lineWrapping": True, "lineNumbers": False}, **other_html_attrs)
], size="auto auto auto").style("container-args-column")
else:
return put_column([
put_text(title).style("arg-title"),
put_textarea(name, value=value, readonly=readonly, code={
"lineWrapping": True, "lineNumbers": False})
], size="auto auto").style("container-args")
"lineWrapping": True, "lineNumbers": False}, **other_html_attrs)
], size="auto auto").style("container-args-column")
def put_checkbox_(
name: str,
title: str,
help: str = None,
value: bool = False,
width: str = "12rem",
title: str,
help: str = None,
value: bool = False,
**other_html_attrs
):
# Not real checkbox, use as a switch (on/off)
if help:
left = put_column([
put_text(title).style("arg-title"),
put_text(help).style("arg-help"),
], size="auto auto")
], size="auto 1fr")
else:
left = put_text(title).style("arg-title")
return put_row([
left,
put_checkbox(
name,
options=[{'label': '', 'value': True, 'selected': value}]
options=[{'label': '', 'value': True, 'selected': value}],
**other_html_attrs
).style("text-align: center")
], size=f"1fr {width}").style("container-large.args")
]).style("container-large.args")
# arg block

View File

@ -47,7 +47,7 @@
left: 0;
top: 0;
width: 100%;
height: 60px;
height: 51px;
display: flex;
flex-direction: row;
-webkit-app-region: drag;