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.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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue