mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 15:29:26 +00:00
Macro Recorder added
This commit is contained in:
parent
c514ba151e
commit
a6024f22e7
12 changed files with 510 additions and 122 deletions
71
fe/src/components/macros/MacroOverview.vue
Normal file
71
fe/src/components/macros/MacroOverview.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="macro-overview mcrm-block block__dark">
|
||||
<h4 class="border-b-2 border-transparent">Saved Macros</h4>
|
||||
<div class="macro-overview__list">
|
||||
<div class="macro-item" v-for="(macro, i) in macros.list" :key="i">
|
||||
<ButtonComp variant="dark" class="w-full" size="sm" @click.prevent="runMacro(macro)">
|
||||
<IconKeyboard /> {{ macro }}
|
||||
</ButtonComp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { IconKeyboard } from '@tabler/icons-vue'
|
||||
import ButtonComp from '../base/ButtonComp.vue'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { appUrl } from '@/services/ApiService'
|
||||
|
||||
const macros = reactive({
|
||||
list: [],
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
axios.post(appUrl() + '/macro/list').then((data) => {
|
||||
if (data.data.length > 0) macros.list = data.data
|
||||
})
|
||||
})
|
||||
|
||||
function runMacro(macro) {
|
||||
console.log(macro)
|
||||
axios.post(appUrl() + '/macro/play', { macro: macro }).then((data) => {
|
||||
console.log(data)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.macro-overview {
|
||||
@apply relative
|
||||
grid
|
||||
grid-rows-[auto_1fr];
|
||||
|
||||
&::after {
|
||||
@apply content-['']
|
||||
absolute
|
||||
top-0
|
||||
left-full
|
||||
h-full
|
||||
w-px
|
||||
bg-slate-600;
|
||||
}
|
||||
|
||||
.macro-overview__list {
|
||||
@apply grid
|
||||
gap-1
|
||||
content-start;
|
||||
}
|
||||
|
||||
.macro-item {
|
||||
@apply flex items-center;
|
||||
|
||||
button {
|
||||
@apply w-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,45 +1,25 @@
|
|||
<template>
|
||||
<div class="macro-recorder">
|
||||
<!-- Recorder buttons -->
|
||||
<RecorderHeader />
|
||||
<div class="macro-recorder mcrm-block block__light">
|
||||
<div class="recorder-interface">
|
||||
<!-- Recorder buttons -->
|
||||
<RecorderHeader />
|
||||
|
||||
<!-- Recorder interface container -->
|
||||
<div
|
||||
:class="`recorder-interface__container ${macroRecorder.state.record && 'record'} ${macroRecorder.state.edit && 'edit'}`"
|
||||
>
|
||||
<!-- Shows the macro steps as kbd elements with delay and spacers-->
|
||||
<RecorderOutput />
|
||||
<!-- Input for recording macro steps -->
|
||||
<RecorderInput />
|
||||
<!-- Recorder interface container -->
|
||||
<div
|
||||
:class="`recorder-interface__container ${macroRecorder.state.record && 'record'} ${macroRecorder.state.edit && 'edit'}`"
|
||||
>
|
||||
<!-- Shows the macro steps as kbd elements with delay and spacers-->
|
||||
<RecorderOutput />
|
||||
<!-- Input for recording macro steps -->
|
||||
<RecorderInput />
|
||||
</div>
|
||||
|
||||
<RecorderFooter />
|
||||
</div>
|
||||
|
||||
<RecorderFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// TODO:
|
||||
// X refactor filtering the keys
|
||||
// X add keyup functionality
|
||||
// X add delay between steps
|
||||
// X restyle keys and delay elements
|
||||
// X add lines between steps
|
||||
// X record macro to object
|
||||
// X refactor macro output based on object?
|
||||
// X Make sure keydown is not spamming steps
|
||||
// X Make record button work as a toggle
|
||||
// X Make edit button work
|
||||
// X Make fixed/custom delay work
|
||||
// X Refactor into multiple components and state store
|
||||
// X Make edit key function
|
||||
// X Make edit delay function
|
||||
// X Make delete key function
|
||||
// X Make sure delay is paused when not recording.
|
||||
// X Refactor macro recorder parts.
|
||||
// X X Layout parts should be parts, smaller parts should be components.
|
||||
// X Make reset function
|
||||
// - Make insert button, menu and function
|
||||
|
||||
import RecorderOutput from './parts/RecorderOutput.vue'
|
||||
import RecorderInput from './parts/RecorderInput.vue'
|
||||
|
||||
|
|
@ -53,24 +33,53 @@ const macroRecorder = useMacroRecorderStore()
|
|||
<style>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.macro-recorder {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.recorder-interface {
|
||||
@apply grid
|
||||
grid-rows-[auto_1fr_auto]
|
||||
gap-4
|
||||
h-full
|
||||
transition-[grid-template-rows];
|
||||
}
|
||||
|
||||
.recorder-interface__container {
|
||||
@apply relative
|
||||
w-full
|
||||
h-96
|
||||
my-4
|
||||
rounded-lg
|
||||
bg-slate-900/50
|
||||
border-2
|
||||
border-white/10
|
||||
bg-slate-950/50
|
||||
border
|
||||
border-slate-600
|
||||
overflow-auto
|
||||
transition-colors;
|
||||
|
||||
&.record {
|
||||
@apply border-rose-300 bg-rose-950/50;
|
||||
@apply border-rose-300 bg-rose-400/10;
|
||||
}
|
||||
|
||||
&.edit {
|
||||
@apply border-sky-300 bg-sky-900/50;
|
||||
@apply border-sky-300 bg-sky-900/10;
|
||||
}
|
||||
}
|
||||
|
||||
#macro-name {
|
||||
@apply w-full
|
||||
bg-transparent
|
||||
py-0
|
||||
outline-0
|
||||
border-transparent
|
||||
border-b-slate-300
|
||||
focus:border-transparent
|
||||
focus:border-b-sky-400
|
||||
focus:bg-sky-400/10
|
||||
transition-colors
|
||||
text-lg
|
||||
rounded-none;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
@apply opacity-50 pointer-events-none cursor-not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<span :class="`delay ${active ? 'active' : ''}`">
|
||||
{{ value < 10000 ? value + 'ms' : '>10s' }}
|
||||
<span :class="`delay ${active ? 'active' : ''} ${preset ? 'preset' : ''}`">
|
||||
<template v-if="value < 10000"> {{ value }} <i>ms</i> </template>
|
||||
<template v-else> >10 <i>s</i> </template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { IconTimeDuration10 } from '@tabler/icons-vue'
|
||||
|
||||
defineProps({
|
||||
value: Number,
|
||||
active: Boolean,
|
||||
preset: Boolean,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -22,10 +26,24 @@ span.delay {
|
|||
border
|
||||
border-slate-400
|
||||
text-slate-950
|
||||
font-sans
|
||||
font-semibold
|
||||
rounded-sm
|
||||
text-sm
|
||||
cursor-default;
|
||||
|
||||
&.preset {
|
||||
@apply text-amber-400
|
||||
border-amber-300/80
|
||||
bg-amber-100/60;
|
||||
}
|
||||
|
||||
i {
|
||||
@apply pl-1
|
||||
font-normal
|
||||
not-italic
|
||||
opacity-80;
|
||||
}
|
||||
}
|
||||
|
||||
.edit span.delay {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ const keyObj = ref(null)
|
|||
|
||||
onMounted(() => {
|
||||
keyObj.value = filterKey(macroRecorder.getEditKey())
|
||||
console.log(macroRecorder.getEditKey());
|
||||
console.log(keyObj.value);
|
||||
console.log('---------');
|
||||
// console.log(macroRecorder.getEditKey());
|
||||
// console.log(keyObj.value);
|
||||
// console.log('---------');
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<ContextMenu>
|
||||
<ContextMenu ref="ctxtMenu">
|
||||
<template #trigger>
|
||||
<ButtonComp variant="secondary" size="sm"> <IconAlarmFilled />Fixed delay </ButtonComp>
|
||||
<ButtonComp variant="secondary" size="sm"> <IconTimeDuration15 />Fixed delay </ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<ul>
|
||||
<li @click="macroRecorder.changeDelay(0)">0ms</li>
|
||||
<li @click="macroRecorder.changeDelay(15)">15ms</li>
|
||||
<li @click="macroRecorder.changeDelay(50)">50ms</li>
|
||||
<li @click="macroRecorder.changeDelay(100)">100ms</li>
|
||||
<li @click="changeDelay(0)">0ms</li>
|
||||
<li @click="changeDelay(15)">15ms</li>
|
||||
<li @click="changeDelay(50)">50ms</li>
|
||||
<li @click="changeDelay(100)">100ms</li>
|
||||
<li>
|
||||
<DialogComp>
|
||||
<template #trigger>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
<h4 class="text-slate-50 mb-4">Custom delay</h4>
|
||||
<form
|
||||
class="grid gap-4 w-44"
|
||||
@submit.prevent="macroRecorder.changeDelay(parseInt($refs.customDelayInput.value))"
|
||||
@submit.prevent="changeDelay(parseInt($refs.customDelayInput.value))"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
<script setup>
|
||||
import ContextMenu from '@/components/base/ContextMenu.vue'
|
||||
import { IconAlarmFilled } from '@tabler/icons-vue'
|
||||
import { IconTimeDuration15 } from '@tabler/icons-vue'
|
||||
import ButtonComp from '@/components/base/ButtonComp.vue'
|
||||
import DialogComp from '@/components/base/DialogComp.vue'
|
||||
|
||||
|
|
@ -56,7 +56,12 @@ import { ref } from 'vue'
|
|||
|
||||
const macroRecorder = useMacroRecorderStore()
|
||||
|
||||
const delayMenu = ref(false)
|
||||
const ctxtMenu = ref()
|
||||
|
||||
function changeDelay(num) {
|
||||
macroRecorder.changeDelay(num)
|
||||
ctxtMenu.value.toggle()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,72 @@
|
|||
<template>
|
||||
<div id="insert-key-dialog" class="dialog__content w-80">
|
||||
<div id="insert-key-dialog" class="dialog__content w-96">
|
||||
<h4 class="text-slate-50 mb-4">Insert key {{ position }}</h4>
|
||||
<div class="flex justify-center w-full mb-4">
|
||||
<p v-if="inputFocus" class="text-center">[Press a key]</p>
|
||||
<input
|
||||
class="size-0 opacity-0"
|
||||
type="text"
|
||||
min="0"
|
||||
max="1"
|
||||
ref="insertKeyInput"
|
||||
placeholder="New key"
|
||||
@focusin="inputFocus = true"
|
||||
@focusout="inputFocus = false"
|
||||
@keydown.prevent="handleInsertKey($event)"
|
||||
autofocus
|
||||
/>
|
||||
<div class="insert-output" :class="position == 'before' ? 'flex-row-reverse' : ''">
|
||||
<MacroKey v-if="keyObjs.selected" :key-obj="keyObjs.selected" />
|
||||
<MacroKey v-if="keyObjs.adjacent" :key-obj="keyObjs.adjacent" />
|
||||
<hr class="spacer" />
|
||||
<DelaySpan :preset="true" :value="10" />
|
||||
<hr class="spacer" />
|
||||
<MacroKey
|
||||
v-if="keyObjs.insert"
|
||||
class="insert"
|
||||
:key-obj="keyObjs.insert"
|
||||
:direction="keyObjs.insertDirection"
|
||||
@click="insertKeyInput.focus()"
|
||||
/>
|
||||
<MacroKey v-if="!keyObjs.insert" :empty="true" @click="insertKeyInput.focus()" />
|
||||
<template v-if="keyObjs.adjacentDelay">
|
||||
<hr class="spacer" />
|
||||
<DelaySpan :value="keyObjs.adjacentDelay.value" />
|
||||
</template>
|
||||
<template v-if="keyObjs.adjacent">
|
||||
<hr class="spacer" />
|
||||
<MacroKey :key-obj="keyObjs.adjacent" />
|
||||
</template>
|
||||
</div>
|
||||
<div class="insert-key__direction">
|
||||
<ButtonComp
|
||||
variant="secondary"
|
||||
:class="keyObjs.insertDirection === 'down' ? 'selected' : ''"
|
||||
size="sm"
|
||||
@click.prevent="keyObjs.insertDirection = 'down'"
|
||||
>
|
||||
↓ Down
|
||||
</ButtonComp>
|
||||
<ButtonComp
|
||||
variant="secondary"
|
||||
:class="keyObjs.insertDirection === 'up' ? 'selected' : ''"
|
||||
size="sm"
|
||||
@click.prevent="keyObjs.insertDirection = 'up'"
|
||||
>
|
||||
↑ Up
|
||||
</ButtonComp>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<ButtonComp variant="primary" size="sm" @click="insertKey()">Insert key</ButtonComp>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MacroKey from './MacroKey.vue'
|
||||
import DelaySpan from './DelaySpan.vue'
|
||||
import ButtonComp from '@/components/base/ButtonComp.vue'
|
||||
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useMacroRecorderStore } from '@/stores/macrorecorder'
|
||||
import MacroKey from './MacroKey.vue'
|
||||
import { filterKey } from '@/services/MacroRecordService'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
@ -23,19 +78,57 @@ const macroRecorder = useMacroRecorderStore()
|
|||
const keyObjs = reactive({
|
||||
selected: null,
|
||||
insert: null,
|
||||
insertEvent: null,
|
||||
insertDirection: 'down',
|
||||
adjacent: null,
|
||||
adjacentDelay: null,
|
||||
adjacentDelayIndex: null,
|
||||
})
|
||||
|
||||
const insertKeyInput = ref(null)
|
||||
const inputFocus = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
keyObjs.selected = filterKey(macroRecorder.getEditKey())
|
||||
|
||||
const adjacentKey = macroRecorder.getAdjacentKey(props.position, true)
|
||||
if (adjacentKey) keyObjs.adjacent = filterKey(adjacentKey.key)
|
||||
if (adjacentKey.delay) keyObjs.adjacentDelay = adjacentKey.delay
|
||||
if (adjacentKey.delay) {
|
||||
keyObjs.adjacentDelay = adjacentKey.delay
|
||||
keyObjs.adjacentDelayIndex = adjacentKey.delayIndex
|
||||
}
|
||||
})
|
||||
|
||||
const handleInsertKey = (e) => {
|
||||
keyObjs.insert = filterKey(e)
|
||||
keyObjs.insertEvent = e
|
||||
}
|
||||
|
||||
const insertKey = () => {
|
||||
macroRecorder.insertKey(keyObjs.insertEvent, keyObjs.insertDirection, keyObjs.adjacentDelayIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.insert-output {
|
||||
@apply flex
|
||||
justify-center
|
||||
items-center
|
||||
w-full
|
||||
mb-4;
|
||||
}
|
||||
.insert-key__direction {
|
||||
@apply flex
|
||||
justify-center
|
||||
gap-2
|
||||
mt-6;
|
||||
}
|
||||
button.selected {
|
||||
@apply bg-sky-500
|
||||
ring-2
|
||||
ring-offset-1
|
||||
ring-sky-500;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
<template>
|
||||
<kbd :class="active ? 'active' : ''">
|
||||
<sup v-if="keyObj.loc">
|
||||
{{ keyObj.loc }}
|
||||
</sup>
|
||||
<span :innerHTML="keyObj.str" />
|
||||
<span class="dir">{{ dir.value === 'down' ? '↓' : '↑' }}</span>
|
||||
<kbd :class="`${active ? 'active' : ''} ${empty ? 'empty' : ''}`">
|
||||
<template v-if="keyObj">
|
||||
<sup v-if="keyObj.loc">
|
||||
{{ keyObj.loc }}
|
||||
</sup>
|
||||
<span :innerHTML="keyObj.str" />
|
||||
<span class="dir">{{ dir.value === 'down' ? '↓' : '↑' }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="empty">
|
||||
<span>[ ]</span>
|
||||
</template>
|
||||
</kbd>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { onMounted, onUpdated, reactive } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
keyObj: Object,
|
||||
direction: String,
|
||||
active: Boolean,
|
||||
empty: Boolean,
|
||||
})
|
||||
|
||||
const dir = reactive({
|
||||
|
|
@ -22,9 +29,18 @@ const dir = reactive({
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.empty) return
|
||||
setDirection()
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
setDirection()
|
||||
})
|
||||
|
||||
const setDirection = () => {
|
||||
if (props.direction) dir.value = props.direction
|
||||
else dir.value = props.keyObj.direction
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
@ -37,10 +53,11 @@ kbd {
|
|||
pl-4 pr-2 py-1
|
||||
h-9
|
||||
bg-slate-700
|
||||
font-sans
|
||||
font-mono
|
||||
font-bold
|
||||
text-lg
|
||||
text-white
|
||||
whitespace-nowrap
|
||||
uppercase
|
||||
rounded-md
|
||||
border
|
||||
|
|
@ -60,6 +77,21 @@ kbd {
|
|||
span.dir {
|
||||
@apply text-slate-200 pl-1;
|
||||
}
|
||||
|
||||
&.empty {
|
||||
@apply pl-3 pr-3
|
||||
bg-sky-400/50
|
||||
border-sky-300
|
||||
shadow-sky-600
|
||||
tracking-widest
|
||||
cursor-pointer;
|
||||
}
|
||||
&.insert {
|
||||
@apply bg-yellow-500/50
|
||||
border-yellow-300
|
||||
shadow-yellow-600
|
||||
cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
:has(kdb):not(.edit) kbd {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div id="validation-error__dialog" class="dialog__content">
|
||||
<h4 class="text-slate-50 mb-4">There's an error in your macro</h4>
|
||||
|
||||
<div class="grid gap-4" v-if="(errors && errors.up.length > 0) || errors.down.length > 0">
|
||||
<div v-if="errors.down.length > 0">
|
||||
<p>
|
||||
The following keys have been <strong>pressed</strong> down, but
|
||||
<strong>not released</strong>.
|
||||
</p>
|
||||
<ul>
|
||||
<li v-for="key in errors.down" :key="key">{{ key.toUpperCase() }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="errors.up.length > 0">
|
||||
<p>
|
||||
The following keys have been <strong>released</strong>, but
|
||||
<strong>not pressed</strong> down.
|
||||
</p>
|
||||
<ul>
|
||||
<li v-for="key in errors.up" :key="key">{{ key.toUpperCase() }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<ButtonComp size="sm" variant="danger" @click="macroRecorder.state.validationErrors = false">
|
||||
Close
|
||||
</ButtonComp>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ButtonComp from '@/components/base/ButtonComp.vue'
|
||||
import { useMacroRecorderStore } from '@/stores/macrorecorder'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
|
||||
const macroRecorder = useMacroRecorderStore()
|
||||
|
||||
const errors = reactive({
|
||||
up: [],
|
||||
down: [],
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
macroRecorder.$subscribe((mutation) => {
|
||||
if (mutation.events && mutation.events.key == 'validationErrors') {
|
||||
errors.up = mutation.events.newValue !== false ? macroRecorder.state.validationErrors.up : []
|
||||
errors.down =
|
||||
mutation.events.newValue !== false ? macroRecorder.state.validationErrors.down : []
|
||||
}
|
||||
console.log(mutation)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
</style>
|
||||
|
|
@ -4,53 +4,60 @@
|
|||
class="flex gap-2"
|
||||
v-if="macroRecorder.state.editKey !== false && typeof macroRecorder.getEditKey() === 'object'"
|
||||
>
|
||||
<ContextMenu>
|
||||
<ContextMenu ref="ctxtMenu">
|
||||
<template #trigger>
|
||||
<ButtonComp variant="success" size="sm"> <IconPlus /> Insert key </ButtonComp>
|
||||
<ButtonComp variant="dark" size="sm"> <IconPlus /> Insert </ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<ul>
|
||||
<li @click="insertPosition = 'before'"><IconArrowLeftCircle /> Before</li>
|
||||
<li @click="insertPosition = 'after'"><IconArrowRightCircle /> After</li>
|
||||
<li @click="insert.position = 'before'"><IconArrowLeftCircle /> Before</li>
|
||||
<li @click="insert.position = 'after'"><IconArrowRightCircle /> After</li>
|
||||
</ul>
|
||||
</template>
|
||||
</ContextMenu>
|
||||
<DialogComp v-if="insertPosition !== null" :open="true" @on-close="insertPosition = null">
|
||||
|
||||
<DialogComp
|
||||
v-if="insert.position !== null"
|
||||
:open="insert.position !== null"
|
||||
@on-open="onOpenDialog"
|
||||
@on-close="onCloseDialog"
|
||||
>
|
||||
<template #content>
|
||||
<InsertKeyDialog :position="insertPosition" />
|
||||
<InsertKeyDialog :position="insert.position" />
|
||||
</template>
|
||||
</DialogComp>
|
||||
|
||||
<DialogComp>
|
||||
<template #trigger>
|
||||
<ButtonComp size="sm" variant="danger" @click="console.log('delete')">
|
||||
<IconTrash />Delete key
|
||||
</ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<DeleteKeyDialog />
|
||||
</template>
|
||||
</DialogComp>
|
||||
<DialogComp
|
||||
:id="`edit-key-${macroRecorder.state.editKey}`"
|
||||
@on-close="macroRecorder.state.editKey = false"
|
||||
@on-open="onOpenDialog"
|
||||
@on-close="onCloseDialog"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonComp variant="primary" size="sm"> <IconKeyboard />Edit key </ButtonComp>
|
||||
<ButtonComp variant="secondary" size="sm"> <IconPencil />Edit </ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<EditKeyDialog />
|
||||
</template>
|
||||
</DialogComp>
|
||||
|
||||
<DialogComp @on-open="onOpenDialog" @on-close="onCloseDialog">
|
||||
<template #trigger>
|
||||
<ButtonComp size="sm" variant="danger"> <IconTrash />Delete </ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<DeleteKeyDialog />
|
||||
</template>
|
||||
</DialogComp>
|
||||
</div>
|
||||
<DialogComp
|
||||
v-if="
|
||||
macroRecorder.state.editDelay !== false && typeof macroRecorder.getEditDelay() === 'object'
|
||||
"
|
||||
@on-close="macroRecorder.state.editDelay = false"
|
||||
@on-open="onOpenDialog"
|
||||
@on-close="onCloseDialog"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonComp variant="primary" size="sm"> <IconAlarm />Edit delay </ButtonComp>
|
||||
<ButtonComp variant="secondary" size="sm"> <IconAlarm />Edit </ButtonComp>
|
||||
</template>
|
||||
<template #content>
|
||||
<EditDelayDialog />
|
||||
|
|
@ -64,7 +71,7 @@ import {
|
|||
IconAlarm,
|
||||
IconArrowLeftCircle,
|
||||
IconArrowRightCircle,
|
||||
IconKeyboard,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
} from '@tabler/icons-vue'
|
||||
|
|
@ -77,11 +84,30 @@ import EditDelayDialog from '../components/EditDelayDialog.vue'
|
|||
import DeleteKeyDialog from '../components/DeleteKeyDialog.vue'
|
||||
import ContextMenu from '@/components/base/ContextMenu.vue'
|
||||
import InsertKeyDialog from '../components/InsertKeyDialog.vue'
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
|
||||
const macroRecorder = useMacroRecorderStore()
|
||||
|
||||
const insertPosition = ref(null)
|
||||
const insert = reactive({ position: null })
|
||||
const ctxtMenu = ref()
|
||||
|
||||
onMounted(() => {
|
||||
macroRecorder.$subscribe((mutation) => {
|
||||
if (mutation.events && mutation.events.key == 'editKey' && mutation.events.newValue === false) {
|
||||
insert.position = null
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function onOpenDialog() {
|
||||
if (insert.position !== null) ctxtMenu.value.toggle()
|
||||
}
|
||||
function onCloseDialog() {
|
||||
macroRecorder.state.editKey = false
|
||||
macroRecorder.state.editDelay = false
|
||||
|
||||
insert.position = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -8,18 +8,58 @@
|
|||
>
|
||||
<IconRestore /> Reset
|
||||
</ButtonComp>
|
||||
|
||||
<DialogComp ref="errorDialog">
|
||||
<template #content>
|
||||
<ValidationErrorDialog />
|
||||
</template>
|
||||
</DialogComp>
|
||||
|
||||
<ButtonComp
|
||||
v-if="macroRecorder.steps.length > 0"
|
||||
:disabled="macroRecorder.state.record || macroRecorder.state.edit"
|
||||
variant="success"
|
||||
size="sm"
|
||||
@click="toggleSave()"
|
||||
>
|
||||
<IconDeviceFloppy />
|
||||
Save
|
||||
</ButtonComp>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ButtonComp from '@/components/base/ButtonComp.vue'
|
||||
import { IconRestore } from '@tabler/icons-vue'
|
||||
import { IconDeviceFloppy, IconRestore } from '@tabler/icons-vue'
|
||||
|
||||
import { useMacroRecorderStore } from '@/stores/macrorecorder'
|
||||
import DialogComp from '@/components/base/DialogComp.vue'
|
||||
import ValidationErrorDialog from '../components/ValidationErrorDialog.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const macroRecorder = useMacroRecorderStore()
|
||||
|
||||
const errorDialog = ref()
|
||||
|
||||
onMounted(() => {
|
||||
macroRecorder.$subscribe((mutation) => {
|
||||
if (mutation.events && mutation.events.key == 'validationErrors') {
|
||||
errorDialog.value.toggleDialog(mutation.events.newValue !== false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const toggleSave = () => {
|
||||
if (!macroRecorder.save()) errorDialog.value.toggleDialog(true)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.macro-recorder__footer {
|
||||
@apply flex
|
||||
justify-between
|
||||
gap-2;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,39 +1,59 @@
|
|||
<template>
|
||||
<div class="macro-recorder__header">
|
||||
<div :class="`recording__buttons ${macroRecorder.state.edit ? 'disabled' : ''}`">
|
||||
<ButtonComp
|
||||
v-if="!macroRecorder.state.record"
|
||||
variant="primary"
|
||||
@click="macroRecorder.state.record = true"
|
||||
>
|
||||
<IconPlayerRecordFilled class="text-red-500" />Start recording
|
||||
</ButtonComp>
|
||||
<ButtonComp
|
||||
v-if="macroRecorder.state.record"
|
||||
variant="danger"
|
||||
@click="macroRecorder.state.record = false"
|
||||
>
|
||||
<IconPlayerStopFilled class="text-white" />Stop recording
|
||||
</ButtonComp>
|
||||
<div class="w-full grid grid-cols-[auto_1fr_auto] gap-2">
|
||||
<h4 class="">Name:</h4>
|
||||
|
||||
<input
|
||||
id="macro-name"
|
||||
type="text"
|
||||
@input.prevent="changeName($event.target.value)"
|
||||
placeholder="New macro"
|
||||
/>
|
||||
<div :class="`recording__buttons ${!nameSet || macroRecorder.state.edit ? 'disabled' : ''}`">
|
||||
{{ macroRecorder.name }}
|
||||
<ButtonComp
|
||||
v-if="!macroRecorder.state.record"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
@click="macroRecorder.state.record = true"
|
||||
>
|
||||
<IconPlayerRecordFilled class="text-red-500" />Record
|
||||
</ButtonComp>
|
||||
<ButtonComp
|
||||
v-if="macroRecorder.state.record"
|
||||
variant="danger"
|
||||
size="sm"
|
||||
@click="macroRecorder.state.record = false"
|
||||
>
|
||||
<IconPlayerStopFilled class="text-white" />Stop
|
||||
</ButtonComp>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`edit__buttons ${macroRecorder.state.record ? 'disabled' : ''}`">
|
||||
<div
|
||||
v-if="macroRecorder.steps.length > 0"
|
||||
:class="`edit__buttons ${macroRecorder.state.record ? 'disabled' : ''}`"
|
||||
>
|
||||
<div>
|
||||
<ButtonComp
|
||||
v-if="!macroRecorder.state.edit"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@click="macroRecorder.state.edit = true"
|
||||
>
|
||||
<IconPencil />Edit macro
|
||||
<IconPencil />Edit
|
||||
</ButtonComp>
|
||||
<ButtonComp
|
||||
v-if="macroRecorder.state.edit"
|
||||
variant="dark"
|
||||
variant="danger"
|
||||
size="sm"
|
||||
@click="macroRecorder.resetEdit()"
|
||||
>
|
||||
<IconPlayerStopFilled />Stop editing
|
||||
<IconPlayerStopFilled />Stop
|
||||
</ButtonComp>
|
||||
</div>
|
||||
|
||||
<FixedDelayMenu v-if="macroRecorder.state.edit" />
|
||||
|
||||
<EditDialogs />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -46,25 +66,35 @@ import FixedDelayMenu from '../components/FixedDelayMenu.vue'
|
|||
|
||||
import { useMacroRecorderStore } from '@/stores/macrorecorder'
|
||||
import EditDialogs from './EditDialogs.vue'
|
||||
import { computed, onMounted, onUpdated, ref } from 'vue'
|
||||
|
||||
const macroRecorder = useMacroRecorderStore()
|
||||
|
||||
const nameSet = ref(false)
|
||||
|
||||
function changeName(name) {
|
||||
macroRecorder.changeName(name)
|
||||
nameSet.value = name.length > 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.macro-recorder__header {
|
||||
@apply grid
|
||||
grid-cols-[auto_1fr]
|
||||
items-end
|
||||
gap-4;
|
||||
@apply grid
|
||||
gap-4
|
||||
w-full;
|
||||
|
||||
.edit__buttons {
|
||||
@apply flex
|
||||
justify-between
|
||||
gap-2
|
||||
w-full;
|
||||
}
|
||||
|
||||
> div {
|
||||
@apply flex gap-4 items-end;
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-50 pointer-events-none cursor-not-allowed;
|
||||
}
|
||||
@apply flex gap-2 items-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -51,4 +51,8 @@ const macroRecorder = useMacroRecorderStore()
|
|||
top-0 left-0
|
||||
h-fit;
|
||||
}
|
||||
|
||||
hr.spacer:last-of-type {
|
||||
@apply hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue