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, 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) @app.get("/level/{level_id}", response_class=HTMLResponse)
async def showLevel(level_id: int): async def showLevel(level_id: int):

View file

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

View file

@ -11,10 +11,23 @@
} }
.fullResponse table, .fullResponse table td { .fullResponse table, .fullResponse table td {
border: 1px solid gray; border: 2px solid transparent;
background-color: #30303a55;
border-collapse: collapse; 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 { .searchResultCard {
display: grid; display: grid;
margin: 4px; margin: 4px;
@ -91,21 +104,21 @@
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
} }
.levelTitle a:link, a:visited { /* .levelTitle a:link, a:visited { */
color: inherit; /* color: inherit; */
text-decoration: initial; /* text-decoration: initial; */
cursor: auto; /* cursor: auto; */
} /* } */
/**/
.levelTitle a:hover { /* .levelTitle a:hover { */
outline: 2px dotted blue; /* outline: 2px dotted blue; */
} /* } */
/**/
.levelTitle a:link:active { /* .levelTitle a:link:active { */
color: inherit; /* color: inherit; */
outline: 2px dotted blue; /* outline: 2px dotted blue; */
} /* } */
/**/
.creatorName { .creatorName {
justify-self: left; justify-self: left;
text-align: left; text-align: left;
@ -152,15 +165,18 @@
text-align: center; text-align: center;
border: 1px solid #505050; border: 1px solid #505050;
transition: background-color 200ms ease;
} }
.levelCode input:focus { .levelCode input:focus {
outline: none; outline: none;
background-color: #00FFFF10; background-color: #00FFFF10;
transition: background-color 1s ease;
} }
.levelCode:active, .levelCode input:active { .levelCode input:active {
animation: pulse 0.5s 1 ease-out; /* animation: pulse 0.5s 1 ease-out; */
background-color: lime;
} }
.illegal { .illegal {
@ -179,3 +195,16 @@
font-size: 2em; font-size: 2em;
overflow: hidden; 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) 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, """Searches for environ value for the specified key,
or returns the default value. 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] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
pymongo = "^4.3.2"
Jinja2 = "^3.1.2" Jinja2 = "^3.1.2"
uvicorn = "^0.19.0" uvicorn = "^0.19.0"
aiohttp = {extras = ["speedups"], version = "^3.8.3"} aiohttp = {extras = ["speedups"], version = "^3.8.3"}