Compare commits
21 Commits
ec9c1c5205
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1233851b87 | |||
| b6d7060e79 | |||
| 90dcc10956 | |||
| 29f9371c1e | |||
| edc6d84ede | |||
| fc3e81c2d6 | |||
|
|
1472c14d44 | ||
|
|
c0c25e2719 | ||
|
|
afd11fba3a | ||
|
|
b56b7176a8 | ||
|
|
15a8072804 | ||
|
|
31e8d152ae | ||
|
|
aa9719cbfe | ||
|
|
f4e5c33c87 | ||
|
|
db575f2bb7 | ||
|
|
3394d66151 | ||
|
|
3a700c4f48 | ||
|
|
f850e4d69c | ||
|
|
49c7a2bba8 | ||
|
|
6e7f3e5e16 | ||
|
|
9e49262118 |
1
.gitignore
vendored
@@ -259,3 +259,4 @@ flycheck_*.el
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
pga.db
|
||||
|
||||
80
AGENTS.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to AI coding agents when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Lutris Year in Review is a Python script that generates a static HTML playtime report from a Lutris gaming platform SQLite database. The report displays game playtime statistics with interactive Chart.js visualizations.
|
||||
|
||||
## Commands
|
||||
|
||||
Generate report with defaults:
|
||||
```bash
|
||||
python generate_report.py
|
||||
```
|
||||
|
||||
Generate report with modern style:
|
||||
```bash
|
||||
python generate_report.py --style glassmorphism --output report.html
|
||||
python generate_report.py --style brutalism --output report.html
|
||||
python generate_report.py --style skeuo --output report.html
|
||||
python generate_report.py --style neumorphism --output report.html
|
||||
python generate_report.py --style material --output report.html
|
||||
python generate_report.py --style flat --output report.html
|
||||
python generate_report.py --style synthwave --output report.html
|
||||
python generate_report.py --style vaporwave --output report.html
|
||||
python generate_report.py --style terminal --output report.html
|
||||
python generate_report.py --style highcontrast --output report.html
|
||||
```
|
||||
|
||||
Generate report with legacy Platinum template:
|
||||
```bash
|
||||
python generate_report.py --template templates/platinum.html --output report.html
|
||||
```
|
||||
|
||||
All options:
|
||||
```bash
|
||||
python generate_report.py --db pga.db --output report.html --top 10 --background background.png --style glassmorphism
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
**Report generator (`generate_report.py`):**
|
||||
- Reads Lutris SQLite database (`pga.db`) containing games, categories, and playtime data
|
||||
- Embeds all data (games JSON, background image as base64) directly into a self-contained HTML file
|
||||
- Loads HTML template from `templates/` folder (default: `templates/platinum.html`)
|
||||
|
||||
**HTML templates (`templates/`):**
|
||||
- **modern.html**: Unified template for modern styles (brutalism, glassmorphism, skeuo, neumorphism, material, flat, synthwave, vaporwave, terminal, highcontrast)
|
||||
- **platinum.html**: Legacy Mac OS 9 Platinum visual style (separate template due to unique structure)
|
||||
- All templates use Chart.js doughnut charts and dynamic JavaScript filtering
|
||||
- Placeholder tokens like `__ALL_GAMES__`, `__BACKGROUND_IMAGE__` are replaced at generation time
|
||||
- Modern templates support light/dark/auto theme toggle button
|
||||
|
||||
**Style system (`styles.py`):**
|
||||
- CSS definitions for each modern style (brutalism, glassmorphism, skeuo, neumorphism, material, flat, synthwave, vaporwave, terminal, highcontrast)
|
||||
- Theme configurations (colors, fonts, chart options) injected via `__THEME_CSS__`
|
||||
- Use `--style` argument to select a modern style instead of `--template`
|
||||
|
||||
**Javascript (`templates/script.js`):**
|
||||
- A single common script is used for each modern style (brutalism, glassmorphism, skeuo, neumorphism, material, flat, synthwave, vaporwave, terminal, highcontrast)
|
||||
- Theme configurations (colors, fonts, chart options) injected via `__THEME_CONFIG__`
|
||||
- Data inserted via `__ALL_GAMES__` and `__TOP_N__`
|
||||
|
||||
**Database schema (`schema.py`):**
|
||||
- Reference file documenting Lutris database structure
|
||||
- Key tables: `games` (with `playtime`, `service` fields), `categories`, `games_categories` (many-to-many join)
|
||||
|
||||
**Generated output (`report.html`):**
|
||||
- Fully static, can be hosted on any web server
|
||||
- Client-side filtering by service (Steam, GOG, itch.io, local)
|
||||
- Expandable "Others" row in games table
|
||||
- Light/dark/auto theme toggle button with persistent preference
|
||||
- Responsive design for mobile and desktop
|
||||
|
||||
## Key Data Relationships
|
||||
|
||||
- Games have a `service` field (steam, gog, itchio, humblebundle, or NULL for local)
|
||||
- Games link to categories via `games_categories` join table
|
||||
- Categories like `.hidden` and `favorite` are filtered out in the report display
|
||||
- `playtime` is cumulative hours (REAL), not per-session data
|
||||
49
CLAUDE.md
@@ -1,48 +1 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Lutris Year in Review is a Python script that generates a static HTML playtime report from a Lutris gaming platform SQLite database. The report displays game playtime statistics with interactive Chart.js visualizations.
|
||||
|
||||
## Commands
|
||||
|
||||
Generate report with defaults:
|
||||
```bash
|
||||
python generate_report.py
|
||||
```
|
||||
|
||||
Generate report with custom options:
|
||||
```bash
|
||||
python generate_report.py --db pga.db --output report.html --top 10 --background background.png --template platinum.html
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
**Report generator (`generate_report.py`):**
|
||||
- Reads Lutris SQLite database (`pga.db`) containing games, categories, and playtime data
|
||||
- Embeds all data (games JSON, background image as base64) directly into a self-contained HTML file
|
||||
- Loads HTML template from external file (default: `platinum.html`)
|
||||
|
||||
**HTML template (`platinum.html`):**
|
||||
- Chart.js doughnut charts and dynamic JavaScript filtering
|
||||
- Mac OS 9 Platinum visual style with placeholder tokens for assets
|
||||
- Tokens like `__ALL_GAMES__`, `__BACKGROUND_IMAGE__` are replaced at generation time
|
||||
|
||||
**Database schema (`schema.py`):**
|
||||
- Reference file documenting Lutris database structure
|
||||
- Key tables: `games` (with `playtime`, `service` fields), `categories`, `games_categories` (many-to-many join)
|
||||
|
||||
**Generated output (`report.html`):**
|
||||
- Fully static, can be hosted on any web server
|
||||
- Client-side filtering by service (Steam, GOG, itch.io, local)
|
||||
- Expandable "Others" row in games table
|
||||
- Light/dark mode support via CSS `prefers-color-scheme`
|
||||
|
||||
## Key Data Relationships
|
||||
|
||||
- Games have a `service` field (steam, gog, itchio, humblebundle, or NULL for local)
|
||||
- Games link to categories via `games_categories` join table
|
||||
- Categories like `.hidden`, `favorite`, `Horny` are filtered out in the report
|
||||
- `playtime` is cumulative hours (REAL), not per-session data
|
||||
@AGENTS.md
|
||||
|
||||
@@ -21,9 +21,25 @@ import json
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from styles import get_theme_css, get_theme_config
|
||||
|
||||
# Directory where this script is located (for finding template.html)
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
|
||||
# Modern styles that use the unified template
|
||||
MODERN_STYLES = [
|
||||
"brutalism",
|
||||
"glassmorphism",
|
||||
"skeuo",
|
||||
"neumorphism",
|
||||
"material",
|
||||
"flat",
|
||||
"synthwave",
|
||||
"vaporwave",
|
||||
"terminal",
|
||||
"highcontrast",
|
||||
]
|
||||
|
||||
|
||||
def load_template(template_file: str) -> str:
|
||||
"""Load the HTML template from the specified file."""
|
||||
@@ -31,6 +47,12 @@ def load_template(template_file: str) -> str:
|
||||
return template_path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def load_script(script_file: str) -> str:
|
||||
"""Load the JS script from the specified file."""
|
||||
script_path = SCRIPT_DIR / script_file
|
||||
return script_path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def load_asset_as_base64(path: Path, mime_type: str) -> str:
|
||||
"""Load a file and return it as a base64 data URL."""
|
||||
if path.exists():
|
||||
@@ -49,7 +71,7 @@ def get_all_games(db_path: str) -> tuple[list[dict], int]:
|
||||
total_library = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, name, playtime, COALESCE(service, 'local') as service
|
||||
SELECT id, name, playtime, COALESCE(service, 'local') as service, COALESCE(runner, 'unknown') as runner
|
||||
FROM games
|
||||
WHERE playtime > 0
|
||||
ORDER BY playtime DESC
|
||||
@@ -75,14 +97,23 @@ def get_all_games(db_path: str) -> tuple[list[dict], int]:
|
||||
"name": row[1],
|
||||
"playtime": row[2],
|
||||
"service": row[3],
|
||||
"categories": game_categories.get(row[0], [])
|
||||
"runner": row[4],
|
||||
"categories": game_categories.get(row[0], []),
|
||||
}
|
||||
for row in games_rows
|
||||
]
|
||||
return games, total_library
|
||||
|
||||
|
||||
def generate_report(db_path: str, output_path: str, top_n: int, assets_dir: str, template_file: str, bg_image_path: str = None) -> None:
|
||||
def generate_report(
|
||||
db_path: str,
|
||||
output_path: str,
|
||||
top_n: int,
|
||||
assets_dir: str,
|
||||
template_file: str,
|
||||
bg_image_path: str | None = None,
|
||||
style: str | None = None,
|
||||
) -> None:
|
||||
"""Generate the HTML report."""
|
||||
all_games, total_library = get_all_games(db_path)
|
||||
|
||||
@@ -98,52 +129,108 @@ def generate_report(db_path: str, output_path: str, top_n: int, assets_dir: str,
|
||||
# Load background image (custom or default stripes)
|
||||
if bg_image_path and Path(bg_image_path).exists():
|
||||
background_image = load_asset_as_base64(Path(bg_image_path), "image/png")
|
||||
background_image_custom = f"url('{background_image}')"
|
||||
else:
|
||||
background_image = load_asset_as_base64(assets_path / "Others" / "stripes.png", "image/png")
|
||||
background_image = load_asset_as_base64(
|
||||
assets_path / "Others" / "stripes.png", "image/png"
|
||||
)
|
||||
background_image_custom = (
|
||||
"none" # For templates that prefer no default background
|
||||
)
|
||||
|
||||
# Load fonts
|
||||
font_charcoal = load_asset_as_base64(assets_path / "Charcoal.ttf", "font/truetype")
|
||||
font_monaco = load_asset_as_base64(assets_path / "MONACO.TTF", "font/truetype")
|
||||
# Check if using modern unified template
|
||||
if style and style in MODERN_STYLES:
|
||||
html = load_template("templates/modern.html")
|
||||
|
||||
# Load images
|
||||
titlebar_bg = load_asset_as_base64(assets_path / "Windows" / "title-1-active.png", "image/png")
|
||||
title_stripes = load_asset_as_base64(assets_path / "Windows" / "title-1-active.png", "image/png")
|
||||
close_btn = load_asset_as_base64(assets_path / "Windows" / "close-active.png", "image/png")
|
||||
hide_btn = load_asset_as_base64(assets_path / "Windows" / "maximize-active.png", "image/png")
|
||||
shade_btn = load_asset_as_base64(assets_path / "Windows" / "shade-active.png", "image/png")
|
||||
check_off = load_asset_as_base64(assets_path / "Check-Radio" / "check-normal.png", "image/png")
|
||||
check_on = load_asset_as_base64(assets_path / "Check-Radio" / "check-active.png", "image/png")
|
||||
# Inject theme CSS and config
|
||||
theme_css = get_theme_css(style)
|
||||
theme_config = get_theme_config(style)
|
||||
|
||||
# Load scrollbar images
|
||||
scrollbar_trough_v = load_asset_as_base64(assets_path / "Scrollbars" / "trough-scrollbar-vert.png", "image/png")
|
||||
scrollbar_thumb_v = load_asset_as_base64(assets_path / "Scrollbars" / "slider-vertical.png", "image/png")
|
||||
scrollbar_up = load_asset_as_base64(assets_path / "Scrollbars" / "stepper-up.png", "image/png")
|
||||
scrollbar_down = load_asset_as_base64(assets_path / "Scrollbars" / "stepper-down.png", "image/png")
|
||||
# Inject javascript
|
||||
javascript = load_script("templates/script.js")
|
||||
javascript = javascript.replace("__ALL_GAMES__", json.dumps(all_games))
|
||||
javascript = javascript.replace("__TOP_N__", str(top_n))
|
||||
javascript = javascript.replace("__THEME_CONFIG__", theme_config)
|
||||
|
||||
# Load tab images
|
||||
tab_active = load_asset_as_base64(assets_path / "Tabs" / "tab-top-active.png", "image/png")
|
||||
tab_inactive = load_asset_as_base64(assets_path / "Tabs" / "tab-top.png", "image/png")
|
||||
html = html.replace("__THEME_CSS__", theme_css)
|
||||
html = html.replace("__SCRIPT__", javascript)
|
||||
html = html.replace("__TOTAL_LIBRARY__", str(total_library))
|
||||
html = html.replace("__BACKGROUND_IMAGE__", background_image)
|
||||
html = html.replace("__BACKGROUND_IMAGE_CUSTOM__", background_image_custom)
|
||||
else:
|
||||
# Legacy template handling (platinum and others)
|
||||
# Load fonts
|
||||
font_charcoal = load_asset_as_base64(
|
||||
assets_path / "Charcoal.ttf", "font/truetype"
|
||||
)
|
||||
font_monaco = load_asset_as_base64(assets_path / "MONACO.TTF", "font/truetype")
|
||||
|
||||
html = load_template(template_file)
|
||||
html = html.replace("__ALL_GAMES__", json.dumps(all_games))
|
||||
html = html.replace("__TOP_N__", str(top_n))
|
||||
html = html.replace("__TOTAL_LIBRARY__", str(total_library))
|
||||
html = html.replace("__FONT_CHARCOAL__", font_charcoal)
|
||||
html = html.replace("__FONT_MONACO__", font_monaco)
|
||||
html = html.replace("__BACKGROUND_IMAGE__", background_image)
|
||||
html = html.replace("__TITLEBAR_BG__", titlebar_bg)
|
||||
html = html.replace("__TITLE_STRIPES__", title_stripes)
|
||||
html = html.replace("__CLOSE_BTN__", close_btn)
|
||||
html = html.replace("__HIDE_BTN__", hide_btn)
|
||||
html = html.replace("__SHADE_BTN__", shade_btn)
|
||||
html = html.replace("__CHECK_OFF__", check_off)
|
||||
html = html.replace("__CHECK_ON__", check_on)
|
||||
html = html.replace("__SCROLLBAR_TROUGH_V__", scrollbar_trough_v)
|
||||
html = html.replace("__SCROLLBAR_THUMB_V__", scrollbar_thumb_v)
|
||||
html = html.replace("__SCROLLBAR_UP__", scrollbar_up)
|
||||
html = html.replace("__SCROLLBAR_DOWN__", scrollbar_down)
|
||||
html = html.replace("__TAB_ACTIVE__", tab_active)
|
||||
html = html.replace("__TAB_INACTIVE__", tab_inactive)
|
||||
# Load images
|
||||
titlebar_bg = load_asset_as_base64(
|
||||
assets_path / "Windows" / "title-1-active.png", "image/png"
|
||||
)
|
||||
title_stripes = load_asset_as_base64(
|
||||
assets_path / "Windows" / "title-1-active.png", "image/png"
|
||||
)
|
||||
close_btn = load_asset_as_base64(
|
||||
assets_path / "Windows" / "close-active.png", "image/png"
|
||||
)
|
||||
hide_btn = load_asset_as_base64(
|
||||
assets_path / "Windows" / "maximize-active.png", "image/png"
|
||||
)
|
||||
shade_btn = load_asset_as_base64(
|
||||
assets_path / "Windows" / "shade-active.png", "image/png"
|
||||
)
|
||||
check_off = load_asset_as_base64(
|
||||
assets_path / "Check-Radio" / "check-normal.png", "image/png"
|
||||
)
|
||||
check_on = load_asset_as_base64(
|
||||
assets_path / "Check-Radio" / "check-active.png", "image/png"
|
||||
)
|
||||
|
||||
# Load scrollbar images
|
||||
scrollbar_trough_v = load_asset_as_base64(
|
||||
assets_path / "Scrollbars" / "trough-scrollbar-vert.png", "image/png"
|
||||
)
|
||||
scrollbar_thumb_v = load_asset_as_base64(
|
||||
assets_path / "Scrollbars" / "slider-vertical.png", "image/png"
|
||||
)
|
||||
scrollbar_up = load_asset_as_base64(
|
||||
assets_path / "Scrollbars" / "stepper-up.png", "image/png"
|
||||
)
|
||||
scrollbar_down = load_asset_as_base64(
|
||||
assets_path / "Scrollbars" / "stepper-down.png", "image/png"
|
||||
)
|
||||
|
||||
# Load tab images
|
||||
tab_active = load_asset_as_base64(
|
||||
assets_path / "Tabs" / "tab-top-active.png", "image/png"
|
||||
)
|
||||
tab_inactive = load_asset_as_base64(
|
||||
assets_path / "Tabs" / "tab-top.png", "image/png"
|
||||
)
|
||||
|
||||
html = load_template(template_file)
|
||||
html = html.replace("__ALL_GAMES__", json.dumps(all_games))
|
||||
html = html.replace("__TOP_N__", str(top_n))
|
||||
html = html.replace("__TOTAL_LIBRARY__", str(total_library))
|
||||
html = html.replace("__FONT_CHARCOAL__", font_charcoal)
|
||||
html = html.replace("__FONT_MONACO__", font_monaco)
|
||||
html = html.replace("__BACKGROUND_IMAGE__", background_image)
|
||||
html = html.replace("__BACKGROUND_IMAGE_CUSTOM__", background_image_custom)
|
||||
html = html.replace("__TITLEBAR_BG__", titlebar_bg)
|
||||
html = html.replace("__TITLE_STRIPES__", title_stripes)
|
||||
html = html.replace("__CLOSE_BTN__", close_btn)
|
||||
html = html.replace("__HIDE_BTN__", hide_btn)
|
||||
html = html.replace("__SHADE_BTN__", shade_btn)
|
||||
html = html.replace("__CHECK_OFF__", check_off)
|
||||
html = html.replace("__CHECK_ON__", check_on)
|
||||
html = html.replace("__SCROLLBAR_TROUGH_V__", scrollbar_trough_v)
|
||||
html = html.replace("__SCROLLBAR_THUMB_V__", scrollbar_thumb_v)
|
||||
html = html.replace("__SCROLLBAR_UP__", scrollbar_up)
|
||||
html = html.replace("__SCROLLBAR_DOWN__", scrollbar_down)
|
||||
html = html.replace("__TAB_ACTIVE__", tab_active)
|
||||
html = html.replace("__TAB_INACTIVE__", tab_inactive)
|
||||
|
||||
Path(output_path).write_text(html, encoding="utf-8")
|
||||
print(f"Report generated: {output_path}")
|
||||
@@ -159,33 +246,50 @@ def main():
|
||||
parser.add_argument(
|
||||
"--db",
|
||||
default="pga.db",
|
||||
help="Path to the Lutris SQLite database (default: pga.db)"
|
||||
help="Path to the Lutris SQLite database (default: pga.db)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default="report.html",
|
||||
help="Output HTML file path (default: report.html)"
|
||||
help="Output HTML file path (default: report.html)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of top games to show individually (default: 10)"
|
||||
help="Number of top games to show individually (default: 10)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--assets",
|
||||
default="Platinum",
|
||||
help="Path to Platinum assets directory (default: Platinum)"
|
||||
default="templates/Platinum",
|
||||
help="Path to Platinum assets directory (default: templates/Platinum)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--background",
|
||||
default=None,
|
||||
help="Path to background image for tiling (default: Platinum stripes pattern)"
|
||||
help="Path to background image for tiling (default: Platinum stripes pattern)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template",
|
||||
default="platinum.html",
|
||||
help="HTML template file to use (default: platinum.html)"
|
||||
default="templates/platinum.html",
|
||||
help="HTML template file to use (default: templates/platinum.html). Ignored if --style is used.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--style",
|
||||
choices=[
|
||||
"brutalism",
|
||||
"glassmorphism",
|
||||
"skeuo",
|
||||
"neumorphism",
|
||||
"material",
|
||||
"flat",
|
||||
"synthwave",
|
||||
"vaporwave",
|
||||
"terminal",
|
||||
"highcontrast",
|
||||
],
|
||||
default=None,
|
||||
help="Modern style to use (brutalism, glassmorphism, skeuo, neumorphism, material, flat, synthwave, vaporwave, terminal, highcontrast). Overrides --template.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -198,12 +302,39 @@ def main():
|
||||
print(f"Error: Assets directory not found: {args.assets}")
|
||||
return 1
|
||||
|
||||
template_path = SCRIPT_DIR / args.template
|
||||
# Determine style from --style or --template
|
||||
style = args.style
|
||||
|
||||
# If no --style provided, check if --template points to a modern CSS file
|
||||
if not style and args.template.endswith(".css"):
|
||||
template_path = Path(args.template)
|
||||
style_name = template_path.stem # e.g., "brutalism" from "brutalism.css"
|
||||
if style_name in MODERN_STYLES:
|
||||
style = style_name
|
||||
|
||||
# Validate template/style
|
||||
if style and style in MODERN_STYLES:
|
||||
template_path = SCRIPT_DIR / "templates" / "modern.html"
|
||||
css_path = SCRIPT_DIR / "templates" / f"{style}.css"
|
||||
if not css_path.exists():
|
||||
print(f"Error: CSS file not found: {css_path}")
|
||||
return 1
|
||||
else:
|
||||
template_path = SCRIPT_DIR / args.template
|
||||
|
||||
if not template_path.exists():
|
||||
print(f"Error: Template file not found: {template_path}")
|
||||
return 1
|
||||
|
||||
generate_report(args.db, args.output, args.top, args.assets, args.template, args.background)
|
||||
generate_report(
|
||||
args.db,
|
||||
args.output,
|
||||
args.top,
|
||||
args.assets,
|
||||
args.template,
|
||||
args.background,
|
||||
style,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
1079
glassmorphism.html
341
styles.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""CSS styles and theme configurations for modern templates."""
|
||||
|
||||
####################################################################################################
|
||||
# Copyright (C) 2026 by WallyHackenslacker wallyhackenslacker@noreply.git.hackenslacker.space #
|
||||
# #
|
||||
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without #
|
||||
# fee is hereby granted. #
|
||||
# #
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS #
|
||||
# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE #
|
||||
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES #
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, #
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE #
|
||||
# OF THIS SOFTWARE. #
|
||||
####################################################################################################
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Directory where templates are located
|
||||
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
||||
|
||||
# Theme configurations for Chart.js
|
||||
THEME_CONFIGS = {
|
||||
"brutalism": {
|
||||
"colors": [
|
||||
"#ff0000",
|
||||
"#0000ff",
|
||||
"#ffff00",
|
||||
"#00ff00",
|
||||
"#ff00ff",
|
||||
"#00ffff",
|
||||
"#ff8800",
|
||||
"#8800ff",
|
||||
"#0088ff",
|
||||
"#88ff00",
|
||||
"#888888",
|
||||
],
|
||||
"fontFamily": "'Courier New', monospace",
|
||||
"fontWeight": "bold",
|
||||
"pointStyle": "rect",
|
||||
"textColorLight": "#000000",
|
||||
"textColorDark": "#ffffff",
|
||||
"borderColorLight": "#000000",
|
||||
"borderColorDark": "#ffffff",
|
||||
"borderWidth": 3,
|
||||
"tooltipBg": "#000000",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "#ffffff",
|
||||
"tooltipBorderWidth": 2,
|
||||
"tooltipCornerRadius": 0,
|
||||
"uppercaseTooltip": True,
|
||||
},
|
||||
"glassmorphism": {
|
||||
"colors": [
|
||||
"#6366f1",
|
||||
"#8b5cf6",
|
||||
"#ec4899",
|
||||
"#f43f5e",
|
||||
"#f97316",
|
||||
"#eab308",
|
||||
"#22c55e",
|
||||
"#14b8a6",
|
||||
"#06b6d4",
|
||||
"#3b82f6",
|
||||
"#64748b",
|
||||
],
|
||||
"fontFamily": "'Inter', sans-serif",
|
||||
"fontWeight": "normal",
|
||||
"pointStyle": "circle",
|
||||
"textColorLight": "#1a1a2e",
|
||||
"textColorDark": "#f0f0f5",
|
||||
"borderColorLight": "rgba(255, 255, 255, 0.2)",
|
||||
"borderColorDark": "rgba(255, 255, 255, 0.2)",
|
||||
"borderWidth": 2,
|
||||
"tooltipBg": "rgba(0, 0, 0, 0.8)",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "transparent",
|
||||
"tooltipBorderWidth": 0,
|
||||
"tooltipCornerRadius": 8,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"skeuo": {
|
||||
"colors": [
|
||||
"#8b4a2c",
|
||||
"#a65d37",
|
||||
"#bf7748",
|
||||
"#d58a60",
|
||||
"#e0a97f",
|
||||
"#6d4f34",
|
||||
"#857153",
|
||||
"#9e8362",
|
||||
"#b58f6c",
|
||||
"#c9a67f",
|
||||
"#7f7f7f",
|
||||
],
|
||||
"fontFamily": "'Trebuchet MS', 'Lucida Grande', sans-serif",
|
||||
"fontWeight": "600",
|
||||
"pointStyle": "rectRounded",
|
||||
"textColorLight": "#2c2218",
|
||||
"textColorDark": "#f4e7d5",
|
||||
"borderColorLight": "rgba(75, 55, 36, 0.35)",
|
||||
"borderColorDark": "rgba(219, 188, 156, 0.3)",
|
||||
"borderWidth": 2,
|
||||
"tooltipBg": "rgba(46, 33, 23, 0.92)",
|
||||
"tooltipTitleColor": "#f4e7d5",
|
||||
"tooltipBodyColor": "#f4e7d5",
|
||||
"tooltipBorderColor": "#d5a67f",
|
||||
"tooltipBorderWidth": 1,
|
||||
"tooltipCornerRadius": 10,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"neumorphism": {
|
||||
"colors": [
|
||||
"#6366f1",
|
||||
"#8b5cf6",
|
||||
"#ec4899",
|
||||
"#f43f5e",
|
||||
"#f97316",
|
||||
"#eab308",
|
||||
"#22c55e",
|
||||
"#14b8a6",
|
||||
"#06b6d4",
|
||||
"#3b82f6",
|
||||
"#64748b",
|
||||
],
|
||||
"fontFamily": "'Inter', sans-serif",
|
||||
"fontWeight": "normal",
|
||||
"pointStyle": "circle",
|
||||
"textColorLight": "#2d3436",
|
||||
"textColorDark": "#f0f0f5",
|
||||
"borderColorLight": "rgba(255, 255, 255, 0.3)",
|
||||
"borderColorDark": "rgba(255, 255, 255, 0.3)",
|
||||
"borderWidth": 3,
|
||||
"tooltipBg": "rgba(0, 0, 0, 0.8)",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "transparent",
|
||||
"tooltipBorderWidth": 0,
|
||||
"tooltipCornerRadius": 8,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"material": {
|
||||
"colors": [
|
||||
"#6200ee",
|
||||
"#03dac6",
|
||||
"#3700b3",
|
||||
"#018786",
|
||||
"#b00020",
|
||||
"#ff0266",
|
||||
"#aa00ff",
|
||||
"#0091ea",
|
||||
"#00c853",
|
||||
"#ffd600",
|
||||
"#757575",
|
||||
],
|
||||
"fontFamily": "'Roboto', sans-serif",
|
||||
"fontWeight": "normal",
|
||||
"pointStyle": "circle",
|
||||
"textColorLight": "#212121",
|
||||
"textColorDark": "#ffffff",
|
||||
"borderColorLight": "#ffffff",
|
||||
"borderColorDark": "#1e1e1e",
|
||||
"borderWidth": 2,
|
||||
"tooltipBg": "rgba(97, 97, 97, 0.9)",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "transparent",
|
||||
"tooltipBorderWidth": 0,
|
||||
"tooltipCornerRadius": 4,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"flat": {
|
||||
"colors": [
|
||||
"#ffadad",
|
||||
"#ffd6a5",
|
||||
"#fdffb6",
|
||||
"#caffbf",
|
||||
"#9bf6ff",
|
||||
"#a0c4ff",
|
||||
"#bdb2ff",
|
||||
"#ffc6ff",
|
||||
"#ff9aa2",
|
||||
"#e2f0cb",
|
||||
"#b5ead7",
|
||||
],
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||
"fontWeight": "500",
|
||||
"pointStyle": "circle",
|
||||
"textColorLight": "#1d1d1f",
|
||||
"textColorDark": "#f5f5f7",
|
||||
"borderColorLight": "#ffffff",
|
||||
"borderColorDark": "#1c1c1e",
|
||||
"borderWidth": 2,
|
||||
"tooltipBg": "rgba(0, 0, 0, 0.8)",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "transparent",
|
||||
"tooltipBorderWidth": 0,
|
||||
"tooltipCornerRadius": 12,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"synthwave": {
|
||||
"colors": [
|
||||
"#ff00ff",
|
||||
"#00ffff",
|
||||
"#ffff00",
|
||||
"#ff0055",
|
||||
"#00ff99",
|
||||
"#7a5af8",
|
||||
"#ff8800",
|
||||
"#ff00aa",
|
||||
"#00ccff",
|
||||
"#ccff00",
|
||||
"#999999",
|
||||
],
|
||||
"fontFamily": "'Orbitron', 'Segoe UI', sans-serif",
|
||||
"fontWeight": "bold",
|
||||
"pointStyle": "triangle",
|
||||
"textColorLight": "#2d004d",
|
||||
"textColorDark": "#ff00ff",
|
||||
"borderColorLight": "rgba(255, 0, 255, 0.2)",
|
||||
"borderColorDark": "rgba(0, 255, 255, 0.2)",
|
||||
"borderWidth": 2,
|
||||
"tooltipBg": "rgba(45, 0, 77, 0.9)",
|
||||
"tooltipTitleColor": "#00ffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "#ff00ff",
|
||||
"tooltipBorderWidth": 1,
|
||||
"tooltipCornerRadius": 0,
|
||||
"uppercaseTooltip": True,
|
||||
},
|
||||
"vaporwave": {
|
||||
"colors": [
|
||||
"#ff71ce",
|
||||
"#01cdfe",
|
||||
"#05ffa1",
|
||||
"#b967ff",
|
||||
"#fffb96",
|
||||
"#ff99cc",
|
||||
"#99ccff",
|
||||
"#ccff99",
|
||||
"#ffcc99",
|
||||
"#ffffcc",
|
||||
"#e0e0e0",
|
||||
],
|
||||
"fontFamily": "'MS PGothic', 'Palatino Linotype', serif",
|
||||
"fontWeight": "normal",
|
||||
"pointStyle": "circle",
|
||||
"textColorLight": "#ff71ce",
|
||||
"textColorDark": "#01cdfe",
|
||||
"borderColorLight": "rgba(255, 255, 255, 0.5)",
|
||||
"borderColorDark": "rgba(255, 255, 255, 0.2)",
|
||||
"borderWidth": 1,
|
||||
"tooltipBg": "rgba(255, 113, 206, 0.8)",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "#01cdfe",
|
||||
"tooltipBorderWidth": 2,
|
||||
"tooltipCornerRadius": 4,
|
||||
"uppercaseTooltip": False,
|
||||
},
|
||||
"terminal": {
|
||||
"colors": [
|
||||
"#00ff00",
|
||||
"#00cc00",
|
||||
"#009900",
|
||||
"#006600",
|
||||
"#003300",
|
||||
"#33ff33",
|
||||
"#66ff66",
|
||||
"#99ff99",
|
||||
"#ccffcc",
|
||||
"#ffffff",
|
||||
"#888888",
|
||||
],
|
||||
"fontFamily": "'Courier New', Courier, monospace",
|
||||
"fontWeight": "bold",
|
||||
"pointStyle": "rectRot",
|
||||
"textColorLight": "#006600",
|
||||
"textColorDark": "#00ff00",
|
||||
"borderColorLight": "#000000",
|
||||
"borderColorDark": "#00ff00",
|
||||
"borderWidth": 1,
|
||||
"tooltipBg": "#000000",
|
||||
"tooltipTitleColor": "#00ff00",
|
||||
"tooltipBodyColor": "#00ff00",
|
||||
"tooltipBorderColor": "#00ff00",
|
||||
"tooltipBorderWidth": 1,
|
||||
"tooltipCornerRadius": 0,
|
||||
"uppercaseTooltip": True,
|
||||
},
|
||||
"highcontrast": {
|
||||
"colors": [
|
||||
"#888888",
|
||||
"#444444",
|
||||
"#cccccc",
|
||||
"#666666",
|
||||
"#aaaaaa",
|
||||
"#333333",
|
||||
"#dddddd",
|
||||
"#555555",
|
||||
"#bbbbbb",
|
||||
"#222222",
|
||||
"#999999",
|
||||
],
|
||||
"fontFamily": "Arial, Helvetica, sans-serif",
|
||||
"fontWeight": "900",
|
||||
"pointStyle": "rect",
|
||||
"textColorLight": "#000000",
|
||||
"textColorDark": "#ffffff",
|
||||
"borderColorLight": "#000000",
|
||||
"borderColorDark": "#ffffff",
|
||||
"borderWidth": 4,
|
||||
"tooltipBg": "#000000",
|
||||
"tooltipTitleColor": "#ffffff",
|
||||
"tooltipBodyColor": "#ffffff",
|
||||
"tooltipBorderColor": "#ffffff",
|
||||
"tooltipBorderWidth": 3,
|
||||
"tooltipCornerRadius": 0,
|
||||
"uppercaseTooltip": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_theme_css(style: str) -> str:
|
||||
"""Get the CSS for a given style by reading from the corresponding .css file."""
|
||||
css_file = TEMPLATES_DIR / f"{style}.css"
|
||||
if css_file.exists():
|
||||
return css_file.read_text(encoding="utf-8")
|
||||
# Fallback to glassmorphism if style not found
|
||||
fallback = TEMPLATES_DIR / "glassmorphism.css"
|
||||
return fallback.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def get_theme_config(style: str) -> str:
|
||||
"""Get the theme config as JSON string for a given style."""
|
||||
config = THEME_CONFIGS.get(style, THEME_CONFIGS["glassmorphism"])
|
||||
return json.dumps(config)
|
||||
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 122 B After Width: | Height: | Size: 122 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 119 B After Width: | Height: | Size: 119 B |
|
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
|
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
|
Before Width: | Height: | Size: 182 B After Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
|
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 123 B After Width: | Height: | Size: 123 B |
|
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 202 B After Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 140 B After Width: | Height: | Size: 140 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 289 B |
|
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 243 B |
|
Before Width: | Height: | Size: 237 B After Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 292 B |
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 202 B After Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 202 B After Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 185 B After Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 204 B After Width: | Height: | Size: 204 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 154 B After Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 164 B After Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
|
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 209 B |
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 219 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 157 B |
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 188 B |
|
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 221 B After Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
|
Before Width: | Height: | Size: 168 B After Width: | Height: | Size: 168 B |
|
Before Width: | Height: | Size: 185 B After Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 195 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 197 B |
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 186 B After Width: | Height: | Size: 186 B |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |