Merge branch 'development'

This commit is contained in:
Jesse Malotaux 2025-05-03 21:31:46 +02:00
commit 358e4fea17
106 changed files with 10173 additions and 1027 deletions

9
fe/.editorconfig Normal file
View file

@ -0,0 +1,9 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
fe/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

30
fe/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

7
fe/.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

8
fe/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}

View file

@ -2,14 +2,16 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="src/assets/Macrame-Logo-gradient.svg" />
<link rel="icon" type="image/svg+xml" href="mcrm-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark" />
<link rel="preconnect" href="https://fonts.bunny.net" />
<link
href="https://fonts.bunny.net/css?family=fira-code:300,500,700|roboto:100,300,700"
rel="stylesheet"
/>
<title>Vite + Vue</title>
<title>Macrame</title>
<script src="config.js"></script>
</head>
<body>
<div id="app"></div>

60
fe/mcrm-icon.svg Normal file
View file

@ -0,0 +1,60 @@
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 140 80"
style="enable-background:new 0 0 140 80;"
xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_2_);}
.st2{fill:url(#SVGID_3_);}
.st3{fill:url(#SVGID_4_);}
.st4{fill:url(#SVGID_5_);}
.st5{fill:url(#SVGID_6_);}
.st6{fill:url(#SVGID_7_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="28.05" x2="140" y2="28.05">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st0" d="M95.5,18.3l-0.2-0.1C95.2,18.1,95,18,94.8,18c-0.3,0-0.5,0.1-0.7,0.3L82.8,29.6l8.5,8.5l12-12L95.5,18.3z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="28" x2="140" y2="28">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st1" d="M57.3,29.5L46,18.3c-0.2-0.2-0.5-0.3-0.7-0.3s-0.4,0-0.5,0.1l-0.2,0.1L36.8,26l12,12L57.3,29.5z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="65.25" x2="140" y2="65.25">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st2" d="M94.7,67l-14-14l-8.5,8.5l11.3,11.3c1,1,2.1,1.8,3.2,2.5c2.5,1.5,5.3,2.2,8.1,2.2s5.6-0.7,8.1-2.2L94.7,67
L94.7,67z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="32.9162" x2="140" y2="32.9162">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st3" d="M114,15.5l-7.8-7.8c-0.2-0.2-0.5-0.5-0.7-0.7c-5.3-4.6-12.8-5.2-18.7-1.8c-1.1,0.7-2.2,1.5-3.2,2.5L72.2,19
l2.6,2.6l5.9,5.9L92,16.2c0.8-0.8,1.8-1.1,2.8-1.1c0.7,0,1.4,0.2,2,0.5l0.1-0.1l8.5,8.5l13.4,13.4c0.8,0.8,1.1,1.8,1.1,2.8
s-0.4,2.1-1.1,2.8l-11.3,11.3l5,5l3.5,3.5l11.3-11.3c3.1-3.1,4.7-7.2,4.7-11.3c0-4.1-1.5-8.2-4.6-11.3L114,15.5z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="38.2163" x2="140" y2="38.2163">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st4" d="M105.4,56.5l-3.5-3.5l-4.5-4.5L81.2,32.2l-8.5-8.5l-2.6-2.6L56.6,7.7c-1-1-2.1-1.8-3.2-2.5
C47.6,1.8,40,2.4,34.8,7c-0.3,0.2-0.5,0.4-0.7,0.7l-7.8,7.8L12.8,28.9C9.7,32,8.1,36.1,8.1,40.2c0,4.1,1.6,8.2,4.7,11.3l11.3,11.3
l3.5-3.5l5-5L21.3,43c-0.8-0.8-1.1-1.8-1.1-2.8s0.4-2.1,1.1-2.8L34.7,24l8.5-8.5l0.1,0.1c1.5-0.9,3.6-0.7,4.8,0.6l11.3,11.3l2.1,2.1
l8.5,8.5l2.1,2.1l8.5,8.5l16.2,16.2L97,65l8.4,8.4c0.3-0.2,0.5-0.4,0.7-0.7l7.8-7.8l-3.5-3.4L105.4,56.5z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="59.85" x2="140" y2="59.85">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st5" d="M70.1,42.3l-8.5,8.5L45.4,67l-0.1,0.1L40.4,72l-3.2,3.2c2.5,1.5,5.3,2.2,8.1,2.2s5.6-0.8,8.1-2.2
c1.1-0.7,2.2-1.5,3.2-2.5L70,59.3l8.5-8.5L70.1,42.3z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="-9.094947e-13" y1="52.6" x2="140" y2="52.6">
<stop offset="0" style="stop-color:#FFB900"/>
<stop offset="1" style="stop-color:#00BCFF"/>
</linearGradient>
<path class="st6" d="M43.1,65.1l0.1-0.1l16.2-16.2l8.5-8.5l-8.4-8.6L51,40.2L38.2,53l-3.5,3.5l-5,5L26.2,65l7.8,7.8
c0.2,0.2,0.5,0.5,0.7,0.7l3.5-3.5L43.1,65.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

1238
fe/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite --profile",
"dev": "vite --host",
"build": "vite build --emptyOutDir",
"preview": "vite preview",
"lint": "eslint . --fix",
@ -27,6 +27,7 @@
"eslint-plugin-vue": "^9.32.0",
"pinia": "^3.0.1",
"prettier": "^3.5.1",
"qrcode": "^1.5.4",
"sass-embedded": "^1.85.1",
"tailwindcss": "^4.0.9",
"uuid": "^11.1.0",

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="app-background">
<img src="./assets/img/bg-gradient.svg" aria-hidden="true" />
@ -5,21 +26,46 @@
</div>
<MainMenu />
<RouterView />
<AlertComp
v-if="!isLocal && !handshake && route.fullPath !== '/devices'"
variant="warning"
:page-wide="true"
href="/devices"
>
<h4>Not authorized!</h4>
<p>Click here to start authorization and open the "Devices" page on your PC.</p>
</AlertComp>
</template>
<script setup>
import MainMenu from '@/components/base/MainMenu.vue'
import { onMounted } from 'vue'
import { RouterView } from 'vue-router'
import { onMounted, ref } from 'vue'
import { RouterView, useRoute } from 'vue-router'
import { useDeviceStore } from './stores/device'
import { isLocal } from './services/ApiService'
import AlertComp from './components/base/AlertComp.vue'
const device = useDeviceStore()
const route = useRoute()
const handshake = ref(false)
onMounted(() => {
// Setting device uuid from localstorage
// If not present in LocalStorage a new uuidV4 will be generated
device.uuid()
if (!isLocal) appHandshake()
device.$subscribe(() => {
if (device.key()) handshake.value = true
})
})
async function appHandshake() {
const hsReq = await device.remoteHandshake()
handshake.value = hsReq
}
</script>
<style scoped>
@ -45,7 +91,6 @@ onMounted(() => {
top-[10%]
left-[10%]
scale-[1.8]
p-28
opacity-35
mix-blend-overlay;
}

View file

@ -1,89 +1,38 @@
@import "./style/_macro.css";
@import "./style/_mcrm-block.css";
@import "./style/_panel.css";
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
@import "tailwindcss";
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@import './style/_content.css';
@import './style/_form.css';
@import './style/_scrollbar.css';
@import './style/_macro.css';
@import './style/_mcrm-block.css';
@import './style/_panel.css';
@import 'tailwindcss';
@variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: "Roboto", sans-serif;
--font-mono: "Fira Code", monospace;
}
body {
@apply font-sans
font-light
tracking-wide
bg-slate-900
text-slate-50;
}
h1,
h2 {
@apply font-mono
font-bold;
}
h3,
h4,
h5,
h6 {
@apply font-semibold;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl;
}
h3 {
@apply text-2xl;
}
h4 {
@apply text-xl;
}
h5 {
@apply text-lg;
}
input {
@apply w-full
px-2 py-1
border
border-slate-400
text-white
rounded-md
bg-black/20;
}
:has(> input + span) {
@apply flex;
input {
@apply rounded-r-none;
}
span {
@apply flex
items-center
px-2
rounded-r-md
text-white
bg-slate-400;
}
}
ul {
@apply list-disc
list-inside;
}
strong {
@apply font-bold;
html,
body,
:not(#panel-html__body) {
--font-sans: 'Roboto', sans-serif;
--font-mono: 'Fira Code', monospace;
}

View file

@ -0,0 +1,68 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
body {
@apply font-sans font-light tracking-wide bg-slate-900 text-slate-50;
}
h1,
h2 {
@apply font-mono font-bold;
}
h3,
h4,
h5,
h6 {
@apply font-semibold;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl;
}
h3 {
@apply text-2xl;
}
h4 {
@apply text-xl;
}
h5 {
@apply text-lg;
}
ul {
@apply list-disc list-inside;
}
strong {
@apply font-bold;
}
a {
@apply underline text-amber-400 hover:text-amber-300;
}

View file

@ -0,0 +1,49 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.input-group {
@apply grid gap-2;
}
input,
select {
@apply w-full px-2 py-1 text-white border rounded-md border-slate-400 bg-black/20;
}
:has(> input + span) {
@apply flex;
input {
@apply rounded-r-none;
}
span {
@apply flex items-center px-2 text-white rounded-r-md bg-slate-400;
}
}
select option {
@apply bg-slate-700;
&:not([disabled]) {
@apply cursor-pointer;
}
}

View file

@ -1,3 +1,24 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* @reference "main"; */
hr.spacer {
@apply relative

View file

@ -1,11 +1,26 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.mcrm-block {
@apply relative
p-6
gap-x-6
gap-y-2
backdrop-blur-lg
rounded-2xl
overflow-hidden;
@apply relative p-6 overflow-hidden gap-x-6 gap-y-2 backdrop-blur-lg rounded-2xl;
&::before {
@apply content-['']
@ -16,7 +31,8 @@
size-full
bg-gradient-to-br
to-transparent
z-[-1];
z-[10]
pointer-events-none;
mask:
linear-gradient(#000 0 0) exclude,
@ -40,18 +56,18 @@
}
&.block__primary {
@apply bg-sky-300/40;
@apply bg-sky-300/20;
&::before {
@apply from-sky-100/40;
@apply from-sky-100/20;
}
}
&.block__secondary {
@apply bg-amber-300/40;
@apply bg-amber-300/20;
&::before {
@apply from-amber-100/40;
@apply from-amber-100/20;
}
}

View file

@ -1,3 +1,24 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.panel {
@apply grid
grid-rows-[auto_1fr]
@ -22,19 +43,12 @@
}
.panel__title {
@apply bg-gradient-to-r
w-fit
from-amber-300
to-white/50
pt-3
pl-16 sm:pl-4
bg-clip-text
text-transparent;
@apply pt-3 pl-16 text-transparent bg-gradient-to-r w-fit from-amber-300 to-white/50 sm:pl-4 bg-clip-text;
}
.panel__content {
@apply grid
h-full
h-[calc(100%-1rem)]
pt-4 sm:pt-0
pl-0 sm:pl-4
overflow-auto;

View file

@ -0,0 +1,44 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
::-webkit-scrollbar {
@apply w-2;
}
::-moz-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-thumb {
@apply rounded bg-slate-400/80;
}
::-moz-scrollbar-thumb {
@apply rounded bg-slate-400/80;
}
::-webkit-scrollbar-track {
@apply mr-1 rounded bg-slate-100/10;
}
::-moz-scrollbar-track {
@apply mr-1 rounded bg-slate-100/10;
}

View file

@ -1,20 +1,76 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="accordion">
<header>
<slot name="title" />
<header @click="toggleAccordion(!accordionOpen)">
<h4>{{ title }}</h4>
<ButtonComp variant="ghost" size="sm" class="!px-1">
<IconChevronDown v-if="!accordionOpen" />
<IconChevronUp v-else />
</ButtonComp>
</header>
<section :class="`accordion__content ${open ? 'open' : ''}`">
<div>
<slot name="content" />
<section :class="`accordion__wrapper ${accordionOpen ? 'open' : ''}`">
<div class="accordion__content">
<slot />
</div>
</section>
</div>
</template>
<script setup>
defineProps({
import { onMounted, onUpdated, ref } from 'vue'
import ButtonComp from './ButtonComp.vue'
import { IconChevronDown, IconChevronUp } from '@tabler/icons-vue'
const emit = defineEmits(['onOpen', 'onClose', 'onToggle'])
defineExpose({ toggleAccordion })
const props = defineProps({
title: String,
open: Boolean,
})
const accordionOpen = ref(false)
onMounted(() => {
if (props.open) toggleAccordion(props.open)
})
onUpdated(() => {
if (props.open) toggleAccordion(props.open)
})
function toggleAccordion(open = false) {
if (open) {
accordionOpen.value = true
emit('onOpen')
} else {
accordionOpen.value = false
emit('onClose')
}
emit('onToggle')
}
</script>
<style scoped>
@ -23,16 +79,40 @@ defineProps({
.accordion {
@apply grid;
.accordion__content {
header {
@apply grid
grid-cols-[1fr_auto]
px-4 py-2
cursor-pointer;
}
.accordion__wrapper {
@apply grid
grid-rows-[0fr]
overflow-hidden
border-y
border-b-white/60
border-t-transparent
duration-300
ease-in-out;
div {
.accordion__content {
@apply grid
grid-rows-[0fr];
grid-rows-[0fr]
overflow-hidden
opacity-0
transition-opacity
delay-0;
}
&.open {
@apply grid-rows-[1fr]
border-t-white/20;
.accordion__content {
@apply grid-rows-[1fr]
opacity-100
delay-200;
}
}
}
}

View file

@ -1,10 +1,36 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div :class="`alert alert__${type}`">
<IconInfoCircle v-if="type === 'info'" />
<IconCheck v-if="type === 'success'" />
<IconExclamationCircle v-if="type === 'warning'" />
<IconAlertTriangle v-if="type === 'error'" />
<slot />
<div
:class="`alert alert__${variant} ${pageWide ? 'page-wide' : ''}`"
@click="href ? router.push(href) : null"
>
<IconInfoCircle v-if="variant === 'info'" />
<IconCheck v-if="variant === 'success'" />
<IconExclamationCircle v-if="variant === 'warning'" />
<IconAlertTriangle v-if="variant === 'error'" />
<div class="alert__content">
<slot />
</div>
</div>
</template>
@ -15,10 +41,15 @@ import {
IconExclamationCircle,
IconInfoCircle,
} from '@tabler/icons-vue'
import { useRouter } from 'vue-router'
defineProps({
type: String, // info, success, warning, error
variant: String, // info, success, warning, error
pageWide: Boolean,
href: String,
})
const router = useRouter()
</script>
<style scoped>
@ -51,5 +82,19 @@ defineProps({
&.alert__error {
@apply text-rose-400 bg-rose-400/10;
}
&.page-wide {
@apply fixed
bottom-0 left-0
w-full;
}
&[href] {
@apply cursor-pointer;
}
.alert__content {
@apply grid gap-2;
}
}
</style>

View file

@ -1,8 +1,29 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<template v-if="href">
<a :href="href" :class="classString">
<RouterLink :to="href" :class="classString">
<slot />
</a>
</RouterLink>
</template>
<template v-else>
<button :class="classString">
@ -12,7 +33,7 @@
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { computed } from 'vue'
const props = defineProps({
href: String,
@ -45,7 +66,8 @@ button,
tracking-wide
font-normal
transition-all
cursor-pointer;
cursor-pointer
no-underline;
transition:
border-color 0.1s ease-in-out,
@ -64,19 +86,29 @@ button,
@apply size-5 transition-[stroke] duration-400 ease-in-out;
}
&.btn__sm svg {
@apply size-4;
&.btn__sm {
@apply px-3 py-1
text-sm;
svg {
@apply size-4;
}
}
&.btn__lg svg {
@apply size-6;
&.btn__lg {
@apply px-6 py-3
text-lg;
svg {
@apply size-6;
}
}
&:hover {
@apply !text-white;
@apply text-white;
svg {
@apply !stroke-white;
@apply stroke-current;
}
}

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="button-group">
<slot />

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="context-menu">
<div class="context-menu__trigger" @click="toggle">
@ -25,8 +46,6 @@ onMounted(() => {
})
function toggle() {
console.log('toggle')
menuOpen.value = !menuOpen.value
}
</script>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="dialog-container">
<div class="trigger" @click="toggleDialog(true)">

View file

@ -0,0 +1,59 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div v-if="loading" class="loading-component">
<span v-if="text">
{{ text }}
</span>
<IconLoader3 class="duration-1000 animate-spin" />
</div>
</template>
<script setup>
import { IconLoader3 } from '@tabler/icons-vue'
defineProps({
loading: Boolean,
text: String,
})
</script>
<style>
@reference "@/assets/main.css";
&:has(.loading-component) {
@apply relative;
}
.loading-component {
@apply absolute
inset-0
size-full
flex gap-2
flex-col
justify-center
items-center
text-sm
bg-black/50
backdrop-blur-md;
}
</style>

View file

@ -1,12 +1,29 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<nav id="main-menu">
<button
id="menu-toggle"
:class="menuOpen ? 'open' : ''"
@click="menuOpen = !menuOpen"
>
<button id="menu-toggle" :class="menuOpen ? 'open' : ''" @click="menuOpen = !menuOpen">
<img
class="logo p-1"
class="p-1 logo"
:class="{ 'opacity-0': menuOpen }"
src="@/assets/img/Macrame-Logo-gradient.svg"
aria-hidden="true"
@ -15,36 +32,30 @@
</button>
<ul :class="menuOpen ? 'open' : ''">
<li>
<RouterLink @click="menuOpen = false" to="/">
<IconHome />Dashboard
</RouterLink>
<RouterLink @click="menuOpen = false" to="/"> <IconHome />Dashboard </RouterLink>
</li>
<li>
<RouterLink @click="menuOpen = false" to="/panels">
<IconLayoutGrid />Panels
</RouterLink>
<RouterLink @click="menuOpen = false" to="/panels"> <IconLayoutGrid />Panels </RouterLink>
</li>
<li>
<RouterLink @click="menuOpen = false" to="/macros">
<IconKeyboard />Macros
</RouterLink>
<li v-if="isLocal()">
<RouterLink @click="menuOpen = false" to="/macros"> <IconKeyboard />Macros </RouterLink>
</li>
<li>
<RouterLink @click="menuOpen = false" to="/devices">
<IconDevices />Device
<IconDevices />{{ isLocal() ? 'Devices' : 'Server' }}
</RouterLink>
</li>
<li>
<!-- <li>
<RouterLink @click="menuOpen = false" to="/settings">
<IconSettings />Settings
</RouterLink>
</li>
</li> -->
</ul>
</nav>
</template>
<script setup>
import { RouterLink } from "vue-router";
import { RouterLink } from 'vue-router'
import {
IconDevices,
IconHome,
@ -52,10 +63,11 @@ import {
IconLayoutGrid,
IconSettings,
IconX,
} from "@tabler/icons-vue";
import { ref } from "vue";
} from '@tabler/icons-vue'
import { ref } from 'vue'
import { isLocal } from '@/services/ApiService'
const menuOpen = ref(false);
const menuOpen = ref(false)
</script>
<style>
@ -116,6 +128,8 @@ nav {
items-center
gap-2
px-4 py-2
text-white
no-underline
border-transparent
transition-colors;

View file

@ -0,0 +1,107 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="remote-dashboard">
<div id="panels" class="dashboard-block mcrm-block block__light" v-if="server.handshake">
<div class="icon__container">
<IconLayoutGrid />
</div>
<h4>{{ server.panelCount }} {{ server.panelCount != 1 ? 'Panels' : 'Panel' }}</h4>
<template v-if="server.panelCount == 0">
<p><em>No panels found. </em></p>
<p>Learn how to create a panel <a href="#" target="_blank">here</a>.</p>
</template>
<template v-else>
<p>Start using a panel!</p>
<ButtonComp variant="danger" href="/panels"> <IconLayoutGrid /> View panels </ButtonComp>
</template>
</div>
<div id="server" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconServer />
</div>
<h4>Server</h4>
<template v-if="server.handshake">
<p>
Linked with: <strong class="text-center">{{ server.ip }}</strong>
</p>
<ButtonComp variant="primary" href="/devices"> <IconServer /> View server</ButtonComp>
</template>
<template v-else>
<p>
<em>Not linked</em>
</p>
<ButtonComp variant="primary" href="/devices"> <IconLink /> Link with server</ButtonComp>
</template>
</div>
</div>
</template>
<script setup>
import { IconLayoutGrid, IconLink, IconServer } from '@tabler/icons-vue'
import { onMounted, reactive } from 'vue'
import ButtonComp from '../base/ButtonComp.vue'
import { useDeviceStore } from '@/stores/device'
import { usePanelStore } from '@/stores/panel'
const device = useDeviceStore()
const panel = usePanelStore()
const server = reactive({
ip: '',
handshake: '',
panelCount: 0,
})
onMounted(async () => {
const serverIp = await device.serverGetIP()
server.ip = serverIp
if (device.key()) server.handshake = true
device.$subscribe(() => {
if (device.key()) server.handshake = true
})
const panelCount = await panel.getList(true)
server.panelCount = panelCount
})
</script>
<style scoped>
@reference "@/assets/main.css";
#remote-dashboard {
@apply grid
pt-8
gap-4
md:w-fit
h-fit
content-start;
&.not__linked #server {
@apply row-start-1 md:col-start-1;
}
}
</style>

View file

@ -0,0 +1,154 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div
id="server-dashboard"
:class="`${server.remoteCount == 0 ? 'no__devices' : 'devices__found'} ${server.macroCount == 0 ? 'no__macros' : 'macros__found'}`"
>
<div id="devices" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconDevices />
</div>
<h4>{{ server.remoteCount }} {{ server.remoteCount != 1 ? 'Devices' : 'Device' }}</h4>
<template v-if="server.remoteCount == 0">
<p><em>No devices found.</em></p>
<ButtonComp variant="primary" href="/devices"> <IconLink /> Link a device</ButtonComp>
</template>
<template v-else>
<p>Unlink a device or add new devices.</p>
<ButtonComp variant="primary" href="/devices"><IconDevices /> View devices</ButtonComp>
</template>
</div>
<div id="macros" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconKeyboard />
</div>
<h4>{{ server.macroCount }} {{ server.macroCount != 1 ? 'Macros' : 'Macro' }}</h4>
<template v-if="server.macroCount == 0">
<p><em>No macros found.</em></p>
<ButtonComp variant="secondary" href="/macros"> <IconLayoutGrid /> Create macro</ButtonComp>
</template>
<template v-else>
<p>Edit and view your macros.</p>
<ButtonComp variant="secondary" href="/macros"><IconKeyboard /> View macros</ButtonComp>
</template>
</div>
<div id="panels" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconLayoutGrid />
</div>
<h4>{{ server.panelCount }} {{ server.panelCount != 1 ? 'Panels' : 'Panel' }}</h4>
<template v-if="server.panelCount == 0">
<p><em>No panels found. </em></p>
<p>Learn how to create a panel <a href="#" target="_blank">here</a>.</p>
</template>
<template v-else>
<p>Link macros to panels or view a panel.</p>
<ButtonComp variant="danger" href="/panels"> <IconLayoutGrid /> View panels </ButtonComp>
</template>
</div>
</div>
</template>
<script setup>
import { useDeviceStore } from '@/stores/device'
import { usePanelStore } from '@/stores/panel'
import { IconDevices, IconKeyboard, IconLayoutGrid, IconLink } from '@tabler/icons-vue'
import { onMounted, reactive } from 'vue'
import ButtonComp from '../base/ButtonComp.vue'
import { GetMacroList } from '@/services/MacroService'
const device = useDeviceStore()
const panel = usePanelStore()
const server = reactive({
ip: '',
port: '',
fullPath: '',
remoteCount: 0,
macroCount: 0,
panelCount: 0,
})
onMounted(async () => {
const serverIP = await device.serverGetIP()
server.ip = serverIP
// server.port = window.__CONFIG__.MCRM__PORT
// server.fullPath = `http://${server.ip}:${server.port}`
const remoteCount = await device.serverGetRemotes(true)
server.remoteCount = remoteCount
const macroCount = await GetMacroList(true)
server.macroCount = macroCount
const panelCount = await panel.getList(true)
server.panelCount = panelCount
console.log(server)
})
</script>
<style scoped>
@reference "@/assets/main.css";
#server-dashboard {
@apply grid
grid-cols-1
grid-rows-3
md:grid-cols-3
md:grid-rows-1
gap-4
w-fit
h-fit
pt-8;
&.no__devices #devices {
@apply row-start-1 md:col-start-1;
}
&.no__macros.devices__found #devices {
@apply row-start-3 md:col-start-3;
}
&.devices__found #devices {
@apply row-start-3 md:col-start-3;
}
&.no__devices.no__macros #macros {
@apply row-start-2 md:col-start-2;
}
&.no__macros #macros {
@apply row-start-1 md:col-start-1;
}
&.macros__found #macros {
@apply row-start-2 md:col-start-2;
}
&.no__devices.macros__found #macros {
@apply row-start-3 md:col-start-3;
}
}
</style>

View file

@ -1,14 +1,33 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="server-overview">
<AlertComp type="info">
<div class="grid">
<strong>This is a remote device.</strong>
<em>UUID: {{ device.uuid() }} </em>
</div>
<AlertComp variant="info">
<strong>This is a remote device.</strong>
<em>UUID: {{ device.uuid() }} </em>
</AlertComp>
<div class="mcrm-block block__light grid gap-4">
<h4 class="text-lg flex gap-4 items-center justify-between">
<div class="grid gap-4 mcrm-block block__light">
<h4 class="flex items-center justify-between gap-4 text-lg">
<span class="flex gap-4"><IconServer />Server</span>
<ButtonComp variant="primary" @click="checkServerStatus()"><IconReload /></ButtonComp>
</h4>
@ -18,15 +37,25 @@
</p>
<!-- Alerts -->
<AlertComp v-if="server.status === 'authorized'" type="success">Authorized</AlertComp>
<AlertComp v-if="server.status === 'unlinked'" type="warning">Not linked</AlertComp>
<AlertComp v-if="server.status === 'unauthorized'" type="info">
<AlertComp v-if="server.status === 'authorized'" variant="success">Authorized</AlertComp>
<AlertComp v-if="server.status === 'unlinked'" variant="warning">Not linked</AlertComp>
<AlertComp v-if="server.status === 'unauthorized'" variant="info">
<div class="grid gap-2">
<strong>Access requested</strong>
<p>
Navigate to <em class="font-semibold">http://localhost:6970/devices</em> on your pc to
authorize.
</p>
<ul class="mb-4">
<li>
Navigate to <em class="font-semibold">http://localhost:{{ server.port }}/devices</em>.
</li>
<li>
<div class="inline-flex flex-wrap items-center gap-2 w-fit">
Click on
<span class="flex items-center gap-1 p-1 text-sm border rounded-sm">
<IconLink class="size-4" /> Link device
</span>
</div>
</li>
<li>Enter the the pin shown on the desktop in the dialog that will appear.</li>
</ul>
<template v-if="server.link == 'checking'">
<div class="grid grid-cols-[2rem_1fr] gap-2">
<IconReload class="animate-spin" />
@ -40,14 +69,6 @@
</template>
</div>
</AlertComp>
<ButtonComp
v-if="server.status === 'unauthorized'"
variant="primary"
@click="requestAccess()"
>
<IconKey />
Request access
</ButtonComp>
<ButtonComp
variant="danger"
v-if="server.status === 'authorized'"
@ -59,15 +80,17 @@
</div>
<DialogComp ref="linkPinDialog">
<template #content>
<div class="grid gap-4 w-64">
<div class="grid w-64 gap-4">
<h3>Server link pin:</h3>
<form class="grid gap-4" @submit.prevent="decryptKey()">
<input
ref="linkPinInput"
class="input"
id="input-pin"
type="text"
pattern="[0-9]{4}"
v-model="server.inputPin"
autocomplete="off"
/>
<ButtonComp variant="primary">Enter</ButtonComp>
</form>
@ -85,7 +108,7 @@
// - - if checkAccess -> pingLink -> check for device.tmp (go)
// - - if [devicePin] -> handshake -> save key local, close dialog, update server status
import { IconKey, IconPlugConnectedX, IconReload, IconServer } from '@tabler/icons-vue'
import { IconKey, IconLink, IconPlugConnectedX, IconReload, IconServer } from '@tabler/icons-vue'
import AlertComp from '../base/AlertComp.vue'
import ButtonComp from '../base/ButtonComp.vue'
import { onMounted, onUpdated, reactive, ref } from 'vue'
@ -99,9 +122,11 @@ import { appUrl } from '@/services/ApiService'
const device = useDeviceStore()
const linkPinDialog = ref()
const linkPinInput = ref()
const server = reactive({
host: '',
port: window.__CONFIG__.MCRM__PORT,
status: false,
link: false,
inputPin: '',
@ -115,6 +140,8 @@ onMounted(async () => {
onUpdated(() => {
if (!server.status) checkServerStatus()
if (server.status === 'authorized' && server.inputPin) server.inputPin = ''
})
async function checkServerStatus(request = true) {
@ -160,11 +187,13 @@ function pingLink() {
server.encryptedKey = encryptedKey
linkPinDialog.value.toggleDialog(true)
linkPinInput.value.focus()
})
}
async function decryptKey() {
const decryptedKey = decryptAES(server.inputPin, server.encryptedKey)
const handshake = await device.remoteHandshake(decryptedKey)
if (handshake) {

View file

@ -1,63 +1,136 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="device-overview">
<AlertComp type="info">
<div class="grid">
<strong>This is a server!</strong>
<em>UUID: {{ device.uuid() }} </em>
</div>
<AlertComp variant="info">
<strong>This is a server!</strong>
<em>UUID: {{ device.uuid() }} </em>
</AlertComp>
<div class="mcrm-block block__light flex flex-wrap items-start gap-4">
<h4 class="w-full flex gap-4 items-center justify-between mb-4">
<span class="flex gap-4"> <IconDevices />Remote devices </span>
<ButtonComp variant="primary" @click="device.serverGetRemotes()"><IconReload /></ButtonComp>
<div class="flex flex-wrap items-start gap-4 mcrm-block block__light">
<h4 class="flex items-center justify-between w-full gap-4 mb-4">
<span class="flex gap-4">
<IconDevices />{{ Object.keys(remote.devices).length }}
{{ Object.keys(remote.devices).length == 1 ? 'Device' : 'Devices' }}
</span>
<ButtonComp v-if="!remote.poll" variant="primary" @click="device.serverGetRemotes()"
><IconReload
/></ButtonComp>
</h4>
<!-- {{ Object.keys(remote.devices).length }} -->
<template v-if="Object.keys(remote.devices).length > 0">
<div
class="mcrm-block block__dark block-size__sm w-64 grid !gap-4 content-start"
v-for="(remoteDevice, id) in remote.devices"
:key="id"
>
<div class="grid gap-2">
<h5 class="grid grid-cols-[auto_1fr] gap-2">
<IconDeviceUnknown v-if="remoteDevice.settings.type == 'unknown'" />
<IconDeviceMobile v-if="remoteDevice.settings.type == 'mobile'" />
<IconDeviceTablet v-if="remoteDevice.settings.type == 'tablet'" />
<IconDeviceDesktop v-if="remoteDevice.settings.type == 'desktop'" />
<span class="w-full truncate">
{{ remoteDevice.settings.name }}
</span>
</h5>
<em>{{ id }}</em>
<template v-for="(remoteDevice, id) in remote.devices" :key="id">
<div class="mcrm-block block__dark block-size__sm w-64 grid !gap-4 content-start">
<div class="grid gap-2">
<h5 class="grid grid-cols-[auto_1fr] gap-2">
<IconDeviceUnknown v-if="remoteDevice.settings.type == 'unknown'" />
<IconDeviceMobile v-if="remoteDevice.settings.type == 'mobile'" />
<IconDeviceTablet v-if="remoteDevice.settings.type == 'tablet'" />
<IconDeviceDesktop v-if="remoteDevice.settings.type == 'desktop'" />
<span class="w-full truncate">
{{ remoteDevice.settings.name }}
</span>
</h5>
<em>{{ id }}</em>
</div>
<template v-if="remoteDevice.key">
<AlertComp variant="success">Authorized</AlertComp>
<ButtonComp variant="danger" @click="unlinkDevice(id)">
<IconLinkOff />Unlink device
</ButtonComp>
</template>
<template v-else>
<AlertComp variant="warning">Unauthorized</AlertComp>
<ButtonComp variant="primary" @click="startLink(id)">
<IconLink />Link device
</ButtonComp>
</template>
<template v-if="remote.pinlink.uuid == id">
<AlertComp variant="info">One time pin: {{ remote.pinlink.pin }}</AlertComp>
</template>
</div>
<template v-if="remoteDevice.key">
<AlertComp type="success">Authorized</AlertComp>
<ButtonComp variant="danger" @click="unlinkDevice(id)">
<IconLinkOff />Unlink device
</ButtonComp>
</template>
<template v-else>
<AlertComp type="warning">Unauthorized</AlertComp>
<ButtonComp variant="primary" @click="startLink(id)">
<IconLink />Link device
</ButtonComp>
</template>
<template v-if="remote.pinlink.uuid == id">
<AlertComp type="info">One time pin: {{ remote.pinlink.pin }}</AlertComp>
</template>
</div>
</template>
</template>
<template v-else>
<!-- <template v-else>
<div class="grid w-full gap-4">
<em class="text-slate-300">No remote devices</em>
<em class="text-slate-300">No remote devices</em>
</div>
</template>
</template> -->
<AccordionComp
class="w-full mt-8 border-t border-t-white/50"
title="How to connect a device?"
:open="Object.keys(remote.devices).length == 0"
>
<div class="grid py-4">
<ul class="space-y-2">
<li>
Scan the QR code with the remote device.
<div class="grid gap-4 py-4 pl-6">
<canvas ref="serverQr"></canvas>
<p>
Or manually type the IP address: <br />
<strong>{{ server.ip }}/devices</strong>
</p>
</div>
</li>
<li>
The device will automatically request access, if you see "Access requested" on the
device.
</li>
<li v-if="!remote.poll">
<div class="inline-flex items-center gap-2">
Click the
<span class="p-1 border rounded-sm"><IconReload class="size-4" /></span> to reload
the devices.
</div>
</li>
<li>
<div class="inline-flex flex-wrap items-center gap-2 w-fit">
Click on
<span class="flex items-center gap-1 p-1 text-sm border rounded-sm">
<IconLink class="size-4" /> Link device
</span>
A one-time-pin will be shown in a dialog.
</div>
</li>
<li>Enter the pin on the remote device.</li>
<li>
Congratulations! You have linked a device! You can now start using panels on that
device.
</li>
</ul>
</div>
</AccordionComp>
<DialogComp ref="pinDialog">
<template #content>
<div class="grid gap-4">
<h3>Pin code</h3>
<span class="text-4xl font-mono tracking-wide">{{ remote.pinlink.pin }}</span>
<span class="font-mono text-4xl tracking-wide">{{ remote.pinlink.pin }}</span>
</div>
</template>
</DialogComp>
@ -66,12 +139,7 @@
</template>
<script setup>
// TODO
// - startLink -> responsePin also in device block
// - startLink -> poll removal of pin file, if removed close dialog, update device list
// - Make unlink work
import { onMounted, reactive, ref } from 'vue'
import { onMounted, onUpdated, reactive, ref } from 'vue'
import AlertComp from '../base/AlertComp.vue'
import { useDeviceStore } from '@/stores/device'
import {
@ -88,21 +156,54 @@ import ButtonComp from '../base/ButtonComp.vue'
import DialogComp from '../base/DialogComp.vue'
import axios from 'axios'
import { appUrl } from '@/services/ApiService'
import AccordionComp from '../base/AccordionComp.vue'
import QRCode from 'qrcode'
const device = useDeviceStore()
const pinDialog = ref()
const serverQr = ref()
const remote = reactive({ devices: [], pinlink: false })
const server = reactive({
ip: '',
})
onMounted(() => {
const remote = reactive({ devices: [], pinlink: false, poll: false })
onMounted(async () => {
device.serverGetRemotes()
device.$subscribe((mutation, state) => {
if (Object.keys(state.remote).length) remote.devices = device.remote
if (state.remote !== remote.devices) remote.devices = device.remote
})
getIp()
})
onUpdated(() => {
getIp()
if (Object.keys(remote.devices).length == 0 && !remote.poll) {
remote.poll = setInterval(() => {
device.serverGetRemotes()
}, 1000)
}
if (Object.keys(remote.devices).length > 0 && remote.poll) {
clearInterval(remote.poll)
remote.poll = false
}
})
async function getIp() {
const serverIP = await device.serverGetIP()
server.ip = serverIP
QRCode.toCanvas(serverQr.value, `${server.ip}/devices`, (error) => {
if (error) console.log('QRCode error: ', error)
})
}
async function startLink(deviceUuid) {
const pin = await device.serverStartLink(deviceUuid)
@ -135,7 +236,9 @@ function resetPinLink() {
function unlinkDevice(id) {
axios.post(appUrl() + '/device/link/remove', { uuid: id }).then((data) => {
if (data.data) device.serverGetRemotes()
if (data.data) {
device.serverGetRemotes()
}
})
}
</script>

View file

@ -0,0 +1,176 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="input-group form-select">
<label v-if="label">
{{ label }}
</label>
<div class="select__container">
<template v-if="search">
<div class="select__search-bar">
<input
type="search"
ref="selectSearch"
:list="`${name}-search__options`"
v-model="select.search"
@change="selectSearchValue($event)"
:disabled="!select.searchActive"
autocomplete="on"
/>
<datalist :id="`${name}-search__options`">
<option v-for="option in select.options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</datalist>
<ButtonComp v-if="!select.searchActive" variant="ghost" size="sm" @click="initSearch">
<IconSearch />
</ButtonComp>
<ButtonComp v-else variant="ghost" size="sm" @click="resetSearch">
<IconSearchOff />
</ButtonComp>
</div>
</template>
<select :name="name" ref="selectEl" v-model="select.value" @change="changeSelect($event)">
<option value="" disabled>- Select {{ label.toLocaleLowerCase() }} -</option>
<option v-for="option in select.options" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
</div>
</template>
<script setup>
import { IconSearch, IconSearchOff } from '@tabler/icons-vue'
import { onMounted, onUpdated, reactive, ref } from 'vue'
import ButtonComp from '../base/ButtonComp.vue'
const emit = defineEmits(['change'])
const props = defineProps({
label: String,
name: String,
options: [Array, Object],
search: Boolean,
value: String,
})
const select = reactive({
options: [],
search: '',
searchActive: false,
changed: false,
value: '',
})
const selectEl = ref(null)
const selectSearch = ref(null)
onMounted(() => {
setValue()
if (typeof props.options == 'object') select.options = Object.values(props.options)
})
onUpdated(() => {
setValue()
})
const setValue = () => {
if ((select.value == '' && props.value) || (!select.changed && props.value != select.value)) {
select.value = props.value
}
select.changed = false
}
const initSearch = () => {
select.searchActive = true
select.search = ''
selectEl.value.classList = 'search__is-active'
setTimeout(() => {
selectSearch.value.focus()
}, 50)
}
const resetSearch = () => {
select.search = ''
select.searchActive = false
selectEl.value.classList = ''
}
const selectSearchValue = (event) => {
changeSelect(event)
resetSearch()
}
const changeSelect = (event) => {
select.changed = true
select.value = event.target.value
emit('change', select.value)
}
</script>
<style scoped>
@reference "@/assets/main.css";
.select__container {
@apply relative
h-8;
select,
.select__search-bar {
@apply absolute top-0 h-8;
}
}
.select__search-bar {
@apply right-0
grid
grid-cols-[1fr_auto]
items-center
w-full
pr-4
z-10
pointer-events-none;
button {
@apply pointer-events-auto;
}
input {
@apply border-0 bg-transparent pointer-events-auto px-2 py-0 focus:outline-0;
&[disabled] {
@apply pointer-events-none;
}
}
datalist {
@apply absolute
top-full left-0;
}
}
select.search__is-active {
@apply text-transparent;
}
</style>

View file

@ -1,40 +1,115 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="macro-overview mcrm-block block__dark">
<h4 class="border-b-2 border-transparent">Saved Macros</h4>
<div class="macro-overview__list">
<LoadComp :loading="macros.loading" text="Loading macros..." />
<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
:variant="macroRecorder.macroName === macro.name ? 'secondary' : 'dark'"
class="overview__macro-open"
size="sm"
@click="macroRecorder.openMacro(macro.macroname, macro.name)"
>
<IconKeyboard /> <span>{{ macro.name }}</span>
</ButtonComp>
<div class="overview__macro-delete">
<ButtonComp
class="!text-red-500 hover:!text-red-300"
variant="ghost"
size="sm"
@click="startDelete(macro.name)"
>
<IconTrash />
</ButtonComp>
</div>
</div>
</div>
<DialogComp ref="deleteDialog">
<template #content>
<div class="grid gap-2">
<h4 class="pr-4">Are you sure you want to delete:</h4>
<h3 class="mb-2 text-center text-sky-500">{{ macroToBeDeleted }}</h3>
<div class="flex justify-between">
<ButtonComp size="sm" variant="subtle" @click="deleteDialog.toggleDialog(false)">
No
</ButtonComp>
<ButtonComp size="sm" variant="danger" @click="deleteMacro()">Yes</ButtonComp>
</div>
</div>
</template>
</DialogComp>
</div>
</template>
<script setup>
import { IconKeyboard } from '@tabler/icons-vue'
// TODO
// - delete macro
import { IconKeyboard, IconTrash } from '@tabler/icons-vue'
import ButtonComp from '../base/ButtonComp.vue'
import { onMounted, reactive } from 'vue'
import axios from 'axios'
import { appUrl, isLocal } from '@/services/ApiService'
import { AuthCall } from '@/services/EncryptService'
import { onMounted, reactive, ref } from 'vue'
import { GetMacroList } from '@/services/MacroService'
import LoadComp from '../base/LoadComp.vue'
import { useMacroRecorderStore } from '@/stores/macrorecorder'
import DialogComp from '../base/DialogComp.vue'
const macros = reactive({
loading: true,
list: [],
})
const macroRecorder = useMacroRecorderStore()
const macroToBeDeleted = ref('')
const deleteDialog = ref()
onMounted(() => {
axios.post(appUrl() + '/macro/list').then((data) => {
if (data.data.length > 0) macros.list = data.data
})
loadMacroList()
})
function runMacro(macro) {
const data = isLocal() ? { macro: macro } : AuthCall({ macro: macro })
const loadMacroList = async () => {
const list = await GetMacroList()
macros.list = list
macros.loading = false
}
axios.post(appUrl() + '/macro/play', data).then((data) => {
console.log(data)
})
const startDelete = (macroFilename) => {
macroToBeDeleted.value = macroFilename
deleteDialog.value.toggleDialog(true)
}
const deleteMacro = async () => {
const resp = await macroRecorder.deleteMacro(macroToBeDeleted.value)
if (resp) {
deleteDialog.value.toggleDialog(false)
if (macroToBeDeleted.value === macroRecorder.macroName) macroRecorder.resetMacro()
macroToBeDeleted.value = ''
loadMacroList()
}
}
</script>
@ -57,16 +132,32 @@ function runMacro(macro) {
}
.macro-overview__list {
@apply grid
@apply flex
flex-col
pr-1
-mr-1
gap-1
content-start;
h-[calc(100vh-11.7rem)]
overflow-auto;
}
.macro-item {
@apply flex items-center;
@apply grid items-center grid-cols-[1fr_0fr] transition-[grid-template-columns] delay-0 duration-300;
button {
@apply w-full;
&:hover {
@apply grid-cols-[1fr_auto] delay-500;
}
button.overview__macro-open {
@apply w-full grid grid-cols-[1rem_1fr] justify-items-start;
span {
@apply truncate w-full text-left;
}
}
div.overview__macro-delete {
@apply grid overflow-hidden transition;
}
}
}

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="macro-recorder mcrm-block block__light">
<div class="recorder-interface">

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<span :class="`delay ${active ? 'active' : ''} ${preset ? 'preset' : ''}`">
<template v-if="value < 10000"> {{ value }} <i>ms</i> </template>

View file

@ -1,6 +1,27 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="delete-key-dialog" class="dialog__content">
<h4 class="text-slate-50 mb-4">Delete key</h4>
<h4 class="mb-4 text-slate-50">Delete key</h4>
<div class="flex justify-center w-full mb-4">
<MacroKey v-if="keyObj" :key-obj="keyObj" />
</div>
@ -26,9 +47,6 @@ const keyObj = ref(null)
onMounted(() => {
keyObj.value = filterKey(macroRecorder.getEditKey())
// console.log(macroRecorder.getEditKey());
// console.log(keyObj.value);
// console.log('---------');
})
</script>

View file

@ -1,6 +1,27 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="edit-delay-dialog" class="dialog__content">
<h4 class="text-slate-50 mb-4">Edit delay</h4>
<h4 class="mb-4 text-slate-50">Edit delay</h4>
<div v-if="editable.delay.value" class="flex justify-center">
<DelaySpan class="!text-lg" :value="editable.delay.value" />
</div>
@ -41,7 +62,6 @@ const editable = reactive({
onMounted(() => {
editable.delay = macroRecorder.getEditDelay()
editable.newDelay.value = editable.delay.value
console.log(editable)
})
const changeDelay = () => {

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="edit-key-dialog" class="dialog__content">
<h4 class="text-slate-50 mb-4">Press a key</h4>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<ContextMenu ref="ctxtMenu">
<template #trigger>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="insert-key-dialog" class="dialog__content w-96">
<h4 class="text-slate-50 mb-4">Insert key {{ position }}</h4>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<kbd :class="`${active ? 'active' : ''} ${empty ? 'empty' : ''}`">
<template v-if="keyObj">

View file

@ -1,6 +1,27 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="validation-error__dialog" class="dialog__content">
<h4 class="text-slate-50 mb-4">There's an error in your macro</h4>
<h4 class="mb-4 text-slate-50">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">
@ -50,7 +71,6 @@ onMounted(() => {
errors.down =
mutation.events.newValue !== false ? macroRecorder.state.validationErrors.down : []
}
console.log(mutation)
})
})
</script>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="macro-edit__dialogs" v-if="macroRecorder.state.edit !== false">
<div

View file

@ -1,9 +1,29 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="macro-recorder__footer">
<ButtonComp
v-if="macroRecorder.steps.length > 0"
variant="danger"
size="sm"
@click="macroRecorder.reset()"
>
<IconRestore /> Reset
@ -14,13 +34,26 @@
<ValidationErrorDialog />
</template>
</DialogComp>
<DialogComp ref="overwriteDialog">
<template #content>
<div class="grid gap-2">
<h4 class="pr-4">Are you sure you want to overwrite:</h4>
<h3 class="mb-2 text-center text-sky-500">{{ macroRecorder.macroName }}</h3>
<div class="flex justify-between">
<ButtonComp size="sm" variant="subtle" @click="overwriteDialog.toggleDialog(false)"
>No</ButtonComp
>
<ButtonComp size="sm" variant="primary" @click="saveMacro()">Yes</ButtonComp>
</div>
</div>
</template>
</DialogComp>
<ButtonComp
v-if="macroRecorder.steps.length > 0"
:disabled="macroRecorder.state.record || macroRecorder.state.edit"
variant="success"
size="sm"
@click="toggleSave()"
@click="startCheck()"
>
<IconDeviceFloppy />
Save
@ -40,6 +73,7 @@ import { onMounted, ref } from 'vue'
const macroRecorder = useMacroRecorderStore()
const errorDialog = ref()
const overwriteDialog = ref()
onMounted(() => {
macroRecorder.$subscribe((mutation) => {
@ -49,8 +83,20 @@ onMounted(() => {
})
})
const toggleSave = () => {
if (!macroRecorder.save()) errorDialog.value.toggleDialog(true)
const startCheck = async () => {
const checkResp = await macroRecorder.checkMacro()
if (checkResp) overwriteDialog.value.toggleDialog(true)
else saveMacro()
}
const saveMacro = async () => {
overwriteDialog.value.toggleDialog(false)
const saveResp = await macroRecorder.saveMacro()
if (!saveResp) errorDialog.value.toggleDialog(true)
else window.location.reload()
}
</script>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="macro-recorder__header">
<div class="w-full grid grid-cols-[auto_1fr_auto] gap-2">
@ -7,6 +28,7 @@
id="macro-name"
type="text"
@input.prevent="changeName($event.target.value)"
:value="macroName"
placeholder="New macro"
/>
<div :class="`recording__buttons ${!nameSet || macroRecorder.state.edit ? 'disabled' : ''}`">
@ -14,7 +36,6 @@
<ButtonComp
v-if="!macroRecorder.state.record"
variant="primary"
size="sm"
@click="macroRecorder.state.record = true"
>
<IconPlayerRecordFilled class="text-red-500" />Record
@ -22,7 +43,6 @@
<ButtonComp
v-if="macroRecorder.state.record"
variant="danger"
size="sm"
@click="macroRecorder.state.record = false"
>
<IconPlayerStopFilled class="text-white" />Stop
@ -37,7 +57,6 @@
<ButtonComp
v-if="!macroRecorder.state.edit"
variant="secondary"
size="sm"
@click="macroRecorder.state.edit = true"
>
<IconPencil />Edit
@ -45,7 +64,6 @@
<ButtonComp
v-if="macroRecorder.state.edit"
variant="danger"
size="sm"
@click="macroRecorder.resetEdit()"
>
<IconPlayerStopFilled />Stop
@ -66,12 +84,18 @@ import FixedDelayMenu from '../components/FixedDelayMenu.vue'
import { useMacroRecorderStore } from '@/stores/macrorecorder'
import EditDialogs from './EditDialogs.vue'
import { computed, onMounted, onUpdated, ref } from 'vue'
import { computed, onUpdated, ref } from 'vue'
const macroRecorder = useMacroRecorderStore()
const macroName = computed(() => macroRecorder.macroName)
const nameSet = ref(false)
onUpdated(() => {
nameSet.value = macroName.value && macroName.value.length > 0
})
function changeName(name) {
macroRecorder.changeName(name)
nameSet.value = name.length > 0

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div :class="`recorder-input__container ${macroRecorder.state.record && 'record'}`">
<input
@ -5,7 +26,6 @@
:class="`macro-recorder__input ${macroRecorder.state.record && 'record'}`"
type="text"
ref="macroInput"
@focus="console.log('focus')"
@keydown.prevent="macroRecorder.recordStep($event, 'down')"
@keyup.prevent="macroRecorder.recordStep($event, 'up')"
/>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div
:class="`macro-recorder__output ${macroRecorder.state.record && 'record'} ${macroRecorder.state.edit && 'edit'}`"

View file

@ -0,0 +1,253 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="panel-edit" class="mcrm-block block__dark !p-0 !gap-0" v-if="editPanel">
<div class="panel-preview">
<div class="panel-preview__content" ref="panelPreview" v-html="editPanel.html"></div>
</div>
<div class="panel-settings">
<AccordionComp title="Panel info" ref="infoAccordion">
<div class="grid grid-cols-[auto_1fr] gap-2 p-4">
<span>Name:</span><strong class="text-right">{{ editPanel.name }}</strong>
<span>Aspect ratio:</span><strong class="text-right">{{ editPanel.aspectRatio }}</strong>
<template v-if="editPanel.macros">
<span>Linked Macros:</span>
<strong class="text-right">{{ Object.keys(editPanel.macros).length }}</strong>
</template>
</div>
</AccordionComp>
<div>
<AccordionComp
v-if="editButton.id"
title="Button"
ref="buttonAccordion"
:open="editButton.id != ''"
>
<div class="grid gap-4 p-4">
<div class="grid grid-cols-[auto_1fr] gap-2">
<span>Button ID:</span>
<strong class="text-right">{{ editButton.id }}</strong>
</div>
<div class="grid">
<FormSelect
name="button_macro"
label="Button macro"
:search="true"
:options="macroList"
:value="editButton.macro"
@change="checkNewMacro(editButton.id, $event)"
/>
<div class="grid grid-cols-2 mt-4">
<ButtonComp
v-if="editButton.macro != ''"
class="col-start-1 w-fit"
size="sm"
variant="danger"
@click="unlinkMacro(editButton.id)"
ref="unlinkButton"
>
<IconTrash /> Unlink
</ButtonComp>
<ButtonComp
v-if="editButton.changed"
class="col-start-2 w-fit justify-self-end"
size="sm"
variant="primary"
@click="linkMacro(editButton.id)"
ref="linkButton"
>
<IconLink /> Link
</ButtonComp>
</div>
</div>
</div>
</AccordionComp>
</div>
<footer class="flex items-end justify-end h-full p-4">
<ButtonComp v-if="panelMacros.changed" variant="success" @click="savePanelChanges()">
<IconDeviceFloppy /> Save changes
</ButtonComp>
</footer>
</div>
</div>
</template>
<script setup>
import { CheckMacroListChange, GetMacroList } from '@/services/MacroService'
import {
PanelButtonListeners,
PanelDialogListeners,
RemovePanelStyle,
SetPanelStyle,
StripPanelHTML,
} from '@/services/PanelService'
import { usePanelStore } from '@/stores/panel'
import { onMounted, onUnmounted, reactive, ref } from 'vue'
import AccordionComp from '../base/AccordionComp.vue'
import FormSelect from '../form/FormSelect.vue'
import ButtonComp from '../base/ButtonComp.vue'
import { IconDeviceFloppy, IconLink, IconTrash } from '@tabler/icons-vue'
import axios from 'axios'
import { appUrl } from '@/services/ApiService'
const props = defineProps({
dirname: String,
})
const panel = usePanelStore()
const panelPreview = ref(false)
const editPanel = ref({})
const panelMacros = reactive({
old: {},
changed: false,
})
const macroList = ref({})
const infoAccordion = ref(false)
const buttonAccordion = ref(false)
const unlinkButton = ref(null)
const linkButton = ref(null)
const editButton = reactive({
id: '',
macro: '',
newMacro: '',
changed: false,
})
onMounted(async () => {
const currentPanel = await panel.get(props.dirname)
editPanel.value = currentPanel
editPanel.value.dir = props.dirname
editPanel.value.html = StripPanelHTML(editPanel.value.html, editPanel.value.aspectRatio)
panelMacros.old = JSON.stringify(currentPanel.macros)
infoAccordion.value.toggleAccordion(true)
const macros = await GetMacroList()
macroList.value = Object.assign(
{},
...Object.keys(macros).map((key) => ({
[key]: { value: macros[key].macroname, label: macros[key].name },
})),
)
SetPanelStyle(editPanel.value.style)
EditButtonListeners()
})
onUnmounted(() => {
RemovePanelStyle()
})
function EditButtonListeners() {
const callback = (button) => {
infoAccordion.value.toggleAccordion(false)
setEditButton(button.id)
}
PanelButtonListeners(panelPreview.value, callback)
PanelDialogListeners(panelPreview.value)
}
function setEditButton(id) {
editButton.id = id
editButton.macro = editPanel.value.macros[id] ? editPanel.value.macros[id] : ''
}
function checkNewMacro(id, macro) {
editButton.changed = editPanel.value.macros[id] != macro
editButton.newMacro = macro
}
function linkMacro(id) {
editPanel.value.macros[id] = editButton.newMacro
editButton.macro = editButton.newMacro
editButton.newMacro = ''
panelMacros.changed = CheckMacroListChange(panelMacros.old, editPanel.value.macros)
}
function unlinkMacro(id) {
delete editPanel.value.macros[id]
buttonAccordion.value.toggleAccordion(false)
panelMacros.changed = CheckMacroListChange(panelMacros.old, editPanel.value.macros)
}
function savePanelChanges() {
const panelData = {
dir: editPanel.value.dir,
name: editPanel.value.name,
description: editPanel.value.description,
aspectRatio: editPanel.value.aspectRatio,
macros: editPanel.value.macros,
}
axios.post(appUrl() + '/panel/save/json', panelData)
}
</script>
<style>
@reference "@/assets/main.css";
[mcrm__button] {
@apply cursor-pointer;
}
#panel-edit {
@apply grid
grid-cols-[1fr_30ch]
size-full
overflow-hidden;
.panel-preview {
@apply border-r
border-slate-700;
.panel-preview__content {
@apply relative
grid
justify-center
size-full
p-8;
#panel-html__body {
@apply size-full
max-w-full max-h-full;
}
}
}
.panel-settings {
@apply grid
grid-rows-[auto_auto_1fr]
bg-black/30;
}
}
</style>

View file

@ -0,0 +1,103 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="panel-view">
<div class="panel-preview__content" ref="panelView" v-html="viewPanel.html"></div>
</div>
</template>
<script setup>
import { RunMacro } from '@/services/MacroService'
import {
PanelButtonListeners,
PanelDialogListeners,
RemovePanelScripts,
RemovePanelStyle,
SetPanelStyle,
StripPanelHTML,
} from '@/services/PanelService'
import { usePanelStore } from '@/stores/panel'
import { onMounted, onUnmounted, ref } from 'vue'
const panel = usePanelStore()
const props = defineProps({
dirname: String,
})
const panelView = ref(null)
const viewPanel = ref({})
onMounted(async () => {
const currentPanel = await panel.get(props.dirname)
viewPanel.value = currentPanel
viewPanel.value.html = StripPanelHTML(viewPanel.value.html, viewPanel.value.aspectRatio)
SetPanelStyle(viewPanel.value.style)
setTimeout(() => {
viewPanelListeners()
if (typeof window.onPanelLoaded === 'function') {
window.onPanelLoaded()
}
}, 50)
})
onUnmounted(() => {
RemovePanelStyle()
RemovePanelScripts()
})
const viewPanelListeners = () => {
const callback = (button) => {
RunMacro(viewPanel.value.macros[button.id])
}
PanelButtonListeners(panelView.value, callback)
PanelDialogListeners(panelView.value)
}
</script>
<style scoped>
@reference "@/assets/main.css";
#panel-view {
@apply fixed
inset-0
size-full
bg-black;
.panel-preview__content {
@apply relative
grid
justify-center
size-full;
#panel-html__body {
@apply size-full
max-w-full max-h-full;
}
}
}
</style>

View file

@ -0,0 +1,183 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="panels-overview">
<AlertComp v-if="Object.keys(panels.list).length == 0" variant="info">
No panels found
</AlertComp>
<div class="panel-list">
<div class="panel-item mcrm-block block__dark" v-for="(panel, i) in panels.list" :key="i">
<div class="panel-item__content" @click="panelItemClick(panel.dir)">
<div class="thumb">
<img v-if="panel.thumb" :src="`data:image/jpeg;base64,${panel.thumb}`" alt="" />
<IconLayoutGrid v-else />
</div>
<h4>{{ panel.name }}</h4>
<div class="description" v-if="isLocal()">
<div class="content">
<strong class="block mb-1 text-slate-400">{{ panel.name }}</strong>
<hr class="mb-2 border-slate-600" />
<p v-if="panel.description != 'null'" class="text-slate-200">
{{ panel.description }}
</p>
</div>
<footer>
<ButtonComp variant="subtle" size="sm" :href="`/panel/view/${panel.dir}`">
<IconEye /> Preview
</ButtonComp>
<ButtonComp variant="primary" size="sm" :href="`/panel/edit/${panel.dir}`">
<IconPencil /> Edit
</ButtonComp>
</footer>
</div>
</div>
<template v-if="!isLocal()"> </template>
</div>
</div>
</div>
</template>
<script setup>
import { usePanelStore } from '@/stores/panel'
import { onMounted, reactive } from 'vue'
import AlertComp from '../base/AlertComp.vue'
import { IconEye, IconLayoutGrid, IconPencil } from '@tabler/icons-vue'
import ButtonComp from '../base/ButtonComp.vue'
import { isLocal } from '@/services/ApiService'
import { useRouter } from 'vue-router'
const panel = usePanelStore()
const panels = reactive({
list: {},
})
const router = useRouter()
onMounted(async () => {
const panelList = await panel.getList()
panels.list = panelList
})
function panelItemClick(dir) {
if (isLocal()) return
router.push(`/panel/view/${dir}`)
}
</script>
<style scoped>
@reference "@/assets/main.css";
.panel-list {
@apply grid
grid-cols-2
md:grid-cols-4
lg:grid-cols-6
gap-4
w-full h-fit;
}
.panel-item {
@apply p-px
overflow-hidden;
.thumb {
@apply flex
justify-center
items-center
w-full
aspect-[4/3];
img {
@apply size-full
object-cover;
}
&:not(:has(img)) {
@apply bg-sky-950;
}
svg {
@apply size-12;
}
}
h4 {
@apply px-4 py-2
h-12
truncate;
}
&:hover .description {
@apply opacity-100;
}
.description {
@apply absolute
inset-0
size-full
pt-2
pr-1
pb-13
bg-slate-900/60
backdrop-blur-md
text-slate-100
opacity-0
transition-opacity
cursor-default
z-10;
.content {
@apply h-full
p-4
pt-2
overflow-y-auto;
}
footer {
@apply absolute
bottom-0 left-0
w-full
h-12
grid
grid-cols-2
bg-slate-900
border-t
border-slate-600;
.btn {
@apply size-full
rounded-none
justify-center
border-0;
&:last-child {
@apply border-l
border-slate-600;
}
}
}
}
}
</style>

View file

@ -1,15 +1,37 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// import './assets/jemx.scss'
import "@/assets/main.css";
import '@/assets/main.css'
import '@/assets/img/Macrame-Logo-gradient.svg'
import { createApp } from "vue";
import { createPinia } from "pinia";
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from "@/App.vue";
import router from "@/router";
import App from '@/App.vue'
import router from '@/router'
const app = createApp(App);
const app = createApp(App)
app.use(createPinia());
app.use(router);
app.use(createPinia())
app.use(router)
app.mount("#app");
app.mount('#app')

View file

@ -1,34 +1,70 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import DashboardView from '../views/DashboardView.vue'
import { checkAuth, isLocal } from '@/services/ApiService'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
name: 'dashboard',
component: DashboardView,
},
{
path: '/panels',
name: 'panels',
component: () => import('../views/PanelsView.vue'),
meta: { requiresAuth: true },
},
{
path: '/panel/edit/:dirname',
name: 'panel-edit',
component: () => import('../views/PanelsView.vue'),
meta: { requiresAuth: true },
},
{
path: '/panel/view/:dirname',
name: 'panel-view',
component: () => import('../views/PanelsView.vue'),
meta: { requiresAuth: true },
},
{
path: '/macros',
name: 'macros',
component: () => import('../views/MacrosView.vue'),
meta: { localOnly: true },
},
{
path: '/devices',
name: 'devices',
component: () => import('../views/DevicesView.vue'),
},
{
path: '/settings',
name: 'settings',
component: () => import('../views/SettingsView.vue'),
},
// {
// path: '/settings',
// name: 'settings',
// component: () => import('../views/SettingsView.vue'),
// },
// {
// path: '/about',
// name: 'about',
@ -40,4 +76,12 @@ const router = createRouter({
],
})
router.beforeEach(async (to, from, next) => {
const auth = await checkAuth()
if (to.meta.requiresAuth && !auth && !isLocal()) next('/devices')
else if (to.meta.localOnly && !isLocal()) next('/')
else next()
})
export default router

View file

@ -1,10 +1,34 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useDeviceStore } from '@/stores/device'
import CryptoJS from 'crypto-js'
export const appUrl = () => {
return window.location.port !== 6970 ? `http://${window.location.hostname}:6970` : ''
const port = window.location.port == 5173 ? window.__CONFIG__.MCRM__PORT : window.location.port
return `http://${window.location.hostname}:${port}`
}
export const isLocal = () => {
export const isLocal = () => {
return window.location.hostname === '127.0.0.1' || window.location.hostname === 'localhost'
}
@ -17,3 +41,15 @@ export const encrypt = (data, key = false) => {
return false
}
}
export const checkAuth = async () => {
const device = useDeviceStore()
const handshake = await device.remoteHandshake()
if (handshake === true) return true
if (device.key()) return true
return false
}

View file

@ -1,10 +1,32 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useDeviceStore } from '@/stores/device'
import { AES, enc, pad } from 'crypto-js'
import { isLocal } from './ApiService'
export const encryptAES = (key, str) => {
key = keyPad(key)
let iv = enc.Utf8.parse(import.meta.env.VITE_MCRM__IV)
let iv = enc.Utf8.parse(window.__CONFIG__.MCRM__IV)
let encrypted = AES.encrypt(str, key, {
iv: iv,
padding: pad.Pkcs7,
@ -15,7 +37,7 @@ export const encryptAES = (key, str) => {
export const decryptAES = (key, str) => {
key = keyPad(key)
let iv = enc.Utf8.parse(import.meta.env.VITE_MCRM__IV)
let iv = enc.Utf8.parse(window.__CONFIG__.MCRM__IV)
let encrypted = AES.decrypt(str.toString(), key, {
iv: iv,
padding: pad.Pkcs7,
@ -23,7 +45,11 @@ export const decryptAES = (key, str) => {
return encrypted.toString(enc.Utf8)
}
export const AuthCall = (data) => {
export const AuthCall = (data = false) => {
if (isLocal()) return data
if (!data) data = {empty: true}
const device = useDeviceStore()
return {
@ -36,7 +62,7 @@ function keyPad(key) {
let returnKey = key
if (key.length == 4) {
returnKey = key + import.meta.env.VITE_MCRM__SALT
returnKey = key + window.__CONFIG__.MCRM__SALT
}
return enc.Utf8.parse(returnKey)

View file

@ -1,3 +1,24 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const keyMap = {
// Modifier keys
Control: 'Ctrl',
@ -125,3 +146,47 @@ export const invalidMacro = (steps) => {
return { down: downKeys, up: upKeys }
}
export const translateJSON = (json) => {
const steps = []
json.forEach((step) => {
if (step.type === 'delay') steps.push(step)
if (step.type === 'key') steps.push(codeToStep(step.code, step.direction))
})
return steps
}
export const codeToStep = (code, direction) => {
let key = ''
let location = 0
let codeStr = code
if (code.includes('Left')) {
key = code.replace('Left', '')
location = 1
}
if (code.includes('Right')) {
key = code.replace('Right', '')
location = 2
}
if (code.includes('Numpad')) {
key = code.replace('Numpad', '')
location = 3
}
if (code.includes('Media')) codeStr = ''
if (key === '') key = code
const stepObj = {
type: 'key',
code: codeStr,
key: key,
location: location,
direction: direction,
}
return { ...stepObj, keyObj: filterKey(stepObj) }
}

View file

@ -0,0 +1,47 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import axios from 'axios'
import { appUrl, isLocal } from './ApiService'
import { AuthCall } from './EncryptService'
export const GetMacroList = async (count = false) => {
const request = await axios.post(appUrl() + '/macro/list')
if (!request.data) return 0
if (!count) return sortMacroList(request.data)
else return request.data.length
}
const sortMacroList = (list) => {
return [...list].sort((a, b) => a.name.localeCompare(b.name))
}
export const RunMacro = async (macro) => {
const data = isLocal() ? { macro: macro } : AuthCall({ macro: macro })
const request = await axios.post(appUrl() + '/macro/play', data)
return request.data
}
export const CheckMacroListChange = (oldList, newList) => {
return oldList !== JSON.stringify(newList)
}

View file

@ -0,0 +1,120 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const SetPanelStyle = (styleStr) => {
const styleEl = document.createElement('style')
styleEl.setAttribute('custom_panel_style', true)
styleEl.innerHTML = styleStr
document.head.appendChild(styleEl)
}
export const RemovePanelStyle = () => {
const styleEl = document.querySelector('style[custom_panel_style]')
if (styleEl) {
styleEl.remove()
}
}
export const StripPanelHTML = (html, aspectRatio) => {
const parser = new DOMParser()
const doc = parser.parseFromString(html, 'text/html')
let scripts = []
if (doc.querySelectorAll('script').length > 0) {
const stripped = StripPanelScripts(doc)
doc.body = stripped.body
scripts = stripped.scripts
}
const body = doc.body
const bodyContents = body.innerHTML
const panelBody = document.createElement('div')
panelBody.id = 'panel-html__body'
panelBody.style = `aspect-ratio: ${aspectRatio}`
panelBody.innerHTML = bodyContents
if (scripts.length > 0) {
SetPanelScripts(scripts)
}
return panelBody.outerHTML
}
export const StripPanelScripts = (doc) => {
const scriptEls = doc.querySelectorAll('script')
const scripts = []
scriptEls.forEach((script) => {
if (script.getAttribute('no-compile') != '') scripts.push(script.innerHTML)
script.remove()
})
return { body: doc.body, scripts }
}
export const SetPanelScripts = (scripts) => {
scripts.forEach((script) => {
const scriptEl = document.createElement('script')
scriptEl.setAttribute('custom_panel_script', true)
scriptEl.innerHTML = script
document.body.appendChild(scriptEl)
})
}
export const RemovePanelScripts = () => {
const scripts = document.querySelectorAll('script[custom_panel_script]')
scripts.forEach((script) => {
script.remove()
})
}
export const PanelButtonListeners = (panelEl, callback) => {
panelEl.querySelectorAll('[mcrm__button]').forEach((button) => {
button.addEventListener('click', () => {
callback(button)
})
})
}
export const PanelDialogListeners = (panelEl) => {
panelEl.querySelectorAll('[mcrm__dialog-trigger]').forEach((dialogTrigger) => {
const dialogEl = document.querySelector(dialogTrigger.getAttribute('dialog-trigger'))
if (dialogEl) {
dialogTrigger.addEventListener('click', () => {
dialogEl.show()
})
}
})
document.querySelectorAll('dialog, dialog .dialog__close').forEach((dialogClose) => {
dialogClose.addEventListener('click', (e) => {
if (
e.target.classList.contains('dialog__close') ||
e.target.closest('.dialog__close') ||
e.target.tagName == 'DIALOG'
) {
dialogClose.closest('dialog').close()
}
})
})
}

View file

@ -1,3 +1,24 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

View file

@ -1,3 +1,24 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { ref } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
@ -50,11 +71,21 @@ export const useDeviceStore = defineStore('device', () => {
localStorage.removeItem('deviceKey')
}
const serverGetIP = async () => {
const request = await axios.post(appUrl() + '/device/server/ip')
return `http://${request.data}:${window.__CONFIG__.MCRM__PORT}`
}
// Server application
const serverGetRemotes = async (remoteUuid) => {
axios.post(appUrl() + '/device/list', { uuid: remoteUuid }).then((data) => {
if (data.data.devices) remote.value = data.data.devices
})
const serverGetRemotes = async (count = false) => {
const request = await axios.post(appUrl() + '/device/list')
if (!request.data.devices) return false
remote.value = request.data.devices
if (!count) return remote.value
else return Object.keys(remote.value).length
}
const serverStartLink = async (deviceUuid) => {
@ -78,8 +109,6 @@ export const useDeviceStore = defineStore('device', () => {
return request
}
const remotePingLink = async (cb) => {
// const linkRequest = await axios.post(appUrl() + '/device/link/ping', { uuid: deviceUuid })
// if (linkRequest.data)
const pingInterval = setInterval(() => {
axios.post(appUrl() + '/device/link/ping', { uuid: uuid() }).then((data) => {
if (data.data) {
@ -90,12 +119,17 @@ export const useDeviceStore = defineStore('device', () => {
}, 1000)
}
const remoteHandshake = async (key) => {
const remoteHandshake = async (keyStr = false) => {
if (!keyStr) keyStr = key()
if (!keyStr) return false
const handshake = await axios.post(appUrl() + '/device/handshake', {
uuid: uuid(),
shake: encryptAES(key, getDateStr()),
shake: encryptAES(keyStr, getDateStr()),
})
console.log(handshake)
if (!handshake.data) removeDeviceKey()
return handshake.data
}
@ -108,6 +142,7 @@ export const useDeviceStore = defineStore('device', () => {
key,
setDeviceKey,
removeDeviceKey,
serverGetIP,
serverGetRemotes,
serverStartLink,
remoteCheckServerAccess,

View file

@ -1,7 +1,28 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { filterKey, isRepeat, invalidMacro } from '../services/MacroRecordService'
import { filterKey, isRepeat, invalidMacro, translateJSON } from '../services/MacroRecordService'
import axios from 'axios'
import { appUrl } from '@/services/ApiService'
@ -48,6 +69,8 @@ export const useMacroRecorderStore = defineStore('macrorecorder', () => {
// Setters - Actions
const recordStep = (e, direction = false, key = false) => {
if ((e.ctrlKey, e.shiftKey, e.altKey, e.metaKey)) e.preventDefault()
const lastStep = steps.value[steps.value.length - 1]
let stepVal = {}
@ -130,7 +153,6 @@ export const useMacroRecorderStore = defineStore('macrorecorder', () => {
const changeName = (name) => {
macroName.value = name
console.log(macroName.value)
}
const changeDelay = (fixed) => {
@ -164,29 +186,58 @@ export const useMacroRecorderStore = defineStore('macrorecorder', () => {
state.value.editDelay = false
}
const reset = () => {
const resetMacro = () => {
state.value.record = false
delay.value.start = 0
macroName.value = ''
steps.value = []
if (state.value.edit) resetEdit()
}
const save = () => {
const checkMacro = async () => {
const resp = await axios.post(appUrl() + '/macro/check', {
macro: macroName.value,
})
return resp.data
}
const saveMacro = async () => {
state.value.validationErrors = invalidMacro(steps.value)
if (state.value.validationErrors) return false
axios
.post(appUrl() + '/macro/record', { name: macroName.value, steps: steps.value })
.then((data) => {
console.log(data)
})
return true
const resp = await axios.post(appUrl() + '/macro/record', {
name: macroName.value,
steps: steps.value,
})
return resp.status == 200
}
const deleteMacro = async (macroFilename) => {
const resp = await axios.post(appUrl() + '/macro/delete', {
macro: macroFilename,
})
if (resp.status == 200) return resp.data
else return false
}
const openMacro = async (macroFileName, name) => {
const openResp = await axios.post(appUrl() + '/macro/open', {
macro: macroFileName,
})
if (openResp.data) steps.value = translateJSON(openResp.data)
macroName.value = name
}
return {
state,
macroName,
steps,
delay,
getEditKey,
@ -200,7 +251,10 @@ export const useMacroRecorderStore = defineStore('macrorecorder', () => {
changeDelay,
toggleEdit,
resetEdit,
reset,
save,
resetMacro,
checkMacro,
saveMacro,
deleteMacro,
openMacro,
}
})

82
fe/src/stores/panel.js Normal file
View file

@ -0,0 +1,82 @@
/*
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { appUrl } from '@/services/ApiService'
import { AuthCall } from '@/services/EncryptService'
import axios from 'axios'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const usePanelStore = defineStore('panel', () => {
const current = ref({
dir: false,
name: false,
description: false,
aspectRatio: false,
macros: false,
thumb: false,
html: false,
style: false,
})
const list = ref([])
const get = async (dir) => {
const data = AuthCall({ dir: dir })
const resp = await axios.post(appUrl() + '/panel/get', data)
if (!resp.data && !current.value.html) return false
current.value.name = resp.data.name
current.value.description = resp.data.description
current.value.aspectRatio = resp.data.aspectRatio
current.value.macros = resp.data.macros
current.value.thumb = resp.data.thumb
current.value.html = resp.data.html
current.value.style = resp.data.style
return current.value
}
const getList = async (count = false) => {
if (list.value.length > 0 && !count) return list.value
else if (list.value.length > 0 && count) return list.value.length
const data = AuthCall()
const resp = await axios.post(appUrl() + '/panel/list', data)
list.value = resp.data
if (!resp.data && !count) return false
else if (!resp.data && count) return 0
if (!count) return list.value
else return list.value.length
}
return {
current,
list,
get,
getList,
}
})

View file

@ -0,0 +1,105 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="dashboard" class="panel">
<div class="panel__title">
<h1>Dashboard</h1>
<div>
<em v-if="isLocal()">This is the server dashboard.</em>
<em v-else>This is the remote dashboard.</em>
</div>
</div>
<div class="panel__content !h-fit !gap-y-16">
<ServerView v-if="isLocal()" />
<RemoteView v-else />
<div class="grid gap-2 text-slate-300">
<h3>About Macrame</h3>
<p>
Macrame is an open-source application designed to turn any device into a customizable
button panel. Whether you're optimizing your workflow or enhancing your gaming experience,
Macrame makes it simple to create and link macros to your button panels.
</p>
<p>
For more information, including details on licensing, visit
<a href="https://macrame.github.io" target="_blank">https://macrame.github.io</a>
</p>
</div>
</div>
</div>
</template>
<script setup>
import { isLocal } from '@/services/ApiService'
import ServerView from '@/components/dashboard/ServerView.vue'
import RemoteView from '@/components/dashboard/RemoteView.vue'
</script>
<style>
@reference "@/assets/main.css";
.dashboard-block {
@apply md:!row-start-1
grid
justify-items-center
gap-4;
&#devices .icon__container,
&#server .icon__container {
@apply bg-sky-300/30
text-sky-400
border-sky-300/60;
}
&#macros .icon__container {
@apply bg-amber-300/30
text-amber-400
border-amber-300/60;
}
&#panels .icon__container {
@apply bg-rose-300/30
text-rose-400
border-rose-300/60;
}
.icon__container {
@apply flex
justify-center
items-center
size-16
aspect-square
rounded-full
border;
svg {
@apply size-8;
}
}
p {
@apply opacity-50
w-42
text-center;
}
}
</style>

View file

@ -1,9 +1,30 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="devices-view" class="panel">
<h1 class="panel__title">
Devices <span class="text-sm">{{ isLocal() ? 'remote' : 'servers' }}</span>
{{ isLocal() ? 'Remote devices' : 'Server' }}
</h1>
<div class="panel__content grid gap-8">
<div class="grid gap-8 pr-2 panel__content">
<ServerView v-if="isLocal()" />
<RemoteView v-else />
</div>

View file

@ -1,7 +1,28 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div id="macros" class="panel">
<h1 class="panel__title">Macros</h1>
<div class="panel__content !p-0">
<div class="panel__content !p-0 !overflow-hidden">
<div class="macro-panel__content">
<MacroOverview />
<MacroRecorder />
@ -13,19 +34,6 @@
<script setup>
import MacroOverview from '@/components/macros/MacroOverview.vue'
import MacroRecorder from '../components/macros/MacroRecorder.vue'
import { onMounted, ref } from 'vue'
const recordMacro = ref(false)
const macroInput = ref(null)
onMounted(() => {
// macroInput.value.focus()
})
const keyDown = (e) => {
console.log(e)
}
</script>
<style scoped>

View file

@ -1,7 +1,82 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div></div>
<div id="panels" class="panel">
<h1 class="flex items-end justify-between !w-full panel__title">
<div>Panels</div>
<ButtonComp
v-if="panel.function != 'overview'"
variant="subtle"
size="sm"
@click="router.push('/panels')"
>
<IconArrowLeft /> Overview
</ButtonComp>
</h1>
<div :class="`panel__content !p-0 !pt-4 ${panel.function == 'overview' ?? '!pr-4'}`">
<PanelsOverview v-if="panel.function == 'overview'" />
<PanelEdit v-if="panel.function == 'edit'" :dirname="panel.dirname" />
<PanelView v-if="panel.function == 'preview'" :dirname="panel.dirname" />
</div>
</div>
</template>
<script setup></script>
<script setup>
import ButtonComp from '@/components/base/ButtonComp.vue'
import PanelEdit from '@/components/panels/PanelEdit.vue'
import PanelView from '@/components/panels/PanelView.vue'
import PanelsOverview from '@/components/panels/PanelsOverview.vue'
import { isLocal } from '@/services/ApiService'
import { IconArrowLeft } from '@tabler/icons-vue'
import { onMounted, onUpdated, reactive } from 'vue'
import { useRoute, useRouter } from 'vue-router'
<style lang="scss" scoped></style>
const route = useRoute()
const router = useRouter()
const panel = reactive({
function: '',
dirname: '',
})
onMounted(() => {
setVarsByRoute()
})
onUpdated(() => {
setVarsByRoute()
})
const setVarsByRoute = () => {
if (route.name.includes('panel-')) {
panel.function = route.name == 'panel-edit' ? 'edit' : 'preview'
} else {
panel.function = 'overview'
}
panel.dirname = route.params.dirname
}
</script>
<style scoped>
@reference "@/assets/main.css";
</style>

View file

@ -1,3 +1,24 @@
<!--
Macrame is a program that enables the user to create keyboard macros and button panels.
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
be created with HTML and CSS.
Copyright (C) 2025 Jesse Malotaux
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div></div>
</template>

View file

@ -16,16 +16,16 @@ export default defineConfig({
},
plugins: [vue(), vueDevTools(), tailwindcss()],
envDir: '../',
assets: ['assets'],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
base: '/',
// publicDir: "../public",
build: {
outDir: '../public',
sourcemap: false,
minify: false,
sourcemap: true,
minify: true,
},
})