update readme due release on codeberg #2

Open
nocci wants to merge 26 commits from dev into main
7 changed files with 119 additions and 65 deletions
Showing only changes of commit 6649cd6e23 - Show all commits

184
setup.sh
View File

@ -589,7 +589,7 @@ def export_pdf():
img = Paragraph('', styles['Normal']) img = Paragraph('', styles['Normal'])
elif game.url and 'gog.com' in game.url: elif game.url and 'gog.com' in game.url:
try: try:
img_path = os.path.join(app.root_path, 'static', 'gog_logo.png') img_path = os.path.join(app.root_path, 'static', 'gog_logo.webp')
img = Image(img_path, width=3*cm, height=img_height) img = Image(img_path, width=3*cm, height=img_height)
except Exception: except Exception:
img = Paragraph('', styles['Normal']) img = Paragraph('', styles['Normal'])
@ -821,10 +821,11 @@ SHELL ["/bin/bash", "-c"]
RUN apt-get update && apt-get install -y --no-install-recommends wget \ RUN apt-get update && apt-get install -y --no-install-recommends wget \
&& mkdir -p /app/static \ && mkdir -p /app/static \
&& wget -O /app/static/logo.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo.png" \ && wget -O /app/static/logo.webp "https://drop.nocadmin.net/logo.webp" \
&& wget -O /app/static/logo_small.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo_small.png" \ && wget -O /app/static/logo_small.webp "https://drop.nocadmin.net/logo_small.webp" \
&& wget -O /app/static/forgejo.svg "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/forgejo.svg" \ && wget -O /app/static/forgejo.webp "https://drop.nocadmin.net/forgejo.webp" \
&& wget -O /app/static/gog_logo.png "https://git.nocci.it/nocci/GameKeyManager/raw/branch/dev/steam-gift-manager/static/gog_logo.png" \ && wget -O /app/static/gog_logo.webp "https://drop.nocadmin.net/gog_logo.webp" \
&& wget -O /app/static/logo_small_maskable.webp "https://drop.nocadmin.net/logo_small_maskable.webp" \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/data && \ RUN mkdir -p /app/data && \
@ -945,6 +946,7 @@ chmod +x ../upgrade.sh
# Manifest for PWA # Manifest for PWA
cat <<MANIFEST_END > static/manifest.json cat <<MANIFEST_END > static/manifest.json
{ {
"id": "/",
"name": "Game Key Manager", "name": "Game Key Manager",
"short_name": "GameKeys", "short_name": "GameKeys",
"start_url": "/", "start_url": "/",
@ -952,31 +954,44 @@ cat <<MANIFEST_END > static/manifest.json
"background_color": "#212529", "background_color": "#212529",
"theme_color": "#212529", "theme_color": "#212529",
"description": "Manage Steam/GOG keys easily!", "description": "Manage Steam/GOG keys easily!",
"orientation": "any",
"launch_handler": {
"client_mode": "navigate-existing"
},
"icons": [ "icons": [
{ {
"src": "/static/logo_small.png", "src": "/static/logo_small.webp",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/webp",
"purpose": "any maskable" "purpose": "any"
}, },
{ {
"src": "/static/logo.png", "src": "/static/logo_small_maskable.webp",
"sizes": "192x192",
"type": "image/webp",
"purpose": "maskable"
},
{
"src": "/static/logo.webp",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/webp",
"purpose": "any maskable"
} }
] ]
} }
MANIFEST_END MANIFEST_END
# Service Worker # Service Worker
cat <<SW_END > static/serviceworker.js cat <<SW_END > static/serviceworker.js
const CACHE_NAME = 'game-key-manager-v1'; const CACHE_NAME = 'game-key-manager-v1';
const ASSETS = [ const ASSETS = [
'/', '/',
'/static/style.css', '/static/style.css',
'/static/logo.png', '/static/logo.webp',
'/static/logo_small.png', '/static/logo_small.webp',
'/static/gog_logo.png' '/static/gog_logo.webp',
'/static/forgejo.webp'
]; ];
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
@ -992,6 +1007,15 @@ self.addEventListener('fetch', (event) => {
.then(cachedResponse => cachedResponse || fetch(event.request)) .then(cachedResponse => cachedResponse || fetch(event.request))
); );
}); });
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
))
);
});
SW_END SW_END
@ -1001,41 +1025,47 @@ mkdir -p templates static
# Base Template # Base Template
cat <<HTML_END > templates/base.html cat <<HTML_END > templates/base.html
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ get_locale() }}" data-bs-theme="{{ theme }}"> <html lang="{{ get_locale() if get_locale() in ['en', 'de'] else 'en' }}" data-bs-theme="{{ theme }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ _('Game Key Manager') }}</title> <meta name="description" content="Manage your Steam and GOG keys efficiently. Track redemption dates, share games, and export lists.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<meta name="theme-color" content="#212529"> <meta name="theme-color" content="#212529">
<title>{{ _('Game Key Manager') }}</title>
<!-- PWA Manifest -->
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<!-- Preload Bootstrap CSS for better LCP -->
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"></noscript>
<!-- Critical CSS (Above-the-Fold) kann hier inline ergänzt werden -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg bg-body-tertiary"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container"> <div class="container">
<a class="navbar-brand d-flex align-items-center gap-2" href="/"> <a class="navbar-brand d-flex align-items-center gap-2" href="/">
<img src="{{ url_for('static', filename='logo_small.png') }}" alt="Logo" width="150" height="116" style="object-fit:contain; border-radius:8px;"> <img src="{{ url_for('static', filename='logo_small.webp') }}" alt="Logo" width="150" height="116" style="object-fit:contain; border-radius:8px;">
<span>Game Key Manager</span> <span>Game Key Manager</span>
</a> </a>
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<form class="d-flex" action="{{ url_for('index') }}" method="GET"> <form class="d-flex" action="{{ url_for('index') }}" method="GET">
<input class="form-control me-2" <label for="searchInput" class="visually-hidden">{{ _('Search') }}</label>
type="search" <input class="form-control me-2"
type="search"
name="q" name="q"
id="searchInput"
placeholder="{{ _('Search') }}" placeholder="{{ _('Search') }}"
value="{{ search_query }}"> value="{{ search_query }}">
<button class="btn btn-outline-success" type="submit">🔍</button> <button class="btn btn-outline-success" type="submit">🔍</button>
</form> </form>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" <input class="form-check-input"
type="checkbox" type="checkbox"
id="darkModeSwitch" {% if theme == 'dark' %}checked{% endif %}> id="darkModeSwitch" {% if theme == 'dark' %}checked{% endif %}>
<label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label> <label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
</div> </div>
<div class="dropdown ms-3"> <div class="dropdown ms-3">
<!-- DEBUG: Current locale {{ get_locale() }} -->
<div hidden id="locale-debug" data-locale="{{ get_locale() }}"></div> <div hidden id="locale-debug" data-locale="{{ get_locale() }}"></div>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
{% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %} {% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %}
@ -1046,12 +1076,12 @@ cat <<HTML_END > templates/base.html
</ul> </ul>
</div> </div>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Password') }}</a> <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Password') }}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a> <a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a>
</li> </li>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -1071,29 +1101,31 @@ cat <<HTML_END > templates/base.html
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
// Service Worker Registration for PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('{{ url_for("static", filename="serviceworker.js") }}', {scope: '/'})
.then(registration => {
console.log('ServiceWorker registered:', registration.scope);
})
.catch(error => {
console.log('ServiceWorker registration failed:', error);
});
});
}
// Dark Mode Switch
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const toggle = document.getElementById('darkModeSwitch') const toggle = document.getElementById('darkModeSwitch')
const html = document.documentElement const html = document.documentElement
if (toggle) {
toggle.addEventListener('change', function() { toggle.addEventListener('change', function() {
const theme = this.checked ? 'dark' : 'light' const theme = this.checked ? 'dark' : 'light'
fetch('/set-theme/' + theme) fetch('/set-theme/' + theme)
.then(() => html.setAttribute('data-bs-theme', theme)) .then(() => html.setAttribute('data-bs-theme', theme))
}) });
}) }
});
</script> </script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/static/serviceworker.js')
.then(function(registration) {
console.log('ServiceWorker registered:', registration.scope);
}, function(err) {
console.log('ServiceWorker registration failed:', err);
});
});
}
</script>
{% include "footer.html" %} {% include "footer.html" %}
</body> </body>
</html> </html>
@ -1134,10 +1166,17 @@ cat <<HTML_END > templates/index.html
<td> <td>
{% if game.steam_appid %} {% if game.steam_appid %}
<img src="https://cdn.cloudflare.steamstatic.com/steam/apps/{{ game.steam_appid }}/header.jpg" <img src="https://cdn.cloudflare.steamstatic.com/steam/apps/{{ game.steam_appid }}/header.jpg"
alt="Steam Header" class="game-cover"> alt="Steam Header"
class="game-cover"
{% if loop.first %}fetchpriority="high"{% endif %}
width="368"
height="172">
{% elif game.url and 'gog.com' in game.url %} {% elif game.url and 'gog.com' in game.url %}
<img src="{{ url_for('static', filename='gog_logo.png') }}" <img src="{{ url_for('static', filename='gog_logo.webp') }}"
alt="GOG Logo" class="game-cover"> alt="GOG Logo"
class="game-cover"
width="368"
height="172">
{% endif %} {% endif %}
</td> </td>
<td>{{ game.name }}</td> <td>{{ game.name }}</td>
@ -1222,7 +1261,7 @@ cat <<HTML_END > templates/login.html
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-body text-center"> <div class="card-body text-center">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="266" height="206" class="mb-4" style="object-fit:contain;"> <img src="{{ url_for('static', filename='logo.webp') }}" alt="Logo" width="266" height="206" class="mb-4" style="object-fit:contain;">
<h2 class="card-title mb-4">{{ _('Login') }}</h2> <h2 class="card-title mb-4">{{ _('Login') }}</h2>
<form method="POST"> <form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
@ -1545,7 +1584,7 @@ cat <<HTML_END > templates/footer.html
</div> </div>
<div class="mb-2"> <div class="mb-2">
<a href="https://git.nocci.it/nocci/GiftGamesDB" target="_blank" rel="noopener"> <a href="https://git.nocci.it/nocci/GiftGamesDB" target="_blank" rel="noopener">
<img src="{{ url_for('static', filename='forgejo.svg') }}" alt="forgejo" width="20" style="vertical-align:middle;margin-right:4px;"> <img src="{{ url_for('static', filename='forgejo.webp') }}" alt="forgejo" width="20" style="vertical-align:middle;margin-right:4px;">
find the source code on my Forgejo find the source code on my Forgejo
</a> </a>
</div> </div>
@ -1637,38 +1676,53 @@ body {
transition: width 0.2s, height 0.2s; transition: width 0.2s, height 0.2s;
} }
/* Responsiv für kleinere Bildschirme */ /* Responsive Cover Images */
.game-cover {
width: 368px;
height: 172px;
object-fit: contain;
background: #222;
border-radius: 6px;
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
.game-cover { .game-cover {
width: 260px; width: 260px;
height: 122px; height: 122px;
} }
} }
@media (max-width: 900px) {
@media (max-width: 992px) {
.game-cover { .game-cover {
width: 180px; width: 180px;
height: 84px; height: 84px;
} }
} }
@media (max-width: 600px) {
@media (max-width: 768px) {
.game-cover { .game-cover {
width: 120px; width: 120px;
height: 56px; height: 56px;
} }
} }
@media (max-width: 400px) {
@media (max-width: 576px) {
.game-cover { .game-cover {
width: 90px; width: 90px;
height: 42px; height: 42px;
} }
} }
img.game-cover { /* Accessibility Improvements */
width: 368px; .visually-hidden {
height: 172px; position: absolute;
object-fit: contain; width: 1px;
background: #222; height: 1px;
border-radius: 8px; padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
} }
CSS_END CSS_END

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB