mirror of
https://github.com/eugene-serb/wavelovers.git
synced 2023-09-09 23:41:16 +03:00
- 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:
parent
c5631603ac
commit
ebe813e7d3
@ -1,6 +1,6 @@
|
||||
/* ------------------------------ */
|
||||
/* Wavelovers styles */
|
||||
/* version: dated 2022.07.25 */
|
||||
/* version: dated 2022.08.07 */
|
||||
/* author: Eugene Serb */
|
||||
/* ------------------------------ */
|
||||
|
||||
@ -205,7 +205,7 @@ fieldset {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
button, input, textarea, select {
|
||||
button, textarea, select, input {
|
||||
padding: 4px 8px;
|
||||
border: 2px solid var(--color-link);
|
||||
border-radius: var(--number-border-radius);
|
||||
@ -215,7 +215,6 @@ button, input, textarea, select {
|
||||
line-height: 1.382em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button:hover, input:hover,
|
||||
textarea:hover, select:hover {
|
||||
border: 2px solid var(--color-link-hover);
|
||||
@ -234,6 +233,114 @@ option {
|
||||
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 */
|
||||
/* ------ */
|
||||
|
41
src/App.vue
41
src/App.vue
@ -1,12 +1,53 @@
|
||||
<template>
|
||||
<div class="wavelovers">
|
||||
<NavigationList />
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
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({
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
|
84
src/components/AppManual.vue
Normal file
84
src/components/AppManual.vue
Normal 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>
|
||||
|
46
src/components/AppPatterns.vue
Normal file
46
src/components/AppPatterns.vue
Normal 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>
|
||||
|
32
src/components/NavigationList.vue
Normal file
32
src/components/NavigationList.vue
Normal 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>
|
||||
|
@ -52,7 +52,7 @@
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 540px) {
|
||||
|
@ -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>
|
||||
|
@ -1,11 +1,17 @@
|
||||
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> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
name: 'patterns-view',
|
||||
component: PatternsView,
|
||||
},
|
||||
{
|
||||
path: '/manual',
|
||||
name: 'manual-view',
|
||||
component: ManualView,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { createStore, Store } from 'vuex';
|
||||
import IRootState from './models/IRootState';
|
||||
import MGamepads from '@/store/modules/MGamepads';
|
||||
import MPatterns from '@/store/modules/MPatterns';
|
||||
import TPatternUnit from '../models/TPatternUnit';
|
||||
|
||||
const store: Store<IRootState> = createStore({
|
||||
state: () => ({
|
||||
@ -46,11 +47,20 @@ const store: Store<IRootState> = createStore({
|
||||
}
|
||||
if (context.getters.isActive === true) {
|
||||
context.dispatch('reset');
|
||||
context.dispatch('vibrate');
|
||||
context.dispatch(
|
||||
'vibrate',
|
||||
context.getters.patterns[context.getters.mode].pattern
|
||||
);
|
||||
} else {
|
||||
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: {
|
||||
MGamepads: MGamepads,
|
||||
|
@ -46,10 +46,11 @@ const MGamepads: Module<IGamepadsState, IRootState> = {
|
||||
});
|
||||
},
|
||||
vibrate: function (
|
||||
context: ActionContext<IGamepadsState, IRootState>
|
||||
context: ActionContext<IGamepadsState, IRootState>,
|
||||
pattern: TPatternUnit[]
|
||||
): void {
|
||||
context.getters.gamepads.forEach((gamepad: Vibrator) => {
|
||||
gamepad.vibrate(context.getters.patterns[context.getters.mode].pattern as TPatternUnit[]);
|
||||
gamepad.vibrate(pattern);
|
||||
});
|
||||
},
|
||||
reset: function (
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<WaveloversApp />
|
||||
<AppManual />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import WaveloversApp from '@/components/WaveloversApp.vue';
|
||||
import AppManual from '@/components/AppManual.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomeView',
|
||||
name: 'ManualView',
|
||||
components: {
|
||||
WaveloversApp: WaveloversApp,
|
||||
AppManual: AppManual,
|
||||
},
|
||||
});
|
||||
</script>
|
16
src/views/PatternsView.vue
Normal file
16
src/views/PatternsView.vue
Normal 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>
|
||||
|
Loading…
Reference in New Issue
Block a user