Add /playlist/{id}

- Shows requested playlist, works similarly to /search
This commit is contained in:
magmaus3 2023-06-08 19:00:26 +02:00
parent b3859eb5be
commit 9de7d20a18
Signed by: magmaus3
GPG key ID: 966755D3F4A9B251
7 changed files with 1016 additions and 645 deletions

View file

@ -174,6 +174,31 @@ async def search(
QueryValues=QueryValues,
)
@app.get("/playlist/{playlist_id}", response_class=HTMLResponse)
async def showList(request: starlette.requests.Request, playlist_id: int):
template = template_env.get_template("playlist.html")
try:
rq = httpx.get(BASE_URL + "/api/v1/list/" + str(playlist_id), timeout=10)
if rq.status_code == 503:
return error_template.render(reason="Server is unavailable right now")
response = rq.json()
# searchResults = response["Maps"]
print(rq.url)
except httpx.ReadTimeout:
return error_template.render(reason="Server timed out")
except json.decoder.JSONDecodeError:
return error_template.render(
reason="Failed to parse server response.", details=rq.text
)
except Exception as exc:
return error_template.render(reason="Uncaught exception", details=exc)
return template.render(
response=response,
QueryValues=request.query_params,
THUMB_URL=THUMB_URL,
)
@app.get("/level/{level_id}", response_class=HTMLResponse)
async def showLevel(level_id: int):

View file

@ -86,17 +86,20 @@ code {
}
a:link, a:visited {
color: inherit;
text-decoration: initial;
cursor: auto;
a, a:link, a:visited {
color: inherit;
text-decoration: initial;
transition: color 200ms ease;
transition: text-shadow 100ms ease;
}
a:hover {
outline: 2px dotted blue;
color: cyan;
/* outline: 2px dotted blue; */
}
a:link:active {
color: inherit;
outline: 2px dotted blue;
color: cyan;
text-shadow: 5px 5px 15px cyan;
/* outline: 2px dotted blue; */
}

View file

@ -11,10 +11,23 @@
}
.fullResponse table, .fullResponse table td {
border: 1px solid gray;
border: 2px solid transparent;
background-color: #30303a55;
border-collapse: collapse;
}
/* .fullResponse table tr { */
/* transition: background-color 100ms cubic-bezier(0.19, 1, 0.22, 1); */
/* } */
.fullResponse table thead {
background-color: #000;
}
.fullResponse table tr:hover {
background-color: #FFFFFF55;
}
.searchResultCard {
display: grid;
margin: 4px;
@ -91,21 +104,21 @@
margin-bottom: 0.3rem;
}
.levelTitle a:link, a:visited {
color: inherit;
text-decoration: initial;
cursor: auto;
}
.levelTitle a:hover {
outline: 2px dotted blue;
}
.levelTitle a:link:active {
color: inherit;
outline: 2px dotted blue;
}
/* .levelTitle a:link, a:visited { */
/* color: inherit; */
/* text-decoration: initial; */
/* cursor: auto; */
/* } */
/**/
/* .levelTitle a:hover { */
/* outline: 2px dotted blue; */
/* } */
/**/
/* .levelTitle a:link:active { */
/* color: inherit; */
/* outline: 2px dotted blue; */
/* } */
/**/
.creatorName {
justify-self: left;
text-align: left;
@ -152,15 +165,18 @@
text-align: center;
border: 1px solid #505050;
transition: background-color 200ms ease;
}
.levelCode input:focus {
outline: none;
background-color: #00FFFF10;
transition: background-color 1s ease;
}
.levelCode:active, .levelCode input:active {
animation: pulse 0.5s 1 ease-out;
.levelCode input:active {
/* animation: pulse 0.5s 1 ease-out; */
background-color: lime;
}
.illegal {
@ -179,3 +195,16 @@
font-size: 2em;
overflow: hidden;
}*/
/* Playlist only */
.playlist-details {
margin-bottom: 1em;
}
.playlist-details h2 {
font-size: large;
}
.playlist-details p {
margin: 0em;
}

View file

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- {{ QueryValues }} -->
<title>IWM Browser</title>
<meta charset="UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ combineCSS(
'/static/index.css',
'/static/main.css',
'/static/search/search.css',
'/static/search/searchResults.css'
) }}">
<!-- Optional features -->
<script>
function copy(obj) {
// copies the contents of the
// input field to clipboard, and
// adds the "copied" css class
navigator.clipboard.writeText(obj.value);
//alert("Level code copied")
}
</script>
</head>
<body>
<div class="Search">
<div class="contentBox_">
<div class="playlist-details">
<h1>{{ QueryValues.name if QueryValues.name}}{{ "Playlist" if not QueryValues.name}} </h1>
<h2>by <a href="/user/{{ response.CreatorID }}">{{ response.CreatorName }}</a></h2>
<p>ID: {{ response.ID }}</p>
<p>Maps: {{ response.MapCount }}</p>
</div>
{% if response.Maps %}
<div class="searchResults">
{% for map in response.Maps %}
<div class="searchResultCard">
<div class="thumbnail"><img aria-hidden="true" src="{{ generate_thumbnail_url(map['ID'], THUMB_URL) }}"></div>
<div class="details">
<div class="basic">
<div class="levelTitle"><a href="/level/{{ map['ID'] }}">{{ map['Name'] }}</a></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="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: <a href="/user/{{map['FirstClearUserID']}}"><b>{{ map['FirstClearUsername'] }}</b></a></div>
{% set BestTime = convert_times(map['BestTimePlaytime']) %}
{% 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>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</body>
</html>

View file

@ -17,7 +17,7 @@ def convert_times(time):
return (hours, minutes, seconds)
def config_value(key: str, default_value=None):
def config_value(key: str, default_value=""):
"""Searches for environ value for the specified key,
or returns the default value.

1465
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@ start = "iwm_browser.main:start"
[tool.poetry.dependencies]
python = "^3.8"
pymongo = "^4.3.2"
Jinja2 = "^3.1.2"
uvicorn = "^0.19.0"
aiohttp = {extras = ["speedups"], version = "^3.8.3"}