This commit is contained in:
2020-07-01 23:47:51 +02:00
parent 3adef6ee28
commit 3b38a8477b
39 changed files with 428 additions and 241 deletions

View File

@@ -1,32 +1,16 @@
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<h1>Bons programmeurs</h1>
<router-view />
</div>
</template>
<style lang="scss">
@import '@/styles/app';
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/sounds/move.mp3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,27 @@
<template>
<div class="dream-maker">
<h2>Coder {{ position }}</h2>
<h4 v-show="isTurn">{{ session }}</h4>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api'
export default defineComponent({
name: 'DreamMaker',
props: {
position: { type: String, required: true },
isTurn: { type: Boolean, required: true },
session: { type: String, required: true }
}
})
</script>
<style lang="scss" scoped>
.dream-maker {
h4 {
font-size: 24pt;
}
}
</style>

View File

@@ -1,148 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
target="_blank"
rel="noopener"
>typescript</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa"
target="_blank"
rel="noopener"
>pwa</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
target="_blank"
rel="noopener"
>router</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
target="_blank"
rel="noopener"
>vuex</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "HelloWorld",
props: {
msg: String
}
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -1,13 +1,16 @@
import Vue from "vue";
import App from "./App.vue";
import "./registerServiceWorker";
import router from "./router";
import store from "./store";
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import VueCompositionAPI from '@vue/composition-api'
Vue.config.productionTip = false;
Vue.use(VueCompositionAPI)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
render: (h) => h(App)
}).$mount('#app')

View File

@@ -1,34 +1,34 @@
/* eslint-disable no-console */
import { register } from "register-service-worker";
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === "production") {
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
"App is being served from cache by a service worker.\n" +
"For more details, visit https://goo.gl/AFskqB"
);
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered() {
console.log("Service worker has been registered.");
console.log('Service worker has been registered.')
},
cached() {
console.log("Content has been cached for offline use.");
console.log('Content has been cached for offline use.')
},
updatefound() {
console.log("New content is downloading.");
console.log('New content is downloading.')
},
updated() {
console.log("New content is available; please refresh.");
console.log('New content is available; please refresh.')
},
offline() {
console.log(
"No internet connection found. App is running in offline mode."
);
'No internet connection found. App is running in offline mode.'
)
},
error(error) {
console.error("Error during service worker registration:", error);
console.error('Error during service worker registration:', error)
}
});
})
}

View File

@@ -1,30 +1,21 @@
import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import Home from "../views/Home.vue";
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter);
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: "/",
name: "Home",
path: '/',
name: 'Home',
component: Home
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue")
}
];
]
const router = new VueRouter({
mode: "history",
mode: 'history',
base: process.env.BASE_URL,
routes
});
})
export default router;
export default router

4
src/shims-tsx.d.ts vendored
View File

@@ -1,4 +1,4 @@
import Vue, { VNode } from "vue";
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
@@ -7,7 +7,7 @@ declare global {
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
[elem: string]: any
}
}
}

6
src/shims-vue.d.ts vendored
View File

@@ -1,4 +1,4 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@@ -1,11 +1,11 @@
import Vue from "vue";
import Vuex from "vuex";
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
});
})

31
src/styles/app.scss Normal file
View File

@@ -0,0 +1,31 @@
@import url('https://fonts.googleapis.com/css2?family=Anonymous+Pro&display=swap');
$color: #f8efba;
body {
text-align: center;
font-family: 'Anonymous Pro', monospace;
background-color: #2c3a47;
color: $color;
margin: 0;
}
button {
font-family: 'Anonymous Pro', monospace;
border: none;
padding: 1rem;
text-decoration: none;
background: #0069ed;
color: $color;
cursor: pointer;
transition: background 250ms ease-in-out, transform 150ms ease;
}
button:hover,
button:focus {
background: #0053ba;
}
button:active {
transform: scale(0.99);
}

19
src/utils/notification.ts Normal file
View File

@@ -0,0 +1,19 @@
declare const Notification: any
export const notify = (message: string) => {
if (!('Notification' in window)) {
return
} else if (Notification.permission === 'granted') {
new Notification(message)
} else if (Notification.permission !== 'denied') {
Notification.requestPermission((permission: any) => {
if (!('permission' in Notification)) {
Notification.permission = permission
}
if (permission === 'granted') {
new Notification(message)
}
})
}
}

View File

@@ -1,18 +1,156 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
<section class="brogrammers">
<DreamMaker
position="1"
:is-turn="isProgrammer1Turn"
:session="session"
/>
<DreamMaker
position="2"
:is-turn="!isProgrammer1Turn"
:session="session"
/>
</section>
<div class="global-time">{{ time }}</div>
<div class="actions">
<button v-if="timeState === 'stopped'" @click="start">play</button>
<button v-if="timeState === 'started'" @click="stop">stop</button>
</div>
<youtube-video
v-show="false"
src="https://www.youtube-nocookie.com/embed/5qap5aO4i9A"
/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
<script lang="ts">
import 'youtube-video-js'
import {
ref,
watchEffect,
defineComponent,
onMounted,
computed,
onUnmounted
} from '@vue/composition-api'
import DreamMaker from '@/components/DreamMaker.vue'
import { notify } from '@/utils/notification'
export default {
name: "Home",
const moveSound = new Audio(require('@/assets/sounds/move.mp3'))
type Stopwatch = 'stopped' | 'started'
const INTERVAL = 120
export default defineComponent({
name: 'Home',
components: {
HelloWorld
DreamMaker
},
setup() {
let chilledcow: HTMLVideoElement | null = null
let stopwatchId: number | null = null
const isProgrammer1Turn = ref(true)
let stopWatchSessionId: number | null = null
const seconds = ref(0)
const time = computed(() => {
const m = Math.floor(seconds.value / 60)
const s = seconds.value % 60
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
})
const sessionSeconds = ref(INTERVAL)
const session = computed(() => {
const m = Math.floor(sessionSeconds.value / 60)
const s = sessionSeconds.value % 60
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
})
const timeState = ref<Stopwatch>('stopped')
const start = () => {
timeState.value = 'started'
notify('Session started!')
stopwatchId = setInterval(() => {
seconds.value++
if (seconds.value % INTERVAL === 0) {
sessionSeconds.value = INTERVAL
isProgrammer1Turn.value = !isProgrammer1Turn.value
const programmer = isProgrammer1Turn.value
? 'Programmer 1'
: 'Programmer 2'
notify(`Time to change, ${programmer}, your turn!`)
moveSound.play()
}
}, 1000)
stopWatchSessionId = setInterval(() => {
sessionSeconds.value--
}, 1000)
}
const stop = () => {
timeState.value = 'stopped'
}
onMounted(() => {
chilledcow = document.querySelector('youtube-video')
})
const clear = () => {
if (stopwatchId) {
clearInterval(stopwatchId)
}
if (stopWatchSessionId) {
clearInterval(stopWatchSessionId)
}
seconds.value = 0
sessionSeconds.value = INTERVAL
}
onUnmounted(clear)
watchEffect(() => {
switch (timeState.value) {
case 'started':
if (chilledcow) {
chilledcow.play()
}
break
case 'stopped':
if (chilledcow) {
chilledcow.pause()
}
clear()
}
})
return {
start,
stop,
timeState,
time,
session,
isProgrammer1Turn
}
}
};
})
</script>
<style lang="scss" scoped>
.home {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-around;
}
.brogrammers {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.global-time {
font-size: 80pt;
padding: 1rem;
}
</style>