Skip to content

Commit 254dc6a

Browse files
committed
Add JS download card
Fixes #655
1 parent b79d291 commit 254dc6a

6 files changed

Lines changed: 414 additions & 225 deletions

File tree

app/blueprints/packages/packages.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from flask_login import login_required, current_user
1313
from flask_wtf import FlaskForm
1414
from jinja2.utils import markupsafe
15-
from sqlalchemy import func, or_, and_
16-
from sqlalchemy.orm import joinedload, subqueryload
15+
from sqlalchemy import func, or_, and_, select
16+
from sqlalchemy.orm import joinedload, subqueryload, Session
1717
from wtforms import SelectField, StringField, TextAreaField, IntegerField, SubmitField, BooleanField
1818
from wtforms.validators import InputRequired, Length, Regexp, DataRequired, Optional, URL, NumberRange, ValidationError
1919
from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
@@ -31,7 +31,7 @@
3131
from app.models import Package, Tag, db, User, Tags, PackageState, Permission, PackageType, MetaPackage, ForumTopic, \
3232
Dependency, Thread, UserRank, PackageReview, PackageDevState, ContentWarning, License, AuditSeverity, \
3333
PackageScreenshot, NotificationType, AuditLogEntry, PackageAlias, PackageProvides, PackageGameSupport, \
34-
PackageDailyStats, Collection, ReleaseState, PackageAIDisclosure
34+
PackageDailyStats, Collection, ReleaseState, PackageAIDisclosure, LuantiRelease, PackageRelease
3535
from app.utils.models import is_package_page, add_audit_log, get_package_by_info, \
3636
add_notification, get_system_user, get_games_from_csv, \
3737
post_to_approval_thread
@@ -115,6 +115,41 @@ def user_redirect(author):
115115
return redirect(url_for("users.profile", username=author))
116116

117117

118+
def get_latest_releases_per_version(session: Session, package_id: int):
119+
ranked = (
120+
select(
121+
LuantiRelease.id,
122+
PackageRelease.id,
123+
PackageRelease.name,
124+
PackageRelease.file_size_bytes,
125+
PackageRelease.created_at,
126+
func.row_number()
127+
.over(
128+
partition_by=LuantiRelease.id,
129+
order_by=PackageRelease.id.desc()
130+
)
131+
.label("rn"),
132+
)
133+
.join(
134+
PackageRelease,
135+
and_(
136+
PackageRelease.package_id == package_id,
137+
or_(PackageRelease.min_rel_id == None, PackageRelease.min_rel_id <= LuantiRelease.id),
138+
or_(PackageRelease.max_rel_id == None, PackageRelease.max_rel_id >= LuantiRelease.id),
139+
PackageRelease.state == ReleaseState.APPROVED,
140+
)
141+
)
142+
.subquery()
143+
)
144+
145+
stmt = select(ranked).where(ranked.c.rn == 1)
146+
rows = session.execute(stmt).all()
147+
return {
148+
luanti_release_id: release
149+
for luanti_release_id, *release in rows
150+
}
151+
152+
118153
@bp.route("/packages/<author>/<name>/")
119154
@is_package_page
120155
def view(package):
@@ -169,11 +204,19 @@ def view(package):
169204
Collection.packages.contains(package),
170205
Collection.name == "favorites").count() > 0
171206

207+
luanti_versions = LuantiRelease.query.filter(LuantiRelease.protocol > 0).order_by(db.asc(LuantiRelease.id)).all()
208+
import time
209+
start = time.perf_counter()
210+
latest_by_version = get_latest_releases_per_version(db.session, package.id)
211+
end = time.perf_counter()
212+
elapsed = f'Time taken: {(end - start):.6f} seconds'
213+
172214
return render_template("packages/view.html",
173215
package=package, releases=releases.all(), packages_uses=packages_uses,
174216
review_thread=review_thread, threads=threads.all(), reviews=reviews, validation=validation,
175217
has_review=has_review, favorites_count=favorites_count, is_favorited=is_favorited,
176-
public_collection_count=public_collection_count)
218+
public_collection_count=public_collection_count, luanti_versions=luanti_versions,
219+
latest_by_version=latest_by_version, elapsed=elapsed)
177220

178221

179222
@bp.route("/packages/<author>/<name>/shields/<type>/")

app/models/packages.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from . import db
2020
from .users import Permission, UserRank, User
2121
from app.utils.flask import abs_url_for, abs_url
22+
from app.utils.misc import format_file_size
23+
2224

2325
class License(db.Model):
2426
id = db.Column(db.Integer, primary_key=True)
@@ -1426,11 +1428,7 @@ def calculate_file_size_bytes(self):
14261428

14271429
@property
14281430
def file_size(self):
1429-
size = self.file_size_bytes / 1024
1430-
if size > 1024:
1431-
return f"{round(size / 1024, 1)} MB"
1432-
else:
1433-
return f"{round(size)} KB"
1431+
return format_file_size(self.file_size_bytes)
14341432

14351433
def get_edit_url(self):
14361434
return url_for("packages.edit_screenshot",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// @author rubenwardy
2+
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
3+
4+
"use strict";
5+
6+
addEventListener("load", () => {
7+
const downloadCard = document.getElementById("download-card");
8+
downloadCard.classList.remove("d-none");
9+
10+
const downloadNoJS = document.getElementById("download-nojs");
11+
downloadNoJS.classList.add("d-none");
12+
13+
const luantiVersion = document.getElementById("luanti-version");
14+
luantiVersion.addEventListener("change", () => {
15+
setLuantiVersion(luantiVersion.selectedOptions[0]);
16+
});
17+
18+
const luantiVersionId = localStorage.getItem("luanti-version");
19+
if (luantiVersionId && document.querySelector(`#luanti-version option[value='${luantiVersionId}']`)) {
20+
luantiVersion.value = luantiVersionId;
21+
}
22+
setLuantiVersion(luantiVersion.selectedOptions[0]);
23+
});
24+
25+
function setLuantiVersion(version) {
26+
if (!version) {
27+
return;
28+
}
29+
30+
localStorage.setItem("luanti-version", version.value);
31+
32+
const {
33+
releaseName,
34+
releaseUrl,
35+
releaseSize,
36+
releaseCreatedAt,
37+
latest,
38+
} = version.dataset;
39+
40+
const success = document.getElementById("download-success");
41+
const noRelease = document.getElementById("download-no-release");
42+
const newerLuanti = document.getElementById("download-newer-luanti");
43+
if (latest) {
44+
newerLuanti.classList.add("d-none");
45+
} else {
46+
newerLuanti.classList.remove("d-none");
47+
}
48+
49+
if (releaseName) {
50+
success.classList.remove("d-none");
51+
noRelease.classList.add("d-none");
52+
53+
const btn = document.querySelector("#download-card .btn-download");
54+
btn.setAttribute("href", releaseUrl);
55+
56+
const size = document.getElementById("download-file-size");
57+
if (releaseSize > 1024*1024) {
58+
size.textContent = `[${(releaseSize / 1024 / 1024).toFixed(1)} MB]`;
59+
} else {
60+
size.textContent = `[${(releaseSize / 1024).toFixed(0)} KB]`;
61+
}
62+
63+
const info = document.getElementById("download-release-info");
64+
info.textContent = info.dataset.releaseText
65+
.replace("@1", releaseName)
66+
.replace("@2", releaseCreatedAt);
67+
} else {
68+
success.classList.add("d-none");
69+
noRelease.classList.remove("d-none");
70+
}
71+
}

app/template_filters.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .models import Permission, Package, PackageState, PackageRelease, ReleaseState
1616
from app.utils.flask import abs_url_for, url_set_query, url_set_anchor, url_current, abs_url as do_abs_url
1717
from app.utils.luanti_hypertext import normalize_whitespace as do_normalize_whitespace
18+
from app.utils.misc import format_file_size as format_file_size_impl
1819

1920

2021
@app.context_processor
@@ -121,3 +122,8 @@ def flatten(list):
121122
for x in list:
122123
ret.extend(x)
123124
return ret
125+
126+
127+
@app.template_filter()
128+
def format_file_size(bytes: int):
129+
return format_file_size_impl(bytes)

0 commit comments

Comments
 (0)