Compare commits

...

29 commits

Author SHA1 Message Date
Yarne Coppens
d2f34555de Started work on freewall 2024-08-14 15:25:13 +02:00
Yarne Coppens
ed066180bc Removed unused class 2024-08-14 15:06:01 +02:00
Yarne Coppens
50710b33b4 Started work on board game grid statistic 2024-08-14 15:05:40 +02:00
Yarne Coppens
938b6e98d2 Added bar chart for games played per year 2024-08-14 13:51:07 +02:00
Yarne Coppens
fb5f5a5bc0 Made navbar be responsive to current page 2024-08-14 11:03:19 +02:00
Yarne Coppens
1239995080 Added navbar 2024-08-14 10:55:43 +02:00
Yarne Coppens
8e10d5fa1d Added loading spinner on plays page 2024-08-14 10:55:31 +02:00
Yarne Coppens
26a1c0422f Split JS in files per page 2024-08-14 10:16:54 +02:00
Yarne Coppens
e0443811b1 Added a 'plays' page to list all plays 2024-08-14 08:49:46 +02:00
Yarne Coppens
41441db75d Created line and bar multi chart for games over time 2024-08-12 09:17:32 +02:00
Yarne Coppens
06e1599461 Filtered out expansions in owned game list. Owned game statistic uses day steps 2024-08-12 09:09:39 +02:00
Yarne Coppens
8036276187 Self hosting chart.js 2024-08-12 08:27:10 +02:00
Yarne Coppens
141f533af6 Implemented chart.js 2024-08-11 23:04:30 +02:00
Yarne Coppens
a3af3aea05 Added bootstrap map 2024-08-11 22:52:46 +02:00
Yarne Coppens
af9196bca0 Made game thumbnails fluid 2024-08-11 17:48:50 +02:00
Yarne Coppens
d88ca6d216 Created more board game table columns 2024-08-11 16:20:53 +02:00
Yarne Coppens
49b97ef0c2 Conformed with the API change of the boardgame ID path -> GET 2024-08-11 16:03:22 +02:00
Yarne Coppens
76497e21c9 Self hosting all css & javascript 2024-08-11 15:17:18 +02:00
Yarne Coppens
51de22aa67 Added wishlist page 2024-08-11 12:07:16 +02:00
Yarne Coppens
d3fd938da3 Added board game card 2024-08-11 10:49:59 +02:00
Yarne Coppens
d9fd74f422 Made datatable rows clickable 2024-08-11 10:29:40 +02:00
Yarne Coppens
ecbc5f9162 Turned table into a bootstrap datatable 2024-08-11 10:06:27 +02:00
Yarne Coppens
08e6bd431d Added route to request specific board game 2024-08-10 22:45:08 +02:00
Yarne Coppens
2fabfb60f0 Created base for Flask application 2024-08-10 22:07:47 +02:00
Yarne Coppens
5a9360d10a Make board games clickable 2024-08-09 15:29:29 +02:00
Yarne Coppens
581adb64a3 Added weight column 2024-08-09 14:42:52 +02:00
Yarne Coppens
bc494a6133 Table now loads boardgame thumbnails 2024-08-08 16:32:05 +02:00
Yarne Coppens
f944e320ef Table now loads owned board games 2024-08-08 16:14:55 +02:00
Yarne Coppens
204f8a4f22 Created base files 2024-08-08 15:43:49 +02:00
23 changed files with 2000 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
__pycache__/
venv/

24
app.py Normal file
View file

@ -0,0 +1,24 @@
from flask import Flask, render_template, request
app = Flask(__name__)
@app.get("/")
def get_owned():
return render_template('owned.jinja')
@app.get("/wishlist")
def get_wishlist():
return render_template('wishlist.jinja')
@app.get("/boardgame")
def get_boardgame():
return render_template('boardgame.jinja')
@app.get('/statistics')
def get_statistics():
return render_template('statistics.jinja')
@app.get('/plays')
def get_plays():
return render_template('plays.jinja')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
static/css/main.css Normal file
View file

@ -0,0 +1,9 @@
.statistic_image_grid_image{
width: 100px;
height: 100px;
object-fit: cover;
}
.test{
background-image: url("https://cf.geekdo-images.com/8IO6sl0jN5IylDDRAOK8GQ__original/img/scsSPfbOuxIedcBNjC4EtGb_MEc=/0x0/filters:format(jpeg)/pic8214042.jpg");
}

View file

@ -0,0 +1,20 @@
document.body.onload=loadGame()
async function loadGame() {
let params = new URLSearchParams(document.location.search);
let boardgame_id = params.get("id");
var loadGameURL = api_url + '/boardgame?id=' + boardgame_id
var requested_game = await makeRequest(loadGameURL)
$('#boardgame_image').attr('src', requested_game.image_url)
$('#boardgame_name').text(requested_game.name)
$('#boardgame_weight').text(requested_game.weight)
$('#boardgame_description').text(requested_game.description)
$('#boardgame_link').attr('href', 'https://boardgamegeek.com/boardgame/' + boardgame_id)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

14
static/javascript/main.js Normal file
View file

@ -0,0 +1,14 @@
const api_url = "http://127.0.0.1:8000"
var all_owned_games
async function makeRequest(url) {
try {
const url_request = new Request(url)
const response = await fetch(url_request);
const result = await response.json();
return result
} catch (error) {
console.error("Error:", error);
}
}

View file

@ -0,0 +1,59 @@
document.body.onload=loadOwnedGames()
document.getElementById('owned_nav').classList.add('active')
async function loadOwnedGames() {
var boardgame_datatable = new DataTable('.boardgame_table', {
ajax: {
url: api_url + '/owned?filter_expansions_out=true',
dataSrc: ''
},
columns: [
{
data: 'thumbnail_url',
render: function (data,type){
return '<img src="' + data + '" class="img-fluid" />'
}
},
{
data: 'name'
},
{
data: 'min_players',
render: function(data,type,row){
if (row.min_players != row.max_players){
return row.min_players + '-' + row.max_players
}else{
return row.min_players
}
}
},
{
data: 'min_playing_time',
render: function(data,type,row){
if (row.min_playing_time != row.max_playing_time){
return row.min_playing_time + '-' + row.max_playing_time
}else{
return row.min_playing_time
}
}
},
{
data: 'weight'
}
],
columnDefs: [
{ targets: 'no-sort', orderable: false }
],
order: [[1, 'asc']]
});
$('.boardgame_table').on('click', 'tbody tr', function() {
var boardgame_id = boardgame_datatable.row(this).data().id;
window.location.href = "/boardgame?id=" + boardgame_id
})
}

View file

@ -0,0 +1,71 @@
document.body.onload=loadPlays()
document.getElementById('plays_nav').classList.add('active')
async function loadPlays() {
function createPlayCard(play){
const card_div = document.createElement('div')
card_div.className = "card"
const card_image = document.createElement('img')
card_image.src = play.boardgame.image_url
const card_body = document.createElement('div')
card_body.className = 'card-body'
const card_title = document.createElement('h4')
card_title.innerHTML = play.boardgame.name
const player_names = document.createElement('div')
for (let player_index in play.players){
const player = play.players[player_index]
const player_div = document.createElement('div')
player_div.innerHTML = player.name
if (player.has_won) {
player_div.style.color = "green"
}
player_names.appendChild(player_div)
}
card_body.appendChild(card_title)
card_body.appendChild(player_names)
card_div.appendChild(card_image)
card_div.appendChild(card_body)
return card_div
}
const all_plays = await makeRequest(api_url + '/plays?filter_expansions_out=true')
const MAX_COLUMNS = 4
const column_width = 12 / MAX_COLUMNS
const rows_needed = Math.ceil(all_plays.length / MAX_COLUMNS)
for (let row_number = 0; row_number < rows_needed; row_number++){
const row_div = document.createElement('div')
row_div.className = 'row'
for (let column_number = 0; column_number < MAX_COLUMNS; column_number++) {
if(((row_number * MAX_COLUMNS) + column_number) == all_plays.length){
break
}
const column_div = document.createElement('div')
column_div.className = "col-sm-" + column_width
const current_play = all_plays[(row_number * MAX_COLUMNS) + column_number]
const play_card = createPlayCard(current_play)
column_div.appendChild(play_card)
row_div.appendChild(column_div)
}
document.body.appendChild(row_div)
}
document.getElementById('plays_loading_spinner').remove()
}

View file

@ -0,0 +1,154 @@
document.body.onload=loadStatistics()
document.getElementById('statistics_nav').classList.add('active')
async function loadStatistics(){
function create_boardgame_image_grid_rows(boardgames){
console.log(boardgames)
const MAX_COLUMNS = 2
const column_width = 12 / MAX_COLUMNS
const rows_needed = Math.ceil(boardgames.length / MAX_COLUMNS)
let rows_to_return = []
for (let row_index = 0; row_index < rows_needed; row_index++){
const row = document.createElement('div')
row.className = 'row'
for (let column_index = 0; column_index < MAX_COLUMNS; column_index++){
if(((row_index * MAX_COLUMNS) + column_index) == boardgames.length){
break
}
const current_boardgame = boardgames[(row_index * MAX_COLUMNS) + column_index]
const column = document.createElement('div')
column.className = 'col'
const boardgame_image = document.createElement('img')
boardgame_image.src = current_boardgame.thumbnail_url
boardgame_image.classList.add('statistic_image_grid_image')
boardgame_image.classList.add('card-img-top')
boardgame_image.classList.add('img-fluid')
column.appendChild(boardgame_image)
row.appendChild(column)
}
rows_to_return.push(row)
}
return rows_to_return
}
const gamesovertimechart = document.getElementById("gamesovertimechart")
let games_over_time_statistic = await makeRequest(api_url + '/statistics/amount_of_games_over_time?day_step=30')
let games_over_time_statistic_no_expanions = await makeRequest(api_url + '/statistics/amount_of_games_over_time?day_step=30&filter_expansions_out=true')
let games_over_time_statistic_only_expanions = await makeRequest(api_url + '/statistics/amount_of_games_over_time?day_step=30&only_expansions=true')
new Chart(gamesovertimechart, {
type: 'bar',
data: {
labels: Object.keys(games_over_time_statistic.result),
datasets: [{
label: games_over_time_statistic.name,
data: Object.values(games_over_time_statistic.result),
borderWidth: 1,
type: 'line'
},
{
label: "Base games",
data: Object.values(games_over_time_statistic_no_expanions.result),
borderWidth: 1
},
{
label: "Expansions",
data: Object.values(games_over_time_statistic_only_expanions.result),
borderWidth: 1
}
]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
$("#overtimechartname").text(games_over_time_statistic.name)
const gamesperyearchart = document.getElementById('gamesperyearchart')
let games_played_per_year_statistic = await makeRequest(api_url + '/statistics/games_played_per_year?filter_expansions_out=true')
let years_played = []
for (date_index in Object.keys(games_played_per_year_statistic.result)){
const current_date = Object.keys(games_played_per_year_statistic.result)[date_index]
const year = current_date.substring(0, current_date.indexOf("-"));
years_played.push(year)
}
new Chart(gamesperyearchart, {
plugins: [ChartDataLabels],
type: 'bar',
data: {
labels: years_played ,
datasets: [{
label: games_played_per_year_statistic.name,
data: Object.values(games_played_per_year_statistic.result),
borderWidth: 1
}
]
},
options: {
scales: {
y: {
beginAtZero: true,
max: Math.ceil((Math.max(...Object.values(games_played_per_year_statistic.result)))/ 250) * 250
}
},
plugins: {
datalabels: {
anchor: 'end',
align: 'end'
}
}
}
});
$("#gamesperyearchartname").text(games_played_per_year_statistic.name)
const mostexpensivegameschart = document.getElementById('mostexpensivegameschart')
let most_expensive_games_statistic = await makeRequest(api_url + '/statistics/most_expensive_games')
console.log(most_expensive_games_statistic.result)
for (boardgame_index in most_expensive_games_statistic.result){
let boardgame = most_expensive_games_statistic.result[boardgame_index]
let boardgame_cell = document.createElement('div')
boardgame_cell.classList.add('test')
boardgame_cell.style["background-image"] = boardgame.thumbnail_url
mostexpensivegameschart.appendChild(boardgame_cell)
}
var boardgame_wall = new Freewall("#mostexpensivegameschart")
boardgame_wall.fitWidth()
// const rows_to_fill_in = create_boardgame_image_grid_rows(most_expensive_games_statistic.result)
// for (row_index in rows_to_fill_in){
// mostexpensivegameschart.insertBefore(rows_to_fill_in[row_index], mostexpensivegameschart.firstChild)
// }
$('#mostexpensivegameschartname').text(most_expensive_games_statistic.name)
}

View file

@ -0,0 +1,69 @@
document.body.onload=loadWishlistedGames()
document.getElementById('wishlist_nav').classList.add('active')
async function loadWishlistedGames() {
var wishlist_priorities = [1,2,3,4]
jQuery.each(wishlist_priorities, function(index, item){
var boardgame_datatable = new DataTable('#wishlist_table'+item, {
ajax: {
url: api_url + '/wishlist?priority='+item,
dataSrc: ''
},
columns: [
{
data: 'wishlist_priority'
},
{
data: 'thumbnail_url',
render: function (data,type){
return '<img src="' + data + '" class="img-fluid" />'
}
},
{
data: 'name'
},
{
data: 'min_players',
render: function(data,type,row){
if (row.min_players != row.max_players){
return row.min_players + '-' + row.max_players
}else{
return row.min_players
}
}
},
{
data: 'min_playing_time',
render: function(data,type,row){
if (row.min_playing_time != row.max_playing_time){
return row.min_playing_time + '-' + row.max_playing_time
}else{
return row.min_playing_time
}
}
},
{
data: 'weight'
}
],
columnDefs: [
{
target: 0,
visible: false
},
{ targets: 'no-sort', orderable: false }
],
order: [[2, 'asc']]
});
$('#wishlist_table'+item).on('click', 'tbody tr', function() {
var boardgame_id = boardgame_datatable.row(this).data().id;
window.location.href = "/boardgame?id=" + boardgame_id
})
});
}

53
templates/base.jinja Normal file
View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
Boardgame Site
</title>
<link href="{{ url_for('static', filename='css/bootstrap/bootstrap.min.css') }}" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="{{ url_for('static', filename='css/datatables/datatables.min.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<script src="{{ url_for('static', filename='javascript/jquery/jquery-3.7.1.min.js') }}" defer></script>
<script src="{{ url_for('static', filename='javascript/freewall/freewall.js') }}" defer></script>
<script src="{{ url_for('static', filename='javascript/datatables/datatables.min.js') }}" defer></script>
<script src="{{ url_for('static', filename='javascript/chart/chart.js') }}" defer></script>
<script src="{{ url_for('static', filename='javascript/chart/chartjs-plugin-datalabels.min.js') }}" defer></script>
</head>
<body>
<ul class="nav nav-tabs">
<li class="nav-item">
<a id="owned_nav" class="nav-link" href="{{ url_for('get_owned') }}">Collectie</a>
</li>
<li class="nav-item">
<a id="wishlist_nav" class="nav-link" href="{{ url_for('get_wishlist') }}">Wishlist</a>
</li>
<li class="nav-item">
<a id="statistics_nav" class="nav-link" href="{{ url_for('get_statistics') }}">Statistieken</a>
</li>
<li class="nav-item">
<a id="plays_nav" class="nav-link" href="{{ url_for('get_plays') }}">Gespeeld</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li>
</ul>
{% block body_block %}
{% endblock body_block %}
<script src="{{ url_for('static', filename='javascript/main.js') }}" defer></script>
{% block extra_js_files %}
{% endblock extra_js_files%}
</body>
</html>

25
templates/boardgame.jinja Normal file
View file

@ -0,0 +1,25 @@
{% extends "base.jinja" %}
{% block body_block %}
<div class="row">
<div class="col-sm-2">
<div class="card">
<img id="boardgame_image">
<div class="card-body">
<h4 class="card-title" id="boardgame_name">John Doe</h4>
<p class="card-text" id="boardgame_description">Some example text.</p>
<a href="#" id="boardgame_link" class="btn btn-primary">Bekijk op BGG</a>
</div>
</div>
</div>
<div class="col-sm-10"><p id="boardgame_weight"></p></div>
</div>
{% endblock body_block %}
{% block extra_js_files %}
<script src="{{ url_for('static', filename='javascript/boardgame.js') }}" defer></script>
{% endblock extra_js_files %}

26
templates/owned.jinja Normal file
View file

@ -0,0 +1,26 @@
{% extends "base.jinja" %}
{% block body_block %}
<div class="table-responsive">
<table class="table table-striped boardgame_table">
<thead>
<tr>
<th scope="col" class="no-sort">Thumbnail</th>
<th scope="col">Naam</th>
<th scope="col">Spelers</th>
<th scope="col">Duratie</th>
<th scope="col">Moeilijkheid</th>
</tr>
</thead>
</table>
</div>
{% endblock body_block %}
{% block extra_js_files %}
<script src="{{ url_for('static', filename='javascript/owned.js') }}" defer></script>
{% endblock extra_js_files %}

11
templates/plays.jinja Normal file
View file

@ -0,0 +1,11 @@
{% extends "base.jinja" %}
{% block body_block %}
<div id="plays_loading_spinner" class="spinner-border"></div>
{% endblock body_block %}
{% block extra_js_files %}
<script src="{{ url_for('static', filename='javascript/plays.js') }}" defer></script>
{% endblock extra_js_files %}

View file

@ -0,0 +1,33 @@
{% extends "base.jinja" %}
{% block body_block %}
<div class="card-group" id="statistic_cards">
<div class="card">
<canvas class="chart_visual" id="gamesovertimechart"></canvas>
<div class="card-body">
<h4 class="card-title" id="overtimechartname"></h4>
</div>
</div>
<div class="card">
<canvas class="chart_visual" id="gamesperyearchart"></canvas>
<div class="card-body">
<h4 class="card-title" id="gamesperyearchartname"></h4>
</div>
</div>
<div class="card">
<div class="chart_visual" id="mostexpensivegameschart"></div>
<div class="card-body">
<h4 class="card-title" id="mostexpensivegameschartname"></h4>
</div>
</div>
</div>
{% endblock body_block %}
{% block extra_js_files %}
<script src="{{ url_for('static', filename='javascript/statistics.js') }}" defer></script>
{% endblock extra_js_files %}

81
templates/wishlist.jinja Normal file
View file

@ -0,0 +1,81 @@
{% extends "base.jinja" %}
{% block body_block %}
<body onload="loadWishlistedGames()">
<div class="table-responsive">
<table id="wishlist_table1" class="table table-striped boardgame_table">
<thead>
<tr>
<th scope="col">wishlist_priority</th>
<th scope="col" class="no-sort">Thumbnail</th>
<th scope="col">Naam</th>
<th scope="col">Spelers</th>
<th scope="col">Duratie</th>
<th scope="col">Moeilijkheid</th>
</tr>
</thead>
</table>
</div>
<div class="table-responsive">
<table id="wishlist_table2" class="table table-striped boardgame_table">
<thead>
<tr>
<th scope="col">wishlist_priority</th>
<th scope="col" class="no-sort">Thumbnail</th>
<th scope="col">Naam</th>
<th scope="col">Spelers</th>
<th scope="col">Duratie</th>
<th scope="col">Moeilijkheid</th>
</tr>
</tr>
</thead>
</table>
</div>
<div class="table-responsive">
<table id="wishlist_table3" class="table table-striped boardgame_table">
<thead>
<tr>
<th scope="col">wishlist_priority</th>
<th scope="col" class="no-sort">Thumbnail</th>
<th scope="col">Naam</th>
<th scope="col">Spelers</th>
<th scope="col">Duratie</th>
<th scope="col">Moeilijkheid</th>
</tr>
</tr>
</thead>
</table>
</div>
<div class="table-responsive">
<table id="wishlist_table4" class="table table-striped boardgame_table">
<thead>
<tr>
<th scope="col">wishlist_priority</th>
<th scope="col" class="no-sort">Thumbnail</th>
<th scope="col">Naam</th>
<th scope="col">Spelers</th>
<th scope="col">Duratie</th>
<th scope="col">Moeilijkheid</th>
</tr>
</tr>
</thead>
</table>
</div>
</body>
{% endblock body_block %}
{% block extra_js_files %}
<script src="{{ url_for('static', filename='javascript/wishlist.js') }}" defer></script>
{% endblock extra_js_files %}