def expand_menu() -> None: run_js( f""" $(".container-menu-collapsed").removeClass("container-menu-collapsed"); $("#pywebio-scope-content").addClass("container-content-collapsed"); """ )
def append(self, text: str) -> None: if text: run_js("""$("#{dom_id}>code").append(text); """.format(dom_id=self.id), text=str(text)) if self.keep_bottom: self.scroll()
def active_button(position, value) -> None: run_js( f""" $("button.btn-{position}").removeClass("btn-{position}-active"); $("div[style*='--{position}-{value}--']>button").addClass("btn-{position}-active"); """ )
def index(): if key != '' and not login(key): logger.warning(f"{info.user_ip} login failed.") time.sleep(1.5) run_js('location.reload();') return AlasGUI().run()
def on_task_exception(self): logger.exception("An internal error occurred in the application") toast_msg = ("应用发生内部错误" if "zh" in session_info.user_language else "An internal error occurred in the application") e_type, e_value, e_tb = sys.exc_info() lines = traceback.format_exception(e_type, e_value, e_tb) traceback_msg = "".join(lines) traceback_console = Console(color_system="truecolor", tab_size=2, record=True, width=90) with traceback_console.capture(): # prevent logging to stdout again traceback_console.print_exception(word_wrap=True, extra_lines=1, show_locals=True) if State.theme == "dark": theme = DARK_TERMINAL_THEME else: theme = LIGHT_TERMINAL_THEME html = traceback_console.export_html(theme=theme, code_format=TRACEBACK_CODE_FORMAT, inline_styles=True) try: popup(title=toast_msg, content=put_html(html), size=PopupSize.LARGE) run_js( "console.error(traceback_msg)", traceback_msg="Internal Server Error\n" + traceback_msg, ) except Exception: pass
def target(): run_js("$('#markdown-body>.alert-warning').remove()") template.basic_output() template.background_output() run_as_function(template.basic_input()) actions(buttons=['Continue']) template.background_input()
def extend(self, text): if text: run_js( """$("#pywebio-scope-{scope}>div").append(text); """.format(scope=self.scope), text=str(text), ) if self.keep_bottom: self.scroll()
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)
def main(): """PyWebIO聊天室 和当前所有在线的人聊天 """ global chat_msgs run_js("alert('233')") put_markdown( "## PyWebIO聊天室\n欢迎来到聊天室,你可以和当前所有在线的人聊天。你可以在浏览器的多个标签页中打开本页面来测试聊天效果。" "本应用使用不到80行代码实现,源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)", lstrip=True) msg_box = output() put_scrollable(msg_box, height=300, keep_bottom=True) nickname = input("请输入你的昵称", required=True, validate=lambda n: '昵称已被使用' if n in online_users or n == '📢' else None) online_users.add(nickname) chat_msgs.append( ('📢', '`%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) msg_box.append( put_markdown('`📢`: `%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) @defer_call def on_close(): online_users.remove(nickname) chat_msgs.append( ('📢', '`%s`退出聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) refresh_task = run_async(refresh_msg(nickname, msg_box)) while True: data = input_group('发送消息', [ input(name='msg', help_text='消息内容支持行内Markdown语法'), actions(name='cmd', buttons=['发送', '多行输入', { 'label': '退出', 'type': 'cancel' }]) ], validate=lambda d: ('msg', '消息不为空') if d['cmd'] == '发送' and not d['msg'] else None) if data is None: break if data['cmd'] == '多行输入': data['msg'] = '\n' + textarea('消息内容', help_text='消息内容支持Markdown语法') msg_box.append( put_markdown('`%s`: %s' % (nickname, data['msg']), sanitize=True)) chat_msgs.append((nickname, data['msg'])) refresh_task.close() toast("你已经退出聊天室")
async def main(): global chat_msgs set_env(title="PyWebIO Chat Room") put_markdown( "##PyWebIO聊天室\n欢迎来到聊天室,你可以和当前所有在线的人聊天。你可以在浏览器的多个标签页中打开本页面来测试聊天效果。" "本应用使用不到80行代码实现,源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)", lstrip=True) msg_box = output() with use_scope('msg-container'): style(put_scrollable(msg_box, max_height=300), 'height:300px') nickname = await input("请输入你的昵称", required=True, validate=lambda n: '昵称已被使用' if n in online_users or n == '📢' else None) online_users.add(nickname) chat_msgs.append( ('📢', '`%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) msg_box.append( put_markdown('`📢`: `%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) @defer_call def on_close(): online_users.remove(nickname) chat_msgs.append( ('📢', '`%s`退出聊天室. 当前在线人数 %s' % (nickname, len(online_users)))) refresh_task = run_async(refresh_msg(nickname, msg_box)) while True: data = await input_group('发送消息', [ input(name='msg', help_text='消息内容支持Markdown 语法', required=True), actions(name='cmd', buttons=['发送', { 'label': '退出', 'type': 'cancel' }]) ]) if data is None: break msg_box.append(put_markdown('`%s`: %s' % (nickname, data['msg']))) run_js( '$("#pywebio-scope-msg-container>div").animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)' ) # hack: to scroll bottom chat_msgs.append((nickname, data['msg'])) refresh_task.close() toast("你已经退出聊天室")
def set_language(s: str, refresh=False): global LANG for i, lang in enumerate(LANGUAGES): # pywebio.session.info.user_language return `zh-CN` or `zh-cn`, depends on browser if lang.lower() == s.lower(): LANG = LANGUAGES[i] break else: LANG = 'en-US' WebuiConfig().Language = LANG if refresh: from pywebio.session import run_js run_js('location.reload();')
async def refresh_msg(my_name, msg_box): """刷新聊天消息""" global chat_msgs last_idx = len(chat_msgs) while True: await asyncio.sleep(0.5) for m in chat_msgs[last_idx:]: if m[0] != my_name: # 仅刷新其他人的新信息 msg_box.append(put_markdown('`%s`: %s' % m)) run_js( '$("#pywebio-scope-msg-container>div").animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)' ) # hack: to scroll bottom # 清理聊天记录 if len(chat_msgs) > MAX_MESSAGES_CNT: chat_msgs = chat_msgs[len(chat_msgs) // 2:] last_idx = len(chat_msgs)
def set_navigator(self, group): js = f''' $("#pywebio-scope-groups").scrollTop( $("#pywebio-scope-group_{group[0]}").position().top + $("#pywebio-scope-groups").scrollTop() - 59 ) ''' put_button(label=t(f"{group[0]}._info.name"), onclick=lambda: run_js(js), color='navigator')
async def main(): global chat_msgs put_markdown("## 🧊 Добро пожаловать в онлайн чат!") msg_box = output() put_scrollable(msg_box, height=300, keep_bottom=True) nickname = await input("Войти в чат", required=True, placeholder="Ваше имя", validate=lambda n: "Такой ник уже используется!" if n in online_users or n == '📢' else None) online_users.add(nickname) chat_msgs.append(('📢', f'`{nickname}` присоединился к чату!')) msg_box.append(put_markdown(f'📢 `{nickname}` присоединился к чату')) refresh_task = run_async(refresh_msg(nickname, msg_box)) while True: data = await input_group( "💭 Новое сообщение", [ input(placeholder="Текст сообщения ...", name="msg"), actions(name="cmd", buttons=[ "Отправить", { 'label': "Выйти из чата", 'type': 'cancel' } ]) ], validate=lambda m: ('msg', "Введите текст сообщения!") if m["cmd"] == "Отправить" and not m['msg'] else None) if data is None: break msg_box.append(put_markdown(f"`{nickname}`: {data['msg']}")) chat_msgs.append((nickname, data['msg'])) refresh_task.close() online_users.remove(nickname) toast("Вы вышли из чата!") msg_box.append(put_markdown(f'📢 Пользователь `{nickname}` покинул чат!')) chat_msgs.append(('📢', f'Пользователь `{nickname}` покинул чат!')) put_buttons(['Перезайти'], onclick=lambda btn: run_js('window.location.reload()'))
async def main(): global chat_msgs put_markdown("Добро пожаловать в онлайн чат\n" "Чат напсиан на языке Python\n" "`Copyright © Mamikon Papikyan`") msg_box = output() put_scrollable(msg_box, height=300, keep_bottom=True) nickname = await input("Войти в чат", required=True, placeholder="Ваше имя", validate=lambda n: "Такой ник уже используется" if n in online_users else None) online_users.add(nickname) chat_msgs.append(("🔉", f"`{nickname}` присоединился к чату!")) msg_box.append(put_markdown(f"`{nickname}` присоединился к чату!")) refresh_task = run_async(refresh_msg(nickname, msg_box)) while True: data = await input_group("Новое сообщение", [ input(placeholder="Текст сообщения", name="msg"), actions(name="cmd", buttons=["Отправить", {"label": "Выйти из чата", "type": "cancel"}] ) ], validate=lambda m: ("msg", "Введите текст сообщения") if "Отправить" == m["cmd"] and not m["msg"] else None ) if data is None: break msg_box.append(put_markdown(f"`{nickname}`: {data['msg']}")) chat_msgs.append((nickname, data['msg'])) # exit chat refresh_task.close() online_users.remove(nickname) toast("Вы вышли из чата") msg_box.append(put_markdown(f"🔉 Пользователь {nickname} покинул чат")) chat_msgs.append(("🔉", f"Пользователь {nickname} покинул чат")) put_buttons(["Перезайти"], onclick=lambda btn: run_js( "window.location.reload()"))
def reset(self) -> None: run_js(r"""$("\#{dom_id}>code").empty();""".format(dom_id=self.id))
def scroll(self) -> None: run_js( r"""$("\#{dom_id}").animate({{scrollTop: $("\#{dom_id}").prop("scrollHeight")}}, 0); """.format(dom_id=self.id))
def scroll(self) -> None: run_js( """$("#pywebio-scope-{scope}").scrollTop($("#pywebio-scope-{scope}").prop("scrollHeight")); """.format(scope=self.scope))
def reset(self): run_js(f"""$("#pywebio-scope-{self.scope}>div").empty();""")
def set_localstorage(key, value): return run_js("localStorage.setItem(key, value)", key=key, value=value)
def add_css(filepath): with open(filepath, "r") as f: css = f.read().replace("\n", "") run_js(f"""$('head').append('<style>{css}</style>')""")
def set_theme(t): self.set_theme(t) run_js("location.reload()")
def run(self) -> None: # setup gui set_env(title="Alas", output_animation=False) 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 == 'dark': add_css(filepath_css('dark-alas')) else: add_css(filepath_css('light-alas')) # Auto refresh when lost connection # [For develop] Disable by run `reload=0` in console run_js(''' reload = 1; WebIO._state.CurrentSession.on_session_close( ()=>{ setTimeout( ()=>{ if (reload == 1){ location.reload(); } }, 4000 ) } ); ''') aside = get_localstorage('aside') self.show() # detect config change _thread_wait_config_change = Thread( target=self._alas_thread_wait_config_change) register_thread(_thread_wait_config_change) _thread_wait_config_change.start() # save config _thread_save_config = Thread(target=self._alas_thread_update_config) register_thread(_thread_save_config) _thread_save_config.start() visibility_state_switch = Switch(status={ True: [ lambda: self.__setattr__('visible', True), lambda: self.alas_update_overiew_task() if self.page == 'Overview' else 0, lambda: self.task_handler._task.__setattr__('delay', 15) ], False: [ lambda: self.__setattr__('visible', False), lambda: self.task_handler._task.__setattr__('delay', 1) ] }, get_state=get_window_visibility_state, name='visibility_state') self.state_switch = Switch( status=self.set_status, get_state=lambda: getattr(getattr(self, 'alas', -1), 'state', 0), name='state') def goto_update(): self.ui_develop() self.dev_update() update_switch = Switch(status={ 1: lambda: toast(t("Gui.Toast.ClickToUpdate"), duration=0, position='right', color='success', onclick=goto_update) }, get_state=lambda: updater.state, name='update_state') self.task_handler.add(self.state_switch.g(), 2) self.task_handler.add(visibility_state_switch.g(), 15) self.task_handler.add(update_switch.g(), 1) self.task_handler.start() # Return to previous page if aside not in ["Develop", "Home", None]: self.ui_alas(aside)
def translate(): """ Translate Alas """ set_env(output_animation=False) run_js(r"""$('head').append('<style>footer {display: none}</style>')""") put_markdown(""" # Translate You can submit(Next) by press `Enter`. """) dict_lang = { "zh-CN": read_file(filepath_i18n('zh-CN')), "zh-TW": read_file(filepath_i18n('zh-TW')), "en-US": read_file(filepath_i18n('en-US')), "ja-JP": read_file(filepath_i18n('ja-JP')), } modified = { "zh-CN": {}, "zh-TW": {}, "en-US": {}, "ja-JP": {}, } list_path = [] # Menu.Task.name list_group = [] # Menu list_arg = [] # Task list_key = [] # name for L, _ in deep_iter(dict_lang['zh-CN'], depth=3): list_path.append('.'.join(L)) list_group.append(L[0]) list_arg.append(L[1]) list_key.append(L[2]) total = len(list_path) class V: lang = lang.LANG untranslated_only = False clear = False idx = -1 group = '' group_idx = 0 groups = list(dict_lang['zh-CN'].keys()) arg = '' arg_idx = 0 args = [] key = '' key_idx = 0 keys = [] def update_var(group=None, arg=None, key=None): if group: V.group = group V.idx = list_group.index(group) V.group_idx = V.idx V.arg = list_arg[V.idx] V.arg_idx = V.idx V.args = list(dict_lang["zh-CN"][V.group].keys()) V.key = list_key[V.idx] V.key_idx = V.idx V.keys = list(dict_lang["zh-CN"][V.group][V.arg].keys()) elif arg: V.arg = arg V.idx = list_arg.index(arg, V.group_idx) V.arg_idx = V.idx V.args = list(dict_lang["zh-CN"][V.group].keys()) V.key = list_key[V.idx] V.key_idx = V.idx V.keys = list(dict_lang["zh-CN"][V.group][V.arg].keys()) elif key: V.key = key V.idx = list_key.index(key, V.arg_idx) V.key_idx = V.idx V.keys = list(dict_lang["zh-CN"][V.group][V.arg].keys()) update_form() def next_key(): if V.idx + 1 > total: V.idx = -1 V.idx += 1 if V.untranslated_only: while True: # print(V.idx) key = deep_get(dict_lang[V.lang], list_path[V.idx]) if list_path[V.idx] == key or list_path[V.idx].split( '.')[2] == key: break else: V.idx += 1 if V.idx + 1 > total: V.idx = 0 break (V.group, V.arg, V.key) = tuple(list_path[V.idx].split('.')) V.group_idx = list_group.index(V.group) V.arg_idx = list_arg.index(V.arg, V.group_idx) V.args = list(dict_lang["zh-CN"][V.group].keys()) V.key_idx = list_key.index(V.key, V.arg_idx) V.keys = list(dict_lang["zh-CN"][V.group][V.arg].keys()) def update_form(): input_update('arg', options=V.args, value=V.arg) input_update('key', options=V.keys, value=V.key) for L in LANGUAGES: input_update(L, value=deep_get(dict_lang[L], f'{V.group}.{V.arg}.{V.key}', 'Key not found!')) old = deep_get(dict_lang[V.lang], f'{V.group}.{V.arg}.{V.key}', 'Key not found!') input_update( V.lang, value=None if V.clear else old, help_text=f'{V.group}.{V.arg}.{V.key}', placeholder=old, ) def get_inputs(): out = [] old = deep_get(dict_lang[V.lang], f'{V.group}.{V.arg}.{V.key}', 'Key not found!') out.append( input( name=V.lang, label=V.lang, value=None if V.clear else old, help_text=f'{V.group}.{V.arg}.{V.key}', placeholder=old, )) out.append( select(name='group', label='Group', options=V.groups, value=V.group, onchange=lambda g: update_var(group=g), required=True)) out.append( select(name='arg', label='Arg', options=V.args, value=V.arg, onchange=lambda a: update_var(arg=a), required=True)) out.append( select(name='key', label='Key', options=V.keys, value=V.key, onchange=lambda k: update_var(key=k), required=True)) _LANGUAGES = LANGUAGES.copy() _LANGUAGES.remove(V.lang) for L in _LANGUAGES: out.append( input(name=L, label=L, readonly=True, value=deep_get(dict_lang[L], f'{V.group}.{V.arg}.{V.key}', 'Key not found!'))) out.append( actions(name='action', buttons=[ { "label": "Next", "value": 'Next', "type": "submit", "color": "success" }, { "label": "Next without save", "value": 'Skip', "type": "submit", "color": "secondary" }, { "label": "Submit", "value": "Submit", "type": "submit", "color": "primary" }, { "label": "Quit and save", "type": "cancel", "color": "secondary" }, ])) return out def save(): for LANG in LANGUAGES: d = read_file(filepath_i18n(LANG)) for k in modified[LANG].keys(): deep_set(d, k, modified[LANG][k]) write_file(filepath_i18n(LANG), d) defer_call(save) def loop(): while True: data = input_group(inputs=get_inputs()) if data is None: save() break if data['action'] == 'Next': modified[V.lang][f'{V.group}.{V.arg}.{V.key}'] = data[ V.lang].replace("\\n", "\n") deep_set(dict_lang[V.lang], f'{V.group}.{V.arg}.{V.key}', data[V.lang].replace("\\n", "\n")) next_key() if data['action'] == 'Skip': next_key() elif data['action'] == 'Submit': modified[V.lang][f'{V.group}.{V.arg}.{V.key}'] = data[ V.lang].replace("\\n", "\n") deep_set(dict_lang[V.lang], f'{V.group}.{V.arg}.{V.key}', data[V.lang].replace("\\n", "\n")) continue def setting(): data = input_group(inputs=[ select(name='language', label='Language', options=LANGUAGES, value=V.lang, required=True), checkbox( name='check', label='Other settings', options=[{ "label": 'Button [Next] only shows untranslated key', 'value': 'untranslated', 'selected': V.untranslated_only }, { "label": 'Do not fill input with old value (only effect the language you selected)', "value": "clear", "selected": V.clear }]) ]) V.lang = data['language'] V.untranslated_only = True if 'untranslated' in data['check'] else False V.clear = True if 'clear' in data['check'] else False put_buttons([{ "label": "Start", "value": "start" }, { "label": "Setting", "value": "setting" }], onclick=[loop, setting]) next_key() setting() hold()