From e46d6127186c813d6190c36f618079dafcdf34af Mon Sep 17 00:00:00 2001 From: Jiri Kalvoda Date: Thu, 22 Sep 2022 01:45:05 +0200 Subject: [PATCH] =?UTF-8?q?Strategick=C3=A1:=20Upgrade=20game=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/hra/web/game.py | 197 +++++++++++++++++++++++++++------------ server/hra/web/pages.py | 50 +++++----- server/static/occupy.css | 4 + 3 files changed, 169 insertions(+), 82 deletions(-) diff --git a/server/hra/web/game.py b/server/hra/web/game.py index 3748126..afde868 100644 --- a/server/hra/web/game.py +++ b/server/hra/web/game.py @@ -1,5 +1,5 @@ from flask import Flask, redirect, flash, session, g, request, get_flashed_messages, Markup -from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField, RadioField +from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField, RadioField, FloatField from wtforms.validators import ValidationError from flask_wtf import FlaskForm from flask_bootstrap import Bootstrap @@ -45,12 +45,22 @@ class OccupyViewConfig(FlaskForm): refresh_meta = "Aktualizace pomocí HTML META" refresh_js = "Aktualizace pomocí java scriptu" - font_size = IntegerField("Velikost fontu", default=8) + clickable_down = "Podrobnosti na kliknutí pod mapou" + clickable_right = "Podrobnosti na kliknutí vpravo vedle mapy" + clickable_not = "Vypnout podrobnosti kliknutím (zmenší nároky na sít a procesor)" + + status_minimalistic = "Minimalistické metadata" + status_up = "Matadata nahoře" + status_left = "Metadata vlevo od mapy" + + font_size = FloatField("Velikost fontu", default=8.0) refresh = RadioField("Aktualizace", choices=(refresh_none, refresh_meta), default=refresh_none) - not_clickable = BooleanField("Vypnout podrobnosti kliknutím (zmenší nároky na sít a procesor)") + clickable = RadioField("", choices=(clickable_not, clickable_down, clickable_right), default=clickable_down) + status = RadioField("", choices=(status_minimalistic, status_left, status_up), default=status_minimalistic) big_fields = BooleanField("Široká políčka (trojciferné počty)") submit = SubmitField("Refresh/Potvrdit") + show_data_only = SubmitField("Zobrazit stránku pouze s daty") @add_wlogic class Occupy(WLogic): @@ -58,59 +68,94 @@ class Occupy(WLogic): meta_refresh_without_reaming = 15 game = state.game + team_id = team.team_id if team else None if game.step_mode == db.StepMode.automatic: time_reaming = (state.create_time - datetime.now()).total_seconds() + game.step_every_s else: time_reaming = None s = state.get_state() + if team is not None: + s = self.logic.personalize_state(s, team.team_id, state.round) conff = OccupyViewConfig(formdata=request.args, csrf_enabled=False) q = db.get_session().query(db.Log).order_by(db.Log.log_id.desc()) conff.validate() max_num = 999 if conff.big_fields.data else 99 - b = BasePage() - b.h2(f"Hra {self.game.print()} kolo {state.round}") - if not conff.not_clickable.data: - b.p("Po kliknutí na buňku se zobrazí další informace.") - if conff.refresh.data == conff.refresh_meta and time_reaming is None: - b.p(f"Není známo, kdy nastane další kolo, proto může být aktualizace až o {meta_refresh_without_reaming} sekund opožděna.") - with b.p(): - b.line().b(_class=f"game_team_{team.team_id}")(f"Pohled týmu {team.print()}") - with b.line().b(_class="pull-right"): - if time_reaming is not None: - b("Čas do konce kola: ", b._span(id="time")(int(time_reaming)), " sekund") - else: - b("Konec kola není stanoven") - with b.p(style="text-align:center;").table(_class="game_table"): - for i, row in enumerate(s["world"]): - with b.tr(): - for j, x in enumerate(row): - occupied_by_team = x["occupied_by_team"] - home_for_team = x["home_for_team"] - members = x["members"] - with b.td(): - classes = [] - with b.a(href=f"#cell_{i}_{j}") if not conff.not_clickable.data else b.bucket(): - if x["protected_for_team"] is not None: - classes.append("game_protected") - if x["hill"]: - classes.append("game_hill") - b(Markup(" ")) - else: - if home_for_team is not None: - classes.append(f'game_home') - if occupied_by_team is not None: - classes.append(f'game_occupied') - classes.append(f'game_occupied_by_{occupied_by_team}') - num = len(members) - if num: - b(num if num <= max_num else ("+++" if conff.big_fields.data else "++")) - else: + def reaming_time(id="time"): + if time_reaming is not None: + b.b("Čas do konce kola: ", b._span(id=id)(int(time_reaming)), " sekund") + b.script(Markup(f""" + var intervalID = window.setInterval(update_time, 100); + var t = { time_reaming*1000 }; + function update_time() {{ + document.getElementById("{id}").innerHTML = Math.floor(t/1000);; + t -= 100; + }} + """)) + else: + b("Konec kola není stanoven") + + def status(): + members_counts = [0 for _ in teams] + occupied_counts = [0 for _ in teams] + + for x in s["world"]: + for y in x: + occupied_by_team = y["occupied_by_team"] + members = y["members"] + if occupied_by_team is not None: + occupied_counts[occupied_by_team] += 1 + members_counts[occupied_by_team] += len(members) + + + with b.div(_class="game_left_status button form-frame"): + b.b("Kolo ", state.round) + with b.p(): + reaming_time(id="time_left") + with b.p(): + for t, occupied_count, members_count in zip(teams, occupied_counts, members_counts): + with b.line().span(_class=f"game_team_{t.team_id}"): + b.b("Tým ", t.print()) + b.br() + b("Bodů: ", t.get_move(state.round).points) + b.br() + b("Políček: ", occupied_count) + b.br() + b("Vojáků: ", members_count) + b.br() + + + def table(): + with b.table(_class="game_table"): + for i, row in enumerate(s["world"]): + with b.tr(): + for j, x in enumerate(row): + occupied_by_team = x["occupied_by_team"] + home_for_team = x["home_for_team"] + members = x["members"] + with b.td(): + classes = [] + with b.a(href=f"#cell_{i}_{j}") if conff.clickable.data != conff.clickable_not else b.bucket(): + if x["protected_for_team"] is not None: + classes.append("game_protected") + if x["hill"]: + classes.append("game_hill") b(Markup(" ")) - b(_class=" ".join(classes)) + else: + if home_for_team is not None: + classes.append(f'game_home') + if occupied_by_team is not None: + classes.append(f'game_occupied') + classes.append(f'game_occupied_by_{occupied_by_team}') + num = len(members) + if num: + b(num if num <= max_num else ("+++" if conff.big_fields.data else "++")) + else: + b(Markup(" ")) + b(_class=" ".join(classes)) - if not conff.not_clickable.data: + def clickable(): for i, row in enumerate(s["world"]): for j, x in enumerate(row): occupied_by_team = x["occupied_by_team"] @@ -131,34 +176,60 @@ class Occupy(WLogic): b.p().b(f"Počet osob: {len(members)}") with b.ul(): for m in members: - b.li(_class=f"game_team_{home_for_team}")(f"Voják {m['id']} týmu {teams[m['team']].print()}") + b.li(_class=f"game_team_{m['team']}")(f"Voják {m['id']} týmu {teams[m['team']].print()}") + + b = BasePage() + if not conff.show_data_only.data: + b.h2(f"Hra {self.game.print()} kolo {state.round}") + if conff.clickable.data != conff.clickable_not: + b.p("Po kliknutí na buňku se zobrazí další informace.") + if conff.refresh.data == conff.refresh_meta and time_reaming is None: + b.p(f"Není známo, kdy nastane další kolo, proto může být aktualizace až o {meta_refresh_without_reaming} sekund opožděna.") + with b.p(): + if team is not None: + b.line().b(_class=f"game_team_{team.team_id}")(f"Pohled týmu {team.print()}") + with b.line().b(_class="pull-right"): + reaming_time() + b.div(style="clear: both; margin-bottom: 1ex")("") + with b.div().div(style="text-align:center;", id="game_main"): + if conff.status.data == conff.status_left: + status() + table() + if conff.clickable.data == conff.clickable_right: + clickable() + b.div(style="clear: both; margin-bottom: 1ex")("") + + if conff.clickable.data == conff.clickable_down: + clickable() del conff.csrf_token b.p() - b.div(_class="form-frame")(jinja_mac.quick_form(conff, form_type="horizontal", method="GET")) - - b.script(Markup(f""" - var intervalID = window.setInterval(update_time, 100); - var t = { time_reaming*1000 }; - function update_time() {{ - document.getElementById("time").innerHTML = Math.floor(t/1000);; - t -= 100; - }} - """)) + if not conff.show_data_only.data: + b.div(_class="form-frame")(jinja_mac.quick_form(conff, form_type="horizontal", method="GET")) def head(x): + style = "" + page_w = float("inf") if conff.show_data_only.data else 1000 refresh_time = time_reaming + 1 if time_reaming is not None else 15 - font_size = int(conff.font_size.data) - box_size = int(font_size * 1.3 + 1) + font_size = float(conff.font_size.data or 8.0) + box_size = int(font_size * 1.5 + 1) + left_w = 0 + if conff.status.data == conff.status_left: + left_w = 150 box_w = box_size if conff.big_fields.data: box_w *= 1.5 table_w = len(s["world"][0]) * box_w margin_cmd = "" - if table_w > 1000: - margin_cmd = f"margin-left: {(1000-table_w) / 2}px;" + main_w = table_w + left_w + if conff.clickable.data == conff.clickable_right: + right_w = 400 + main_w += right_w + style += f".game_tab {{\n width: {right_w}px\n}}\n" + if main_w > page_w: + margin_cmd = f"margin-left: {(page_w-main_w) / 2}px;" if conff.refresh.data == conff.refresh_meta: - b.meta(**{"HTTP-EQUIV": "refresh", "CONTENT": f"{refresh_time};{app.url_for(web_game_view.__name__, game_id=game.game_id, team_id=team.team_id, **request.args)}"}) + b.meta(**{"HTTP-EQUIV": "refresh", "CONTENT": f"{refresh_time};{app.url_for(web_game_view.__name__, game_id=game.game_id, team_id=team_id, **request.args)}"}) b.link(rel="stylesheet", href=app.url_for('static', filename='occupy.css'), type='text/css', media="all") b.style(Markup(f""" table.game_table tr td {{ @@ -169,8 +240,15 @@ class Occupy(WLogic): }} table.game_table {{ width: {table_w}px; + }} + #game_main {{ {margin_cmd} + width: {main_w}px; + }} + .game_left_status {{ + width: {left_w-20}px; }} + {style} """)) @@ -178,5 +256,6 @@ class Occupy(WLogic): limited_size=False, sticky_head=False, head=head, + empty=conff.show_data_only.data ) return b.print_file() diff --git a/server/hra/web/pages.py b/server/hra/web/pages.py index efa648d..6ef6588 100644 --- a/server/hra/web/pages.py +++ b/server/hra/web/pages.py @@ -22,7 +22,7 @@ import hra.lib as lib @html.WrapAfterBuilder_decorator -def BasePage(b, content, head=lambda x:None, limited_size=True, sticky_head=True): +def BasePage(b, content, head=lambda x:None, limited_size=True, sticky_head=True, empty=False): b.current_tag = html.Tag(b, "html", []) b.root_tag = b.current_tag with b.head(): @@ -34,29 +34,32 @@ def BasePage(b, content, head=lambda x:None, limited_size=True, sticky_head=True b.link(rel="shortcut icon", href=app.url_for('static', filename='img/favicon.ico')) b(head) with b.body() as body: - with b.header(_class=f"flavor-{config.WEB_FLAVOR}"): - with b.div(_class="content content_limited" if limited_size else "content"): - with b.a(href="/", title="Na hlavní stránku"): - b.img(src=app.url_for('static', filename='hippo.png'), style="width: 60px;height: auto;", alt="KSP") - b.h1()("Hra na soustředění KSP") - with b.div(id="nav-wrapper", _class="nav-wrapper-sticky" if sticky_head else ""): - with b.nav(id="main-menu", _class="content"): - for item in g.menu: - b.a(href=item.url)(item.name) - if g.user: - b.a(_class="right", href="/")(f"Přihlášen: {g.user.username}") - b.a(_class="right", href="/logout")(f"Odhlásit") - else: - b.a(_class="right", href="/login")(f"Přihlásit") - b.a(_class="right", href="/registration")(f"Registrovat") - with b.main(): - messages = get_flashed_messages(with_categories=True) - if messages: - for category, message in messages: - if category == "message": - category = "warning" - b.div(_class=f"alert alert-{category}", role="alert")(message) + if empty: b(*content) + else: + with b.header(_class=f"flavor-{config.WEB_FLAVOR}"): + with b.div(_class="content content_limited" if limited_size else "content"): + with b.a(href="/", title="Na hlavní stránku"): + b.img(src=app.url_for('static', filename='hippo.png'), style="width: 60px;height: auto;", alt="KSP") + b.h1()("Hra na soustředění KSP") + with b.div(id="nav-wrapper", _class="nav-wrapper-sticky" if sticky_head else ""): + with b.nav(id="main-menu", _class="content"): + for item in g.menu: + b.a(href=item.url)(item.name) + if g.user: + b.a(_class="right", href="/")(f"Přihlášen: {g.user.username}") + b.a(_class="right", href="/logout")(f"Odhlásit") + else: + b.a(_class="right", href="/login")(f"Přihlásit") + b.a(_class="right", href="/registration")(f"Registrovat") + with b.main(): + messages = get_flashed_messages(with_categories=True) + if messages: + for category, message in messages: + if category == "message": + category = "warning" + b.div(_class=f"alert alert-{category}", role="alert")(message) + b(*content) class OptionalIntField(wtforms.IntegerField): @@ -310,6 +313,7 @@ def web_game_step(game_id): return redirect(app.url_for(web_game.__name__, game_id=game_id)) +@app.route("/game//view", methods=['GET']) @app.route("/game//view/", methods=['GET']) def web_game_view(game_id, team_id=None): ses = db.get_session() diff --git a/server/static/occupy.css b/server/static/occupy.css index 2d0d0ae..c97b99c 100644 --- a/server/static/occupy.css +++ b/server/static/occupy.css @@ -136,3 +136,7 @@ table.game_table tr td a, table.game_table tr td a:hover, table.game_table tr td color: inherit; text-decoration: none; } + +#game_main div, #game_main table { + float: left; +}