mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 07:19:26 +00:00
Merge branch 'development'
This commit is contained in:
commit
358e4fea17
106 changed files with 10173 additions and 1027 deletions
17
.air.toml
Normal file
17
.air.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Working directory
|
||||
root = "."
|
||||
|
||||
# The main Go file
|
||||
main = "main.go"
|
||||
|
||||
bin = "tmp/main.exe"
|
||||
|
||||
# Watching all Go files, excluding certain directories
|
||||
[build]
|
||||
cmd = "go build -o ./tmp/main.exe main.go"
|
||||
include_ext = ["go"]
|
||||
exclude_dir = ["fe", "panels", "builds"]
|
||||
|
||||
# Restart on file changes
|
||||
[log]
|
||||
time = true
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
|
|
@ -1 +1,14 @@
|
|||
.env
|
||||
config.js
|
||||
|
||||
devices/*
|
||||
tmp/
|
||||
log.txt
|
||||
|
||||
Macrame.exe
|
||||
|
||||
public
|
||||
macros/*
|
||||
builds
|
||||
node_modules
|
||||
ToDo.md
|
||||
47
add-gpl-header.sh
Normal file
47
add-gpl-header.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Directory to start searching from (default to current directory)
|
||||
DIR="${1:-.}"
|
||||
|
||||
# Define the GPLv3 header content
|
||||
GPL_HEADER="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/>."
|
||||
|
||||
# Loop through all files in the directory (and subdirectories), excluding node_modules
|
||||
find "$DIR" \( -iname \*.go -o \( -path "$DIR/fe/src/*.js" -o -path "$DIR/fe/src/*.vue" -o -path "$DIR/fe/src/*.css" \) \) ! -path "*/node_modules/*" | while read file
|
||||
do
|
||||
# Check if the file already has a GPL header
|
||||
if ! grep -q "Copyright (C) 2025 Jesse Malotaux" "$file"; then
|
||||
# Check if it's a Vue file and handle it carefully
|
||||
if [[ "$file" == *.vue ]]; then
|
||||
echo "Adding GPL header to: $file"
|
||||
# Prepend the GPL header to Vue files as raw text
|
||||
# Make sure we don't add comment marks that break Vue syntax
|
||||
echo -e "<!--\n$GPL_HEADER\n-->\n\n$(cat "$file")" > "$file"
|
||||
else
|
||||
echo "Adding GPL header to: $file"
|
||||
# Prepend the GPL header to other files (go, js, ts, etc.) as comments
|
||||
echo -e "/*\n$GPL_HEADER\n*/\n\n$(cat "$file")" > "$file"
|
||||
fi
|
||||
else
|
||||
echo "GPL header already present in: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "GPL header addition complete!"
|
||||
137
app/api.go
Normal file
137
app/api.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"macrame/app/helper"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ApiCORS(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
|
||||
|
||||
if strings.HasPrefix(r.Host, "192.168.") {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Accept-Language, Accept-Encoding")
|
||||
|
||||
return w, r
|
||||
}
|
||||
|
||||
func ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
root, err := filepath.Abs("public")
|
||||
if err != nil {
|
||||
MCRMLog("ApiGet Abs Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
var file string
|
||||
|
||||
if strings.Contains(r.URL.Path, "/config.js") {
|
||||
file = filepath.Join(root, "config.js")
|
||||
w.Header().Set("Content-Type", "text/javascript") // set content type header
|
||||
} else if r.URL.Path != "/" {
|
||||
file = filepath.Join(root, r.URL.Path)
|
||||
}
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(file)) // get content type
|
||||
|
||||
if contentType == "" {
|
||||
file = filepath.Join(root, "index.html")
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, file)
|
||||
}
|
||||
|
||||
func ApiPost(w http.ResponseWriter, r *http.Request) {
|
||||
access, data, err := helper.EndpointAccess(w, r)
|
||||
|
||||
if !access || err != nil {
|
||||
MCRMLog("ApiPost EndPointAccess Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if data != "" {
|
||||
ApiAuth(data, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/macro/check":
|
||||
CheckMacro(w, r)
|
||||
case "/macro/record":
|
||||
SaveMacro(w, r)
|
||||
case "/macro/list":
|
||||
ListMacros(w, r)
|
||||
case "/macro/open":
|
||||
OpenMacro(w, r)
|
||||
case "/macro/delete":
|
||||
DeleteMacro(w, r)
|
||||
case "/macro/play":
|
||||
PlayMacro("", w, r)
|
||||
case "/device/server/ip":
|
||||
ListServerIP(w)
|
||||
case "/device/list":
|
||||
DeviceList(w, r)
|
||||
case "/device/access/check":
|
||||
DeviceAccessCheck(w, r)
|
||||
case "/device/access/request":
|
||||
DeviceAccessRequest(w, r)
|
||||
case "/device/link/ping":
|
||||
PingLink(w, r)
|
||||
case "/device/link/start":
|
||||
StartLink(w, r)
|
||||
case "/device/link/poll":
|
||||
PollLink(w, r)
|
||||
case "/device/link/remove":
|
||||
RemoveLink("", w, r)
|
||||
case "/device/handshake":
|
||||
Handshake(w, r)
|
||||
case "/panel/list":
|
||||
PanelList(w, r)
|
||||
case "/panel/get":
|
||||
GetPanel("", w, r)
|
||||
case "/panel/save/json":
|
||||
SavePanelJSON(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func ApiAuth(data string, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/macro/play":
|
||||
PlayMacro(data, w, r)
|
||||
case "/device/link/remove":
|
||||
RemoveLink(data, w, r)
|
||||
case "/panel/list":
|
||||
MCRMLog("Authenticated Panellist")
|
||||
PanelList(w, r)
|
||||
case "/panel/get":
|
||||
GetPanel(data, w, r)
|
||||
}
|
||||
}
|
||||
329
app/device.go
Normal file
329
app/device.go
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ListServerIP(w http.ResponseWriter) {
|
||||
ip, err := GetServerIp()
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("GetServerIP err: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(ip)
|
||||
}
|
||||
|
||||
func GetServerIp() (string, error) {
|
||||
ifs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
MCRMLog(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, ifi := range ifs {
|
||||
// Skip interfaces that are down
|
||||
if ifi.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
// Skip loopback interfaces
|
||||
if ifi.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := ifi.Addrs()
|
||||
if err != nil {
|
||||
MCRMLog(err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip APIPA (169.254.x.x) addresses
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a good IP, return it
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No IP found")
|
||||
}
|
||||
|
||||
func DeviceList(w http.ResponseWriter, r *http.Request) {
|
||||
dir := "devices"
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
os.MkdirAll(dir, 0600)
|
||||
files = nil
|
||||
MCRMLog("DeviceList Error: ", err)
|
||||
}
|
||||
|
||||
devices := make(map[string]map[string]interface{})
|
||||
|
||||
for _, file := range files {
|
||||
filePath := dir + "/" + file.Name()
|
||||
ext := filepath.Ext(filePath)
|
||||
device := strings.TrimSuffix(file.Name(), ext)
|
||||
|
||||
if _, ok := devices[device]; !ok {
|
||||
devices[device] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if ext == ".json" {
|
||||
devices[device]["settings"] = readDeviceSettings(filePath)
|
||||
}
|
||||
if ext == ".key" {
|
||||
devices[device]["key"] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"devices": devices,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func readDeviceSettings(filepath string) (settings structs.Settings) {
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
MCRMLog("readDeviceSettings Error: ", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &settings)
|
||||
if err != nil {
|
||||
MCRMLog("readDeviceSettings JSON Error: ", err)
|
||||
}
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
func DeviceAccessCheck(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessCheck Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, errSett := os.Stat("devices/" + req.Uuid + ".json")
|
||||
_, errKey := os.Stat("devices/" + req.Uuid + ".key")
|
||||
|
||||
if (errSett == nil) && (errKey == nil) {
|
||||
json.NewEncoder(w).Encode("authorized")
|
||||
} else if (errSett == nil) && (errKey != nil) {
|
||||
MCRMLog("DeviceAccessCheck: UUID: ", req.Uuid, "; Access: Unauthorized")
|
||||
json.NewEncoder(w).Encode("unauthorized")
|
||||
} else {
|
||||
MCRMLog("DeviceAccessCheck: UUID: ", req.Uuid, "; Access: Unlinked")
|
||||
json.NewEncoder(w).Encode("unlinked")
|
||||
}
|
||||
}
|
||||
|
||||
func DeviceAccessRequest(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Request
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
deviceSettings := structs.Settings{Name: req.Name, Type: req.Type}
|
||||
|
||||
settingsJSON, err := json.Marshal(deviceSettings)
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessRequest JSON Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile("devices/"+req.Uuid+".json", settingsJSON, 0644)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessRequest Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode("unauthorized")
|
||||
}
|
||||
|
||||
func PingLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PingLink Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
key, keyErr := os.ReadFile("devices/" + req.Uuid + ".key")
|
||||
pin, pinErr := os.ReadFile("devices/" + req.Uuid + ".tmp")
|
||||
|
||||
encryptedKey, encErr := helper.EncryptAES(string(pin), string(key))
|
||||
|
||||
if keyErr == nil && pinErr == nil && encErr == nil {
|
||||
MCRMLog("PINGLINK FIXED")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(encryptedKey))
|
||||
return
|
||||
} else {
|
||||
MCRMLog("PingLink Error: keyErr:", keyErr, "; pinErr:", pinErr, "; encErr:", encErr)
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func StartLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("StartLink Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
pin := fmt.Sprintf("%04d", rand.Intn(10000))
|
||||
|
||||
deviceKey := helper.GenerateKey()
|
||||
|
||||
errKey := helper.SaveDeviceKey(req.Uuid, deviceKey)
|
||||
savedPin, errPin := helper.TempPinFile(req.Uuid, pin)
|
||||
|
||||
if errKey == nil && errPin == nil && savedPin {
|
||||
json.NewEncoder(w).Encode(pin)
|
||||
} else {
|
||||
MCRMLog("StartLink Error: errKey:", errKey, "; errPin:", errPin)
|
||||
}
|
||||
}
|
||||
|
||||
func PollLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
if helper.CheckPinFile(req.Uuid) {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func RemoveLink(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.Check{}
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("RemoveLink ParseRequest Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove("devices/" + req.Uuid + ".key")
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("RemoveLink Remove Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(true)
|
||||
}
|
||||
|
||||
func Handshake(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Handshake
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
deviceKey, err := helper.GetKeyByUuid(req.Uuid)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("Handshake GetKeyByUuid Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
decryptShake, _ := helper.DecryptAES(deviceKey, req.Shake)
|
||||
|
||||
helper.RemovePinFile(req.Uuid)
|
||||
|
||||
if decryptShake == getDateStr() {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
} else {
|
||||
os.Remove("devices/" + req.Uuid + ".key")
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func getDateStr() string {
|
||||
date := time.Now()
|
||||
year, month, day := date.Date()
|
||||
formattedDate := fmt.Sprintf("%04d%02d%02d", year, month, day)
|
||||
return formattedDate
|
||||
}
|
||||
123
app/helper/api-helper.go
Normal file
123
app/helper/api-helper.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"macrame/app/structs"
|
||||
. "macrame/app/structs"
|
||||
)
|
||||
|
||||
func EndpointAccess(w http.ResponseWriter, r *http.Request) (bool, string, error) {
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
|
||||
if err != nil {
|
||||
return false, "", errors.New(r.URL.Path + ": SplitHostPort error: " + err.Error())
|
||||
}
|
||||
|
||||
if (isLocal(ip) && isEndpointAllowed("Local", r.URL.Path)) ||
|
||||
(isLanRemote(ip) && isEndpointAllowed("Remote", r.URL.Path)) {
|
||||
return true, "", nil
|
||||
} else if isLanRemote(ip) && isEndpointAllowed("Auth", r.URL.Path) {
|
||||
data, err := decryptAuth(r)
|
||||
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
return data != "", data, nil
|
||||
}
|
||||
|
||||
return false, "", errors.New(r.URL.Path + ": not authorized or accessible")
|
||||
}
|
||||
|
||||
func isLocal(ip string) bool {
|
||||
return ip == "127.0.0.1" || ip == "::1"
|
||||
}
|
||||
|
||||
func isLanRemote(ip string) bool {
|
||||
return strings.HasPrefix(ip, "192.168.")
|
||||
}
|
||||
|
||||
func isEndpointAllowed(source string, endpoint string) bool {
|
||||
var endpoints, err = getAllowedEndpoints(source)
|
||||
if err != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if (endpoints != nil) && (len(endpoints) > 0) {
|
||||
for _, e := range endpoints {
|
||||
if e == endpoint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getAllowedEndpoints(source string) (endpoints []string, err string) {
|
||||
if source == "Local" {
|
||||
return Endpoints.Local, ""
|
||||
}
|
||||
if source == "Remote" {
|
||||
return Endpoints.Remote, ""
|
||||
}
|
||||
if source == "Auth" {
|
||||
return Endpoints.Auth, ""
|
||||
}
|
||||
|
||||
return []string{}, "No allowed endpoints"
|
||||
}
|
||||
|
||||
func decryptAuth(r *http.Request) (string, error) {
|
||||
var req structs.Authcall
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil || req.Uuid == "" || req.Data == "" {
|
||||
return "", errors.New("DecryptAuth Error: " + err.Error() + "; UUID: " + req.Uuid + "; Data: " + req.Data)
|
||||
}
|
||||
|
||||
deviceKey, errKey := GetKeyByUuid(req.Uuid)
|
||||
decryptData, errDec := DecryptAES(deviceKey, req.Data)
|
||||
|
||||
if errKey != nil && errDec != nil || decryptData == "" {
|
||||
return "", errors.New("DecryptAuth Error: " + errKey.Error() + "; " + errDec.Error() + "; UUID: " + req.Uuid + "; Data: " + req.Data)
|
||||
}
|
||||
|
||||
return decryptData, nil
|
||||
}
|
||||
|
||||
func ParseRequest(req interface{}, data string, r *http.Request) (d interface{}, err error) {
|
||||
if data != "" {
|
||||
dataBytes := []byte(data)
|
||||
return req, json.Unmarshal(dataBytes, &req)
|
||||
} else {
|
||||
return req, json.NewDecoder(r.Body).Decode(&req)
|
||||
}
|
||||
}
|
||||
44
app/helper/browser-helper.go
Normal file
44
app/helper/browser-helper.go
Normal 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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
var args []string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
args = []string{"open"}
|
||||
case "windows":
|
||||
args = []string{"cmd", "/c", "start"}
|
||||
default:
|
||||
args = []string{"xdg-open"}
|
||||
}
|
||||
|
||||
cmd := exec.Command(args[0], append(args[1:], url)...)
|
||||
|
||||
return cmd.Start() == nil
|
||||
}
|
||||
66
app/helper/device-helper.go
Normal file
66
app/helper/device-helper.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TempPinFile(Uuid string, pin string) (bool, error) {
|
||||
err := os.WriteFile("devices/"+Uuid+".tmp", []byte(pin), 0644)
|
||||
if err != nil {
|
||||
return false, errors.New("TempPinFile Error: " + err.Error())
|
||||
}
|
||||
|
||||
time.AfterFunc(1*time.Minute, func() {
|
||||
os.Remove("devices/" + Uuid + ".tmp")
|
||||
})
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func RemovePinFile(Uuid string) error { return os.Remove("devices/" + Uuid + ".tmp") }
|
||||
|
||||
func CheckPinFile(Uuid string) bool {
|
||||
_, err := os.Stat("devices/" + Uuid + ".tmp")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func SaveDeviceKey(Uuid string, key string) error {
|
||||
err := os.WriteFile("devices/"+Uuid+".key", []byte(key), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetKeyByUuid(Uuid string) (string, error) {
|
||||
data, err := os.ReadFile("devices/" + Uuid + ".key")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
135
app/helper/encrypt-helper.go
Normal file
135
app/helper/encrypt-helper.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
mathRand "math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func EncryptAES(key string, plaintext string) (string, error) {
|
||||
origData := []byte(plaintext)
|
||||
|
||||
// Create AES cipher
|
||||
block, err := aes.NewCipher(keyToBytes(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
|
||||
origData = PKCS5Padding(origData, blockSize)
|
||||
|
||||
iv := []byte(EnvGet("MCRM__IV"))
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv)
|
||||
|
||||
crypted := make([]byte, len(origData))
|
||||
blockMode.CryptBlocks(crypted, origData)
|
||||
|
||||
cryptedString := base64.StdEncoding.EncodeToString(crypted)
|
||||
|
||||
return cryptedString, nil
|
||||
}
|
||||
|
||||
func DecryptAES(key string, cryptedText string) (string, error) {
|
||||
crypted, err := base64.StdEncoding.DecodeString(cryptedText)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(keyToBytes(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
iv := []byte(EnvGet("MCRM__IV"))
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||
|
||||
origData := make([]byte, len(crypted))
|
||||
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
origData, err = PKCS5UnPadding(origData)
|
||||
|
||||
if err != nil || len(origData) <= 3 {
|
||||
return "", errors.New("invalid key")
|
||||
}
|
||||
|
||||
origDataString := string(origData)
|
||||
|
||||
return origDataString, nil
|
||||
}
|
||||
|
||||
func GenerateRandomString(length int) string {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func GenerateRandomIntegerString(length int) string {
|
||||
var sb strings.Builder
|
||||
for i := 0; i < length; i++ {
|
||||
sb.WriteByte('0' + byte(mathRand.Intn(10)))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func GenerateKey() string {
|
||||
return strings.Replace(GenerateRandomString(24), "=", "", -1)
|
||||
}
|
||||
|
||||
func keyToBytes(key string) []byte {
|
||||
// Convert key to bytes
|
||||
keyBytes := []byte(key)
|
||||
|
||||
// If key is 4 characters, append salt
|
||||
if len(key) == 4 {
|
||||
keyBytes = []byte(key + EnvGet("MCRM__SALT"))
|
||||
}
|
||||
|
||||
return keyBytes
|
||||
}
|
||||
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS5UnPadding(origData []byte) ([]byte, error) {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
|
||||
if (unpadding >= length) || (unpadding == 0) {
|
||||
return nil, errors.New("unpadding error")
|
||||
}
|
||||
|
||||
return origData[:(length - unpadding)], nil
|
||||
}
|
||||
128
app/helper/env-helper.go
Normal file
128
app/helper/env-helper.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var configPath = "public/config.js"
|
||||
|
||||
func EnvGet(key string) string {
|
||||
|
||||
if !configFileExists() {
|
||||
CreateConfigFile(configPath)
|
||||
CheckFeDevDir()
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.Println("Error reading config.js:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
raw := strings.TrimSpace(string(data))
|
||||
raw = strings.TrimPrefix(raw, "window.__CONFIG__ = ")
|
||||
raw = strings.TrimSuffix(raw, ";")
|
||||
|
||||
var config map[string]string
|
||||
if err := json.Unmarshal([]byte(raw), &config); err != nil {
|
||||
log.Println("Error parsing config.js:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return config[key]
|
||||
}
|
||||
|
||||
func configFileExists() bool {
|
||||
_, err := os.Stat(configPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CheckFeDevDir() {
|
||||
log.Println("Checking FE dev directory...")
|
||||
_, err := os.Stat("fe")
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error checking FE dev directory:", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyConfigToFe()
|
||||
}
|
||||
|
||||
func copyConfigToFe() {
|
||||
data, err := os.ReadFile(configPath)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error reading config.js:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile("fe/config.js", data, 0644); err != nil {
|
||||
log.Println("Error writing config.js:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateConfigFile(filename string) {
|
||||
port, _ := findOpenPort()
|
||||
saltKey := GenerateKey()
|
||||
salt := saltKey[:28]
|
||||
iv := GenerateRandomIntegerString(16)
|
||||
|
||||
config := map[string]string{
|
||||
"MCRM__PORT": port,
|
||||
"MCRM__SALT": salt,
|
||||
"MCRM__IV": iv,
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
log.Println("Error creating config JSON:", err)
|
||||
return
|
||||
}
|
||||
jsData := "window.__CONFIG__ = " + string(jsonData) + ";"
|
||||
|
||||
log.Println("Created JS config:", jsData)
|
||||
|
||||
if err := os.WriteFile(filename, []byte(jsData), 0644); err != nil {
|
||||
log.Println("Error writing config.js:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func findOpenPort() (string, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer l.Close()
|
||||
return strconv.Itoa(l.Addr().(*net.TCPAddr).Port), nil
|
||||
}
|
||||
114
app/helper/macro-helper.go
Normal file
114
app/helper/macro-helper.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-vgo/robotgo"
|
||||
)
|
||||
|
||||
func FormatMacroFileName(s string) string {
|
||||
// Remove invalid characters
|
||||
re := regexp.MustCompile(`[\/\?\*\>\<\:\"\|
|
||||
]`)
|
||||
s = re.ReplaceAllString(s, "")
|
||||
|
||||
// Replace spaces with underscores
|
||||
s = strings.ReplaceAll(s, " ", "_")
|
||||
|
||||
// Remove special characters
|
||||
re = regexp.MustCompile(`[!@#$%^&\(\)\[\]\{\}\~]`)
|
||||
s = re.ReplaceAllString(s, "")
|
||||
|
||||
// Truncate the string
|
||||
if len(s) > 255 {
|
||||
s = s[:255]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func ReadMacroFile(filename string) (steps []map[string]interface{}, err error) {
|
||||
content, err := os.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, &steps)
|
||||
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func RunMacroSteps(steps []map[string]interface{}) error {
|
||||
for _, step := range steps {
|
||||
switch step["type"] {
|
||||
case "key":
|
||||
keyCode := step["code"].(string)
|
||||
|
||||
if strings.Contains(keyCode, "|") {
|
||||
keyCode = handleToggleCode(keyCode, step["direction"].(string))
|
||||
}
|
||||
|
||||
robotgo.KeyToggle(keyCode, step["direction"].(string))
|
||||
case "delay":
|
||||
|
||||
time.Sleep(time.Duration(step["value"].(float64)) * time.Millisecond)
|
||||
|
||||
default:
|
||||
return errors.New("RunMacroSteps Unknown step type: %v" + fmt.Sprint(step["type"]))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var toggleCodes = map[string]bool{}
|
||||
|
||||
func handleToggleCode(keyCode string, direction string) string {
|
||||
splitCodes := strings.Split(keyCode, "|")
|
||||
|
||||
if direction == "down" {
|
||||
if _, ok := toggleCodes[splitCodes[0]]; !ok {
|
||||
toggleCodes[splitCodes[0]] = true
|
||||
return splitCodes[0]
|
||||
}
|
||||
return splitCodes[1]
|
||||
}
|
||||
|
||||
if direction == "up" {
|
||||
if toggleCodes[splitCodes[0]] {
|
||||
toggleCodes[splitCodes[0]] = false
|
||||
return splitCodes[0]
|
||||
}
|
||||
delete(toggleCodes, splitCodes[0])
|
||||
return splitCodes[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
101
app/helper/translation-helper.go
Normal file
101
app/helper/translation-helper.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var translations = map[string]string{
|
||||
"ArrowUp": "up",
|
||||
"ArrowDown": "down",
|
||||
"ArrowRight": "right",
|
||||
"ArrowLeft": "left",
|
||||
"Meta": "cmd",
|
||||
"MetaLeft": "lcmd",
|
||||
"MetaRight": "rcmd",
|
||||
"Alt": "alt",
|
||||
"AltLeft": "lalt",
|
||||
"AltRight": "ralt",
|
||||
"Control": "ctrl",
|
||||
"ControlLeft": "lctrl",
|
||||
"ControlRight": "rctrl",
|
||||
"Shift": "shift",
|
||||
"ShiftLeft": "lshift",
|
||||
"ShiftRight": "rshift",
|
||||
"AudioVolumeMute": "audio_mute",
|
||||
"AudioVolumeDown": "audio_vol_down",
|
||||
"AudioVolumeUp": "audio_vol_up",
|
||||
"MediaTrackPrevious": "audio_prev",
|
||||
"MediaTrackNext": "audio_next",
|
||||
"MediaPlayPause": "audio_play|audio_pause",
|
||||
"Numpad0": "num0",
|
||||
"Numpad1": "num1",
|
||||
"Numpad2": "num2",
|
||||
"Numpad3": "num3",
|
||||
"Numpad4": "num4",
|
||||
"Numpad5": "num5",
|
||||
"Numpad6": "num6",
|
||||
"Numpad7": "num7",
|
||||
"Numpad8": "num8",
|
||||
"Numpad9": "num9",
|
||||
"NumLock": "num_lock",
|
||||
"NumpadDecimal": "num.",
|
||||
"NumpadAdd": "num+",
|
||||
"NumpadSubtract": "num-",
|
||||
"NumpadMultiply": "num*",
|
||||
"NumpadDivide": "num/",
|
||||
"NumpadEnter": "num_enter",
|
||||
"Clear": "num_clear",
|
||||
"BracketLeft": "[",
|
||||
"BracketRight": "]",
|
||||
"Quote": "'",
|
||||
"Semicolon": ";",
|
||||
"Backquote": "`",
|
||||
"Backslash": "\\",
|
||||
"IntlBackslash": "\\",
|
||||
"Slash": "/",
|
||||
"Comma": ",",
|
||||
"Period": ".",
|
||||
"Equal": "=",
|
||||
"Minus": "-",
|
||||
}
|
||||
|
||||
func Translate(code string) string {
|
||||
if val, ok := translations[code]; ok {
|
||||
return val
|
||||
}
|
||||
return strings.ToLower(code)
|
||||
}
|
||||
|
||||
func ReverseTranslate(name string) string {
|
||||
if name == "\\" {
|
||||
return "Backslash"
|
||||
}
|
||||
|
||||
for key, value := range translations {
|
||||
if value == name {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
45
app/log.go
Normal file
45
app/log.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var logFile *os.File
|
||||
|
||||
func MCRMLogInit() {
|
||||
var err error
|
||||
// Open or create the log file with append permissions
|
||||
logFile, err = os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err) // Exit if we can't open the log file
|
||||
}
|
||||
|
||||
// Optionally set log to write to file in addition to standard log output
|
||||
log.SetOutput(logFile)
|
||||
}
|
||||
|
||||
func MCRMLog(v ...interface{}) {
|
||||
log.Println(v...) // Logs to terminal as well
|
||||
}
|
||||
229
app/macro.go
Normal file
229
app/macro.go
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
)
|
||||
|
||||
func CheckMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var filename = helper.FormatMacroFileName(req.Macro)
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(fmt.Sprintf("macros/%s.json", filename))
|
||||
|
||||
if macroFile != nil && err == nil {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
} else {
|
||||
MCRMLog("OpenMacro ReadMacroFile Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func SaveMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var newMacro structs.NewMacro
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro ReadAll Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &newMacro)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro Unmarshal Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
simplifiedSteps := make([]map[string]interface{}, 0)
|
||||
for _, step := range newMacro.Steps {
|
||||
simplifiedSteps = append(simplifiedSteps, simplifyMacro(step))
|
||||
}
|
||||
|
||||
stepsJSON, err := json.Marshal(simplifiedSteps)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro Marshal Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile("macros/"+helper.FormatMacroFileName(newMacro.Name)+".json", stepsJSON, 0644)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro WriteFile Error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func simplifyMacro(step structs.Step) map[string]interface{} {
|
||||
simplified := make(map[string]interface{})
|
||||
|
||||
simplified["type"] = step.Type
|
||||
|
||||
switch step.Type {
|
||||
case "delay":
|
||||
simplified["value"] = step.Value
|
||||
case "key":
|
||||
keyCode := step.Code
|
||||
|
||||
if keyCode == "" || (strings.Contains(keyCode, "Digit")) {
|
||||
keyCode = step.Key
|
||||
} else if strings.Contains(keyCode, "Key") {
|
||||
keyCode = strings.Replace(keyCode, "Key", "", 1)
|
||||
}
|
||||
|
||||
simplified["code"] = helper.Translate(keyCode)
|
||||
simplified["direction"] = step.Direction
|
||||
}
|
||||
|
||||
return simplified
|
||||
}
|
||||
|
||||
func ListMacros(w http.ResponseWriter, r *http.Request) {
|
||||
dir := "macros"
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
MCRMLog("ListMacros ReadDir Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
var macroList []structs.MacroInfo
|
||||
|
||||
for _, file := range files {
|
||||
filename := filepath.Base(file.Name())
|
||||
macroname := strings.TrimSuffix(filename, filepath.Ext(filename))
|
||||
nicename := strings.Replace(macroname, "_", " ", -1)
|
||||
|
||||
macroList = append(macroList, structs.MacroInfo{
|
||||
Name: nicename,
|
||||
Macroname: macroname,
|
||||
})
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(macroList)
|
||||
}
|
||||
|
||||
func DeleteMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeleteMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var filename = helper.FormatMacroFileName(req.Macro)
|
||||
|
||||
err = os.Remove("macros/" + filename + ".json")
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeleteMacro Remove Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
log.Println("Deleted Macro:", req.Macro)
|
||||
json.NewEncoder(w).Encode(true)
|
||||
}
|
||||
|
||||
func PlayMacro(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.MacroRequest{}
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro ParseRequest Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
macro := req.Macro
|
||||
|
||||
MCRMLog("Playing Macro: ", macro)
|
||||
|
||||
var filename = helper.FormatMacroFileName(macro)
|
||||
var filepath = fmt.Sprintf("macros/%s.json", filename)
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(filepath)
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro ReadMacroFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = helper.RunMacroSteps(macroFile)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro RunMacroSteps Error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func OpenMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(fmt.Sprintf("macros/%s.json", req.Macro))
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro ReadMacroFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Walk through the macro file and reverse translate codes
|
||||
for i, action := range macroFile {
|
||||
if actionType, ok := action["type"].(string); ok && actionType == "key" {
|
||||
if code, ok := action["code"].(string); ok {
|
||||
macroFile[i]["code"] = helper.ReverseTranslate(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(macroFile)
|
||||
}
|
||||
176
app/panel.go
Normal file
176
app/panel.go
Normal 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/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PanelList(w http.ResponseWriter, r *http.Request) {
|
||||
panelDirs, err := os.ReadDir("panels")
|
||||
if err != nil {
|
||||
MCRMLog("PanelList ReadDir Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
var panelList []interface{}
|
||||
|
||||
for _, panelDir := range panelDirs {
|
||||
if panelDir.IsDir() {
|
||||
panelList = append(panelList, getPanelInfo(panelDir.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(panelList) == 0 {
|
||||
MCRMLog("PanelList: No panels found")
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(panelList)
|
||||
}
|
||||
|
||||
func getPanelInfo(dirname string) structs.PanelInfo {
|
||||
var panelInfo structs.PanelInfo
|
||||
|
||||
jsonFile, err := os.ReadFile("panels/" + dirname + "/panel.json")
|
||||
|
||||
if err != nil {
|
||||
panelInfo.Name = strings.Replace(dirname, "_", " ", -1)
|
||||
panelInfo.Description = "null"
|
||||
panelInfo.AspectRatio = "null"
|
||||
panelInfo.Macros = make(map[string]string)
|
||||
} else {
|
||||
err = json.Unmarshal(jsonFile, &panelInfo)
|
||||
if err != nil {
|
||||
MCRMLog("getPanelInfo Unmarshal Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
panelInfo.Dir = dirname
|
||||
|
||||
thumb := getPanelThumb(dirname)
|
||||
panelInfo.Thumb = thumb
|
||||
|
||||
return panelInfo
|
||||
}
|
||||
|
||||
func getPanelThumb(dirname string) string {
|
||||
extensions := []string{".jpg", ".jpeg", ".png", ".webp"}
|
||||
|
||||
for _, ext := range extensions {
|
||||
filename := "thumbnail" + ext
|
||||
file, err := os.Open("panels/" + dirname + "/" + filename)
|
||||
if err != nil {
|
||||
MCRMLog("getPanelThumb Open Error: ", err)
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return encodeImg(file)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getPanelCode(dirname string) (html string, css string) {
|
||||
htmlBytes, _ := os.ReadFile("panels/" + dirname + "/index.html")
|
||||
cssBytes, _ := os.ReadFile("panels/" + dirname + "/output.css")
|
||||
|
||||
return string(htmlBytes), string(cssBytes)
|
||||
}
|
||||
|
||||
func encodeImg(file *os.File) string {
|
||||
contents, err := os.ReadFile(file.Name())
|
||||
if err != nil {
|
||||
MCRMLog("encodeImg ReadFile Error: ", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(contents)
|
||||
return encoded
|
||||
}
|
||||
|
||||
func GetPanel(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.PanelRequest{}
|
||||
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
if err != nil {
|
||||
MCRMLog("GetPanel ParseRequest Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var response = structs.PanelResponse{}
|
||||
|
||||
panelInfo := getPanelInfo(req.Dir)
|
||||
panelHtml, panelCss := getPanelCode(req.Dir)
|
||||
|
||||
response.Dir = panelInfo.Dir
|
||||
response.Name = panelInfo.Name
|
||||
response.Description = panelInfo.Description
|
||||
response.AspectRatio = panelInfo.AspectRatio
|
||||
response.Macros = panelInfo.Macros
|
||||
response.Thumb = panelInfo.Thumb
|
||||
response.HTML = panelHtml
|
||||
response.Style = panelCss
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func SavePanelJSON(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.PanelSaveJSON
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filePath := "panels/" + req.Dir + "/panel.json"
|
||||
|
||||
req.Dir = ""
|
||||
|
||||
// Marshal the data to JSON without the dir field
|
||||
jsonData, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON Marshal Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(filePath, jsonData, 0644)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON WriteFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
66
app/structs/api-struct.go
Normal file
66
app/structs/api-struct.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type Allowed struct {
|
||||
Local []string
|
||||
Remote []string
|
||||
Auth []string
|
||||
}
|
||||
|
||||
var Endpoints = Allowed{
|
||||
Local: []string{
|
||||
"/macro/check",
|
||||
"/macro/record",
|
||||
"/macro/list",
|
||||
"/macro/open",
|
||||
"/macro/delete",
|
||||
"/macro/play",
|
||||
"/device/server/ip",
|
||||
"/device/list",
|
||||
"/device/access/check",
|
||||
"/device/access/request",
|
||||
"/device/link/ping",
|
||||
"/device/link/start",
|
||||
"/device/link/poll",
|
||||
"/device/link/remove",
|
||||
"/device/handshake",
|
||||
"/panel/get",
|
||||
"/panel/list",
|
||||
"/panel/save/json",
|
||||
},
|
||||
Remote: []string{
|
||||
"/device/access/check",
|
||||
"/device/access/request",
|
||||
"/device/server/ip",
|
||||
"/device/link/ping",
|
||||
"/device/link/end",
|
||||
"/device/handshake",
|
||||
"/device/auth",
|
||||
},
|
||||
Auth: []string{
|
||||
"/macro/play",
|
||||
"/device/link/remove",
|
||||
"/panel/get",
|
||||
"/panel/list",
|
||||
},
|
||||
}
|
||||
52
app/structs/device-struct.go
Normal file
52
app/structs/device-struct.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type Settings struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type RemoteWebhook struct {
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Uuid string `json:"uuid"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Handshake struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Shake string `json:"shake"`
|
||||
}
|
||||
|
||||
type Authcall struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Data string `json:"d"`
|
||||
}
|
||||
58
app/structs/macro-struct.go
Normal file
58
app/structs/macro-struct.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type MacroRequest struct {
|
||||
Macro string `json:"macro"`
|
||||
}
|
||||
|
||||
type Step struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Code string `json:"code"`
|
||||
Location int `json:"location"`
|
||||
Direction string `json:"direction"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
type NewMacro struct {
|
||||
Name string `json:"name"`
|
||||
Steps []Step `json:"steps"`
|
||||
}
|
||||
|
||||
type MacroInfo struct {
|
||||
Name string `json:"name"`
|
||||
Macroname string `json:"macroname"`
|
||||
}
|
||||
|
||||
type MacroKey struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Code string `json:"code"`
|
||||
Location int `json:"location"`
|
||||
Direction string `json:"direction"`
|
||||
}
|
||||
|
||||
type MacroDelay struct {
|
||||
Type string `json:"type"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
65
app/structs/panel-struct.go
Normal file
65
app/structs/panel-struct.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type PanelJSON struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
}
|
||||
|
||||
type PanelSaveJSON struct {
|
||||
// Name string `json:"name"`
|
||||
// Description string `json:"description"`
|
||||
// AspectRatio string `json:"aspectRatio"`
|
||||
// Macros map[string]string `json:"macros"`
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros interface{} `json:"macros"`
|
||||
}
|
||||
|
||||
type PanelInfo struct {
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
|
||||
type PanelRequest struct {
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
||||
type PanelResponse struct {
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
Thumb string `json:"thumb"`
|
||||
HTML string `json:"html"`
|
||||
Style string `json:"style"`
|
||||
}
|
||||
64
app/systray.go
Normal file
64
app/systray.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"macrame/app/helper"
|
||||
"os"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
)
|
||||
|
||||
func InitSystray() {
|
||||
go func() {
|
||||
systray.Run(OnReady, OnExit)
|
||||
}()
|
||||
}
|
||||
|
||||
func OnReady() {
|
||||
systray.SetIcon(getIcon("favicon.ico"))
|
||||
systray.SetTitle("Macrame")
|
||||
systray.SetTooltip("Macrame - Server")
|
||||
|
||||
ip, err := GetServerIp()
|
||||
|
||||
if err == nil {
|
||||
systray.AddMenuItem("IP: "+ip+":"+helper.EnvGet("MCRM__PORT"), "Server IP")
|
||||
}
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
addMCRMItem("Dashboard", "/")
|
||||
addMCRMItem("Panels", "/panels")
|
||||
addMCRMItem("Macros", "/macros")
|
||||
addMCRMItem("Devices", "/devices")
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
mQuit := systray.AddMenuItem("Quit Macrame", "Quit Macrame")
|
||||
go func() {
|
||||
<-mQuit.ClickedCh
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func addMCRMItem(name, urlPath string) {
|
||||
m := systray.AddMenuItem(name, name)
|
||||
|
||||
go func() {
|
||||
<-m.ClickedCh
|
||||
helper.OpenBrowser("http://localhost:" + helper.EnvGet("MCRM__PORT") + urlPath)
|
||||
}()
|
||||
}
|
||||
|
||||
func OnExit() {
|
||||
systray.Quit()
|
||||
}
|
||||
|
||||
func getIcon(path string) []byte {
|
||||
icon, err := os.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("getIcon Error: ", err)
|
||||
}
|
||||
|
||||
return icon
|
||||
}
|
||||
35
build.sh
Normal file
35
build.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Set the name of the build directory
|
||||
BUILD_DIR="Macrame_$(date +'%m%d%H%M%S')"
|
||||
|
||||
# Build the Go application
|
||||
go build -ldflags "-H=windowsgui" -o Macrame.exe main.go
|
||||
|
||||
# Build the frontend
|
||||
cd fe
|
||||
npm run build
|
||||
|
||||
cd ../builds
|
||||
|
||||
# Create the new build directory
|
||||
mkdir $BUILD_DIR
|
||||
mkdir $BUILD_DIR/macros
|
||||
mkdir $BUILD_DIR/panels
|
||||
mkdir $BUILD_DIR/panels/test_panel
|
||||
mkdir $BUILD_DIR/public
|
||||
mkdir $BUILD_DIR/public/assets
|
||||
|
||||
# Move the generated files to the new build directory
|
||||
cp ../Macrame.exe $BUILD_DIR/Macrame.exe
|
||||
cp ../favicon.ico $BUILD_DIR/favicon.ico
|
||||
find ../macros -type f ! -name 'ED-*' -exec cp --parents {} "$BUILD_DIR/macros/" \;
|
||||
cp -r ../panels/test_panel/* $BUILD_DIR/panels/test_panel/
|
||||
mv ../public/* $BUILD_DIR/public/
|
||||
|
||||
# cp ../install.bat $BUILD_DIR/install.bat
|
||||
|
||||
powershell -Command "Compress-Archive -Path $BUILD_DIR/* -DestinationPath $BUILD_DIR.zip -Force"
|
||||
|
||||
# Print the path to the new build directory
|
||||
echo "Build directory: ../$BUILD_DIR"
|
||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
9
fe/.editorconfig
Normal file
9
fe/.editorconfig
Normal 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
1
fe/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
||||
30
fe/.gitignore
vendored
Normal file
30
fe/.gitignore
vendored
Normal 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
7
fe/.prettierrc.json
Normal 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
8
fe/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
60
fe/mcrm-icon.svg
Normal 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
1238
fe/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
68
fe/src/assets/style/_content.css
Normal file
68
fe/src/assets/style/_content.css
Normal 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;
|
||||
}
|
||||
49
fe/src/assets/style/_form.css
Normal file
49
fe/src/assets/style/_form.css
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
44
fe/src/assets/style/_scrollbar.css
Normal file
44
fe/src/assets/style/_scrollbar.css
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
header {
|
||||
@apply grid
|
||||
grid-cols-[1fr_auto]
|
||||
px-4 py-2
|
||||
cursor-pointer;
|
||||
}
|
||||
|
||||
.accordion__wrapper {
|
||||
@apply grid
|
||||
grid-rows-[0fr]
|
||||
border-y
|
||||
border-b-white/60
|
||||
border-t-transparent
|
||||
duration-300
|
||||
ease-in-out;
|
||||
|
||||
.accordion__content {
|
||||
@apply grid
|
||||
grid-rows-[0fr]
|
||||
overflow-hidden
|
||||
duration-300
|
||||
ease-in-out;
|
||||
opacity-0
|
||||
transition-opacity
|
||||
delay-0;
|
||||
}
|
||||
|
||||
div {
|
||||
@apply grid
|
||||
grid-rows-[0fr];
|
||||
&.open {
|
||||
@apply grid-rows-[1fr]
|
||||
border-t-white/20;
|
||||
|
||||
.accordion__content {
|
||||
@apply grid-rows-[1fr]
|
||||
opacity-100
|
||||
delay-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +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/>.
|
||||
-->
|
||||
|
||||
<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'" />
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
&.btn__sm {
|
||||
@apply px-3 py-1
|
||||
text-sm;
|
||||
|
||||
svg {
|
||||
@apply size-4;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn__lg svg {
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)">
|
||||
|
|
|
|||
59
fe/src/components/base/LoadComp.vue
Normal file
59
fe/src/components/base/LoadComp.vue
Normal 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>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
107
fe/src/components/dashboard/RemoteView.vue
Normal file
107
fe/src/components/dashboard/RemoteView.vue
Normal 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>
|
||||
154
fe/src/components/dashboard/ServerView.vue
Normal file
154
fe/src/components/dashboard/ServerView.vue
Normal 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>
|
||||
|
|
@ -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">
|
||||
<AlertComp variant="info">
|
||||
<strong>This is a remote device.</strong>
|
||||
<em>UUID: {{ device.uuid() }} </em>
|
||||
</div>
|
||||
</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) {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,45 @@
|
|||
<!--
|
||||
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">
|
||||
<AlertComp variant="info">
|
||||
<strong>This is a server!</strong>
|
||||
<em>UUID: {{ device.uuid() }} </em>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
<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'" />
|
||||
|
|
@ -31,33 +52,85 @@
|
|||
</h5>
|
||||
<em>{{ id }}</em>
|
||||
</div>
|
||||
|
||||
<template v-if="remoteDevice.key">
|
||||
<AlertComp type="success">Authorized</AlertComp>
|
||||
<AlertComp variant="success">Authorized</AlertComp>
|
||||
<ButtonComp variant="danger" @click="unlinkDevice(id)">
|
||||
<IconLinkOff />Unlink device
|
||||
</ButtonComp>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<AlertComp type="warning">Unauthorized</AlertComp>
|
||||
<AlertComp variant="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>
|
||||
<AlertComp variant="info">One time pin: {{ remote.pinlink.pin }}</AlertComp>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
</template>
|
||||
|
||||
<!-- <template v-else>
|
||||
<div class="grid w-full gap-4">
|
||||
<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>
|
||||
|
|
|
|||
176
fe/src/components/form/FormSelect.vue
Normal file
176
fe/src/components/form/FormSelect.vue
Normal 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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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'}`"
|
||||
|
|
|
|||
253
fe/src/components/panels/PanelEdit.vue
Normal file
253
fe/src/components/panels/PanelEdit.vue
Normal 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>
|
||||
103
fe/src/components/panels/PanelView.vue
Normal file
103
fe/src/components/panels/PanelView.vue
Normal 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>
|
||||
183
fe/src/components/panels/PanelsOverview.vue
Normal file
183
fe/src/components/panels/PanelsOverview.vue
Normal 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>
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,31 @@
|
|||
/*
|
||||
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 = () => {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
47
fe/src/services/MacroService.js
Normal file
47
fe/src/services/MacroService.js
Normal 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)
|
||||
}
|
||||
120
fe/src/services/PanelService.js
Normal file
120
fe/src/services/PanelService.js
Normal 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()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
const resp = await axios.post(appUrl() + '/macro/record', {
|
||||
name: macroName.value,
|
||||
steps: steps.value,
|
||||
})
|
||||
return true
|
||||
|
||||
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
82
fe/src/stores/panel.js
Normal 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,
|
||||
}
|
||||
})
|
||||
105
fe/src/views/DashboardView.vue
Normal file
105
fe/src/views/DashboardView.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
46
go.mod
Normal file
46
go.mod
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module macrame
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/getlantern/systray v1.2.2
|
||||
github.com/go-vgo/robotgo v0.110.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/gen2brain/shm v0.1.1 // indirect
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/jezek/xgb v1.1.1 // indirect
|
||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/otiai10/gosseract v2.2.1+incompatible // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/robotn/xgb v0.10.0 // indirect
|
||||
github.com/robotn/xgbutil v0.10.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/vcaesar/gops v0.40.0 // indirect
|
||||
github.com/vcaesar/imgo v0.40.2 // indirect
|
||||
github.com/vcaesar/keycode v0.10.1 // indirect
|
||||
github.com/vcaesar/tt v0.20.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
)
|
||||
102
go.sum
Normal file
102
go.sum
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
|
||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e h1:L+XrFvD0vBIBm+Wf9sFN6aU395t7JROoai0qXZraA4U=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gen2brain/shm v0.1.1 h1:1cTVA5qcsUFixnDHl14TmRoxgfWEEZlTezpUj1vm5uQ=
|
||||
github.com/gen2brain/shm v0.1.1/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
|
||||
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-vgo/robotgo v0.110.7 h1:4scqQrJOBHoFCfcMROYEVFBxHvB3nF/UN6DWoRIFzBE=
|
||||
github.com/go-vgo/robotgo v0.110.7/go.mod h1:eBUjTHY1HYjzdi1+UWJUbxB+b9gE+l4Ei7vQU/9SnLw=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c h1:1IlzDla/ZATV/FsRn1ETf7ir91PHS2mrd4VMunEtd9k=
|
||||
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/otiai10/gosseract v2.2.1+incompatible h1:Ry5ltVdpdp4LAa2bMjsSJH34XHVOV7XMi41HtzL8X2I=
|
||||
github.com/otiai10/gosseract v2.2.1+incompatible/go.mod h1:XrzWItCzCpFRZ35n3YtVTgq5bLAhFIkascoRo8G32QE=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ=
|
||||
github.com/robotn/xgb v0.10.0 h1:O3kFbIwtwZ3pgLbp1h5slCQ4OpY8BdwugJLrUe6GPIM=
|
||||
github.com/robotn/xgb v0.10.0/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ=
|
||||
github.com/robotn/xgbutil v0.10.0 h1:gvf7mGQqCWQ68aHRtCxgdewRk+/KAJui6l3MJQQRCKw=
|
||||
github.com/robotn/xgbutil v0.10.0/go.mod h1:svkDXUDQjUiWzLrA0OZgHc4lbOts3C+uRfP6/yjwYnU=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35 h1:wAZbkTZkqDzWsqxPh2qkBd3KvFU7tcxV0BP0Rnhkxog=
|
||||
github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35/go.mod h1:aMd4yDHLjbOuYP6fMxj1d9ACDQlSWwYztcpybGHCQc8=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/vcaesar/gops v0.40.0 h1:I+1RCGiV+LkZJUYNzAd373xs0uM2UyeFdZBmow8HfCM=
|
||||
github.com/vcaesar/gops v0.40.0/go.mod h1:3u/USW7JovqUK6i13VOD3qWfvXXd2TIIKE4PYIv4TOM=
|
||||
github.com/vcaesar/imgo v0.40.2 h1:5GWScRLdBCMtO1v2I1bs+ZmDLZFINxYSMZ+mtUw5qPM=
|
||||
github.com/vcaesar/imgo v0.40.2/go.mod h1:MVCl+FxHI2gTgmiHoi0n5xNCbYcfv9SVtdEOUC92+eo=
|
||||
github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw=
|
||||
github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
|
||||
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
|
||||
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg=
|
||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
73
main.go
Normal file
73
main.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"macrame/app"
|
||||
"macrame/app/helper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app.MCRMLogInit()
|
||||
|
||||
switchDir()
|
||||
|
||||
if helper.EnvGet("MCRM__PORT") == "" {
|
||||
app.MCRMLog("Error: MCRM__PORT is not set")
|
||||
}
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
apiInit(w, r)
|
||||
})
|
||||
|
||||
helper.OpenBrowser("http://localhost:" + helper.EnvGet("MCRM__PORT"))
|
||||
|
||||
app.MCRMLog("Listening on http://localhost:" + helper.EnvGet("MCRM__PORT"))
|
||||
|
||||
app.InitSystray()
|
||||
|
||||
app.MCRMLog(http.ListenAndServe(":"+helper.EnvGet("MCRM__PORT"), nil))
|
||||
}
|
||||
|
||||
func switchDir() {
|
||||
cwd, err := os.Getwd()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println(cwd)
|
||||
}
|
||||
|
||||
func apiInit(w http.ResponseWriter, r *http.Request) {
|
||||
app.ApiCORS(w, r)
|
||||
|
||||
if r.Method == "GET" {
|
||||
app.ApiGet(w, r)
|
||||
} else if r.Method == "POST" {
|
||||
app.ApiPost(w, r)
|
||||
}
|
||||
}
|
||||
913
panels/Elite_Dangerous/index.html
Normal file
913
panels/Elite_Dangerous/index.html
Normal file
|
|
@ -0,0 +1,913 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="./output.css" />
|
||||
</head>
|
||||
<body id="panel-html__body" class="relative m-0">
|
||||
<div
|
||||
class="absolute inset-0 bg-slate-950 size-full grid grid-cols-[1fr_2fr_1fr] gap-2 p-2"
|
||||
>
|
||||
<div class="group-left">
|
||||
<div id="menu-spacer" class="size-16"></div>
|
||||
|
||||
<div id="maps" class="ed-panel pnl__blue">
|
||||
<h3>Maps</h3>
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
class="ed-button btn__yellow btn__vertical"
|
||||
id="System__map"
|
||||
mcrm__button
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g style="fill: currentColor">
|
||||
<circle cx="21.6" cy="17.1" r="0.9" />
|
||||
<circle cx="21.6" cy="14.6" r="0.9" />
|
||||
<circle cx="21.6" cy="12" r="0.9" />
|
||||
<circle cx="15.4" cy="12" r="0.9" />
|
||||
<circle cx="15.4" cy="7.9" r="1.9" />
|
||||
<circle cx="21.6" cy="7.9" r="1.9" />
|
||||
<circle cx="5.9" cy="7.6" r="5.4" />
|
||||
<circle cx="5.9" cy="19.2" r="2.6" />
|
||||
</g>
|
||||
</svg>
|
||||
System
|
||||
</div>
|
||||
<div
|
||||
class="ed-button btn__orange btn__vertical"
|
||||
id="Galaxy__map"
|
||||
mcrm__button
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
style="fill: currentColor"
|
||||
d="M22.1,8.9c-0.3,0-0.6,0.1-0.9,0.3L17,4.9c0.2-0.4,0.3-0.8,0.3-1.3c0-1.6-1.3-2.8-2.8-2.8S11.7,2,11.7,3.6
|
||||
c0,0.8,0.4,1.6,0.9,2.1l-5.5,7.9c-0.5-0.2-1.1-0.3-1.7-0.3c-2.7,0-5,2.2-5,5s2.2,5,5,5s5-2.2,5-5c0-1.6-0.7-3-1.9-3.9l4.8-8.2
|
||||
c0.3,0.2,0.7,0.2,1.1,0.2c0.6,0,1.1-0.2,1.5-0.5l4.7,3.8c-0.1,0.2-0.1,0.4-0.1,0.6c0,0.8,0.6,1.4,1.4,1.4s1.4-0.6,1.4-1.4
|
||||
C23.5,9.6,22.9,8.9,22.1,8.9z"
|
||||
/>
|
||||
</svg>
|
||||
Galaxy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fsd" class="ed-panel pnl__yellow">
|
||||
<h3>Frame Shift Drive</h3>
|
||||
|
||||
<div
|
||||
id="FSD__toggle"
|
||||
class="ed-button btn__blue !rounded-b-none"
|
||||
mcrm__button
|
||||
>
|
||||
Toggle FSD
|
||||
</div>
|
||||
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
id="Super__Cruise"
|
||||
class="!rounded-tl-none ed-button btn__yellow"
|
||||
mcrm__button
|
||||
>
|
||||
Super Cruise
|
||||
</div>
|
||||
<div
|
||||
id="Hyper__Space"
|
||||
class="!rounded-tr-none ed-button btn__orange"
|
||||
mcrm__button
|
||||
>
|
||||
Hyper Space
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div
|
||||
id="mode-switch"
|
||||
class="grid gap-2 justify-items-center ed-panel pnl__white"
|
||||
>
|
||||
<h4>Mode</h4>
|
||||
<span>Analysis</span>
|
||||
<div
|
||||
id="Mode__toggle"
|
||||
class="ed-toggle toggle__vertical"
|
||||
inactive-wrapper="border-sky-600 bg-sky-400/30"
|
||||
inactive-indicator="bg-sky-400"
|
||||
active-wrapper="border-red-600 bg-red-400/30"
|
||||
active-indicator="bg-red-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span>Combat</span>
|
||||
</div>
|
||||
<div
|
||||
id="mode-switch"
|
||||
class="grid gap-2 justify-items-center ed-panel pnl__red"
|
||||
>
|
||||
<h4>Hardpoints</h4>
|
||||
<span>Retract</span>
|
||||
<div
|
||||
id="Hardpoints__toggle"
|
||||
class="ed-toggle toggle__vertical"
|
||||
inactive-wrapper="border-sky-600 bg-sky-400/30"
|
||||
inactive-indicator="bg-sky-400"
|
||||
active-wrapper="border-red-600 bg-red-400/30"
|
||||
active-indicator="bg-red-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span>Deploy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-middle">
|
||||
<div id="ship-panels" class="ed-panel pnl__blue">
|
||||
<h3>Panels</h3>
|
||||
<div class="grid-cols-4 ed-button-group__horizontal">
|
||||
<div
|
||||
class="ed-button btn__orange"
|
||||
id="External__panel"
|
||||
mcrm__button
|
||||
>
|
||||
External
|
||||
</div>
|
||||
<div class="ed-button btn__yellow" id="Comms__panel" mcrm__button>
|
||||
Comms
|
||||
</div>
|
||||
<div class="ed-button btn__yellow" id="Roles__panel" mcrm__button>
|
||||
Roles
|
||||
</div>
|
||||
<div
|
||||
class="ed-button btn__orange"
|
||||
id="Internal__panel"
|
||||
mcrm__button
|
||||
>
|
||||
Internal
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-[3fr_1fr_1fr] gap-2">
|
||||
<div class="ed-panel pnl__blue">
|
||||
<h3>Scanner</h3>
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
id="Scanner__FSS"
|
||||
class="ed-button btn__blue btn__filled"
|
||||
mcrm__button
|
||||
>
|
||||
FSS
|
||||
</div>
|
||||
<div
|
||||
id="Scanner__DiscScan"
|
||||
class="ed-button btn__blue"
|
||||
mcrm__button
|
||||
>
|
||||
Discovery
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="Route__NextSystem"
|
||||
class="ed-button btn__yellow !px-2"
|
||||
mcrm__button
|
||||
>
|
||||
<div>
|
||||
<span class="text-base opacity-80">Route:</span>
|
||||
<strong>Next System</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="Speed_0percent"
|
||||
class="ed-button btn__white !px-2"
|
||||
mcrm__button
|
||||
>
|
||||
<div>
|
||||
<span class="text-base opacity-80">Speed:</span>
|
||||
<strong>0%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ed-panel pnl__red">
|
||||
<h3>Fighters</h3>
|
||||
<div class="grid grid-cols-[2fr_1fr] gap-2">
|
||||
<div class="grid gap-2">
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
class="!justify-start ed-button btn__red btn__filled btn__vertical"
|
||||
id="Fighter_attack"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style="fill: currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M12,1.5C6.5,1.5,2,8,2,11.5s1.5,4.1,1.6,5.6c0.1,1,0.9,1.7,1.8,1.6c0.1,0-0.1-0.1,0.1-0.1v-2h1.2v1.9v2.1
|
||||
c0,1,0.8,1.8,1.8,1.8H12h3.5c1,0,1.8-0.8,1.8-1.8v-2.1v-1.9h1.2v2c0.2,0-0.1,0.1,0.1,0.1c0.9,0.1,1.7-0.6,1.8-1.6
|
||||
c0.2-1.5,1.6-2.1,1.6-5.6S17.5,1.5,12,1.5z M10.4,11.5c-0.6,1.4-2.3,2-3.7,1.4s-2-2.3-1.4-3.7C6,7.9,7.7,9.3,9.1,10
|
||||
S11,10.2,10.4,11.5z M13,15.6c-0.3,0-0.7-0.3-0.8-0.6c-0.1-0.3-0.3-0.3-0.4,0c-0.1,0.3-0.5,0.6-0.8,0.6s-0.3-0.9,0.1-1.9l0.3-0.8
|
||||
c0.4-1,0.9-1,1.3,0l0.3,0.8C13.3,14.7,13.3,15.6,13,15.6z M17.3,12.9c-1.4,0.6-3,0-3.7-1.4c-0.6-1.4-0.1-1,1.3-1.6s3.1-2.1,3.7-0.7
|
||||
C19.3,10.7,18.7,12.3,17.3,12.9z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Attack
|
||||
</div>
|
||||
<div
|
||||
class="!justify-start text-red-400 ed-button btn__red btn__vertical"
|
||||
id="Fighter__engage"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<path
|
||||
d="M14,4.3c2.8,0.7,5,2.9,5.7,5.7h2.1C21,6.1,17.9,3,14,2.2V4.3z"
|
||||
/>
|
||||
<path
|
||||
d="M4.3,10C5,7.2,7.2,5,10,4.3V2.2C6.1,3,3,6.1,2.2,10H4.3z"
|
||||
/>
|
||||
<path
|
||||
d="M10,19.7C7.2,19,5,16.8,4.3,14H2.2c0.8,3.9,3.9,7,7.8,7.8V19.7z"
|
||||
/>
|
||||
<path
|
||||
d="M19.7,14c-0.7,2.8-2.9,5-5.7,5.7v2.1c3.9-0.8,7-3.9,7.8-7.8H19.7z"
|
||||
/>
|
||||
|
||||
<polygon points="13,1 11,1 11,6.5 13,6.5 13,1 " />
|
||||
<polygon points="6.5,11 1,11 1,13 6.5,13 6.5,11 " />
|
||||
<polygon points="23,11 17.5,11 17.5,13 23,13 23,11 " />
|
||||
<polygon points="13,17.5 11,17.5 11,23 13,23 13,17.5 " />
|
||||
</svg>
|
||||
|
||||
Engage
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
class="!justify-start ed-button btn__orange btn__filled btn__vertical"
|
||||
id="Fighter_defend"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M12,2.4l0.3,0.1l7.2,2.6l0.6,0.2l0,0.7c0,0.5,0.6,12.6-7.9,15.4L12,21.6l-0.3-0.1C3.2,18.6,3.8,6.6,3.8,6.1l0-0.7l0.6-0.2
|
||||
l7.2-2.6L12,2.4 M12,0.3l-1,0.4L3.8,3.3L1.9,4l-0.1,2c0,0.6-0.7,14.1,9.2,17.4l0.9,0.3l0.9-0.3c9.9-3.3,9.3-16.8,9.2-17.4l-0.1-2
|
||||
l-1.9-0.7L13,0.7L12,0.3L12,0.3z"
|
||||
/>
|
||||
<path
|
||||
d="M19.2,6.1L12,3.5L4.8,6.1c0,0-0.7,11.8,7.2,14.4C19.8,17.9,19.2,6.1,19.2,6.1z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Defend
|
||||
</div>
|
||||
<div
|
||||
class="!justify-start text-red-400 ed-button btn__orange btn__vertical"
|
||||
id="Fighter__hold"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<polygon
|
||||
style="opacity: 0.8"
|
||||
points="13,1 11,1 11,6.5 13,6.5 13,1 "
|
||||
/>
|
||||
<polygon
|
||||
style="opacity: 0.8"
|
||||
points="6.5,11 1,11 1,13 6.5,13 6.5,11 "
|
||||
/>
|
||||
<polygon
|
||||
style="opacity: 0.8"
|
||||
points="23,11 17.5,11 17.5,13 23,13 23,11 "
|
||||
/>
|
||||
<polygon
|
||||
style="opacity: 0.8"
|
||||
points="13,17.5 11,17.5 11,23 13,23 13,17.5 "
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M6.1,20c1.1,0.8,2.5,1.5,3.9,1.8v-2.1c-0.9-0.2-1.7-0.6-2.5-1.1L6.1,20z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M4.3,14H2.2c0.3,1.4,0.9,2.8,1.8,3.9l1.5-1.5C4.9,15.7,4.5,14.9,4.3,14z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M7.5,5.4C8.3,4.9,9.1,4.5,10,4.3V2.2C8.6,2.5,7.2,3.1,6.1,4L7.5,5.4z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M5.4,7.5L4,6.1C3.1,7.2,2.5,8.6,2.2,10h2.1C4.5,9.1,4.9,8.3,5.4,7.5z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M18.6,7.5c0.5,0.7,0.9,1.6,1.1,2.5h2.1c-0.3-1.4-0.9-2.8-1.8-3.9L18.6,7.5z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M17.9,4c-1.1-0.8-2.5-1.5-3.9-1.8v2.1c0.9,0.2,1.7,0.6,2.5,1.1L17.9,4z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M18.6,16.5l1.5,1.5c0.8-1.1,1.5-2.5,1.8-3.9h-2.1C19.5,14.9,19.1,15.7,18.6,16.5z"
|
||||
/>
|
||||
<path
|
||||
style="opacity: 0.8"
|
||||
d="M16.5,18.6c-0.7,0.5-1.6,0.9-2.5,1.1v2.1c1.4-0.3,2.8-0.9,3.9-1.8L16.5,18.6z"
|
||||
/>
|
||||
<polygon
|
||||
points="21.2,19.8 13.4,12 21.2,4.2 19.8,2.8 12,10.6 4.2,2.8 2.8,4.2 10.6,12 2.8,19.8 4.2,21.2 12,13.4 19.8,21.2 "
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Hold
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
class="!justify-start ed-button btn__blue btn__filled btn__vertical"
|
||||
id="Fighter_follow"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<polygon
|
||||
points="11,13 2.5,13 4.5,15 0.3,19.2 4.8,23.7 9,19.5 11,21.5 "
|
||||
/>
|
||||
<polygon
|
||||
points="11,11 11,2.5 9,4.5 4.8,0.3 0.3,4.8 4.5,9 2.5,11 "
|
||||
/>
|
||||
<polygon
|
||||
points="13,11 21.5,11 19.5,9 23.7,4.8 19.2,0.3 15,4.5 13,2.5 "
|
||||
/>
|
||||
<polygon
|
||||
points="13,13 13,21.5 15,19.5 19.2,23.7 23.7,19.2 19.5,15 21.5,13 "
|
||||
/>
|
||||
</svg>
|
||||
Follow
|
||||
</div>
|
||||
<div
|
||||
class="!justify-start text-red-400 ed-button btn__blue btn__vertical"
|
||||
id="Fighter__recall"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12,1C5.9,1,1,5.9,1,12s4.9,11,11,11s11-4.9,11-11S18.1,1,12,1z M12,20c-4.4,0-8-3.6-8-8c0-3,1.6-5.5,4-6.9V10H5l7,7l7-7
|
||||
h-3V5.1c2.4,1.4,4,4,4,6.9C20,16.4,16.4,20,12,20z"
|
||||
/>
|
||||
</svg>
|
||||
Recall
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="h-full grid-rows-3 ed-button-group__vertical">
|
||||
<div
|
||||
id="Fighter__wingman1"
|
||||
class="ed-button btn__yellow btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-base opacity-80">Wingman</span>
|
||||
<strong>#1</strong>
|
||||
</div>
|
||||
<div
|
||||
id="Fighter__wingman2"
|
||||
class="ed-button btn__yellow btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-base opacity-80">Wingman</span>
|
||||
<strong>#2</strong>
|
||||
</div>
|
||||
<div
|
||||
id="Fighter__wingman3"
|
||||
class="ed-button btn__yellow btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-base opacity-80">Wingman</span>
|
||||
<strong>#3</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-panel pnl__red">
|
||||
<h3>Counter Measures</h3>
|
||||
<div class="grid-cols-4 ed-button-group__horizontal">
|
||||
<div id="CM__Heatsink" class="ed-button btn__red" mcrm__button>
|
||||
Heatsink
|
||||
</div>
|
||||
<div
|
||||
id="CM__Chaff"
|
||||
class="ed-button btn__red btn__filled"
|
||||
mcrm__button
|
||||
>
|
||||
Chaff
|
||||
</div>
|
||||
<div
|
||||
id="CM__ECM"
|
||||
class="ed-button btn__red btn__filled"
|
||||
mcrm__button
|
||||
>
|
||||
ECM
|
||||
</div>
|
||||
<div id="CM__Shieldcell" class="ed-button btn__red" mcrm__button>
|
||||
Shieldcell
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group-right">
|
||||
<div class="ed-panel pnl__white">
|
||||
<h3>Flight Assist</h3>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="grid gap-2 justify-items-center">
|
||||
<div
|
||||
id="FlightAssist__toggle"
|
||||
class="ed-toggle toggle__horizontal"
|
||||
inactive-wrapper="border-red-600 bg-red-400/30"
|
||||
inactive-indicator="bg-red-400"
|
||||
active-wrapper="border-lime-600 bg-lime-400/30"
|
||||
active-indicator="bg-lime-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
active="true"
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span>Off</span>
|
||||
<span>On</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="Rotational__Correction"
|
||||
class="!px-2 ed-button btn__blue w-fit"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-xs">Rotational Correction</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-2 divide-x ed-panel pnl__white divider-white !p-0"
|
||||
>
|
||||
<div id="light-switch" class="grid gap-2 p-2 justify-items-center">
|
||||
<h4>Lights</h4>
|
||||
<div
|
||||
id="Lights__toggle"
|
||||
class="ed-toggle toggle__horizontal"
|
||||
inactive-wrapper="border-white bg-white/30"
|
||||
inactive-indicator="bg-white"
|
||||
active-wrapper="border-sky-600 bg-sky-400/30"
|
||||
active-indicator="bg-sky-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span>Off</span>
|
||||
<span>On</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="light-switch" class="grid gap-2 p-2 justify-items-center">
|
||||
<h4>Night Vis.</h4>
|
||||
<div
|
||||
id="NightVis__toggle"
|
||||
class="ed-toggle toggle__horizontal"
|
||||
inactive-wrapper="border-white bg-white/30"
|
||||
inactive-indicator="bg-white"
|
||||
active-wrapper="border-lime-600 bg-lime-400/30"
|
||||
active-indicator="bg-lime-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span>Off</span>
|
||||
<span>On</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="grid gap-2 text-center ed-panel pnl__orange">
|
||||
<h4>Silent Running</h4>
|
||||
<div
|
||||
id="SilentRunning__toggle"
|
||||
class="ed-toggle toggle__horizontal"
|
||||
inactive-wrapper="border-white bg-white/30"
|
||||
inactive-indicator="bg-white"
|
||||
active-wrapper="border-red-600 bg-red-400/30"
|
||||
active-indicator="bg-red-400"
|
||||
mcrm__button
|
||||
toggle__button
|
||||
>
|
||||
<div class="toggle__wrapper">
|
||||
<div class="toggle__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span>Off</span>
|
||||
<span>On</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
id="Jettison__Cargo"
|
||||
class="flex flex-col items-center justify-center gap-2 text-center text-white border-red-500 rounded-full border-3 bg-red-500/80 aspect-square"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12 1.67c.955 0 1.845 .467 2.39 1.247l.105 .16l8.114 13.548a2.914 2.914 0 0 1 -2.307 4.363l-.195 .008h-16.225a2.914 2.914 0 0 1 -2.582 -4.2l.099 -.185l8.11 -13.538a2.914 2.914 0 0 1 2.491 -1.403zm.01 13.33l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -7a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z"
|
||||
/>
|
||||
</svg>
|
||||
<span> JETTISON CARGO </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div
|
||||
class="ed-button btn__blue btn__vertical"
|
||||
dialog-trigger="#camera-dialog"
|
||||
mcrm__dialog-trigger
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
style="fill: currentColor"
|
||||
>
|
||||
<path
|
||||
d="M22.6,9.1c-0.1-0.5-0.3-1-0.5-1.6c-1.5,0-5.3,0-6.8,0c2.3,2.3,5.1,5.1,7.3,7.3C23.1,13.1,23.1,10.9,22.6,9.1z"
|
||||
/>
|
||||
<path
|
||||
d="M6.6,12.8c0-3.2,0-7.2,0-10.4C4.4,3.7,2.6,5.7,1.7,8C2.3,8.6,6.6,12.8,6.6,12.8z"
|
||||
/>
|
||||
<path
|
||||
d="M12.8,17.4c-3.2,0-7.2,0-10.4,0c1.2,2.2,3.2,3.9,5.6,4.8C8.6,21.7,12.8,17.4,12.8,17.4z"
|
||||
/>
|
||||
<path
|
||||
d="M17.4,11.2c0,3.2,0,7.2,0,10.4c2.2-1.2,3.9-3.2,4.8-5.6C21.7,15.4,17.4,11.2,17.4,11.2z"
|
||||
/>
|
||||
<path
|
||||
d="M11.2,6.6c3.2,0,7.2,0,10.4,0c-1.2-2.2-3.2-3.9-5.6-4.8C15.4,2.3,11.2,6.6,11.2,6.6z"
|
||||
/>
|
||||
<path
|
||||
d="M7.6,16.4h1.2c-2.3-2.3-5.1-5.1-7.3-7.3c-0.7,2.4-0.5,5.1,0.5,7.3C2.7,16.4,6.9,16.4,7.6,16.4z"
|
||||
/>
|
||||
<path
|
||||
d="M14.3,17.4c-1.3,1.3-3.8,3.8-5.2,5.2c2.4,0.7,5.1,0.5,7.3-0.5c0-1.5,0-5.3,0-6.8C15.9,15.8,14.8,16.9,14.3,17.4z"
|
||||
/>
|
||||
<path
|
||||
d="M14.9,1.4c-2.4-0.7-5.1-0.5-7.3,0.5c0,1.5,0,5.3,0,6.8C8.1,8.2,13.5,2.8,14.9,1.4z"
|
||||
/>
|
||||
</svg>
|
||||
<span> CAMERA </span>
|
||||
</div>
|
||||
<dialog id="camera-dialog" mcrm__dialog>
|
||||
<div
|
||||
class="dialog__content ed-panel pnl__blue !w-fit !bg-sky-900 relative !p-4"
|
||||
>
|
||||
<div class="dialog__close">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-8"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M18 6l-12 12" />
|
||||
<path d="M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4>Camera</h4>
|
||||
<div class="grid grid-cols-[1fr_2fr] gap-2 mt-4">
|
||||
<div
|
||||
id="Camera__Suite"
|
||||
class="ed-button btn__orange btn__filled btn__vertical !text-gray-800"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-gradienter"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M3.227 14c.917 4 4.497 7 8.773 7c4.277 0 7.858 -3 8.773 -7"
|
||||
/>
|
||||
<path
|
||||
d="M20.78 10a9 9 0 0 0 -8.78 -7a8.985 8.985 0 0 0 -8.782 7"
|
||||
/>
|
||||
<path d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
</svg>
|
||||
<span>Camera Suite</span>
|
||||
</div>
|
||||
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
id="Previous__Camera"
|
||||
class="ed-button btn__yellow !text-gray-800 btn__filled !pt-8"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M20.341 4.247l-8 7a1 1 0 0 0 0 1.506l8 7c.647 .565 1.659 .106 1.659 -.753v-14c0 -.86 -1.012 -1.318 -1.659 -.753z"
|
||||
/>
|
||||
<path
|
||||
d="M9.341 4.247l-8 7a1 1 0 0 0 0 1.506l8 7c.647 .565 1.659 .106 1.659 -.753v-14c0 -.86 -1.012 -1.318 -1.659 -.753z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="relative text-right w-28">
|
||||
<span
|
||||
class="absolute right-0 text-base opacity-90 bottom-full"
|
||||
>
|
||||
Previous
|
||||
</span>
|
||||
<strong>Camera</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="Next__Camera"
|
||||
class="!pt-8 ed-button btn__yellow"
|
||||
mcrm__button
|
||||
>
|
||||
<div class="relative text-left w-28">
|
||||
<span class="absolute text-base opacity-80 bottom-full">
|
||||
Next
|
||||
</span>
|
||||
<strong>Camera</strong>
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M2 5v14c0 .86 1.012 1.318 1.659 .753l8 -7a1 1 0 0 0 0 -1.506l-8 -7c-.647 -.565 -1.659 -.106 -1.659 .753z"
|
||||
/>
|
||||
<path
|
||||
d="M13 5v14c0 .86 1.012 1.318 1.659 .753l8 -7a1 1 0 0 0 0 -1.506l-8 -7c-.647 -.565 -1.659 -.106 -1.659 .753z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="Free__Camera"
|
||||
class="ed-button btn__orange btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M18 9l3 3l-3 3" />
|
||||
<path d="M15 12h6" />
|
||||
<path d="M6 9l-3 3l3 3" />
|
||||
<path d="M3 12h6" />
|
||||
<path d="M9 18l3 3l3 -3" />
|
||||
<path d="M12 15v6" />
|
||||
<path d="M15 6l-3 -3l-3 3" />
|
||||
<path d="M12 3v6" />
|
||||
</svg>
|
||||
<span>Free Camera</span>
|
||||
</div>
|
||||
<div class="grid-cols-2 ed-button-group__horizontal">
|
||||
<div
|
||||
id="Camera__Pos1"
|
||||
class="ed-button btn__yellow !text-gray-800 btn__filled btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-base opacity-80">Position</span>
|
||||
<strong>#1</strong>
|
||||
</div>
|
||||
<div
|
||||
id="Camera__Pos2"
|
||||
class="ed-button btn__yellow btn__vertical"
|
||||
mcrm__button
|
||||
>
|
||||
<span class="text-base opacity-80">Position</span>
|
||||
<strong>#2</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
<div class="flex items-end justify-end h-28">
|
||||
<div id="clock">
|
||||
<div class="hours-minutes"></div>
|
||||
<sup class="seconds"></sup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function onPanelLoaded() {
|
||||
document
|
||||
.querySelectorAll("#panel-html__body [toggle__button]")
|
||||
.forEach((toggleButton) => {
|
||||
if (toggleButton.getAttribute("active")) {
|
||||
toggleClasses(toggleButton, true);
|
||||
} else {
|
||||
toggleClasses(toggleButton, false);
|
||||
}
|
||||
|
||||
toggleButton.addEventListener("click", (e) => {
|
||||
if (!toggleButton.getAttribute("active")) {
|
||||
toggleButton.setAttribute("active", true);
|
||||
|
||||
toggleClasses(toggleButton, true);
|
||||
} else {
|
||||
toggleButton.removeAttribute("active");
|
||||
|
||||
toggleClasses(toggleButton, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function toggleClasses(toggleButton, active) {
|
||||
const wrapper = toggleButton.querySelector(".toggle__wrapper");
|
||||
const indicator = toggleButton.querySelector(".toggle__indicator");
|
||||
|
||||
const stateClasses = getStateClasses(toggleButton);
|
||||
|
||||
if (active) {
|
||||
wrapper.classList.remove(...stateClasses.wrapper.inactive);
|
||||
wrapper.classList.add(...stateClasses.wrapper.active);
|
||||
|
||||
indicator.classList.remove(...stateClasses.indicator.inactive);
|
||||
indicator.classList.add(...stateClasses.indicator.active);
|
||||
} else {
|
||||
wrapper.classList.remove(...stateClasses.wrapper.active);
|
||||
wrapper.classList.add(...stateClasses.wrapper.inactive);
|
||||
|
||||
indicator.classList.remove(...stateClasses.indicator.active);
|
||||
indicator.classList.add(...stateClasses.indicator.inactive);
|
||||
}
|
||||
}
|
||||
|
||||
function getStateClasses(toggleButton) {
|
||||
return {
|
||||
wrapper: {
|
||||
active: mapClasses(toggleButton.getAttribute("active-wrapper")),
|
||||
inactive: mapClasses(
|
||||
toggleButton.getAttribute("inactive-wrapper")
|
||||
),
|
||||
},
|
||||
indicator: {
|
||||
active: mapClasses(toggleButton.getAttribute("active-indicator")),
|
||||
inactive: mapClasses(
|
||||
toggleButton.getAttribute("inactive-indicator")
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapClasses(classStr) {
|
||||
return classStr.split(" ").map((c) => c.trim());
|
||||
}
|
||||
|
||||
// document
|
||||
// .querySelectorAll("[dialog-trigger]")
|
||||
// .forEach((dialogTrigger) => {
|
||||
// dialogTrigger.addEventListener("click", (e) => {
|
||||
// document
|
||||
// .querySelector(dialogTrigger.getAttribute("dialog-trigger"))
|
||||
// .showModal();
|
||||
// });
|
||||
// });
|
||||
|
||||
// document
|
||||
// .querySelectorAll("dialog, dialog .dialog__close")
|
||||
// .forEach((dialogClose) => {
|
||||
// const dialog = dialogClose.closest("dialog");
|
||||
|
||||
// dialogClose.addEventListener("click", (e) => {
|
||||
// if (
|
||||
// e.target.classList.contains("dialog__close") ||
|
||||
// e.target.closest(".dialog__close") ||
|
||||
// e.target.tagName == "DIALOG"
|
||||
// ) {
|
||||
// dialog.close();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
setInterval(() => {
|
||||
const clockEl = document.querySelector("#clock");
|
||||
const hoursMins = clockEl.querySelector(".hours-minutes");
|
||||
const seconds = clockEl.querySelector(".seconds");
|
||||
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const secondsStr = now.getSeconds();
|
||||
|
||||
hoursMins.innerHTML = formatTimeToHTML(
|
||||
`${hours.toString().padStart(2, "0")}:${minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}`
|
||||
);
|
||||
seconds.innerHTML = formatTimeToHTML(
|
||||
secondsStr.toString().padStart(2, "0")
|
||||
);
|
||||
}, 1000);
|
||||
|
||||
function formatTimeToHTML(timeStr) {
|
||||
let htmlStr = "";
|
||||
|
||||
timeStr.split("").forEach((char) => {
|
||||
if (char === ":") htmlStr += "<i>:</i>";
|
||||
else htmlStr += "<span>" + char + "</span>";
|
||||
});
|
||||
|
||||
return htmlStr;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script no-compile>
|
||||
onPanelLoaded();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
253
panels/Elite_Dangerous/input.css
Normal file
253
panels/Elite_Dangerous/input.css
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
@import url(https://fonts.bunny.net/css?family=orbitron:400,600,800);
|
||||
@layer theme, utilities;
|
||||
@import "tailwindcss/theme.css" layer(theme);
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
|
||||
@layer theme {
|
||||
/* :root {
|
||||
} */
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#panel-html__body {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
#panel-html__body {
|
||||
--font-sans: "Orbitron", sans-serif;
|
||||
@apply font-sans text-sm font-light tracking-wide bg-gray-900 text-slate-50 size-full;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.group-left,
|
||||
.group-middle,
|
||||
.group-right {
|
||||
@apply grid gap-2 h-fit;
|
||||
}
|
||||
|
||||
.ed-panel {
|
||||
@apply w-full p-2 border h-fit rounded-b-xl;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply m-0 mb-1 font-extralight;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-sm;
|
||||
}
|
||||
|
||||
&.pnl__blue {
|
||||
@apply border-sky-300 bg-sky-500/30;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-sky-100;
|
||||
|
||||
text-shadow: 0 0 0.2em var(--color-sky-300);
|
||||
}
|
||||
}
|
||||
|
||||
&.pnl__yellow {
|
||||
@apply border-amber-300 bg-amber-500/30;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-amber-100;
|
||||
|
||||
text-shadow: 0 0 0.2em var(--color-amber-300);
|
||||
}
|
||||
}
|
||||
|
||||
&.pnl__orange {
|
||||
@apply border-orange-300 bg-orange-500/30;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-orange-100;
|
||||
|
||||
text-shadow: 0 0 0.2em var(--color-orange-300);
|
||||
}
|
||||
}
|
||||
|
||||
&.pnl__red {
|
||||
@apply border-rose-300 bg-rose-500/30;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-rose-100;
|
||||
|
||||
text-shadow: 0 0 0.2em var(--color-rose-400);
|
||||
}
|
||||
}
|
||||
|
||||
&.pnl__white {
|
||||
@apply border-white bg-white/20;
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-white;
|
||||
|
||||
text-shadow: 0 0 0.2em var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-button {
|
||||
@apply flex items-center justify-center px-4 py-2 text-base text-center rounded-lg border-3;
|
||||
|
||||
svg {
|
||||
@apply block !size-5;
|
||||
}
|
||||
|
||||
&.btn__vertical {
|
||||
@apply flex-col;
|
||||
}
|
||||
|
||||
&.btn__filled {
|
||||
@apply text-gray-900;
|
||||
}
|
||||
|
||||
&.btn__orange {
|
||||
@apply text-orange-100 border-orange-400 bg-orange-500/50;
|
||||
|
||||
&.btn__filled {
|
||||
@apply bg-orange-400;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn__yellow {
|
||||
@apply text-orange-100 border-amber-400 bg-amber-500/50;
|
||||
|
||||
&.btn__filled {
|
||||
@apply bg-amber-400;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn__blue {
|
||||
@apply border-sky-400 bg-sky-500/50 text-sky-100;
|
||||
|
||||
&.btn__filled {
|
||||
@apply bg-sky-500;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn__red {
|
||||
@apply border-rose-500 bg-rose-600/50 text-rose-100;
|
||||
|
||||
&.btn__filled {
|
||||
@apply bg-rose-600;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn__white {
|
||||
@apply border-white bg-white/30;
|
||||
|
||||
&.btn__filled {
|
||||
@apply bg-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-button-group__horizontal {
|
||||
@apply grid divide-x;
|
||||
|
||||
.ed-button {
|
||||
@apply rounded-none;
|
||||
|
||||
&:first-child {
|
||||
@apply rounded-l-lg;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply rounded-r-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-button-group__vertical {
|
||||
@apply grid divide-y;
|
||||
|
||||
.ed-button {
|
||||
@apply rounded-none;
|
||||
|
||||
&:first-child {
|
||||
@apply rounded-t-lg;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply rounded-b-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-toggle {
|
||||
.toggle__wrapper {
|
||||
@apply relative p-1.5 border-2 rounded-full size-full;
|
||||
}
|
||||
|
||||
.toggle__indicator {
|
||||
@apply absolute transition rounded-full aspect-square;
|
||||
}
|
||||
|
||||
&.toggle__horizontal {
|
||||
@apply w-20 h-12;
|
||||
|
||||
.toggle__indicator {
|
||||
@apply left-1.5 translate-x-0 h-[calc(100%-.75rem)];
|
||||
}
|
||||
|
||||
&[active] .toggle__indicator {
|
||||
@apply translate-x-full;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggle__vertical {
|
||||
@apply w-12 h-20;
|
||||
|
||||
.toggle__indicator {
|
||||
@apply top-1.5 translate-y-0 w-[calc(100%-.75rem)];
|
||||
}
|
||||
&[active] .toggle__indicator {
|
||||
@apply translate-y-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog[open] {
|
||||
@apply absolute -translate-x-1/2 -translate-y-1/2 bg-transparent border-0 outline-0 top-1/2 left-1/2;
|
||||
@apply backdrop:absolute backdrop:bg-black/50;
|
||||
|
||||
.dialog__close {
|
||||
@apply absolute text-white top-3 right-3;
|
||||
}
|
||||
}
|
||||
|
||||
#clock {
|
||||
@apply relative flex pr-16 text-3xl w-fit;
|
||||
|
||||
i {
|
||||
@apply pl-1 not-italic;
|
||||
}
|
||||
|
||||
.hours-minutes,
|
||||
.seconds {
|
||||
@apply flex gap-1;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply inline-block w-[.75em] text-center;
|
||||
}
|
||||
|
||||
sup {
|
||||
@apply absolute right-0 w-16 pl-2 text-lg text-left opacity-80;
|
||||
}
|
||||
}
|
||||
688
panels/Elite_Dangerous/output.css
Normal file
688
panels/Elite_Dangerous/output.css
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
/*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */
|
||||
@import url(https://fonts.bunny.net/css?family=orbitron:400,600,800);
|
||||
@layer properties;
|
||||
@layer theme, utilities;
|
||||
@layer theme {
|
||||
:root, :host {
|
||||
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
--color-red-400: oklch(70.4% 0.191 22.216);
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
||||
--color-orange-100: oklch(95.4% 0.038 75.164);
|
||||
--color-orange-300: oklch(83.7% 0.128 66.29);
|
||||
--color-orange-400: oklch(75% 0.183 55.934);
|
||||
--color-orange-500: oklch(70.5% 0.213 47.604);
|
||||
--color-amber-100: oklch(96.2% 0.059 95.617);
|
||||
--color-amber-300: oklch(87.9% 0.169 91.605);
|
||||
--color-amber-400: oklch(82.8% 0.189 84.429);
|
||||
--color-amber-500: oklch(76.9% 0.188 70.08);
|
||||
--color-lime-400: oklch(84.1% 0.238 128.85);
|
||||
--color-lime-600: oklch(64.8% 0.2 131.684);
|
||||
--color-sky-100: oklch(95.1% 0.026 236.824);
|
||||
--color-sky-300: oklch(82.8% 0.111 230.318);
|
||||
--color-sky-400: oklch(74.6% 0.16 232.661);
|
||||
--color-sky-500: oklch(68.5% 0.169 237.323);
|
||||
--color-sky-600: oklch(58.8% 0.158 241.966);
|
||||
--color-sky-900: oklch(39.1% 0.09 240.876);
|
||||
--color-rose-100: oklch(94.1% 0.03 12.58);
|
||||
--color-rose-300: oklch(81% 0.117 11.638);
|
||||
--color-rose-400: oklch(71.2% 0.194 13.428);
|
||||
--color-rose-500: oklch(64.5% 0.246 16.439);
|
||||
--color-rose-600: oklch(58.6% 0.253 17.585);
|
||||
--color-slate-50: oklch(98.4% 0.003 247.858);
|
||||
--color-slate-950: oklch(12.9% 0.042 264.695);
|
||||
--color-gray-800: oklch(27.8% 0.033 256.848);
|
||||
--color-gray-900: oklch(21% 0.034 264.665);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: calc(1 / 0.75);
|
||||
--text-sm: 0.875rem;
|
||||
--text-sm--line-height: calc(1.25 / 0.875);
|
||||
--text-base: 1rem;
|
||||
--text-base--line-height: calc(1.5 / 1);
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: calc(1.75 / 1.125);
|
||||
--text-3xl: 1.875rem;
|
||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--tracking-wide: 0.025em;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--default-transition-duration: 150ms;
|
||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
.right-0 {
|
||||
right: calc(var(--spacing) * 0);
|
||||
}
|
||||
.bottom-full {
|
||||
bottom: 100%;
|
||||
}
|
||||
.m-0 {
|
||||
margin: calc(var(--spacing) * 0);
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: calc(var(--spacing) * 4);
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.aspect-square {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.size-8 {
|
||||
width: calc(var(--spacing) * 8);
|
||||
height: calc(var(--spacing) * 8);
|
||||
}
|
||||
.size-16 {
|
||||
width: calc(var(--spacing) * 16);
|
||||
height: calc(var(--spacing) * 16);
|
||||
}
|
||||
.size-full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.h-28 {
|
||||
height: calc(var(--spacing) * 28);
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.\!w-fit {
|
||||
width: fit-content !important;
|
||||
}
|
||||
.w-28 {
|
||||
width: calc(var(--spacing) * 28);
|
||||
}
|
||||
.w-fit {
|
||||
width: fit-content;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-\[1fr_2fr\] {
|
||||
grid-template-columns: 1fr 2fr;
|
||||
}
|
||||
.grid-cols-\[1fr_2fr_1fr\] {
|
||||
grid-template-columns: 1fr 2fr 1fr;
|
||||
}
|
||||
.grid-cols-\[2fr_1fr\] {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
.grid-cols-\[3fr_1fr_1fr\] {
|
||||
grid-template-columns: 3fr 1fr 1fr;
|
||||
}
|
||||
.grid-rows-3 {
|
||||
grid-template-rows: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.\!justify-start {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.justify-items-center {
|
||||
justify-items: center;
|
||||
}
|
||||
.gap-2 {
|
||||
gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.divide-x {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-inline-style: var(--tw-border-style);
|
||||
border-inline-start-width: calc(1px * var(--tw-divide-x-reverse));
|
||||
border-inline-end-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
}
|
||||
.rounded-full {
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
.\!rounded-tl-none {
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
.\!rounded-tr-none {
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
.\!rounded-b-none {
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
.border-3 {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 3px;
|
||||
}
|
||||
.border-lime-600 {
|
||||
border-color: var(--color-lime-600);
|
||||
}
|
||||
.border-red-500 {
|
||||
border-color: var(--color-red-500);
|
||||
}
|
||||
.border-red-600 {
|
||||
border-color: var(--color-red-600);
|
||||
}
|
||||
.border-sky-600 {
|
||||
border-color: var(--color-sky-600);
|
||||
}
|
||||
.border-white {
|
||||
border-color: var(--color-white);
|
||||
}
|
||||
.\!bg-sky-900 {
|
||||
background-color: var(--color-sky-900) !important;
|
||||
}
|
||||
.bg-lime-400 {
|
||||
background-color: var(--color-lime-400);
|
||||
}
|
||||
.bg-lime-400\/30 {
|
||||
background-color: color-mix(in srgb, oklch(84.1% 0.238 128.85) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-lime-400) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-400 {
|
||||
background-color: var(--color-red-400);
|
||||
}
|
||||
.bg-red-400\/30 {
|
||||
background-color: color-mix(in srgb, oklch(70.4% 0.191 22.216) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-400) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-500\/80 {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 80%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-500) 80%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-sky-400 {
|
||||
background-color: var(--color-sky-400);
|
||||
}
|
||||
.bg-sky-400\/30 {
|
||||
background-color: color-mix(in srgb, oklch(74.6% 0.16 232.661) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-sky-400) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-slate-950 {
|
||||
background-color: var(--color-slate-950);
|
||||
}
|
||||
.bg-white {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
.bg-white\/30 {
|
||||
background-color: color-mix(in srgb, #fff 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.\!p-0 {
|
||||
padding: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
.\!p-4 {
|
||||
padding: calc(var(--spacing) * 4) !important;
|
||||
}
|
||||
.p-2 {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
.\!px-2 {
|
||||
padding-inline: calc(var(--spacing) * 2) !important;
|
||||
}
|
||||
.\!pt-8 {
|
||||
padding-top: calc(var(--spacing) * 8) !important;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.text-base {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
}
|
||||
.text-xs {
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
}
|
||||
.\!text-gray-800 {
|
||||
color: var(--color-gray-800) !important;
|
||||
}
|
||||
.text-red-400 {
|
||||
color: var(--color-red-400);
|
||||
}
|
||||
.text-white {
|
||||
color: var(--color-white);
|
||||
}
|
||||
.opacity-80 {
|
||||
opacity: 80%;
|
||||
}
|
||||
.opacity-90 {
|
||||
opacity: 90%;
|
||||
}
|
||||
}
|
||||
@layer theme;
|
||||
html, body, #panel-html__body {
|
||||
position: relative;
|
||||
}
|
||||
#panel-html__body {
|
||||
--font-sans: "Orbitron", sans-serif;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-gray-900);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
--tw-font-weight: var(--font-weight-light);
|
||||
font-weight: var(--font-weight-light);
|
||||
--tw-tracking: var(--tracking-wide);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
color: var(--color-slate-50);
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.group-left, .group-middle, .group-right {
|
||||
display: grid;
|
||||
height: fit-content;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.ed-panel {
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
border-bottom-right-radius: var(--radius-xl);
|
||||
border-bottom-left-radius: var(--radius-xl);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
padding: calc(var(--spacing) * 2);
|
||||
h3, h4 {
|
||||
margin: calc(var(--spacing) * 0);
|
||||
margin-bottom: calc(var(--spacing) * 1);
|
||||
--tw-font-weight: var(--font-weight-extralight);
|
||||
font-weight: var(--font-weight-extralight);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
&.pnl__blue {
|
||||
border-color: var(--color-sky-300);
|
||||
background-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-sky-500) 30%, transparent);
|
||||
}
|
||||
h3, h4 {
|
||||
color: var(--color-sky-100);
|
||||
text-shadow: 0 0 0.2em var(--color-sky-300);
|
||||
}
|
||||
}
|
||||
&.pnl__yellow {
|
||||
border-color: var(--color-amber-300);
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 30%, transparent);
|
||||
}
|
||||
h3, h4 {
|
||||
color: var(--color-amber-100);
|
||||
text-shadow: 0 0 0.2em var(--color-amber-300);
|
||||
}
|
||||
}
|
||||
&.pnl__orange {
|
||||
border-color: var(--color-orange-300);
|
||||
background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-orange-500) 30%, transparent);
|
||||
}
|
||||
h3, h4 {
|
||||
color: var(--color-orange-100);
|
||||
text-shadow: 0 0 0.2em var(--color-orange-300);
|
||||
}
|
||||
}
|
||||
&.pnl__red {
|
||||
border-color: var(--color-rose-300);
|
||||
background-color: color-mix(in srgb, oklch(64.5% 0.246 16.439) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-rose-500) 30%, transparent);
|
||||
}
|
||||
h3, h4 {
|
||||
color: var(--color-rose-100);
|
||||
text-shadow: 0 0 0.2em var(--color-rose-400);
|
||||
}
|
||||
}
|
||||
&.pnl__white {
|
||||
border-color: var(--color-white);
|
||||
background-color: color-mix(in srgb, #fff 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 20%, transparent);
|
||||
}
|
||||
h3, h4 {
|
||||
color: var(--color-white);
|
||||
text-shadow: 0 0 0.2em var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ed-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-lg);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 3px;
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
text-align: center;
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
svg {
|
||||
display: block;
|
||||
width: calc(var(--spacing) * 5) !important;
|
||||
height: calc(var(--spacing) * 5) !important;
|
||||
}
|
||||
&.btn__vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
&.btn__filled {
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
&.btn__orange {
|
||||
border-color: var(--color-orange-400);
|
||||
background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-orange-500) 50%, transparent);
|
||||
}
|
||||
color: var(--color-orange-100);
|
||||
&.btn__filled {
|
||||
background-color: var(--color-orange-400);
|
||||
}
|
||||
}
|
||||
&.btn__yellow {
|
||||
border-color: var(--color-amber-400);
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 50%, transparent);
|
||||
}
|
||||
color: var(--color-orange-100);
|
||||
&.btn__filled {
|
||||
background-color: var(--color-amber-400);
|
||||
}
|
||||
}
|
||||
&.btn__blue {
|
||||
border-color: var(--color-sky-400);
|
||||
background-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-sky-500) 50%, transparent);
|
||||
}
|
||||
color: var(--color-sky-100);
|
||||
&.btn__filled {
|
||||
background-color: var(--color-sky-500);
|
||||
}
|
||||
}
|
||||
&.btn__red {
|
||||
border-color: var(--color-rose-500);
|
||||
background-color: color-mix(in srgb, oklch(58.6% 0.253 17.585) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-rose-600) 50%, transparent);
|
||||
}
|
||||
color: var(--color-rose-100);
|
||||
&.btn__filled {
|
||||
background-color: var(--color-rose-600);
|
||||
}
|
||||
}
|
||||
&.btn__white {
|
||||
border-color: var(--color-white);
|
||||
background-color: color-mix(in srgb, #fff 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 30%, transparent);
|
||||
}
|
||||
&.btn__filled {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ed-button-group__horizontal {
|
||||
display: grid;
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-inline-style: var(--tw-border-style);
|
||||
border-inline-start-width: calc(1px * var(--tw-divide-x-reverse));
|
||||
border-inline-end-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
.ed-button {
|
||||
border-radius: 0;
|
||||
&:first-child {
|
||||
border-top-left-radius: var(--radius-lg);
|
||||
border-bottom-left-radius: var(--radius-lg);
|
||||
}
|
||||
&:last-child {
|
||||
border-top-right-radius: var(--radius-lg);
|
||||
border-bottom-right-radius: var(--radius-lg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ed-button-group__vertical {
|
||||
display: grid;
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-bottom-style: var(--tw-border-style);
|
||||
border-top-style: var(--tw-border-style);
|
||||
border-top-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
}
|
||||
.ed-button {
|
||||
border-radius: 0;
|
||||
&:first-child {
|
||||
border-top-left-radius: var(--radius-lg);
|
||||
border-top-right-radius: var(--radius-lg);
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom-right-radius: var(--radius-lg);
|
||||
border-bottom-left-radius: var(--radius-lg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ed-toggle {
|
||||
.toggle__wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: calc(infinity * 1px);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 2px;
|
||||
padding: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.toggle__indicator {
|
||||
position: absolute;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: calc(infinity * 1px);
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
&.toggle__horizontal {
|
||||
height: calc(var(--spacing) * 12);
|
||||
width: calc(var(--spacing) * 20);
|
||||
.toggle__indicator {
|
||||
left: calc(var(--spacing) * 1.5);
|
||||
height: calc(100% - .75rem);
|
||||
--tw-translate-x: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[active] .toggle__indicator {
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
&.toggle__vertical {
|
||||
height: calc(var(--spacing) * 20);
|
||||
width: calc(var(--spacing) * 12);
|
||||
.toggle__indicator {
|
||||
top: calc(var(--spacing) * 1.5);
|
||||
width: calc(100% - .75rem);
|
||||
--tw-translate-y: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[active] .toggle__indicator {
|
||||
--tw-translate-y: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog[open] {
|
||||
position: absolute;
|
||||
top: calc(1/2 * 100%);
|
||||
left: calc(1/2 * 100%);
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 0px;
|
||||
background-color: transparent;
|
||||
outline-style: var(--tw-outline-style);
|
||||
outline-width: 0px;
|
||||
&::backdrop {
|
||||
background-color: color-mix(in srgb, #000 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.dialog__close {
|
||||
position: absolute;
|
||||
top: calc(var(--spacing) * 3);
|
||||
right: calc(var(--spacing) * 3);
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
#clock {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
padding-right: calc(var(--spacing) * 16);
|
||||
font-size: var(--text-3xl);
|
||||
line-height: var(--tw-leading, var(--text-3xl--line-height));
|
||||
i {
|
||||
padding-left: calc(var(--spacing) * 1);
|
||||
font-style: normal;
|
||||
}
|
||||
.hours-minutes, .seconds {
|
||||
display: flex;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
width: .75em;
|
||||
text-align: center;
|
||||
}
|
||||
sup {
|
||||
position: absolute;
|
||||
right: calc(var(--spacing) * 0);
|
||||
width: calc(var(--spacing) * 16);
|
||||
padding-left: calc(var(--spacing) * 2);
|
||||
text-align: left;
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--tw-leading, var(--text-lg--line-height));
|
||||
opacity: 80%;
|
||||
}
|
||||
}
|
||||
@property --tw-divide-x-reverse {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-border-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-font-weight {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-tracking {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-divide-y-reverse {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-translate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-translate-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-translate-z {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@layer properties {
|
||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||
*, ::before, ::after, ::backdrop {
|
||||
--tw-divide-x-reverse: 0;
|
||||
--tw-border-style: solid;
|
||||
--tw-font-weight: initial;
|
||||
--tw-tracking: initial;
|
||||
--tw-divide-y-reverse: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-translate-z: 0;
|
||||
--tw-outline-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
974
panels/Elite_Dangerous/package-lock.json
generated
Normal file
974
panels/Elite_Dangerous/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,974 @@
|
|||
{
|
||||
"name": "test_panel",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "test_panel",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.1.2",
|
||||
"tailwindcss": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/cli": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.4.tgz",
|
||||
"integrity": "sha512-gP05Qihh+cZ2FqD5fa0WJXx3KEk2YWUYv/RBKAyiOg0V4vYVDr/xlLc0sacpnVEXM45BVUR9U2hsESufYs6YTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@tailwindcss/node": "4.1.4",
|
||||
"@tailwindcss/oxide": "4.1.4",
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"mri": "^1.2.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"tailwindcss": "4.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"tailwindcss": "dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz",
|
||||
"integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"jiti": "^2.4.2",
|
||||
"lightningcss": "1.29.2",
|
||||
"tailwindcss": "4.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.4.tgz",
|
||||
"integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.4",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.4",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.4",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.4",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.4",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.4",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.4",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.4",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.4",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.4",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.4.tgz",
|
||||
"integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.4.tgz",
|
||||
"integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.4.tgz",
|
||||
"integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.4.tgz",
|
||||
"integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.4.tgz",
|
||||
"integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.4.tgz",
|
||||
"integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.4.tgz",
|
||||
"integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.4.tgz",
|
||||
"integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.4.tgz",
|
||||
"integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.4.tgz",
|
||||
"integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
"@emnapi/runtime",
|
||||
"@tybys/wasm-util",
|
||||
"@emnapi/wasi-threads",
|
||||
"tslib"
|
||||
],
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.4.0",
|
||||
"@emnapi/runtime": "^1.4.0",
|
||||
"@emnapi/wasi-threads": "^1.0.1",
|
||||
"@napi-rs/wasm-runtime": "^0.2.8",
|
||||
"@tybys/wasm-util": "^0.9.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.4.tgz",
|
||||
"integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.4.tgz",
|
||||
"integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
"integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-darwin-arm64": "1.29.2",
|
||||
"lightningcss-darwin-x64": "1.29.2",
|
||||
"lightningcss-freebsd-x64": "1.29.2",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.29.2",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.2",
|
||||
"lightningcss-linux-arm64-musl": "1.29.2",
|
||||
"lightningcss-linux-x64-gnu": "1.29.2",
|
||||
"lightningcss-linux-x64-musl": "1.29.2",
|
||||
"lightningcss-win32-arm64-msvc": "1.29.2",
|
||||
"lightningcss-win32-x64-msvc": "1.29.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
|
||||
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
|
||||
"integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
|
||||
"integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
|
||||
"integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
|
||||
"integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
|
||||
"integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
|
||||
"integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
|
||||
"integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
|
||||
"integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
|
||||
"integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss/node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",
|
||||
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
panels/Elite_Dangerous/package.json
Normal file
16
panels/Elite_Dangerous/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "test_panel",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "npx tailwindcss -i ./input.css -o ./output.css --watch"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.1.2",
|
||||
"tailwindcss": "^4.1.2"
|
||||
}
|
||||
}
|
||||
48
panels/Elite_Dangerous/panel.json
Normal file
48
panels/Elite_Dangerous/panel.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"dir": "",
|
||||
"name": "Elite Dangerous",
|
||||
"description": "A Semi Realistic button panel for Elite Dangerous, made by JaxxMoss.",
|
||||
"aspectRatio": "16/10",
|
||||
"macros": {
|
||||
"CM__Chaff": "ED-CM-Chaff",
|
||||
"CM__ECM": "ED-CM-ECM",
|
||||
"CM__Heatsink": "ED-CM-Heat_Sink",
|
||||
"CM__Shieldcell": "ED-CM-Schield_Cell",
|
||||
"Camera__Pos1": "ED-Camera-Position1",
|
||||
"Camera__Pos2": "ED-Camera-Position2",
|
||||
"Camera__Suite": "ED-Camera-Suite",
|
||||
"Comms__panel": "ED-Comms_Panel",
|
||||
"External__panel": "ED-External_Panel",
|
||||
"FSD__toggle": "ED-Toggle_FSD",
|
||||
"Fighter__engage": "ED-Fighter-Engage",
|
||||
"Fighter__hold": "ED-Fighter-Hold",
|
||||
"Fighter__recall": "ED-Fighter-Recall",
|
||||
"Fighter__wingman1": "ED-Fighter-Wingman1",
|
||||
"Fighter__wingman2": "ED-Fighter-Wingman2",
|
||||
"Fighter__wingman3": "ED-Fighter-Wingman3",
|
||||
"Fighter_attack": "ED-Fighter-Attack",
|
||||
"Fighter_defend": "ED-Fighter-Defend",
|
||||
"Fighter_follow": "ED-Fighter-Follow",
|
||||
"FlightAssist__toggle": "ED-Flight_Assist",
|
||||
"Free__Camera": "ED-Camera-Free",
|
||||
"Galaxy__map": "ED-Galaxy_Map",
|
||||
"Hardpoints__toggle": "ED-Hardpoints_Switch",
|
||||
"Hyper__Space": "ED-Hyperspace",
|
||||
"Internal__panel": "ED-Internal_Panel",
|
||||
"Jettison__Cargo": "ED-Jettison_Cargo",
|
||||
"Lights__toggle": "ED-Flight_Assist",
|
||||
"Mode__toggle": "ED-Mode_Switch",
|
||||
"Next__Camera": "ED-Camera-Next",
|
||||
"NightVis__toggle": "ED-Night_Vision",
|
||||
"Previous__Camera": "ED-Camera-Previous",
|
||||
"Roles__panel": "ED-Roles_Panel",
|
||||
"Rotational__Correction": "ED-Rotational_Correction",
|
||||
"Route__NextSystem": "ED-Route_Next_System",
|
||||
"Scanner__DiscScan": "ED-Scanner_Discovery",
|
||||
"Scanner__FSS": "ED-Scanner-FSS",
|
||||
"SilentRunning__toggle": "ED-Silent_Running",
|
||||
"Speed_0percent": "ED-0percent_Speed",
|
||||
"Super__Cruise": "ED-Super_Cruise",
|
||||
"System__map": "ED-System_Map"
|
||||
}
|
||||
}
|
||||
17
panels/Elite_Dangerous/tailwind.config.js
Normal file
17
panels/Elite_Dangerous/tailwind.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./index.html", // Ensure this path is correct
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
safelist: [
|
||||
{
|
||||
pattern: /^(border|bg)-(blue|red|sky|orange|lime)-(200|400|300\/40)$/,
|
||||
},
|
||||
],
|
||||
plugins: [],
|
||||
preflight: false,
|
||||
mode: "jit",
|
||||
};
|
||||
127
panels/test_panel/index.html
Normal file
127
panels/test_panel/index.html
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="./output.css" />
|
||||
</head>
|
||||
<body class="bg-slate-400 w-screen h-screen m-0 aspect-[9/20]">
|
||||
<div class="h-full bg-slate-500">
|
||||
<div class="grid grid-cols-2 gap-2 size-full grid-rows-8">
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_1"
|
||||
mcrm__button
|
||||
>
|
||||
Task manager
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_2"
|
||||
mcrm__button
|
||||
>
|
||||
Close application
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_3"
|
||||
mcrm__button
|
||||
>
|
||||
Run
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_4"
|
||||
mcrm__button
|
||||
>
|
||||
Files
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_5"
|
||||
mcrm__button
|
||||
>
|
||||
Settings
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_6"
|
||||
mcrm__button
|
||||
>
|
||||
New Desktop
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_7"
|
||||
mcrm__button
|
||||
>
|
||||
Displays
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-sky-400"
|
||||
id="button_8"
|
||||
mcrm__button
|
||||
>
|
||||
Task View
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_9"
|
||||
mcrm__button
|
||||
>
|
||||
New Window
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_10"
|
||||
mcrm__button
|
||||
>
|
||||
Close Window
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_11"
|
||||
mcrm__button
|
||||
>
|
||||
Previous Tab
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_12"
|
||||
mcrm__button
|
||||
>
|
||||
Next Tab
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_13"
|
||||
mcrm__button
|
||||
>
|
||||
Close Tab
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_14"
|
||||
mcrm__button
|
||||
>
|
||||
New Tab
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_15"
|
||||
mcrm__button
|
||||
>
|
||||
Fullscreen
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center bg-rose-400"
|
||||
id="button_16"
|
||||
mcrm__button
|
||||
>
|
||||
Home
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
6
panels/test_panel/input.css
Normal file
6
panels/test_panel/input.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@layer theme, utilities;
|
||||
@import "tailwindcss/theme.css" layer(theme);
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
69
panels/test_panel/output.css
Normal file
69
panels/test_panel/output.css
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*! tailwindcss v4.1.2 | MIT License | https://tailwindcss.com */
|
||||
@layer theme, utilities;
|
||||
@layer theme {
|
||||
:root, :host {
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-sky-400: oklch(74.6% 0.16 232.661);
|
||||
--color-rose-400: oklch(71.2% 0.194 13.428);
|
||||
--color-slate-400: oklch(70.4% 0.04 256.788);
|
||||
--color-slate-500: oklch(55.4% 0.046 257.417);
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.m-0 {
|
||||
margin: calc(var(--spacing) * 0);
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.aspect-\[9\/20\] {
|
||||
aspect-ratio: 9/20;
|
||||
}
|
||||
.size-full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
.w-screen {
|
||||
width: 100vw;
|
||||
}
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.grid-rows-8 {
|
||||
grid-template-rows: repeat(8, minmax(0, 1fr));
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.gap-2 {
|
||||
gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.bg-rose-400 {
|
||||
background-color: var(--color-rose-400);
|
||||
}
|
||||
.bg-sky-400 {
|
||||
background-color: var(--color-sky-400);
|
||||
}
|
||||
.bg-slate-400 {
|
||||
background-color: var(--color-slate-400);
|
||||
}
|
||||
.bg-slate-500 {
|
||||
background-color: var(--color-slate-500);
|
||||
}
|
||||
}
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue