ADD: Profile management functionality & store
This commit is contained in:
12
package-lock.json
generated
12
package-lock.json
generated
@@ -27,7 +27,6 @@
|
|||||||
"vee-validate": "^4.12.4",
|
"vee-validate": "^4.12.4",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"vuex": "^4.1.0",
|
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -13201,17 +13200,6 @@
|
|||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vuex": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@vue/devtools-api": "^6.0.0-beta.11"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "1.7.5",
|
"version": "1.7.5",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"postuninstall": "electron-builder install-app-deps",
|
"postuninstall": "electron-builder install-app-deps",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"serve": "concurrently \"json-server --watch src/data/nanoConfig.json --port=3001\" \"vite\" "
|
"serve": "concurrently \"json-server src/data/nanoConfig.json --port=3001\" \"vite\" "
|
||||||
},
|
},
|
||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
"vee-validate": "^4.12.4",
|
"vee-validate": "^4.12.4",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"vuex": "^4.1.0",
|
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
114
src/components/ProfileButton.vue
Normal file
114
src/components/ProfileButton.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="h-12 flex profile-row"
|
||||||
|
@mouseenter="hover=true" @mouseleave="hover=false">
|
||||||
|
<button
|
||||||
|
:class="{'font-semibold bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||||
|
'hover:bg-zinc-900 bg-opacity-50 text-white': !selected,
|
||||||
|
'text-ellipsis': !editing}"
|
||||||
|
class="flex-1 h-full text-left whitespace-nowrap overflow-hidden"
|
||||||
|
@click="!editing && $emit('select') && $refs.profileTitle.scramble()">
|
||||||
|
<FileDigit
|
||||||
|
:class="{'text-zinc-600': selected,
|
||||||
|
'text-muted-foreground': !selected,
|
||||||
|
'w-0': hover}"
|
||||||
|
class="h-4 ml-10 mb-1 inline-block" />
|
||||||
|
<input
|
||||||
|
v-if="editing" ref="profileNameInput" v-model="profile.name"
|
||||||
|
onfocus="this.select()" :placeholder="$t('profiles.name_placeholder')"
|
||||||
|
class="pl-10 w-full h-full bg-transparent focus-visible:ring-0 focus-visible:outline-none"
|
||||||
|
style="color: inherit; background: inherit">
|
||||||
|
<template v-else>
|
||||||
|
<ScrambleText
|
||||||
|
ref="profileTitle"
|
||||||
|
:class="{'text-black': selected, 'text-zinc-100': !selected}"
|
||||||
|
:text="profile.name" />
|
||||||
|
<span
|
||||||
|
class="text-xs text-zinc-600"
|
||||||
|
:class="{'hidden': hover}"> uID:{{ profile.id }}</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<template v-if="!confirmDelete">
|
||||||
|
<button
|
||||||
|
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12': editing,
|
||||||
|
'w-0': !editing}"
|
||||||
|
class="flex h-12 justify-center items-center flex-shrink-0"
|
||||||
|
@click="editing=false">
|
||||||
|
<Check class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12' : hover && !editing}"
|
||||||
|
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||||
|
@click="editing=true; $nextTick(()=>{$refs.profileNameInput.focus()})">
|
||||||
|
<PenLine class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12' : hover && !editing}"
|
||||||
|
class="flex w-0 h-12 justify-center items-center flex-shrink-0">
|
||||||
|
<Copy class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{'bg-orange-600 hover:bg-orange-500 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-orange-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12' : hover && !editing}"
|
||||||
|
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||||
|
@click="confirmDelete=true">
|
||||||
|
<Trash2 class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
:class="{'bg-orange-600 hover:bg-orange-500 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-orange-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12' : hover && !editing}"
|
||||||
|
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||||
|
@click="$emit('delete', profile.id)">
|
||||||
|
<Check class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||||
|
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||||
|
'w-12' : hover && !editing}"
|
||||||
|
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||||
|
@click="confirmDelete=false">
|
||||||
|
<X class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { Check, Copy, FileDigit, PenLine, Trash2, X } from 'lucide-vue-next'
|
||||||
|
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineEmits(['select', 'duplicate', 'delete'])
|
||||||
|
|
||||||
|
const profile = defineModel({
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({
|
||||||
|
id: '1234',
|
||||||
|
name: 'Profile Name',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
|
||||||
|
const confirmDelete = ref(false)
|
||||||
|
|
||||||
|
const hover = ref(false)
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
v-model="filter"
|
v-model="filter"
|
||||||
:placeholder="$t('profiles.filter_placeholder')"
|
:placeholder="$t('profiles.filter_placeholder')"
|
||||||
class="grow h-full bg-transparent text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50">
|
class="grow h-full bg-transparent text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50">
|
||||||
<button class="h-full flex text-zinc-200 bg-zinc-900 justify-center items-center aspect-square border-solid border-0 border-l hover:bg-zinc-800">
|
<button
|
||||||
|
class="h-full flex text-zinc-200 bg-zinc-900 justify-center items-center aspect-square border-solid border-0 border-l hover:bg-zinc-800">
|
||||||
<Plus />
|
<Plus />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,7 +38,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
v-for="[profileTag, tagProfiles] in filteredProfilesByTag" :key="profileTag" v-model:open="collapse[profileTag]"
|
v-for="[profileTag, tagProfiles] in filteredProfilesByTag" :key="profileTag"
|
||||||
|
v-model:open="collapse[profileTag]"
|
||||||
:default-open="true">
|
:default-open="true">
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
class="w-full h-12 py-2 text-left text-muted-foreground text-sm hover:bg-zinc-900">
|
class="w-full h-12 py-2 text-left text-muted-foreground text-sm hover:bg-zinc-900">
|
||||||
@@ -45,53 +47,33 @@
|
|||||||
{{ profileTag }}<span class="font-heading text-sm text-zinc-600"> ({{ tagProfiles.length }})</span>
|
{{ profileTag }}<span class="font-heading text-sm text-zinc-600"> ({{ tagProfiles.length }})</span>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div
|
<ProfileButton
|
||||||
v-for="profile in tagProfiles" :key="profile.id"
|
v-for="(profile, index) in tagProfiles" :key="profile.id" v-model="tagProfiles[index]"
|
||||||
class="h-12 flex profile-row">
|
:selected="currentProfile===profile.id"
|
||||||
<button
|
@select="currentProfile=profile.id" />
|
||||||
:data-selected="currentProfile===profile.id"
|
|
||||||
class="flex-1 h-full text-left data-[selected=true]:font-semibold hover:bg-zinc-900 data-[selected=true]:bg-zinc-200 hover:data-[selected=true]:bg-zinc-100 data-[selected=true]:text-black flex-nowrap text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
@click="currentProfile=profile.id; profileTitles[profile.id].scramble()">
|
|
||||||
<FileDigit
|
|
||||||
:data-selected="currentProfile===profile.id"
|
|
||||||
class="h-4 w-4 mb-1 ml-10 mr-2 inline-block text-muted-foreground data-[selected=true]:text-zinc-600" />
|
|
||||||
<ScrambleText :ref="el => { profileTitles[profile.id] = el }" :text="profile.name" />
|
|
||||||
<span class="text-xs text-zinc-600"> uID:{{ profile.id }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:data-selected="currentProfile===profile.id"
|
|
||||||
class="flex w-0 h-12 transition-all text-zinc-100 justify-center items-center profile-button hover:bg-opacity-100 bg-opacity-50 bg-zinc-900 data-[selected=true]:bg-zinc-200 hover:data-[selected=true]:bg-zinc-100 data-[selected=true]:text-black">
|
|
||||||
<Copy class="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:data-selected="currentProfile===profile.id"
|
|
||||||
class="flex w-0 h-12 transition-all bg-orange-900 text-zinc-100 justify-center items-center profile-button hover:bg-orange-700 data-[selected=true]:bg-orange-600 hover:data-[selected=true]:bg-orange-500 data-[selected=true]:text-black">
|
|
||||||
<Trash2 class="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SchemaTest />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import SchemaTest from '@/components/SchemaTest.vue'
|
|
||||||
import { Separator } from '@/components/ui/separator/index.js'
|
import { Separator } from '@/components/ui/separator/index.js'
|
||||||
import { FileDigit, ChevronRight, Search, Trash2, Copy, Plus } from 'lucide-vue-next'
|
import { ChevronRight, Plus, Search } from 'lucide-vue-next'
|
||||||
import axios from 'axios'
|
import { computed, ref } from 'vue'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from '@/components/ui/collapsible'
|
|
||||||
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
||||||
|
import { store } from '@/store.js'
|
||||||
|
import ProfileButton from '@/components/ProfileButton.vue'
|
||||||
|
|
||||||
const maxProfiles = 32
|
const maxProfiles = 32
|
||||||
|
|
||||||
const profiles = ref([])
|
const editingId = ref(null)
|
||||||
|
|
||||||
|
const profiles = computed({
|
||||||
|
get: () => store.device.profiles,
|
||||||
|
set: val => store.device.profiles = val,
|
||||||
|
})
|
||||||
const filter = ref('')
|
const filter = ref('')
|
||||||
const collapse = ref({})
|
const collapse = ref({})
|
||||||
|
|
||||||
@@ -122,25 +104,10 @@ const filteredProfilesByTag = computed(() => {
|
|||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
|
||||||
function fetchProfiles() {
|
|
||||||
axios.get('http://localhost:3001/profiles').then(res => {
|
|
||||||
profiles.value = res.data
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchProfiles()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
[data-state=open] > .chevrot {
|
[data-state=open] > .chevrot {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-row:hover .profile-button {
|
|
||||||
width: 3rem /* 48px */;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -51,6 +51,108 @@
|
|||||||
"guiEnable": true
|
"guiEnable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "1232",
|
||||||
|
"name": "Another",
|
||||||
|
"profileTag": "Binaris",
|
||||||
|
"profileConfig": {
|
||||||
|
"profileDesc": "KORG MINILOGUE OSCILLATOR 1",
|
||||||
|
"profileType": 1,
|
||||||
|
"showDesc": true
|
||||||
|
},
|
||||||
|
"feedbackConfig": {
|
||||||
|
"feedbackEn": true,
|
||||||
|
"feedbackType": "fd",
|
||||||
|
"multiRev": false,
|
||||||
|
"feedbackStrength": 1,
|
||||||
|
"endstopStrength": 1,
|
||||||
|
"outputRamp": 10000,
|
||||||
|
"pos": 140,
|
||||||
|
"secondaryHaptic": true,
|
||||||
|
"secondaryVol": 5
|
||||||
|
},
|
||||||
|
"mappingConfig": {
|
||||||
|
"internalMacro": false,
|
||||||
|
"knobMap": "arrL",
|
||||||
|
"switchA": "shift",
|
||||||
|
"switchB": "ctrl",
|
||||||
|
"switchC": "alt",
|
||||||
|
"switchD": "esc"
|
||||||
|
},
|
||||||
|
"ledConfig": {
|
||||||
|
"ledEnable": true,
|
||||||
|
"ledMode": 1,
|
||||||
|
"primary": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 100
|
||||||
|
},
|
||||||
|
"secondary": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 100
|
||||||
|
},
|
||||||
|
"pointer": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"guiConfig": {
|
||||||
|
"guiEnable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1232",
|
||||||
|
"name": "DUPLICATE ID",
|
||||||
|
"profileTag": "Binaris",
|
||||||
|
"profileConfig": {
|
||||||
|
"profileDesc": "KORG MINILOGUE OSCILLATOR 1",
|
||||||
|
"profileType": 1,
|
||||||
|
"showDesc": true
|
||||||
|
},
|
||||||
|
"feedbackConfig": {
|
||||||
|
"feedbackEn": true,
|
||||||
|
"feedbackType": "fd",
|
||||||
|
"multiRev": false,
|
||||||
|
"feedbackStrength": 1,
|
||||||
|
"endstopStrength": 1,
|
||||||
|
"outputRamp": 10000,
|
||||||
|
"pos": 140,
|
||||||
|
"secondaryHaptic": true,
|
||||||
|
"secondaryVol": 5
|
||||||
|
},
|
||||||
|
"mappingConfig": {
|
||||||
|
"internalMacro": false,
|
||||||
|
"knobMap": "arrL",
|
||||||
|
"switchA": "shift",
|
||||||
|
"switchB": "ctrl",
|
||||||
|
"switchC": "alt",
|
||||||
|
"switchD": "esc"
|
||||||
|
},
|
||||||
|
"ledConfig": {
|
||||||
|
"ledEnable": true,
|
||||||
|
"ledMode": 1,
|
||||||
|
"primary": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 100
|
||||||
|
},
|
||||||
|
"secondary": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 100
|
||||||
|
},
|
||||||
|
"pointer": {
|
||||||
|
"h": 255,
|
||||||
|
"s": 255,
|
||||||
|
"l": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"guiConfig": {
|
||||||
|
"guiEnable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "5238",
|
"id": "5238",
|
||||||
"name": "sdfsdf",
|
"name": "sdfsdf",
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
"filter_placeholder": "Filter Profiles...",
|
"filter_placeholder": "Filter Profiles...",
|
||||||
"not_found": "No profiles found ;-;",
|
"not_found": "No profiles found ;-;",
|
||||||
"subtitle": "Customize to your heart's content",
|
"subtitle": "Customize to your heart's content",
|
||||||
"title": "Device Profiles"
|
"title": "Device Profiles",
|
||||||
|
"name_placeholder": "Profile name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
44
src/main.js
44
src/main.js
@@ -1,26 +1,39 @@
|
|||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
import Axios from 'axios'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createStore } from 'vuex'
|
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import en from './lang/en.json'
|
import en from '@/lang/en.json'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from '@/App.vue'
|
||||||
|
|
||||||
|
import { store } from '@/store.js'
|
||||||
|
|
||||||
import Ajv from 'ajv'
|
import Ajv from 'ajv'
|
||||||
|
import schema from '@/data/profileSchema.json'
|
||||||
|
|
||||||
// Create a new store instance
|
const ajv = new Ajv()
|
||||||
const store = createStore({
|
|
||||||
state() {
|
Axios.get('http://localhost:3001/profiles').then((res) => {
|
||||||
return {
|
const profiles = res.data
|
||||||
device: {
|
console.log(profiles)
|
||||||
connected: false,
|
const ids = new Set()
|
||||||
profiles: [],
|
const validate = ajv.compile(schema)
|
||||||
},
|
store.device.profiles = profiles.filter((profile) => {
|
||||||
|
if (!validate(profile)) {
|
||||||
|
console.error('Failed to validate profile: ' + profile.name, validate.errors)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
if (ids.has(profile.id)) {
|
||||||
mutations: {},
|
console.error('Duplicate profile id: ' + profile.id + ' for profile: ' + profile.name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ids.add(profile.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create VueI18n instance with locales loaded from /lang directory
|
// Create VueI18n instance with locales loaded from /lang directory
|
||||||
@@ -31,10 +44,5 @@ const i18n = createI18n({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
app.use(store)
|
|
||||||
|
|
||||||
app.provide('ajv', new Ajv())
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
9
src/store.js
Normal file
9
src/store.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
export const store = reactive({
|
||||||
|
device: {
|
||||||
|
profiles: [],
|
||||||
|
activeProfile: null,
|
||||||
|
},
|
||||||
|
currentProfile: null,
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user