Implemented tools as applets, Added Docker CCTV page, Fixed small issues
Update app.py, uuid-generator.yml, and 47 more files...
This commit is contained in:
94
app.py
94
app.py
@@ -9,22 +9,43 @@ from flask import render_template
|
||||
from minify_html import minify
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from website.content import reload_content_items, get_articles, get_projects, get_tools, sanitize_input_tags
|
||||
from website.content import get_articles, get_projects, get_tools, sanitize_input_tags, load_content_items, get_content, \
|
||||
get_applets
|
||||
from website.contributors import reload_contributors_data, get_contributors_data
|
||||
from website.domains import ALLOWED_DOMAINS
|
||||
from website.l10n.utils import get_user_lang, localize, reload_strings, l10n_url_abs, l10n_url_switch, L10N, \
|
||||
DEFAULT_LANG
|
||||
from website.renderers.applet import render_applet_scripts, render_applet_head
|
||||
from website.renderers.button import render_button
|
||||
from website.renderers.headings import render_heading, render_h2, render_h1, render_h3
|
||||
from website.renderers.code import render_code_block
|
||||
from website.renderers.headings import render_heading, render_h2, render_h1, render_h3, render_h4
|
||||
from website.renderers.paragraph import render_paragraph
|
||||
from website.renderers.lists import render_list_ul
|
||||
from website.renderers.splide import render_splide
|
||||
from website.sidebar import reload_sidebar_entries, get_sidebar_entries
|
||||
from website.sitemap import reload_sitemap_entries, get_sitemap_entries
|
||||
|
||||
# try:
|
||||
# from rich import print
|
||||
# except ImportError:
|
||||
# pass
|
||||
try:
|
||||
from rich import print
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
if os.environ.get('NP_HTML_POST_PROCESS', "NONE") == "MINIFY":
|
||||
print("Using 'minify' as HTML post-processor")
|
||||
|
||||
def post_process_html(html: str) -> str:
|
||||
return minify(html).replace("> <", "><")
|
||||
elif os.environ.get('NP_HTML_POST_PROCESS', "NONE") == "BS4":
|
||||
print("Using 'BeautifulSoup4' as HTML post-processor")
|
||||
|
||||
def post_process_html(html: str) -> str:
|
||||
return BeautifulSoup(html, features="html.parser").prettify()
|
||||
else:
|
||||
print("Using no HTML post-processor")
|
||||
|
||||
def post_process_html(html: str) -> str:
|
||||
return html
|
||||
|
||||
|
||||
app = Flask(
|
||||
@@ -72,24 +93,35 @@ def inject_processors():
|
||||
domain_host=request.headers['Host'],
|
||||
domain_tld=request.headers['Host'].split('.')[-1] if request.headers['Host'] in ALLOWED_DOMAINS else "lu",
|
||||
domain_url_root=request.url_root,
|
||||
|
||||
# L10N
|
||||
l10n=localize,
|
||||
l10n_url_abs=l10n_url_abs,
|
||||
l10n_url_switch=l10n_url_switch,
|
||||
|
||||
# Sidebar
|
||||
get_sidebar_entries=get_sidebar_entries,
|
||||
|
||||
# Content
|
||||
get_content=get_content,
|
||||
get_articles=get_articles,
|
||||
get_projects=get_projects,
|
||||
get_tools=get_tools,
|
||||
|
||||
# Renderers
|
||||
render_button=render_button,
|
||||
render_heading=render_heading,
|
||||
render_h1=render_h1,
|
||||
render_h2=render_h2,
|
||||
render_h3=render_h3,
|
||||
render_h4=render_h4,
|
||||
render_paragraph=render_paragraph,
|
||||
render_list_ul=render_list_ul,
|
||||
render_splide=render_splide,
|
||||
render_applet_scripts=render_applet_scripts,
|
||||
render_applet_head=render_applet_head,
|
||||
render_code_block=render_code_block,
|
||||
|
||||
# Commons
|
||||
url_for=url_for,
|
||||
escape=escape,
|
||||
@@ -118,7 +150,7 @@ def route_sitemap():
|
||||
@app.route('/fr/', defaults={'lang': "fr"})
|
||||
def route_root(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/root.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -132,7 +164,7 @@ def route_root(lang: Optional[str]):
|
||||
@app.route('/fr/contact/', defaults={'lang': "fr"})
|
||||
def route_contact(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/contact.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -152,7 +184,7 @@ def route_content(lang: Optional[str]):
|
||||
except ValueError:
|
||||
requested_tags = None
|
||||
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/project_index.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -178,7 +210,7 @@ def route_content_project(lang: Optional[str], project_id: str):
|
||||
error_code = 404
|
||||
|
||||
if error_key is not None:
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/error.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -188,7 +220,7 @@ def route_content_project(lang: Optional[str], project_id: str):
|
||||
error_code=error_code,
|
||||
)).replace("> <", "><"), error_code
|
||||
else:
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"projects/" + project_id + ".jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -210,7 +242,7 @@ def route_tools_index(lang: Optional[str]):
|
||||
except ValueError:
|
||||
requested_tags = None
|
||||
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/tools_index.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -236,7 +268,7 @@ def route_tools_page(lang: Optional[str], tool_id: str):
|
||||
error_code = 404
|
||||
|
||||
if error_key is not None:
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/error.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -246,14 +278,15 @@ def route_tools_page(lang: Optional[str], tool_id: str):
|
||||
error_code=error_code,
|
||||
)).replace("> <", "><"), error_code
|
||||
else:
|
||||
return minify(render_template(
|
||||
"tools/" + tool_id + ".jinja",
|
||||
return post_process_html(render_template(
|
||||
"pages/tools_page.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
request_path=request.path,
|
||||
standalone="standalone" in request.args,
|
||||
tool_data=get_tools().get(tool_id),
|
||||
tool_id=tool_id,
|
||||
applet_data=get_applets().get(get_tools().get(tool_id).applet_id),
|
||||
)).replace("> <", "><")
|
||||
|
||||
|
||||
@@ -262,7 +295,7 @@ def route_tools_page(lang: Optional[str], tool_id: str):
|
||||
@app.route('/fr/about/', defaults={'lang': "fr"})
|
||||
def route_about(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/about.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -276,7 +309,7 @@ def route_about(lang: Optional[str]):
|
||||
@app.route('/fr/privacy/', defaults={'lang': "fr"})
|
||||
def route_privacy(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/privacy.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -290,7 +323,7 @@ def route_privacy(lang: Optional[str]):
|
||||
@app.route('/fr/links/', defaults={'lang': "fr"})
|
||||
def route_links(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/links.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -304,7 +337,7 @@ def route_links(lang: Optional[str]):
|
||||
@app.route('/fr/debug/', defaults={'lang': "fr"})
|
||||
def route_debug(lang: Optional[str]):
|
||||
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/debug.jinja",
|
||||
user_lang=user_lang,
|
||||
raw_lang=lang,
|
||||
@@ -323,7 +356,7 @@ def handle_exception(e: Exception):
|
||||
if isinstance(e, HTTPException):
|
||||
error_code = e.code
|
||||
|
||||
return minify(render_template(
|
||||
return post_process_html(render_template(
|
||||
"pages/error.jinja",
|
||||
user_lang=DEFAULT_LANG,
|
||||
raw_lang=DEFAULT_LANG,
|
||||
@@ -335,7 +368,7 @@ def handle_exception(e: Exception):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
reload_content_items()
|
||||
load_content_items()
|
||||
reload_strings(os.path.join(os.getcwd(), "data/strings/"))
|
||||
reload_sidebar_entries(os.path.join(os.getcwd(), "data/sidebar.yml"))
|
||||
reload_contributors_data(os.path.join(os.getcwd(), "data/contributors.yml"))
|
||||
@@ -362,22 +395,3 @@ if __name__ == '__main__':
|
||||
#debug=False,
|
||||
load_dotenv=False
|
||||
)
|
||||
|
||||
# return BeautifulSoup(render_template(
|
||||
# "pages/root.jinja",
|
||||
# lang=user_lang,
|
||||
# raw_lang=lang,
|
||||
# request_path=request.path,
|
||||
# standalone="standalone" in request.args,
|
||||
# ), features="html.parser").prettify()
|
||||
|
||||
# try:
|
||||
# from minify_html import minify
|
||||
# FORCE_NON_DEBUG = False
|
||||
# except ImportError:
|
||||
# from bs4 import BeautifulSoup
|
||||
# FORCE_NON_DEBUG = True
|
||||
#
|
||||
# def minify(html):
|
||||
# return BeautifulSoup(html, features="html.parser").prettify()
|
||||
# debug=False if FORCE_NON_DEBUG else True,
|
||||
|
8
data/applets/uuid-generator.yml
Normal file
8
data/applets/uuid-generator.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
applets:
|
||||
- id: "uuid-generator"
|
||||
resources:
|
||||
scripts:
|
||||
- "uuid-generator.mjs"
|
||||
stylesheets:
|
||||
- "uuid-generator.css"
|
35
data/projects/.circuitpython-custom-fs.yml
Normal file
35
data/projects/.circuitpython-custom-fs.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
projects:
|
||||
- id: "circuitpython-custom-fs"
|
||||
metadata:
|
||||
head:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
opengraph:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
type: null
|
||||
url: null
|
||||
image_url: "/resources/NibblePoker/images/content/circuitpython-ebyte-e32/main.png"
|
||||
image_type: null
|
||||
twitter:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
index:
|
||||
priority: 105
|
||||
enable: true
|
||||
title_key: "meta.title"
|
||||
preamble_key: "meta.description"
|
||||
image_url: "/resources/NibblePoker/images/content/circuitpython-ebyte-e32/main.png"
|
||||
image_alt_key: ""
|
||||
general:
|
||||
icon: "fab fa-python"
|
||||
title_key: "meta.title"
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "experiments"
|
||||
- "electronic"
|
||||
- "python"
|
||||
- "circuitpython"
|
||||
languages:
|
||||
- "python"
|
@@ -1,31 +0,0 @@
|
||||
head:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
opengraph:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
type: null
|
||||
url: null
|
||||
image_url: "/resources/NibblePoker/images/content/circuitpython-ebyte-e32/main.png"
|
||||
image_type: null
|
||||
twitter:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
index:
|
||||
priority: 105
|
||||
enable: true
|
||||
title_key: "meta.title"
|
||||
preamble_key: "meta.description"
|
||||
image_url: "/resources/NibblePoker/images/content/circuitpython-ebyte-e32/main.png"
|
||||
image_alt_key: ""
|
||||
general:
|
||||
icon: "fab fa-python"
|
||||
title_key: "meta.title"
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "experiments"
|
||||
- "electronic"
|
||||
- "python"
|
||||
- "circuitpython"
|
||||
languages:
|
||||
- "python"
|
34
data/projects/docker-mini-cctv-nvr.yml
Normal file
34
data/projects/docker-mini-cctv-nvr.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
projects:
|
||||
- id: "docker-mini-cctv-nvr"
|
||||
metadata:
|
||||
head:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
opengraph:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
type: null
|
||||
url: null
|
||||
image_url: "/resources/NibblePoker/images/content/docker-mini-cctv-nvr/main.png"
|
||||
image_type: null
|
||||
twitter:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
index:
|
||||
priority: 105
|
||||
enable: true
|
||||
title_key: "meta.title"
|
||||
preamble_key: "meta.description"
|
||||
image_url: "/resources/NibblePoker/images/content/docker-mini-cctv-nvr/main.png"
|
||||
image_alt_key: ""
|
||||
general:
|
||||
icon: "fab fa-docker"
|
||||
title_key: "meta.title"
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "docker"
|
||||
languages:
|
||||
- "docker"
|
||||
- "php"
|
||||
- "python"
|
@@ -42,13 +42,13 @@
|
||||
abs_href: "/tools"
|
||||
icon: fad fa-toolbox
|
||||
active_id: tools
|
||||
has_new_until_utc: 0
|
||||
has_new_until_utc: 1760986472
|
||||
|
||||
- title_key: text.downloads
|
||||
raw_href: "https://files.nibblepoker.lu/"
|
||||
icon: fad fa-download
|
||||
active_id: ""
|
||||
has_new_until_utc: 1760986472
|
||||
has_new_until_utc: 0
|
||||
|
||||
-
|
||||
|
||||
|
72
data/strings/en/docker-mini-cctv-nvr.yml
Normal file
72
data/strings/en/docker-mini-cctv-nvr.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
# EN - Docker Mini CCTV NVR
|
||||
|
||||
meta.title: "Mini Dockerized CCTV NVR"
|
||||
meta.description: "Mini docker stack that allows you to easily record, clean and serve CCTV recordings made
|
||||
over RSTP while using a minimal amount of system resources."
|
||||
|
||||
intro.title: "Introduction"
|
||||
intro.1: "A mini docker stack that allows you to easily record, clean and serve CCTV recordings made
|
||||
over RSTP while using a minimal amount of system resources."
|
||||
intro.2: "This stack is mainly intended to be used as a backup when other and more complete solutions crash or
|
||||
need to be shutdown.This simple docker stack aims to provide you with a simple,
|
||||
lightweight and robust NVR for all of your RTSP-based CCTV cameras."
|
||||
|
||||
preamble.title: "Preamble"
|
||||
preamble.1: "This stack records the camera's streams as-is and doesn't re-encode or compress it which uses more disk space.
|
||||
See \"Usage statistics example\" for an example."
|
||||
preamble.2: "If served out of your LAN, the web server should be behind a secure reverse-proxy that requires authentication."
|
||||
|
||||
setup.title: "Setup"
|
||||
setup.1: "All of the setup is done through environment variables in the docker-compose.yml file."
|
||||
setup.2: "It should only take 2-3 minutes if you already have the RTSP URL on hand.<br>
|
||||
If you don't have them, you should see your camera's user manual and test the URLs with VLC."
|
||||
|
||||
setup.camera.title: "Cameras"
|
||||
setup.camera.1: "Each recording container needs to be given a RSTP stream URL and a unique folder
|
||||
into which the recordings will go."
|
||||
setup.camera.2: "The URL must be given via the <span class=\"code\">NP_CCTV_URL</span> environment variable,
|
||||
and the output folder via a mounted volume that is mounted as <span class=\"code\">/data</span> in the container."
|
||||
setup.camera.3: "This example will use the <span class=\"code\">rtsp://user:password@address:554/sub-path</span>
|
||||
URL and will put its recordings in <span class=\"code\">./recordings/cam1.</span>"
|
||||
|
||||
setup.cleaner.title: "Cleaner"
|
||||
setup.cleaner.1: "The cleaner script named cleaner.py only requires you to set 1 environment variable named
|
||||
<span class=\"code\">NP_MAX_FILE_AGE_HOURS</span> to the max amount of hours any recording should be kept."
|
||||
setup.cleaner.2: "If not set, the script will simply clean any recordings older than 72 hours."
|
||||
|
||||
setup.web.title: "Web interface"
|
||||
setup.web.1: "The web interface provides more customization options, but at its core,
|
||||
it only requires the camera's environment variables to be set."
|
||||
setup.web.2: "Each camera requires one of the following environment variable:<br>
|
||||
<span class=\"code\">NP_CAM_<camId> = <Camera's name></span>"
|
||||
setup.web.3: "Here is an example for <span class=\"code\">cam1</span> if named as <span class=\"code\">Camera #1</span>:<br>
|
||||
<span class=\"code\">NP_CAM_cam1 = Camera #1</span>"
|
||||
setup.web.vars.title: "Other variables"
|
||||
setup.web.vars.description.title: "Page's title"
|
||||
setup.web.vars.description.footer: "Page's footer HTML content"
|
||||
|
||||
startup.title: "Startup"
|
||||
startup.1: "Once you have finished setting up the stack, you can simply run the following command:"
|
||||
#docker-compose up --build -d
|
||||
|
||||
screenshots.title: "Screenshots"
|
||||
|
||||
statistics.title: "Usage statistics example"
|
||||
statistics.1: "NanoPi R4S 4GB"
|
||||
statistics.1.1: "Uses 0.008 kWh / 8 Watts with other containers and USB HDD & USB SSD"
|
||||
statistics.2: "4 IP Cameras"
|
||||
statistics.2.1: "All H.256 4k RTSP TCP streams"
|
||||
statistics.2.2: "Around 220 GB of data per day"
|
||||
statistics.2.2.1: "Around 20.4 Mbit/s or 2.6 MB/s"
|
||||
statistics.3: "Less than 200MB of RAM usage"
|
||||
statistics.3.1: "~32 MB per recorder"
|
||||
statistics.3.2: "4 MB for cleaner"
|
||||
statistics.3.3: "4 MB for web server"
|
||||
statistics.4: "Uses ~10% of CPU on average over 6 cores"
|
||||
statistics.4.1: "Average of 15% per recorder"
|
||||
statistics.4.2: "Average of 1-5% on cleaner and web server"
|
||||
|
||||
license.title: "License"
|
||||
license.1: "This software, as well as the Simplette CSS Stylesheet used for the web interface are both licensed under Unlicense."
|
||||
|
||||
commons.example: "Example"
|
@@ -1,4 +1,17 @@
|
||||
# EN - UUID Generator
|
||||
|
||||
option.count: "UUID/GUID count"
|
||||
meta.title: "UUID Generator"
|
||||
|
||||
type.label: "UUID Type"
|
||||
|
||||
type.uuid4: "UUID4 / GUID4"
|
||||
|
||||
option.count: "UUID Count"
|
||||
option.hyphen: "Add hyphens"
|
||||
option.guid_brackets: "Add GUID brackets"
|
||||
|
||||
generate: "Generate"
|
||||
|
||||
download.raw: "Raw"
|
||||
download.json: "JSON"
|
||||
download.yaml: "YAML"
|
||||
|
5
data/strings/fr/docker-mini-cctv-nvr.yml
Normal file
5
data/strings/fr/docker-mini-cctv-nvr.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
# FR - Docker Mini CCTV NVR
|
||||
|
||||
meta.title: "Mini Dockerized CCTV NVR"
|
||||
meta.description: "Mini docker stack that allows you to easily record, clean and serve CCTV recordings made
|
||||
over RSTP while using a minimal amount of system resources."
|
@@ -1,4 +1,17 @@
|
||||
# FR - UUID Generator
|
||||
|
||||
meta.title: "Générateur d'UUID"
|
||||
|
||||
type.label: "Type d'UUID"
|
||||
|
||||
type.uuid4: "UUID4 / GUID4"
|
||||
|
||||
option.count: "Nombre d'UUID/GUID"
|
||||
option.hyphen: "Ajouter trait d'union"
|
||||
option.guid_brackets: "Ajouter accolades pour GUID"
|
||||
|
||||
generate: "Générer"
|
||||
|
||||
download.raw: "Brut"
|
||||
download.json: "JSON"
|
||||
download.yaml: "YAML"
|
||||
|
@@ -25,6 +25,6 @@ metadata:
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "undefined"
|
||||
data:
|
||||
resources:
|
||||
scripts:
|
||||
- "epr_main.js"
|
@@ -25,7 +25,7 @@ metadata:
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "undefined"
|
||||
data:
|
||||
resources:
|
||||
scripts:
|
||||
- "svg-to-png.mjs"
|
||||
stylesheets:
|
@@ -1,32 +1,31 @@
|
||||
metadata:
|
||||
head:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
opengraph:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
type: null
|
||||
url: null
|
||||
image_url: "/resources/NibblePoker/images/tools/excel-password-remover/excel-password-remover.png"
|
||||
image_type: null
|
||||
twitter:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
index:
|
||||
priority: 100
|
||||
enable: true
|
||||
title_key: "meta.title"
|
||||
preamble_key: "meta.description"
|
||||
image_url: "/resources/NibblePoker/images/tools/excel-password-remover/excel-password-remover.png"
|
||||
image_alt_key: ""
|
||||
general:
|
||||
icon: "fab fa-python"
|
||||
title_key: "meta.title"
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "undefined"
|
||||
data:
|
||||
scripts:
|
||||
- "uuid-generator.mjs"
|
||||
stylesheets:
|
||||
- "uuid-generator.css"
|
||||
|
||||
tools:
|
||||
- id: "uuid-generator"
|
||||
applet_id: "uuid-generator"
|
||||
metadata:
|
||||
head:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
opengraph:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
type: null
|
||||
url: null
|
||||
image_url: "/resources/NibblePoker/images/tools/excel-password-remover/excel-password-remover.png"
|
||||
image_type: null
|
||||
twitter:
|
||||
title_key: "meta.title"
|
||||
description_key: "meta.description"
|
||||
index:
|
||||
priority: 100
|
||||
enable: true
|
||||
title_key: "meta.title"
|
||||
preamble_key: "meta.description"
|
||||
image_url: "/resources/NibblePoker/images/tools/excel-password-remover/excel-password-remover.png"
|
||||
image_alt_key: ""
|
||||
general:
|
||||
icon: "fab fa-python"
|
||||
title_key: "meta.title"
|
||||
subtitle_key: "article.subtitle"
|
||||
tags:
|
||||
- "undefined"
|
||||
|
@@ -10,4 +10,4 @@ locked-dict
|
||||
Werkzeug~=3.0.4
|
||||
|
||||
#gunicorn
|
||||
waitress~=3.0.0
|
||||
waitress~=3.0.2
|
||||
|
@@ -39,8 +39,8 @@ popd
|
||||
:js-uuidgenerator-minify
|
||||
echo Minifying UUID Generator
|
||||
pushd %CD%
|
||||
cd %~dp0\..\static\resources\NibblePoker\tools\uuid-generator\
|
||||
echo ^> static\resources\NibblePoker\tools\svg-to-png\svg-to-png.mjs
|
||||
cd %~dp0\..\static\resources\NibblePoker\applets\uuid-generator\
|
||||
echo ^> static\resources\NibblePoker\applets\uuid-generator\uuid-generator.mjs
|
||||
call "%~dp0node_modules\.bin\rollup" uuid-generator.mjs --file uuid-generator.js
|
||||
call "%~dp0node_modules\.bin\terser" uuid-generator.js -c -m -o uuid-generator.min.js
|
||||
popd
|
||||
|
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Generates a random UUID4 and returns its string representation
|
||||
* @returns {`${string}-${string}-${string}-${string}-${string}`}
|
||||
*/
|
||||
export function generateUUID4(addHyphens, addGuidBrackets) {
|
||||
let uuid4 = crypto.randomUUID();
|
||||
if(!addHyphens) {
|
||||
uuid4 = uuid4.replace(/-/g, "");
|
||||
}
|
||||
if(addGuidBrackets) {
|
||||
uuid4 = "{" + uuid4 + "}";
|
||||
}
|
||||
return uuid4;
|
||||
}
|
||||
|
||||
// Tool-centric stuff
|
||||
{
|
||||
/** @type {HTMLSelectElement} */
|
||||
const eOptionTypeSelect = document.querySelector("select#uuid-generator-option-type");
|
||||
|
||||
/** @type {HTMLInputElement} */
|
||||
const eOptionCountInput = document.querySelector("input#uuid-generator-option-count");
|
||||
|
||||
/** @type {HTMLInputElement} */
|
||||
const eOptionHyphenInput = document.querySelector("input#uuid-generator-option-hyphens");
|
||||
/** @type {HTMLInputElement} */
|
||||
const eOptionGuidBracketsInput = document.querySelector("input#uuid-generator-option-guid-brackets");
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
const eGenerateButton = document.querySelector("#uuid-generator-generate");
|
||||
/** @type {HTMLElement} */
|
||||
const eDownloadRawButton = document.querySelector("#uuid-generator-download-raw");
|
||||
/** @type {HTMLElement} */
|
||||
const eDownloadJsonButton = document.querySelector("#uuid-generator-download-json");
|
||||
/** @type {HTMLElement} */
|
||||
const eDownloadYamlButton = document.querySelector("#uuid-generator-download-yaml");
|
||||
|
||||
/** @type {HTMLTextAreaElement} */
|
||||
const ePreviewTextArea = document.querySelector("textarea#uuid-generator-preview");
|
||||
|
||||
let lastUUIDs = [];
|
||||
|
||||
/** @returns {number} */
|
||||
function getDesiredCount() {
|
||||
let desiredCount = null;
|
||||
try {
|
||||
desiredCount = parseInt(eOptionCountInput.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if(desiredCount === null) {
|
||||
desiredCount = 1;
|
||||
}
|
||||
if(desiredCount < 1) {
|
||||
desiredCount = 1;
|
||||
}
|
||||
if(desiredCount > 1000) {
|
||||
desiredCount = 1000;
|
||||
}
|
||||
return desiredCount;
|
||||
}
|
||||
|
||||
function changeDesiredCount(difference = 0) {
|
||||
if(difference !== 0) {
|
||||
eOptionCountInput.value = getDesiredCount() + difference;
|
||||
}
|
||||
eOptionCountInput.value = getDesiredCount();
|
||||
}
|
||||
|
||||
|
||||
function downloadStringAsFile(content, filename, contentType) {
|
||||
const blob = new Blob([content], { type: contentType });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
eGenerateButton.addEventListener("click", function() {
|
||||
ePreviewTextArea.value = "";
|
||||
|
||||
let desiredCount = getDesiredCount();
|
||||
let uuidGenerator = generateUUID4;
|
||||
|
||||
let addHyphens = eOptionHyphenInput.checked;
|
||||
let addGuidBrackets = eOptionGuidBracketsInput.checked;
|
||||
|
||||
lastUUIDs = [];
|
||||
for(let i= 0; i < desiredCount; i++) {
|
||||
lastUUIDs.push(uuidGenerator(addHyphens, addGuidBrackets));
|
||||
ePreviewTextArea.value += uuidGenerator(addHyphens, addGuidBrackets) + "\n";
|
||||
}
|
||||
ePreviewTextArea.value = lastUUIDs.join("\n");
|
||||
});
|
||||
eOptionCountInput.addEventListener("change", function() {
|
||||
changeDesiredCount(0);
|
||||
});
|
||||
eOptionCountInput.addEventListener("mousewheel", function(e) {
|
||||
// Handling wheel scroll on count field.
|
||||
if(e.wheelDelta < 0) {
|
||||
changeDesiredCount(-1);
|
||||
} else {
|
||||
changeDesiredCount(1);
|
||||
}
|
||||
});
|
||||
|
||||
eDownloadRawButton.addEventListener("click", function() {
|
||||
if (lastUUIDs.length <= 0) {
|
||||
return;
|
||||
}
|
||||
downloadStringAsFile(lastUUIDs.join("\n"), "uuids.txt", "text/plain");
|
||||
});
|
||||
eDownloadJsonButton.addEventListener("click", function() {
|
||||
if (lastUUIDs.length <= 0) {
|
||||
return;
|
||||
}
|
||||
downloadStringAsFile(JSON.stringify(lastUUIDs, null, 4), "uuids.json", "application/json");
|
||||
});
|
||||
eDownloadYamlButton.addEventListener("click", function() {
|
||||
if (lastUUIDs.length <= 0) {
|
||||
return;
|
||||
}
|
||||
downloadStringAsFile("- \"" + lastUUIDs.join("\"\n- \"") + "\"", "uuids.yaml", "text/yaml");
|
||||
});
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,38 @@
|
||||
# CircuitPython - Custom File Systems
|
||||
|
||||
The goal of this experiment was to try and give people a strong, clear, and documented starting point for
|
||||
future experiments that may require virtual file systems and block-level devices on CircuitPython devices.
|
||||
|
||||
For example, by using the blank examples, you can easily create a bootstrapping code and file system that connects
|
||||
securely to a remote server and pulls code directly from it without ever having to touch the MCU's flash storage. \
|
||||
|
||||
The second main advantage is that this project can serve as a robust educational tool. \
|
||||
Due to the permissive nature of Python and CircuitPython's APIs, it lets people easily test out different designs and
|
||||
mechanisms for their file systems without running the risk of corrupting unrelated data or bricking their device. \
|
||||
Additionally, it is possible to manipulate and add logging to many of the methods which allows you to see and understand
|
||||
the inner workings of CircuitPython, filesystems and BLD devices themselves.
|
||||
|
||||
|
||||
|
||||
Le but de cette expérience était de fournir un point de départ solide, clair et documenté pour
|
||||
de futures expériences nécessitant des systèmes de fichiers virtuels, ou des périphériques de bloc sur
|
||||
des appareils utilisant CircuitPython.
|
||||
|
||||
Par exemple, en utilisant les exemples modèles, il est possible de créer facilement un code de démarrage et
|
||||
un système de fichiers qui vont connecter de manière sécurisée à un serveur distant, et y récupérer directement du
|
||||
code sans jamais avoir à toucher à la mémoire flash du MCU.
|
||||
|
||||
Le deuxième avantage majeur est que ce projet peut servir d'outil éducatif.
|
||||
En effet, les APIs extrêmement permissives de Python et CircuitPython permettent à leurs utilisateurs de tester
|
||||
facilement différents designs et mécanismes pour leurs systèmes de fichiers sans risquer de corrompre des
|
||||
données ou de rendre leur appareil inutilisable. \
|
||||
De plus, vous pouvez très facilement ajouter des messages de débogage et manipuler plusieurs méthodes, ce
|
||||
qui permet de voir et de comprendre le fonctionnement interne de CircuitPython, des systèmes de fichiers et
|
||||
des périphériques de bloc en eux-mêmes.
|
||||
|
||||
|
||||
|
||||
## ???
|
||||
|
||||
## Media coverage
|
||||
https://blog.adafruit.com/2023/02/22/icymi-python-on-microcontrollers-newsletter-new-raspberry-pi-debug-probe-circuitpython-8-0-2-and-much-more-circuitpython-python-micropython-icymi-raspberry_pi/
|
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Generates a random UUID4 and returns its string representation
|
||||
* @returns {`${string}-${string}-${string}-${string}-${string}`}
|
||||
*/
|
||||
export function generateUUID4() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Tool-centric stuff
|
||||
{
|
||||
/** @type {HTMLSelectElement} */
|
||||
const eOptionTypeSelect = document.querySelector("select#uuid-generator-option-type");
|
||||
/** @type {HTMLInputElement} */
|
||||
const eOptionCountInput = document.querySelector("input#uuid-generator-option-count");
|
||||
/** @type {HTMLInputElement} */
|
||||
const eOptionHyphenInput = document.querySelector("input#uuid-generator-option-hyphens");
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
const eGenerateButton = document.querySelector("#uuid-generator-generate");
|
||||
/** @type {HTMLElement} */
|
||||
const eDownloadButton = document.querySelector("#uuid-generator-download");
|
||||
|
||||
/** @type {HTMLTextAreaElement} */
|
||||
const ePreviewTextArea = document.querySelector("textarea#uuid-generator-preview");
|
||||
|
||||
/** @returns {number} */
|
||||
function getDesiredCount() {
|
||||
let desiredCount = null;
|
||||
try {
|
||||
desiredCount = parseInt(eOptionCountInput.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if(desiredCount === null) {
|
||||
desiredCount = 1;
|
||||
}
|
||||
if(desiredCount < 1) {
|
||||
desiredCount = 1;
|
||||
}
|
||||
if(desiredCount > 1000) {
|
||||
desiredCount = 1000;
|
||||
}
|
||||
return desiredCount;
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
eGenerateButton.addEventListener("click", function() {
|
||||
ePreviewTextArea.value = "";
|
||||
|
||||
let desiredCount = getDesiredCount();
|
||||
let uuidGenerator = generateUUID4;
|
||||
|
||||
for(let i= 0; i < desiredCount; i++) {
|
||||
ePreviewTextArea.value += uuidGenerator() + "\n";
|
||||
}
|
||||
});
|
||||
eDownloadButton.addEventListener("click", function() {
|
||||
//eFileDropInput.click();
|
||||
});
|
||||
}
|
||||
}
|
44
templates/applets/uuid-generator.jinja
Normal file
44
templates/applets/uuid-generator.jinja
Normal file
@@ -0,0 +1,44 @@
|
||||
<label for="uuid-generator-option-type" class="mr-xs">{{ l10n("type.label", "uuid-generator", user_lang) }}:</label>
|
||||
<select name="uuid-generator-option-type" id="uuid-generator-option-type" class="p-xxs border r-s">
|
||||
<option value="type-uuid4" selected>{{ l10n("type.uuid4", "uuid-generator", user_lang) }}</option>
|
||||
<!--<option value="type-guid">{{ l10n("type.guid", "uuid-generator", user_lang) }}</option>-->
|
||||
</select>
|
||||
|
||||
<br>
|
||||
|
||||
<label for="uuid-generator-option-count" class="mr-xs">{{ l10n("option.count", "uuid-generator", user_lang) }}:</label>
|
||||
<input id="uuid-generator-option-count" class="p-xxs border r-s" type="number" value="4" min="1" max="1000">
|
||||
|
||||
<br>
|
||||
|
||||
<label for="uuid-generator-option-hyphens" class="mr-xxs">{{ l10n("option.hyphen", "uuid-generator", user_lang) }}:</label>
|
||||
<input id="uuid-generator-option-hyphens" class="r-m border" type="checkbox" checked>
|
||||
|
||||
<br>
|
||||
|
||||
<label for="uuid-generator-option-guid-brackets" class="mr-xxs">{{ l10n("option.guid_brackets", "uuid-generator", user_lang) }}:</label>
|
||||
<input id="uuid-generator-option-guid-brackets" class="r-m border" type="checkbox">
|
||||
|
||||
<hr class="subtle">
|
||||
|
||||
<button id="uuid-generator-generate" class="p-xs r-s border b-light success">
|
||||
<i class="fa-duotone fa-solid fa-gears mr-xs"></i>{{ l10n("generate", "uuid-generator", user_lang) }}
|
||||
</button>
|
||||
|
||||
<button class="p-xs r-s border b-light primary rr-0 br-0">
|
||||
<i class="fa-duotone fa-solid fa-download"></i>
|
||||
</button>
|
||||
<button id="uuid-generator-download-raw" class="p-xs r-s border b-light primary ml-0 r-0 br-0">
|
||||
{{ l10n("download.raw", "uuid-generator", user_lang) }}
|
||||
</button>
|
||||
<button id="uuid-generator-download-json" class="p-xs r-s border b-light primary ml-0 r-0 br-0">
|
||||
{{ l10n("download.json", "uuid-generator", user_lang) }}
|
||||
</button>
|
||||
<button id="uuid-generator-download-yaml" class="p-xs r-s border b-light primary ml-0 rl-0">
|
||||
{{ l10n("download.yaml", "uuid-generator", user_lang) }}
|
||||
</button>
|
||||
|
||||
<hr class="subtle">
|
||||
|
||||
<label for="uuid-generator-preview" class="d-none">{{ l10n("preview.label", "uuid-generator", user_lang) }}:</label>
|
||||
<textarea name="uuid-generator-preview" id="uuid-generator-preview" rows="16" class="w-full border r-s"></textarea>
|
@@ -10,7 +10,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="alternate icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/FontAwesomePro/6.5.1/css/all.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/StandardCSS/nibblepoker.min.css">
|
||||
<!--<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/StandardCSS/nibblepoker.min.css">-->
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/IndevCSS/nibblepoker.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/Quantum/Quantum.min.css">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
|
@@ -19,7 +19,8 @@
|
||||
{% block extra_preloads %}{% endblock %}
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/FontAwesomePro/6.5.1/css/all.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/StandardCSS/nibblepoker.min.css">
|
||||
<!--<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/StandardCSS/nibblepoker.min.css">-->
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/NibblePoker/IndevCSS/nibblepoker.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.nibblepoker.{{ domain_tld }}/Quantum/Quantum.min.css">
|
||||
|
||||
{% block extra_stylesheets %}{% endblock %}
|
||||
|
5
templates/elements/code.jinja
Normal file
5
templates/elements/code.jinja
Normal file
@@ -0,0 +1,5 @@
|
||||
<code class="code ox-auto w-full d-inline-block position-relative language-{{ code_language }}">
|
||||
{% for code_line in code_lines %}
|
||||
<span class="code-line t-nowrap">{{ code_line }}</span><br>
|
||||
{% endfor %}
|
||||
</code>
|
9
templates/elements/list-ul.jinja
Normal file
9
templates/elements/list-ul.jinja
Normal file
@@ -0,0 +1,9 @@
|
||||
<ul class="l-bullets l-bullet-manual">
|
||||
{% for list_item in list_items %}
|
||||
{% if list_item.__class__.__name__ == 'list' %}
|
||||
{{ render_list_ul(list_item) }}
|
||||
{% else %}
|
||||
<li>{{ list_item }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
@@ -1,3 +1,3 @@
|
||||
<p class="mt-xs mx-s">
|
||||
<p class="mt-xs">
|
||||
{{ paragraph_inner_html }}
|
||||
</p>
|
@@ -17,22 +17,20 @@
|
||||
{{ render_paragraph(l10n("introduction.text.1", "privacy", user_lang)) }}
|
||||
{{ render_paragraph(l10n("introduction.text.2", "privacy", user_lang) +
|
||||
'<br><i class="fad fa-globe ml-s t-size-8"></i>
|
||||
<a href="https://gdpr.eu/privacy-notice/">
|
||||
https://gdpr.eu/
|
||||
</a><br>
|
||||
<a href="https://gdpr.eu/privacy-notice/" class="ml-xs">https://gdpr.eu/</a><br>
|
||||
<i class="fad fa-globe ml-s t-size-8"></i>
|
||||
<a href="https://eur-lex.europa.eu/legal-content/ALL/?uri=CELEX%3A32016R0679">
|
||||
https://eur-lex.europa.eu/
|
||||
</a>')}}
|
||||
<a href="https://eur-lex.europa.eu/legal-content/ALL/?uri=CELEX%3A32016R0679" class="ml-xs">https://eur-lex.europa.eu/</a>')}}
|
||||
|
||||
|
||||
{{ render_h1(l10n("v2.data.title", "privacy", user_lang), "fad fa-database") }}
|
||||
{{ render_paragraph(l10n("v2.data.intro.1", "privacy", user_lang) +
|
||||
'<br>' + l10n("v2.data.intro.2", "privacy", user_lang)) }}
|
||||
{{ render_paragraph(l10n('v2.data.private.1', "privacy", user_lang) +
|
||||
'<ul><li>' + l10n('v2.data.private_list.1', "privacy", user_lang) +
|
||||
'</li><li>' + l10n('v2.data.private_list.2', "privacy", user_lang) +
|
||||
'</li></ul>') }}
|
||||
render_list_ul([
|
||||
l10n('v2.data.private_list.1', "privacy", user_lang),
|
||||
l10n('v2.data.private_list.2', "privacy", user_lang),
|
||||
])
|
||||
) }}
|
||||
{{ render_paragraph(l10n('v2.data.non_private.1', "privacy", user_lang) +
|
||||
'<ul><li>' + l10n('v2.data.non_private_list.1', "privacy", user_lang) +
|
||||
'</li><li>' + l10n('v2.data.non_private_list.2', "privacy", user_lang) +
|
||||
@@ -167,17 +165,17 @@
|
||||
|
||||
{{ render_h1(l10n("contact.title", "privacy", user_lang), "fad fa-mailbox") }}
|
||||
{{ render_paragraph(l10n("contact.text.1", "privacy", user_lang) +
|
||||
'<br><i class="fad fa-at t-size-8 ml-s"></i><a href="mailto:herwin.bozet@gmail.com">herwin.bozet@gmail.com</a>') }}
|
||||
'<br><i class="fad fa-at t-size-8 ml-s"></i><a class="ml-xs" href="mailto:herwin.bozet@gmail.com">herwin.bozet@gmail.com</a>') }}
|
||||
|
||||
|
||||
{{ render_h1(l10n("complaint.title", "privacy", user_lang), "fad fa-gavel") }}
|
||||
{{ render_paragraph(l10n("complaint.text.1", "privacy", user_lang)) }}
|
||||
{{ render_paragraph(l10n('complaint.text.2', "privacy", user_lang) +
|
||||
'<br><i class="fad fa-globe ml-s t-size-8"></i>' +
|
||||
'<a href="https://ec.europa.eu/info/law/law-topic/data-protection/reform/rights-citizens/redress/what-should-i-do-if-i-think-my-personal-data-protection-rights-havent-been-respected_en">https://ec.europa.eu/</a>' +
|
||||
'<a class="ml-xs" href="https://ec.europa.eu/info/law/law-topic/data-protection/reform/rights-citizens/redress/what-should-i-do-if-i-think-my-personal-data-protection-rights-havent-been-respected_en">https://ec.europa.eu/</a>' +
|
||||
'<span class="ml-s">(' + l10n('english', "langs", user_lang) + ')</span>' +
|
||||
'<br><i class="fad fa-globe ml-s t-size-8"></i>' +
|
||||
'<a href="https://gegevensbeschermingsautoriteit.be/citoyen/agir/introduire-une-plainte">https://gegevensbeschermingsautoriteit.be/</a>' +
|
||||
'<a class="ml-xs" href="https://gegevensbeschermingsautoriteit.be/citoyen/agir/introduire-une-plainte">https://gegevensbeschermingsautoriteit.be/</a>' +
|
||||
'<span class="ml-s">(' + l10n('french', "langs", user_lang) + ')</span>' ) }}
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -20,34 +20,35 @@
|
||||
|
||||
{{ render_h2(l10n("updates.title", "home", user_lang)) }}
|
||||
|
||||
<p><i class="fad fa-calendar-alt mr-xs"></i>{{ l10n("updates.4.date", "home", user_lang) }}</p>
|
||||
<ul>
|
||||
<li>{{ l10n("updates.4.text.1", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.4.text.2", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.4.text.3", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.text.privacy", "home", user_lang) }}</li>
|
||||
</ul>
|
||||
<p><i class="fad fa-calendar-alt mr-xs mt-s"></i>{{ l10n("updates.4.date", "home", user_lang) }}</p>
|
||||
{{ render_list_ul([
|
||||
l10n("updates.4.text.1", "home", user_lang),
|
||||
l10n("updates.4.text.2", "home", user_lang),
|
||||
l10n("updates.4.text.3", "home", user_lang),
|
||||
l10n("updates.text.privacy", "home", user_lang)
|
||||
]) }}
|
||||
|
||||
<p><i class="fad fa-calendar-alt mr-xs"></i>{{ l10n("updates.3.date", "home", user_lang) }}</p>
|
||||
<ul>
|
||||
<li>{{ l10n("updates.3.text.1", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.3.text.2", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.3.text.3", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.text.privacy", "home", user_lang) }}</li>
|
||||
</ul>
|
||||
<p><i class="fad fa-calendar-alt mr-xs mt-s"></i>{{ l10n("updates.3.date", "home", user_lang) }}</p>
|
||||
{{ render_list_ul([
|
||||
l10n("updates.3.text.1", "home", user_lang),
|
||||
l10n("updates.3.text.2", "home", user_lang),
|
||||
l10n("updates.3.text.3", "home", user_lang),
|
||||
l10n("updates.text.privacy", "home", user_lang)
|
||||
]) }}
|
||||
|
||||
<p><i class="fad fa-calendar-alt mr-xs"></i>{{ l10n("updates.2.date", "home", user_lang) }}</p>
|
||||
<ul>
|
||||
<li>{{ l10n("updates.2.text.1", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.2.text.2", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.2.text.3", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.2.text.4", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.text.privacy", "home", user_lang) }}</li>
|
||||
</ul>
|
||||
<p><i class="fad fa-calendar-alt mr-xs mt-s"></i>{{ l10n("updates.2.date", "home", user_lang) }}</p>
|
||||
{{ render_list_ul([
|
||||
l10n("updates.2.text.1", "home", user_lang),
|
||||
l10n("updates.2.text.2", "home", user_lang),
|
||||
l10n("updates.2.text.3", "home", user_lang),
|
||||
l10n("updates.2.text.4", "home", user_lang),
|
||||
l10n("updates.text.privacy", "home", user_lang)
|
||||
]) }}
|
||||
|
||||
<p><i class="fad fa-calendar-alt mr-xs mt-s"></i>{{ l10n("updates.1.date", "home", user_lang) }}</p>
|
||||
{{ render_list_ul([
|
||||
l10n("updates.1.text.1", "home", user_lang),
|
||||
l10n("updates.text.privacy", "home", user_lang)
|
||||
]) }}
|
||||
|
||||
<p><i class="fad fa-calendar-alt mr-xs"></i>{{ l10n("updates.1.date", "home", user_lang) }}</p>
|
||||
<ul>
|
||||
<li>{{ l10n("updates.1.text.1", "home", user_lang) }}</li>
|
||||
<li>{{ l10n("updates.text.privacy", "home", user_lang) }}</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@@ -4,9 +4,7 @@
|
||||
{% block head_description %}{{ l10n(tool_data.metadata.head.description_key, tool_id, user_lang) }}{% endblock %}
|
||||
|
||||
{% block extra_stylesheets %}
|
||||
{% for tool_stylesheet in tool_data.data.stylesheets %}
|
||||
<link rel="stylesheet" href="{{ url_for("static", filename="/resources/NibblePoker/tools/" + tool_id + "/" + tool_stylesheet) }}">
|
||||
{% endfor %}
|
||||
{{ render_applet_head(applet_data) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_title %}
|
||||
@@ -21,16 +19,10 @@
|
||||
) }}
|
||||
|
||||
<div class="px-xxs">
|
||||
{% block tool_content %}{% endblock %}
|
||||
{% include 'applets/'+applet_data.id+'.jinja' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{% for tool_script in tool_data.data.scripts %}
|
||||
{% if tool_script.endswith(".mjs") %}
|
||||
<script src="{{ url_for("static", filename="/resources/NibblePoker/tools/" + tool_id + "/" + tool_script) }}" type="module"></script>
|
||||
{% else %}
|
||||
<script src="{{ url_for("static", filename="/resources/NibblePoker/tools/" + tool_id + "/" + tool_script) }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ render_applet_scripts(applet_data) }}
|
||||
{% endblock %}
|
@@ -38,4 +38,5 @@
|
||||
<script src="https://cdn.nibblepoker.lu/HighlightJS/11.9.0-custom/highlight.min.js"></script>
|
||||
|
||||
<script src="{{ url_for("static", filename="/resources/NibblePoker/js/nibblepoker-splide.min.js") }}"></script>
|
||||
<script src="{{ url_for("static", filename="/resources/NibblePoker/js/nibblepoker-code.min.js") }}"></script>
|
||||
{% endblock %}
|
||||
|
131
templates/projects/docker-mini-cctv-nvr.jinja
Normal file
131
templates/projects/docker-mini-cctv-nvr.jinja
Normal file
@@ -0,0 +1,131 @@
|
||||
{% extends "projects/_project.jinja" %}
|
||||
|
||||
{% block project_content %}
|
||||
{{ render_h2(l10n("intro.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("intro.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("intro.2", project_id, user_lang)) }}
|
||||
|
||||
{{ render_h2(l10n("preamble.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("preamble.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("preamble.2", project_id, user_lang)) }}
|
||||
|
||||
{{ render_h2(l10n("setup.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.2", project_id, user_lang)) }}
|
||||
|
||||
{{ render_h3(l10n("setup.camera.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.camera.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.camera.2", project_id, user_lang)) }}
|
||||
{{ render_h4(l10n("commons.example", project_id, user_lang)) }}
|
||||
{{
|
||||
render_code_block([
|
||||
"nvr_stack:",
|
||||
" cctv_recorder_cam1:",
|
||||
" container_name: cctv-recorder-cam1",
|
||||
" build:",
|
||||
" context: .",
|
||||
" dockerfile: Dockerfile_recorder",
|
||||
" environment:",
|
||||
" - TZ=Europe/Brussels",
|
||||
" - \"NP_CCTV_URL=rtsp://user:password@address:554/sub-path\"",
|
||||
" volumes:",
|
||||
" - ./recordings/cam1:/data",
|
||||
" restart: unless-stopped",
|
||||
], "yaml")
|
||||
}}
|
||||
{{ render_paragraph(l10n("setup.camera.3", project_id, user_lang)) }}
|
||||
|
||||
{{ render_h3(l10n("setup.cleaner.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.cleaner.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.cleaner.2", project_id, user_lang)) }}
|
||||
{{ render_h4(l10n("commons.example", project_id, user_lang)) }}
|
||||
{{
|
||||
render_code_block([
|
||||
"nvr_stack:",
|
||||
" cctv_cleaner:",
|
||||
" container_name: cctv-cleaner",
|
||||
" build:",
|
||||
" context: .",
|
||||
" dockerfile: Dockerfile_cleaner",
|
||||
" environment:",
|
||||
" - TZ=Europe/Brussels",
|
||||
" - \"NP_MAX_FILE_AGE_HOURS=72\"",
|
||||
" volumes:",
|
||||
" - ./recordings:/data",
|
||||
" - ./cleaner.py:/app/app.py:ro",
|
||||
" restart: unless-stopped",
|
||||
], "yaml")
|
||||
}}
|
||||
|
||||
{{ render_h3(l10n("setup.web.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.web.1", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.web.2", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("setup.web.3", project_id, user_lang)) }}
|
||||
{{ render_h4(l10n("commons.web.vars.title", project_id, user_lang)) }}
|
||||
{{ render_h4(l10n("commons.example", project_id, user_lang)) }}
|
||||
{{
|
||||
render_code_block([
|
||||
"nvr_stack:",
|
||||
" cctv_web:",
|
||||
" container_name: cctv-web",
|
||||
" image: php:apache",
|
||||
" ports:",
|
||||
" - 26880:80",
|
||||
" environment:",
|
||||
" - TZ=Europe/Brussels",
|
||||
" - \"NP_CAM_cam1=Camera #1\"",
|
||||
" - \"NP_CAM_cam2=Camera #2\"",
|
||||
" - \"NP_TITLE=NibblePoker's Mini CCTV NVR\"",
|
||||
" - \"NP_FOOTER=Made by <i>BOZET Herwin</i>\"",
|
||||
" volumes:",
|
||||
" - ./htdocs:/var/www/html # Cannot be \":ro\" since the recordings are mounted into it.",
|
||||
" - ./apache2.conf:/etc/apache2/apache2.conf:ro",
|
||||
" - ./recordings:/var/www/html/data:ro",
|
||||
" restart: unless-stopped",
|
||||
], "yaml")
|
||||
}}
|
||||
|
||||
{{ render_h2(l10n("startup.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("startup.1", project_id, user_lang)) }}
|
||||
{{
|
||||
render_code_block([
|
||||
"docker-compose up --build -d",
|
||||
], "bash")
|
||||
}}
|
||||
|
||||
{{ render_h2(l10n("screenshots.title", project_id, user_lang)) }}
|
||||
{{ render_splide([
|
||||
'<img src="' + url_for("static", filename="/resources/NibblePoker/images/content/" + project_id + "/home.png") + '">',
|
||||
'<img src="' + url_for("static", filename="/resources/NibblePoker/images/content/" + project_id + "/cam.png") + '">',
|
||||
]) }}
|
||||
|
||||
{{ render_h2(l10n("statistics.title", project_id, user_lang)) }}
|
||||
{{ render_list_ul([
|
||||
l10n("statistics.1", project_id, user_lang),
|
||||
[
|
||||
l10n("statistics.1.1", project_id, user_lang),
|
||||
],
|
||||
l10n("statistics.2", project_id, user_lang),
|
||||
[
|
||||
l10n("statistics.2.1", project_id, user_lang),
|
||||
l10n("statistics.2.2", project_id, user_lang),
|
||||
[
|
||||
l10n("statistics.2.2.1", project_id, user_lang),
|
||||
],
|
||||
],
|
||||
l10n("statistics.3", project_id, user_lang),
|
||||
[
|
||||
l10n("statistics.3.1", project_id, user_lang),
|
||||
l10n("statistics.3.2", project_id, user_lang),
|
||||
l10n("statistics.3.3", project_id, user_lang),
|
||||
],
|
||||
l10n("statistics.4", project_id, user_lang),
|
||||
[
|
||||
l10n("statistics.4.1", project_id, user_lang),
|
||||
l10n("statistics.4.2", project_id, user_lang),
|
||||
],
|
||||
]) }}
|
||||
|
||||
{{ render_h2(l10n("license.title", project_id, user_lang)) }}
|
||||
{{ render_paragraph(l10n("license.1", project_id, user_lang)) }}
|
||||
{% endblock %}
|
@@ -1,38 +0,0 @@
|
||||
{% extends "tools/_tool.jinja" %}
|
||||
|
||||
{% block tool_content %}
|
||||
|
||||
<div class="px-xs mt-s">
|
||||
|
||||
<label for="uuid-generator-option-type" class="mr-xs">{{ l10n("type.label", tool_id, user_lang) }}:</label>
|
||||
<select name="uuid-generator-option-type" id="uuid-generator-option-type" class="p-xxs border r-s">
|
||||
<option value="type-uuid4" selected>{{ l10n("type.uuid4", tool_id, user_lang) }}</option>
|
||||
<option value="type-guid">{{ l10n("type.guid", tool_id, user_lang) }}</option>
|
||||
</select>
|
||||
|
||||
<br>
|
||||
|
||||
<label for="uuid-generator-option-count" class="mr-xs">{{ l10n("option.count", tool_id, user_lang) }}:</label>
|
||||
<input id="uuid-generator-option-count" class="p-xxs border r-s" type="number" value="4" min="1" max="1000">
|
||||
|
||||
<br>
|
||||
|
||||
<label for="uuid-generator-option-hyphens" class="mr-xxs">{{ l10n("option.hyphen", tool_id, user_lang) }}:</label>
|
||||
<input id="uuid-generator-option-hyphens" type="checkbox" checked>
|
||||
|
||||
<hr class="subtle">
|
||||
|
||||
<button id="uuid-generator-generate" class="p-xs r-s border b-light success">
|
||||
<i class="fa-duotone fa-solid fa-gears mr-xs"></i>{{ l10n("generate", tool_id, user_lang) }}
|
||||
</button>
|
||||
<button id="uuid-generator-download" class="p-xs r-s border b-light primary">
|
||||
<i class="fa-duotone fa-solid fa-download mr-xs"></i>{{ l10n("download", tool_id, user_lang) }}
|
||||
</button>
|
||||
|
||||
<hr class="subtle">
|
||||
|
||||
<label for="uuid-generator-preview" class="d-none">{{ l10n("preview.label", tool_id, user_lang) }}:</label>
|
||||
<textarea name="uuid-generator-preview" id="uuid-generator-preview" rows="16" class="w-full border r-s"></textarea>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@@ -5,31 +5,52 @@ from typing import Any
|
||||
from locked_dict.locked_dict import LockedDict
|
||||
import yaml
|
||||
|
||||
from .metadata import ContentMetadata
|
||||
from .project import ContentProject
|
||||
from .tool import ContentTool, ContentToolData
|
||||
from .dataclasses import *
|
||||
|
||||
__CONTENT: ContentRoot = ContentRoot()
|
||||
|
||||
__CONTENT_APPLETS: LockedDict[str, ContentApplet] = LockedDict()
|
||||
__CONTENT_ARTICLES: LockedDict = LockedDict()
|
||||
__CONTENT_PROJECTS: LockedDict[str, ContentProject] = LockedDict()
|
||||
__CONTENT_TOOLS: LockedDict[str, ContentTool] = LockedDict()
|
||||
|
||||
|
||||
def get_content() -> ContentRoot:
|
||||
return __CONTENT
|
||||
|
||||
|
||||
def get_applets() -> LockedDict[str, ContentApplet]:
|
||||
return __CONTENT.applets
|
||||
|
||||
|
||||
def get_articles() -> LockedDict:
|
||||
return __CONTENT_ARTICLES
|
||||
|
||||
|
||||
def get_projects() -> LockedDict[str, ContentProject]:
|
||||
return __CONTENT_PROJECTS
|
||||
return __CONTENT.projects
|
||||
|
||||
|
||||
def get_projects_by_tags(tags: list[str]) -> dict[Any, ContentProject]:
|
||||
project_obj: ContentProject
|
||||
return {
|
||||
project_key: project_value for project_key, project_value in __CONTENT_PROJECTS.items()
|
||||
project_key: project_value for project_key, project_value in __CONTENT.projects.items()
|
||||
if any(tag in project_value.metadata.general.tags for tag in tags)
|
||||
}
|
||||
|
||||
|
||||
def get_tools() -> LockedDict[str, ContentTool]:
|
||||
return __CONTENT.tools
|
||||
|
||||
|
||||
def get_tools_by_tags(tags: list[str]) -> dict[Any, ContentProject]:
|
||||
tool_obj: ContentProject
|
||||
return {
|
||||
tool_key: tool_value for tool_key, tool_value in __CONTENT.tools.items()
|
||||
if any(tag in tool_value.metadata.general.tags for tag in tags)
|
||||
}
|
||||
|
||||
|
||||
def sanitize_input_tags(input_tags: str) -> list[str]:
|
||||
tags: list[str] = input_tags.split(";")
|
||||
for tag in tags:
|
||||
@@ -38,34 +59,50 @@ def sanitize_input_tags(input_tags: str) -> list[str]:
|
||||
return tags
|
||||
|
||||
|
||||
def get_tools() -> LockedDict:
|
||||
return __CONTENT_TOOLS
|
||||
def load_content_items() -> None:
|
||||
global __CONTENT
|
||||
|
||||
__CONTENT = ContentRoot()
|
||||
|
||||
def get_tools_by_tags(tags: list[str]) -> dict[Any, ContentProject]:
|
||||
tool_obj: ContentProject
|
||||
return {
|
||||
tool_key: tool_value for tool_key, tool_value in __CONTENT_TOOLS.items()
|
||||
if any(tag in tool_value.metadata.general.tags for tag in tags)
|
||||
}
|
||||
# Loading applets definition files
|
||||
for applets_file in os.listdir(os.path.join(os.getcwd(), "data/applets")):
|
||||
applets_file_path = os.path.join(os.getcwd(), "data/applets", applets_file)
|
||||
if not os.path.isfile(applets_file_path) or applets_file.startswith("."):
|
||||
continue
|
||||
|
||||
applets_data = yaml.safe_load(open(applets_file_path))
|
||||
if "applets" not in applets_data:
|
||||
print(f"Unable to load '{applets_file_path}' due to missing 'applets' field !")
|
||||
continue
|
||||
|
||||
def reload_content_items() -> None:
|
||||
global __CONTENT_ARTICLES
|
||||
global __CONTENT_PROJECTS
|
||||
global __CONTENT_TOOLS
|
||||
for applet_data in applets_data["applets"]:
|
||||
_applet = ContentApplet(**applet_data)
|
||||
__CONTENT.applets[_applet.id] = _applet
|
||||
|
||||
__CONTENT_ARTICLES = LockedDict()
|
||||
__CONTENT_PROJECTS = LockedDict()
|
||||
__CONTENT_TOOLS = LockedDict()
|
||||
|
||||
for article_folder in os.listdir(os.path.join(os.getcwd(), "data/articles")):
|
||||
# Loading articles definition files
|
||||
"""for article_folder in os.listdir(os.path.join(os.getcwd(), "data/articles")):
|
||||
article_folder_path = os.path.join(os.getcwd(), "data/articles", article_folder)
|
||||
if not os.path.isdir(article_folder_path):
|
||||
continue
|
||||
pass
|
||||
pass"""
|
||||
|
||||
for project_item in os.listdir(os.path.join(os.getcwd(), "data/projects")):
|
||||
# Loading projects definition files
|
||||
for project_file in os.listdir(os.path.join(os.getcwd(), "data/projects")):
|
||||
project_file_path = os.path.join(os.getcwd(), "data/projects", project_file)
|
||||
if not os.path.isfile(project_file_path) or project_file.startswith("."):
|
||||
continue
|
||||
|
||||
projects_data = yaml.safe_load(open(project_file_path))
|
||||
if "projects" not in projects_data:
|
||||
print(f"Unable to load '{project_file_path}' due to missing 'projects' field !")
|
||||
continue
|
||||
|
||||
for project_data in projects_data["projects"]:
|
||||
_project = ContentProject(**project_data)
|
||||
__CONTENT.projects[_project.id] = _project
|
||||
print(_project)
|
||||
|
||||
"""for project_item in os.listdir(os.path.join(os.getcwd(), "data/projects")):
|
||||
project_item_path = os.path.join(os.getcwd(), "data/projects/", project_item)
|
||||
if not os.path.isfile(project_item_path) or project_item.startswith("."):
|
||||
continue
|
||||
@@ -87,33 +124,25 @@ def reload_content_items() -> None:
|
||||
print(f"Loaded project '{project_id}'")
|
||||
except Exception as e:
|
||||
print(f"Unable to load project '{project_id}' due to an exception !")
|
||||
print(e)
|
||||
print(e)"""
|
||||
|
||||
for tool_item in os.listdir(os.path.join(os.getcwd(), "data/tools")):
|
||||
tool_item_path = os.path.join(os.getcwd(), "data/tools", tool_item)
|
||||
if not os.path.isfile(tool_item_path) or tool_item_path.startswith("."):
|
||||
for tools_file in os.listdir(os.path.join(os.getcwd(), "data/tools")):
|
||||
tools_file_path = os.path.join(os.getcwd(), "data/tools", tools_file)
|
||||
if not os.path.isfile(tools_file_path) or tools_file.startswith("."):
|
||||
continue
|
||||
|
||||
tool_id = Path(tool_item_path).stem
|
||||
tool_page_path = os.path.join(os.getcwd(), f"templates/tools/{tool_id}.jinja")
|
||||
|
||||
if not all(os.path.isfile(project_file) for project_file in
|
||||
[tool_item_path, tool_page_path]):
|
||||
print(f"Unable to load tool '{tool_id}' due to missing files !")
|
||||
tools_data = yaml.safe_load(open(tools_file_path))
|
||||
if "tools" not in tools_data:
|
||||
print(f"Unable to load '{tools_file_path}' due to missing 'tools' field !")
|
||||
continue
|
||||
|
||||
tool_data: ContentTool
|
||||
try:
|
||||
raw_tool_data = yaml.safe_load(open(tool_item_path))
|
||||
__CONTENT_TOOLS[tool_id] = ContentTool(
|
||||
id=tool_id,
|
||||
metadata=ContentMetadata(**raw_tool_data["metadata"]),
|
||||
data=ContentToolData(**raw_tool_data["data"]),
|
||||
)
|
||||
print(f"Loaded tool '{tool_id}'")
|
||||
except Exception as e:
|
||||
print(f"Unable to load tool '{tool_id}' due to an exception !")
|
||||
print(e)
|
||||
continue
|
||||
for tool_data in tools_data["tools"]:
|
||||
_tool = ContentTool(**tool_data)
|
||||
__CONTENT.tools[_tool.id] = _tool
|
||||
#print(_tool)
|
||||
|
||||
# FIXME: Check if the required files exist too !
|
||||
# FIXME: Check if the required files exist too !"""
|
||||
|
||||
|
||||
def validate_content_items() -> bool:
|
||||
pass
|
||||
|
@@ -1,6 +1,8 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from locked_dict.locked_dict import LockedDict
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentHeadMetadata:
|
||||
@@ -66,3 +68,49 @@ class ContentMetadata:
|
||||
|
||||
self.general: dict
|
||||
self.general = ContentGeneralMetadata(**self.general)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentResource:
|
||||
scripts: list[str] = field(default_factory=list)
|
||||
stylesheets: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentApplet:
|
||||
id: str
|
||||
resources: ContentResource
|
||||
|
||||
def __post_init__(self):
|
||||
self.resources: dict
|
||||
self.resources = ContentResource(**self.resources)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentTool:
|
||||
# NOTE: could extend `ContentProject`
|
||||
id: str
|
||||
applet_id: str
|
||||
metadata: ContentMetadata
|
||||
|
||||
def __post_init__(self):
|
||||
self.metadata: dict
|
||||
self.metadata = ContentMetadata(**self.metadata)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentProject:
|
||||
id: str
|
||||
metadata: ContentMetadata
|
||||
|
||||
def __post_init__(self):
|
||||
self.metadata: dict
|
||||
self.metadata = ContentMetadata(**self.metadata)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentRoot:
|
||||
applets: LockedDict[str, ContentApplet] = field(default_factory=LockedDict)
|
||||
# articles: list[Con] = field(default_factory=list)
|
||||
projects: LockedDict[str, ContentProject] = field(default_factory=LockedDict)
|
||||
tools: LockedDict[str, ContentTool] = field(default_factory=LockedDict)
|
@@ -1,9 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .metadata import ContentMetadata
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentProject:
|
||||
id: str
|
||||
metadata: ContentMetadata
|
@@ -1,16 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from .metadata import ContentMetadata
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentToolData:
|
||||
scripts: list[str] = field(default_factory=list)
|
||||
stylesheets: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContentTool:
|
||||
id: str
|
||||
metadata: ContentMetadata
|
||||
data: ContentToolData
|
29
website/renderers/applet.py
Normal file
29
website/renderers/applet.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from flask import url_for
|
||||
|
||||
from website.content import ContentApplet
|
||||
|
||||
|
||||
def render_applet_head(applet_data: ContentApplet) -> str:
|
||||
applet_style_html = ""
|
||||
|
||||
for applet_style in applet_data.resources.stylesheets:
|
||||
applet_style_html += ("<link rel='stylesheet' href='" +
|
||||
url_for(
|
||||
"static",
|
||||
filename="/resources/NibblePoker/applets/" + applet_data.id + "/" + applet_style) +
|
||||
"'>")
|
||||
|
||||
return applet_style_html
|
||||
|
||||
|
||||
def render_applet_scripts(applet_data: ContentApplet):
|
||||
applet_script_html = ""
|
||||
|
||||
for applet_script in applet_data.resources.scripts:
|
||||
applet_script_html += ("<script src='" +
|
||||
url_for(
|
||||
"static",
|
||||
filename="/resources/NibblePoker/applets/" + applet_data.id + "/" + applet_script) +
|
||||
"'" + (" type='module'" if applet_script.endswith(".mjs") else "") + "></script>")
|
||||
|
||||
return applet_script_html
|
31
website/renderers/code.py
Normal file
31
website/renderers/code.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import html
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from flask import render_template
|
||||
|
||||
|
||||
def render_code_block(code_lines: list[str], language: Optional[str] = None):
|
||||
_code_lines = list()
|
||||
|
||||
for code_line in code_lines:
|
||||
code_line = html.escape(code_line)
|
||||
code_line = code_line.replace('\t', ' ' * 4)
|
||||
code_line = code_line.replace(' ', ' ')
|
||||
_code_lines.append(code_line)
|
||||
|
||||
return render_template(
|
||||
"elements/code.jinja",
|
||||
code_lines=_code_lines,
|
||||
code_language=language,
|
||||
)
|
||||
|
||||
# return re.sub('>\s*<span', "><span",
|
||||
# re.sub('<br>\s*</code>', "</code>",
|
||||
# render_template(
|
||||
# "elements/code.jinja",
|
||||
# code_lines=_code_lines,
|
||||
# code_language=language,
|
||||
# )
|
||||
# )
|
||||
# )
|
3
website/renderers/file_input.py
Normal file
3
website/renderers/file_input.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
def render_file_input() -> str:
|
||||
return ""
|
@@ -35,3 +35,11 @@ def render_h3(inner_html: str, icon: Optional[str] = None, right_html: Optional[
|
||||
return render_heading(
|
||||
inner_html, 3, icon, right_html, anchor_id, background_class
|
||||
)
|
||||
|
||||
|
||||
def render_h4(inner_html: str, icon: Optional[str] = None, right_html: Optional[str] = None,
|
||||
anchor_id: Optional[str] = None, background_class: str = "bkgd-grid") -> str:
|
||||
return render_heading(
|
||||
inner_html, 4, icon, right_html, anchor_id, background_class
|
||||
)
|
||||
|
||||
|
@@ -0,0 +1,11 @@
|
||||
from typing import Union
|
||||
|
||||
from flask import render_template
|
||||
|
||||
|
||||
def render_list_ul(items: list[Union[str|list]]) -> str:
|
||||
return render_template(
|
||||
"elements/list-ul.jinja",
|
||||
list_items=items,
|
||||
render_list_ul=render_list_ul
|
||||
)
|
||||
|
Reference in New Issue
Block a user