ADD: ValueCards
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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
|
||||
class="flex flex-1 items-center px-4"
|
||||
:class="{ 'cursor-pointer hover:bg-zinc-800': showToggle }"
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<CollapsibleTrigger
|
||||
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" />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
@@ -15,34 +15,41 @@
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
: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" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0" :style="{ width: `${width * 1.125}px` }">
|
||||
<PopoverContent class="p-0" :style="{ width: `${width + 35}px` }">
|
||||
<Command>
|
||||
<CommandInput class="h-9" placeholder="Search actions..." />
|
||||
<CommandInput class="h-9" placeholder="Search action types..." />
|
||||
<CommandEmpty>
|
||||
<ScrambleText scramble-on-mount text="No actions found." />
|
||||
<ScrambleText scramble-on-mount text="No action types found." />
|
||||
</CommandEmpty>
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="(action, key) in actionOptions"
|
||||
v-for="(actionType, key) in actionTypeOptions"
|
||||
:key="key"
|
||||
:value="action"
|
||||
:value="actionType"
|
||||
@select="
|
||||
() => {
|
||||
value = key
|
||||
inputValue = key
|
||||
open = false
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ action.label }}
|
||||
{{ actionType.label }}
|
||||
<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>
|
||||
</CommandGroup>
|
||||
@@ -70,7 +77,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<component :is="actionOptions[value]?.component ? actionOptions[value]?.component : WIP" />
|
||||
<component
|
||||
:is="
|
||||
actionTypeOptions[inputValue]?.component ? actionTypeOptions[inputValue]?.component : WIP
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -102,7 +113,7 @@ defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const actionOptions = ref({
|
||||
const actionTypeOptions = ref({
|
||||
sendKey: { label: 'Press Key or Combination', component: SendKeyAction },
|
||||
sendString: { label: 'Type a String', component: SendStringAction },
|
||||
sendMouse: { label: 'Move, Scroll or Click', component: 'SendMouseAction' },
|
||||
@@ -110,9 +121,6 @@ const actionOptions = ref({
|
||||
sendMidi: { label: 'Send a MIDI Message', component: 'SendMidiAction' },
|
||||
sendOsc: { label: 'Send an OSC Message', component: 'SendOscAction' },
|
||||
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' }
|
||||
})
|
||||
|
||||
@@ -120,6 +128,6 @@ const comboboxButton = ref(null)
|
||||
const { width } = useElementSize(comboboxButton)
|
||||
|
||||
const open = ref(false)
|
||||
const value = ref('')
|
||||
const inputValue = ref('')
|
||||
const confirmDelete = ref(false)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col" :class="{ 'gap-2': actions.length }">
|
||||
<draggable
|
||||
key="actionsDraggable"
|
||||
class="flex flex-col gap-2"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="flex flex-col p-4">
|
||||
<Button
|
||||
class="flex-1"
|
||||
:class="{ 'bg-orange-600 hover:bg-orange-500': isCapturing }"
|
||||
:class="{ 'bg-orange-700 hover:bg-orange-600': isCapturing }"
|
||||
@click="toggleCapture"
|
||||
>⬤
|
||||
{{ 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 { useStore } from '@renderer/store'
|
||||
import { ref } from 'vue'
|
||||
import ActionGroup from '../actions/ActionGroup.vue'
|
||||
import ActionGroup from '@renderer/components/config/actions/ActionGroup.vue'
|
||||
|
||||
const store = useStore()
|
||||
const actionsPressed = ref([
|
||||
|
||||
@@ -1,82 +1,23 @@
|
||||
<template>
|
||||
<ConfigSection title="Knob Mapping" :icon-component="PlusCircle">
|
||||
<div class="my-4 px-8">
|
||||
<span class="font-mono text-sm text-muted-foreground">Control:</span>
|
||||
<Popover v-model:open="open">
|
||||
<PopoverTrigger as-child>
|
||||
<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 title="Knob Values" :icon-component="PlusCircle">
|
||||
<template #title>
|
||||
<span class="text-zinc-500"> ({{ values.length }})</span></template
|
||||
>
|
||||
<ValueGroup :values="values" class="p-2" />
|
||||
</ConfigSection>
|
||||
</template>
|
||||
<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 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 { cn } from '@renderer/lib/utils'
|
||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||
import ValueGroup from '@renderer/components/config/values/ValueGroup.vue'
|
||||
|
||||
const knobMappingOptions = ref({
|
||||
sendKey: 'Send a Key for each Step',
|
||||
controlMidi: 'Control a MIDI Value',
|
||||
controlOsc: 'Control an OSC Value',
|
||||
controlVolume: 'Control your OS Volume',
|
||||
moveMouse: 'Move the Mouse',
|
||||
scrollMouse: 'Scroll the Mouse'
|
||||
})
|
||||
|
||||
const comboboxButton = ref(null)
|
||||
|
||||
const open = ref(false)
|
||||
const value = ref('')
|
||||
const values = ref([
|
||||
{
|
||||
id: '1'
|
||||
},
|
||||
{
|
||||
id: '2'
|
||||
}
|
||||
])
|
||||
</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