- Added component and view for manual configuring and playing patterns.

- Added component NavigationList for routing over vue-router views.
- Rename component WaveloversApp, now is a AppPatterns.
- Rename HomeView, now is a PatternView.
- Modified function vibrate in MGamepads and change function in index vuex.
- Root component App now has mounted and unmounted event listeners for gamepads adding and deleteting functions.
- Fixed CSS rules for inputs and input[type=range].
- Added new rules for input[type=range].
This commit is contained in:
Eugene Serb 2022-08-07 21:29:22 +03:00
parent c5631603ac
commit ebe813e7d3
12 changed files with 358 additions and 97 deletions

View File

@ -1,6 +1,6 @@
/* ------------------------------ */ /* ------------------------------ */
/* Wavelovers styles */ /* Wavelovers styles */
/* version: dated 2022.07.25 */ /* version: dated 2022.08.07 */
/* author: Eugene Serb */ /* author: Eugene Serb */
/* ------------------------------ */ /* ------------------------------ */
@ -205,7 +205,7 @@ fieldset {
padding: 8px; padding: 8px;
} }
button, input, textarea, select { button, textarea, select, input {
padding: 4px 8px; padding: 4px 8px;
border: 2px solid var(--color-link); border: 2px solid var(--color-link);
border-radius: var(--number-border-radius); border-radius: var(--number-border-radius);
@ -215,7 +215,6 @@ button, input, textarea, select {
line-height: 1.382em; line-height: 1.382em;
white-space: nowrap; white-space: nowrap;
} }
button:hover, input:hover, button:hover, input:hover,
textarea:hover, select:hover { textarea:hover, select:hover {
border: 2px solid var(--color-link-hover); border: 2px solid var(--color-link-hover);
@ -234,6 +233,114 @@ option {
color: var(--color-white); color: var(--color-white);
} }
input[type=range] {
width: 100%;
margin: 8px 0;
padding: 0;
border: 0px solid var(--color-link);
-webkit-appearance: none;
}
input[type=range]:hover {
margin: 8px 0;
padding: 0;
border: 0px solid var(--color-link-hover);
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
cursor: pointer;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
background: var(--color-b);
border-radius: 8px;
border: 0px solid #000101;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 8px;
background: var(--color-a);
cursor: pointer;
-webkit-appearance: none;
margin-top: -4px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: var(--color-b);
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8px;
cursor: pointer;
animate: 0.2s;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
background: var(--color-b);
border-radius: 8px;
border: 0px solid #000101;
}
input[type=range]::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 8px;
background: var(--color-a);
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 8px;
cursor: pointer;
animate: 0.2s;
background: transparent;
border-color: transparent;
border-width: 8px 0;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: var(--color-b);
border: 0px solid #000101;
border-radius: 8px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: var(--color-b);
border: 0px solid #000101;
border-radius: 8px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 8px;
background: var(--color-a);
cursor: pointer;
}
input[type=range]:focus::-ms-fill-lower {
background: var(--color-b);
}
input[type=range]:focus::-ms-fill-upper {
background: var(--color-b);
}
/* ------ */ /* ------ */
/* TABLES */ /* TABLES */
/* ------ */ /* ------ */

View File

@ -1,12 +1,53 @@
<template> <template>
<div class="wavelovers">
<NavigationList />
<router-view /> <router-view />
<GamepadList v-if="gamepads.length > 0"
:gamepads="gamepads" />
<MessageItem v-else>Press any gamepad button or connect a new gamepad to vibrate.</MessageItem>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import store from '@/store/index';
import NavigationList from '@/components/NavigationList.vue';
import GamepadList from '@/components/GamepadList.vue';
import MessageItem from '@/components/MessageItem.vue';
import Vibrator from '@/models/Vibrator';
export default defineComponent({ export default defineComponent({
name: 'App', name: 'App',
components: {
NavigationList: NavigationList,
GamepadList: GamepadList,
MessageItem: MessageItem,
},
computed: {
gamepads: function (): Vibrator[] {
return store.getters.gamepads as Vibrator[];
},
},
methods: {
addEventListeners(): void {
window.addEventListener('gamepadconnected', (event: GamepadEvent) => store.dispatch('addGamepad', event));
window.addEventListener('gamepaddisconnected', (event: GamepadEvent) => store.dispatch('deleteGamepad', event));
},
removeEventListeners(): void {
window.removeEventListener('gamepadconnected', (event: GamepadEvent) => store.dispatch('addGamepad', event));
window.removeEventListener('gamepaddisconnected', (event: GamepadEvent) => store.dispatch('deleteGamepad', event));
},
},
mounted() {
this.addEventListeners();
},
unmounted() {
this.removeEventListeners();
},
}); });
</script> </script>
<style lang="scss">
</style>

View File

@ -0,0 +1,84 @@
<template>
<div class="content-item app-manual">
<fieldset class="manual-form">
<label class="manual-form__inputs">
<span>Start Delay (ms)</span>
<input v-model="startDelay"
type="number" placeholder="Start Delay"
min="0" max="1000" step="25" required />
</label>
<label class="manual-form__inputs">
<span>Duration (ms)</span>
<input v-model="duration"
type="number" placeholder="Duration"
min="0" max="1000" step="25" required />
</label>
<label class="manual-form__inputs">
<span>Weak Magnitude</span>
<input v-model="weakMagnitude"
type="range" placeholder="Weak Magnitude"
min="0.0" max="1.0" step="0.01" required />
</label>
<label class="manual-form__inputs">
<span>Strong Magnitude</span>
<input v-model="strongMagnitude"
type="range" placeholder="Strong Magnitude"
min="0.0" max="1.0" step="0.01" required />
</label>
<button @click="start">Start</button>
<button @click="stop">Stop</button>
</fieldset>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import store from '@/store/index';
import TPatternUnit from '@/models/TPatternUnit';
export default defineComponent({
name: 'AppManual',
data: () => {
return {
startDelay: 250 as number,
duration: 250 as number,
weakMagnitude: 1 as number,
strongMagnitude: 1 as number,
};
},
methods: {
createPatternUnit: function (): TPatternUnit[] {
const patternUnit: TPatternUnit[] = [{
startDelay: this.startDelay,
duration: this.duration,
weakMagnitude: this.weakMagnitude,
strongMagnitude: this.strongMagnitude,
},
];
return patternUnit as TPatternUnit[];
},
start: function (): void {
store.dispatch('startCustom', this.createPatternUnit());
},
stop: function (): void {
store.dispatch('reset');
},
},
});
</script>
<style lang="scss">
.manual-form {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 16px;
}
.manual-form__inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="app-patterns">
<PatternList v-if="patterns.length > 0"
:patterns="patterns"
:mode="mode"
:isActive="isActive"
@change="change" />
<MessageItem v-else>Loading...</MessageItem>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import store from '@/store/index';
import PatternList from '@/components/PatternList.vue';
import MessageItem from '@/components/MessageItem.vue';
import TPattern from '@/models/TPattern';
export default defineComponent({
name: 'AppPatterns',
components: {
PatternList: PatternList,
MessageItem: MessageItem,
},
computed: {
patterns: function (): TPattern[] {
return store.getters.patterns as TPattern[];
},
mode: function (): number {
return store.getters.mode as number;
},
isActive: function (): boolean {
return store.getters.isActive as boolean;
},
},
methods: {
change(index: number): void {
store.dispatch('change', index as number);
},
},
mounted() {
store.dispatch('loadPatterns');
},
});
</script>

View File

@ -0,0 +1,32 @@
<template>
<div class="content-item navigation-list">
<router-link to="/manual" class="navigation-item">Manual</router-link>
<router-link to="/" class="navigation-item">Patterns</router-link>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NavigationList',
});
</script>
<style lang="scss">
.navigation-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
gap: 16px;
}
.navigation-item.router-link-active,
.navigation-item.router-link-exact-active {
border-bottom: 2px solid var(--color-link-hover);
color: var(--color-link-hover);
transition: all 0.5s ease;
text-decoration: none;
}
</style>

View File

@ -52,7 +52,7 @@
align-items: center; align-items: center;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
cursor: default; cursor: pointer;
} }
@media only screen and (min-width: 540px) { @media only screen and (min-width: 540px) {

View File

@ -1,82 +0,0 @@
<template>
<div class="wavelovers">
<PatternList v-if="patterns.length > 0"
:patterns="patterns"
:mode="mode"
:isActive="isActive"
@change="change" />
<MessageItem v-else>Loading...</MessageItem>
<GamepadList v-if="gamepads.length > 0"
:gamepads="gamepads" />
<MessageItem v-else>Press any gamepad button or connect a new gamepad to vibrate.</MessageItem>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import PatternList from '@/components/PatternList.vue';
import GamepadList from '@/components/GamepadList.vue';
import MessageItem from '@/components/MessageItem.vue';
import TPattern from '@/models/TPattern';
import Vibrator from '@/models/Vibrator';
import store from '@/store/index';
export default defineComponent({
name: 'WaveloversApp',
components: {
PatternList: PatternList,
GamepadList: GamepadList,
MessageItem: MessageItem,
},
computed: {
gamepads: function (): Vibrator[] {
return store.getters.gamepads as Vibrator[];
},
patterns: function (): TPattern[] {
return store.getters.patterns as TPattern[];
},
mode: function (): number {
return store.getters.mode as number;
},
isActive: function (): boolean {
return store.getters.isActive as boolean;
},
},
methods: {
addEventListeners(): void {
window.addEventListener('gamepadconnected', (event: GamepadEvent) => store.dispatch('addGamepad', event));
window.addEventListener('gamepaddisconnected', (event: GamepadEvent) => store.dispatch('deleteGamepad', event));
},
removeEventListeners(): void {
window.removeEventListener('gamepadconnected', (event: GamepadEvent) => store.dispatch('addGamepad', event));
window.removeEventListener('gamepaddisconnected', (event: GamepadEvent) => store.dispatch('deleteGamepad', event));
},
change(index: number): void {
store.dispatch('change', index as number);
},
},
mounted() {
store.dispatch('loadPatterns');
this.addEventListeners();
},
unmounted() {
this.removeEventListeners();
},
});
</script>
<style lang="scss">
.wavelovers {
display: flex;
flex-direction: column-reverse;
justify-content: flex-start;
gap: 16px;
}
@media only screen and (min-width: 540px) {
.wavelovers {
flex-direction: column;
}
}
</style>

View File

@ -1,11 +1,17 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import HomeView from '@/views/HomeView.vue'; import PatternsView from '@/views/PatternsView.vue';
import ManualView from '@/views/ManualView.vue';
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
name: 'home', name: 'patterns-view',
component: HomeView, component: PatternsView,
},
{
path: '/manual',
name: 'manual-view',
component: ManualView,
}, },
]; ];

View File

@ -2,6 +2,7 @@ import { createStore, Store } from 'vuex';
import IRootState from './models/IRootState'; import IRootState from './models/IRootState';
import MGamepads from '@/store/modules/MGamepads'; import MGamepads from '@/store/modules/MGamepads';
import MPatterns from '@/store/modules/MPatterns'; import MPatterns from '@/store/modules/MPatterns';
import TPatternUnit from '../models/TPatternUnit';
const store: Store<IRootState> = createStore({ const store: Store<IRootState> = createStore({
state: () => ({ state: () => ({
@ -46,11 +47,20 @@ const store: Store<IRootState> = createStore({
} }
if (context.getters.isActive === true) { if (context.getters.isActive === true) {
context.dispatch('reset'); context.dispatch('reset');
context.dispatch('vibrate'); context.dispatch(
'vibrate',
context.getters.patterns[context.getters.mode].pattern
);
} else { } else {
context.dispatch('reset'); context.dispatch('reset');
} }
}, },
startCustom: function (context, pattern: TPatternUnit[]): void {
context.dispatch('setIsActive', false);
context.dispatch('setMode', 0);
context.dispatch('reset');
context.dispatch('vibrate', pattern);
},
}, },
modules: { modules: {
MGamepads: MGamepads, MGamepads: MGamepads,

View File

@ -46,10 +46,11 @@ const MGamepads: Module<IGamepadsState, IRootState> = {
}); });
}, },
vibrate: function ( vibrate: function (
context: ActionContext<IGamepadsState, IRootState> context: ActionContext<IGamepadsState, IRootState>,
pattern: TPatternUnit[]
): void { ): void {
context.getters.gamepads.forEach((gamepad: Vibrator) => { context.getters.gamepads.forEach((gamepad: Vibrator) => {
gamepad.vibrate(context.getters.patterns[context.getters.mode].pattern as TPatternUnit[]); gamepad.vibrate(pattern);
}); });
}, },
reset: function ( reset: function (

View File

@ -1,15 +1,15 @@
<template> <template>
<WaveloversApp /> <AppManual />
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import WaveloversApp from '@/components/WaveloversApp.vue'; import AppManual from '@/components/AppManual.vue';
export default defineComponent({ export default defineComponent({
name: 'HomeView', name: 'ManualView',
components: { components: {
WaveloversApp: WaveloversApp, AppManual: AppManual,
}, },
}); });
</script> </script>

View File

@ -0,0 +1,16 @@
<template>
<AppPatterns />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import AppPatterns from '@/components/AppPatterns.vue';
export default defineComponent({
name: 'PatternsView',
components: {
AppPatterns: AppPatterns,
},
});
</script>