From b99af5f7345dea71e54e3334359d76abd85dfaaf Mon Sep 17 00:00:00 2001 From: roy01 Date: Thu, 4 Jun 2026 11:38:38 +0800 Subject: [PATCH] feat: implement single-port UART --- dea_api.py | 6 + uart_example/README.md | 55 + uart_example/app.js | 418 +++++++ ...mentation_plan_Single_web_uart_terminal.md | 77 ++ uart_example/index.css | 686 +++++++++++ uart_example/index.html | 212 ++++ uart_example/package-lock.json | 1026 +++++++++++++++++ uart_example/package.json | 14 + uart_example/vite.config.js | 9 + 9 files changed, 2503 insertions(+) create mode 100644 uart_example/README.md create mode 100644 uart_example/app.js create mode 100644 uart_example/implementation_plan_Single_web_uart_terminal.md create mode 100644 uart_example/index.css create mode 100644 uart_example/index.html create mode 100644 uart_example/package-lock.json create mode 100644 uart_example/package.json create mode 100644 uart_example/vite.config.js diff --git a/dea_api.py b/dea_api.py index 2a48d62..1091d36 100755 --- a/dea_api.py +++ b/dea_api.py @@ -51,6 +51,7 @@ async def restrict_to_lan(request: Request, call_next): # 掛載靜態網頁檔案,將預設首頁導向到 /ui/ app.mount("/ui", StaticFiles(directory="static", html=True), name="static") +app.mount("/uart_terminal", StaticFiles(directory="uart_example", html=True), name="uart_terminal") @app.get("/") @@ -58,6 +59,11 @@ def redirect_to_ui(): return RedirectResponse(url="/ui/") +@app.get("/uart_terminal") +def redirect_to_uart_terminal(): + return RedirectResponse(url="/uart_terminal/") + + @app.post("/api/design") def design_filter(params: DesignParams): try: diff --git a/uart_example/README.md b/uart_example/README.md new file mode 100644 index 0000000..b133bc4 --- /dev/null +++ b/uart_example/README.md @@ -0,0 +1,55 @@ +# Web UART Terminal + +A sleek, premium, self-contained web application that enables serial port communication directly from your browser using the **Web Serial API**. It supports standard UART configuration controls (Baud rate, data bits, stop bits, parity), ASCII/Hex display, Hex data sending, log exporting, and a responsive dark/light themed user interface. + +![Web UART Terminal Mockup](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/index.html) + +## Prerequisites & Compatibility + +1. **Browser Support**: The Web Serial API is supported in modern Chromium-based browsers, including: + - Google Chrome (version 89+) + - Microsoft Edge (version 89+) + - Opera (version 75+) + - *(Note: Firefox, Safari, and iOS browsers are not supported at this time.)* +2. **Secure Context**: The browser only permits Web Serial API calls in a secure context (`https://`) or from `localhost` (which Vite handles automatically). + +## Quick Start + +To launch the development server locally: + +1. **Install dependencies**: + ```bash + npm install + ``` + +2. **Start the local server**: + ```bash + cd ui/uart_terminal + # If not create the virtual environment + python3 -m venv .venv + # Activate it + source .venv/bin/activate + # start server + npm run dev + ``` + +3. **Open the browser**: + Navigate to the URL printed in the terminal (usually `http://localhost:3000`). + +## Features & Usage + +- **Connect / Disconnect**: Click the circular green play icon in the top left to request a COM/USB serial port and open connection. The button changes to a red stop icon when connected. +- **Save Logs**: Click the blue download button next to the connection status to export the accumulated console output log as a `.txt` file. +- **Scroll Modes**: Switch between "Auto-scroll" and "No scroll" to control terminal navigation. +- **Display Formats**: + - `Auto`: Prints incoming data as decoded UTF-8 text string (default). + - `Hex`: Displays incoming bytes as space-separated hexadecimal numbers (e.g., `55 AA 01 02`). +- **Data Transmission**: + - Enter text in the send input at the bottom and click the Send button or press `Enter` to transmit. + - Switch the send format select dropdown between `ASCII` and `Hex` to format data packages. + - **Hex Parsing Rules**: When in Hex mode, you can input hex characters with or without spaces (e.g., `55 AA FF` or `55aaff`). The string must contain an even number of characters and valid hexadecimal characters only. + +### Keyboard Shortcuts + +- **Send Data**: Press `Enter` (or `Ctrl+Enter`) inside the input bar to send. +- **Insert Tabs**: Press `Ctrl+Tab` in the input bar to insert a literal tab character (`\t`) instead of losing input focus. diff --git a/uart_example/app.js b/uart_example/app.js new file mode 100644 index 0000000..518880f --- /dev/null +++ b/uart_example/app.js @@ -0,0 +1,418 @@ +// DOM Elements Selection +const themeToggle = document.getElementById('theme-toggle'); +const connectBtn = document.getElementById('connect-btn'); +const downloadBtn = document.getElementById('download-btn'); +const statusBanner = document.getElementById('status-banner'); +const statusText = document.getElementById('status-text'); + +// Settings Dropdowns +const baudRateSelect = document.getElementById('baud-rate'); +const dataBitsSelect = document.getElementById('data-bits'); +const stopBitsSelect = document.getElementById('stop-bits'); +const paritySelect = document.getElementById('parity'); + +// Controls & Console +const scrollModeSelect = document.getElementById('scroll-mode'); +const displayFormatSelect = document.getElementById('display-format'); +const terminalOutput = document.getElementById('terminal-output'); + +// Sending Inputs +const sendInput = document.getElementById('send-input'); +const sendFormatSelect = document.getElementById('send-format'); +const sendEolSelect = document.getElementById('send-eol'); +const sendBtn = document.getElementById('send-btn'); +const clearBtn = document.getElementById('clear-btn'); + +// App Serial State Variables +let port = null; +let reader = null; +let writer = null; +let keepReading = false; +let receivedLogBuffer = []; // Accumulates all received characters/data for download log +let isTerminalEmpty = true; + +// Initialize theme and browser support on load +document.addEventListener('DOMContentLoaded', () => { + initTheme(); + checkWebSerialSupport(); +}); + +// Toast notification helper +function showToast(message, type = 'success') { + const toast = document.createElement('div'); + toast.className = `toast-msg show toast-${type}`; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => toast.remove(), 300); + }, 3000); +} + +// ------------------------------------------------------------- +// 1. Theme Configuration (Dark / Light Mode) +// ------------------------------------------------------------- +function initTheme() { + const savedTheme = localStorage.getItem('theme') || 'dark'; + if (savedTheme === 'light') { + document.body.classList.add('light-theme'); + } else { + document.body.classList.remove('light-theme'); + } +} + +themeToggle.addEventListener('click', () => { + document.body.classList.toggle('light-theme'); + const activeTheme = document.body.classList.contains('light-theme') ? 'light' : 'dark'; + localStorage.setItem('theme', activeTheme); +}); + +// ------------------------------------------------------------- +// 2. Web Serial API Checks & State Updates +// ------------------------------------------------------------- +function checkWebSerialSupport() { + if (!('serial' in navigator)) { + statusBanner.className = 'status-banner error'; + statusText.innerHTML = 'Web Serial API not supported. Please use a compatible Chromium-based browser (Chrome, Edge, Opera).'; + connectBtn.disabled = true; + connectBtn.style.opacity = '0.5'; + connectBtn.style.cursor = 'not-allowed'; + showToast('Web Serial API is not supported in this browser.', 'error'); + } +} + +function updateConnectionUI(connected, errorMsg = '') { + if (connected) { + connectBtn.className = 'connect-btn connected'; + connectBtn.title = 'Disconnect from serial port'; + statusBanner.className = 'status-banner connected'; + + const info = port.getInfo(); + const portLabel = info.usbVendorId ? `USB Device (VID: 0x${info.usbVendorId.toString(16).toUpperCase()}, PID: 0x${info.usbProductId.toString(16).toUpperCase()})` : 'Serial Device'; + + statusText.innerHTML = `Connected to ${portLabel} at ${baudRateSelect.value} bps.`; + } else { + connectBtn.className = 'connect-btn disconnected'; + connectBtn.title = 'Connect to serial port'; + + if (errorMsg) { + statusBanner.className = 'status-banner error'; + statusText.textContent = errorMsg; + } else { + statusBanner.className = 'status-banner warn'; + statusText.textContent = 'Not connected. Click the connect button to select a port.'; + } + } +} + +// ------------------------------------------------------------- +// 3. Serial Port Connection & Reader Loop +// ------------------------------------------------------------- +async function connectSerial() { + try { + // Request a port from the user + port = await navigator.serial.requestPort(); + + const options = { + baudRate: parseInt(baudRateSelect.value, 10), + dataBits: parseInt(dataBitsSelect.value, 10), + stopBits: parseInt(stopBitsSelect.value, 10), + parity: paritySelect.value + }; + + // Open connection + await port.open(options); + + updateConnectionUI(true); + keepReading = true; + startReadingLoop(); + + } catch (err) { + console.error('Connection failed:', err); + updateConnectionUI(false, `Connection failed: ${err.message}`); + showToast('Failed to open port.', 'error'); + } +} + +async function disconnectSerial() { + keepReading = false; + + if (reader) { + try { + await reader.cancel(); + } catch (e) { + console.error('Error cancelling reader:', e); + } + } + + // Wait a short moment for loops to clear + setTimeout(async () => { + if (port) { + try { + await port.close(); + } catch (e) { + console.error('Error closing port:', e); + } + port = null; + } + updateConnectionUI(false); + showToast('Disconnected from port.', 'success'); + }, 100); +} + +// Background read loop +async function startReadingLoop() { + const decoder = new TextDecoder(); + + while (port && port.readable && keepReading) { + try { + reader = port.readable.getReader(); + + while (keepReading) { + const { value, done } = await reader.read(); + if (done) break; + if (value) { + handleIncomingData(value, decoder); + } + } + } catch (err) { + console.error('Read error:', err); + updateConnectionUI(false, `Port disconnected: ${err.message}`); + showToast('Serial device disconnected.', 'error'); + break; + } finally { + if (reader) { + reader.releaseLock(); + reader = null; + } + } + } +} + +// Handle incoming bytes +function handleIncomingData(uint8Array, decoder) { + const format = displayFormatSelect.value; + let formattedText = ''; + + if (format === 'hex') { + formattedText = Array.from(uint8Array) + .map(b => b.toString(16).padStart(2, '0').toUpperCase()) + .join(' ') + ' '; + } else { + // text or auto mode + formattedText = decoder.decode(uint8Array); + } + + // Save to download log buffer + // Always store string representation + receivedLogBuffer.push(formattedText); + + // Display in terminal UI + appendTerminalText(formattedText, 'received'); +} + +// ------------------------------------------------------------- +// 4. Writing Data (Transmission) +// ------------------------------------------------------------- +async function sendData() { + if (!port || !port.writable) { + showToast('Cannot send. Serial port is not connected.', 'error'); + return; + } + + const rawInput = sendInput.value; + if (!rawInput) return; + + const format = sendFormatSelect.value; + const eol = sendEolSelect.value; + let bytesToSend; + + // Determine EOL bytes/string + let eolText = ''; + let eolBytes = new Uint8Array(0); + if (eol === 'lf') { + eolText = '\n'; + eolBytes = new Uint8Array([10]); + } else if (eol === 'cr') { + eolText = '\r'; + eolBytes = new Uint8Array([13]); + } else if (eol === 'crlf') { + eolText = '\r\n'; + eolBytes = new Uint8Array([13, 10]); + } + + try { + if (format === 'hex') { + const parsedBytes = parseHexString(rawInput); + bytesToSend = new Uint8Array(parsedBytes.length + eolBytes.length); + bytesToSend.set(parsedBytes); + bytesToSend.set(eolBytes, parsedBytes.length); + } else { + bytesToSend = new TextEncoder().encode(rawInput + eolText); + } + } catch (err) { + showToast(err.message, 'error'); + return; + } + + try { + writer = port.writable.getWriter(); + await writer.write(bytesToSend); + + // Log transmission visually on the terminal + let displaySent = ''; + if (format === 'hex') { + displaySent = Array.from(bytesToSend) + .map(b => b.toString(16).padStart(2, '0').toUpperCase()) + .join(' ') + ' '; + } else { + displaySent = rawInput; + if (eol === 'lf') displaySent += '\\n'; + else if (eol === 'cr') displaySent += '\\r'; + else if (eol === 'crlf') displaySent += '\\r\\n'; + } + + appendTerminalText(`\n[Sent] ${displaySent}\n`, 'sent'); + sendInput.value = ''; // clear input field + + } catch (err) { + console.error('Send error:', err); + showToast(`Failed to send: ${err.message}`, 'error'); + } finally { + if (writer) { + writer.releaseLock(); + writer = null; + } + } +} + +// Hex Parsing Utility +function parseHexString(hexString) { + // Remove all white spaces + const clean = hexString.replace(/\s+/g, ''); + + if (!/^[0-9a-fA-F]*$/.test(clean)) { + throw new Error('Hex contains invalid characters (0-9, A-F only)'); + } + + if (clean.length % 2 !== 0) { + throw new Error('Hex string must have an even number of characters'); + } + + const result = new Uint8Array(clean.length / 2); + for (let i = 0; i < clean.length; i += 2) { + result[i / 2] = parseInt(clean.substring(i, i + 2), 16); + } + return result; +} + +// ------------------------------------------------------------- +// 5. Terminal Console Operations (Append, Clear, Scroll) +// ------------------------------------------------------------- +function appendTerminalText(text, className = '') { + if (isTerminalEmpty) { + terminalOutput.textContent = ''; + terminalOutput.classList.remove('empty'); + isTerminalEmpty = false; + downloadBtn.disabled = false; + } + + // Create text container + const span = document.createElement('span'); + if (className) { + span.className = `terminal-line ${className}`; + } + span.textContent = text; + terminalOutput.appendChild(span); + + // Auto-scroll handler + if (scrollModeSelect.value === 'auto') { + terminalOutput.scrollTop = terminalOutput.scrollHeight; + } +} + +// Clear terminal function +function clearTerminal() { + terminalOutput.innerHTML = 'No data received yet...'; + terminalOutput.classList.add('empty'); + isTerminalEmpty = true; + receivedLogBuffer = []; + downloadBtn.disabled = true; + showToast('Terminal cleared.', 'success'); +} + +// Download received console logs +function downloadLog() { + if (receivedLogBuffer.length === 0) return; + + const content = receivedLogBuffer.join(''); + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + const url = URL.createObjectURL(blob); + + const dateStr = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-'); + + const a = document.createElement('a'); + a.href = url; + a.download = `uart_log_${dateStr}.txt`; + document.body.appendChild(a); + a.click(); + + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 100); + + showToast('Download started.', 'success'); +} + +// ------------------------------------------------------------- +// 6. Event Listeners & Keyboard Shortcuts +// ------------------------------------------------------------- +connectBtn.addEventListener('click', () => { + if (port) { + disconnectSerial(); + } else { + connectSerial(); + } +}); + +sendBtn.addEventListener('click', sendData); +clearBtn.addEventListener('click', clearTerminal); +downloadBtn.addEventListener('click', downloadLog); + +// Input Keyboard Shortcuts +sendInput.addEventListener('keydown', (e) => { + // Ctrl + Enter or Enter (standard text input submit) to send + if (e.key === 'Enter') { + e.preventDefault(); + sendData(); + } + // Ctrl + Tab to insert actual tab characters + if (e.key === 'Tab' && e.ctrlKey) { + e.preventDefault(); + const start = sendInput.selectionStart; + const end = sendInput.selectionEnd; + + // Set input value: [text before tab] + \t + [text after tab] + sendInput.value = sendInput.value.substring(0, start) + '\t' + sendInput.value.substring(end); + + // Put caret in right position + sendInput.selectionStart = sendInput.selectionEnd = start + 1; + } +}); + +// Auto-detect browser USB connect/disconnect events +if ('serial' in navigator) { + navigator.serial.addEventListener('connect', (event) => { + showToast('Serial device connected to port.', 'success'); + }); + + navigator.serial.addEventListener('disconnect', (event) => { + if (port && event.target === port) { + disconnectSerial(); + updateConnectionUI(false, 'Selected serial device was disconnected.'); + showToast('Device connection lost.', 'error'); + } + }); +} diff --git a/uart_example/implementation_plan_Single_web_uart_terminal.md b/uart_example/implementation_plan_Single_web_uart_terminal.md new file mode 100644 index 0000000..55a65c1 --- /dev/null +++ b/uart_example/implementation_plan_Single_web_uart_terminal.md @@ -0,0 +1,77 @@ +# Implementation Plan - Web UART Terminal + +This plan details the implementation of a modern, responsive, and feature-rich Web UART Terminal application. The design will match the dark-themed UI provided in the user's reference image and support the Web Serial API for real-time serial communication with hardware. + +## User Review Required + +> [!IMPORTANT] +> **Browser Compatibility**: The Web Serial API is supported in Chromium-based browsers (Chrome, Edge, Opera). It is not supported by default in Firefox or Safari. A friendly warning banner will be displayed to users accessing the page from unsupported browsers. +> **Local Server Requirement**: The Web Serial API requires a secure context (HTTPS) or localhost to work. To facilitate this, we will configure a simple dev environment using **Vite** so you can run it locally with `npm run dev` (serving at `http://localhost:5173`). + +## Proposed Changes + +We will create a self-contained, lightweight, and high-performance Vanilla HTML/CSS/JS application within the `/home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal` directory. + +### UI & Dev Environment Configuration + +--- + +#### [NEW] [package.json](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/package.json) +- Define a project package with `vite` as a dev dependency. +- Include scripts for local development (`npm run dev`) and building (`npm run build`). + +#### [NEW] [vite.config.js](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/vite.config.js) +- A basic configuration file for Vite to ensure it runs correctly and resolves static assets. + +#### [NEW] [index.html](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/index.html) +- Create the HTML skeleton of the UART Terminal. +- Match the layout in the screenshot: + - Header: App Title, Theme Toggle button. + - Action Control: Connect/Disconnect button (circle with play/stop icon), Download button (square download icon). + - Info Banner: "Not connected" warning banner, dynamically changing based on serial connection status. + - Serial Settings Grid: Dropdowns for Baud Rate, Data Bits, Stop Bits, Parity. + - Received Data Section: Section title, controls (Scroll: Auto-scroll/None, Display: Auto/Hex/Text), console output textarea/container. + - Send Data Footer: Form input with keyboard shortcut tooltip, Send Format dropdown (ASCII/Hex), Send button, and Clear Terminal (trash) button. +- Embed all required SVG icons inline for zero external dependencies and fast load times. +- Include a Google Font link (e.g., "Inter" for UI typography, "JetBrains Mono" for the terminal console). + +#### [NEW] [index.css](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/index.css) +- Implement a premium design system using CSS custom properties (variables) for HSL colors. +- Support Dark Mode (default) and Light Mode. +- Incorporate subtle animations (transitions on hover/active, pulsing connection status, smooth modal fades). +- Implement responsive grids and flex layouts, ensuring pixel-perfect alignment with the reference image. +- Design custom scrollbars for the terminal output. + +#### [NEW] [app.js](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/app.js) +- Implement core UART logic via Web Serial API: + - Request serial port and open connection using selected settings (Baud Rate, Data Bits, Stop Bits, Parity). + - Background reading loop with cancellation support. + - Writing loop supporting ASCII/Hex conversion. +- Implement helper utilities: + - Hex parser (validating and converting hex string inputs like `AA BB CC` or `AABBCC` to Uint8Array). + - Hex formatter (representing received bytes as spaced hex strings e.g. `00 12 AF`). +- Implement UI interactions: + - Terminal text appending with Auto-scroll capability. + - Download log: converts terminal content to a `.txt` blob and triggers a browser download. + - Clear log: clears the screen and reset states. + - Keyboard shortcuts (e.g. `Ctrl+Enter` to send, `Ctrl+Tab` to insert tab in input, etc.). + - Theme toggling with `localStorage` persistence. + +#### [NEW] [README.md](file:///home/roy/zData/WTPCode/pec930/pec930_pfc_llc/ui/uart_terminal/README.md) +- Instructions on installing dependencies (`npm install`) and launching the development server (`npm run dev`). +- Quick notes on Web Serial browser compatibility and security requirements. + +## Verification Plan + +### Manual Verification +1. Launch the local dev server using `npm run dev`. +2. Access the app via Chrome or Edge browser. +3. Verify that the UI matches the reference screenshot (dark theme, layouts, styling, icons, font sizes). +4. Verify the theme toggle (switching from dark to light mode and back). +5. Verify inputs: + - Check if serial configuration dropdowns have correct options. + - Verify that clicking "Connect" triggers the browser's Serial Port prompt. + - Verify Hex transmission (e.g., inputting `48 45 4C 4C 4F` in Hex mode sends `HELLO`). + - Verify Text transmission. + - Test "Clear" (trash bin) and "Download" buttons. + - Test auto-scroll behaviour with mock data. diff --git a/uart_example/index.css b/uart_example/index.css new file mode 100644 index 0000000..6a8053b --- /dev/null +++ b/uart_example/index.css @@ -0,0 +1,686 @@ +/* CSS Variables for Premium Design System */ +:root { + /* Dark Theme Variables (Default) */ + --bg-app: #0b0f19; + --bg-panel: #111827; + --bg-input: #1f2937; + --border-color: #374151; + --border-hover: #4b5563; + --border-focus: #6366f1; + --text-main: #f9fafb; + --text-muted: #9ca3af; + --terminal-bg: #090d16; + --terminal-text: #e5e7eb; + --terminal-border: #1f2937; + + /* Accent Colors */ + --accent-green: #10b981; + --accent-green-hover: #059669; + --accent-blue: #3b82f6; + --accent-blue-hover: #2563eb; + --accent-purple: #6366f1; + --accent-purple-hover: #4f46e5; + --accent-warn-bg: #fef3c7; + --accent-warn-text: #b45309; + --accent-warn-border: #f59e0b; + --accent-danger: #ef4444; + --accent-danger-hover: #dc2626; + + /* Layout and Styling Details */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 16px; + --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: 'JetBrains Mono', 'Fira Code', source-code-pro, Menlo, Monaco, Consolas, monospace; + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.15); + --transition-fast: 0.15s ease; + --transition-normal: 0.25s ease; +} + +/* Light Theme Variables */ +body.light-theme { + --bg-app: #f3f4f6; + --bg-panel: #ffffff; + --bg-input: #f9fafb; + --border-color: #d1d5db; + --border-hover: #9ca3af; + --border-focus: #4f46e5; + --text-main: #111827; + --text-muted: #6b7280; + --terminal-bg: #f8fafc; + --terminal-text: #0f172a; + --terminal-border: #e2e8f0; + + --accent-warn-bg: #fffbeb; + --accent-warn-text: #b45309; + --accent-warn-border: #f59e0b; + + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.02); +} + +/* Base resets & styling */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-app); + color: var(--text-main); + font-family: var(--font-ui); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: flex-start; + transition: background-color var(--transition-normal), color var(--transition-normal); +} + +/* App container */ +.app-container { + width: 100%; + max-width: 960px; + padding: 2.5rem 1.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Header */ +.app-header { + display: flex; + justify-content: center; + align-items: center; + position: relative; + margin-bottom: 0.5rem; +} + +.app-header h1 { + font-size: 1.75rem; + font-weight: 700; + letter-spacing: -0.025em; + color: var(--text-main); + text-align: center; +} + +#theme-toggle { + position: absolute; + right: 0; + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-main); + padding: 8px; + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + transition: border-color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast); +} + +#theme-toggle:hover { + border-color: var(--border-hover); + background-color: rgba(255, 255, 255, 0.05); + transform: scale(1.05); +} + +body.light-theme #theme-toggle:hover { + background-color: rgba(0, 0, 0, 0.03); +} + +/* Toggle Sun/Moon icons depending on active class */ +.sun-icon { + display: none; + width: 20px; + height: 20px; +} + +.moon-icon { + display: block; + width: 20px; + height: 20px; +} + +body.light-theme .sun-icon { + display: block; +} + +body.light-theme .moon-icon { + display: none; +} + +/* Main Content Area */ +.app-main { + background-color: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 2rem; + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + gap: 1.75rem; + transition: background-color var(--transition-normal), border-color var(--transition-normal); +} + +/* Action Controls & Banner Row */ +.control-row { + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; +} + +.action-buttons { + display: flex; + gap: 0.75rem; + align-items: center; +} + +/* Circular Connect Button */ +.connect-btn { + width: 48px; + height: 48px; + border-radius: 50%; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; + transition: transform var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast); + box-shadow: var(--shadow-md); +} + +.connect-btn:hover { + transform: scale(1.08); +} + +.connect-btn.disconnected { + background-color: var(--accent-green); +} + +.connect-btn.disconnected:hover { + background-color: var(--accent-green-hover); + box-shadow: 0 0 12px rgba(16, 185, 129, 0.4); +} + +.connect-btn.connected { + background-color: var(--accent-danger); +} + +.connect-btn.connected:hover { + background-color: var(--accent-danger-hover); + box-shadow: 0 0 12px rgba(239, 68, 68, 0.4); +} + +.connect-btn svg { + width: 20px; + height: 20px; +} + +/* Play/Stop SVG displays */ +.connect-btn.disconnected .stop-icon { + display: none; +} + +.connect-btn.disconnected .play-icon { + display: block; +} + +.connect-btn.connected .stop-icon { + display: block; +} + +.connect-btn.connected .play-icon { + display: none; +} + +/* Download Button */ +.download-btn { + width: 48px; + height: 48px; + border-radius: 8px; + border: none; + background-color: var(--accent-blue); + color: #ffffff; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: transform var(--transition-fast), background-color var(--transition-fast), opacity var(--transition-fast); + box-shadow: var(--shadow-md); +} + +.download-btn:hover:not(:disabled) { + background-color: var(--accent-blue-hover); + transform: scale(1.08); +} + +.download-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.download-btn svg { + width: 22px; + height: 22px; +} + +/* Status Banner */ +.status-banner { + flex-grow: 1; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.85rem 1.25rem; + border-radius: var(--radius-md); + font-size: 0.9rem; + font-weight: 500; + line-height: 1.4; + border: 1px solid transparent; + transition: background-color var(--transition-fast), border-color var(--transition-fast); +} + +.status-icon { + width: 20px; + height: 20px; + flex-shrink: 0; +} + +/* Status Banner Themes */ +.status-banner.warn { + background-color: rgba(245, 158, 11, 0.1); + border-color: rgba(245, 158, 11, 0.25); + color: var(--accent-warn-border); +} + +body.light-theme .status-banner.warn { + background-color: var(--accent-warn-bg); + border-color: var(--accent-warn-border); + color: var(--accent-warn-text); +} + +.status-banner.connected { + background-color: rgba(16, 185, 129, 0.1); + border-color: rgba(16, 185, 129, 0.25); + color: var(--accent-green); +} + +body.light-theme .status-banner.connected { + background-color: #ecfdf5; + border-color: var(--accent-green); + color: #047857; +} + +.status-banner.error { + background-color: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.25); + color: var(--accent-danger); +} + +body.light-theme .status-banner.error { + background-color: #fef2f2; + border-color: var(--accent-danger); + color: #b91c1c; +} + +/* Port Config Dropdowns Grid */ +.settings-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.25rem; +} + +@media (max-width: 600px) { + .settings-grid { + grid-template-columns: 1fr; + } + .control-row { + flex-direction: column; + align-items: stretch; + } + .action-buttons { + justify-content: center; + } +} + +.settings-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.settings-group label { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-muted); +} + +/* Custom Styled Select Dropdown */ +.select-wrapper { + position: relative; + width: 100%; +} + +.select-wrapper select { + width: 100%; + padding: 0.75rem 1rem; + font-size: 0.95rem; + font-weight: 500; + font-family: var(--font-ui); + color: var(--text-main); + background-color: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + appearance: none; + cursor: pointer; + outline: none; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); +} + +.select-wrapper select:hover { + border-color: var(--border-hover); +} + +.select-wrapper select:focus { + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); +} + +/* Select arrow icon custom styling */ +.select-wrapper::after { + content: ""; + position: absolute; + right: 1.25rem; + top: 50%; + transform: translateY(-50%); + width: 10px; + height: 6px; + background-color: var(--text-muted); + clip-path: polygon(100% 0, 0 0, 50% 100%); + pointer-events: none; +} + +/* Small Select Dropdowns in UI Controls */ +.select-wrapper-sm select { + padding: 0.4rem 2rem 0.4rem 0.75rem; + font-size: 0.85rem; + border-radius: var(--radius-sm); +} + +.select-wrapper-sm::after { + right: 0.75rem; + width: 8px; + height: 5px; +} + +/* Received Data Terminal Area */ +.received-data-section { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.received-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.received-header h2 { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-main); +} + +.received-controls { + display: flex; + gap: 1.25rem; + align-items: center; +} + +.control-select-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.control-select-group label { + font-size: 0.8rem; + font-weight: 600; + color: var(--text-muted); + white-space: nowrap; +} + +.terminal-wrapper { + position: relative; + width: 100%; +} + +.terminal-output { + width: 100%; + height: 400px; + background-color: var(--terminal-bg); + border: 1px solid var(--terminal-border); + border-radius: var(--radius-md); + padding: 1.25rem; + overflow-y: auto; + font-family: var(--font-mono); + font-size: 0.9rem; + line-height: 1.6; + color: var(--terminal-text); + white-space: pre-wrap; + word-break: break-all; + outline: none; + transition: border-color var(--transition-fast), background-color var(--transition-normal), color var(--transition-normal); +} + +.terminal-output:focus { + border-color: var(--border-focus); +} + +.terminal-output.empty { + color: var(--text-muted); + font-style: italic; + display: flex; + align-items: center; + justify-content: center; +} + +/* Custom Scrollbar for Terminal Output */ +.terminal-output::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +.terminal-output::-webkit-scrollbar-track { + background: var(--terminal-bg); + border-radius: 0 var(--radius-md) var(--radius-md) 0; +} + +.terminal-output::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 5px; + border: 2px solid var(--terminal-bg); +} + +.terminal-output::-webkit-scrollbar-thumb:hover { + background: var(--border-hover); +} + +/* Send Data Controls Footer */ +.send-data-section { + margin-top: 0.25rem; +} + +.send-bar { + display: flex; + gap: 0.75rem; + align-items: center; +} + +.input-container { + flex-grow: 1; + position: relative; +} + +.input-container input { + width: 100%; + padding: 0.75rem 1rem; + font-size: 0.95rem; + font-family: var(--font-ui); + color: var(--text-main); + background-color: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + outline: none; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); +} + +.input-container input:hover { + border-color: var(--border-hover); +} + +.input-container input:focus { + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); +} + +.input-container input::placeholder { + color: var(--text-muted); + opacity: 0.7; +} + +.send-format-wrapper { + width: 90px; + flex-shrink: 0; +} + +.send-eol-wrapper { + width: 120px; + flex-shrink: 0; +} + +/* Button UI */ +.send-btn, .clear-btn { + width: 44px; + height: 44px; + border-radius: 8px; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; + transition: transform var(--transition-fast), background-color var(--transition-fast); + box-shadow: var(--shadow-sm); + flex-shrink: 0; +} + +.send-btn { + background-color: var(--accent-purple); +} + +.send-btn:hover { + background-color: var(--accent-purple-hover); + transform: scale(1.05); +} + +.send-btn:active { + transform: scale(0.98); +} + +.clear-btn { + background-color: var(--bg-input); + border: 1px solid var(--border-color); + color: var(--text-muted); +} + +.clear-btn:hover { + border-color: var(--accent-danger); + color: var(--accent-danger); + background-color: rgba(239, 68, 68, 0.05); + transform: scale(1.05); +} + +body.light-theme .clear-btn:hover { + background-color: #fef2f2; +} + +.clear-btn:active { + transform: scale(0.98); +} + +.send-btn svg, .clear-btn svg { + width: 18px; + height: 18px; +} + +/* Terminal Line Highlights & Formatting */ +.terminal-line { + margin-bottom: 2px; +} + +.terminal-line.sent { + color: var(--accent-purple); + font-weight: 500; +} + +body.light-theme .terminal-line.sent { + color: #4f46e5; +} + +.terminal-line.error { + color: var(--accent-danger); + font-weight: 600; +} + +.terminal-line.system { + color: var(--text-muted); + font-style: italic; +} + +/* Toast Notification Utility */ +.toast-msg { + position: fixed; + bottom: 24px; + right: 24px; + background-color: var(--bg-panel); + border: 1px solid var(--border-color); + padding: 0.75rem 1.25rem; + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + font-size: 0.85rem; + font-weight: 500; + display: flex; + align-items: center; + gap: 0.5rem; + transform: translateY(100px); + opacity: 0; + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease; + z-index: 1000; +} + +.toast-msg.show { + transform: translateY(0); + opacity: 1; +} + +.toast-success { + border-color: var(--accent-green); + color: var(--accent-green); +} + +.toast-error { + border-color: var(--accent-danger); + color: var(--accent-danger); +} diff --git a/uart_example/index.html b/uart_example/index.html new file mode 100644 index 0000000..d72574a --- /dev/null +++ b/uart_example/index.html @@ -0,0 +1,212 @@ + + + + + + UART Terminal + + + + + + + +
+ +
+

UART Terminal

+ +
+ + +
+ + +
+
+ + + +
+ + +
+ + + + + + Not connected. Click the connect button to select a port. +
+
+ + +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ + +
+
+

Received Data

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
No data received yet...
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ + +
+
+ +
+
+ + + + diff --git a/uart_example/package-lock.json b/uart_example/package-lock.json new file mode 100644 index 0000000..552a9dd --- /dev/null +++ b/uart_example/package-lock.json @@ -0,0 +1,1026 @@ +{ + "name": "web-uart-terminal", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web-uart-terminal", + "version": "1.0.0", + "devDependencies": { + "vite": "^5.2.11" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", + "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", + "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", + "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", + "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", + "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", + "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", + "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", + "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", + "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", + "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", + "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", + "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", + "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", + "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", + "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", + "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", + "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", + "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", + "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", + "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", + "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", + "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", + "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", + "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", + "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", + "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.0", + "@rollup/rollup-android-arm64": "4.61.0", + "@rollup/rollup-darwin-arm64": "4.61.0", + "@rollup/rollup-darwin-x64": "4.61.0", + "@rollup/rollup-freebsd-arm64": "4.61.0", + "@rollup/rollup-freebsd-x64": "4.61.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", + "@rollup/rollup-linux-arm-musleabihf": "4.61.0", + "@rollup/rollup-linux-arm64-gnu": "4.61.0", + "@rollup/rollup-linux-arm64-musl": "4.61.0", + "@rollup/rollup-linux-loong64-gnu": "4.61.0", + "@rollup/rollup-linux-loong64-musl": "4.61.0", + "@rollup/rollup-linux-ppc64-gnu": "4.61.0", + "@rollup/rollup-linux-ppc64-musl": "4.61.0", + "@rollup/rollup-linux-riscv64-gnu": "4.61.0", + "@rollup/rollup-linux-riscv64-musl": "4.61.0", + "@rollup/rollup-linux-s390x-gnu": "4.61.0", + "@rollup/rollup-linux-x64-gnu": "4.61.0", + "@rollup/rollup-linux-x64-musl": "4.61.0", + "@rollup/rollup-openbsd-x64": "4.61.0", + "@rollup/rollup-openharmony-arm64": "4.61.0", + "@rollup/rollup-win32-arm64-msvc": "4.61.0", + "@rollup/rollup-win32-ia32-msvc": "4.61.0", + "@rollup/rollup-win32-x64-gnu": "4.61.0", + "@rollup/rollup-win32-x64-msvc": "4.61.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/uart_example/package.json b/uart_example/package.json new file mode 100644 index 0000000..c4597e1 --- /dev/null +++ b/uart_example/package.json @@ -0,0 +1,14 @@ +{ + "name": "web-uart-terminal", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.2.11" + } +} diff --git a/uart_example/vite.config.js b/uart_example/vite.config.js new file mode 100644 index 0000000..4d08179 --- /dev/null +++ b/uart_example/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 3000, + open: false, // Don't auto open browser in command-line environments to avoid errors + }, + base: './', +});