Initial commit, basic pages and sessions.
This commit is contained in:
21
renderer/Link.vue
Normal file
21
renderer/Link.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<a :class="{ active: pageContext.urlPathname === $attrs.href }">
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { usePageContext } from './usePageContext';
|
||||
|
||||
const pageContext = usePageContext();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
a.active {
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
47
renderer/PageShell.vue
Normal file
47
renderer/PageShell.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="content"><slot /></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
border-left: 2px solid #eee;
|
||||
padding-bottom: 50px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
padding: 20px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
line-height: 1.8em;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
15
renderer/_default.page.client.js
Normal file
15
renderer/_default.page.client.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createApp } from './app';
|
||||
|
||||
async function render(pageContext) {
|
||||
if (!pageContext.Page) {
|
||||
throw new Error('Client-side render() hook expects pageContext.Page to be defined');
|
||||
}
|
||||
|
||||
const { app, store } = createApp(pageContext);
|
||||
|
||||
store.state.value = pageContext.initialState;
|
||||
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
export { render };
|
||||
80
renderer/_default.page.server.js
Normal file
80
renderer/_default.page.server.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { renderToString as renderToString_ } from '@vue/server-renderer';
|
||||
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server';
|
||||
|
||||
import { createApp } from './app';
|
||||
import { useUser } from '../stores/user';
|
||||
import logoUrl from './logo.svg';
|
||||
|
||||
async function renderToString(app) {
|
||||
let err;
|
||||
|
||||
// Workaround: renderToString_() swallows errors in production, see https://github.com/vuejs/core/issues/7876
|
||||
app.config.errorHandler = (err_) => { // eslint-disable-line no-param-reassign
|
||||
err = err_;
|
||||
};
|
||||
|
||||
const appHtml = await renderToString_(app);
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return appHtml;
|
||||
}
|
||||
|
||||
async function render(pageContext) {
|
||||
const appHtml = await renderToString(pageContext.app);
|
||||
|
||||
// See https://vite-plugin-ssr.com/head
|
||||
const { documentProps } = pageContext.exports;
|
||||
const title = (documentProps && documentProps.title) || 'shack';
|
||||
const desc = (documentProps && documentProps.description) || 'Shack';
|
||||
|
||||
const documentHtml = escapeInject`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="${logoUrl}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="${desc}" />
|
||||
<title>${title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">${dangerouslySkipEscape(appHtml)}</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return {
|
||||
documentHtml,
|
||||
};
|
||||
}
|
||||
|
||||
async function onBeforeRender(pageContext) {
|
||||
if (!pageContext.Page) {
|
||||
throw new Error('My render() hook expects pageContext.Page to be defined');
|
||||
}
|
||||
|
||||
const { app, store } = createApp(pageContext);
|
||||
const userStore = useUser();
|
||||
|
||||
userStore.user = pageContext.session.user;
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
app,
|
||||
initialState: store.state.value,
|
||||
pageData: {
|
||||
user: pageContext.session.user,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
render,
|
||||
onBeforeRender,
|
||||
};
|
||||
|
||||
export const passToClient = ['urlPathname', 'initialState', 'pageData'];
|
||||
14
renderer/_error.page.vue
Normal file
14
renderer/_error.page.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div v-if="is404">
|
||||
<h1>404 Page Not Found</h1>
|
||||
<p>This page could not be found.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1>500 Internal Error</h1>
|
||||
<p>Something went wrong.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps(['is404'])
|
||||
</script>
|
||||
30
renderer/app.js
Normal file
30
renderer/app.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createSSRApp, h } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import Container from './container.vue';
|
||||
import { setPageContext } from './usePageContext';
|
||||
|
||||
import '../assets/css/style.css';
|
||||
|
||||
function createApp(pageContext) {
|
||||
const PageWithLayout = {
|
||||
render() {
|
||||
return h(Container, {}, {
|
||||
default() {
|
||||
return h(pageContext.Page, pageContext.pageData || {});
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const app = createSSRApp(PageWithLayout);
|
||||
const store = createPinia();
|
||||
|
||||
app.use(store);
|
||||
|
||||
// We make pageContext available from any Vue component
|
||||
setPageContext(app, pageContext);
|
||||
|
||||
return { app, store };
|
||||
}
|
||||
|
||||
export { createApp };
|
||||
127
renderer/container.vue
Normal file
127
renderer/container.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<a href="/">
|
||||
<h1 class="title">
|
||||
<div
|
||||
class="logo"
|
||||
v-html="logo"
|
||||
/>shack
|
||||
</h1>
|
||||
</a>
|
||||
|
||||
<div class="search">
|
||||
<input
|
||||
type="search"
|
||||
class="input"
|
||||
placeholder="Search the shack"
|
||||
>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="user"
|
||||
class="userpanel"
|
||||
@click="logout"
|
||||
>{{ user.username }}</span>
|
||||
</header>
|
||||
|
||||
<div class="content-container">
|
||||
<slot />
|
||||
<footer class="footer">shuck {{ version }}</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import { onMounted } from 'vue';
|
||||
// import { usePageContext } from './usePageContext';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import logo from '../assets/img/logo.svg?raw';
|
||||
|
||||
import { del } from '../assets/js/api';
|
||||
import { useUser } from '../stores/user';
|
||||
|
||||
// const pageContext = usePageContext();
|
||||
const version = CLIENT_VERSION;
|
||||
|
||||
const userStore = useUser();
|
||||
const { user } = storeToRefs(userStore);
|
||||
|
||||
async function logout() {
|
||||
await del('/api/session');
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background-dark-10);
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak-10);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: auto;
|
||||
height: 1.75rem;
|
||||
fill: var(--primary);
|
||||
margin: .75rem .5rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
margin: 0 1rem;
|
||||
|
||||
.input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.userpanel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: .5rem 1rem;
|
||||
color: var(--shadow);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
36
renderer/logo.svg
Normal file
36
renderer/logo.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="175" height="175" fill="none" version="1.1" viewBox="0 0 175 175" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs>
|
||||
<linearGradient id="linearGradient880" x1="108.64" x2="115.51" y1="88.726" y2="136.2" gradientTransform="matrix(1.0498 0 0 1.0498 -2.9171 -2.9658)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffea83" offset="0"/>
|
||||
<stop stop-color="#FFDD35" offset=".083333"/>
|
||||
<stop stop-color="#FFA800" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="48.975" x2="61.299" y1="3.9232" y2="158.04" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83" offset="0"/>
|
||||
<stop stop-color="#FFDD35" offset=".083333"/>
|
||||
<stop stop-color="#FFA800" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint0_linear-6" x1="-1.4492" x2="116.62" y1="-5.8123" y2="137.08" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF" offset="0"/>
|
||||
<stop stop-color="#BD34FE" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="87.5" cy="87.5" r="87.5" fill="#c4c4c4"/>
|
||||
<circle cx="87.5" cy="87.5" r="87.5" fill="url(#paint0_linear-6)"/>
|
||||
<g transform="translate(632.92 54.355)" fill="#d38787" stroke-width="1.0614">
|
||||
<path d="m-549.75 68.457c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35464-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12956 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.9521c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7178 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2687 8.0366 8.2687 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77686 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91739 0.9174 2.3036 1.668 3.0805 1.668 0.77688 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97108 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.345-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35184 1.3135-0.64655 2.7465-0.65453 3.1843-8e-3 0.43784-0.69682 0.79608-1.5308 0.79608-0.83399 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.244 0.91741 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" style="mix-blend-mode:lighten"/>
|
||||
<path d="m-552.2 68.911c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35463-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12957 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.952c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7179 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2688 8.0366 8.2688 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77687 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91741 0.91739 2.3036 1.668 3.0805 1.668 0.77686 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97107 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.34502-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35182 1.3135-0.64653 2.7465-0.65452 3.1843-8e-3 0.43784-0.69683 0.79608-1.5308 0.79608-0.83397 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.245 0.9174 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" fill-opacity=".47466" style="mix-blend-mode:lighten"/>
|
||||
</g>
|
||||
<path d="m128.48 88.913-24.027 4.6784c-0.39475 0.07685-0.68766 0.40944-0.71076 0.80849l-1.4782 24.805c-0.0347 0.58371 0.50497 1.0372 1.0792 0.90602l6.6886-1.5338c0.62676-0.14383 1.1916 0.40419 1.0635 1.0299l-1.9874 9.6702c-0.13438 0.65091 0.48084 1.2073 1.1202 1.0142l4.1322-1.2472c0.64041-0.19317 1.2556 0.36535 1.1202 1.0162l-3.158 15.191c-0.19842 0.95011 1.074 1.4677 1.6042 0.653l0.35485-0.54382 19.578-38.827c0.32755-0.64985-0.23727-1.391-0.95641-1.2535l-6.8849 1.3207c-0.6467 0.12389-1.1979-0.47453-1.0152-1.1034l4.4944-15.482c0.18266-0.63012-0.36955-1.2295-1.0173-1.1034z" fill="url(#linearGradient880)" stroke-width="1.0498"/>
|
||||
<rect x="3" y="3" width="169" height="169" rx="84.5" stroke="url(#paint2_linear)" stroke-width="6" style="mix-blend-mode:soft-light"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
19
renderer/usePageContext.js
Normal file
19
renderer/usePageContext.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// `usePageContext` allows us to access `pageContext` in any Vue component.
|
||||
// See https://vite-plugin-ssr.com/pageContext-anywhere
|
||||
|
||||
import { inject } from 'vue';
|
||||
|
||||
const key = Symbol();
|
||||
|
||||
function usePageContext() {
|
||||
const pageContext = inject(key);
|
||||
|
||||
return pageContext;
|
||||
}
|
||||
|
||||
function setPageContext(app, pageContext) {
|
||||
app.provide(key, pageContext);
|
||||
}
|
||||
|
||||
export { usePageContext };
|
||||
export { setPageContext };
|
||||
Reference in New Issue
Block a user