ADD: ValueCards
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Collapsible v-model:open="collapse" :default-open="true">
|
<Collapsible v-model:open="collapse" :default-open="true">
|
||||||
<div class="flex h-12 w-full bg-zinc-900">
|
<div class="flex h-12 w-full border-b border-zinc-800 bg-zinc-900 hover:bg-zinc-800">
|
||||||
<div
|
<div
|
||||||
class="flex flex-1 items-center px-4"
|
class="flex flex-1 items-center px-4"
|
||||||
:class="{ 'cursor-pointer hover:bg-zinc-800': showToggle }"
|
:class="{ 'cursor-pointer hover:bg-zinc-800': showToggle }"
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
v-if="foldable"
|
v-if="foldable"
|
||||||
class="flex aspect-square h-12 items-center justify-center hover:bg-zinc-800"
|
class="flex aspect-square h-12 items-center justify-center"
|
||||||
>
|
>
|
||||||
<ChevronLeft class="chevrot mt-0.5 size-4 text-muted-foreground transition-transform" />
|
<ChevronLeft class="chevrot mt-0.5 size-4 text-muted-foreground transition-transform" />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|||||||
@@ -15,34 +15,41 @@
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
:aria-expanded="open"
|
:aria-expanded="open"
|
||||||
class="my-2 w-full justify-between"
|
class="my-2 w-full min-w-0 justify-between"
|
||||||
>
|
>
|
||||||
<ScrambleText :text="value ? actionOptions[value].label : 'Select an action...'" />
|
<ScrambleText
|
||||||
|
class="overflow-hidden text-ellipsis text-nowrap"
|
||||||
|
:text="
|
||||||
|
inputValue ? actionTypeOptions[inputValue].label : 'Select an action type...'
|
||||||
|
"
|
||||||
|
/>
|
||||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0" :style="{ width: `${width * 1.125}px` }">
|
<PopoverContent class="p-0" :style="{ width: `${width + 35}px` }">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput class="h-9" placeholder="Search actions..." />
|
<CommandInput class="h-9" placeholder="Search action types..." />
|
||||||
<CommandEmpty>
|
<CommandEmpty>
|
||||||
<ScrambleText scramble-on-mount text="No actions found." />
|
<ScrambleText scramble-on-mount text="No action types found." />
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
v-for="(action, key) in actionOptions"
|
v-for="(actionType, key) in actionTypeOptions"
|
||||||
:key="key"
|
:key="key"
|
||||||
:value="action"
|
:value="actionType"
|
||||||
@select="
|
@select="
|
||||||
() => {
|
() => {
|
||||||
value = key
|
inputValue = key
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ action.label }}
|
{{ actionType.label }}
|
||||||
<Check
|
<Check
|
||||||
:class="cn('ml-auto h-4 w-4', value === key ? 'opacity-100' : 'opacity-0')"
|
:class="
|
||||||
|
cn('ml-auto h-4 w-4', inputValue === key ? 'opacity-100' : 'opacity-0')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
@@ -70,7 +77,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<component :is="actionOptions[value]?.component ? actionOptions[value]?.component : WIP" />
|
<component
|
||||||
|
:is="
|
||||||
|
actionTypeOptions[inputValue]?.component ? actionTypeOptions[inputValue]?.component : WIP
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -102,7 +113,7 @@ defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const actionOptions = ref({
|
const actionTypeOptions = ref({
|
||||||
sendKey: { label: 'Press Key or Combination', component: SendKeyAction },
|
sendKey: { label: 'Press Key or Combination', component: SendKeyAction },
|
||||||
sendString: { label: 'Type a String', component: SendStringAction },
|
sendString: { label: 'Type a String', component: SendStringAction },
|
||||||
sendMouse: { label: 'Move, Scroll or Click', component: 'SendMouseAction' },
|
sendMouse: { label: 'Move, Scroll or Click', component: 'SendMouseAction' },
|
||||||
@@ -110,9 +121,6 @@ const actionOptions = ref({
|
|||||||
sendMidi: { label: 'Send a MIDI Message', component: 'SendMidiAction' },
|
sendMidi: { label: 'Send a MIDI Message', component: 'SendMidiAction' },
|
||||||
sendOsc: { label: 'Send an OSC Message', component: 'SendOscAction' },
|
sendOsc: { label: 'Send an OSC Message', component: 'SendOscAction' },
|
||||||
sendSerial: { label: 'Send a Serial Message', component: 'SendSerialAction' },
|
sendSerial: { label: 'Send a Serial Message', component: 'SendSerialAction' },
|
||||||
controlMedia: { label: 'Control Media Playback', component: 'ControlMediaAction' },
|
|
||||||
controlSystem: { label: 'Control your OS', component: 'ControlSystemAction' },
|
|
||||||
runProgram: { label: 'Start a Program', component: 'RunProgramAction' },
|
|
||||||
changeProfile: { label: 'Change Device Profile', component: 'ChangeProfileAction' }
|
changeProfile: { label: 'Change Device Profile', component: 'ChangeProfileAction' }
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -120,6 +128,6 @@ const comboboxButton = ref(null)
|
|||||||
const { width } = useElementSize(comboboxButton)
|
const { width } = useElementSize(comboboxButton)
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const value = ref('')
|
const inputValue = ref('')
|
||||||
const confirmDelete = ref(false)
|
const confirmDelete = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col" :class="{ 'gap-2': actions.length }">
|
||||||
<draggable
|
<draggable
|
||||||
key="actionsDraggable"
|
key="actionsDraggable"
|
||||||
class="flex flex-col gap-2"
|
class="flex flex-col gap-2"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="flex flex-col p-4">
|
<div class="flex flex-col p-4">
|
||||||
<Button
|
<Button
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
:class="{ 'bg-orange-600 hover:bg-orange-500': isCapturing }"
|
:class="{ 'bg-orange-700 hover:bg-orange-600': isCapturing }"
|
||||||
@click="toggleCapture"
|
@click="toggleCapture"
|
||||||
>⬤
|
>⬤
|
||||||
{{ isCapturing ? 'Capturing Keyboard Input' : 'Capture Keyboard Input' }}
|
{{ isCapturing ? 'Capturing Keyboard Input' : 'Capture Keyboard Input' }}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { PanelBottomClose, PanelBottomOpen, Clock2 } from 'lucide-vue-next'
|
|||||||
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
||||||
import { useStore } from '@renderer/store'
|
import { useStore } from '@renderer/store'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import ActionGroup from '../actions/ActionGroup.vue'
|
import ActionGroup from '@renderer/components/config/actions/ActionGroup.vue'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const actionsPressed = ref([
|
const actionsPressed = ref([
|
||||||
|
|||||||
@@ -1,82 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<ConfigSection title="Knob Mapping" :icon-component="PlusCircle">
|
<ConfigSection title="Knob Values" :icon-component="PlusCircle">
|
||||||
<div class="my-4 px-8">
|
<template #title>
|
||||||
<span class="font-mono text-sm text-muted-foreground">Control:</span>
|
<span class="text-zinc-500"> ({{ values.length }})</span></template
|
||||||
<Popover v-model:open="open">
|
>
|
||||||
<PopoverTrigger as-child>
|
<ValueGroup :values="values" class="p-2" />
|
||||||
<Button
|
|
||||||
ref="comboboxButton"
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
:aria-expanded="open"
|
|
||||||
class="my-2 w-full justify-between"
|
|
||||||
>
|
|
||||||
<ScrambleText :text="value ? knobMappingOptions[value] : 'Select an action...'" />
|
|
||||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent class="p-0" :style="{ width: $refs.comboboxButton?.$el.offsetWidth }">
|
|
||||||
<Command>
|
|
||||||
<CommandInput class="h-9" placeholder="Search actions..." />
|
|
||||||
<CommandEmpty>
|
|
||||||
<ScrambleText scramble-on-mount text="No actions found." />
|
|
||||||
</CommandEmpty>
|
|
||||||
<CommandList>
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem
|
|
||||||
v-for="(action, key) in knobMappingOptions"
|
|
||||||
:key="key"
|
|
||||||
:value="action"
|
|
||||||
@select="
|
|
||||||
() => {
|
|
||||||
value = key
|
|
||||||
open = false
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ action }}
|
|
||||||
<Check
|
|
||||||
:class="cn('ml-auto h-4 w-4', value === key ? 'opacity-100' : 'opacity-0')"
|
|
||||||
/>
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<WIP />
|
|
||||||
</ConfigSection>
|
</ConfigSection>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlusCircle, ChevronsUpDown, Check } from 'lucide-vue-next'
|
import { PlusCircle } from 'lucide-vue-next'
|
||||||
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
||||||
import WIP from '@renderer/components/WIP.vue'
|
|
||||||
import { Popover, PopoverTrigger, PopoverContent } from '@renderer/components/ui/popover'
|
|
||||||
import { Button } from '@renderer/components/ui/button'
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList
|
|
||||||
} from '@renderer/components/ui/command'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@renderer/lib/utils'
|
import ValueGroup from '@renderer/components/config/values/ValueGroup.vue'
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
|
||||||
|
|
||||||
const knobMappingOptions = ref({
|
const values = ref([
|
||||||
sendKey: 'Send a Key for each Step',
|
{
|
||||||
controlMidi: 'Control a MIDI Value',
|
id: '1'
|
||||||
controlOsc: 'Control an OSC Value',
|
},
|
||||||
controlVolume: 'Control your OS Volume',
|
{
|
||||||
moveMouse: 'Move the Mouse',
|
id: '2'
|
||||||
scrollMouse: 'Scroll the Mouse'
|
}
|
||||||
})
|
])
|
||||||
|
|
||||||
const comboboxButton = ref(null)
|
|
||||||
|
|
||||||
const open = ref(false)
|
|
||||||
const value = ref('')
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<ConfigSection title="Every Step" :icon-component="CircleDashed">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-zinc-500"> ({{ actionsEvery.length }})</span></template
|
||||||
|
>
|
||||||
|
<ActionGroup :actions="actionsEvery" class="p-2" />
|
||||||
|
</ConfigSection>
|
||||||
|
<ConfigSection title="Clockwise step" :icon-component="RotateCw">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-zinc-500"> ({{ actionsCw.length }})</span></template
|
||||||
|
>
|
||||||
|
<ActionGroup :actions="actionsCw" class="p-2" />
|
||||||
|
</ConfigSection>
|
||||||
|
<ConfigSection title="Counterclockwise Step" :icon-component="RotateCcw">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-zinc-500"> ({{ actionsCcw.length }})</span></template
|
||||||
|
>
|
||||||
|
<ActionGroup :actions="actionsCcw" class="p-2" />
|
||||||
|
</ConfigSection>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
||||||
|
import ActionGroup from '@renderer/components/config/actions/ActionGroup.vue'
|
||||||
|
import { RotateCw, RotateCcw, CircleDashed } from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const actionsEvery = ref([
|
||||||
|
{
|
||||||
|
id: '1'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const actionsCw = ref([])
|
||||||
|
const actionsCcw = ref([])
|
||||||
|
</script>
|
||||||
168
src/renderer/src/components/config/values/ValueCard.vue
Normal file
168
src/renderer/src/components/config/values/ValueCard.vue
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<div class="overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900/50">
|
||||||
|
<div class="p-4">
|
||||||
|
<span class="font-mono text-sm text-muted-foreground"
|
||||||
|
>Value{{ valueIndex ? ` ${valueIndex}` : '' }}:</span
|
||||||
|
>
|
||||||
|
<span class="float-end mx-2 w-4 cursor-grab">
|
||||||
|
<GripHorizontal class="value-handle mb-0.5 inline-block size-4 text-zinc-600" />
|
||||||
|
</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Popover v-model:open="open">
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
ref="comboboxButton"
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
:aria-expanded="open"
|
||||||
|
class="my-2 w-full min-w-0 justify-between"
|
||||||
|
>
|
||||||
|
<ScrambleText
|
||||||
|
class="overflow-hidden text-ellipsis text-nowrap"
|
||||||
|
:text="inputValue ? valueTypeOptions[inputValue].label : 'Select a value type...'"
|
||||||
|
/>
|
||||||
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="p-0" :style="{ width: `${width + 35}px` }">
|
||||||
|
<Command>
|
||||||
|
<CommandInput class="h-9" placeholder="Search value types..." />
|
||||||
|
<CommandEmpty>
|
||||||
|
<ScrambleText scramble-on-mount text="No value types found." />
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandList>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
v-for="(valueType, key) in valueTypeOptions"
|
||||||
|
:key="key"
|
||||||
|
:value="valueType"
|
||||||
|
@select="
|
||||||
|
() => {
|
||||||
|
inputValue = key
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ valueType.label }}
|
||||||
|
<Check
|
||||||
|
:class="
|
||||||
|
cn('ml-auto h-4 w-4', inputValue === key ? 'opacity-100' : 'opacity-0')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Button
|
||||||
|
v-if="!confirmDelete"
|
||||||
|
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 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>
|
||||||
|
</div>
|
||||||
|
<span class="font-mono text-sm text-muted-foreground">Conditions:</span>
|
||||||
|
<div class="flex gap-2 py-2">
|
||||||
|
<Button
|
||||||
|
v-for="(condition, key) in conditions"
|
||||||
|
:key="key"
|
||||||
|
class="font-heading flex flex-1 basis-1/4 items-center justify-center"
|
||||||
|
:class="{
|
||||||
|
'border border-zinc-200 bg-zinc-300': condition === true,
|
||||||
|
'border border-zinc-800 bg-transparent text-muted-foreground hover:border-zinc-700 hover:bg-zinc-800 hover:text-zinc-300':
|
||||||
|
condition === false
|
||||||
|
}"
|
||||||
|
@click="cycleCondition(key)"
|
||||||
|
>
|
||||||
|
<span class="mr-0.5">{{ key.toUpperCase() }}:</span>
|
||||||
|
<PanelBottomClose v-if="condition === true" class="size-4" />
|
||||||
|
<PanelBottomOpen v-else-if="condition === false" class="size-4" />
|
||||||
|
<HelpCircle v-else class="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<component
|
||||||
|
:is="valueTypeOptions[inputValue]?.component ? valueTypeOptions[inputValue]?.component : WIP"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import WIP from '@renderer/components/WIP.vue'
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from '@renderer/components/ui/popover'
|
||||||
|
import { Button } from '@renderer/components/ui/button'
|
||||||
|
import { Separator } from '@renderer/components/ui/separator'
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList
|
||||||
|
} from '@renderer/components/ui/command'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@renderer/lib/utils'
|
||||||
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
|
import {
|
||||||
|
ChevronsUpDown,
|
||||||
|
Check,
|
||||||
|
GripHorizontal,
|
||||||
|
Trash2,
|
||||||
|
X,
|
||||||
|
PanelBottomClose,
|
||||||
|
PanelBottomOpen,
|
||||||
|
HelpCircle
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { useElementSize } from '@vueuse/core'
|
||||||
|
import TriggerActionsValue from '@renderer/components/config/values/TriggerActionsValue.vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
valueIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const valueTypeOptions = ref({
|
||||||
|
controlMouse: { label: 'Move or Scroll the Mouse', component: 'ControlMouseValue' },
|
||||||
|
controlGamepad: { label: 'Control a Gamepad Axis', component: 'ControlGamepadValue' },
|
||||||
|
controlMidi: { label: 'Control a MIDI Value', component: 'ControlMidiValue' },
|
||||||
|
controlOsc: { label: 'Control an OSC Value', component: 'ControlOscValue' },
|
||||||
|
controlSerial: { label: 'Control a Value over Serial', component: 'ControlSerialValue' },
|
||||||
|
controlProfile: { label: 'Switch Device Profiles', component: 'ControlProfileValue' },
|
||||||
|
triggerActions: { label: 'Trigger Actions on Rotation', component: TriggerActionsValue }
|
||||||
|
})
|
||||||
|
|
||||||
|
const conditions = ref({
|
||||||
|
a: true,
|
||||||
|
b: false,
|
||||||
|
c: false,
|
||||||
|
d: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const cycleCondition = (key: string) => {
|
||||||
|
const condition = conditions.value[key]
|
||||||
|
if (condition === true) conditions.value[key] = false
|
||||||
|
else conditions.value[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const comboboxButton = ref(null)
|
||||||
|
const { width } = useElementSize(comboboxButton)
|
||||||
|
|
||||||
|
const open = ref(false)
|
||||||
|
const inputValue = ref('')
|
||||||
|
const confirmDelete = ref(false)
|
||||||
|
</script>
|
||||||
42
src/renderer/src/components/config/values/ValueGroup.vue
Normal file
42
src/renderer/src/components/config/values/ValueGroup.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col" :class="{ 'gap-2': values.length }">
|
||||||
|
<draggable
|
||||||
|
key="valuesDraggable"
|
||||||
|
class="flex flex-col gap-2"
|
||||||
|
group="knobValues"
|
||||||
|
item-key="id"
|
||||||
|
handle=".value-handle"
|
||||||
|
:list="values"
|
||||||
|
v-bind="dragOptions"
|
||||||
|
>
|
||||||
|
<template #item="dragValue">
|
||||||
|
<div :key="dragValue.element.id">
|
||||||
|
<ValueCard :value-index="dragValue.index + 1" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
<button
|
||||||
|
class="flex flex-1 items-center justify-center rounded-lg border border-zinc-800 bg-zinc-900/50 p-2 text-sm text-muted-foreground hover:bg-zinc-800 hover:text-zinc-200"
|
||||||
|
>
|
||||||
|
<Plus class="mr-2" /> Add a value
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Plus } from 'lucide-vue-next'
|
||||||
|
import ValueCard from '@renderer/components/config/values/ValueCard.vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
defineProps({
|
||||||
|
values: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const dragOptions = ref({
|
||||||
|
ghostClass: 'ghost',
|
||||||
|
animation: 150,
|
||||||
|
direction: 'vertical'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user