Move SVGs into the frontend build pipeline
That way it can be used in the VueJS frontend directly
1
.gitignore
vendored
|
|
@ -12,3 +12,4 @@ stadia/cmd/reverse-geocode/reverse-geocode
|
|||
stadia/cmd/structured-geocode/structured-geocode
|
||||
static/gen/
|
||||
temp/
|
||||
ts/gen
|
||||
|
|
|
|||
10
README.md
|
|
@ -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
|
|
@ -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();
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||