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: {
|
||||
@@ -179,6 +181,12 @@ export const useAppStore = defineStore('app', {
|
||||
switchPreviewDeviceModel() {
|
||||
this.previewDeviceModel = this.previewDeviceModel === 'nanoOne' ? 'nanoZero' : 'nanoOne'
|
||||
localStorage.setItem('previewDeviceModel', this.previewDeviceModel)
|
||||
},
|
||||
setProfileManagerDragging(dragging) {
|
||||
this.profileManagerDragging = dragging
|
||||
},
|
||||
setShowProfileConfig(show) {
|
||||
this.showProfileConfig = show
|
||||
}
|
||||
// cycleScreenOrientation() {
|
||||
// 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
|
||||
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
|
||||
:text="showProfileConfig ? deviceStore.currentProfileName : $t('profiles.title')"
|
||||
:text="
|
||||
appStore.showProfileConfig ? deviceStore.currentProfileName : $t('profiles.title')
|
||||
"
|
||||
class="min-w-0 overflow-hidden text-ellipsis"
|
||||
/>
|
||||
<ScrambleText
|
||||
v-if="!showProfileConfig"
|
||||
v-if="!appStore.showProfileConfig"
|
||||
class="min-w-0 overflow-hidden text-ellipsis text-sm text-zinc-600"
|
||||
scramble-on-mount
|
||||
:fill-interval="20"
|
||||
@@ -26,7 +35,7 @@
|
||||
<DropdownMenuTrigger>
|
||||
<Transition name="fade">
|
||||
<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"
|
||||
@click="console.log('Add profile not implemented!')"
|
||||
>
|
||||
@@ -61,84 +70,21 @@
|
||||
:list="deviceStore.profileTags"
|
||||
v-bind="dragOptions"
|
||||
handle=".category-handle"
|
||||
@start="drag = true"
|
||||
@end="drag = false"
|
||||
@start="appStore.setProfileManagerDragging(true)"
|
||||
@end="appStore.setProfileManagerDragging(false)"
|
||||
@change="onCategoryDrop"
|
||||
>
|
||||
<template #item="dragCategory">
|
||||
<Collapsible v-model:open="collapse[dragCategory.element]" :default-open="true">
|
||||
<!-- TODO: Make profile groups computed instead defining them of using v-for -->
|
||||
<CollapsibleTrigger
|
||||
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>
|
||||
<ProfileCategory
|
||||
:category-name="dragCategory.element"
|
||||
:category-index="dragCategory.index"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<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 />
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -146,32 +92,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Separator } from '@renderer/components/ui/separator'
|
||||
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 { useAppStore } from '@renderer/appStore'
|
||||
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 draggable from 'vuedraggable'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { Separator } from '@renderer/components/ui/separator'
|
||||
import { useDeviceStore } from '@renderer/deviceStore'
|
||||
import { ArrowLeft, List, Plus } from 'lucide-vue-next'
|
||||
import { ref, watch } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
defineProps({
|
||||
showFilter: {
|
||||
@@ -181,6 +116,7 @@ defineProps({
|
||||
})
|
||||
|
||||
const deviceStore = useDeviceStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const dragOptions = ref({
|
||||
ghostClass: 'ghost',
|
||||
@@ -190,15 +126,10 @@ const dragOptions = ref({
|
||||
|
||||
const maxProfiles = 32
|
||||
|
||||
const collapse = ref({})
|
||||
const renderProfileConfig = ref(appStore.showProfileConfig)
|
||||
const renderProfileList = ref(!appStore.showProfileConfig)
|
||||
|
||||
const showProfileConfig = ref(false)
|
||||
const renderProfileConfig = ref(showProfileConfig.value)
|
||||
const renderProfileList = ref(!showProfileConfig.value)
|
||||
|
||||
const drag = ref(false)
|
||||
|
||||
watch(showProfileConfig, (value) => {
|
||||
watch(appStore.showProfileConfig, (value) => {
|
||||
if (value) {
|
||||
renderProfileConfig.value = true
|
||||
setTimeout(() => {
|
||||
@@ -246,28 +177,8 @@ const onCategoryDrop = (event) => {
|
||||
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>
|
||||
<style scoped>
|
||||
[data-state='open'] > .chevrot {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: transform 300ms ease;
|
||||
@@ -295,12 +206,4 @@ const onProfileDrop = (event, categoryIndex) => {
|
||||
.sortable-drag {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hideable-header:not(:only-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hideable-header:only-child {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { useAppStore } from '@renderer/appStore'
|
||||
|
||||
interface Profile {
|
||||
export interface Profile {
|
||||
version: number
|
||||
name: string
|
||||
desc: string
|
||||
|
||||
Reference in New Issue
Block a user