typescript(og-website): basic projects page
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
4b9d6c80ad
commit
79fea699a7
|
@ -3,8 +3,9 @@ import { withCss } from '../helpers/withCss'
|
||||||
import { Nav } from './Nav'
|
import { Nav } from './Nav'
|
||||||
import { buttons } from '../constants/navButtons'
|
import { buttons } from '../constants/navButtons'
|
||||||
import { LayoutOptions } from '../types/LayoutOptions'
|
import { LayoutOptions } from '../types/LayoutOptions'
|
||||||
|
import { ServiceWorker } from './ServiceWorker'
|
||||||
|
|
||||||
export const Layout = ({ title, body }: LayoutOptions) => html`
|
export const Layout = ({ title, body, url }: LayoutOptions) => html`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -14,8 +15,10 @@ export const Layout = ({ title, body }: LayoutOptions) => html`
|
||||||
${withCss('layout', 'config')}
|
${withCss('layout', 'config')}
|
||||||
</head>
|
</head>
|
||||||
<body class="background">
|
<body class="background">
|
||||||
${Nav(buttons)}
|
${Nav(buttons, url)}
|
||||||
<div id="page-content">${body}</div>
|
<div id="page-content">${body}</div>
|
||||||
|
|
||||||
|
${ServiceWorker('/static/js/service-worker.js')}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
|
@ -2,17 +2,23 @@ import { TemplateResult, html } from '@popeindustries/lit-html-server'
|
||||||
import { withCss } from '../helpers/withCss'
|
import { withCss } from '../helpers/withCss'
|
||||||
import { UrlConfig } from '../types/UrlConfig'
|
import { UrlConfig } from '../types/UrlConfig'
|
||||||
|
|
||||||
export const NavButtons = (config: UrlConfig) => html`
|
export const NavButtons = (config: UrlConfig, url: string) => {
|
||||||
<a class="nav-button" href=${config.url}>${config.name}</a>
|
const className = ['nav-button', config.url === url && 'glow']
|
||||||
`
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
export const Nav = (buttons: UrlConfig[]) =>
|
return html`
|
||||||
|
<a class=${className} href=${config.url}>${config.name}</a>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Nav = (buttons: UrlConfig[], url: string) =>
|
||||||
html`
|
html`
|
||||||
${withCss('nav')}
|
${withCss('nav')}
|
||||||
|
|
||||||
<div id="nav">
|
<div id="nav">
|
||||||
<div id="nav-buttons">
|
<div id="nav-buttons">
|
||||||
${buttons.map(NavButtons)}
|
${buttons.map(button => NavButtons(button, url))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
|
@ -8,7 +8,8 @@ export const Project = (project: ProjectConfig) => {
|
||||||
return html`
|
return html`
|
||||||
<div class="project">
|
<div class="project">
|
||||||
<div class="project-thumbail background" style=${style}></div>
|
<div class="project-thumbail background" style=${style}></div>
|
||||||
<div class="project-icons">
|
<div class="project-name">${project.name}</div>
|
||||||
|
<div class="project-links">
|
||||||
<a class="project-source" href=${project.source}>Source</a>
|
<a class="project-source" href=${project.source}>Source</a>
|
||||||
<a class="project-demo" href=${project.demo}>Demo</a>
|
<a class="project-demo" href=${project.demo}>Demo</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
11
typescript/og-website/src/components/ServiceWorker.ts
Normal file
11
typescript/og-website/src/components/ServiceWorker.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { html } from '@popeindustries/lit-html-server'
|
||||||
|
|
||||||
|
export const ServiceWorker = (url: string) => html`
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('${url}')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
`
|
|
@ -1,16 +1,22 @@
|
||||||
import { UrlConfig } from '../types/UrlConfig'
|
import { UrlConfig } from '../types/UrlConfig'
|
||||||
|
import { Home } from '../components/Home'
|
||||||
|
import { Projects } from '../components/Projects'
|
||||||
|
import { html } from '@popeindustries/lit-html-server'
|
||||||
|
|
||||||
export const buttons: UrlConfig[] = [
|
export const buttons: UrlConfig[] = [
|
||||||
{
|
{
|
||||||
url: '/',
|
url: '/',
|
||||||
name: 'Home'
|
name: 'Home',
|
||||||
|
component: Home
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/projects',
|
url: '/projects',
|
||||||
name: 'Projects'
|
name: 'Projects',
|
||||||
|
component: Projects
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/blog',
|
url: '/blog',
|
||||||
name: 'Blog'
|
name: 'Blog',
|
||||||
|
component: () => html``
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { Response } from 'express'
|
||||||
export const createComponentRenderer = (
|
export const createComponentRenderer = (
|
||||||
layout: (options: LayoutOptions) => TemplateResult,
|
layout: (options: LayoutOptions) => TemplateResult,
|
||||||
name: string
|
name: string
|
||||||
) => (res: Response, { body, title }: LayoutOptions) => {
|
) => (res: Response, { body, title, url }: LayoutOptions) => {
|
||||||
renderToStream(
|
renderToStream(
|
||||||
layout({
|
layout({
|
||||||
body,
|
body,
|
||||||
|
url,
|
||||||
title: `${name} | ${title}`
|
title: `${name} | ${title}`
|
||||||
})
|
})
|
||||||
).pipe(res)
|
).pipe(res)
|
||||||
|
|
|
@ -2,8 +2,7 @@ import express from 'express'
|
||||||
import { Layout } from './components/Layout'
|
import { Layout } from './components/Layout'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { createPageMiddlewareFactory } from './middleware/servePage'
|
import { createPageMiddlewareFactory } from './middleware/servePage'
|
||||||
import { Home } from './components/Home'
|
import { buttons } from './constants/navButtons'
|
||||||
import { Projects } from './components/Projects'
|
|
||||||
|
|
||||||
const port = process.env.PORT || 8080
|
const port = process.env.PORT || 8080
|
||||||
const app = express()
|
const app = express()
|
||||||
|
@ -12,21 +11,16 @@ const renderComponent = createPageMiddlewareFactory(Layout, 'Matei Adriel')
|
||||||
|
|
||||||
app.use('/static', express.static(resolve(__dirname, 'static')))
|
app.use('/static', express.static(resolve(__dirname, 'static')))
|
||||||
|
|
||||||
app.get(
|
for (const button of buttons) {
|
||||||
'/',
|
app.get(
|
||||||
|
button.url,
|
||||||
renderComponent({
|
renderComponent({
|
||||||
body: Home(),
|
body: button.component(),
|
||||||
title: 'Home'
|
title: button.name,
|
||||||
|
url: button.url
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
}
|
||||||
app.get(
|
|
||||||
'/projects',
|
|
||||||
renderComponent({
|
|
||||||
body: Projects(),
|
|
||||||
title: 'Projects'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Listening on port ${port}`)
|
console.log(`Listening on port ${port}`)
|
||||||
|
|
|
@ -6,4 +6,8 @@
|
||||||
--on-bg: white;
|
--on-bg: white;
|
||||||
--title-font: 'Permanent Marker', cursive;
|
--title-font: 'Permanent Marker', cursive;
|
||||||
--home-bg: url(https://cdn.discordapp.com/attachments/485859146558865408/635409620537442325/Wallpaper_space_galaxy_planet_4k_Space_8652415352.jpg);
|
--home-bg: url(https://cdn.discordapp.com/attachments/485859146558865408/635409620537442325/Wallpaper_space_galaxy_planet_4k_Space_8652415352.jpg);
|
||||||
|
--glow-color-1: white;
|
||||||
|
--glow-color-2: cyan;
|
||||||
|
--glow-color-3: blue;
|
||||||
|
--glow-duration: 2s;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
div#nav {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#home-title {
|
div#home-title {
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
div#nav {
|
div#nav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--spacing, 1rem);
|
padding: var(--spacing, 1rem);
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#nav-buttons {
|
div#nav-buttons {
|
||||||
|
@ -11,3 +12,8 @@ a.nav-button {
|
||||||
margin: var(--spacing, 1rem);
|
margin: var(--spacing, 1rem);
|
||||||
font-family: var(--title-font);
|
font-family: var(--title-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.nav-button:hover {
|
||||||
|
animation: glow var(--glow-duration, 1s) ease-in-out infinite alternate;
|
||||||
|
--glow-color-3: red;
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,38 @@
|
||||||
div.project-thumbail {
|
div.project-thumbail {
|
||||||
width: 100px;
|
width: 200px;
|
||||||
height: 100px;
|
height: 200px;
|
||||||
display: block;
|
display: block;
|
||||||
margin: var(--spacing, 1rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.project {
|
div.project {
|
||||||
background-color: var(--bg-raised, black);
|
background-color: var(--bg-raised, black);
|
||||||
margin: var(--spacing, 1rem);
|
margin: var(--spacing, 1rem);
|
||||||
|
border-radius: var(--spacing, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.project > div {
|
||||||
|
margin: var(--spacing, 1rem);
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.project-name {
|
||||||
|
border-bottom: 1px solid #777777;
|
||||||
|
padding-bottom: var(--spacing, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.project-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#projects {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-family: var(--title-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.project-links *:hover {
|
||||||
|
animation: glow var(--glow-duration, 1s) ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,3 +15,28 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glow {
|
||||||
|
animation: glow var(--glow-duration, 1s) ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
from {
|
||||||
|
text-shadow: 0 0 10px var(--glow-color-1, white),
|
||||||
|
0 0 20px var(--glow-color-1, white),
|
||||||
|
0 0 30px var(--glow-color-2, white),
|
||||||
|
0 0 40px var(--glow-color-2, white),
|
||||||
|
0 0 50px var(--glow-color-2, white),
|
||||||
|
0 0 60px var(--glow-color-2, white),
|
||||||
|
0 0 70px var(--glow-color-2, white);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
text-shadow: 0 0 20px var(--glow-color-1, white),
|
||||||
|
0 0 30px var(--glow-color-3, white),
|
||||||
|
0 0 40px var(--glow-color-3, white),
|
||||||
|
0 0 50px var(--glow-color-3, white),
|
||||||
|
0 0 60px var(--glow-color-3, white),
|
||||||
|
0 0 70px var(--glow-color-3, white),
|
||||||
|
0 0 80px var(--glow-color-3, white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
45
typescript/og-website/src/static/js/service-worker.js
Normal file
45
typescript/og-website/src/static/js/service-worker.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
importScripts(
|
||||||
|
'https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (workbox) {
|
||||||
|
// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
/^https:\/\/fonts\.googleapis\.com/,
|
||||||
|
new workbox.strategies.StaleWhileRevalidate({
|
||||||
|
cacheName: 'google-fonts-stylesheets'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache the underlying font files with a cache-first strategy for 1 year.
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
/^https:\/\/fonts\.gstatic\.com/,
|
||||||
|
new workbox.strategies.CacheFirst({
|
||||||
|
cacheName: 'google-fonts-webfonts',
|
||||||
|
plugins: [
|
||||||
|
new workbox.cacheableResponse.Plugin({
|
||||||
|
statuses: [0, 200]
|
||||||
|
}),
|
||||||
|
new workbox.expiration.Plugin({
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
|
maxEntries: 30
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
/\.(?:png|gif|jpg|jpeg|webp|svg)$/,
|
||||||
|
new workbox.strategies.CacheFirst({
|
||||||
|
cacheName: 'images',
|
||||||
|
plugins: [
|
||||||
|
new workbox.expiration.Plugin({
|
||||||
|
maxEntries: 60,
|
||||||
|
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
console.log(`Boo! Workbox didn't load 😬`)
|
||||||
|
}
|
|
@ -2,5 +2,6 @@ import { TemplateResult } from '@popeindustries/lit-html-server'
|
||||||
|
|
||||||
export interface LayoutOptions {
|
export interface LayoutOptions {
|
||||||
title: string
|
title: string
|
||||||
|
url: string
|
||||||
body: TemplateResult | string
|
body: TemplateResult | string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { TemplateResult } from '@popeindustries/lit-html-server'
|
||||||
|
|
||||||
export interface UrlConfig {
|
export interface UrlConfig {
|
||||||
url: string
|
url: string
|
||||||
name: string
|
name: string
|
||||||
|
component: () => TemplateResult
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue