Skip to content

Commit 0ed1647

Browse files
website: add map, atlas cli
1 parent 83a48f2 commit 0ed1647

File tree

9 files changed

+331
-21
lines changed

9 files changed

+331
-21
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dev = [
1919
[project.scripts]
2020
sti = "sti:main"
2121
serve = "sti.app:app.run"
22+
atlas = "sti.atlas:main"
2223

2324
[build-system]
2425
requires = ["hatchling"]

src/sti/app.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from io import BytesIO
33
from zlib import crc32
44
from pathlib import Path
5+
from base64 import b64encode
56

67
# dependencies
7-
from flask import Flask, render_template, send_file
8+
from flask import Flask, Response, render_template, send_file
89

910
# local
1011
from .utils import get_icons_from, get_refs_from
@@ -14,6 +15,7 @@
1415
__name__,
1516
static_folder=git_root / "images",
1617
)
18+
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
1719

1820
svg_lib = {
1921
"refs": get_refs_from(git_root / "images/reference"),
@@ -24,10 +26,32 @@
2426
FAVICON = file.read()
2527
FAVETAG = f"{crc32(FAVICON):x}"
2628

29+
@app.template_filter()
30+
def b2s(b: bytes) -> str:
31+
return b.decode("utf8")
32+
2733
@app.route("/favicon.ico")
2834
def favicon():
2935
return send_file(BytesIO(FAVICON), mimetype='image/x-icon', etag=FAVETAG)
3036

37+
@app.route("/atlas.svg")
38+
def svglib():
39+
return Response(
40+
render_template(
41+
"atlas.svg", library=svg_lib["icons"].values()
42+
),
43+
mimetype="image/svg+xml",
44+
)
45+
46+
@app.route("/mxlibrary.xml")
47+
def mxlibrary():
48+
return Response(
49+
render_template(
50+
"mxlibrary.xml", library=svg_lib["icons"].values()
51+
),
52+
mimetype="application/xml",
53+
)
54+
3155
@app.route("/", defaults={"path": "index"})
3256
@app.route("/<string:path>.html")
3357
@app.route("/<string:path>")

src/sti/atlas.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
"""Super Tiny Icons Atlas generator"""
3+
# stdlib
4+
from argparse import ArgumentParser
5+
from base64 import b64encode
6+
from pathlib import Path
7+
import json
8+
import logging
9+
import sys
10+
11+
# dependencies
12+
from jinja2 import Environment, FileSystemLoader
13+
14+
# local
15+
from .utils import Icon
16+
17+
current_dir = Path(__file__).absolute().parent
18+
tpl = Environment(loader=FileSystemLoader(current_dir / "templates"))
19+
20+
class Atlas:
21+
lib: list[Icon] = None
22+
23+
def __init__(self):
24+
self.lib = []
25+
26+
def add(self, path: Path):
27+
self.lib.append(Icon.from_path(path, with_path=False, with_data=True))
28+
return self
29+
30+
def write_drawio(self, fp):
31+
fp.write("<mxlibrary>")
32+
json.dump([
33+
{
34+
"title": entry.label,
35+
"data": f"data:image/svg+xml;base64,{b64encode(entry.data).decode('ascii')}",
36+
"aspect": "fixed",
37+
"w": entry.dimension,
38+
"h": entry.dimension,
39+
} for entry in self.lib
40+
], fp, indent=1, sort_keys=True)
41+
fp.write("</mxlibrary>")
42+
43+
def write_svg(self, fp):
44+
template = tpl.get_template(name="atlas.svg")
45+
fp.write(template.render(library=self.lib))
46+
47+
def parse_args():
48+
"""parse arguments, initialize logging"""
49+
parser = ArgumentParser(description=__doc__.splitlines()[0])
50+
parser.add_argument(
51+
"-q",
52+
"--quiet",
53+
action="store_const",
54+
const=0,
55+
default=2,
56+
dest="verbose",
57+
)
58+
parser.add_argument("-v", "--verbose", action="count")
59+
60+
group = parser.add_argument_group("Configuration")
61+
group.add_argument(
62+
"-f",
63+
"--format",
64+
choices=["drawio", "svg"],
65+
default=None,
66+
required=True,
67+
)
68+
args = parser.parse_args()
69+
loglevel = min(
70+
max(logging.CRITICAL - (args.verbose * 10), logging.DEBUG), logging.CRITICAL
71+
)
72+
logging.basicConfig(
73+
format="%(levelname)s: %(message)s",
74+
level=loglevel,
75+
stream=sys.stderr,
76+
)
77+
return args
78+
79+
def main():
80+
args = parse_args()
81+
atlas = Atlas()
82+
for entry in Path(__file__).parents[1].with_name("images").glob("svg/*.svg"):
83+
atlas.add(entry)
84+
if args.format == "drawio":
85+
atlas.write_drawio(sys.stdout)
86+
if args.format == "svg":
87+
atlas.write_svg(sys.stdout)
88+
89+
if __name__ == "__main__":
90+
main()

src/sti/cli.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
# stdlib
44
from argparse import ArgumentParser
55
from pathlib import Path
6-
from os.path import commonprefix
76
import logging
87
import sys
98

109
# dependencies
1110
from jinja2 import Environment, FileSystemLoader
1211

1312
# local
13+
from .atlas import Atlas
1414
from .utils import get_icons_from, get_refs_from
1515

1616

@@ -60,7 +60,17 @@ def main():
6060
if git_root in out.parents and not out.exists():
6161
out.mkdir(parents=True)
6262

63-
for template_name in ("index", "list", "reference"):
63+
atlas = Atlas()
64+
for entry in Path(__file__).parents[1].with_name("images").glob("svg/*.svg"):
65+
atlas.add(entry)
66+
67+
for writer in filter(lambda _:_.startswith('write_'), dir(atlas)):
68+
with (Path(args.output) / f"atlas.{writer.removeprefix('write_')}").open(
69+
"w", encoding="utf8"
70+
) as out:
71+
getattr(atlas, writer)(out)
72+
73+
for template_name in ("index", "libraries", "list", "reference"):
6474
template = tpl_env.get_template(name=f"{template_name}.html")
6575
with (Path(args.output) / f"{template_name}.html").open(
6676
"w", encoding="utf8"

src/sti/templates/atlas.svg

Lines changed: 9 additions & 0 deletions
Loading

src/sti/templates/base.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>{% block title %}{% endblock %}SuperTinyIcons</title>
7+
<link rel="icon" href="favicon.ico">
78
<style>{% block style %}
89
html {
910
display: grid;
@@ -100,6 +101,7 @@
100101
}
101102
{% endblock %}
102103
</style>
104+
{% block script %}{% endblock %}
103105
</head>
104106
<body{% if pagename %} id="{{ pagename }}"{% endif %}>
105107
<header>
@@ -112,6 +114,7 @@ <h1>Super Tiny Icons</h1>
112114
<a href="index.html">Home</a>
113115
<a href="list.html">What's available so far</a>
114116
<a href="reference.html">Reference</a>
117+
<a href="libraries.html">Libraries</a>
115118
<a href="https://github.com/edent/SuperTinyIcons/">This project on GitHub</a>
116119
</nav>
117120
<main>

src/sti/templates/libraries.html

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{% extends "base.html" %}
2+
3+
<style>{% block style %}
4+
{{ super() }}
5+
h3 img {
6+
height:1em;width:1em;padding-right:1ex;vertical-align:text-bottom;
7+
}
8+
{% endblock %}
9+
</style>
10+
11+
{% block script %}
12+
<script>
13+
var tiles;
14+
fetch("atlas.svg", { cache: "force-cache" })
15+
.then((response) => response.text())
16+
.then((data) => {
17+
let size = Number(data.length).toString().replace(/\B(?=(\d{3})+(?!\d))/g, "\u2009");
18+
const container = document.querySelector(".atlas");
19+
const parser = new DOMParser();
20+
let tiles = parser.parseFromString(data, "image/svg+xml").querySelectorAll("svg svg");
21+
container.querySelector("p").insertAdjacentHTML("afterend", `<p>The <a href="atlas.svg"><b>whole</b> library</a> weight only <var>${size}&nbsp;B</var> (uncompressed) for <var>${tiles.length}</var> icons!</p>`);
22+
let table = container.querySelector("table tbody");
23+
for (let i = 0; i < 10; ++i) {
24+
let tile = tiles[Math.floor(Math.random()*tiles.length)];
25+
let sample = `<img alt="${tile.ariaLabel}" height="32" width="32" src="atlas.svg#${tile.id}">\n`;
26+
table.insertAdjacentHTML("beforeend", `<tr><td><code>${sample.replace(/</g, "&lt;")}</code></td><td>${sample}</td></tr>\n`)
27+
}
28+
});
29+
</script>
30+
{% endblock %}
31+
32+
{% block content %}
33+
<h2>Libraries</h2>
34+
35+
<section class="atlas">
36+
<h3><img alt="" src="atlas.svg#svg">SVG texture atlas (a.k.a. spritesheet)</h3>
37+
<p>A single file containing the icons; this limit the requests to the server and can improve the page load time.</p>
38+
<p>Example:</p>
39+
<table>
40+
<thead>
41+
<tr>
42+
<th>HTML</th>
43+
</tr>
44+
</thead>
45+
<tbody>
46+
<tr>
47+
<td><code>&lt;img alt="Acast" height="32" src="atlas.svg#acast"&gt;</code></td>
48+
<td><img alt="Acast" height="32" width="32" src="atlas.svg#acast"></td>
49+
</tr>
50+
</tbody>
51+
</table>
52+
</section>
53+
54+
<section>
55+
<h3><img alt="" src="atlas.svg#drawio">Draw.io</h3>
56+
<p>Are you using <a href="https://draw.io">Draw.io</a>?</p>
57+
<p>Feeling you are missing some icons for your charts?</p>
58+
<p><a href="atlas.drawio">Download Super Tiny Icons as a graphic library for Draw.io</a>!</p>
59+
<p>File ▸ Open Library from ▸ URL...</p>
60+
</section>
61+
{% endblock %}

src/sti/templates/list.html

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,98 @@
99
align-items: center;
1010
justify-content: center;
1111
}
12-
section div {
13-
text-align: center;
14-
padding: 1.5em 1em;
15-
box-sizing: border-box;
16-
width:128px;
17-
align-self: flex-end;
18-
}
19-
section img {
20-
width:100px;
21-
}
22-
section span {
23-
font-family: monospace;
24-
}
12+
section div {
13+
text-align: center;
14+
padding: 1.5em 1em;
15+
box-sizing: border-box;
16+
width:128px;
17+
align-self: flex-end;
18+
}
19+
section img {
20+
width:100px;
21+
}
22+
section span {
23+
font-family: monospace;
24+
}
25+
26+
#map {
27+
background:#eee;
28+
display:flex;
29+
flex-wrap:wrap;
30+
gap:1px;
31+
}
32+
#map i {
33+
display:inline-block;
34+
padding:2px;
35+
background-color:hsl(var(--a) 100% 50%);
36+
opacity: .25;
37+
}
38+
#map i:hover {
39+
opacity:1;
40+
}
41+
.thumb.selected img {
42+
box-shadow: 0 0 1em #2098D1;
43+
}
2544
{% endblock %}
2645
</style>
46+
47+
{% block script %}<script>
48+
function save(filename, data, mime) {
49+
const blob = new Blob([data], {type: mime || "application/octet-stream"});
50+
if(window.navigator.msSaveOrOpenBlob) {
51+
window.navigator.msSaveBlob(blob, filename);
52+
}
53+
else{
54+
const elem = window.document.createElement('a');
55+
elem.href = window.URL.createObjectURL(blob);
56+
elem.download = filename;
57+
document.body.appendChild(elem);
58+
elem.click();
59+
document.body.removeChild(elem);
60+
}
61+
}
62+
63+
document.addEventListener("DOMContentLoaded", function(){
64+
let tpl='';
65+
document.querySelectorAll('.thumb').forEach(function(e){
66+
let size = parseInt(e.dataset.size);
67+
let percent = size/1024;
68+
let angle = 360*percent;
69+
tpl += '<i style="--s:' + size + '; --p:'+ percent + '; --a:' + (angle) +'" title="'+ e.dataset.name + ': ' + size +' B"></i>';
70+
e.addEventListener('click', function() { e.classList.toggle("selected") }, false);
71+
fetch(e.querySelector("img").src)
72+
.then((response) => response.text())
73+
.then((text) => {
74+
localStorage.setItem(e.querySelector("img").src, text);
75+
});
76+
});
77+
document.querySelector('#map').innerHTML = tpl;
78+
79+
document.querySelector(".download .drawio").addEventListener('click', function() {
80+
let lib = [];
81+
document.querySelectorAll('.thumb.selected').forEach(function (e) {
82+
lib.push({
83+
title: e.querySelector("strong").textContent,
84+
data: "data:image/svg+xml;base64," + btoa(localStorage.getItem(e.querySelector("img").src)),
85+
aspect: "fixed",
86+
h: 32,
87+
w: 32,
88+
});
89+
});
90+
if (lib.length) {save("STILib.xml", "<mxlibrary>" + JSON.stringify(lib) + "</mxlibrary>");}
91+
});
92+
});
93+
</script>
94+
{% endblock %}
95+
2796
{% block content %}
2897
<h2>What's Available so far?</h2>
29-
98+
<aside id="map"></aside>
99+
<div class="download">
100+
<h3>Download selected for</h3>
101+
<button class="drawio">Draw.io</button>
102+
<button class="svg">SVG</button>
103+
</div>
30104
<section>
31105
{% for _, icon in icons | dictsort -%}
32106
<div class="thumb" data-name="{{ icon.name }}" data-size="{{ icon.size }}">

0 commit comments

Comments
 (0)