-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from lorenzofox3/examples__spa
add first module of spa app example
- Loading branch information
Showing
61 changed files
with
2,706 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import { define } from '@cofn/core'; | ||
import { UIIcon } from './components/ui-icon.component.js'; | ||
import { PageLink } from './router/page-link.component.js'; | ||
import { PageOutlet } from './router/page-outlet.component.js'; | ||
import { navigationEvents } from './router/router.js'; | ||
import { createAnimationsService } from './utils/animations.service.js'; | ||
import { createStorageService } from './utils/storage.service.js'; | ||
import { | ||
createPreferencesService, | ||
motionSettings, | ||
preferencesEvents, | ||
themeSettings, | ||
} from './users/preferences.service.js'; | ||
import { querySelector } from './utils/dom.js'; | ||
import { compose } from './utils/functions.js'; | ||
import { mapValues } from './utils/objects.js'; | ||
import { UiLabelComponent } from './components/ui-label.component.js'; | ||
import { notificationsService } from './utils/notifications.service.js'; | ||
import { UIAlert } from './components/ui-alert.component.js'; | ||
|
||
const togglePreferences = ({ motion, theme }) => { | ||
const classList = querySelector('body').classList; | ||
classList.toggle('dark', theme === themeSettings.dark); | ||
classList.toggle('motion-reduced', motion === motionSettings.reduced); | ||
}; | ||
|
||
export const createApp = ({ router }) => { | ||
const storageService = createStorageService(); | ||
const preferencesService = createPreferencesService({ | ||
storageService, | ||
}); | ||
const animationService = createAnimationsService({ | ||
preferencesService, | ||
}); | ||
|
||
preferencesService.on( | ||
preferencesEvents.PREFERENCES_CHANGED, | ||
compose([ | ||
togglePreferences, | ||
mapValues(({ computed }) => computed), | ||
preferencesService.getState, | ||
]), | ||
); | ||
|
||
const root = { | ||
animationService, | ||
router, | ||
storageService, | ||
preferencesService, | ||
notificationsService, | ||
}; | ||
const withRoot = (comp) => | ||
function* (deps) { | ||
yield* comp({ | ||
...root, | ||
...deps, | ||
}); | ||
}; | ||
|
||
const _define = (tag, comp, ...rest) => define(tag, withRoot(comp), ...rest); | ||
|
||
_define('ui-icon', UIIcon, { | ||
shadow: { mode: 'open' }, | ||
observedAttributes: ['name'], | ||
}); | ||
_define('ui-label', UiLabelComponent, { | ||
extends: 'label', | ||
}); | ||
_define('ui-page-link', PageLink, { | ||
extends: 'a', | ||
}); | ||
_define('ui-page-outlet', PageOutlet); | ||
_define('ui-alert', UIAlert, { | ||
shadow: { | ||
mode: 'open', | ||
}, | ||
}); | ||
|
||
const usePageLoader = | ||
({ pagePath }) => | ||
async (ctx, next) => { | ||
const module = await import(pagePath); | ||
const page = await module.loadPage({ | ||
state: ctx.state, | ||
...root, | ||
define: _define, | ||
}); | ||
router.emit({ | ||
type: navigationEvents.PAGE_LOADED, | ||
detail: { page }, | ||
}); | ||
return next(); | ||
}; | ||
|
||
router | ||
.addRoute({ pattern: 'me' }, [ | ||
usePageLoader({ pagePath: '/users/me.page.js' }), | ||
]) | ||
.addRoute({ pattern: 'dashboard' }, [ | ||
usePageLoader({ pagePath: '/not-available.page.js' }), | ||
]) | ||
.addRoute({ pattern: 'products' }, [ | ||
usePageLoader({ pagePath: '/products/list/product-list.page.js' }), | ||
]) | ||
.addRoute({ pattern: 'products/new' }, [ | ||
usePageLoader({ pagePath: '/products/new/new-product.page.js' }), | ||
]) | ||
.addRoute({ pattern: 'products/:product-sku' }, [ | ||
usePageLoader({ pagePath: '/products/edit/edit-product.page.js' }), | ||
]) | ||
.addRoute({ pattern: 'sales' }, [ | ||
usePageLoader({ pagePath: '/not-available.page.js' }), | ||
]) | ||
.notFound(() => { | ||
router.redirect('/products'); | ||
}); | ||
|
||
// trigger initial preference state | ||
preferencesService.emit({ | ||
type: preferencesEvents.PREFERENCES_CHANGED, | ||
}); | ||
|
||
return { | ||
start() { | ||
router.redirect(location.pathname + location.search + location.hash); | ||
}, | ||
}; | ||
}; | ||
|
||
const useLogger = (ctx, next) => { | ||
console.debug(`loading route: ${ctx.state.navigation?.URL}`); | ||
return next(); | ||
}; |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { createElement, createTextNode } from '../utils/dom.js'; | ||
import { notificationsEvents } from '../utils/notifications.service.js'; | ||
|
||
const template = createElement('template'); | ||
template.innerHTML = `<style> | ||
:host { | ||
display: grid; | ||
grid-template-columns: auto 1fr; | ||
align-items: center; | ||
gap: var(--_offest , 1em); | ||
font: inherit; | ||
} | ||
ui-icon{ | ||
width: 1rem; | ||
height: 1rem; | ||
} | ||
p { | ||
margin: 0; | ||
} | ||
</style> | ||
<ui-icon name="exclamation-octagon"></ui-icon><p><slot></slot></p> | ||
`; | ||
|
||
const connectToNotifications = (comp) => | ||
function* ({ notificationsService, $signal, $host, ...rest }) { | ||
notificationsService.on( | ||
notificationsEvents.messagePublished, | ||
({ detail }) => { | ||
if (detail.level === 'error') { | ||
$host.render({ notification: detail.payload }); | ||
} | ||
}, | ||
{ signal: $signal }, | ||
); | ||
|
||
yield* comp({ | ||
$host, | ||
$signal, | ||
...rest, | ||
}); | ||
}; | ||
export const UIAlert = connectToNotifications(function* ({ | ||
$host, | ||
$root, | ||
$signal: signal, | ||
}) { | ||
const duration = $host.hasAttribute('duration') | ||
? Number($host.getAttribute('duration')) | ||
: 4_000; | ||
const dismiss = () => $host.replaceChildren(); | ||
|
||
$root.appendChild(template.content.cloneNode(true)); | ||
$host.addEventListener('click', dismiss, { signal }); | ||
$host.setAttribute('role', 'alert'); | ||
|
||
while (true) { | ||
const { notification } = yield; | ||
if (!$host.hasChildNodes() && notification?.message) { | ||
$host.replaceChildren(createTextNode(notification.message)); | ||
setTimeout(dismiss, duration, { signal }); | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const template = document.createElement('template'); | ||
template.innerHTML = ` | ||
<style> | ||
:host { | ||
display: inline-block; | ||
} | ||
svg { | ||
width: 100%; | ||
height: 100%; | ||
fill: currentColor; | ||
} | ||
</style> | ||
<svg> | ||
<use id="use" xlink:href=""></use> | ||
</svg> | ||
`; | ||
|
||
const spriteURL = `/node_modules/bootstrap-icons/bootstrap-icons.svg`; | ||
|
||
export const UIIcon = function* ({ $host, $root }) { | ||
$root.replaceChildren(template.content.cloneNode(true)); | ||
$root.querySelector('svg').setAttribute('aria-hidden', 'true'); | ||
while (true) { | ||
const iconName = $host.getAttribute('name'); | ||
$root | ||
.getElementById('use') | ||
.setAttribute('xlink:href', `${spriteURL}#${iconName}`); | ||
yield; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { createRange, createTextNode } from '../utils/dom.js'; | ||
|
||
const errorTemplate = document.createElement('template'); | ||
errorTemplate.innerHTML = `<span class="input-error" aria-live="polite"><ui-icon name="exclamation-octagon"></ui-icon></span>`; | ||
|
||
export const UiLabelComponent = function* ({ $host, $signal: signal }) { | ||
const { control } = $host; | ||
$host.append(errorTemplate.content.cloneNode(true)); | ||
const textRange = createRange(); | ||
const inputError = $host.querySelector('.input-error'); | ||
const iconEl = $host.querySelector('.input-error > ui-icon'); | ||
textRange.setStartAfter(iconEl); | ||
textRange.setEndAfter(iconEl); | ||
control.addEventListener( | ||
'invalid', | ||
(ev) => { | ||
if (textRange.collapsed) { | ||
textRange.insertNode(createTextNode(control.validationMessage)); | ||
inputError.classList.toggle('active'); | ||
control.addEventListener( | ||
'input', | ||
() => { | ||
inputError.classList.toggle('active'); | ||
control.setCustomValidity(''); | ||
textRange.deleteContents(); | ||
}, | ||
{ | ||
once: true, | ||
signal, | ||
}, | ||
); | ||
} | ||
}, | ||
{ | ||
signal, | ||
}, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const APIRootURL = 'http://localhost:5173/api/'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>Restaurant Cashier</title> | ||
<base href="/"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<script src="https://unpkg.com/@ungap/[email protected]/es.js"></script> | ||
<link href="theme/main.css" rel="stylesheet" type="text/css" /> | ||
</head> | ||
<body> | ||
<header id="main-header" class="surface boxed"> | ||
<div class="wrapper"> | ||
<div id="logo"> | ||
<svg xmlns="http://www.w3.org/2000/svg" fill="orangered" viewBox="0 0 16 16"> | ||
<path | ||
d="m15.734 6.1-.022-.058L13.534.358a.568.568 0 0 0-.563-.356.583.583 0 0 0-.328.122.582.582 0 0 0-.193.294l-1.47 4.499H5.025l-1.47-4.5A.572.572 0 0 0 2.47.358L.289 6.04l-.022.057A4.044 4.044 0 0 0 1.61 10.77l.007.006.02.014 3.318 2.485 1.64 1.242 1 .755a.673.673 0 0 0 .814 0l1-.755 1.64-1.242 3.338-2.5.009-.007a4.046 4.046 0 0 0 1.34-4.668Z" /> | ||
</svg> | ||
</div> | ||
<nav id="main-nav"> | ||
<ul> | ||
<li><a is="ui-page-link" href="/me"> | ||
<ui-icon name="person-fill"></ui-icon> | ||
<span>me</span></a></li> | ||
<li><a href="/dashboard" is="ui-page-link"> | ||
<ui-icon name="bar-chart-fill"></ui-icon> | ||
<span>dashboard</span></a></li> | ||
<li><a is="ui-page-link" href="/products"> | ||
<ui-icon name="tag-fill"></ui-icon> | ||
<span>products</span></a></li> | ||
<li><a href="/sales" is="ui-page-link"> | ||
<ui-icon name="cash-coin"></ui-icon> | ||
<span>sales</span></a></li> | ||
</ul> | ||
<!-- <div>--> | ||
<!-- <a href="./"><ui-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">--> | ||
<!-- <path fill-rule="evenodd" d="M15.528 2.973a.75.75 0 0 1 .472.696v8.662a.75.75 0 0 1-.472.696l-7.25 2.9a.75.75 0 0 1-.557 0l-7.25-2.9A.75.75 0 0 1 0 12.331V3.669a.75.75 0 0 1 .471-.696L7.443.184l.01-.003.268-.108a.75.75 0 0 1 .558 0l.269.108.01.003 6.97 2.789ZM10.404 2 4.25 4.461 1.846 3.5 1 3.839v.4l6.5 2.6v7.922l.5.2.5-.2V6.84l6.5-2.6v-.4l-.846-.339L8 5.961 5.596 5l6.154-2.461z"/>--> | ||
<!-- </svg></ui-icon><span>carts</span></a>--> | ||
<!-- <a href="./"><ui-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">--> | ||
<!-- <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>--> | ||
<!-- </svg></ui-icon>new cart</a>--> | ||
<!-- </div>--> | ||
</nav> | ||
</div> | ||
</header> | ||
<main class="wrapper"> | ||
<ui-page-outlet></ui-page-outlet> | ||
</main> | ||
<ui-alert duration="5000"></ui-alert> | ||
<script type="module" src="./index.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { createApp } from './app.js'; | ||
import { defaultRouter } from './router/index.js'; | ||
|
||
const app = createApp({ router: defaultRouter }); | ||
|
||
app.start(); | ||
|
||
// service worker | ||
const registerServiceWorker = async () => { | ||
if ('serviceWorker' in navigator) { | ||
try { | ||
const registration = await navigator.serviceWorker.register( | ||
'./service-worker.js', | ||
{ type: 'module' }, | ||
); | ||
if (registration.installing) { | ||
console.log('Service worker installing'); | ||
} else if (registration.waiting) { | ||
console.log('Service worker installed'); | ||
} else if (registration.active) { | ||
console.log('Service worker active'); | ||
} | ||
} catch (error) { | ||
console.error(`Registration failed with ${error}`); | ||
} | ||
} | ||
}; | ||
|
||
registerServiceWorker(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { createElement } from './utils/dom.js'; | ||
|
||
export const loadPage = () => { | ||
const p = createElement('p'); | ||
p.textContent = 'not yet implemented'; | ||
return p; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"description": "", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"nanoid": "^5.0.4", | ||
"typescript": "^5.2.2", | ||
"vite": "^4.5.0" | ||
}, | ||
"dependencies": { | ||
"@cofn/controllers": "workspace:*", | ||
"@cofn/core": "workspace:*", | ||
"@cofn/view": "workspace:*", | ||
"bootstrap-icons": "^1.11.2" | ||
} | ||
} |
Oops, something went wrong.