Move SVGs into the frontend build pipeline

That way it can be used in the VueJS frontend directly
This commit is contained in:
Eli Ribble 2026-03-21 21:27:50 +00:00
parent 9b8c079d79
commit 1bd0adbc50
No known key found for this signature in database
20 changed files with 107 additions and 37 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ stadia/cmd/reverse-geocode/reverse-geocode
stadia/cmd/structured-geocode/structured-geocode
static/gen/
temp/
ts/gen

View file

@ -84,6 +84,16 @@ This uses [goose](https://github.com/pressly/goose). You can use the goose comma
> GOOSE_DRIVER=postgres GOOSE_DBSTRING="dbname=nidus-sync sslmode=disable" goose up
```
### svg icons
These icons are generated as part of the build system. You can generate them manually with:
```
pnpm generate-icons
```
This will produce an scss file at `ts/gen/custom-icons.scss`
### typescript
In order to work on the TypeScript code you'll need to install the dependencies locally in your dev environment:

58
generate-icons.js Normal file
View file

@ -0,0 +1,58 @@
// generate-icons.js
import fs from "fs";
import path from "path";
const svgDir = "./svg";
const outputFile = "./ts/gen/custom-icons.scss";
const outputDir = path.dirname(outputFile);
function svgToDataUri(svgContent) {
// Remove unnecessary attributes first
svgContent = svgContent
.replace(/\s+id="[^"]*"/g, "")
.replace(/\s+id='[^']*'/g, "")
.replace(/\s+data-name="[^"]*"/g, "")
.replace(/\s+data-name='[^']*'/g, "");
// Encode for data URI
return svgContent
.replace(/%/g, "%25")
.replace(/</g, "%3C")
.replace(/>/g, "%3E")
.replace(/#/g, "%23")
.replace(/"/g, "'") // Use single quotes in SVG
.replace(/'/g, "%27") // Then encode them
.replace(/\s+/g, " ") // Collapse whitespace
.trim();
}
function generateIconStyles() {
const svgFiles = fs.readdirSync(svgDir).filter((f) => f.endsWith(".svg"));
let scss = `// Auto-generated custom icons\n\n`;
scss += `.bi-custom {
display: inline-block;
width: 1em;
height: 1em;
vertical-align: -0.125em;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}\n\n`;
svgFiles.forEach((file) => {
const iconName = path.basename(file, ".svg");
const svgContent = fs.readFileSync(path.join(svgDir, file), "utf-8");
const dataUri = svgToDataUri(svgContent);
scss += `.bi-${iconName} {
@extend .bi-custom;
background-image: url("data:image/svg+xml,${dataUri}");
}\n\n`;
});
fs.writeFileSync(outputFile, scss);
console.log(`Generated ${svgFiles.length} icon styles`);
}
generateIconStyles();

View file

@ -40,32 +40,6 @@ func addSupportingTemplates(sourceFS fs.FS, t *template.Template) error {
}
return nil
}
func addSVGTemplates(filesystem fs.FS, t *template.Template) error {
err := fs.WalkDir(filesystem, "svg", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
content, err := fs.ReadFile(filesystem, path)
if err != nil {
return fmt.Errorf("Failed to read svg '%s' from filesystem: %w", path, err)
}
svg_name := removeLeadingDir(path)
svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content))
svg_t, err := template.New(svg_name).Parse(svg_template)
if err != nil {
return fmt.Errorf("Failed to parse svg '%s' from embedded filesystem: %v", path, err)
}
_, err = t.AddParseTree(svg_t.Name(), svg_t.Tree)
if err != nil {
return fmt.Errorf("Failed to add svg '%s' to embedded template: %v", path, err)
}
//log.Debug().Str("name", svg_name).Msg("add svg template")
return nil
})
//log.Debug().Msg("Done adding SVG templates")
return err
}
func parseTemplate(sourceFS fs.FS, template_name string) (*template.Template, error) {
t, err := parseTemplateFile(sourceFS, template_name)
if err != nil {
@ -75,10 +49,6 @@ func parseTemplate(sourceFS fs.FS, template_name string) (*template.Template, er
if err != nil {
return nil, fmt.Errorf("Failed to add supporting templates: %w", err)
}
err = addSVGTemplates(sourceFS, t)
if err != nil {
return nil, fmt.Errorf("Failed to add supporting svg templates: %w", err)
}
return t, nil
}
func parseTemplateFile(sourceFS fs.FS, filename string) (*template.Template, error) {

View file

@ -20,6 +20,7 @@
"scripts": {
"build": "node build.js",
"build:prod": "node build.js --minify",
"generate-icons": "node generate-icons.js",
"watch": "node build.js --watch"
}
}

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -1,11 +1,39 @@
<template>
<aside class="sidebar">
<h2>My App</h2>
<nav>
<NavigationLink to="/" label="Home" />
<NavigationLink to="/about" label="About" />
</nav>
</aside>
<div id="sidebar" x-data="$store.user">
<div class="sidebar-header">
<div class="logo-container">
<img class="logo" src="/static/img/nidus-logo-256-transparent.png" />
</div>
</div>
<button id="sidebarToggle" class="btn btn-sm p-0">
<i id="sidebarToggleIcon" class="bi bi-chevron-left"></i>
</button>
<ul class="sidebar-menu">
<li>
<a
href="/root"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="Home"
>
<div class="menu-icon"><i class="bi bi-house"></i></div>
<span class="menu-text ms-2">Home</span>
</a>
</li>
<li>
<a
href="/intelligence"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="Intelligence"
>
<div class="menu-icon"><i class="bi bi-brain"></i></div>
<span class="menu-text ms-2">Intelligence</span>
</a>
</li>
</ul>
</div>
</template>
<script setup lang="ts">

View file

@ -9,6 +9,8 @@ import "maplibre-gl/dist/maplibre-gl.css";
import "bootstrap-icons/font/bootstrap-icons.css";
// Import Bootstrap SCSS
import "./style/style.scss";
// Import custom icons
import "./gen/custom-icons.scss";
// Import Bootstrap JavaScript and make it available globally
import * as bootstrap from "bootstrap";