PoC: flat and advanced mode
This commit is contained in:
536
static/css/style.css
Normal file
536
static/css/style.css
Normal file
@@ -0,0 +1,536 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #2d2d2d;
|
||||
--bg-tertiary: #3a3a3a;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-secondary: #a0a0a0;
|
||||
--accent: #4a9eff;
|
||||
--accent-hover: #3a8eef;
|
||||
--border: #444;
|
||||
--success: #4caf50;
|
||||
--danger: #f44336;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header-input {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header-input label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.header-input input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.header-input input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.arguments-section h2 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.argument-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr 3fr 60px;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 2px solid var(--border);
|
||||
margin-bottom: 15px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.argument-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr 3fr 60px;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.argument-row input {
|
||||
padding: 10px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.argument-row input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.argument-row input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.remove-btn:hover {
|
||||
background: var(--danger);
|
||||
border-color: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.output-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.output-section h2 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.code-container {
|
||||
position: relative;
|
||||
background: #1f1f1f;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 8px 16px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.copy-btn.copied {
|
||||
background: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.code-container pre {
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-container code {
|
||||
color: rgb(131, 177, 131);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.help-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.help-section h2 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.help-preview {
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.help-preview pre {
|
||||
color: #0f0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Mode Toggle */
|
||||
.mode-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 10px 30px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 2px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Global Section */
|
||||
.global-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 3px solid var(--success);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Subcommands */
|
||||
.subcommands-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.subcommand-section {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.subcommand-section.collapsed .subcommand-body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subcommand-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 3fr auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.subcommand-name-input,
|
||||
.subcommand-desc-input {
|
||||
padding: 10px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.subcommand-name-input {
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.toggle-collapse-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.toggle-collapse-btn::before {
|
||||
content: '▼';
|
||||
}
|
||||
|
||||
.subcommand-section.collapsed .toggle-collapse-btn::before {
|
||||
content: '▶';
|
||||
}
|
||||
|
||||
.add-subcommand-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: transparent;
|
||||
border: 2px dashed var(--border);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.add-subcommand-btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Mode-specific visibility */
|
||||
.flat-mode .subcommands-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flat-mode .global-section {
|
||||
border-left-color: var(--accent);
|
||||
}
|
||||
|
||||
.mode-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 10px 30px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 2px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.global-section {
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 3px solid var(--success);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.subcommands-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.subcommand-section {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.subcommand-section.collapsed .subcommand-body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subcommand-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 3fr auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.subcommand-name-input,
|
||||
.subcommand-desc-input {
|
||||
padding: 10px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.subcommand-name-input {
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.toggle-collapse-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.toggle-collapse-btn:hover {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.subcommand-section.collapsed .toggle-collapse-btn::before {
|
||||
content: '▶';
|
||||
}
|
||||
|
||||
.subcommand-section:not(.collapsed) .toggle-collapse-btn::before {
|
||||
content: '▼';
|
||||
}
|
||||
|
||||
.add-subcommand-btn {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: transparent;
|
||||
border: 2px dashed var(--border);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.add-subcommand-btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
background: rgba(74, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
.subcommand-body {
|
||||
animation: slideDown 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.flat-mode .subcommands-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subcommand-mode .global-section .section-title::after {
|
||||
content: '(Optional)';
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.argument-header,
|
||||
.argument-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.argument-header span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
234
static/js/app.js
Normal file
234
static/js/app.js
Normal file
@@ -0,0 +1,234 @@
|
||||
class ArgumentBuilder {
|
||||
constructor() {
|
||||
this.arguments = [];
|
||||
this.container = document.getElementById('arguments-container');
|
||||
this.headerInput = document.getElementById('custom-header');
|
||||
this.generateBtn = document.getElementById('generate-btn');
|
||||
this.helpContent = document.getElementById('help-content');
|
||||
this.outputSection = document.getElementById('output-section');
|
||||
this.generatedCode = document.getElementById('generated-code');
|
||||
this.copyBtn = document.getElementById('copy-btn');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Add initial empty row
|
||||
this.addArgumentRow();
|
||||
|
||||
// Event listeners
|
||||
this.generateBtn.addEventListener('click', () => this.generateScript());
|
||||
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
|
||||
this.copyBtn.addEventListener('click', () => this.copyToClipboard());
|
||||
|
||||
// Update help preview initially
|
||||
this.updateHelpPreview();
|
||||
}
|
||||
|
||||
addArgumentRow(params = '', command = '', helpText = '') {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'argument-row';
|
||||
|
||||
const paramsInput = document.createElement('input');
|
||||
paramsInput.type = 'text';
|
||||
paramsInput.placeholder = '-v --verbose';
|
||||
paramsInput.value = params;
|
||||
|
||||
const commandInput = document.createElement('input');
|
||||
commandInput.type = 'text';
|
||||
commandInput.placeholder = 'verbose';
|
||||
commandInput.value = command;
|
||||
|
||||
const helpTextInput = document.createElement('input');
|
||||
helpTextInput.type = 'text';
|
||||
helpTextInput.placeholder = 'Enable verbose output';
|
||||
helpTextInput.value = helpText;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'remove-btn';
|
||||
removeBtn.innerHTML = '×';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
row.remove();
|
||||
this.updateHelpPreview();
|
||||
});
|
||||
|
||||
// Add input event listeners for real-time updates
|
||||
const updatePreview = () => {
|
||||
this.updateHelpPreview();
|
||||
// Add new empty row if this is the last row and has content
|
||||
const rows = this.container.querySelectorAll('.argument-row');
|
||||
const isLastRow = rows[rows.length - 1] === row;
|
||||
const hasContent = commandInput.value.trim() !== '';
|
||||
|
||||
if (isLastRow && hasContent) {
|
||||
this.addArgumentRow();
|
||||
}
|
||||
};
|
||||
|
||||
paramsInput.addEventListener('input', updatePreview);
|
||||
commandInput.addEventListener('input', updatePreview);
|
||||
helpTextInput.addEventListener('input', updatePreview);
|
||||
|
||||
row.appendChild(paramsInput);
|
||||
row.appendChild(commandInput);
|
||||
row.appendChild(helpTextInput);
|
||||
row.appendChild(removeBtn);
|
||||
|
||||
this.container.appendChild(row);
|
||||
}
|
||||
|
||||
getArguments() {
|
||||
const rows = this.container.querySelectorAll('.argument-row');
|
||||
const args = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
const inputs = row.querySelectorAll('input');
|
||||
const params = inputs[0].value.trim();
|
||||
const command = inputs[1].value.trim();
|
||||
const helpText = inputs[2].value.trim();
|
||||
|
||||
if (command) {
|
||||
args.push({
|
||||
params: params || command,
|
||||
command: command,
|
||||
helpText: helpText || command
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
addSubcommand(name = '', description = '') {
|
||||
const subcommandSection = document.createElement('div');
|
||||
subcommandSection.className = 'subcommand-section';
|
||||
|
||||
const header = this.createSubcommandHeader(name, description);
|
||||
const argsContainer = this.createArgumentsContainer();
|
||||
|
||||
subcommandSection.appendChild(header);
|
||||
subcommandSection.appendChild(argsContainer);
|
||||
|
||||
this.subcommandContainer.appendChild(subcommandSection);
|
||||
|
||||
return subcommandSection;
|
||||
}
|
||||
|
||||
updateHelpPreview() {
|
||||
const header = this.headerInput.value.trim();
|
||||
const args = this.getArguments();
|
||||
|
||||
if (args.length === 0) {
|
||||
this.helpContent.textContent = 'Add arguments to see the help output...';
|
||||
return;
|
||||
}
|
||||
|
||||
let helpText = '';
|
||||
|
||||
if (header) {
|
||||
helpText += header + '\n\n';
|
||||
}
|
||||
|
||||
helpText += 'Usage: script.sh [OPTIONS]\n\n';
|
||||
helpText += 'Options:\n';
|
||||
|
||||
args.forEach(arg => {
|
||||
const params = arg.params || arg.command;
|
||||
const help = arg.helpText || arg.command;
|
||||
helpText += ` ${params.padEnd(20)} ${help}\n`;
|
||||
});
|
||||
|
||||
helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`;
|
||||
|
||||
this.helpContent.textContent = helpText;
|
||||
}
|
||||
createSubcommandHeader(name, description) {
|
||||
const header = document.createElement('div');
|
||||
header.className = 'subcommand-header';
|
||||
header.innerHTML = `
|
||||
<input type="text" class="subcommand-name"
|
||||
placeholder="container" value="${name}">
|
||||
<input type="text" class="subcommand-desc"
|
||||
placeholder="Manage containers" value="${description}">
|
||||
<button class="toggle-btn">▼</button>
|
||||
<button class="remove-btn">×</button>
|
||||
`;
|
||||
return header;
|
||||
}
|
||||
|
||||
getSubcommands() {
|
||||
const sections = this.subcommandContainer.querySelectorAll('.subcommand-section');
|
||||
return Array.from(sections).map(section => {
|
||||
const nameInput = section.querySelector('.subcommand-name');
|
||||
const descInput = section.querySelector('.subcommand-desc');
|
||||
const args = this.getArgumentsFromSection(section);
|
||||
|
||||
return {
|
||||
name: nameInput.value.trim(),
|
||||
description: descInput.value.trim(),
|
||||
arguments: args
|
||||
};
|
||||
}).filter(sc => sc.name);
|
||||
}
|
||||
|
||||
async generateScript() {
|
||||
const header = this.headerInput.value.trim();
|
||||
const args = this.getArguments();
|
||||
|
||||
if (args.length === 0) {
|
||||
alert('Please add at least one argument');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
header: this.headerInput.value.trim(),
|
||||
arguments: this.getArguments(),
|
||||
subcommands: this.getSubcommands()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/generate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate script');
|
||||
}
|
||||
|
||||
const scriptCode = await response.text();
|
||||
this.generatedCode.textContent = scriptCode;
|
||||
this.outputSection.style.display = 'block';
|
||||
|
||||
// Smooth scroll to output
|
||||
this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
} catch (error) {
|
||||
alert('Error generating script: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async copyToClipboard() {
|
||||
const code = this.generatedCode.textContent;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
this.copyBtn.textContent = 'Copied!';
|
||||
this.copyBtn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
this.copyBtn.textContent = 'Copy';
|
||||
this.copyBtn.classList.remove('copied');
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
alert('Failed to copy to clipboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ArgumentBuilder();
|
||||
});
|
234
static/js/v2.js
Normal file
234
static/js/v2.js
Normal file
@@ -0,0 +1,234 @@
|
||||
class ArgumentBuilder {
|
||||
constructor() {
|
||||
this.arguments = [];
|
||||
this.container = document.getElementById('arguments-container');
|
||||
this.headerInput = document.getElementById('custom-header');
|
||||
this.generateBtn = document.getElementById('generate-btn');
|
||||
this.helpContent = document.getElementById('help-content');
|
||||
this.outputSection = document.getElementById('output-section');
|
||||
this.generatedCode = document.getElementById('generated-code');
|
||||
this.copyBtn = document.getElementById('copy-btn');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Add initial empty row
|
||||
this.addArgumentRow();
|
||||
|
||||
// Event listeners
|
||||
this.generateBtn.addEventListener('click', () => this.generateScript());
|
||||
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
|
||||
this.copyBtn.addEventListener('click', () => this.copyToClipboard());
|
||||
|
||||
// Update help preview initially
|
||||
this.updateHelpPreview();
|
||||
}
|
||||
|
||||
addArgumentRow(params = '', command = '', helpText = '') {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'argument-row';
|
||||
|
||||
const paramsInput = document.createElement('input');
|
||||
paramsInput.type = 'text';
|
||||
paramsInput.placeholder = '-v --verbose';
|
||||
paramsInput.value = params;
|
||||
|
||||
const commandInput = document.createElement('input');
|
||||
commandInput.type = 'text';
|
||||
commandInput.placeholder = 'verbose';
|
||||
commandInput.value = command;
|
||||
|
||||
const helpTextInput = document.createElement('input');
|
||||
helpTextInput.type = 'text';
|
||||
helpTextInput.placeholder = 'Enable verbose output';
|
||||
helpTextInput.value = helpText;
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'remove-btn';
|
||||
removeBtn.innerHTML = '×';
|
||||
removeBtn.addEventListener('click', () => {
|
||||
row.remove();
|
||||
this.updateHelpPreview();
|
||||
});
|
||||
|
||||
// Add input event listeners for real-time updates
|
||||
const updatePreview = () => {
|
||||
this.updateHelpPreview();
|
||||
// Add new empty row if this is the last row and has content
|
||||
const rows = this.container.querySelectorAll('.argument-row');
|
||||
const isLastRow = rows[rows.length - 1] === row;
|
||||
const hasContent = commandInput.value.trim() !== '';
|
||||
|
||||
if (isLastRow && hasContent) {
|
||||
this.addArgumentRow();
|
||||
}
|
||||
};
|
||||
|
||||
paramsInput.addEventListener('input', updatePreview);
|
||||
commandInput.addEventListener('input', updatePreview);
|
||||
helpTextInput.addEventListener('input', updatePreview);
|
||||
|
||||
row.appendChild(paramsInput);
|
||||
row.appendChild(commandInput);
|
||||
row.appendChild(helpTextInput);
|
||||
row.appendChild(removeBtn);
|
||||
|
||||
this.container.appendChild(row);
|
||||
}
|
||||
|
||||
getArguments() {
|
||||
const rows = this.container.querySelectorAll('.argument-row');
|
||||
const args = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
const inputs = row.querySelectorAll('input');
|
||||
const params = inputs[0].value.trim();
|
||||
const command = inputs[1].value.trim();
|
||||
const helpText = inputs[2].value.trim();
|
||||
|
||||
if (command) {
|
||||
args.push({
|
||||
params: params || command,
|
||||
command: command,
|
||||
helpText: helpText || command
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
addSubcommand(name = '', description = '') {
|
||||
const subcommandSection = document.createElement('div');
|
||||
subcommandSection.className = 'subcommand-section';
|
||||
|
||||
const header = this.createSubcommandHeader(name, description);
|
||||
const argsContainer = this.createArgumentsContainer();
|
||||
|
||||
subcommandSection.appendChild(header);
|
||||
subcommandSection.appendChild(argsContainer);
|
||||
|
||||
this.subcommandContainer.appendChild(subcommandSection);
|
||||
|
||||
return subcommandSection;
|
||||
}
|
||||
|
||||
updateHelpPreview() {
|
||||
const header = this.headerInput.value.trim();
|
||||
const args = this.getArguments();
|
||||
|
||||
if (args.length === 0) {
|
||||
this.helpContent.textContent = 'Add arguments to see the help output...';
|
||||
return;
|
||||
}
|
||||
|
||||
let helpText = '';
|
||||
|
||||
if (header) {
|
||||
helpText += header + '\n\n';
|
||||
}
|
||||
|
||||
helpText += 'Usage: script.sh [OPTIONS]\n\n';
|
||||
helpText += 'Options:\n';
|
||||
|
||||
args.forEach(arg => {
|
||||
const params = arg.params || arg.command;
|
||||
const help = arg.helpText || arg.command;
|
||||
helpText += ` ${params.padEnd(20)} ${help}\n`;
|
||||
});
|
||||
|
||||
helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`;
|
||||
|
||||
this.helpContent.textContent = helpText;
|
||||
}
|
||||
createSubcommandHeader(name, description) {
|
||||
const header = document.createElement('div');
|
||||
header.className = 'subcommand-header';
|
||||
header.innerHTML = `
|
||||
<input type="text" class="subcommand-name"
|
||||
placeholder="container" value="${name}">
|
||||
<input type="text" class="subcommand-desc"
|
||||
placeholder="Manage containers" value="${description}">
|
||||
<button class="toggle-btn">▼</button>
|
||||
<button class="remove-btn">×</button>
|
||||
`;
|
||||
return header;
|
||||
}
|
||||
|
||||
getSubcommands() {
|
||||
const sections = this.subcommandContainer.querySelectorAll('.subcommand-section');
|
||||
return Array.from(sections).map(section => {
|
||||
const nameInput = section.querySelector('.subcommand-name');
|
||||
const descInput = section.querySelector('.subcommand-desc');
|
||||
const args = this.getArgumentsFromSection(section);
|
||||
|
||||
return {
|
||||
name: nameInput.value.trim(),
|
||||
description: descInput.value.trim(),
|
||||
arguments: args
|
||||
};
|
||||
}).filter(sc => sc.name);
|
||||
}
|
||||
|
||||
async generateScript() {
|
||||
const header = this.headerInput.value.trim();
|
||||
const args = this.getArguments();
|
||||
|
||||
if (args.length === 0) {
|
||||
alert('Please add at least one argument');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
header: this.headerInput.value.trim(),
|
||||
arguments: this.getArguments(),
|
||||
subcommands: this.getSubcommands()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/generate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate script');
|
||||
}
|
||||
|
||||
const scriptCode = await response.text();
|
||||
this.generatedCode.textContent = scriptCode;
|
||||
this.outputSection.style.display = 'block';
|
||||
|
||||
// Smooth scroll to output
|
||||
this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
} catch (error) {
|
||||
alert('Error generating script: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async copyToClipboard() {
|
||||
const code = this.generatedCode.textContent;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
this.copyBtn.textContent = 'Copied!';
|
||||
this.copyBtn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
this.copyBtn.textContent = 'Copy';
|
||||
this.copyBtn.classList.remove('copied');
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
alert('Failed to copy to clipboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ArgumentBuilder();
|
||||
});
|
Reference in New Issue
Block a user