Simplify web interface to execute-only, improve desktop editor UX
## Web Interface - Remove Add/Edit functionality from web interface (execute-only now) - Remove modal dialog and command builder - Simplified JS from 480 to 267 lines - Users can still create/edit macros in the desktop app ## Desktop Editor - Fix Edit button padding (set fixed width of 50px) - Capitalize key options for better readability (Enter, Tab, etc.) - Display keys capitalized in command list 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,7 @@ class CommandItem(QWidget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
edit_btn = QPushButton("Edit")
|
edit_btn = QPushButton("Edit")
|
||||||
|
edit_btn.setFixedWidth(50)
|
||||||
edit_btn.setStyleSheet(btn_style)
|
edit_btn.setStyleSheet(btn_style)
|
||||||
edit_btn.clicked.connect(self.edit_clicked.emit)
|
edit_btn.clicked.connect(self.edit_clicked.emit)
|
||||||
layout.addWidget(edit_btn)
|
layout.addWidget(edit_btn)
|
||||||
@@ -104,9 +105,11 @@ class CommandItem(QWidget):
|
|||||||
if cmd_type == "text":
|
if cmd_type == "text":
|
||||||
return self.command.get("value", "")[:50]
|
return self.command.get("value", "")[:50]
|
||||||
elif cmd_type == "key":
|
elif cmd_type == "key":
|
||||||
return self.command.get("value", "")
|
key = self.command.get("value", "")
|
||||||
|
return key.capitalize() if key else ""
|
||||||
elif cmd_type == "hotkey":
|
elif cmd_type == "hotkey":
|
||||||
return " + ".join(self.command.get("keys", []))
|
keys = self.command.get("keys", [])
|
||||||
|
return " + ".join(k.capitalize() for k in keys)
|
||||||
elif cmd_type == "wait":
|
elif cmd_type == "wait":
|
||||||
return f"{self.command.get('ms', 0)}ms"
|
return f"{self.command.get('ms', 0)}ms"
|
||||||
elif cmd_type == "app":
|
elif cmd_type == "app":
|
||||||
@@ -207,9 +210,9 @@ class CommandBuilder(QWidget):
|
|||||||
|
|
||||||
elif cmd_type == "key":
|
elif cmd_type == "key":
|
||||||
from PySide6.QtWidgets import QInputDialog
|
from PySide6.QtWidgets import QInputDialog
|
||||||
keys = ["enter", "tab", "escape", "space", "backspace", "delete",
|
keys = ["Enter", "Tab", "Escape", "Space", "Backspace", "Delete",
|
||||||
"up", "down", "left", "right", "home", "end", "pageup", "pagedown",
|
"Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown",
|
||||||
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12"]
|
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"]
|
||||||
key, ok = QInputDialog.getItem(self, "Key Command", "Select key:", keys, 0, True)
|
key, ok = QInputDialog.getItem(self, "Key Command", "Select key:", keys, 0, True)
|
||||||
if not ok or not key:
|
if not ok or not key:
|
||||||
return
|
return
|
||||||
@@ -282,10 +285,11 @@ class CommandBuilder(QWidget):
|
|||||||
cmd["value"] = text
|
cmd["value"] = text
|
||||||
|
|
||||||
elif cmd_type == "key":
|
elif cmd_type == "key":
|
||||||
keys = ["enter", "tab", "escape", "space", "backspace", "delete",
|
keys = ["Enter", "Tab", "Escape", "Space", "Backspace", "Delete",
|
||||||
"up", "down", "left", "right", "home", "end", "pageup", "pagedown",
|
"Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown",
|
||||||
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12"]
|
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"]
|
||||||
current = keys.index(cmd.get("value", "enter")) if cmd.get("value") in keys else 0
|
keys_lower = [k.lower() for k in keys]
|
||||||
|
current = keys_lower.index(cmd.get("value", "enter")) if cmd.get("value") in keys_lower else 0
|
||||||
key, ok = QInputDialog.getItem(self, "Edit Key", "Select key:", keys, current, True)
|
key, ok = QInputDialog.getItem(self, "Edit Key", "Select key:", keys, current, True)
|
||||||
if ok and key:
|
if ok and key:
|
||||||
cmd["value"] = key.lower()
|
cmd["value"] = key.lower()
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
<span>Disconnected</span>
|
<span>Disconnected</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="header-btn secondary" onclick="app.refresh()">Refresh</button>
|
<button class="header-btn secondary" onclick="app.refresh()">Refresh</button>
|
||||||
<button class="header-btn" onclick="app.openAddModal()">+ Add</button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -42,47 +41,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal-overlay" id="modal-overlay" style="display: none;">
|
|
||||||
<div class="modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 id="modal-title">Add Macro</h2>
|
|
||||||
<button class="modal-close" onclick="app.closeModal()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="macro-name">Name</label>
|
|
||||||
<input type="text" id="macro-name" placeholder="Macro name">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="macro-category">Category (optional)</label>
|
|
||||||
<input type="text" id="macro-category" placeholder="Category">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Commands</label>
|
|
||||||
<div class="command-list" id="command-list">
|
|
||||||
<div class="empty-state"><p>No commands added yet</p></div>
|
|
||||||
</div>
|
|
||||||
<div class="add-command-btns">
|
|
||||||
<button class="add-command-btn" onclick="app.addCommand('text')">+ Text</button>
|
|
||||||
<button class="add-command-btn" onclick="app.addCommand('key')">+ Key</button>
|
|
||||||
<button class="add-command-btn" onclick="app.addCommand('hotkey')">+ Hotkey</button>
|
|
||||||
<button class="add-command-btn" onclick="app.addCommand('wait')">+ Wait</button>
|
|
||||||
<button class="add-command-btn" onclick="app.addCommand('app')">+ App</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-danger" id="delete-btn" style="display: none;"
|
|
||||||
onclick="app.deleteMacro(app.editingMacroId); app.closeModal();">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" onclick="app.closeModal()">Cancel</button>
|
|
||||||
<button class="btn btn-primary" onclick="app.saveMacro()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toast Container -->
|
<!-- Toast Container -->
|
||||||
<div class="toast-container" id="toast-container"></div>
|
<div class="toast-container" id="toast-container"></div>
|
||||||
|
|
||||||
|
|||||||
218
web/js/app.js
218
web/js/app.js
@@ -1,4 +1,4 @@
|
|||||||
// MacroPad PWA Application
|
// MacroPad PWA Application (Execute-only)
|
||||||
|
|
||||||
class MacroPadApp {
|
class MacroPadApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -6,8 +6,6 @@ class MacroPadApp {
|
|||||||
this.tabs = [];
|
this.tabs = [];
|
||||||
this.currentTab = 'All';
|
this.currentTab = 'All';
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
this.editingMacroId = null;
|
|
||||||
this.commands = [];
|
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -70,73 +68,6 @@ class MacroPadApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveMacro() {
|
|
||||||
const name = document.getElementById('macro-name').value.trim();
|
|
||||||
const category = document.getElementById('macro-category').value.trim();
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
this.showToast('Please enter a macro name', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.commands.length === 0) {
|
|
||||||
this.showToast('Please add at least one command', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const macroData = {
|
|
||||||
name,
|
|
||||||
category,
|
|
||||||
commands: this.commands
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
let response;
|
|
||||||
if (this.editingMacroId) {
|
|
||||||
response = await fetch(`/api/macros/${this.editingMacroId}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(macroData)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await fetch('/api/macros', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(macroData)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Save failed');
|
|
||||||
|
|
||||||
this.closeModal();
|
|
||||||
await this.loadTabs();
|
|
||||||
await this.loadMacros();
|
|
||||||
this.showToast('Macro saved successfully', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving macro:', error);
|
|
||||||
this.showToast('Error saving macro', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMacro(macroId) {
|
|
||||||
if (!confirm('Are you sure you want to delete this macro?')) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/macros/${macroId}`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Delete failed');
|
|
||||||
|
|
||||||
await this.loadTabs();
|
|
||||||
await this.loadMacros();
|
|
||||||
this.showToast('Macro deleted', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting macro:', error);
|
|
||||||
this.showToast('Error deleting macro', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket
|
// WebSocket
|
||||||
setupWebSocket() {
|
setupWebSocket() {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
@@ -217,9 +148,7 @@ class MacroPadApp {
|
|||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>No macros found</p>
|
<p>No macros found</p>
|
||||||
<button class="btn btn-primary" onclick="app.openAddModal()">
|
<p class="hint">Create macros in the desktop app</p>
|
||||||
Add Your First Macro
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
@@ -233,9 +162,6 @@ class MacroPadApp {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="macro-card" data-macro-id="${id}" onclick="app.executeMacro('${id}')">
|
<div class="macro-card" data-macro-id="${id}" onclick="app.executeMacro('${id}')">
|
||||||
<button class="macro-edit-btn" onclick="event.stopPropagation(); app.openEditModal('${id}')">
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
${imageSrc
|
${imageSrc
|
||||||
? `<img src="${imageSrc}" alt="${macro.name}" class="macro-image" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">`
|
? `<img src="${imageSrc}" alt="${macro.name}" class="macro-image" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">`
|
||||||
: ''
|
: ''
|
||||||
@@ -249,132 +175,6 @@ class MacroPadApp {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCommandList() {
|
|
||||||
const container = document.getElementById('command-list');
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
if (this.commands.length === 0) {
|
|
||||||
container.innerHTML = '<div class="empty-state"><p>No commands added yet</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.innerHTML = this.commands.map((cmd, index) => {
|
|
||||||
let displayValue = '';
|
|
||||||
switch (cmd.type) {
|
|
||||||
case 'text':
|
|
||||||
displayValue = cmd.value || '';
|
|
||||||
break;
|
|
||||||
case 'key':
|
|
||||||
displayValue = cmd.value || '';
|
|
||||||
break;
|
|
||||||
case 'hotkey':
|
|
||||||
displayValue = (cmd.keys || []).join(' + ');
|
|
||||||
break;
|
|
||||||
case 'wait':
|
|
||||||
displayValue = `${cmd.ms || 0}ms`;
|
|
||||||
break;
|
|
||||||
case 'app':
|
|
||||||
displayValue = cmd.command || '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="command-item" data-index="${index}">
|
|
||||||
<span class="command-type">${cmd.type}</span>
|
|
||||||
<span class="command-value">${displayValue}</span>
|
|
||||||
<div class="command-actions">
|
|
||||||
<button onclick="app.moveCommand(${index}, -1)">Up</button>
|
|
||||||
<button onclick="app.moveCommand(${index}, 1)">Down</button>
|
|
||||||
<button class="delete" onclick="app.removeCommand(${index})">X</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command Builder
|
|
||||||
addCommand(type) {
|
|
||||||
let command = { type };
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'text':
|
|
||||||
const text = prompt('Enter text to type:');
|
|
||||||
if (!text) return;
|
|
||||||
command.value = text;
|
|
||||||
break;
|
|
||||||
case 'key':
|
|
||||||
const key = prompt('Enter key to press (e.g., enter, tab, escape, space):');
|
|
||||||
if (!key) return;
|
|
||||||
command.value = key.toLowerCase();
|
|
||||||
break;
|
|
||||||
case 'hotkey':
|
|
||||||
const keys = prompt('Enter key combination (comma separated, e.g., ctrl,c):');
|
|
||||||
if (!keys) return;
|
|
||||||
command.keys = keys.split(',').map(k => k.trim().toLowerCase());
|
|
||||||
break;
|
|
||||||
case 'wait':
|
|
||||||
const ms = prompt('Enter delay in milliseconds:');
|
|
||||||
if (!ms) return;
|
|
||||||
command.ms = parseInt(ms, 10) || 0;
|
|
||||||
break;
|
|
||||||
case 'app':
|
|
||||||
const appCmd = prompt('Enter application command:');
|
|
||||||
if (!appCmd) return;
|
|
||||||
command.command = appCmd;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.commands.push(command);
|
|
||||||
this.renderCommandList();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCommand(index) {
|
|
||||||
this.commands.splice(index, 1);
|
|
||||||
this.renderCommandList();
|
|
||||||
}
|
|
||||||
|
|
||||||
moveCommand(index, direction) {
|
|
||||||
const newIndex = index + direction;
|
|
||||||
if (newIndex < 0 || newIndex >= this.commands.length) return;
|
|
||||||
|
|
||||||
[this.commands[index], this.commands[newIndex]] =
|
|
||||||
[this.commands[newIndex], this.commands[index]];
|
|
||||||
this.renderCommandList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal
|
|
||||||
openAddModal() {
|
|
||||||
this.editingMacroId = null;
|
|
||||||
this.commands = [];
|
|
||||||
document.getElementById('macro-name').value = '';
|
|
||||||
document.getElementById('macro-category').value = '';
|
|
||||||
document.getElementById('modal-title').textContent = 'Add Macro';
|
|
||||||
document.getElementById('delete-btn').style.display = 'none';
|
|
||||||
this.renderCommandList();
|
|
||||||
document.getElementById('modal-overlay').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
async openEditModal(macroId) {
|
|
||||||
this.editingMacroId = macroId;
|
|
||||||
const macro = this.macros[macroId];
|
|
||||||
if (!macro) return;
|
|
||||||
|
|
||||||
document.getElementById('macro-name').value = macro.name || '';
|
|
||||||
document.getElementById('macro-category').value = macro.category || '';
|
|
||||||
document.getElementById('modal-title').textContent = 'Edit Macro';
|
|
||||||
document.getElementById('delete-btn').style.display = 'block';
|
|
||||||
|
|
||||||
this.commands = JSON.parse(JSON.stringify(macro.commands || []));
|
|
||||||
this.renderCommandList();
|
|
||||||
document.getElementById('modal-overlay').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal() {
|
|
||||||
document.getElementById('modal-overlay').style.display = 'none';
|
|
||||||
this.editingMacroId = null;
|
|
||||||
this.commands = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event Listeners
|
// Event Listeners
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
// Tab clicks
|
// Tab clicks
|
||||||
@@ -385,20 +185,6 @@ class MacroPadApp {
|
|||||||
this.loadMacros();
|
this.loadMacros();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal close on overlay click
|
|
||||||
document.getElementById('modal-overlay')?.addEventListener('click', (e) => {
|
|
||||||
if (e.target.id === 'modal-overlay') {
|
|
||||||
this.closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Escape key to close modal
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
this.closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toast notifications
|
// Toast notifications
|
||||||
|
|||||||
Reference in New Issue
Block a user