ADD: Pinia

This commit is contained in:
Robert Kossessa
2024-01-31 00:11:55 +01:00
parent 98058e9a4a
commit 0f5e0df58b
7 changed files with 209 additions and 113 deletions

68
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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" />

View File

@@ -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],

View File

@@ -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 {

View File

@@ -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')

View File

@@ -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,
}
},
currentProfileId: null,
currentProfile: computed(() => {
return store.device.profiles.find(p => p.id === store.currentProfileId)
}),
profileIds: computed(() => store.device.profiles.map(p => p.id)),
})
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
},
},
})
export const pinia = createPinia()