dynamic html table with javascript, more screenshots
This commit is contained in:
@ -7,10 +7,13 @@
|
||||
<meta name="author" content="Trilarion">
|
||||
<meta name="description" content="Infos and technical information about many open source games and frameworks.">
|
||||
<title>{{ base['title'] }}</title>
|
||||
<link rel="stylesheet" href="{{ base['url_to'](['css', 'bulma.min.css']) }}">
|
||||
<link rel="stylesheet" href="{{ base['url_to'](['css', 'osgl.min.css']) }}">
|
||||
{%- for css in base['css'] %}
|
||||
<link rel="stylesheet" href="{{ base['url_to'](['css', css]) }}">
|
||||
{%- endfor %}
|
||||
<link rel="icon" type="image/svg+xml" href="{{ base['url_to'](['favicon.svg']) }}">
|
||||
<script type="text/javascript" src="{{ base['url_to'](['js', 'osgl.js']) }}"></script>
|
||||
{%- for js in base['js'] %}
|
||||
<script type="text/javascript" src="{{ base['url_to'](['js', js]) }}"></script>
|
||||
{%- endfor %}
|
||||
</head>
|
||||
<body>
|
||||
{#- navigation bar -#}
|
||||
|
173
code/html/css/simple-datatables.css
Normal file
173
code/html/css/simple-datatables.css
Normal file
@ -0,0 +1,173 @@
|
||||
.dataTable-wrapper.no-header .dataTable-container {
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.dataTable-wrapper.no-footer .dataTable-container {
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.dataTable-top,
|
||||
.dataTable-bottom {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.dataTable-top > nav:first-child,
|
||||
.dataTable-top > div:first-child,
|
||||
.dataTable-bottom > nav:first-child,
|
||||
.dataTable-bottom > div:first-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.dataTable-top > nav:last-child,
|
||||
.dataTable-top > div:last-child,
|
||||
.dataTable-bottom > nav:last-child,
|
||||
.dataTable-bottom > div:last-child {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.dataTable-selector {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.dataTable-input {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.dataTable-info {
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
/* PAGER */
|
||||
.dataTable-pagination ul {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.dataTable-pagination li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.dataTable-pagination a {
|
||||
border: 1px solid transparent;
|
||||
float: left;
|
||||
margin-left: 2px;
|
||||
padding: 6px 12px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dataTable-pagination a:hover {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.dataTable-pagination .active a,
|
||||
.dataTable-pagination .active a:focus,
|
||||
.dataTable-pagination .active a:hover {
|
||||
background-color: #d9d9d9;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dataTable-pagination .ellipsis a,
|
||||
.dataTable-pagination .disabled a,
|
||||
.dataTable-pagination .disabled a:focus,
|
||||
.dataTable-pagination .disabled a:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.dataTable-pagination .disabled a,
|
||||
.dataTable-pagination .disabled a:focus,
|
||||
.dataTable-pagination .disabled a:hover {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.dataTable-pagination .pager a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* TABLE */
|
||||
.dataTable-table {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.dataTable-table > tbody > tr > td,
|
||||
.dataTable-table > tbody > tr > th,
|
||||
.dataTable-table > tfoot > tr > td,
|
||||
.dataTable-table > tfoot > tr > th,
|
||||
.dataTable-table > thead > tr > td,
|
||||
.dataTable-table > thead > tr > th {
|
||||
vertical-align: top;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.dataTable-table > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.dataTable-table > tfoot > tr > th {
|
||||
vertical-align: bottom;
|
||||
text-align: left;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.dataTable-table th {
|
||||
vertical-align: bottom;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dataTable-table th a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dataTable-sorter {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataTable-sorter::before,
|
||||
.dataTable-sorter::after {
|
||||
content: "";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.dataTable-sorter::before {
|
||||
border-top: 4px solid #000;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.dataTable-sorter::after {
|
||||
border-bottom: 4px solid #000;
|
||||
border-top: 4px solid transparent;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.asc .dataTable-sorter::after,
|
||||
.desc .dataTable-sorter::before {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dataTables-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dataTable-top::after, .dataTable-bottom::after {
|
||||
clear: both;
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
@ -7,6 +7,14 @@ Sitemaps is not needed, only for large projects with lots of JavaScript und many
|
||||
|
||||
"""
|
||||
|
||||
# TODO simple data table with columns: name (homepage as link), keywords (at least essential ones), inspiration, language, code license
|
||||
|
||||
# TODO languages: ? at the end sort
|
||||
|
||||
# TODO too many spans, especially for text (maybe just plain text), also text with URLs inside is difficult (but why)
|
||||
|
||||
# TODO keywords: content, multiplayer replace by icons (open, commercial (dollar signs))
|
||||
|
||||
# TODO most people only come to the main page, put more information there (direct links to genres, ...)
|
||||
# TODO put more explanations on the category pages and the categories (number and short sentences)
|
||||
|
||||
@ -31,6 +39,13 @@ Sitemaps is not needed, only for large projects with lots of JavaScript und many
|
||||
|
||||
# TODO games: platform icons and mature, state larger (but maybe not on mobile)
|
||||
|
||||
# TODO games: platform information larger printed, keyword tags underlined to indicate links
|
||||
# TODO devs: icons mark as links (how? ask on ux.stackexchange.com?)
|
||||
|
||||
# TODO everywhere: better link replacements
|
||||
|
||||
# TODO game: first homepage link bold
|
||||
|
||||
# TODO statistics: better and more statistics with links where possible
|
||||
# TODO statistics: with nice graphics (pie charts in SVG) with matplotlib
|
||||
# TODO statistics: get it from common statistics generator
|
||||
@ -89,6 +104,7 @@ import shutil
|
||||
import math
|
||||
import datetime
|
||||
import time
|
||||
import json
|
||||
from functools import partial
|
||||
from utils import osg, constants as c, utils, osg_statistics as stat, osg_parse
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
@ -172,7 +188,7 @@ non_game_category_names = {
|
||||
'tool': 'Tools',
|
||||
'framework': 'Frameworks',
|
||||
'library': 'Libraries',
|
||||
'game engine': 'Game Engine'
|
||||
'game engine': 'Game Engines'
|
||||
}
|
||||
|
||||
# we check the output html structure every time
|
||||
@ -842,6 +858,37 @@ def add_screenshot_information(entries):
|
||||
entry['screenshots'] = screenshots
|
||||
|
||||
|
||||
def create_table_json_data(entries):
|
||||
"""
|
||||
We assume that everything including internal is setup correctly.
|
||||
Columns are Title, Link (entry, first homepage), State, Essential Keywords, Language, License
|
||||
:param entries:
|
||||
:return:
|
||||
"""
|
||||
# create json structure
|
||||
db = {'headings': ['Title', 'State', 'Tags', 'Platform', 'Language', 'License']}
|
||||
data = []
|
||||
for entry in entries:
|
||||
title = '<a href="{}">{}</a> (<a href="{}">Entry</a>)'.format(entry['Home'][0], entry['Title'], url_to([], entry['href']))
|
||||
state = ', '.join(entry['State'])
|
||||
tags = entry['Keyword']
|
||||
tags = [tag for tag in tags if tag in c.recommended_keywords]
|
||||
tags = ', '.join(tags)
|
||||
platform = entry.get('Platform', ['-'])
|
||||
platform = ', '.join(platform)
|
||||
language = ', '.join(entry['Code language'])
|
||||
license = [x[-1] for x in entry['Code license']]
|
||||
license = ', '.join(license)
|
||||
data.append([title, state, tags, platform, language, license])
|
||||
data.sort(key=lambda x: str.casefold(x[0]))
|
||||
db['data'] = data
|
||||
|
||||
# write out
|
||||
text = json.dumps(db, indent=1)
|
||||
os.makedirs(c.web_data_path, exist_ok=True)
|
||||
utils.write_text(os.path.join(c.web_data_path, 'entries.json'), text)
|
||||
|
||||
|
||||
def generate(entries, inspirations, developers):
|
||||
"""
|
||||
Regenerates the whole static website given an already imported set of entries, inspirations and developers.
|
||||
@ -874,6 +921,9 @@ def generate(entries, inspirations, developers):
|
||||
# set external links up
|
||||
add_license_links_to_entries(games)
|
||||
|
||||
# create entries.json for the table
|
||||
create_table_json_data(entries)
|
||||
|
||||
# sort into categories
|
||||
sorter = lambda item, category: category == item['letter']
|
||||
games_by_alphabet = sort_into_categories(games, extended_alphabet, sorter)
|
||||
@ -893,7 +943,9 @@ def generate(entries, inspirations, developers):
|
||||
# base dictionary
|
||||
base = {
|
||||
'title': 'OSGL',
|
||||
'creation-date': datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M')
|
||||
'creation-date': datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M'),
|
||||
'css': ['bulma.min.css', 'osgl.min.css'],
|
||||
'js': ['osgl.js']
|
||||
}
|
||||
|
||||
# copy css and js
|
||||
@ -925,16 +977,18 @@ def generate(entries, inspirations, developers):
|
||||
|
||||
# index.html
|
||||
base['active_nav'] = 'index'
|
||||
index = {'subtitle': make_text('Contains information about {} open source games and {} frameworks/tools.'.format(len(games), len(non_games))) }
|
||||
index = {'subtitle': make_text('Contains information about {} open source games and {} game engines/tools.'.format(len(games), len(non_games))) }
|
||||
template = environment.get_template('index.jinja')
|
||||
write(template.render(index=index), ['index.html'])
|
||||
|
||||
# contribute page
|
||||
base['title'] = 'OSGL | Contributions'
|
||||
base['active_nav'] = 'contribute'
|
||||
template = environment.get_template('contribute.jinja')
|
||||
write(template.render(), ['contribute.html'])
|
||||
|
||||
# statistics page in statistics folder
|
||||
base['title'] = 'OSGL | Statistics'
|
||||
base['url_to'] = partial(url_to, statistics_path)
|
||||
base['active_nav'] = 'statistics'
|
||||
|
||||
@ -960,6 +1014,7 @@ def generate(entries, inspirations, developers):
|
||||
write(template.render(data=data), statistics_index_path)
|
||||
|
||||
# non-games folder
|
||||
base['title'] = 'OSGL | Game engines, frameworks, tools'
|
||||
base['url_to'] = partial(url_to, non_games_path)
|
||||
base['active_nav'] = 'frameworks'
|
||||
|
||||
@ -985,6 +1040,7 @@ def generate(entries, inspirations, developers):
|
||||
write(template_listing_entries.render(listing=listing), non_games_path + ['{}.html'.format(keyword)])
|
||||
|
||||
# games folder
|
||||
base['title'] = 'OSGL | Games | Alphabetical'
|
||||
base['url_to'] = partial(url_to, games_path)
|
||||
base['active_nav'] = 'games'
|
||||
|
||||
@ -999,7 +1055,7 @@ def generate(entries, inspirations, developers):
|
||||
# generate games index
|
||||
index = divide_in_three_columns_and_transform(games_by_alphabet, game_index)
|
||||
index['title'] = make_text('Open source games')
|
||||
index['subtitle'] = make_text('Alphabetical index of informations about {} games'.format(len(games)))
|
||||
index['subtitle'] = [make_text('Alphabetical index of informations about {} games (or see the '.format(len(games))), make_url(['table.html'], 'table'), ')']
|
||||
index['categories'] = extended_alphabet
|
||||
index['category-names'] = extended_alphabet_names
|
||||
index['category-icons'] = {}
|
||||
@ -1009,10 +1065,11 @@ def generate(entries, inspirations, developers):
|
||||
write(template_categorical_index.render(index=index), games_index_path)
|
||||
|
||||
# genres
|
||||
base['title'] = 'OSGL | Games | Genres'
|
||||
base['active_nav'] = ['filter', 'genres']
|
||||
index = divide_in_three_columns_and_transform(games_by_genre, game_index)
|
||||
index['title'] = make_text('Open source games')
|
||||
index['subtitle'] = make_text('Index by game genre')
|
||||
index['subtitle'] = [make_text('Index by game genre (or see the '), make_url(['table.html'], 'table'), ')']
|
||||
index['categories'] = genres
|
||||
index['category-names'] = {k: make_text(k) for k in index['categories']}
|
||||
index['category-icons'] = {k: make_icon(genre_icon_map[k]) for k in index['categories'] if k in genre_icon_map}
|
||||
@ -1022,10 +1079,11 @@ def generate(entries, inspirations, developers):
|
||||
write(template_categorical_index.render(index=index), games_by_genres_path)
|
||||
|
||||
# games by language
|
||||
base['title'] = 'OSGL | Games | Programming language'
|
||||
base['active_nav'] = ['filter', 'code language']
|
||||
index = divide_in_three_columns_and_transform(games_by_language, game_index)
|
||||
index['title'] = 'Open source games and frameworks'
|
||||
index['subtitle'] = make_text('Index by programming language')
|
||||
index['subtitle'] = [make_text('Index by programming language (or see the '), make_url(['table.html'], 'table'), ')']
|
||||
index['categories'] = c.known_languages
|
||||
index['category-names'] = {k:k for k in index['categories']}
|
||||
index['category-icons'] = {}
|
||||
@ -1035,10 +1093,11 @@ def generate(entries, inspirations, developers):
|
||||
write(template_categorical_index.render(index=index), games_by_language_path)
|
||||
|
||||
# games by platform
|
||||
base['title'] = 'OSGL | Games | Supported Platform'
|
||||
base['active_nav'] = ['filter', 'platforms']
|
||||
index = divide_in_three_columns_and_transform(games_by_platform, game_index)
|
||||
index['title'] = 'Open source games and frameworks'
|
||||
index['subtitle'] = make_text('Index by supported platform')
|
||||
index['subtitle'] = [make_text('Index by supported platform (or see the '), make_url(['table.html'], 'table'), ')']
|
||||
index['categories'] = c.valid_platforms + ('Unspecified',)
|
||||
index['category-names'] = {k: make_text(k) for k in index['categories']}
|
||||
index['category-icons'] = {k: make_icon(platform_icon_map[k]) for k in index['categories']}
|
||||
@ -1049,6 +1108,7 @@ def generate(entries, inspirations, developers):
|
||||
write(template_categorical_index.render(index=index), games_by_platform_path)
|
||||
|
||||
# top 50 games
|
||||
base['title'] = 'OSGL | Games | GitHub Top 50'
|
||||
base['active_nav'] = ['filter', 'top50']
|
||||
# there are no other games coming afterwards, can actually number them
|
||||
for index, game in enumerate(top50_games):
|
||||
@ -1061,6 +1121,7 @@ def generate(entries, inspirations, developers):
|
||||
write(template_listing_entries.render(listing=listing), games_top50_path)
|
||||
|
||||
# inspirations folder
|
||||
base['title'] = 'OSGL | Inspirational games'
|
||||
base['url_to'] = partial(url_to, inspirations_path)
|
||||
base['active_nav'] = 'inspirations'
|
||||
|
||||
@ -1088,6 +1149,7 @@ def generate(entries, inspirations, developers):
|
||||
write(template_listing_inspirations.render(listing=listing), inspirations_path + ['{}.html'.format(letter.capitalize())])
|
||||
|
||||
# developers folder
|
||||
base['title'] = 'OSGL | Games | Developers'
|
||||
base['url_to'] = partial(url_to, developers_path)
|
||||
base['active_nav'] = 'developers'
|
||||
|
||||
@ -1112,6 +1174,15 @@ def generate(entries, inspirations, developers):
|
||||
index['category-infos'] = {}
|
||||
write(template_categorical_index.render(index=index), developers_index_path)
|
||||
|
||||
# dynamic table (is in top level folder)
|
||||
base['title'] = 'OSGL | Entries | Table'
|
||||
base['url_to'] = partial(url_to, [])
|
||||
base['css'].append('simple-datatables.css')
|
||||
base['js'].append('simple-datatables.js')
|
||||
base['active_nav'] = []
|
||||
template = environment.get_template('table.jinja')
|
||||
write(template.render(), ['table.html'])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
8
code/html/js/simple-datatables.js
Normal file
8
code/html/js/simple-datatables.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,3 +1,29 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="box">
|
||||
<div class="block">
|
||||
<p class="title is-4">Table</p>
|
||||
<p class="subtitle is-6">Sortable and searchable.</p>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table is-narrow is-hoverable"></table>
|
||||
</div>
|
||||
<script>
|
||||
fetch("data/entries.json").then(response => response.json()).then(data => {
|
||||
let table = new simpleDatatables.DataTable(".table", {
|
||||
perPage: 50,
|
||||
perPageSelect: [30, 50, 100],
|
||||
footer: true,
|
||||
data: {
|
||||
headings: data["headings"],
|
||||
data: data["data"]
|
||||
},
|
||||
});
|
||||
|
||||
table.on('datatable.init', function(args) {
|
||||
table.columns().sort(0);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -42,6 +42,7 @@ def check_validity_backlog():
|
||||
if r.is_redirect or r.history:
|
||||
print('{} redirected to {}, {}'.format(url, r.url, r.history))
|
||||
|
||||
|
||||
def create_toc(title, file, entries):
|
||||
"""
|
||||
|
||||
@ -697,15 +698,6 @@ class EntriesMaintainer:
|
||||
|
||||
print('statistics updated')
|
||||
|
||||
def update_html(self):
|
||||
"""
|
||||
"""
|
||||
if not self.entries:
|
||||
print('entries not yet loaded')
|
||||
return
|
||||
|
||||
print('HTML not updated')
|
||||
|
||||
def update_repos(self):
|
||||
"""
|
||||
export to json for local repository update of primary repos
|
||||
@ -905,7 +897,6 @@ if __name__ == "__main__":
|
||||
'Clean backlog': m.clean_backlog,
|
||||
'Update Readme and TOCs': m.update_readme_tocs,
|
||||
'Update statistics': m.update_statistics,
|
||||
'Update HTML': m.update_html,
|
||||
'Update repository list': m.update_repos,
|
||||
'Special': m.special_ops,
|
||||
'Complete run': m.complete_run
|
||||
|
@ -7,15 +7,17 @@ import configparser
|
||||
|
||||
# paths
|
||||
root_path = os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
|
||||
code_path = os.path.join(root_path, 'code')
|
||||
web_template_path = os.path.join(code_path, 'html')
|
||||
entries_path = os.path.join(root_path, 'entries')
|
||||
tocs_path = os.path.join(entries_path, 'tocs')
|
||||
code_path = os.path.join(root_path, 'code')
|
||||
web_path = os.path.join(root_path, 'docs')
|
||||
screenshots_path = os.path.join(entries_path, 'screenshots')
|
||||
web_template_path = os.path.join(code_path, 'html')
|
||||
|
||||
web_path = os.path.join(root_path, 'docs')
|
||||
web_css_path = os.path.join(web_path, 'css')
|
||||
web_js_path = os.path.join(web_path, 'js')
|
||||
web_screenshots_path = os.path.join(web_path, 'screenshots')
|
||||
web_data_path = os.path.join(web_path, 'data')
|
||||
|
||||
private_properties_file = os.path.join(root_path, 'private.properties')
|
||||
inspirations_file = os.path.join(root_path, 'inspirations.md')
|
||||
|
@ -657,9 +657,13 @@ def write_screenshots_overview(overview):
|
||||
text = utils.read_text(c.screenshots_file)
|
||||
text = text.split('\n# ')[0] + '\n'
|
||||
|
||||
for name, a in overview.items():
|
||||
# write out each entry sorted by name
|
||||
for name in sorted(overview.keys()):
|
||||
a = overview[name]
|
||||
t = '# {}\n\n'.format(name)
|
||||
for id, ai in a.items():
|
||||
# write out each line sorted by id
|
||||
for id in sorted(a.keys()):
|
||||
ai = a[id]
|
||||
if ai[-1] is None:
|
||||
ai = ai[:-1]
|
||||
t += ' '.join(['{:02d}'.format(id)] + [str(x) for x in ai]) + '\n'
|
||||
|
Reference in New Issue
Block a user