diff --git a/app/static/script.js b/app/static/script.js index 2ce2e96..b01c38f 100644 --- a/app/static/script.js +++ b/app/static/script.js @@ -6,10 +6,84 @@ let isConnected = false; let currentNickname = null; let cdcActivityChart = null; let metricsUpdateInterval = null; +let commandListResizeTimer = null; + +// Curated SQL examples grouped by operation type for the sidebar helpers +const SQL_EXAMPLES = { + insert: { + storm: { + sql: "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Storm', 'Ororo Munroe', 'weather manipulation, lightning, flight');" + }, + dr_strange: { + sql: "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Doctor Strange', 'Stephen Strange', 'sorcery, astral projection, time manipulation');" + }, + black_panther: { + sql: "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Black Panther', 'T\'Challa', 'vibranium suit, enhanced agility, tactical genius');" + }, + captain_marvel: { + sql: "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Captain Marvel', 'Carol Danvers', 'cosmic energy, flight, photon blasts');" + }, + green_lantern: { + sql: "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Green Lantern', 'Hal Jordan', 'power ring constructs, flight, protective aura');" + } + }, + update: { + storm_powers: { + sql: "UPDATE super_heroes SET powers = 'omega-level weather control, flight, tactical leadership' WHERE name = 'Storm';" + }, + spider_identity: { + sql: "UPDATE super_heroes SET secret_identity = 'Gwen Stacy' WHERE name = 'Spider-Woman';" + }, + batman_kit: { + sql: "UPDATE super_heroes SET powers = 'detective genius, stealth tech, tactical gadgets' WHERE name = 'Batman';" + }, + marvel_energy: { + sql: "UPDATE super_heroes SET powers = 'binary form, cosmic awareness, photon energy overload' WHERE name = 'Captain Marvel';" + }, + lighting_team: { + sql: "UPDATE super_heroes SET powers = powers || ', team strategist' WHERE name = 'Black Panther';" + } + }, + delete: { + retire_batman: { + sql: "DELETE FROM super_heroes WHERE name = 'Batman';" + }, + retire_magic: { + sql: "DELETE FROM super_heroes WHERE powers ILIKE '%sorcery%';" + }, + retire_secret: { + sql: "DELETE FROM super_heroes WHERE secret_identity IN ('Bruce Wayne', 'Tony Stark');" + }, + retire_duplicates: { + sql: "DELETE FROM super_heroes WHERE name IN ('Storm', 'Doctor Strange') AND powers ILIKE '%flight%';" + }, + retire_low_power: { + sql: "DELETE FROM super_heroes WHERE powers ILIKE '%acrobatics%' AND powers NOT ILIKE '%strength%';" + } + }, + select: { + list_all: { + sql: "SELECT name, secret_identity, powers FROM super_heroes ORDER BY name;" + }, + flight_team: { + sql: "SELECT name, powers FROM super_heroes WHERE powers ILIKE '%flight%' ORDER BY name;" + }, + power_combo: { + sql: "SELECT name, powers FROM super_heroes WHERE powers ILIKE '%flight%' AND powers ILIKE '%strength%';" + }, + count_total: { + sql: "SELECT COUNT(*) AS total_heroes FROM super_heroes;" + }, + secret_watch: { + sql: "SELECT secret_identity, name FROM super_heroes WHERE secret_identity IS NOT NULL ORDER BY secret_identity;" + } + } +}; // Initialize the application document.addEventListener('DOMContentLoaded', function() { setupEventListeners(); + initializeCommandDropdowns(); showNicknameModal(); updateConnectionStatus('connecting', 'Waiting for nickname...'); initializeDashboard(); @@ -179,6 +253,33 @@ function setupEventListeners() { }); } +function initializeCommandDropdowns() { + const sections = document.querySelectorAll('.command-section'); + + sections.forEach(section => { + const toggle = section.querySelector('.command-toggle'); + const list = section.querySelector('.command-list'); + + if (!toggle || !list) { + return; + } + + const isInitiallyOpen = section.classList.contains('open'); + toggle.setAttribute('aria-expanded', isInitiallyOpen ? 'true' : 'false'); + + toggle.addEventListener('click', () => { + const isOpen = section.classList.toggle('open'); + toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + }); + }); + + window.addEventListener('resize', handleCommandSectionResize, { passive: true }); +} + +function handleCommandSectionResize() { + // No longer needed with CSS-based height transition +} + // Connection status management function updateConnectionStatus(status, text) { const statusIndicator = document.getElementById("connection-status"); @@ -320,37 +421,115 @@ function addUserMessage(message) { } // Sample data functions for sidebar buttons -function insertSampleData() { - const sampleQueries = [ - "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Batman', 'Bruce Wayne', 'intelligence, martial arts');", - "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Wonder Woman', 'Diana Prince', 'super strength, flight, combat');", - "INSERT INTO super_heroes (name, secret_identity, powers) VALUES ('Spider-Man', 'Peter Parker', 'wall-crawling, spider-sense, agility');" - ]; - - const randomQuery = sampleQueries[Math.floor(Math.random() * sampleQueries.length)]; - addSystemMessage(`📝 Execute this SQL in your database to test CDC: ${randomQuery}`); +function insertSampleData(exampleKey) { + const sql = getSqlExample('insert', exampleKey); + if (sql) { + showSqlInstruction(sql); + } } -function updateSampleData() { - const sampleQueries = [ - "UPDATE super_heroes SET powers = 'enhanced strength, flight, combat, leadership' WHERE name = 'Wonder Woman';", - "UPDATE super_heroes SET secret_identity = 'Miles Morales' WHERE name = 'Spider-Man';", - "UPDATE super_heroes SET powers = 'intelligence, martial arts, detective skills, gadgets' WHERE name = 'Batman';" - ]; - - const randomQuery = sampleQueries[Math.floor(Math.random() * sampleQueries.length)]; - addSystemMessage(`📝 Execute this SQL in your database to test CDC: ${randomQuery}`); +function updateSampleData(exampleKey) { + const sql = getSqlExample('update', exampleKey); + if (sql) { + showSqlInstruction(sql); + } } -function deleteSampleData() { - const sampleQueries = [ - "DELETE FROM super_heroes WHERE name = 'Batman';", - "DELETE FROM super_heroes WHERE secret_identity = 'Bruce Wayne';", - "DELETE FROM super_heroes WHERE powers LIKE '%flight%';" - ]; - - const randomQuery = sampleQueries[Math.floor(Math.random() * sampleQueries.length)]; - addSystemMessage(`📝 Execute this SQL in your database to test CDC: ${randomQuery}`); +function deleteSampleData(exampleKey) { + const sql = getSqlExample('delete', exampleKey); + if (sql) { + showSqlInstruction(sql); + } +} + +function selectSampleData(exampleKey) { + const sql = getSqlExample('select', exampleKey); + if (sql) { + showSqlInstruction(sql); + } +} + +function showSqlInstruction(sql) { + addSystemMessage(`📝 Execute this SQL in your database to test CDC:\n${sql}`); +} + +function getSqlExample(category, exampleKey) { + const categoryExamples = SQL_EXAMPLES[category]; + + if (!categoryExamples) { + console.warn(`No SQL examples available for category: ${category}`); + return null; + } + + if (exampleKey && categoryExamples[exampleKey]) { + return categoryExamples[exampleKey].sql; + } + + const randomExample = getRandomExample(Object.values(categoryExamples)); + return randomExample ? randomExample.sql : null; +} + +function getRandomExample(examples) { + if (!examples.length) { + return null; + } + + const index = Math.floor(Math.random() * examples.length); + return examples[index]; +} + +function copySQL(event, category, exampleKey) { + const sql = getSqlExample(category, exampleKey); + if (!sql) { + return; + } + + event.stopPropagation(); + const button = event.currentTarget; + + const attemptClipboardWrite = () => { + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(sql); + } + return Promise.reject(new Error('Clipboard API not available')); + }; + + attemptClipboardWrite() + .then(() => showCopyFeedback(button)) + .catch(() => fallbackCopy(sql, button)); +} + +function fallbackCopy(sql, button) { + const textarea = document.createElement('textarea'); + textarea.value = sql; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'absolute'; + textarea.style.left = '-9999px'; + document.body.appendChild(textarea); + + textarea.select(); + try { + document.execCommand('copy'); + showCopyFeedback(button); + } catch (error) { + console.error('Failed to copy SQL to clipboard:', error); + } finally { + document.body.removeChild(textarea); + } +} + +function showCopyFeedback(button) { + if (!button.dataset.defaultHtml) { + button.dataset.defaultHtml = button.innerHTML; + } + + button.innerHTML = ''; + button.classList.add('copied'); + + setTimeout(() => { + button.classList.remove('copied'); + button.innerHTML = button.dataset.defaultHtml; + }, 1500); } // Utility functions @@ -366,6 +545,8 @@ function formatJSON(jsonString) { window.insertSampleData = insertSampleData; window.updateSampleData = updateSampleData; window.deleteSampleData = deleteSampleData; +window.selectSampleData = selectSampleData; +window.copySQL = copySQL; window.sendMessage = sendMessage; window.submitNickname = submitNickname; window.editNickname = editNickname; diff --git a/app/static/styles.css b/app/static/styles.css index 84e201a..20cf62e 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -469,9 +469,89 @@ body { } .commands { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + + +.command-section { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--surface); + transition: border-color 0.2s ease; + margin-bottom: 0.75rem; + box-sizing: border-box; +} + +.command-section.open { + border-color: var(--primary-color); +} + +.command-toggle { + display: flex; + align-items: center; + gap: 0.65rem; + width: 100%; + padding: 0.75rem 1rem; + background: none; + border: none; + color: var(--text-primary); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + text-align: left; +} + +.command-toggle:hover, +.command-toggle:focus-visible { + background: var(--background); +} + +.command-toggle i { + color: var(--primary-color); +} + +.command-toggle .toggle-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1rem; + font-size: 1.25rem; + color: var(--text-secondary); + transition: transform 0.25s ease; +} + +.command-section.open .command-toggle .toggle-icon { + transform: rotate(90deg); +} + + +.command-list { display: flex; flex-direction: column; gap: 0.5rem; + padding: 0 1rem; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 0.4s cubic-bezier(.4,0,.2,1), opacity 0.3s ease, padding-top 0.4s, padding-bottom 0.4s; + box-sizing: border-box; +} + +.command-section.open .command-list { + opacity: 1; + max-height: 2000px; + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + + +.command-row { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.25rem; } .command-btn { @@ -484,8 +564,9 @@ body { border-radius: var(--radius); font-size: 0.875rem; cursor: pointer; - transition: all 0.2s; + transition: all 0.2s ease; text-align: left; + flex: 1; } .command-btn:hover { @@ -494,6 +575,51 @@ body { transform: translateY(-1px); } +.command-btn-alt { + background: transparent; + border-style: dashed; + color: var(--primary-color); +} + +.command-btn-alt:hover { + background: var(--primary-color); + color: white; + border-color: transparent; +} + +.command-row-single { + justify-content: flex-start; +} + +.command-row-single .command-btn { + width: 100%; +} + +.copy-btn { + width: 2.25rem; + height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s ease; +} + +.copy-btn:hover { + background: var(--primary-color); + color: white; +} + +.copy-btn.copied { + background: var(--success-color); + color: white; + border-color: transparent; +} + .links { display: flex; flex-direction: column; diff --git a/app/templates/index.html b/app/templates/index.html index f0305ee..0c63076 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -4,7 +4,7 @@