add template + document framework in root README
template/README.md: stripped to a one-liner bootstrap command. README.md: full project docs (structure, add page, add component, build.sh). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
0d5c2fbd35
commit
6796e102e0
16 changed files with 663 additions and 1 deletions
3
template/sources/config.json
Normal file
3
template/sources/config.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"api_url": "http://localhost:8080"
|
||||
}
|
||||
4
template/sources/core/config.js
Normal file
4
template/sources/core/config.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
const response = await fetch('./config.json')
|
||||
if (!response.ok) throw new Error('Failed to load config.json')
|
||||
|
||||
export const config = await response.json()
|
||||
10
template/sources/core/menu.js
Normal file
10
template/sources/core/menu.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Dynamic menu loader.
|
||||
// Fetches data for groups declared with empty children in navigation.json
|
||||
// and injects them into side-nav after it is ready.
|
||||
//
|
||||
// Imported non-blocking from index.html — does not delay routing.
|
||||
//
|
||||
// Example usage:
|
||||
// const nav = document.querySelector('side-nav')
|
||||
// await nav.ready
|
||||
// nav.setGroupChildren('My Group', [{ label: 'Item', href: '#/route', icon: 'icon' }])
|
||||
8
template/sources/core/registry.js
Normal file
8
template/sources/core/registry.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const response = await fetch('./components.json')
|
||||
if (!response.ok) throw new Error('Failed to load components.json')
|
||||
|
||||
const components = await response.json()
|
||||
|
||||
for (const path of components) {
|
||||
await import(`../${path}`)
|
||||
}
|
||||
41
template/sources/core/router.js
Normal file
41
template/sources/core/router.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
const response = await fetch('./pages.json')
|
||||
if (!response.ok) throw new Error('router: failed to load pages.json — run build.sh')
|
||||
const PAGES = await response.json()
|
||||
|
||||
const MODULE_CACHE = new Map()
|
||||
let activeCSS = null
|
||||
|
||||
function currentRoute() {
|
||||
const hash = window.location.hash.replace(/^#\//, '') || PAGES[0]?.route || ''
|
||||
return hash.split('?')[0]
|
||||
}
|
||||
|
||||
async function render(routeName) {
|
||||
const page = PAGES.find(p => p.route === routeName) ?? PAGES[0]
|
||||
const main = document.querySelector('main')
|
||||
if (!main || !page) return
|
||||
|
||||
const htmlRes = await fetch(page.html)
|
||||
if (!htmlRes.ok) throw new Error(`router: failed to load ${page.html}`)
|
||||
main.innerHTML = await htmlRes.text()
|
||||
|
||||
activeCSS?.remove()
|
||||
activeCSS = null
|
||||
if (page.css) {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = page.css
|
||||
document.head.appendChild(link)
|
||||
activeCSS = link
|
||||
}
|
||||
|
||||
if (page.js) {
|
||||
if (!MODULE_CACHE.has(page.js)) {
|
||||
MODULE_CACHE.set(page.js, await import(`../${page.js}`))
|
||||
}
|
||||
MODULE_CACHE.get(page.js)?.init?.(main)
|
||||
}
|
||||
}
|
||||
|
||||
await render(currentRoute())
|
||||
window.addEventListener('hashchange', () => render(currentRoute()))
|
||||
5
template/sources/favicon.svg
Normal file
5
template/sources/favicon.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#1e1e2e"/>
|
||||
<text x="16" y="22" font-family="monospace" font-size="18" font-weight="bold"
|
||||
text-anchor="middle" fill="#89b4fa">S</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
106
template/sources/index.html
Normal file
106
template/sources/index.html
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mon Projet</title>
|
||||
<link rel="icon" type="image/svg+xml" href="./favicon.svg">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
background: #181825;
|
||||
color: #cdd6f4;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 24px;
|
||||
border-bottom: 1px solid #313244;
|
||||
background: #1e1e2e;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #89b4fa;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
header span {
|
||||
font-size: 12px;
|
||||
color: #6c7086;
|
||||
}
|
||||
|
||||
header .spacer { flex: 1; }
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 28px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
main { padding: 16px; }
|
||||
}
|
||||
|
||||
#error {
|
||||
display: none;
|
||||
background: #313244;
|
||||
border: 1px solid #f38ba8;
|
||||
border-radius: 8px;
|
||||
padding: 14px 18px;
|
||||
color: #f38ba8;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Mon Projet</h1>
|
||||
<span>powered by Syn</span>
|
||||
<div class="spacer"></div>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<side-nav></side-nav>
|
||||
<main></main>
|
||||
</div>
|
||||
|
||||
<div id="error"></div>
|
||||
|
||||
<script type="module">
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
const el = document.getElementById('error')
|
||||
if (el) {
|
||||
el.style.display = 'block'
|
||||
el.textContent = `Error: ${e.reason?.message ?? e.reason}`
|
||||
}
|
||||
})
|
||||
|
||||
await import('./core/registry.js')
|
||||
await import('./core/router.js')
|
||||
|
||||
import('./core/menu.js')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
24
template/sources/pages/home/home.css
Normal file
24
template/sources/pages/home/home.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
.home {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.home h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #cdd6f4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.home p {
|
||||
color: #6c7086;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home code {
|
||||
background: #313244;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
color: #89b4fa;
|
||||
}
|
||||
14
template/sources/pages/home/home.js
Normal file
14
template/sources/pages/home/home.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
class HomePage extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<div class="home">
|
||||
<h2>Bienvenue</h2>
|
||||
<p>Modifiez cette page dans <code>sources/pages/home/</code>.</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('home-page', HomePage)
|
||||
|
||||
export function init(_main) {}
|
||||
1
template/sources/pages/home/index.html
Normal file
1
template/sources/pages/home/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<home-page></home-page>
|
||||
5
template/sources/pages/home/manifest.json
Normal file
5
template/sources/pages/home/manifest.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"route": "home",
|
||||
"label": "Home",
|
||||
"icon": "home"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue