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:
parent
347143ab44
commit
5f4a08f3ad
3 changed files with 75 additions and 8 deletions
|
@ -3,6 +3,9 @@ from fastapi.staticfiles import StaticFiles
|
|||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.responses import HTMLResponse, PlainTextResponse
|
||||
from fastapi import FastAPI, Form, Response
|
||||
from typing import Optional
|
||||
import urllib.parse
|
||||
import starlette
|
||||
import jinja2
|
||||
import uvicorn
|
||||
import httpx
|
||||
|
@ -25,7 +28,11 @@ template_env = jinja2.Environment(
|
|||
|
||||
error_template = template_env.get_template("error.html")
|
||||
|
||||
|
||||
template_env.globals["builtins"] = __builtins__
|
||||
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["isSelected"] = utils.isSelected
|
||||
template_env.globals["generate_thumbnail_url"] = utils.generate_thumbnail_url
|
||||
|
@ -68,6 +75,7 @@ async def root():
|
|||
|
||||
@app.get("/search", response_class=HTMLResponse)
|
||||
async def search(
|
||||
request: starlette.requests.Request,
|
||||
q: Union[str, None] = None,
|
||||
p: int = 0,
|
||||
sort: str = "average_rating",
|
||||
|
@ -80,7 +88,7 @@ async def search(
|
|||
p = 0
|
||||
|
||||
# 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
|
||||
if q is not None:
|
||||
|
|
|
@ -4,6 +4,17 @@
|
|||
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 {
|
||||
display: grid;
|
||||
margin: 4px;
|
||||
|
@ -117,6 +128,18 @@
|
|||
outline: 1px dashed pink; */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
border: 2px solid initial;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
to {
|
||||
border: 2px solid lime;
|
||||
color: lime
|
||||
}
|
||||
}
|
||||
|
||||
.levelCode {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
@ -131,6 +154,19 @@
|
|||
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 {
|
||||
display: flex;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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 name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
@ -18,7 +19,7 @@
|
|||
// input field to clipboard, and
|
||||
// adds the "copied" css class
|
||||
navigator.clipboard.writeText(obj.value);
|
||||
alert("Level code copied")
|
||||
//alert("Level code copied")
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
@ -75,13 +76,33 @@
|
|||
<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="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 %}
|
||||
<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 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']) %}
|
||||
<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>
|
||||
|
@ -90,10 +111,12 @@
|
|||
|
||||
</div>
|
||||
<div class="searchResultsFooter">
|
||||
{% set searchPage = QueryValues['p'] %}
|
||||
{% set searchPage = builtins.int(QueryValues['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 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?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?{{ 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>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue