Modular router db and locale
This commit is contained in:
1
dist/sigpro.db.js
vendored
Normal file
1
dist/sigpro.db.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
var o=async(e,n={},t=null)=>{if(t)t(!0);try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),credentials:"include"});if(!r.ok){let s=await r.text();throw Error(`Error ${r.status}: ${s}`)}return await r.json()}finally{if(t)t(!1)}};export{o as db};
|
||||
2
dist/sigpro.js
vendored
2
dist/sigpro.js
vendored
File diff suppressed because one or more lines are too long
1
dist/sigpro.locale.js
vendored
Normal file
1
dist/sigpro.locale.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
var{$:c}=window.SigPro,s=c("en"),n={},e=(t)=>{for(let o of Object.keys(t)){if(!n[o])n[o]={};Object.assign(n[o],t[o])}},r=(t)=>{if(t&&n[t])s(t)},a=(t)=>{return()=>n[s()]?.[t]??t};export{a as t,r as setLocale,e as addLang};
|
||||
1
dist/sigpro.router.js
vendored
Normal file
1
dist/sigpro.router.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
var{$:p,h:u,watch:m,render:g,isF:y}=window.SigPro,d=()=>window.location.hash.slice(1)||"/",s=p(d());window.addEventListener("hashchange",()=>s(d()));var w=p({}),c=(n)=>{let i=u("div",{class:"router-hook"}),r=null;return m([s],()=>{let l=s(),t=n.find((o)=>{let e=o.path.split("/").filter(Boolean),a=l.split("/").filter(Boolean);return e.length===a.length&&e.every((h,f)=>h[0]===":"||h===a[f])})||n.find((o)=>o.path==="*");if(t){r?.destroy();let o={};t.path.split("/").filter(Boolean).forEach((e,a)=>{if(e[0]===":")o[e.slice(1)]=l.split("/").filter(Boolean)[a]}),w(o),r=g(()=>y(t.component)?t.component(o):t.component),i.replaceChildren(r.container)}}),i.destroy=()=>{r?.destroy()},i};c.params=w;c.to=(n)=>window.location.hash=n.replace(/^#?\/?/,"#/");c.back=()=>window.history.back();c.path=()=>s();export{w as routerParams,c as router};
|
||||
1
dist/sigpro.utils.js
vendored
1
dist/sigpro.utils.js
vendored
@@ -1 +0,0 @@
|
||||
var{$:d,h:m,watch:g,render:x,isF:b}=window.SigPro,l=(t)=>{let e=()=>window.location.hash.slice(1)||"/",o=d(e()),n=()=>o(e());window.addEventListener("hashchange",n);let s=m("div",{class:"router-hook"}),h=null;return g([o],()=>{let f=o(),a=t.find((r)=>{let c=r.path.split("/").filter(Boolean),p=f.split("/").filter(Boolean);return c.length===p.length&&c.every((w,y)=>w[0]===":"||w===p[y])})||t.find((r)=>r.path==="*");if(a){h?.destroy();let r={};a.path.split("/").filter(Boolean).forEach((c,p)=>{if(c[0]===":")r[c.slice(1)]=f.split("/").filter(Boolean)[p]}),l.params(r),h=x(()=>b(a.component)?a.component(r):a.component),s.replaceChildren(h.container)}}),s.destroy=()=>{window.removeEventListener("hashchange",n),h?.destroy()},s};l.params=d({});l.to=(t)=>window.location.hash=t.replace(/^#?\/?/,"#/");l.back=()=>window.history.back();l.path=()=>window.location.hash.replace(/^#/,"")||"/";var k=async(t,e={},o=null)=>{if(o)o(!0);try{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),credentials:"include"});if(!n.ok){let s=await n.text();throw Error(`Error ${n.status}: ${s}`)}return await n.json()}finally{if(o)o(!1)}},u=d("en"),i={},v=(t)=>{for(let e of Object.keys(t)){if(!i[e])i[e]={};Object.assign(i[e],t[e])}},E=(t)=>{if(t&&i[t])u(t)},L=(t)=>{return()=>i[u()]?.[t]??t};export{L as t,E as setLocale,l as router,k as db,v as addLang};
|
||||
@@ -17,7 +17,7 @@ router(routes: Route[]): HTMLElement
|
||||
|
||||
**Returns:** A `div` element (with class `"router-hook"`) that acts as the router outlet. The router automatically destroys the previous view and mounts the matched component when the hash changes.
|
||||
|
||||
> **Availability:** `router` and its helper methods (`router.to`, `router.back`, `router.path`, `router.params`) are exported from the SigPro module. In **ESM** you must import them (`import { router } from 'sigpro/utils'`). In the **IIFE** classic script, they are automatically available on `window`. The examples below assume the functions are already in scope.
|
||||
> You must import them (`import { router } from 'sigpro/router'`). The examples below assume the functions are already in scope.
|
||||
|
||||
---
|
||||
|
||||
@@ -28,6 +28,9 @@ router(routes: Route[]): HTMLElement
|
||||
Place the `router` element where you want the page content to appear. Inside the routes array, define your routes.
|
||||
|
||||
```javascript
|
||||
// remember import router
|
||||
import { router } from 'sigpro/router'
|
||||
|
||||
const Home = () => h1("Home Page");
|
||||
const UserProfile = (params) => h1(`User ID: ${params.id}`);
|
||||
const NotFound = () => h1("404 – Page not found");
|
||||
@@ -189,6 +192,10 @@ mount(App, "#app");
|
||||
| `router.params()` | Reactive signal of the current route parameters. |
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# Vite Plugin: File-based Routing
|
||||
|
||||
The `sigproRouter` plugin for Vite automates route generation by scanning your `pages` directory. It creates a **virtual module** that you can import directly into your code, eliminating the need to maintain a manual routes array.
|
||||
|
||||
1
docs/sigpro.db.js
Normal file
1
docs/sigpro.db.js
Normal file
@@ -0,0 +1 @@
|
||||
var o=async(e,n={},t=null)=>{if(t)t(!0);try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),credentials:"include"});if(!r.ok){let s=await r.text();throw Error(`Error ${r.status}: ${s}`)}return await r.json()}finally{if(t)t(!1)}};export{o as db};
|
||||
File diff suppressed because one or more lines are too long
1
docs/sigpro.locale.js
Normal file
1
docs/sigpro.locale.js
Normal file
@@ -0,0 +1 @@
|
||||
var{$:c}=window.SigPro,s=c("en"),n={},e=(t)=>{for(let o of Object.keys(t)){if(!n[o])n[o]={};Object.assign(n[o],t[o])}},r=(t)=>{if(t&&n[t])s(t)},a=(t)=>{return()=>n[s()]?.[t]??t};export{a as t,r as setLocale,e as addLang};
|
||||
1
docs/sigpro.router.js
Normal file
1
docs/sigpro.router.js
Normal file
@@ -0,0 +1 @@
|
||||
var{$:p,h:u,watch:m,render:g,isF:y}=window.SigPro,d=()=>window.location.hash.slice(1)||"/",s=p(d());window.addEventListener("hashchange",()=>s(d()));var w=p({}),c=(n)=>{let i=u("div",{class:"router-hook"}),r=null;return m([s],()=>{let l=s(),t=n.find((o)=>{let e=o.path.split("/").filter(Boolean),a=l.split("/").filter(Boolean);return e.length===a.length&&e.every((h,f)=>h[0]===":"||h===a[f])})||n.find((o)=>o.path==="*");if(t){r?.destroy();let o={};t.path.split("/").filter(Boolean).forEach((e,a)=>{if(e[0]===":")o[e.slice(1)]=l.split("/").filter(Boolean)[a]}),w(o),r=g(()=>y(t.component)?t.component(o):t.component),i.replaceChildren(r.container)}}),i.destroy=()=>{r?.destroy()},i};c.params=w;c.to=(n)=>window.location.hash=n.replace(/^#?\/?/,"#/");c.back=()=>window.history.back();c.path=()=>s();export{w as routerParams,c as router};
|
||||
40
package.json
40
package.json
@@ -19,10 +19,33 @@
|
||||
"import": "./dist/sigpro.js",
|
||||
"default": "./dist/sigpro.js"
|
||||
},
|
||||
"./utils": "./dist/sigpro.utils.js",
|
||||
"./grid": "./dist/sigpro.grid.js",
|
||||
"./editor": "./dist/sigpro.editor.js",
|
||||
"./vite": "./dist/sigpro.vite.js",
|
||||
"./db": {
|
||||
"types": "./sigpro.d.ts",
|
||||
"import": "./dist/sigpro.db.js",
|
||||
"default": "./dist/sigpro.db.js"
|
||||
},
|
||||
"./router": {
|
||||
"types": "./sigpro.d.ts",
|
||||
"import": "./dist/sigpro.router.js",
|
||||
"default": "./dist/sigpro.router.js"
|
||||
},
|
||||
"./locale": {
|
||||
"types": "./sigpro.d.ts",
|
||||
"import": "./dist/sigpro.locale.js",
|
||||
"default": "./dist/sigpro.locale.js"
|
||||
},
|
||||
"./grid": {
|
||||
"import": "./dist/sigpro.grid.js",
|
||||
"default": "./dist/sigpro.grid.js"
|
||||
},
|
||||
"./editor": {
|
||||
"import": "./dist/sigpro.editor.js",
|
||||
"default": "./dist/sigpro.editor.js"
|
||||
},
|
||||
"./vite": {
|
||||
"import": "./dist/sigpro.vite.js",
|
||||
"default": "./dist/sigpro.vite.js"
|
||||
},
|
||||
"./css": "./dist/sigpro.ui.css",
|
||||
"./ui": {
|
||||
"types": "./sigpro.ui.d.ts",
|
||||
@@ -34,7 +57,8 @@
|
||||
"dist/",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"sigpro.d.ts"
|
||||
"sigpro.d.ts",
|
||||
"sigpro.ui.d.ts"
|
||||
],
|
||||
"homepage": "https://sigpro.natxocc.com/#/",
|
||||
"repository": {
|
||||
@@ -50,14 +74,16 @@
|
||||
"clean": "rm -rf dist",
|
||||
"prebuild": "npm run clean",
|
||||
"build:core": "bun build ./src/sigpro.js --bundle --outfile=./dist/sigpro.js --format=esm --minify",
|
||||
"build:utils": "bun build ./src/sigpro.utils.js --bundle --outfile=./dist/sigpro.utils.js --format=esm --external ./src/sigpro.js --minify",
|
||||
"build:db": "bun build ./src/sigpro.db.js --bundle --outfile=./dist/sigpro.db.js --format=esm --minify",
|
||||
"build:router": "bun build ./src/sigpro.router.js --bundle --outfile=./dist/sigpro.router.js --format=esm --external ./src/sigpro.js --minify",
|
||||
"build:locale": "bun build ./src/sigpro.locale.js --bundle --outfile=./dist/sigpro.locale.js --format=esm --external ./src/sigpro.js --minify",
|
||||
"build:ui": "bun build ./src/sigpro.ui.js --bundle --outfile=./dist/sigpro.ui.js --format=esm --external ./src/sigpro.js --minify",
|
||||
"build:grid": "bun build ./src/sigpro.grid.js --bundle --external sigpro --outfile=./dist/sigpro.grid.js --format=esm --minify",
|
||||
"build:editor": "bun build ./src/sigpro.editor.js --bundle --external sigpro --outfile=./dist/sigpro.editor.js --format=esm --minify",
|
||||
"build:vite": "bun build ./src/sigpro.vite.js --bundle --outfile=./dist/sigpro.vite.js --format=esm --external fs --external path --minify",
|
||||
"build:css": "tailwindcss -i ./src/sigpro.ui.css -o ./dist/sigpro.ui.css --minify --content './src/tailwind' && du -h ./dist/sigpro.ui.css",
|
||||
"build:convert": "bun build ./src/sigpro.convert.js --bundle --outfile=./docs/sigpro.convert.js --format=esm --external ./src/sigpro.js --minify",
|
||||
"build": "bun run build:core && bun run build:utils && bun run build:ui && bun run build:grid && bun run build:editor && bun run build:vite && bun run build:css && bun run build:convert && cp ./dist/* ./docs",
|
||||
"build": "bun run build:core && bun run build:db && bun run build:router && bun run build:locale && bun run build:ui && bun run build:grid && bun run build:editor && bun run build:vite && bun run build:css && bun run build:convert && cp ./dist/* ./docs",
|
||||
"docs": "bun x serve docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
18
src/sigpro.db.js
Normal file
18
src/sigpro.db.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export const db = async (url, data = {}, loading = null) => {
|
||||
if (loading) loading(true);
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Error ${res.status}: ${errorText}`);
|
||||
}
|
||||
return await res.json();
|
||||
} finally {
|
||||
if (loading) loading(false);
|
||||
}
|
||||
};
|
||||
@@ -242,13 +242,13 @@ export const mount = (c, tgt) => {
|
||||
t.replaceChildren(i._cnt); MOUNTED.set(t, i); return i;
|
||||
};
|
||||
|
||||
const htmlTags = "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video";
|
||||
// const htmlTags = "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video";
|
||||
|
||||
export const SigPro = { $, watch, batch, h, fragment, render, mount, when, each, onUnmount, val, isA, isF, isO };
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.SigPro = SigPro;
|
||||
htmlTags.split(" ").forEach(tag => {
|
||||
window[tag] = (props, children) => h(tag, props, children);
|
||||
});
|
||||
// htmlTags.split(" ").forEach(tag => {
|
||||
// window[tag] = (props, children) => h(tag, props, children);
|
||||
// });
|
||||
}
|
||||
21
src/sigpro.locale.js
Normal file
21
src/sigpro.locale.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { $ } = window.SigPro;
|
||||
|
||||
const currentLocale = $("en");
|
||||
const translations = {};
|
||||
|
||||
export const addLang = obj => {
|
||||
for (const locale of Object.keys(obj)) {
|
||||
if (!translations[locale]) translations[locale] = {};
|
||||
Object.assign(translations[locale], obj[locale]);
|
||||
}
|
||||
};
|
||||
|
||||
export const setLocale = locale => {
|
||||
if (locale && translations[locale]) {
|
||||
currentLocale(locale);
|
||||
}
|
||||
};
|
||||
|
||||
export const t = key => {
|
||||
return () => translations[currentLocale()]?.[key] ?? key;
|
||||
};
|
||||
49
src/sigpro.router.js
Normal file
49
src/sigpro.router.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const { $, h, watch, render, isF } = window.SigPro;
|
||||
|
||||
const getHash = () => window.location.hash.slice(1) || "/";
|
||||
const currentPath = $(getHash());
|
||||
|
||||
window.addEventListener("hashchange", () => currentPath(getHash()));
|
||||
|
||||
export const routerParams = $({});
|
||||
|
||||
export const router = routes => {
|
||||
const hook = h("div", { class: "router-hook" });
|
||||
let currentView = null;
|
||||
|
||||
watch([currentPath], () => {
|
||||
const cur = currentPath();
|
||||
|
||||
const route = routes.find(r => {
|
||||
const p1 = r.path.split("/").filter(Boolean);
|
||||
const p2 = cur.split("/").filter(Boolean);
|
||||
return p1.length === p2.length && p1.every((p, i) => p[0] === ":" || p === p2[i]);
|
||||
}) || routes.find(r => r.path === "*");
|
||||
|
||||
if (route) {
|
||||
currentView?.destroy();
|
||||
|
||||
const params = {};
|
||||
route.path.split("/").filter(Boolean).forEach((p, i) => {
|
||||
if (p[0] === ":") params[p.slice(1)] = cur.split("/").filter(Boolean)[i];
|
||||
});
|
||||
|
||||
routerParams(params);
|
||||
|
||||
currentView = render(() => isF(route.component) ? route.component(params) : route.component);
|
||||
|
||||
hook.replaceChildren(currentView.container);
|
||||
}
|
||||
});
|
||||
|
||||
hook.destroy = () => {
|
||||
currentView?.destroy();
|
||||
};
|
||||
|
||||
return hook;
|
||||
};
|
||||
|
||||
router.params = routerParams;
|
||||
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
|
||||
router.back = () => window.history.back();
|
||||
router.path = () => currentPath();
|
||||
@@ -1,90 +0,0 @@
|
||||
/// <reference path="../sigpro.d.ts" />
|
||||
|
||||
const { $, h, watch, render, isF } = window.SigPro;
|
||||
|
||||
// router
|
||||
export const router = routes => {
|
||||
const getHash = () => window.location.hash.slice(1) || "/";
|
||||
const path = $(getHash());
|
||||
const handler = () => path(getHash());
|
||||
window.addEventListener("hashchange", handler);
|
||||
|
||||
const hook = h("div", { class: "router-hook" });
|
||||
let currentView = null;
|
||||
|
||||
watch([path], () => {
|
||||
const cur = path();
|
||||
const route = routes.find(r => {
|
||||
const p1 = r.path.split("/").filter(Boolean);
|
||||
const p2 = cur.split("/").filter(Boolean);
|
||||
return p1.length === p2.length && p1.every((p, i) => p[0] === ":" || p === p2[i]);
|
||||
}) || routes.find(r => r.path === "*");
|
||||
|
||||
if (route) {
|
||||
currentView?.destroy();
|
||||
const params = {};
|
||||
route.path.split("/").filter(Boolean).forEach((p, i) => {
|
||||
if (p[0] === ":") params[p.slice(1)] = cur.split("/").filter(Boolean)[i];
|
||||
});
|
||||
router.params(params);
|
||||
currentView = render(() => isF(route.component) ? route.component(params) : route.component);
|
||||
hook.replaceChildren(currentView.container);
|
||||
}
|
||||
});
|
||||
|
||||
hook.destroy = () => {
|
||||
window.removeEventListener("hashchange", handler);
|
||||
currentView?.destroy();
|
||||
};
|
||||
|
||||
return hook;
|
||||
};
|
||||
|
||||
router.params = $({});
|
||||
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
|
||||
router.back = () => window.history.back();
|
||||
router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
||||
|
||||
// db
|
||||
export const db = async (url, data = {}, loading = null) => {
|
||||
if (loading) loading(true);
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Error ${res.status}: ${errorText}`);
|
||||
}
|
||||
return await res.json();
|
||||
} finally {
|
||||
if (loading) loading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const currentLocale = $('en');
|
||||
|
||||
const translations = {};
|
||||
|
||||
// addLang
|
||||
export const addLang = (obj) => {
|
||||
for (const locale of Object.keys(obj)) {
|
||||
if (!translations[locale]) translations[locale] = {};
|
||||
Object.assign(translations[locale], obj[locale]);
|
||||
}
|
||||
};
|
||||
|
||||
// setLocale
|
||||
export const setLocale = (locale) => {
|
||||
if (locale && translations[locale]) {
|
||||
currentLocale(locale);
|
||||
}
|
||||
};
|
||||
|
||||
// t
|
||||
export const t = (key) => {
|
||||
return () => translations[currentLocale()]?.[key] ?? key;
|
||||
};
|
||||
Reference in New Issue
Block a user