Some small updates

- Show 100% clears (also shows if a 100% clear is impossible (no gems in
  the level))
- Show the type in the title if `type` param is set (it might conflict
  with search query)
- Pass the same search query to the prev/next page
- Shows some 0.822 info (if gems are in the level and replay visibility
  status)
- Add a (hidden by default) table with the server response
- (incomplete) pulse when copying instead of using an alert
This commit is contained in:
magmaus3 2023-04-09 18:06:58 +02:00
parent 347143ab44
commit 5f4a08f3ad
Signed by: magmaus3
GPG key ID: 966755D3F4A9B251
3 changed files with 75 additions and 8 deletions

View file

@ -3,6 +3,9 @@ from fastapi.staticfiles import StaticFiles
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import HTMLResponse, PlainTextResponse from fastapi.responses import HTMLResponse, PlainTextResponse
from fastapi import FastAPI, Form, Response from fastapi import FastAPI, Form, Response
from typing import Optional
import urllib.parse
import starlette
import jinja2 import jinja2
import uvicorn import uvicorn
import httpx import httpx
@ -25,7 +28,11 @@ template_env = jinja2.Environment(
error_template = template_env.get_template("error.html") error_template = template_env.get_template("error.html")
template_env.globals["builtins"] = __builtins__
template_env.globals["convert_times"] = utils.convert_times template_env.globals["convert_times"] = utils.convert_times
template_env.globals["json_dumps"] = json.dumps
template_env.globals["urlencode"] = urllib.parse.urlencode
template_env.globals["combineCSS"] = utils.combineCSS template_env.globals["combineCSS"] = utils.combineCSS
template_env.globals["isSelected"] = utils.isSelected template_env.globals["isSelected"] = utils.isSelected
template_env.globals["generate_thumbnail_url"] = utils.generate_thumbnail_url template_env.globals["generate_thumbnail_url"] = utils.generate_thumbnail_url
@ -68,6 +75,7 @@ async def root():
@app.get("/search", response_class=HTMLResponse) @app.get("/search", response_class=HTMLResponse)
async def search( async def search(
request: starlette.requests.Request,
q: Union[str, None] = None, q: Union[str, None] = None,
p: int = 0, p: int = 0,
sort: str = "average_rating", sort: str = "average_rating",
@ -80,7 +88,7 @@ async def search(
p = 0 p = 0
# Passed to the template # Passed to the template
QueryValues = {"q": q, "p": p, "sort": sort, "dir": dir, "date": date} QueryValues = {"q": q, "p": p, "sort": sort, "dir": dir, "date": date, **request.query_params}
searchResults = None searchResults = None
if q is not None: if q is not None:

View file

@ -4,6 +4,17 @@
align-content: start; align-content: start;
} }
.fullResponse {
font-size: small;
overflow: scroll;
max-width: 20rem;
}
.fullResponse table, .fullResponse table td {
border: 1px solid gray;
border-collapse: collapse;
}
.searchResultCard { .searchResultCard {
display: grid; display: grid;
margin: 4px; margin: 4px;
@ -117,6 +128,18 @@
outline: 1px dashed pink; */ outline: 1px dashed pink; */
} }
@keyframes pulse {
from {
border: 2px solid initial;
color: inherit;
}
to {
border: 2px solid lime;
color: lime
}
}
.levelCode { .levelCode {
margin-top: 0.5em; margin-top: 0.5em;
} }
@ -131,6 +154,19 @@
border: 1px solid #505050; border: 1px solid #505050;
} }
.levelCode input:focus {
outline: none;
background-color: #00FFFF10;
}
.levelCode:active, .levelCode input:active {
animation: pulse 0.5s 1 ease-out;
}
.illegal {
color: red;
}
/* /*
.levelDifficulty { .levelDifficulty {
display: flex; display: flex;

View file

@ -1,7 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>IWM Browser - {{ QueryValues["q"] if QueryValues["q"] is not none }}</title> <!-- {{ QueryValues }} -->
<title>IWM Browser - {{ QueryValues["q"] if QueryValues["q"] is not none }}{{ QueryValues["type"] }}</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -18,7 +19,7 @@
// input field to clipboard, and // input field to clipboard, and
// adds the "copied" css class // adds the "copied" css class
navigator.clipboard.writeText(obj.value); navigator.clipboard.writeText(obj.value);
alert("Level code copied") //alert("Level code copied")
} }
</script> </script>
</head> </head>
@ -75,13 +76,33 @@
<div class="levelDifficulty"><span aria-label="Difficulty: {{ map['AverageUserDifficulty']|int }}">Difficulty: {{ '▲' * map['AverageUserDifficulty']|int }}</span></div> <div class="levelDifficulty"><span aria-label="Difficulty: {{ map['AverageUserDifficulty']|int }}">Difficulty: {{ '▲' * map['AverageUserDifficulty']|int }}</span></div>
<div class="creatorName">by <a href="/user/{{ map['CreatorId'] }}"><b>{{ map['CreatorName'] }}</b></a></div> <div class="creatorName">by <a href="/user/{{ map['CreatorId'] }}"><b>{{ map['CreatorName'] }}</b></a></div>
<div class="levelRating">{{ map['NumThumbsUp'] }} 👍 {{ map['NumThumbsDown'] }} 👎</div> <div class="levelRating">{{ map['NumThumbsUp'] }} 👍 {{ map['NumThumbsDown'] }} 👎</div>
<div class="replayVisibility">Replays visible: {{ "always" if map['ReplayVisibility'] == 2 }}{{ "after clear" if map['ReplayVisibility'] == 0 }}</div>
<div class="dragonCoins">Gems : {{ map['DragonCoins'] }}</div>
{% if map['Description'] != ''%}<div class="levelDescription">{{ map['Description'] }}</div>{% endif %} {% if map['Description'] != ''%}<div class="levelDescription">{{ map['Description'] }}</div>{% endif %}
<div class="levelCode"><label for="levelCode-input-{{ map['ID'] }}">Code:</label> <input id="levelCode-input-{{ map['ID'] }}" type="text" onclick="copy(this)" readonly=1 value="{{ map['MapCode'] }}" /></div> <div class="levelCode"><label for="levelCode-input-{{ map['ID'] }}">Code:</label> <input id="levelCode-input-{{ map['ID'] }}" type="text" onclick="copy(this)" readonly=1 value="{{ map['MapCode'] }}" /></div>
<div class="fullResponse">
<details>
<summary>Full server response</summary>
<table>
<thead><td>Key</td><td>Value</td></thead>
<tbody>
{% for value in map %}
<tr>
<td>{{value}}</td><td>{{map[value]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- <pre>{{ json_dumps(map, indent=4) }}</pre> -->
</details>
</div>
</div> </div>
<div class="times"> <div class="times">
<div class="FirstClear">First Clear: <b>{{ map['FirstClearUsername'] }}</b></div> <div class="FirstClear">First Clear: <a href="/user/{{map['FirstClearUserID']}}"><b>{{ map['FirstClearUsername'] }}</b></a></div>
{% set BestTime = convert_times(map['BestTimePlaytime']) %} {% set BestTime = convert_times(map['BestTimePlaytime']) %}
<div class="BestTime">Best Time: <b>{{ map['BestTimeUsername'] }}</b> [{{ BestTime[0] }}:{{ BestTime[1] }}:{{ BestTime[2] }}]</div> {% set BestFullTime = convert_times(map['BestFullTimePlaytime']) %}
<div class="BestTime">Best Time: <a href="/user/{{ map['BestFullTimeUserID'] }}"><b>{{ map['BestTimeUsername'] }}</b></a> [{{ BestTime[0] }}:{{ BestTime[1] }}:{{ BestTime[2] }}]</div>
{% if map['BestFullTimeUserID'] != 0 %}<div class="BestTime {{ 'illegal' if not map['DragonCoins']}}">Best 100% Time: <a href="/user/{{map['BestFullTimeUserID']}}"><b>{{ map['BestFullTimeUsername'] }}</b></a> [{{ BestFullTime[0] }}:{{ BestFullTime[1] }}:{{ BestFullTime[2] }}]{{ "(illegal time)" if not map['DragonCoins'] }}</div>{% endif %}
</div> </div>
</div> </div>
@ -90,10 +111,12 @@
</div> </div>
<div class="searchResultsFooter"> <div class="searchResultsFooter">
{% set searchPage = QueryValues['p'] %} {% set searchPage = builtins.int(QueryValues['p']) %}
<p>Page {{ searchPage+1 }}</p> <p>Page {{ searchPage+1 }}</p>
{% if not searchPage <= 0 %}<a href="/search?q={{ QueryValues['q'] }}&sort={{ QueryValues['sort'] }}&dir={{ QueryValues['dir'] }}&date={{ QueryValues['date'] }}&p={{ QueryValues['p']-1 }}">Prev</a>{% endif %} {#<!-- {% if not searchPage <= 0 %}<a href="/search?q={{ QueryValues['q'] }}&sort={{ QueryValues['sort'] }}&dir={{ QueryValues['dir'] }}&date={{ QueryValues['date'] }}&p={{ QueryValues['p']-1 }}">Prev</a>{% endif %} -->#}
{% if not entryNumber < entryLimit %}<a href="/search?q={{ QueryValues['q'] }}&sort={{ QueryValues['sort'] }}&dir={{ QueryValues['dir'] }}&date={{ QueryValues['date'] }}&p={{ QueryValues['p']+1 }}">Next</a>{% endif %} {% if not searchPage <= 0 %}<a href="/search?{{ urlencode(builtins.dict(QueryValues, **{'p': searchPage-1})) }}">Prev</a>{% endif %}
{% if not entryNumber < entryLimit %}<a href="/search?{{ urlencode(builtins.dict(QueryValues, **{'p': searchPage+1})) }}">Next</a>{% endif %}
{#{% if not entryNumber < entryLimit %}<a href="/search?q={{ QueryValues['q'] }}&sort={{ QueryValues['sort'] }}&dir={{ QueryValues['dir'] }}&date={{ QueryValues['date'] }}&p={{ QueryValues['p']+1 }}">Next</a>{% endif %}#}
</div> </div>
</div> </div>
{% endif %} {% endif %}