diff --git a/2025-10-27 01.06.24 localhost b47035f03aa0.png b/assets/img/flat-view.png similarity index 100% rename from 2025-10-27 01.06.24 localhost b47035f03aa0.png rename to assets/img/flat-view.png diff --git a/static/js/app.js b/static/js/app.js index 737ced4..f880b09 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -99,21 +99,6 @@ class ArgumentBuilder { 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(); @@ -142,34 +127,6 @@ class ArgumentBuilder { this.helpContent.textContent = helpText; } - createSubcommandHeader(name, description) { - const header = document.createElement('div'); - header.className = 'subcommand-header'; - header.innerHTML = ` - - - - - `; - 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(); @@ -181,9 +138,8 @@ class ArgumentBuilder { } const payload = { - header: this.headerInput.value.trim(), - arguments: this.getArguments(), - subcommands: this.getSubcommands() + header: header, + arguments: args }; try { diff --git a/static/js/v2.js b/static/js/v2.js index 737ced4..7edc305 100644 --- a/static/js/v2.js +++ b/static/js/v2.js @@ -1,31 +1,120 @@ 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.globalContainer = + document.getElementById('global-arguments-container') || + document.getElementById('arguments-container'); + this.subcommandsContainer = document.querySelector('.subcommands-container'); + this.addSubcommandBtn = document.getElementById('add-subcommand-btn'); this.init(); } init() { - // Add initial empty row - this.addArgumentRow(); + if (!this.headerInput || !this.generateBtn || !this.helpContent || !this.globalContainer) { + return; + } + + this.hydrateArgumentContainer(this.globalContainer); + + if (this.subcommandsContainer) { + const sections = Array.from(this.subcommandsContainer.querySelectorAll('.subcommand-section')); + sections.forEach(section => this.hydrateSubcommandSection(section)); + } + + if (this.addSubcommandBtn) { + const clone = this.addSubcommandBtn.cloneNode(true); + clone.type = 'button'; + clone.addEventListener('click', () => { + const section = this.createSubcommandSection(); + if (section) { + section.classList.remove('collapsed'); + section.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + const nameInput = section.querySelector('.subcommand-name-input'); + if (nameInput) { + nameInput.focus(); + } + this.updateHelpPreview(); + } + }); + this.addSubcommandBtn.replaceWith(clone); + this.addSubcommandBtn = clone; + } - // Event listeners this.generateBtn.addEventListener('click', () => this.generateScript()); this.headerInput.addEventListener('input', () => this.updateHelpPreview()); - this.copyBtn.addEventListener('click', () => this.copyToClipboard()); - // Update help preview initially + if (this.copyBtn) { + this.copyBtn.addEventListener('click', () => this.copyToClipboard()); + } + this.updateHelpPreview(); } - addArgumentRow(params = '', command = '', helpText = '') { + hydrateArgumentContainer(container) { + if (!container) { + return; + } + const rows = Array.from(container.querySelectorAll('.argument-row')); + if (rows.length === 0) { + this.createArgumentRow(container); + return; + } + rows.forEach(row => this.attachRowHandlers(row, container)); + this.ensureTrailingRow(container); + } + + attachRowHandlers(row, container) { + const inputs = Array.from(row.querySelectorAll('input')); + if (inputs.length === 0) { + return; + } + const update = () => { + this.updateHelpPreview(); + this.ensureTrailingRow(container); + }; + inputs.forEach(input => { + input.addEventListener('input', update); + }); + + const removeBtn = row.querySelector('.remove-btn'); + if (removeBtn) { + removeBtn.type = 'button'; + removeBtn.addEventListener('click', () => { + row.remove(); + this.updateHelpPreview(); + this.ensureTrailingRow(container); + }); + } + } + + ensureTrailingRow(container) { + if (!container) { + return; + } + const rows = Array.from(container.querySelectorAll('.argument-row')); + if (rows.length === 0) { + this.createArgumentRow(container); + return; + } + const lastRow = rows[rows.length - 1]; + const hasContent = Array.from(lastRow.querySelectorAll('input')).some( + input => input.value.trim() !== '' + ); + if (hasContent) { + this.createArgumentRow(container); + } + } + + createArgumentRow(container, params = '', command = '', helpText = '') { + if (!container) { + return null; + } const row = document.createElement('div'); row.className = 'argument-row'; @@ -46,148 +135,287 @@ class ArgumentBuilder { const removeBtn = document.createElement('button'); removeBtn.className = 'remove-btn'; + removeBtn.type = 'button'; 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); + container.appendChild(row); + this.attachRowHandlers(row, container); + + return row; } - getArguments() { - const rows = this.container.querySelectorAll('.argument-row'); - const args = []; + hydrateSubcommandSection(section, presetArgs) { + if (!section) { + return; + } - 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(); + const nameInput = section.querySelector('.subcommand-name-input'); + const descInput = section.querySelector('.subcommand-desc-input'); + const toggleBtn = section.querySelector('.toggle-collapse-btn'); + const removeBtn = section.querySelector('.subcommand-header .remove-btn'); + const argsContainer = section.querySelector('.arguments-container'); - if (command) { - args.push({ + if (nameInput) { + nameInput.addEventListener('input', () => this.updateHelpPreview()); + } + if (descInput) { + descInput.addEventListener('input', () => this.updateHelpPreview()); + } + if (toggleBtn) { + toggleBtn.type = 'button'; + toggleBtn.addEventListener('click', () => { + section.classList.toggle('collapsed'); + }); + } + if (removeBtn) { + removeBtn.type = 'button'; + removeBtn.addEventListener('click', () => { + section.remove(); + this.updateHelpPreview(); + }); + } + + if (argsContainer) { + if (Array.isArray(presetArgs)) { + argsContainer.innerHTML = ''; + presetArgs.forEach(arg => { + this.createArgumentRow( + argsContainer, + arg.params || '', + arg.command || '', + arg.helpText || '' + ); + }); + } + this.hydrateArgumentContainer(argsContainer); + } + } + + createSubcommandSection(name = '', description = '', args = []) { + if (!this.subcommandsContainer) { + return null; + } + + const section = document.createElement('div'); + section.className = 'subcommand-section'; + + const header = document.createElement('div'); + header.className = 'subcommand-header'; + + const nameInput = document.createElement('input'); + nameInput.type = 'text'; + nameInput.className = 'subcommand-name-input'; + nameInput.placeholder = 'container'; + nameInput.value = name; + + const descInput = document.createElement('input'); + descInput.type = 'text'; + descInput.className = 'subcommand-desc-input'; + descInput.placeholder = 'Manage containers'; + descInput.value = description; + + const toggleBtn = document.createElement('button'); + toggleBtn.className = 'toggle-collapse-btn'; + toggleBtn.type = 'button'; + + const removeBtn = document.createElement('button'); + removeBtn.className = 'remove-btn'; + removeBtn.type = 'button'; + removeBtn.innerHTML = '×'; + + header.appendChild(nameInput); + header.appendChild(descInput); + header.appendChild(toggleBtn); + header.appendChild(removeBtn); + + const body = document.createElement('div'); + body.className = 'subcommand-body'; + + const headerRow = this.createArgumentHeader(); + const argsContainer = document.createElement('div'); + argsContainer.className = 'arguments-container'; + + body.appendChild(headerRow); + body.appendChild(argsContainer); + + section.appendChild(header); + section.appendChild(body); + + if (this.addSubcommandBtn) { + this.addSubcommandBtn.before(section); + } else { + this.subcommandsContainer.appendChild(section); + } + + this.hydrateSubcommandSection(section, args); + + return section; + } + + createArgumentHeader() { + const row = document.createElement('div'); + row.className = 'argument-header'; + const labels = ['Parameters', 'Variable Name', 'Help Text', '']; + labels.forEach(text => { + const span = document.createElement('span'); + span.textContent = text; + row.appendChild(span); + }); + return row; + } + + getArgumentsFromContainer(container) { + if (!container) { + return []; + } + const rows = Array.from(container.querySelectorAll('.argument-row')); + return rows + .map(row => { + const inputs = row.querySelectorAll('input'); + if (inputs.length < 3) { + return null; + } + + const params = inputs[0].value.trim(); + const command = inputs[1].value.trim(); + const helpText = inputs[2].value.trim(); + + if (!command) { + return null; + } + + return { params: params || command, command: command, helpText: helpText || command - }); - } - }); - - return args; + }; + }) + .filter(Boolean); } - addSubcommand(name = '', description = '') { - const subcommandSection = document.createElement('div'); - subcommandSection.className = 'subcommand-section'; + getGlobalArguments() { + return this.getArgumentsFromContainer(this.globalContainer); + } - const header = this.createSubcommandHeader(name, description); - const argsContainer = this.createArgumentsContainer(); + getSubcommands() { + if (!this.subcommandsContainer) { + return []; + } + const sections = Array.from( + this.subcommandsContainer.querySelectorAll('.subcommand-section') + ); + return sections + .map(section => { + const name = + section.querySelector('.subcommand-name-input')?.value.trim() || ''; + const description = + section.querySelector('.subcommand-desc-input')?.value.trim() || ''; + const argsContainer = section.querySelector('.arguments-container'); + const arguments = this.getArgumentsFromContainer(argsContainer); + return { + name, + description, + arguments + }; + }) + .filter(subcmd => subcmd.name); + } - subcommandSection.appendChild(header); - subcommandSection.appendChild(argsContainer); - - this.subcommandContainer.appendChild(subcommandSection); - - return subcommandSection; + padLabel(label, width = 20) { + if (!label) { + return ''.padEnd(width, ' '); + } + if (label.length >= width) { + return label + ' '; + } + return label + ' '.repeat(width - label.length); } updateHelpPreview() { - const header = this.headerInput.value.trim(); - const args = this.getArguments(); + if (!this.helpContent) { + return; + } - if (args.length === 0) { + const header = this.headerInput ? this.headerInput.value.trim() : ''; + const globalArgs = this.getGlobalArguments(); + const subcommands = this.getSubcommands(); + + if (globalArgs.length === 0 && subcommands.length === 0) { this.helpContent.textContent = 'Add arguments to see the help output...'; return; } - let helpText = ''; + const lines = []; if (header) { - helpText += header + '\n\n'; + lines.push(header, ''); } - helpText += 'Usage: script.sh [OPTIONS]\n\n'; - helpText += 'Options:\n'; + let usage = 'Usage: script.sh'; + if (subcommands.length > 0) { + if (globalArgs.length > 0) { + usage += ' [GLOBAL OPTIONS]'; + } + usage += ' COMMAND [COMMAND OPTIONS]'; + } else { + usage += ' [OPTIONS]'; + } + lines.push(usage, ''); - args.forEach(arg => { - const params = arg.params || arg.command; - const help = arg.helpText || arg.command; - helpText += ` ${params.padEnd(20)} ${help}\n`; - }); + if (globalArgs.length > 0) { + lines.push('Global Options:'); + globalArgs.forEach(arg => { + const params = arg.params || arg.command; + const help = arg.helpText || arg.command; + lines.push(` ${this.padLabel(params)}${help}`); + }); + lines.push(` ${this.padLabel('-h --help')}Show this help message`, ''); + } - helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`; + if (subcommands.length > 0) { + lines.push('Subcommands:'); + subcommands.forEach(subcmd => { + const desc = subcmd.description || subcmd.name; + lines.push(` ${this.padLabel(subcmd.name)}${desc}`); + }); + lines.push('', "Run 'script.sh COMMAND --help' for more information on a command."); - this.helpContent.textContent = helpText; - } - createSubcommandHeader(name, description) { - const header = document.createElement('div'); - header.className = 'subcommand-header'; - header.innerHTML = ` - - - - - `; - return header; - } + subcommands.forEach(subcmd => { + if (subcmd.arguments.length === 0) { + return; + } + lines.push('', `${subcmd.name} Options:`); + subcmd.arguments.forEach(arg => { + const params = arg.params || arg.command; + const help = arg.helpText || arg.command; + lines.push(` ${this.padLabel(params)}${help}`); + }); + lines.push(` ${this.padLabel('-h --help')}Show this help message`); + }); + } - 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); + this.helpContent.textContent = lines.join('\n'); } 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(), + header: this.headerInput ? this.headerInput.value.trim() : '', + arguments: this.getGlobalArguments(), subcommands: this.getSubcommands() }; + if (payload.arguments.length === 0 && payload.subcommands.length === 0) { + alert('Please add at least one global argument or subcommand before generating.'); + return; + } + try { - const response = await fetch('/generate', { + const response = await fetch('/generate/v2', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -200,19 +428,24 @@ class ArgumentBuilder { } 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' }); + if (this.generatedCode) { + this.generatedCode.textContent = scriptCode; + } + if (this.outputSection) { + this.outputSection.style.display = 'block'; + this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } } catch (error) { alert('Error generating script: ' + error.message); } } async copyToClipboard() { - const code = this.generatedCode.textContent; + if (!this.generatedCode || !this.copyBtn) { + return; + } + const code = this.generatedCode.textContent; try { await navigator.clipboard.writeText(code); this.copyBtn.textContent = 'Copied!'; @@ -228,7 +461,6 @@ class ArgumentBuilder { } } -// Initialize the application when DOM is ready document.addEventListener('DOMContentLoaded', () => { new ArgumentBuilder(); }); diff --git a/templates/advanced.html b/templates/advanced.html index ee60344..b14d481 100644 --- a/templates/advanced.html +++ b/templates/advanced.html @@ -150,6 +150,7 @@ + +