🎉
This commit is contained in:
28
src/App.vue
28
src/App.vue
@@ -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
BIN
src/assets/sounds/move.mp3
Normal file
Binary file not shown.
27
src/components/DreamMaker.vue
Normal file
27
src/components/DreamMaker.vue
Normal 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>
|
||||
@@ -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>
|
||||
19
src/main.ts
19
src/main.ts
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
4
src/shims-tsx.d.ts
vendored
@@ -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
6
src/shims-vue.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
|
||||
@@ -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
31
src/styles/app.scss
Normal 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
19
src/utils/notification.ts
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user