Initial commit

This commit is contained in:
2023-04-30 19:10:59 +02:00
commit 9ce11fe589
22 changed files with 562 additions and 0 deletions

29
src/components/App.vue Normal file
View File

@@ -0,0 +1,29 @@
<template lang="pug">
Header Vue Example App
Navigation
Content.content
Footer © Dietrich
</template>
<script lang="ts">
import Header from './Header.vue'
import Navigation from './Navigation.vue'
import Content from './Content.vue'
import Footer from './Footer.vue'
export default {
components: { Header, Navigation, Content, Footer },
}
</script>
<style lang="sass">
body > #app
height: 100vh
display: flex
flex-direction: column
align-items: stretch
> .content
flex-grow: 1
</style>

View File

@@ -0,0 +1,33 @@
<template lang="pug">
main
RouterView(v-slot="{ Component }")
Transition
KeepAlive
Component.page(:is="Component")
</template>
<style lang="sass" scoped>
main
position: relative
overflow: hidden
font-size: 1.5em
background: #16a085
> .page
position: absolute
width: 100%
height: 100%
display: flex
flex-direction: column
overflow-y: auto
padding: 1em 4em
background: inherit
> .page.v-leave-active, > .page.v-enter-active
transition: transform 0.3s ease
> .page.v-enter-from
transform: translateY(100%)
</style>

15
src/components/Footer.vue Normal file
View File

@@ -0,0 +1,15 @@
<template lang="pug">
footer
span
slot
RouterLink(to="/imprint") Imprint
</template>
<style lang="sass" scoped>
footer
display: flex
align-items: center
justify-content: space-around
height: 3em
background: #2c3e50
</style>

26
src/components/Header.vue Normal file
View File

@@ -0,0 +1,26 @@
<template lang="pug">
header
RouterLink(to="/")
img(src="../img/logo.svg")
h1
slot
</template>
<style lang="sass" scoped>
header
display: flex
align-items: center
justify-content: center
background: #2c3e50
a
position: absolute
display: block
left: 1em
width: 3em
height: 3em
img
width: 100%
height: 100%
</style>

View File

@@ -0,0 +1,32 @@
<template lang="pug">
nav
template(v-for="route in $router.options.routes")
RouterLink(:to="route.path" v-if="route.meta.nav") {{ route.meta.nav }}
</template>
<script lang="ts">
export default {
props: ['items'],
emits: ['navigate'],
}
</script>
<style lang="sass" scoped>
nav
display: flex
align-items: stretch
justify-content: center
height: 3em
a
cursor: pointer
flex-basis: 0
flex-grow: 1
display: flex
align-items: center
justify-content: center
background: #34495e
&.router-link-active, &:hover
background: #8e44ad
</style>

View File

@@ -0,0 +1,14 @@
<template lang="pug">
section
h2 About
p
| This is an example single page application built in Vue.js.
p
| It uses nested components, routing, composables and the fancy language
| extensions pug, typescript and sass, that compile to html, javascript and
| css.
p
| To make developing and building the project a breeze, the zero
| configuration build tool parcel is used.
</template>

View File

@@ -0,0 +1,40 @@
<template lang="pug">
section
#text-container You clicked #[span.counter {{ counterText }}].
#button-container
button(@click="counter = (counter > 0) ? counter - 1 : 0") -
button(@click="counter++") +
</template>
<script lang="ts">
export default {
data: () => ({
counter: 0
}),
computed: {
counterText() {
switch (this.counter) {
case 0 : return "not once"
case 1 : return "once"
default : return `${this.counter} times`
}
}
},
}
</script>
<style lang="sass" scoped>
section
align-items: center
justify-content: center
gap: 1em
font-size: 1.5em
span.counter
color: #bdc3c7
button
width: 2em
height: 2em
font-size: inherit
</style>

View File

@@ -0,0 +1,65 @@
<template lang="pug">
section
h2 Imprint
p
| Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
| tempor incididunt ut labore et dolore magna aliqua. Tempor commodo
| ullamcorper a lacus. Id neque aliquam vestibulum morbi blandit cursus
| risus at ultrices. Turpis tincidunt id aliquet risus feugiat in ante.
| Morbi blandit cursus risus at ultrices mi. Fringilla phasellus faucibus
| scelerisque eleifend donec pretium vulputate. Ac feugiat sed lectus
| vestibulum. Placerat duis ultricies lacus sed turpis tincidunt id aliquet
| risus. Nunc faucibus a pellentesque sit. Enim lobortis scelerisque
| fermentum dui faucibus in ornare quam viverra. Sagittis vitae et leo duis
| ut diam. Sed faucibus turpis in eu mi bibendum neque egestas congue.
p
| Pharetra sit amet aliquam id diam maecenas ultricies mi. Neque convallis
| a cras semper auctor neque. Est sit amet facilisis magna etiam tempor
| orci eu. Fermentum iaculis eu non diam phasellus vestibulum lorem sed
| risus. Pellentesque pulvinar pellentesque habitant morbi tristique
| senectus et. Elementum pulvinar etiam non quam lacus. Ac turpis egestas
| integer eget aliquet. Volutpat ac tincidunt vitae semper quis. Nisl nunc
| mi ipsum faucibus vitae aliquet nec ullamcorper sit. Ultricies integer
| quis auctor elit sed vulputate. In est ante in nibh mauris cursus mattis
| molestie. Suspendisse in est ante in nibh mauris cursus mattis molestie.
| Vel orci porta non pulvinar neque laoreet suspendisse. Massa tincidunt
| nunc pulvinar sapien et ligula ullamcorper malesuada proin. Id semper
| risus in hendrerit gravida.
p
| Risus at ultrices mi tempus imperdiet. Nulla pharetra diam sit amet nisl
| suscipit. Pretium lectus quam id leo in vitae turpis massa sed. Neque
| vitae tempus quam pellentesque nec nam aliquam sem et. Dui id ornare arcu
| odio ut sem nulla pharetra. Ligula ullamcorper malesuada proin libero
| nunc consequat. At varius vel pharetra vel turpis nunc eget lorem.
| Sodales neque sodales ut etiam sit amet nisl purus in. Pharetra diam sit
| amet nisl suscipit. Vitae nunc sed velit dignissim sodales ut eu sem.
| Blandit massa enim nec dui nunc mattis enim ut tellus. Habitant morbi
| tristique senectus et netus et malesuada fames. In eu mi bibendum neque
| egestas.
p
| Morbi blandit cursus risus at. Viverra ipsum nunc aliquet bibendum enim
| facilisis gravida. Diam in arcu cursus euismod. Imperdiet proin fermentum
| leo vel orci porta non pulvinar. Volutpat blandit aliquam etiam erat
| velit scelerisque in dictum non. Scelerisque felis imperdiet proin
| fermentum leo vel. Ullamcorper malesuada proin libero nunc consequat
| interdum varius sit. Nunc sed augue lacus viverra vitae congue eu
| consequat ac. Etiam dignissim diam quis enim lobortis scelerisque
| fermentum dui faucibus. Cursus euismod quis viverra nibh cras pulvinar
| mattis nunc. Velit scelerisque in dictum non consectetur a. Nunc aliquet
| bibendum enim facilisis gravida neque convallis a cras. Aliquet enim
| tortor at auctor urna nunc id cursus. Sed euismod nisi porta lorem mollis
| aliquam ut porttitor leo. Suspendisse in est ante in nibh. Cursus metus
| aliquam eleifend mi in nulla. Sit amet consectetur adipiscing elit duis
| tristique sollicitudin nibh sit. Enim eu turpis egestas pretium aenean
| pharetra magna ac placerat. Amet nulla facilisi morbi tempus. Nibh sit
| amet commodo nulla facilisi nullam vehicula.
p
| Id aliquet lectus proin nibh nisl. Dui ut ornare lectus sit amet est
| placerat. Ultrices dui sapien eget mi proin sed. Enim nulla aliquet
| porttitor lacus luctus accumsan tortor posuere ac. Odio ut enim blandit
| volutpat maecenas volutpat blandit. Sed nisi lacus sed viverra tellus.
| Faucibus a pellentesque sit amet porttitor eget dolor. In arcu cursus
| euismod quis. Ultrices mi tempus imperdiet nulla. Curabitur vitae nunc
| sed velit dignissim sodales ut eu sem.
</template>

View File

@@ -0,0 +1,11 @@
<template lang="pug">
section
h2 Welcome
p Go look around and click some buttons!
</template>
<style lang="sass" scoped>
section
align-items: center
justify-content: center
</style>

View File

@@ -0,0 +1,43 @@
<template lang="pug">
section
ErrorBox(v-if="joke.error !== null" @click="loadJoke").error
| {{ joke.error }}
Spinner(v-else-if="joke.setup === null" color="white")
.content(v-else @click="loadJoke")
| {{ joke.setup }}
.punchline {{ joke.punchline || "" }}
</template>
<script lang="ts">
import { useJokeAPI } from '/src/composables/jokeAPI.ts'
export default {
setup() {
let { joke, loadJoke } = useJokeAPI()
return { joke, loadJoke }
},
async mounted() {
await this.loadJoke()
},
}
</script>
<style lang="sass" scoped>
section
align-items: center
justify-content: center
.error
cursor: pointer
.content
cursor: pointer
font-family: "Times New Roman"
font-size: 1.5em
text-align: center
.punchline
margin-top: 1em
font-size: 0.8em
font-style: italic
</style>

View File

@@ -0,0 +1,11 @@
<template lang="pug">
section
h2 Not Found
p Whoopsy daisy! This page doesn't seem to exist :(
</template>
<style lang="sass" scoped>
section
align-items: center
justify-content: center
</style>

View File

@@ -0,0 +1,29 @@
import Index from './Index.vue'
import Counter from './Counter.vue'
import Joke from './Joke.vue'
import About from './About.vue'
import Imprint from './Imprint.vue'
import NotFound from './NotFound.vue'
export default [
{
path: "/", component: Index,
meta: { nav: false, title: "Welcome to my Vue example" },
}, {
path: "/counter", component: Counter,
meta: { nav: "Counter", title: "Vue counter" },
}, {
path: "/joke", component: Joke,
meta: { nav: "Joke", title: "Vue joke API consumer" },
}, {
path: "/about", component: About,
meta: { nav: "About", title: "About this lorem ipsum" },
}, {
path: "/imprint", component: Imprint,
meta: { nav: false, title: "Vue example imprint" },
}, {
path: "/:path(.*)", component: NotFound,
meta: { nav: false, title: "Vue example page not found" },
},
]

View File

@@ -0,0 +1,11 @@
<template lang="pug">
div
slot
</template>
<style lang="sass" scoped>
div
color: #e74c3c
background: rgba(255, 255, 255, 0.75)
padding: 1em
</style>

View File

@@ -0,0 +1,38 @@
<template lang="pug">
.spinner(:style="style")
</template>
<script lang="ts">
export default {
props: {
size: { default: "3em" },
color: { default: "black" },
width: { default: "0.3em" },
period: { default: "2s" },
},
computed: {
style() {
return {
width: this.size,
height: this.size,
borderColor: `${this.color} transparent`,
borderWidth: this.width,
animationDuration: this.period,
}
}
},
}
</script>
<style lang="sass" scoped>
.spinner
border-style: solid
border-radius: 50%
animation: spin 2s linear infinite
@keyframes spin
0%
transform: rotate(0)
100%
transform: rotate(360deg)
</style>

View File

@@ -0,0 +1,63 @@
import { reactive } from 'vue'
const DEFAULT_BASE = 'https://official-joke-api.appspot.com'
const DEFAULT_URL = `${DEFAULT_BASE}/jokes/programming/random`
const DEFAULT_CHAR_DELAY = 20
const DEFAULT_PUNCHLINE_DELAY = 2000
export class Joke {
error : string | null = null
setup : string | null = null
punchline : string = ""
reset() {
this.error = null
this.setup = null
this.punchline = ""
}
}
export function useJokeAPI(
url: string = DEFAULT_URL,
charDelay: int = DEFAULT_CHAR_DELAY,
punchlineDelay: int = DEFAULT_PUNCHLINE_DELAY
) {
let joke = reactive(new Joke())
let working = false
async function loadJoke(): bool {
if (working) return false
working = true
// Load joke
joke.reset()
try {
let res = await fetch(url)
var [{ setup, punchline }] = await res.json()
} catch (e) {
console.error(e)
}
// Animate joke appearance
if (setup && punchline) {
for (let i = 0; i <= setup.length; i++) {
joke.setup = setup.substr(0, i)
await sleep(charDelay)
}
await sleep(punchlineDelay)
for (let i = 0; i <= punchline.length; i++) {
joke.punchline = punchline.substr(0, i)
await sleep(charDelay)
}
} else {
joke.error = "Loading joke failed"
}
working = false
}
return { joke, loadJoke }
}
async function sleep(ms: number) {
await new Promise((res, rej) => setTimeout(res, ms))
}

2
src/img/logo.svg Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="128" height="128" enable-background="new" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><path d="m63.5-1.35e-7c-0.612 0.00401-1.22 0.0247-1.82 0.0593-9.95 0.586-19.2 4.75-26.3 11.8-8.09 8.09-12.3 19.1-11.8 30.5 0.115 2.4 0.264 3.64 0.69 5.85 0.678 3.48 1.69 6.49 3.27 9.76 1.48 3.08 2.87 5.27 4.98 7.89 2.87 3.56 4.16 6.24 6.16 12.8 0.23 0.758 0.621 1.8 0.862 2.32 1.63 3.5 4.56 6.08 8.04 7.08 1.96 0.563 2.41 0.598 7.99 0.598h5.29l0.919-0.322c1.15-0.402 1.91-0.838 2.98-1.69 2.92-2.31 8.13-9.92 8.62-16.8 0.492-6.86-1.18-18.2-1.21-24 0-6.24 0.411-13.9 0.985-18.4h10.3v-7.3h-28.7c-4.43 0-5.99 0.574-8.37 2.87-2.3 2.3-3.61 4.68-5.01 9.6h1.64c2.71-4.27 4.1-5.17 8.04-5.17h3.77l-2.13 18.2c-0.328 2.3-1.39 4.1-4.18 6.65-2.54 2.3-3.28 3.61-3.28 5.42 0 2.46 1.97 4.51 4.51 4.51 2.54 0 4.51-1.89 5.58-5.42 0.739-2.13 1.89-10.5 2.63-18.8l0.985-10.6h8.94c-0.672 8.24-2.08 16.9-1.28 25.5 0.807 8.64 1.85 18.4-1.71 22.6-3.07 3.67-4.41 5.03-5.37 5.49l-0.747 0.356-3.91-0.0101c-4.34 0-4.87-0.0686-6.19-0.758-1.56-0.827-2.57-2.33-3.3-4.92-1.18-4.26-3.03-8.47-5.09-11.6-0.402-0.598-1.26-1.77-1.93-2.6-4.07-5.07-6.33-10.2-7.29-16.5-0.253-1.63-0.334-5.68-0.173-7.48 0.609-6.48 3.09-12.7 7.14-17.7 1.18-1.49 3.58-3.9 5.12-5.12 4.67-3.73 10-6.06 16.1-7.02 1.88-0.299 7.3-0.299 9.19 0 5.44 0.85 10.4 2.85 14.6 5.8 6.53 4.69 11.1 11.4 13 19.1 1.16 4.7 1.3 8.97 0.472 13.9-1.01 6-3.54 11.8-7 16.1-1.62 2.01-1.98 2.48-2.76 3.69-3.06 4.69-5.07 10.5-5.87 17.1-0.0689 0.563-0.195 2.28-0.275 3.79-0.172 3.19-0.207 3.3-1.38 4.52-1.87 1.95-5.52 3.52-10.7 4.55-4.65 0.942-9.1 1.38-15.9 1.54l-3.45 0.0916-0.724 0.391c-1.23 0.655-1.77 1.53-1.84 3.01-0.0459 0.873-0.0104 1.06 0.23 1.61 0.379 0.816 0.862 1.36 1.61 1.76 0.575 0.322 0.701 0.345 2.05 0.368 12.4 0.253 14.2 0.321 17.8 0.666 5.01 0.483 8.33 1.42 9.69 2.75 0.701 0.678 0.782 1.01 0.356 1.46-0.724 0.747-2.62 1.32-5.68 1.71-3.07 0.391-5.32 0.471-14.4 0.54-8.28 0.0578-8.88 0.08-9.43 0.276-0.816 0.31-1.46 0.908-1.87 1.74-0.31 0.609-0.356 0.827-0.356 1.64 0 0.827 0.0462 1.03 0.356 1.66 0.241 0.494 0.54 0.862 0.931 1.16 0.931 0.747 1.3 0.804 4.96 0.804 3.73 0 9.23 0.172 11.4 0.345 5.32 0.437 9 1.45 11.7 3.21 0.931 0.609 2.44 2.1 3.26 3.23 0.885 1.2 0.988 1.29 1.85 1.68 1.31 0.598 2.87 0.356 4-0.632 0.873-0.77 1.3-2.31 1-3.57-0.333-1.4-2.65-4.21-5.01-6.04l-0.609-0.471 0.425-0.195c0.747-0.368 2-1.22 2.63-1.83 1.28-1.21 2.11-2.7 2.47-4.41 0.218-1.07 0.218-1.18 0.0459-2.36-0.471-3.3-2.59-5.91-6.07-7.49l-0.839-0.38 0.931-0.471c2.1-1.06 4.34-2.86 5.58-4.5 0.689-0.908 1.61-2.77 1.87-3.79 0.115-0.437 0.23-1.62 0.299-2.87 0.299-5.99 0.965-9.57 2.46-13.4 1.13-2.88 2.54-5.31 4.42-7.57 3.93-4.72 6.64-10.1 8.26-16.5 2.95-11.6 1.07-23.3-5.29-33-5.04-7.66-12.8-13.5-21.4-16.3-4.01-1.29-8.6-1.96-12.9-1.92z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

10
src/index.pug Normal file
View File

@@ -0,0 +1,10 @@
doctype html
html(lang="en")
head
meta(charset="utf-8")
title Hello Seb
link(rel="stylesheet" href="index.sass")
body
#app
script(src="index.ts" type="module")

16
src/index.sass Normal file
View File

@@ -0,0 +1,16 @@
html, body
margin: 0
min-height: 100%
font-family: sans-serif
color: white
*
box-sizing: border-box
h1, h2, h3, h4, h5
font-weight: normal
text-align: center
a
text-decoration: none
color: inherit

18
src/index.ts Normal file
View File

@@ -0,0 +1,18 @@
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './components/App.vue'
import routes from './components/routes/routes.ts'
const history = createWebHistory()
const router = createRouter({ history, routes })
router.afterEach((to, from) => { document.title = to.meta.title })
import ErrorBox from './components/widgets/ErrorBox.vue'
import Spinner from './components/widgets/Spinner.vue'
const app = createApp(App)
app.use(router)
app.component('ErrorBox', ErrorBox)
app.component('Spinner', Spinner)
app.mount("#app")