UPD: Refactor profile categories
This commit is contained in:
12
.vscode/tasks.json
vendored
Normal file
12
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "dev",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"label": "ZERO/ONE Development Mode",
|
||||||
|
"detail": "electron-vite dev"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -39,7 +39,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
previewDeviceModel: localStorage.getItem('previewDeviceModel') || 'nanoOne'
|
previewDeviceModel: localStorage.getItem('previewDeviceModel') || 'nanoOne',
|
||||||
|
profileManagerDragging: false,
|
||||||
|
showProfileConfig: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@@ -179,6 +181,12 @@ export const useAppStore = defineStore('app', {
|
|||||||
switchPreviewDeviceModel() {
|
switchPreviewDeviceModel() {
|
||||||
this.previewDeviceModel = this.previewDeviceModel === 'nanoOne' ? 'nanoZero' : 'nanoOne'
|
this.previewDeviceModel = this.previewDeviceModel === 'nanoOne' ? 'nanoZero' : 'nanoOne'
|
||||||
localStorage.setItem('previewDeviceModel', this.previewDeviceModel)
|
localStorage.setItem('previewDeviceModel', this.previewDeviceModel)
|
||||||
|
},
|
||||||
|
setProfileManagerDragging(dragging) {
|
||||||
|
this.profileManagerDragging = dragging
|
||||||
|
},
|
||||||
|
setShowProfileConfig(show) {
|
||||||
|
this.showProfileConfig = show
|
||||||
}
|
}
|
||||||
// cycleScreenOrientation() {
|
// cycleScreenOrientation() {
|
||||||
// this.screenOrientation = (this.screenOrientation + 90) % 360
|
// this.screenOrientation = (this.screenOrientation + 90) % 360
|
||||||
|
|||||||
169
src/renderer/src/components/profile/ProfileCategory.vue
Normal file
169
src/renderer/src/components/profile/ProfileCategory.vue
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<Collapsible v-model:open="expanded" :default-open="true">
|
||||||
|
<!-- TODO: Make profile groups computed instead defining them of using v-for -->
|
||||||
|
<div
|
||||||
|
class="group flex h-12 w-full items-center justify-between border-0 border-b bg-zinc-900 py-2 text-left text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
<CollapsibleTrigger class="flex flex-1 items-center">
|
||||||
|
<ChevronRight class="chevrot mb-0.5 ml-4 inline-block size-4 transition-transform" />
|
||||||
|
{{ categoryName
|
||||||
|
}}<span class="font-heading text-sm text-zinc-600">
|
||||||
|
({{ deviceStore.profilesByTag[categoryName].length || 0 }})</span
|
||||||
|
>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<div
|
||||||
|
class="float-right flex items-center gap-1 opacity-0 transition-all group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
<template v-if="!confirmDelete">
|
||||||
|
<Button
|
||||||
|
class="aspect-square border border-zinc-800 bg-transparent p-1 text-muted-foreground hover:bg-orange-900 hover:text-zinc-100"
|
||||||
|
><PenLine class="size-4"
|
||||||
|
/></Button>
|
||||||
|
<Button
|
||||||
|
class="aspect-square border border-zinc-800 bg-transparent p-1 text-muted-foreground hover:bg-orange-900 hover:text-zinc-100"
|
||||||
|
@click="confirmDelete = true"
|
||||||
|
><Trash2 class="size-4"
|
||||||
|
/></Button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<Button
|
||||||
|
class="aspect-square bg-orange-950 p-1 text-zinc-200 hover:bg-orange-900 hover:text-zinc-100"
|
||||||
|
><Check class="size-4"
|
||||||
|
/></Button>
|
||||||
|
<Button
|
||||||
|
class="aspect-square border border-zinc-800 bg-transparent p-1 text-zinc-200 hover:bg-zinc-800 hover:text-zinc-100"
|
||||||
|
@click="confirmDelete = false"
|
||||||
|
><X class="size-4"
|
||||||
|
/></Button>
|
||||||
|
</template>
|
||||||
|
<span class="mx-4 w-4 cursor-grab text-zinc-600">
|
||||||
|
<GripHorizontal class="category-handle mb-0.5 inline-block size-4" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<draggable
|
||||||
|
key="profilesDraggable"
|
||||||
|
group="profiles"
|
||||||
|
item-key="id"
|
||||||
|
:list="deviceStore.profilesByTag[categoryName]"
|
||||||
|
v-bind="dragOptions"
|
||||||
|
handle=".profile-handle"
|
||||||
|
@start="appStore.setProfileManagerDragging(true)"
|
||||||
|
@end="appStore.setProfileManagerDragging(false)"
|
||||||
|
@change="(event) => onProfileDrop(event, categoryIndex)"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="hideable-header m-2 flex h-12 items-center justify-center">
|
||||||
|
<MoreHorizontal class="w-4 text-zinc-600" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #item="dragProfile">
|
||||||
|
<div :key="dragProfile.element.name">
|
||||||
|
<ProfileButton
|
||||||
|
:profile="dragProfile.element"
|
||||||
|
:show-hover-buttons="!appStore.profileManagerDragging"
|
||||||
|
:selected="deviceStore.currentProfileName === dragProfile.element.name"
|
||||||
|
@select="
|
||||||
|
() => {
|
||||||
|
deviceStore.selectProfile(dragProfile.element.name)
|
||||||
|
appStore.setShowProfileConfig(true)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@rename="
|
||||||
|
(oldName, newName) => {
|
||||||
|
deviceStore.renameProfile(oldName, newName)
|
||||||
|
if (deviceStore.currentProfileName === oldName) {
|
||||||
|
deviceStore.selectProfile(newName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@duplicate="
|
||||||
|
(originalName, profile) => {
|
||||||
|
deviceStore.duplicateProfile(originalName, profile)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@delete="(profileName) => deviceStore.deleteProfile(profileName)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore } from '@renderer/appStore'
|
||||||
|
import ProfileButton from '@renderer/components/profile/ProfileButton.vue'
|
||||||
|
import { Button } from '@renderer/components/ui/button'
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger
|
||||||
|
} from '@renderer/components/ui/collapsible'
|
||||||
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
|
import {
|
||||||
|
Check,
|
||||||
|
ChevronRight,
|
||||||
|
GripHorizontal,
|
||||||
|
MoreHorizontal,
|
||||||
|
PenLine,
|
||||||
|
Trash2,
|
||||||
|
X
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
const deviceStore = useDeviceStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const dragOptions = ref({
|
||||||
|
ghostClass: 'ghost',
|
||||||
|
animation: 150,
|
||||||
|
direction: 'vertical'
|
||||||
|
})
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
categoryName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
categoryIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const expanded = ref(true)
|
||||||
|
const confirmDelete = ref(false)
|
||||||
|
|
||||||
|
const onProfileDrop = (event, categoryIndex) => {
|
||||||
|
if (event.moved) {
|
||||||
|
const profile = event.moved.element
|
||||||
|
const oldIndex = event.moved.oldIndex
|
||||||
|
const newIndex = event.moved.newIndex
|
||||||
|
// store.moveProfile(profile.id, oldIndex, newIndex)
|
||||||
|
console.log('Move profile not implemented!')
|
||||||
|
}
|
||||||
|
if (event.added) {
|
||||||
|
const profile = event.added.element
|
||||||
|
const newIndex = event.added.newIndex
|
||||||
|
// store.changeProfileCategory(profile.id, categoryIndex, newIndex)
|
||||||
|
console.log('Change profile category not implemented!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
[data-state='open'] > .chevrot {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideable-header:not(:only-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideable-header:only-child {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,15 +6,24 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="font-heading flex h-full min-w-0 flex-1 items-center"
|
class="font-heading flex h-full min-w-0 flex-1 items-center"
|
||||||
@click="showProfileConfig = deviceStore.currentProfileName && !showProfileConfig"
|
@click="
|
||||||
|
appStore.setShowProfileConfig(
|
||||||
|
deviceStore.currentProfileName && !appStore.showProfileConfig
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<component :is="showProfileConfig ? ArrowLeft : List" class="mr-1 h-full w-5 shrink-0" />
|
<component
|
||||||
|
:is="appStore.showProfileConfig ? ArrowLeft : List"
|
||||||
|
class="mr-1 h-full w-5 shrink-0"
|
||||||
|
/>
|
||||||
<ScrambleText
|
<ScrambleText
|
||||||
:text="showProfileConfig ? deviceStore.currentProfileName : $t('profiles.title')"
|
:text="
|
||||||
|
appStore.showProfileConfig ? deviceStore.currentProfileName : $t('profiles.title')
|
||||||
|
"
|
||||||
class="min-w-0 overflow-hidden text-ellipsis"
|
class="min-w-0 overflow-hidden text-ellipsis"
|
||||||
/>
|
/>
|
||||||
<ScrambleText
|
<ScrambleText
|
||||||
v-if="!showProfileConfig"
|
v-if="!appStore.showProfileConfig"
|
||||||
class="min-w-0 overflow-hidden text-ellipsis text-sm text-zinc-600"
|
class="min-w-0 overflow-hidden text-ellipsis text-sm text-zinc-600"
|
||||||
scramble-on-mount
|
scramble-on-mount
|
||||||
:fill-interval="20"
|
:fill-interval="20"
|
||||||
@@ -26,7 +35,7 @@
|
|||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<button
|
<button
|
||||||
v-if="!showProfileConfig"
|
v-if="!appStore.showProfileConfig"
|
||||||
class="flex aspect-square h-8 items-center justify-center rounded-lg border border-zinc-100 bg-zinc-300 text-black hover:bg-zinc-200"
|
class="flex aspect-square h-8 items-center justify-center rounded-lg border border-zinc-100 bg-zinc-300 text-black hover:bg-zinc-200"
|
||||||
@click="console.log('Add profile not implemented!')"
|
@click="console.log('Add profile not implemented!')"
|
||||||
>
|
>
|
||||||
@@ -61,84 +70,21 @@
|
|||||||
:list="deviceStore.profileTags"
|
:list="deviceStore.profileTags"
|
||||||
v-bind="dragOptions"
|
v-bind="dragOptions"
|
||||||
handle=".category-handle"
|
handle=".category-handle"
|
||||||
@start="drag = true"
|
@start="appStore.setProfileManagerDragging(true)"
|
||||||
@end="drag = false"
|
@end="appStore.setProfileManagerDragging(false)"
|
||||||
@change="onCategoryDrop"
|
@change="onCategoryDrop"
|
||||||
>
|
>
|
||||||
<template #item="dragCategory">
|
<template #item="dragCategory">
|
||||||
<Collapsible v-model:open="collapse[dragCategory.element]" :default-open="true">
|
<ProfileCategory
|
||||||
<!-- TODO: Make profile groups computed instead defining them of using v-for -->
|
:category-name="dragCategory.element"
|
||||||
<CollapsibleTrigger
|
:category-index="dragCategory.index"
|
||||||
class="group h-12 w-full border-0 border-b bg-zinc-900 py-2 text-left text-sm text-muted-foreground"
|
/>
|
||||||
>
|
|
||||||
<ChevronRight
|
|
||||||
class="chevrot mb-0.5 ml-4 inline-block size-4 transition-transform"
|
|
||||||
/>
|
|
||||||
{{ dragCategory.element
|
|
||||||
}}<span class="font-heading text-sm text-zinc-600">
|
|
||||||
({{ deviceStore.profilesByTag[dragCategory.element].length || 0 }})</span
|
|
||||||
>
|
|
||||||
<span class="float-right mx-4 w-4 cursor-grab text-zinc-600">
|
|
||||||
<GripHorizontal
|
|
||||||
class="category-handle mb-0.5 inline-block size-4 opacity-0 transition-all group-hover:opacity-100"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<draggable
|
|
||||||
key="profilesDraggable"
|
|
||||||
group="profiles"
|
|
||||||
item-key="id"
|
|
||||||
:list="deviceStore.profilesByTag[dragCategory.element]"
|
|
||||||
v-bind="dragOptions"
|
|
||||||
handle=".profile-handle"
|
|
||||||
@start="drag = true"
|
|
||||||
@end="drag = false"
|
|
||||||
@change="(event) => onProfileDrop(event, dragCategory.index)"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div class="hideable-header m-2 flex h-12 items-center justify-center">
|
|
||||||
<MoreHorizontal class="w-4 text-zinc-600" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #item="dragProfile">
|
|
||||||
<div :key="dragProfile.element.name">
|
|
||||||
<ProfileButton
|
|
||||||
:profile="dragProfile.element"
|
|
||||||
:show-hover-buttons="!drag"
|
|
||||||
:selected="deviceStore.currentProfileName === dragProfile.element.name"
|
|
||||||
@select="
|
|
||||||
() => {
|
|
||||||
deviceStore.selectProfile(dragProfile.element.name)
|
|
||||||
showProfileConfig = true
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@rename="
|
|
||||||
(oldName, newName) => {
|
|
||||||
deviceStore.renameProfile(oldName, newName)
|
|
||||||
if (deviceStore.currentProfileName === oldName) {
|
|
||||||
deviceStore.selectProfile(newName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@duplicate="
|
|
||||||
(originalName, profile) => {
|
|
||||||
deviceStore.duplicateProfile(originalName, profile)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@delete="(profileName) => deviceStore.deleteProfile(profileName)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</draggable>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Transition name="slide">
|
<Transition name="slide">
|
||||||
<div v-if="showProfileConfig" class="absolute h-full bg-[#101013]">
|
<div v-if="appStore.showProfileConfig" class="absolute h-full bg-[#101013]">
|
||||||
<ProfileConfig />
|
<ProfileConfig />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -146,32 +92,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Separator } from '@renderer/components/ui/separator'
|
import { useAppStore } from '@renderer/appStore'
|
||||||
import {
|
|
||||||
ChevronRight,
|
|
||||||
Plus,
|
|
||||||
ArrowLeft,
|
|
||||||
List,
|
|
||||||
MoreHorizontal,
|
|
||||||
GripHorizontal
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger
|
|
||||||
} from '@renderer/components/ui/collapsible'
|
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
import ProfileButton from '@renderer/components/profile/ProfileButton.vue'
|
import ProfileCategory from '@renderer/components/profile/ProfileCategory.vue'
|
||||||
import ProfileConfig from '@renderer/components/profile/ProfileConfig.vue'
|
import ProfileConfig from '@renderer/components/profile/ProfileConfig.vue'
|
||||||
import draggable from 'vuedraggable'
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '@renderer/components/ui/dropdown-menu'
|
} from '@renderer/components/ui/dropdown-menu'
|
||||||
|
import { Separator } from '@renderer/components/ui/separator'
|
||||||
import { useDeviceStore } from '@renderer/deviceStore'
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
|
import { ArrowLeft, List, Plus } from 'lucide-vue-next'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
showFilter: {
|
showFilter: {
|
||||||
@@ -181,6 +116,7 @@ defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const deviceStore = useDeviceStore()
|
const deviceStore = useDeviceStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const dragOptions = ref({
|
const dragOptions = ref({
|
||||||
ghostClass: 'ghost',
|
ghostClass: 'ghost',
|
||||||
@@ -190,15 +126,10 @@ const dragOptions = ref({
|
|||||||
|
|
||||||
const maxProfiles = 32
|
const maxProfiles = 32
|
||||||
|
|
||||||
const collapse = ref({})
|
const renderProfileConfig = ref(appStore.showProfileConfig)
|
||||||
|
const renderProfileList = ref(!appStore.showProfileConfig)
|
||||||
|
|
||||||
const showProfileConfig = ref(false)
|
watch(appStore.showProfileConfig, (value) => {
|
||||||
const renderProfileConfig = ref(showProfileConfig.value)
|
|
||||||
const renderProfileList = ref(!showProfileConfig.value)
|
|
||||||
|
|
||||||
const drag = ref(false)
|
|
||||||
|
|
||||||
watch(showProfileConfig, (value) => {
|
|
||||||
if (value) {
|
if (value) {
|
||||||
renderProfileConfig.value = true
|
renderProfileConfig.value = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -246,28 +177,8 @@ const onCategoryDrop = (event) => {
|
|||||||
console.log('Move category not implemented!')
|
console.log('Move category not implemented!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onProfileDrop = (event, categoryIndex) => {
|
|
||||||
if (event.moved) {
|
|
||||||
const profile = event.moved.element
|
|
||||||
const oldIndex = event.moved.oldIndex
|
|
||||||
const newIndex = event.moved.newIndex
|
|
||||||
// store.moveProfile(profile.id, oldIndex, newIndex)
|
|
||||||
console.log('Move profile not implemented!')
|
|
||||||
}
|
|
||||||
if (event.added) {
|
|
||||||
const profile = event.added.element
|
|
||||||
const newIndex = event.added.newIndex
|
|
||||||
// store.changeProfileCategory(profile.id, categoryIndex, newIndex)
|
|
||||||
console.log('Change profile category not implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
[data-state='open'] > .chevrot {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-enter-active,
|
.slide-enter-active,
|
||||||
.slide-leave-active {
|
.slide-leave-active {
|
||||||
transition: transform 300ms ease;
|
transition: transform 300ms ease;
|
||||||
@@ -295,12 +206,4 @@ const onProfileDrop = (event, categoryIndex) => {
|
|||||||
.sortable-drag {
|
.sortable-drag {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hideable-header:not(:only-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideable-header:only-child {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { useAppStore } from '@renderer/appStore'
|
import { useAppStore } from '@renderer/appStore'
|
||||||
|
|
||||||
interface Profile {
|
export interface Profile {
|
||||||
version: number
|
version: number
|
||||||
name: string
|
name: string
|
||||||
desc: string
|
desc: string
|
||||||
|
|||||||
Reference in New Issue
Block a user