initial commit

This commit is contained in:
Daniel Bulant 2021-08-27 19:26:36 +02:00
commit 73f8c00f95
96 changed files with 18419 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
**/node_modules/
/dist/
.DS_Store
**/.history
src/tmp/
.routify
.netlify
assets/build
.vercel

7
.nolluprc.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
hot: true,
contentBase: 'assets',
publicPath: 'build',
historyApiFallback: '__app.html',
port: 5000
}

73
README.md Normal file
View file

@ -0,0 +1,73 @@
### Oh hai! ❤
We've created a new project called [**stackmix**](https://github.com/roxiness/stackmix). It's an experimental CLI that let's you customize new Routify templates. Go check it out!
---
# routify-starter
Starter template for [Routify](https://github.com/roxiness/routify).
### Get started
#### Starter templates
| Template | Description |
|-------------------------------------------|-------------------------------------------------------------|
| [master](https://example.routify.dev/) | Default template, includes examples folder |
| [blog](https://blog-example.routify.dev/) | Generates a blog from local markdown posts. Includes mdsvex |
| [auth](https://auth-example.routify.dev/) | Embedded login on protected pages. Includes Auth0 |
To use a template, run:
`npx @roxi/routify init`
or
`npx @roxi/routify init --branch <branch-name>`
The above commands will populate the current directory, they don't create a new one.
### npm scripts
| Syntax | Description |
|------------------|-----------------------------------------------------------------------------------|
| `dev` | Development (port 5000) |
| `dev:nollup` | Development with crazy fast rebuilds (port 5000) |
| `dev-dynamic` | Development with dynamic imports |
| `build` | Build a bundled app with SSR + prerendering and dynamic imports |
| `serve` | Run after a build to preview. Serves SPA on 5000 and SSR on 5005 |
| `deploy:*` | Deploy to netlify or now |
| `export` | Create static pages from content in dist folder (used by `npm run build`) |
### SSR and pre-rendering
SSR and pre-rendering are included in the default build process.
`npm run deploy:(now|netlify)` will deploy the app with SSR and prerendering included.
To render async data, call the `$ready()` helper whenever your data is ready.
If $ready() is present, rendering will be delayed till the function has been called.
Otherwise it will be rendered instantly.
See [src/pages/example/api/[showId].svelte](https://github.com/roxiness/routify-starter/blob/master/src/pages/example/api/%5BshowId%5D.svelte) for an example.
### Production
* For SPA or SSR apps please make sure that url rewrite is enabled on the server.
* For SPA redirect to `__app.html`.
* For SSR redirect to the lambda function or express server.
### Typescript
For Typescript, we recommend [@lamualfa](https://github.com/lamualfa) excellent [routify-ts](https://github.com/lamualfa/routify-ts/)
New project: `npx routify-ts init <project-name> [routify-init-args]`
Existing project: `npx routify-ts convert [project-directory]`
### Issues?
File on Github! See https://github.com/sveltech/routify/issues .

16
api/netlify/package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "ssr",
"version": "1.0.0",
"description": "",
"main": "ssr.js",
"scripts": {
"build": "node utils/build.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"esbuild": "^0.8.8",
"tossr": "^1.3.1"
}
}

11
api/netlify/ssr.js Normal file
View file

@ -0,0 +1,11 @@
const fs = require('fs')
const { tossr } = require('tossr')
const { script, template } = require('./bundle.json')
exports.handler = async (event, context) => {
const qs = Object.entries(event.queryStringParameters)
.map(([key, value]) => `${key}=${value}`)
.join('&');
const body = await tossr(template, script, `${event.path}?${qs}`);
return { statusCode: 200, body: body + '\n<!--ssr rendered-->' }
}

View file

@ -0,0 +1,26 @@
/**
* Creates a JSON and inlines it with esbuild for ssr.js to consume
* {
* data: duh,
* script: inlined main.js
* template: __app.html
* }
*/
const { resolve } = require('path')
const { readFileSync, writeFileSync } = require('fs')
const { build } = require('esbuild')
const scriptPath = resolve(__dirname, '../../../dist/build/main.js')
const templatePath = resolve(__dirname, '../../../dist/__app.html')
const bundlePath = resolve(__dirname, '../build/bundle.js')
build({ entryPoints: [scriptPath], outfile: bundlePath, bundle: true }).then(() => {
const bundle = {
date: new Date,
script: readFileSync(bundlePath, 'utf8'),
template: readFileSync(templatePath, 'utf8')
}
writeFileSync(resolve(__dirname, '../bundle.json'), JSON.stringify(bundle, null, 2))
})

36
api/vercel-ssr/build.js Normal file
View file

@ -0,0 +1,36 @@
const { resolve } = require('path')
const { existsSync } = require('fs')
const { execSync } = require('child_process')
const { rollup } = require('rollup')
const shouldBuildSpa = process.env.NOW_GITHUB_DEPLOYMENT || process.env.NOW_BUILDER
const script = resolve(__dirname, '../../dist/build/main.js')
const bundlePath = resolve(__dirname, '../../dist/build/bundle.js')
build()
async function build() {
if (shouldBuildSpa)
execSync('npm install && npm run build:app', { cwd: resolve('..', '..'), stdio: 'inherit' })
else
await waitForAppToExist()
buildSSRBundle()
}
async function waitForAppToExist() {
while (!existsSync(script)) {
console.log(`checking if "${script}" exists`)
await new Promise(r => setTimeout(r, 2000))
}
console.log(`found "${script}"`)
}
async function buildSSRBundle() {
const bundle = await rollup({
input: script,
inlineDynamicImports: true,
})
await bundle.write({ format: 'umd', file: bundlePath, name: 'roxi-ssr' })
}

11
api/vercel-ssr/index.js Normal file
View file

@ -0,0 +1,11 @@
const fs = require('fs')
const { tossr } = require('tossr')
const script = fs.readFileSync(require.resolve('../../dist/build/bundle.js'), 'utf8')
const template = fs.readFileSync(require.resolve('../../dist/__app.html'), 'utf8')
module.exports = async (req, res) => {
const html = await tossr(template, script, req.url, {})
res.send(html + '\n<!--ssr rendered-->')
}

View file

@ -0,0 +1,8 @@
{
"scripts": {
"vercel-build": "node ./build.js"
},
"devDependencies": {
"rollup": "^2.28.2"
}
}

1
assets/404.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><linearGradient id="a" x1="2.625" x2="25.637" y1="13.491" y2="13.491" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><path fill="url(#a)" d="M10.5 15.5H12v.5c0 .3.2.5.5.5s.5-.2.5-.5v-.5h.5c.3 0 .5-.2.5-.5s-.2-.5-.5-.5H13V13c0-.3-.2-.5-.5-.5s-.5.2-.5.5v1.5h-.9l.6-3.4c.1-.3-.1-.5-.4-.6-.3-.1-.5.1-.6.4l-.8 4c0 .1 0 .3.1.4s.4.2.5.2z"/><linearGradient id="b" x1="2.625" x2="25.637" y1="13.491" y2="13.491" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><path fill="url(#b)" d="M20.5 15.5H22v.5c0 .3.2.5.5.5s.5-.2.5-.5v-.5h.5c.3 0 .5-.2.5-.5s-.2-.5-.5-.5H23V13c0-.3-.2-.5-.5-.5s-.5.2-.5.5v1.5h-.9l.6-3.4c.1-.3-.1-.5-.4-.6-.3-.1-.5.1-.6.4l-.8 4c0 .1 0 .3.1.4s.4.2.5.2z"/><linearGradient id="c" x1="2.625" x2="25.637" y1="22" y2="22" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><path fill="url(#c)" d="M6.5 21.5h-.6c-.1-.3-.2-.6-.4-.9l.5-.4c.2-.2.2-.5 0-.7s-.5-.2-.7 0l-.4.4c-.3-.2-.6-.3-.9-.4V19c0-.3-.2-.5-.5-.5s-.5.2-.5.5v.6c-.3.1-.6.2-.9.4l-.4-.4c-.2-.2-.5-.2-.7 0s-.2.4 0 .6l.4.4c-.2.3-.3.6-.4.9H.5c-.3 0-.5.2-.5.5s.2.5.5.5h.6c.1.3.2.6.4.9l-.5.4c-.2.2-.2.5 0 .7.1.1.2.1.4.1s.3 0 .4-.1l.4-.4c.3.2.6.3.9.4v.5c0 .3.2.5.5.5s.4-.2.4-.5v-.6c.3-.1.6-.2.9-.4l.4.4c.1.1.2.1.4.1s.3 0 .4-.1c.2-.2.2-.5 0-.7l-.4-.4c.2-.3.3-.6.4-.9h.6c.1.1.3-.1.3-.4s-.2-.5-.5-.5zm-3 2c-.8 0-1.5-.7-1.5-1.5 0-.4.2-.8.4-1.1.3-.3.7-.4 1.1-.4.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5z"/><linearGradient id="d" x1="2.625" x2="25.637" y1="22" y2="22" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><circle cx="3.5" cy="22" r=".5" fill="url(#d)"/><linearGradient id="e" x1="2.625" x2="25.637" y1="16" y2="16" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><path fill="url(#e)" d="M31.5 12h-.8l.6-.6c.2-.2.2-.5 0-.7l-1.4-1.4c-.2-.2-.5-.2-.7 0l-.6.6V9c0-.3-.2-.5-.5-.5h-1V8c0-.8-.7-1.5-1.5-1.5h-17C7.7 6.5 7 7.2 7 8v5.6c-.3.1-.6.2-.9.4l-.4-.4c-.2-.2-.5-.2-.7 0s-.2.4 0 .6l.4.4c-.2.3-.3.6-.4.9h-.5c-.3 0-.5.2-.5.5s.2.5.5.5h.6c.1.3.2.6.4.9l-.5.4c-.2.2-.2.5 0 .7.1.1.2.1.4.1s.3 0 .4-.1l.4-.4c.3.2.6.3.9.4V20c0 .8.7 1.5 1.5 1.5H14v3h-3.5c-.3 0-.5.2-.5.5s.2.5.5.5h13c.3 0 .5-.2.5-.5s-.2-.5-.5-.5H20v-3h5.5c.8 0 1.5-.7 1.5-1.5v-1.5h1c.3 0 .5-.2.5-.5v-.8l.6.6c.2.2.5.2.7 0l1.4-1.4c.2-.2.2-.5 0-.7l-.5-.7h.8c.3 0 .5-.2.5-.5v-2c0-.3-.2-.5-.5-.5zm-4.5.5c.6 0 1 .4 1 1s-.4 1-1 1v-2zm-18.5-5h17c.3 0 .5.2.5.5v9.5H8V8c0-.3.2-.5.5-.5zM7 17.4c-.6-.2-1-.8-1-1.4 0-.4.2-.8.4-1.1.2-.2.4-.3.6-.3v2.8zm12 7.1h-4v-3h4v3zm6.5-4h-17c-.3 0-.5-.2-.5-.5v-1.5h18V20c0 .3-.2.5-.5.5zM31 14h-.7c-.2 0-.4.1-.5.4-.1.2-.1.4-.2.6-.1.2-.1.4.1.6l.5.5-.7.7-.5-.6c-.2-.2-.4-.2-.6-.1-.2.1-.4.2-.6.2-.2.1-.3.3-.3.5v.7H27v-2c1.1 0 2-.9 2-2s-.9-2-2-2v-2h.5v.7c0 .2.1.4.3.5.2.1.4.1.6.2.2.1.4.1.6-.1l.5-.5.7.7-.5.5c-.2.2-.2.4-.1.6.1.2.2.4.2.6.1.2.3.4.5.4h.7v.9z"/><linearGradient id="f" x1="2.625" x2="25.637" y1="13.5" y2="13.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2B2E81"/><stop offset="1" stop-color="#BE4F9C"/></linearGradient><path fill="url(#f)" d="M16.5 16.5h1c.8 0 1.5-.7 1.5-1.5v-3c0-.8-.7-1.5-1.5-1.5h-1c-.8 0-1.5.7-1.5 1.5v3c0 .8.7 1.5 1.5 1.5zM16 12c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v3c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-3z"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

22
assets/__app.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<meta name="theme-color" content="#E938C2">
<link rel="apple-touch-icon" href="/images/touch-icons/logo-192.png">
<link rel="manifest" href="/manifest.json">
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel="modulepreload" href="/build/main.js" />
<script type="module" src="/build/main.js"></script>
</head>
<body>
<noscript>Please enable Javascript for best experience.</noscript>
</body>
</html>

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

66
assets/global.css Normal file
View file

@ -0,0 +1,66 @@
html, body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

21
assets/manifest.json Normal file
View file

@ -0,0 +1,21 @@
{
"background_color": "#ffffff",
"theme_color": "#E938C2",
"name": "Routify app",
"short_name": "Routify app",
"start_url": "/",
"display": "standalone",
"icons": [
{
"src": "/images/touch-icons/logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/touch-icons/logo-800.png",
"sizes": "800x800",
"type": "image/png",
"purpose": "maskable any"
}
]
}

2
assets/robots.txt Normal file
View file

@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

23
netlify.toml Normal file
View file

@ -0,0 +1,23 @@
[build]
publish = "dist"
functions = "api/netlify"
command = "npm run build && cd api/netlify && npm run build"
# Dev doesn't work yet. Any takers?
# [dev]
# command = "npm run dev:ssr"
# targetPort = 5000
# publish = "assets"
# autoLaunch = true
[[redirects]]
# SSR and SPA
from = "/*"
to = "/.netlify/functions/ssr"
status = 200
# SPA only
# from = "/*"
# to = "/__app.html"
# status = 200

11798
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

61
package.json Normal file
View file

@ -0,0 +1,61 @@
{
"name": "svelte-app",
"version": "1.0.0",
"@comments scripts": {
"dev": "develop with blazing fast rebuilds",
"dev:features": "develop with features like SSR and serviceworker enabled",
"build": "run build scripts below",
"build:app": "build single page application (SPA)",
"build:static": "Generate static pages",
"serve": "serve content in 'dist' folder",
"rollup": "run the rollup bundler",
"nollup": "run the nollup no-bundler",
"routify": "run routify"
},
"scripts": {
"dev": "run-p routify nollup",
"dev:ssr": "run-p routify rollup",
"build": "run-s build:*",
"build:app": "routify -b && rollup -c",
"build:static": "spank",
"serve": "spassr --ssr",
"rollup": "rollup -cw",
"nollup": "nollup -c --verbose",
"routify": "routify"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^19.0.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@roxi/routify": "^2.18.1",
"cross-env": "^7.0.3",
"fs-extra": "^10.0.0",
"nollup": "^0.16.4",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.14",
"postcss-import": "^14.0.1",
"rollup": "^2.47.0",
"rollup-plugin-hot": "^0.1.1",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-svelte-hot": "^1.0.0-7",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-workbox": "^5.2.1",
"spank": "^1.7.0",
"spassr": "^2.6.0",
"svelte": "^3.38.2",
"svelte-preprocess": "^4.7.3",
"tossr": "^1.4.2"
},
"routify": {
"extensions": "svelte,html,svx,md"
},
"spassr": {},
"spank": {
"blacklist": [
"/example/modal/basic/4"
]
},
"dependencies": {
"apexcharts": "^3.28.1"
}
}

4325
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

94
rollup.config.js Normal file
View file

@ -0,0 +1,94 @@
import svelte from 'rollup-plugin-svelte-hot';
import Hmr from 'rollup-plugin-hot'
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import { copySync, removeSync } from 'fs-extra'
import { spassr } from 'spassr'
import getConfig from '@roxi/routify/lib/utils/config'
import autoPreprocess from 'svelte-preprocess'
import postcssImport from 'postcss-import'
import { injectManifest } from 'rollup-plugin-workbox'
const { distDir } = getConfig() // use Routify's distDir for SSOT
const assetsDir = 'assets'
const buildDir = `${distDir}/build`
const isNollup = !!process.env.NOLLUP
const production = !process.env.ROLLUP_WATCH;
// clear previous builds
removeSync(distDir)
removeSync(buildDir)
const serve = () => ({
writeBundle: async () => {
const options = {
assetsDir: [assetsDir, distDir],
entrypoint: `${assetsDir}/__app.html`,
script: `${buildDir}/main.js`
}
spassr({ ...options, port: 5000 })
spassr({ ...options, ssr: true, port: 5005, ssrOptions: { inlineDynamicImports: true, dev: true } })
}
})
const copyToDist = () => ({ writeBundle() { copySync(assetsDir, distDir) } })
export default {
preserveEntrySignatures: false,
input: [`src/main.js`],
output: {
sourcemap: true,
format: 'esm',
dir: buildDir,
// for performance, disabling filename hashing in development
chunkFileNames:`[name]${production && '-[hash]' || ''}.js`
},
plugins: [
svelte({
emitCss: false,
hot: isNollup,
preprocess: [
autoPreprocess({
postcss: { plugins: [postcssImport()] },
defaults: { style: 'postcss' }
})
]
}),
// resolve matching modules from current working directory
resolve({
browser: true,
dedupe: importee => !!importee.match(/svelte(\/|$)/)
}),
commonjs(),
production && terser(),
!production && !isNollup && serve(),
!production && !isNollup && livereload(distDir), // refresh entire window when code is updated
!production && isNollup && Hmr({ inMemory: true, public: assetsDir, }), // refresh only updated code
{
// provide node environment on the client
transform: code => ({
code: code.replace(/process\.env\.NODE_ENV/g, `"${process.env.NODE_ENV}"`),
map: { mappings: '' }
})
},
injectManifest({
globDirectory: assetsDir,
globPatterns: ['**/*.{js,css,svg}', '__app.html'],
swSrc: `src/sw.js`,
swDest: `${distDir}/serviceworker.js`,
maximumFileSizeToCacheInBytes: 10000000, // 10 MB,
mode: 'production'
}),
production && copyToDist(),
],
watch: {
clearScreen: false,
buildDelay: 100,
}
}

6
sandbox.config.json Normal file
View file

@ -0,0 +1,6 @@
{
"container": {
"port": 5000,
"template": "node"
}
}

10
src/App.svelte Normal file
View file

@ -0,0 +1,10 @@
<script>
import { Router } from "@roxi/routify";
import { routes } from "../.routify/routes";
</script>
<style global>
@import "../assets/global.css";
</style>
<Router {routes} />

20
src/Serviceworker.svelte Normal file
View file

@ -0,0 +1,20 @@
<script>
/**
* This file handles serviceworker registration
* To enable it, import it from another file, ie. src/pages/_layout.svelte
* ⚠ The imported component could get treeshaken if not used, eg. <SW />
* For configuring the serviceworker, refer to sw.js
*/
if ("serviceWorker" in navigator) {
import("workbox-window").then(async ({ Workbox }) => {
const wb = new Workbox("/serviceworker.js");
const registration = await wb.register();
// Reload the page if the PWA has been updated. Change strategy if needed.
wb.addEventListener("redundant", () => {
location.reload();
console.log("updated app");
});
});
}
</script>

14
src/chart.js Normal file
View file

@ -0,0 +1,14 @@
import ApexCharts from "apexcharts";
export const chart = (node, options) => {
console.log(ApexCharts);
let myChart = new ApexCharts(node, options)
myChart.render()
return {
update(options) {
myChart.updateOptions(options)
},
}
}

6
src/main.js Normal file
View file

@ -0,0 +1,6 @@
import HMR from '@roxi/routify/hmr'
import App from './App.svelte';
const app = HMR(App, { target: document.body }, 'routify-app')
export default app;

View file

@ -0,0 +1,24 @@
<script>
import { url } from '@roxi/routify'
</script>
<style>
.huge {
font-size: 12rem;
}
.e404 {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<div class="e404">
<div class="huge">404</div>
<div class="big">Page not found.
<!-- link to the parent folder of _fallback.svelte -->
<a href={$url('../')}>Go back</a>
</div>
</div>

2
src/pages/_layout.svelte Normal file
View file

@ -0,0 +1,2 @@
<!-- routify:options preload="proximity" -->
<slot />

View file

@ -0,0 +1,110 @@
<script>
import { draw } from 'svelte/transition'
let duration = 2000
let drawing = true
let show = false
let error = false
setTimeout(() => (show = true))
setTimeout(() => (drawing = false), 1800)
setTimeout(() => (error = true), 5000)
</script>
<style>
.container {
text-align: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin: 0;
}
.error {min-height: 100px}
.svg {
width: 200px;
margin: auto;
}
path {
transition: 0.3s ease;
stroke-width: 1;
stroke: black;
stroke-width: 0;
}
.svg.drawing path {
stroke-width: 2;
fill-opacity: 0;
}
</style>
<div class="container">
<div class="svg" class:drawing>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="isolation:isolate"
viewBox="0 0 1000 1000">
<defs>
<clipPath id="_clipPath_40vHZL606H8eXCPAFONHYpjfq1ISybTL">
<rect width="1000" height="1000" />
</clipPath>
</defs>
<g clip-path="url(#_clipPath_40vHZL606H8eXCPAFONHYpjfq1ISybTL)">
<linearGradient
id="_lgradient_2"
x1="-0.011142038971568513"
y1="-0.011791871475954507"
x2="0.9938039543302696"
y2="0.9909604299907665"
gradientTransform="matrix(532,0,0,368.749,249,625.251)"
gradientUnits="userSpaceOnUse">
<stop
offset="1.7391304347826086%"
stop-opacity="1"
style="stop-color:rgb(255,124,247)" />
<stop
offset="100%"
stop-opacity="1"
style="stop-color:rgb(255,203,252)" />
</linearGradient>
{#if show}
<path
transition:draw|local={{ duration }}
d=" M 564.251 625.251 L 659 720 L 700 675 L 781 994 L 457 921 L 506
873 L 249 626 L 249 626 L 542.5 626 C 549.812 626 557.065 625.748
564.251 625.251 Z "
fill="url(#_lgradient_2)" />
<linearGradient
id="_lgradient_3"
x1="0.13056277056277052"
y1="0.05232744783306609"
x2="0.9350649350649348"
y2="0.7710005350454795"
gradientTransform="matrix(770,0,0,623,84,3)"
gradientUnits="userSpaceOnUse">
<stop
offset="2.1739130434782608%"
stop-opacity="1"
style="stop-color:rgb(241,93,232)" />
<stop
offset="100%"
stop-opacity="1"
style="stop-color:rgb(184,58,177)" />
</linearGradient>
<path
transition:draw|local={{ duration }}
d=" M 542.5 215.388 L 84 215.388 L 203 3 L 542.5 3 L 542.5 3 C
714.422 3 854 142.578 854 314.5 C 854 486.422 714.422 626 542.5 626
L 249 626 L 364 413.612 L 542.5 413.612 L 542.5 413.612 C 597.201
413.612 641.612 369.201 641.612 314.5 C 641.612 259.799 597.201
215.388 542.5 215.388 L 542.5 215.388 L 542.5 215.388 Z "
fill="url(#_lgradient_3)" />
{/if}
</g>
</svg>
</div>
<h1 style="">Generating routes...</h1>
<div class="error">
{#if error}
<h3>Your app should probably have loaded by now</h3>
{/if}
</div>
</div>

View file

@ -0,0 +1,18 @@
<script>
import { leftover } from "@roxi/routify";
import list from "./_list.svelte";
import update from "./_update.svelte";
import view from "./_view.svelte";
export let data;
const components = { list, update, view };
$: [id, action = "view"] = $leftover.split("/");
$: component = (id && components[action]) || list;
</script>
<div>
<div style="width: 512px; margin: auto;" class="card shadow">
<h1 style="text-align: center; margin-top: -8px">CrudWidget</h1>
<svelte:component this={component} {data} {id} />
</div>
</div>

View file

@ -0,0 +1,25 @@
<script>
import { url } from "@roxi/routify";
export let data;
</script>
<style>
.item {
display: inline-block;
width: 50%;
height: 128px;
}
</style>
<div class="items">
{#each data as item}
<a href={$url('../:id', { id: item.id })} class="item">
{#each Object.entries(item).slice(0, 3) as [name, value]}
<div>
<b>{name}:</b>
{value}
</div>
{/each}
</a>
{/each}
</div>

View file

@ -0,0 +1,18 @@
<script>
import { url } from "@roxi/routify";
export let data, id;
$: item = data.filter(item => item.id == id)[0];
</script>
<div >
<div >
{#each Object.entries(item) as [name, value]}
<div>
<b>{name}:</b>
<input type="text" {value}>
</div>
{/each}
</div>
<br>
<a href="{$url('../:id', {id})}">Back</a>
</div>

View file

@ -0,0 +1,22 @@
<script>
import { url } from "@roxi/routify";
export let data = [],
id;
$: item = data.filter(item => item.id == id)[0];
</script>
{#if item}
<div>
<div>
{#each Object.entries(item) as [name, value]}
<div>
<b>{name}:</b>
{value}
</div>
{/each}
</div>
<br />
<a href={$url('../', { id })}>[Back]</a>
<a href={$url('../:id/update', { id })}>[Update]</a>
</div>
{/if}

View file

@ -0,0 +1,107 @@
<script>
import { url, isActive } from "@roxi/routify";
let show = false;
const _links = [
["/", "⯇ BACK TO APP"],
["./index", "Home"],
["./modal", "Modal"],
["./reset", "Reset"],
["./layouts", "Layouts"],
["./widget", "Widget"],
["./aliasing", "Aliasing"],
["./404", "404"],
["./api", "Api"],
["./app", "App"],
["./transitions/tabs", "Transitions"],
];
function handleBurger() {
show = !show;
}
</script>
<style>
aside {
text-align: center;
}
nav {
background: white;
}
.link {
display: block;
white-space: nowrap;
}
.active {
font-weight: bold;
}
.mobile-nav {
font-size: 18px;
background: white;
padding: 8px;
box-shadow: 0px 5px 20px 5px rgba(0, 0, 0, 0.075);
display: block;
}
.mobile-nav * {
vertical-align: middle;
}
.title {
top: 6px;
left: 0;
right: 0;
text-align: center;
}
.burger {
font-size: 24px;
position: absolute;
left: 8px;
top: 4px;
}
nav {
display: none;
width: 100%;
padding: 16px;
z-index: 1;
}
@media (min-width: 640px) {
nav {
margin: 16px;
border-radius: 4px;
box-shadow: 0px 5px 20px 5px rgba(0, 0, 0, 0.075);
display: inline-block;
position: relative;
width: auto;
}
.link {
padding: 0 16px;
min-width: 56px;
display: inline-block;
}
.mobile-nav {
display: none;
}
}
nav.show {
display: block;
}
</style>
<aside>
<nav class="mobile-nav">
<span class="burger" on:click={handleBurger}>☰</span>
<span class="title">Routify Examples</span>
<span />
</nav>
<nav class:show on:click={() => (show = false)}>
{#each _links as [path, name]}
<a class="link" class:active={$isActive(path)} href={$url(path)}>
{name}
</a>
{/each}
</nav>
</aside>

View file

@ -0,0 +1,26 @@
<script>
import { fade } from "svelte/transition";
let show = true;
let render = window.__preRendered ? "prerender" : window.__ssrRendered ? "ssr" : "spa";
setTimeout(() => {
show = false;
}, 3000);
</script>
<style>
.box {
position: fixed;
bottom: 20px;
left: 20px;
padding: 8px 16px;
background: white;
border-radius: 4px;
font-weight: bold;
color: #777;
}
</style>
{#if show}
<div transition:fade|local class="box">source: {render}</div>
{/if}

View file

@ -0,0 +1,6 @@
<h1>Routify Starter</h1>
To see an example app, go to
<a href="/example">/example</a>
<p>To delete the example app, simply delete the ./src/pages/example folder.</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

359
src/pages/example/_data.js Normal file
View file

@ -0,0 +1,359 @@
export const todos = [
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
},
{
"userId": 1,
"id": 4,
"title": "eum et est occaecati",
"body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"
},
{
"userId": 1,
"id": 5,
"title": "nesciunt quas odio",
"body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"
},
{
"userId": 1,
"id": 6,
"title": "dolorem eum magni eos aperiam quia",
"body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae"
},
{
"userId": 1,
"id": 7,
"title": "magnam facilis autem",
"body": "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas"
},
{
"userId": 1,
"id": 8,
"title": "dolorem dolore est ipsam",
"body": "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae"
},
{
"userId": 1,
"id": 9,
"title": "nesciunt iure omnis dolorem tempora et accusantium",
"body": "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas"
},
{
"userId": 1,
"id": 10,
"title": "optio molestias id quia eum",
"body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error"
}
]
export const users = [
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": {
"street": "Douglas Extension",
"suite": "Suite 847",
"city": "McKenziehaven",
"zipcode": "59590-4157",
"geo": {
"lat": "-68.6102",
"lng": "-47.0653"
}
},
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": {
"name": "Romaguera-Jacobson",
"catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications"
}
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "Julianne.OConner@kory.org",
"address": {
"street": "Hoeger Mall",
"suite": "Apt. 692",
"city": "South Elvis",
"zipcode": "53919-4257",
"geo": {
"lat": "29.4572",
"lng": "-164.2990"
}
},
"phone": "493-170-9623 x156",
"website": "kale.biz",
"company": {
"name": "Robel-Corkery",
"catchPhrase": "Multi-tiered zero tolerance productivity",
"bs": "transition cutting-edge web services"
}
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "Lucio_Hettinger@annie.ca",
"address": {
"street": "Skiles Walks",
"suite": "Suite 351",
"city": "Roscoeview",
"zipcode": "33263",
"geo": {
"lat": "-31.8129",
"lng": "62.5342"
}
},
"phone": "(254)954-1289",
"website": "demarco.info",
"company": {
"name": "Keebler LLC",
"catchPhrase": "User-centric fault-tolerant solution",
"bs": "revolutionize end-to-end systems"
}
},
{
"id": 6,
"name": "Mrs. Dennis Schulist",
"username": "Leopoldo_Corkery",
"email": "Karley_Dach@jasper.info",
"address": {
"street": "Norberto Crossing",
"suite": "Apt. 950",
"city": "South Christy",
"zipcode": "23505-1337",
"geo": {
"lat": "-71.4197",
"lng": "71.7478"
}
},
"phone": "1-477-935-8478 x6430",
"website": "ola.org",
"company": {
"name": "Considine-Lockman",
"catchPhrase": "Synchronised bottom-line interface",
"bs": "e-enable innovative applications"
}
},
{
"id": 7,
"name": "Kurtis Weissnat",
"username": "Elwyn.Skiles",
"email": "Telly.Hoeger@billy.biz",
"address": {
"street": "Rex Trail",
"suite": "Suite 280",
"city": "Howemouth",
"zipcode": "58804-1099",
"geo": {
"lat": "24.8918",
"lng": "21.8984"
}
},
"phone": "210.067.6132",
"website": "elvis.io",
"company": {
"name": "Johns Group",
"catchPhrase": "Configurable multimedia task-force",
"bs": "generate enterprise e-tailers"
}
},
{
"id": 8,
"name": "Nicholas Runolfsdottir V",
"username": "Maxime_Nienow",
"email": "Sherwood@rosamond.me",
"address": {
"street": "Ellsworth Summit",
"suite": "Suite 729",
"city": "Aliyaview",
"zipcode": "45169",
"geo": {
"lat": "-14.3990",
"lng": "-120.7677"
}
},
"phone": "586.493.6943 x140",
"website": "jacynthe.com",
"company": {
"name": "Abernathy Group",
"catchPhrase": "Implemented secondary concept",
"bs": "e-enable extensible e-tailers"
}
},
{
"id": 9,
"name": "Glenna Reichert",
"username": "Delphine",
"email": "Chaim_McDermott@dana.io",
"address": {
"street": "Dayna Park",
"suite": "Suite 449",
"city": "Bartholomebury",
"zipcode": "76495-3109",
"geo": {
"lat": "24.6463",
"lng": "-168.8889"
}
},
"phone": "(775)976-6794 x41206",
"website": "conrad.com",
"company": {
"name": "Yost and Sons",
"catchPhrase": "Switchable contextually-based project",
"bs": "aggregate real-time technologies"
}
},
{
"id": 10,
"name": "Clementina DuBuque",
"username": "Moriah.Stanton",
"email": "Rey.Padberg@karina.biz",
"address": {
"street": "Kattie Turnpike",
"suite": "Suite 198",
"city": "Lebsackbury",
"zipcode": "31428-2261",
"geo": {
"lat": "-38.2386",
"lng": "57.2232"
}
},
"phone": "024-648-3804",
"website": "ambrose.net",
"company": {
"name": "Hoeger LLC",
"catchPhrase": "Centralized empowering task-force",
"bs": "target end-to-end models"
}
}
]
export const posts = [
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
},
{
"userId": 1,
"id": 4,
"title": "eum et est occaecati",
"body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"
},
{
"userId": 1,
"id": 5,
"title": "nesciunt quas odio",
"body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"
},
{
"userId": 1,
"id": 6,
"title": "dolorem eum magni eos aperiam quia",
"body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae"
},
{
"userId": 1,
"id": 7,
"title": "magnam facilis autem",
"body": "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas"
},
{
"userId": 1,
"id": 8,
"title": "dolorem dolore est ipsam",
"body": "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae"
},
{
"userId": 1,
"id": 9,
"title": "nesciunt iure omnis dolorem tempora et accusantium",
"body": "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas"
},
{
"userId": 1,
"id": 10,
"title": "optio molestias id quia eum",
"body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error"
}
]

View file

@ -0,0 +1,24 @@
<script>
import { url } from '@roxi/routify'
</script>
<style>
.huge {
font-size: 12rem;
}
.e404 {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<div class="e404">
<div class="huge">404</div>
<div class="big">Page not found.
<!-- link to the parent folder of _fallback.svelte -->
<a href={$url('../')}>Go back</a>
</div>
</div>

View file

@ -0,0 +1,54 @@
<script>
import { url } from "@roxi/routify";
import NavLinks from "./_components/NavLinks.svelte";
</script>
<style>
.example {
background-color: #ffeffe;
background-attachment: fixed;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
* :global(.card) {
border-radius: 0.25rem;
border-width: 1px;
border: 1px solid #e2e8f0;
margin-bottom: 3rem;
padding: 2rem;
background: white;
}
* :global(.shadow) {
box-shadow: 0px 5px 20px 5px rgba(0, 0, 0, 0.075);
}
.main {
overflow: auto;
height: calc(100%);
padding: 8px;
}
</style>
<!-- Reset files doesn't inherit the parent scope.
This ensures that the example app doesn't inherit
layouts from the main app. -->
<div class="example">
<div class="nav">
<NavLinks />
<!-- we load RenderStatus popup dynamically as it's not
important and we don't want to slow down the initial load -->
{#await import('./_components/RenderStatus.svelte') then Module}
<svelte:component this={Module.default} />
{/await}
</div>
<div class="main">
<slot>
<!-- optional fallback -->
</slot>
</div>
</div>

View file

@ -0,0 +1,15 @@
<script>
import { url } from "@roxi/routify";
</script>
<div style="text-align: center">
<div style="font-weight: bold">
<a href={$url('./')}>Aliasing</a> |
<a href={$url('./v1')}>V1</a> |
<a href={$url('./v1.1')}>V1.1</a>
</div>
<slot>
<!-- optional fallback -->
</slot>
</div>

View file

@ -0,0 +1,3 @@
<p>Pages can redirect to other pages while not changing the current URL.</p>
<p>By using _fallback.svelte we can reference whole libraries and modules instead of having to duplicate them.</p>

View file

@ -0,0 +1,4 @@
<script>
import {goto, leftover} from '@roxi/routify'
$goto('../../v1/'+$leftover, null, true, true)
</script>

View file

@ -0,0 +1,22 @@
<script>
import { url } from "@roxi/routify";
</script>
<p>V1.1 has only one file: feature2.svelte</p>
<p>The rest are handled with _fallback.svelte, which redirects to v1</p>
<code>
<pre>
/** _fallback.svelte **/
import {`{(goto, leftover)}`} from '@roxi/routify'
$goto('../../v1/'+$leftover, null, true, true)
</pre>
</code>
<a href={$url('./feature1')}>Feature 1</a>
<a href={$url('./feature2')}>Feature 2</a>
<a href={$url('./feature3')}>Feature 3</a>
<slot>
<!-- optional fallback -->
</slot>

View file

@ -0,0 +1,3 @@
<h1>Feature 2</h1>
<b>v1.1 feature</b>

View file

@ -0,0 +1 @@
<h1>Welcome to v2</h1>

View file

@ -0,0 +1,16 @@
<script>
import { url } from '@roxi/routify'
</script>
<p>V1 has three files, which can be seen in the links below </p>
<a href={$url('./feature1')}>Feature 1</a> |
<a href={$url('./feature2')}>Feature 2</a> |
<a href={$url('./feature3')}>Feature 3</a>
<slot>
<!-- optional fallback -->
</slot>

View file

@ -0,0 +1,3 @@
<h1>Feature 1</h1>
<b>v1 feature</b>

View file

@ -0,0 +1,3 @@
<h1>Feature 2</h1>
<b>v1 feature</b>

View file

@ -0,0 +1,3 @@
<h1>Feature 3</h1>
<b>v1 feature</b>

View file

@ -0,0 +1 @@
<h1>Welcome to v1</h1>

View file

@ -0,0 +1,30 @@
<script>
import { ready, url, params } from "@roxi/routify";
let series = {};
$: updateShow($params.showId);
function updateShow(id) {
fetch(`https://api.tvmaze.com/shows/${id}`)
.then(response => response.json())
.then(json => {
series = json;
$ready();
});
}
</script>
<div style="text-align: center; max-width: 540px; margin: auto">
<h4>
<a href={$url('./')}>Go back</a>
</h4>
{#if series.id}
<img src={series.image.medium.replace('http:', 'https:')} alt="cover" style="height: 295px" />
<h1>{series.name} ({series.premiered.split('-')[0]})</h1>
<p>
{@html series.summary}
</p>
<a href={series.url}>Read more on TVMaze</a>
{/if}
</div>

View file

@ -0,0 +1,16 @@
<script>
const movies = [
[32, "Fargo"],
[179, "The Wire"],
[318, "Community"],
[5, "True Detective"],
[532, "Scrubs"],
[30960, "Cobra Kai"],
[530, "Seinfeld"],
[347, "It's Always Sunny in Philadelphia"]
];
</script>
<slot scoped={{movies}}>
<!-- optional fallback -->
</slot>

View file

@ -0,0 +1,20 @@
<script>
import { url, prefetch } from "@roxi/routify";
export let scoped;
const options = {
validFor: 3600 * 24 * 31, // don't refresh assets on the page for a month
writeHeaders: true // useful for debugging
};
const { movies } = scoped;
// console.log(movies)
</script>
<div style="text-align: center">
{#each movies as [showId, title]}
<h3>
<a use:prefetch={options} href={$url('./:showId', { showId })}>
{title}
</a>
</h3>
{/each}
</div>

View file

@ -0,0 +1,24 @@
<script>
import { url } from '@roxi/routify'
</script>
<style>
.huge {
font-size: 12rem;
}
.e404 {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<div class="e404">
<div class="huge">404</div>
<div class="big">Page not found.
<!-- link to the parent folder of _fallback.svelte -->
<a href={$url('./')}>Go back</a>
</div>
</div>

View file

@ -0,0 +1,27 @@
<script>
import { goto, url } from "@roxi/routify";
import { user } from "./_store";
/** We set the static parameter to true since we don't want to change the browser's URL
* Notice the $: prefix which makes the statement reactive. This way if the user logs
* out the $goto is called again.
* **/
$: if (!$user) $goto("./login", {}, true);
function logout() {
$user = false;
}
</script>
{#if $user}
<a href={$url('/example')}>Back to examples</a>
<a href={$url('./')}>Home</a>
<a href={$url('./about')}>About</a>
<button on:click={logout} style="position: absolute; right: 24px">
Logout
</button>
<slot>
<!-- optional fallback -->
</slot>
{/if}

View file

@ -0,0 +1,3 @@
import {writable} from 'svelte/store'
export const user = writable(false)

View file

@ -0,0 +1 @@
<h1>Welcome logged in user</h1>

View file

@ -0,0 +1,2 @@
<!-- We don't want to inherit the parent layout on the login page, so we use a reset to clear the scope. -->
<slot></slot>

View file

@ -0,0 +1,40 @@
<script>
import { user } from "../_store.js";
import { goto, url } from "@roxi/routify";
let username = "anything";
let password = "goes";
function login() {
$user = { username };
/** We want to $goto our current location.
* Since we're now logged in, we shouldn't be redirected to this login page again.
* **/
$goto(window.location.href);
}
</script>
<div style="width: 256px; margin: 128px auto; text-align: center">
<h1>Login</h1>
<input type="text" bind:value={username} />
<br />
<input type="text" bind:value={password} />
<br />
<button on:click={login}>Submit</button>
<br />
<br />
<br />
<p>
This login page is actually located at
<a href={$url()}>{$url()}</a>
</p>
<p>
You are seeing it here, because we're using $goto with the static option
enabled. This renders the login page, without changing the URL in the
browser.
</p>
<p>
On submit, we're "redirected" to the current URL in your browser.
</p>
</div>

View file

@ -0,0 +1,24 @@
<script>
import base64Logo from "./_components/assets/logo.js";
</script>
<!-- routify:option name="example-app" -->
<div style="width: 100%; text-align: center; margin-top: 4rem;">
<img src="data:image/png;base64, {base64Logo}" alt="logo" style="max-width: 100%; padding-bottom: 128px" />
<div>
<b>Guide:</b>
<br />
<a href="https://routify.dev">https://routify.dev</a>
</div>
<br />
<div>
<b>This template:</b>
<br />
<a href="https://github.com/roxiness/routify-starter">
https://github.com/roxiness/routify-starter
</a>
</div>
</div>

View file

@ -0,0 +1,30 @@
<script>
import { url } from "@roxi/routify";
</script>
<style>
.layout-container :global(.card) {
width: 66%;
margin: 16px;
padding: 16px;
border-radius: 4px;
background: white;
box-shadow: 0px 5px 20px 5px rgba(0, 0, 0, 0.075);
display: inline-block;
}
* > :global(.layout-container) {
text-align: center;
}
</style>
<div >
<div class="layout-container">
<div class="card">
<a href={$url('./child')}>Child</a>
<slot>
<!-- optional fallback -->
</slot>
</div>
</div>
</div>

View file

@ -0,0 +1,15 @@
<script>
import { url } from "@roxi/routify";
</script>
<div class="layout-container">
<div class="card">
<a href={$url('./grandchild')}>Grandchild</a>
<slot>
<!-- optional fallback -->
</slot>
</div>
</div>

View file

@ -0,0 +1,13 @@
<script>
import { url } from "@roxi/routify";
</script>
<div class="layout-container">
<div class="card">
<slot>
<!-- optional fallback -->
</slot>
</div>
</div>

View file

@ -0,0 +1,3 @@
<div style="text-align: center">
<h4>I'm src/pages/example/nesting/child/grandchild/index.svelte</h4>
</div>

View file

@ -0,0 +1,3 @@
<div style="text-align: center">
<h1>I'm src/pages/example/nesting/child/index.svelte</h1>
</div>

View file

@ -0,0 +1,3 @@
<div style="text-align: center">
<h1>I'm src/pages/example/nesting/index.svelte</h1>
</div>

View file

@ -0,0 +1,74 @@
<script>
import { url, route } from "@roxi/routify";
$: match = $route.path.match(/\/modal\/([^\/]+)\//);
$: active = match && match[1];
</script>
<style>
* :global(.cards) {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
padding: 0;
}
* :global(.card) {
border-radius: 0.25rem;
border-width: 1px;
border: 1px solid #e2e8f0;
margin-bottom: 3rem;
padding: 2rem;
background: white;
list-style: none;
width: 25%;
position: relative;
cursor: pointer;
}
* :global(.container) {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
padding-top: 5rem;
background: rgba(0, 0, 0, 0.2);
}
* :global(.modal) {
margin: auto;
background: white;
font-size: 5rem;
border: 1px solid #e2e8f0;
width: 30%;
padding-top: 3rem;
padding-bottom: 3rem;
text-align: center;
}
.center {
text-align: center;
}
.active {
font-weight: bold;
}
</style>
<div data-routify="scroll-lock">
<div class="center">
<a href={$url('./basic')} class={active === 'basic' ? 'active' : ''}>
Basic
</a>
<a href={$url('./animated')} class={active === 'animated' ? 'active' : ''}>
Animated
</a>
</div>
<br />
<slot>
<!-- optional fallback -->
</slot>
</div>

View file

@ -0,0 +1,14 @@
<script>
import { goto } from "@roxi/routify";
export let scoped
$: ({ send, receive, key } = scoped);
</script>
<div class="container" on:click={() => $goto('./')} >
<div
class="modal"
in:receive|local={{ key: 'modal' }}
out:send|local={{ key: 'modal' }}>
{key}
</div>
</div>

View file

@ -0,0 +1,21 @@
<script>
import { url, context, afterPageLoad } from "@roxi/routify";
import { crossfade, fade } from "svelte/transition";
import Target from "./_target.svelte";
const [send, receive] = crossfade({});
$: _key = $context.child && $context.child.params.key
</script>
<div class="cards">
{#each Array(12) as item, key}
<a class="card" href={$url('./:key', { key })} style="background: #333">
<!-- <Target/> is a placeholder that takes the size of its parent element.
If a modal is show and its key matches this cards key, <Target/> is hidden.
This triggers the modal transition. -->
<Target {receive} {send} hide={key == _key} />
<div class="content" style="color: white">{key}</div>
</a>
{/each}
</div>
<slot scoped={{ send, receive, fade, key: _key }} />

View file

@ -0,0 +1,21 @@
<script>
export let send, receive, hide;
</script>
<style>
.canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
{#if !hide}
<div
class="canvas"
in:receive|local={{ key: 'modal' }}
out:send|local={{ key: 'modal' }} />
{/if}

View file

@ -0,0 +1,11 @@
<script>
import { goto, url } from "@roxi/routify";
export let key
</script>
<div class="container" on:click={() => $goto('./')} >
<div class="modal" >
{key}
</div>
</div>

View file

@ -0,0 +1,15 @@
<script>
import { url, params } from "@roxi/routify";
import { getContext } from "svelte";
</script>
<div class="cards">
{#each Array(12) as item, key}
<a class="card" href={$url('./:key', { key })}>
<div class="content">{key}</div>
</a>
{/each}
</div>
<slot />

View file

View file

@ -0,0 +1,24 @@
<script>
import { url } from '@roxi/routify'
</script>
<style>
.huge {
font-size: 12rem;
}
.e404 {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<div class="e404">
<div class="huge">404</div>
<div class="big">Page not found.
<!-- link to the parent folder of _fallback.svelte -->
<a href={$url('../')}>Go back</a>
</div>
</div>

View file

@ -0,0 +1,3 @@
<slot>
<!-- optional fallback -->
</slot>

View file

@ -0,0 +1,20 @@
<script>
import { url } from '@roxi/routify'
import base64Kevin from "../_components/assets/kevin.js";
</script>
<style>
.center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<img src="data:image/png;base64, {base64Kevin}" alt="KEVIN!" class="center" />
<a href={$url('../../')}>Go back</a>

View file

@ -0,0 +1,72 @@
<script>
import { url, isActive } from "@roxi/routify";
export let urls, height;
let linkElems = [];
let overlay;
let clientWidth
$: urlsWithElem = linkElems.map((elem, i) => ({ ...urls[i], elem }));
$: activeUrl = urlsWithElem.find(({active}) => active)
$: if (overlay && clientWidth && activeUrl) copyDimensions(activeUrl.elem, overlay);
$: color = activeUrl && activeUrl.color
function copyDimensions(source, target) {
target.style.left = source.offsetLeft + "px";
target.style.top = source.offsetTop + "px";
target.style.width = source.clientWidth + "px";
target.style.height = source.clientHeight + "px";
}
const saveElement = el => (linkElems = [...linkElems, el]);
</script>
<style>
nav {
width: 100%;
background: white;
display: flex;
position: fixed;
bottom: 0;
left: 0;
box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19);
justify-content: space-evenly;
}
a {
padding: 0 16px;
line-height: 100%;
font-weight: 500;
color: #aaa;
text-transform: uppercase;
text-decoration: none;
width: 100%;
text-align: center;
position: relative;
z-index: 10;
transition: all 0.8s;
/* transition-delay: 0.05s */
}
a.active {
color: #fff;
}
.overlay {
position: absolute;
/* background: #555; */
transition: 0.3s all;
background: linear-gradient(
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0.15)
)
}
</style>
<nav bind:clientWidth>
{#each urls as { name, path, active, href }, i}
<a
style="line-height: {height}"
{href}
class:active
use:saveElement>
{name}
</a>
{/each}
<div class="overlay" bind:this={overlay} style="background-color: {color}" />
</nav>

View file

@ -0,0 +1,49 @@
<script>
import { scale, fly } from "svelte/transition";
import { route } from "@roxi/routify";
import { Transition } from "@roxi/routify/decorators";
export let scoped;
const { width } = scoped;
const configs = [
{
// New and old route are identical, do nothing
condition: ({ routes }) => routes[0] === routes[1],
transition: () => {}
},
{
condition: c => c.toAncestor,
transition: scale,
inParams: { start: 1.2 },
outParams: { start: 0.8 }
},
{
condition: c => c.toDescendant,
transition: scale,
inParams: { start: 0.8 },
outParams: { start: 1.2 }
},
{
condition: c => c.toHigherIndex,
transition: fly,
inParams: { x: $width, duration: 500 },
outParams: { x: -$width, duration: 500 }
},
{
condition: c => c.toLowerIndex,
transition: fly,
inParams: { x: -$width, duration: 500 },
outParams: { x: $width, duration: 500 }
},
{
// No matching config. We don't want a transition
condition: () => true,
transition: () => {}
}
];
</script>
<Transition {configs}>
<slot />
</Transition>

View file

@ -0,0 +1,58 @@
<script>
import { TabsTransition } from "@roxi/routify/decorators";
import BottomNav from "./_components/BottomNav.svelte";
import { url, isActive } from "@roxi/routify";
const _urls = [
["./home", "Home", "#7fc5bb"],
["./feed", "Feed", "#0bf5cc"],
["./updates", "Updates", "#88f0d0"],
["./settings", "Settings", "#a1fac3"],
];
$: urls = _urls.map(([path, name, color]) => ({
name,
href: $url(path),
color,
active: !!$isActive(path),
}));
</script>
<style>
:global(body) {
padding: 0;
}
* :global(.inset) {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
main.inset {
bottom: 64px;
overflow: hidden;
}
* :global(*) {
text-align: center;
}
a {
position: fixed;
top: 0;
left: 0;
padding: 8px 16px;
background: #555;
color: white;
}
</style>
<div style="height: 100%">
<main class="inset">
<slot decorator={TabsTransition} />
</main>
<BottomNav {urls} height="64px" />
</div>
<a href={$url('../../')}>Back to examples</a>
<!-- routify:options bundle=true -->

View file

@ -0,0 +1,20 @@
<script>
export let id;
import { url } from "@roxi/routify";
</script>
<style>
a {
font-size: 1.5em;
padding: 12px 24px;
}
</style>
<div class="card" style="width: 512px;">
<h1>{id}</h1>
</div>
<br />
<a href={$url('../../home')}>Go home</a>
<a href={$url('../')}>Go back</a>

View file

@ -0,0 +1,19 @@
<style>
main {
background: #0bf5cc;
height: 100%;
}
* :global(.card) {
background: white;
width: 256px;
height: 256px;
display: inline-block;
box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19);
margin: 24px;
}
</style>
<!-- routify:options index=1 -->
<main>
<slot />
</main>

View file

@ -0,0 +1,13 @@
<script>
import { url } from "@roxi/routify";
</script>
<div style="padding-top: 20px">
<h1>Feed</h1>
</div>
{#each new Array(10) as item, id}
<a class="card" href={$url('./:id', { id })}>
<h3 class="item">{id}</h3>
</a>
{/each}

View file

@ -0,0 +1,13 @@
<style>
main {
background: #7fc5bb;
height: 100%;
overflow: auto;
}
</style>
<!-- routify:options index=0 -->
<main>
<br />
<h1>Home</h1>
</main>

View file

@ -0,0 +1,4 @@
<script>
import { redirect } from '@roxi/routify'
$redirect('./home')
</script>

View file

@ -0,0 +1,12 @@
<style>
main {
background: #a1fac3;
height: 100%;
}
</style>
<!-- routify:options index=3 -->
<main>
<br>
<h1>Settings</h1>
</main>

View file

@ -0,0 +1,14 @@
<style>
main {
background: #88F0D0;
height: 100%;
}
</style>
<!-- routify:options index=2 -->
<main>
<br />
<h1>Updates</h1>
</main>

View file

@ -0,0 +1,16 @@
<script>
import CrudWidget from "../_components/CrudWidget/Index.svelte";
import { users } from "../_data";
</script>
<div style="text-align: center">
<p>
By using a _fallback.svelte in example/widget, we can grab the leftover URL
and pass it to an embedded widget.
</p>
<p>Alternatively, the widget can grab the leftover URL itself.</p>
<p>This allows for reusable navigable components.</p>
</div>
<CrudWidget data={users} />

10
src/pages/index.svelte Normal file
View file

@ -0,0 +1,10 @@
<script>
import { metatags } from '@roxi/routify'
import { chart } from "../chart";
metatags.title = 'My Routify app'
metatags.description = 'Description coming soon...'
var opts = {};
</script>
<div use:chart={opts} />

108
src/sw.js Normal file
View file

@ -0,0 +1,108 @@
// @ts-check
import { registerRoute, setDefaultHandler, setCatchHandler } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { skipWaiting, clientsClaim } from 'workbox-core';
import { precacheAndRoute, matchPrecache } from 'workbox-precaching';
import { ExpirationPlugin } from 'workbox-expiration';
import { RoutifyPlugin, freshCacheData } from '@roxi/routify/workbox-plugin'
/**********
* CONFIG *
**********/
const entrypointUrl = '__app.html' // entrypoint
const fallbackImage = '404.svg'
const files = self.__WB_MANIFEST // files matching globDirectory and globPattern in rollup.config.js
const externalAssetsConfig = () => ({
cacheName: 'external',
plugins: [
RoutifyPlugin({
validFor: 60 // cache is considered fresh for n seconds.
}),
new ExpirationPlugin({
maxEntries: 50, // last used entries will be purged when we hit this limit
purgeOnQuotaError: true // purge external assets on quota error
})]
})
/**************
* INITIALIZE *
**************/
/**
* precache all files
* remember to precache __app.html and 404.svg if caching of all files is disabled
*/
precacheAndRoute(files)
/** precache only fallback files */
// precacheAndRoute(files.filter(file =>
// ['__app.html', '404.svg']
// .includes(file.url)
// ))
skipWaiting() // auto update service workers across all tabs when new release is available
clientsClaim() // take control of client without having to wait for refresh
/**
* manually upgrade service worker by sending a SKIP_WAITING message.
* (remember to disable skipWaiting() above)
*/
// addEventListener('message', event => { if (event.data && event.data.type === 'SKIP_WAITING') skipWaiting(); });
/**********
* ROUTES *
**********/
// serve local pages from the SPA entry point (__app.html)
registerRoute(isLocalPage, matchPrecache(entrypointUrl))
// serve local assets from cache first
registerRoute(isLocalAsset, new CacheFirst())
// serve external assets from cache if they're fresh
registerRoute(hasFreshCache, new CacheFirst(externalAssetsConfig()))
// serve external pages and assets
setDefaultHandler(new NetworkFirst(externalAssetsConfig()));
// serve a fallback for 404s if possible or respond with an error
setCatchHandler(async ({ event }) => {
switch (event.request.destination) {
case 'document':
return await matchPrecache(entrypointUrl)
case 'image':
return await matchPrecache(fallbackImage)
default:
return Response.error();
}
})
/**********
* CONDITIONS *
**********/
function isLocalAsset({ url, request }) { return url.host === self.location.host && request.destination != 'document' }
function isLocalPage({ url, request }) { return url.host === self.location.host && request.destination === 'document' }
function hasFreshCache(event) { return !!freshCacheData(event) }
/** Example condition */
function hasWitheringCache(event) {
const cache = freshCacheData(event)
if (cache) {
const { cachedAt, validFor, validLeft, validUntil } = cache
// return true if half the fresh time has passed
return validFor / 2 > validFor - validLeft
}
}

17
vercel.json Normal file
View file

@ -0,0 +1,17 @@
{
"version": 2,
"functions": {
"api/vercel-ssr/index.js": {
"includeFiles": "dist/**"
}
},
"routes": [
{
"handle": "filesystem"
},
{
"src": "/.*",
"dest": "/api/vercel-ssr/index.js"
}
]
}