ADD: Pinia
This commit is contained in:
68
package-lock.json
generated
68
package-lock.json
generated
@@ -21,6 +21,7 @@
|
||||
"concurrently": "^8.2.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"lucide-vue-next": "^0.309.0",
|
||||
"pinia": "^2.1.7",
|
||||
"radix-vue": "^1.3.0",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -8304,6 +8305,56 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
|
||||
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
@@ -16738,6 +16789,23 @@
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
|
||||
},
|
||||
"pinia": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
|
||||
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pirates": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"concurrently": "^8.2.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"lucide-vue-next": "^0.309.0",
|
||||
"pinia": "^2.1.7",
|
||||
"radix-vue": "^1.3.0",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
@@ -7,15 +7,21 @@ import ConfigSelect from '@/components/config/ConfigSelect.vue'
|
||||
import { ref } from 'vue'
|
||||
import ConfigSection from '@/components/config/ConfigSection.vue'
|
||||
import { Bolt } from 'lucide-vue-next'
|
||||
import { useStore } from '@/store'
|
||||
|
||||
const currentConfigPage = ref('profile_settings')
|
||||
|
||||
const store = useStore()
|
||||
|
||||
store.fetchProfiles()
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<main class="select-none w-screen h-screen flex flex-col">
|
||||
<Navbar class="flex-none" />
|
||||
<div class="flex-1 min-h-0 flex flex-row">
|
||||
<ProfileManager class="basis-1/3 min-w-80 flex-1 flex flex-col border-solid border-0 border-r bg-zinc-900 bg-opacity-30" />
|
||||
<ProfileManager
|
||||
class="basis-1/3 min-w-80 flex-1 flex flex-col border-solid border-0 border-r bg-zinc-900 bg-opacity-30" />
|
||||
<div class="basis-1/3 flex-1 flex flex-col border-solid border-0 border-r">
|
||||
<DevicePreview class="flex-col flex flex-none" />
|
||||
<ConfigSection class="flex-none" :title="$t('config_options.title')" :foldable="false" :icon-component="Bolt" />
|
||||
|
||||
@@ -17,10 +17,12 @@ import ConfigSection from '@/components/config/ConfigSection.vue'
|
||||
import PaletteInput from '@/components/config/PaletteInput.vue'
|
||||
import Color from 'color'
|
||||
import { computed, ref } from 'vue'
|
||||
import { store } from '@/store.js'
|
||||
import { useStore } from '@/store.js'
|
||||
import { Slider } from '@/components/ui/slider/index.js'
|
||||
|
||||
const ledConfig = computed(() => store.currentProfile.ledConfig)
|
||||
const store = useStore()
|
||||
|
||||
const ledConfig = computed(() => store.selectedProfile.ledConfig)
|
||||
|
||||
const brightnessSliderModel = computed({
|
||||
get: () => [ledConfig.value.ledBrightness],
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="w-full px-4 h-20 flex items-center">
|
||||
<div>
|
||||
<h1 class="text-lg">
|
||||
{{ $t(`profiles.title`) }}<span class="text-sm text-zinc-600"> ({{ profiles.length }}/{{ maxProfiles
|
||||
{{ $t(`profiles.title`) }}<span class="text-sm text-zinc-600"> ({{ store.profiles.length }}/{{ maxProfiles
|
||||
}})</span>
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
@@ -53,10 +53,10 @@
|
||||
<CollapsibleContent>
|
||||
<ProfileButton
|
||||
v-for="(profile, index) in tagProfiles" :key="profile.id" v-model="tagProfiles[index]"
|
||||
:selected="currentProfileId===profile.id"
|
||||
@select="currentProfileId=profile.id"
|
||||
@duplicate="duplicateProfile(profile.id)"
|
||||
@delete="deleteProfile(profile.id)" />
|
||||
:selected="store.selectedProfile.id === profile.id"
|
||||
@select="store.selectProfile(profile.id)"
|
||||
@duplicate="store.duplicateProfile(profile.id)"
|
||||
@delete="store.deleteProfile(profile.id)" />
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
@@ -69,28 +69,21 @@ import { ChevronRight, Plus, Search } from 'lucide-vue-next'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
||||
import { store } from '@/store.js'
|
||||
import { useStore } from '@/store.js'
|
||||
import ProfileButton from '@/components/profile/ProfileButton.vue'
|
||||
|
||||
const maxProfiles = 32
|
||||
|
||||
const profiles = computed({
|
||||
get: () => store.device.profiles,
|
||||
set: val => store.device.profiles = val,
|
||||
})
|
||||
const currentProfileId = computed({
|
||||
get: () => store.currentProfileId,
|
||||
set: val => store.currentProfileId = val,
|
||||
})
|
||||
const store = useStore()
|
||||
const filter = ref('')
|
||||
const collapse = ref({})
|
||||
|
||||
const filteredProfiles = computed(() => {
|
||||
if (!filter.value) {
|
||||
return profiles.value
|
||||
return store.profiles
|
||||
}
|
||||
const filterLower = filter.value.toLowerCase()
|
||||
return profiles.value.filter(profile => {
|
||||
return store.profiles.filter(profile => {
|
||||
const nameLower = profile.name.toLowerCase()
|
||||
const idLower = profile.id.toLowerCase()
|
||||
const tagLower = profile.profileTag.toLowerCase()
|
||||
@@ -98,47 +91,6 @@ const filteredProfiles = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
function newProfileId(originalId = '') {
|
||||
let id = originalId
|
||||
if (originalId) {
|
||||
do {
|
||||
id = Math.floor((parseInt(id) + 1) % 9999).toString().padStart(4, '0')
|
||||
} while (store.profileIds.includes(id))
|
||||
} else {
|
||||
do {
|
||||
id = Math.floor(Math.random() * 9999).toString().padStart(4, '0')
|
||||
} while (store.profileIds.includes(id))
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
function newProfileName(originalName) {
|
||||
let name = originalName
|
||||
let i = 1
|
||||
while (profiles.value.find(p => p.name === name)) {
|
||||
name = `${originalName} (${i++})`
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
const duplicateProfile = (id) => {
|
||||
const profile = profiles.value.find(p => p.id === id)
|
||||
const profileIndex = profiles.value.indexOf(profile)
|
||||
const newProfile = JSON.parse(JSON.stringify(profile))
|
||||
newProfile.id = newProfileId(id)
|
||||
newProfile.name = newProfileName(profile.name)
|
||||
profiles.value.splice(profileIndex + 1, 0, newProfile)
|
||||
}
|
||||
|
||||
const deleteProfile = (id) => {
|
||||
const profile = profiles.value.find(p => p.id === id)
|
||||
const profileIndex = profiles.value.indexOf(profile)
|
||||
profiles.value.splice(profileIndex, 1)
|
||||
if (currentProfileId.value === id && profiles.value.length > 0) {
|
||||
currentProfileId.value = profiles.value[0].id
|
||||
}
|
||||
}
|
||||
|
||||
const filteredProfilesByTag = computed(() => {
|
||||
const map = new Map()
|
||||
filteredProfiles.value.forEach(profile => {
|
||||
@@ -151,11 +103,6 @@ const filteredProfilesByTag = computed(() => {
|
||||
return map
|
||||
})
|
||||
|
||||
watch(profiles, () => {
|
||||
if (!currentProfileId.value && profiles.value.length > 0)
|
||||
currentProfileId.value = profiles.value[0].id
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
[data-state=open] > .chevrot {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
*/
|
||||
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import App from './App.vue'
|
||||
import en from '@/lang/en.json'
|
||||
@@ -35,41 +35,16 @@ import en from '@/lang/en.json'
|
||||
import './assets/main.css'
|
||||
import './assets/index.css'
|
||||
|
||||
import { store } from '@/store.js'
|
||||
import Ajv from 'ajv'
|
||||
import schema from '@/data/profileSchema.json'
|
||||
import Axios from 'axios'
|
||||
|
||||
const ajv = new Ajv()
|
||||
|
||||
Axios.get('http://localhost:3001/profiles').then((res) => {
|
||||
const profiles = res.data
|
||||
console.log(profiles)
|
||||
const ids = new Set()
|
||||
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)) {
|
||||
console.error('Duplicate profile id: ' + profile.id + ' for profile: ' + profile.name)
|
||||
return false
|
||||
}
|
||||
ids.add(profile.id)
|
||||
return true
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
import { pinia } from '@/store.js'
|
||||
|
||||
// Create VueI18n instance with locales loaded from /lang directory
|
||||
const i18n = createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: { en: en },
|
||||
});
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: { en: en },
|
||||
})
|
||||
|
||||
const app = createApp(App);
|
||||
const app = createApp(App)
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.mount('#app');
|
||||
app.mount('#app')
|
||||
123
src/store.js
123
src/store.js
@@ -1,16 +1,113 @@
|
||||
import { computed, reactive } from 'vue'
|
||||
// import { computed, reactive } from 'vue'
|
||||
//
|
||||
// export const store = reactive({
|
||||
// device: {
|
||||
// profiles: [],
|
||||
// activeProfileId: null,
|
||||
// activeProfile: computed(() => {
|
||||
// return store.device.profiles.find(p => p.id === store.device.activeProfileId)
|
||||
// }),
|
||||
// },
|
||||
// currentProfileId: null,
|
||||
// currentProfile: computed(() => {
|
||||
// return store.device.profiles.find(p => p.id === store.currentProfileId)
|
||||
// }),
|
||||
// profileIds: computed(() => store.device.profiles.map(p => p.id)),
|
||||
// })
|
||||
|
||||
export const store = reactive({
|
||||
device: {
|
||||
profiles: [],
|
||||
activeProfileId: null,
|
||||
activeProfile: computed(() => {
|
||||
return store.device.profiles.find(p => p.id === store.device.activeProfileId)
|
||||
}),
|
||||
import { createPinia, defineStore } from 'pinia'
|
||||
import Axios from 'axios'
|
||||
import schema from '@/data/profileSchema.json'
|
||||
import Ajv from 'ajv'
|
||||
|
||||
const ajv = new Ajv()
|
||||
|
||||
export const useStore = defineStore('main', {
|
||||
// arrow function recommended for full type inference
|
||||
state: () => {
|
||||
return {
|
||||
profiles: [],
|
||||
selectedProfileId: null,
|
||||
connected: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
profileIds: (state) => state.profiles.map(p => p.id),
|
||||
selectedProfile: (state) => state.profiles.find(p => p.id === state.selectedProfileId),
|
||||
},
|
||||
actions: {
|
||||
selectProfile(id) {
|
||||
if (!this.profileIds.includes(id)) return false
|
||||
this.selectedProfileId = id
|
||||
return true
|
||||
},
|
||||
duplicateProfile(id) {
|
||||
const originalProfile = this.profiles.find(p => p.id === id)
|
||||
const newProfile = JSON.parse(JSON.stringify(originalProfile))
|
||||
newProfile.id = this.newProfileId(originalProfile.id)
|
||||
newProfile.name = this.newProfileName(originalProfile.name)
|
||||
this.profiles.push(newProfile)
|
||||
this.selectedProfileId = newProfile.id
|
||||
return newProfile.id
|
||||
},
|
||||
deleteProfile(id) {
|
||||
const index = this.profiles.findIndex(p => p.id === id)
|
||||
if (index >= 0) {
|
||||
this.profiles.splice(index, 1)
|
||||
if (this.selectedProfileId === id) {
|
||||
this.selectedProfileId = this.profiles[0]?.id || null
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
fetchProfiles() {
|
||||
Axios.get('http://localhost:3001/profiles').then((res) => {
|
||||
const profiles = res.data
|
||||
console.log(profiles)
|
||||
const ids = new Set()
|
||||
const validate = ajv.compile(schema)
|
||||
this.$patch({
|
||||
profiles: profiles.filter((profile) => {
|
||||
if (!validate(profile)) {
|
||||
console.error('Failed to validate profile: ' + profile.name, validate.errors)
|
||||
return false
|
||||
}
|
||||
if (ids.has(profile.id)) {
|
||||
console.error('Duplicate profile id: ' + profile.id + ' for profile: ' + profile.name)
|
||||
return false
|
||||
}
|
||||
ids.add(profile.id)
|
||||
return true
|
||||
}),
|
||||
selectedProfileId: profiles[0]?.id || null,
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
newProfileName(originalName = '') {
|
||||
let name = originalName
|
||||
let i = 1
|
||||
while (this.profiles.find(p => p.name === name)) {
|
||||
name = `${originalName} (${i++})`
|
||||
}
|
||||
return name
|
||||
},
|
||||
newProfileId(originalId = '') {
|
||||
let id = originalId
|
||||
if (originalId) {
|
||||
do {
|
||||
id = Math.floor((parseInt(id) + 1) % 9999).toString().padStart(4, '0')
|
||||
} while (this.profileIds.includes(id))
|
||||
} else {
|
||||
do {
|
||||
id = Math.floor(Math.random() * 9999).toString().padStart(4, '0')
|
||||
} while (this.profileIds.includes(id))
|
||||
}
|
||||
return id
|
||||
},
|
||||
},
|
||||
currentProfileId: null,
|
||||
currentProfile: computed(() => {
|
||||
return store.device.profiles.find(p => p.id === store.currentProfileId)
|
||||
}),
|
||||
profileIds: computed(() => store.device.profiles.map(p => p.id)),
|
||||
})
|
||||
|
||||
export const pinia = createPinia()
|
||||
Reference in New Issue
Block a user