commit 5a83e86bc97f2e1ee52307b111f5f531c175029d Author: loveuer Date: Sun Mar 29 06:49:23 2026 -0700 feat: Initial project setup - uzdb database client Initialize complete Wails (Go + React) database management tool ## Project Structure - Frontend: React 18 + TypeScript + Vite - Backend: Go 1.23 + Gin + GORM + SQLite + Zap - Desktop: Wails v2 ## Features Implemented ### UI/UX Design - Complete design system with colors, typography, spacing - Wireframes for all major screens - User flows and interaction specifications - Layout design with 3-panel architecture ### Frontend Components - ConnectionPanel: Database connection sidebar with status indicators - AppLayout: Resizable main layout framework - MenuBar & ToolBar: Navigation and quick actions - QueryEditor: SQL editor with syntax highlighting support - DataGrid: Sortable, filterable, editable data table - TableStructure: Table metadata viewer - StatusBar: Connection info and query statistics - StatusIndicator: Animated connection status component ### Backend Services - Wails bindings: 15+ methods exposed to frontend - Connection management: CRUD operations with connection pooling - Query execution: SQL execution with result handling - Table metadata: Schema introspection - Encryption service: AES-256-GCM password encryption - HTTP API: RESTful endpoints for debugging/integration ### Documentation - Design system specification - Feature requirements document - Wireframes and user flows - API testing guide - Project README ## Technical Details - Password encryption using AES-256-GCM - Thread-safe connection manager with sync.RWMutex - Unified error handling and logging - Clean architecture with dependency injection - SQLite for storing user data (connections, history) ๐Ÿค– Generated with Qoder diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..480e178 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +uzdb.exe +build/bin/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Go workspace file +go.work + +# Dependency directories +vendor/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Node modules (frontend) +frontend/node_modules/ +frontend/dist/ +frontend/.vite/ + +# Wails build artifacts +wails_build/ + +# Data directory (contains user data including encrypted passwords) +data/ +*.db +*.sqlite +*.sqlite3 + +# Log files +*.log +logs/ + +# Environment variables +.env +.env.local +.env.*.local + +# Temporary files +tmp/ +temp/ +*.tmp + +# Coverage reports +coverage.txt +coverage.html + +# Profiling files +cpu.pprof +mem.pprof +block.pprof +mutex.pprof diff --git a/.qoder/agents/uiux-designer.md b/.qoder/agents/uiux-designer.md new file mode 100644 index 0000000..e899965 --- /dev/null +++ b/.qoder/agents/uiux-designer.md @@ -0,0 +1,34 @@ +# UI/UX Designer Agent + +## Role +You are a UI/UX design expert specializing in database management tools and desktop applications. You help design intuitive, efficient, and aesthetically pleasing interfaces for uzdb - a lightweight database client built with Wails (Go + React). + +## Responsibilities +1. **Information Architecture**: Organize database browsing, querying, and management features logically +2. **Visual Design**: Create clean, professional designs following modern desktop app conventions +3. **Interaction Design**: Ensure smooth workflows for common database operations +4. **Design System**: Maintain consistent components, colors, typography, and spacing +5. **Accessibility**: Design for diverse users with clear visual hierarchy and keyboard navigation + +## Domain Knowledge +- Database clients (DBeaver, Navicat, DataGrip, TablePlus) +- SQL query editors with syntax highlighting +- Data grid/table visualization +- Connection management and authentication flows +- Schema exploration and navigation patterns + +## Output Formats +- Wireframes and mockups (ASCII or descriptions) +- Component specifications +- User flow diagrams +- Design tokens (colors, spacing, typography) +- Interaction guidelines +- Figma/Sketch handoff notes when applicable + +## Working Directory +All design documentation is stored in `/root/codes/project/self/uzdb/doc/` + +## Collaboration +- Work with frontend developers to ensure design feasibility +- Consider technical constraints of Wails framework +- Align with existing React component structure diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..1507921 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,186 @@ +# uzdb Project Documentation + +## ๐Ÿ“š Documentation Index + +This directory contains all design and specification documents for the uzdb project. + +### Core Documents + +| Document | Description | Status | +|----------|-------------|--------| +| [Features](./features.md) | Complete feature specification with priorities | โœ… Complete | +| [Design System](./design-system.md) | Visual design language, colors, typography, components | โœ… Complete | +| [Wireframes](./wireframes.md) | UI mockups and layout specifications | โœ… Complete | +| [User Flows](./user-flows.md) | User interaction flows and edge cases | โœ… Complete | + +--- + +## ๐ŸŽฏ Project Overview + +**uzdb** is a lightweight database management tool inspired by DBeaver and Navicat, built with Wails (Go + React). + +### Key Characteristics +- **Lightweight**: Fast startup, minimal resource usage +- **Simple**: Focus on essential features done well +- **Modern**: Clean UI following current design trends +- **Cross-platform**: Windows, macOS, Linux support via Wails + +### Tech Stack +- **Frontend**: React + TypeScript + Vite +- **Backend**: Go 1.23+ +- **Desktop Framework**: Wails v2 +- **Styling**: CSS Variables + modern CSS + +--- + +## ๐Ÿ—๏ธ Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Frontend (React) โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Sidebar โ”‚ Editor โ”‚ Data Grid โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ Wails Runtime โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Backend (Go) โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ MySQL โ”‚PostgreSQLโ”‚ SQLite โ”‚ โ”‚ +โ”‚ โ”‚ Driver โ”‚ Driver โ”‚ Driver โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿ“ File Structure + +``` +uzdb/ +โ”œโ”€โ”€ frontend/ # React application +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # UI components +โ”‚ โ”‚ โ”œโ”€โ”€ App.tsx # Main app component +โ”‚ โ”‚ โ””โ”€โ”€ main.tsx # Entry point +โ”‚ โ””โ”€โ”€ package.json +โ”œโ”€โ”€ doc/ # This directory - design docs +โ”‚ โ”œโ”€โ”€ features.md +โ”‚ โ”œโ”€โ”€ design-system.md +โ”‚ โ”œโ”€โ”€ wireframes.md +โ”‚ โ”œโ”€โ”€ user-flows.md +โ”‚ โ””โ”€โ”€ README.md # This file +โ”œโ”€โ”€ internal/ # Go backend code +โ”œโ”€โ”€ main.go # Go entry point +โ””โ”€โ”€ wails.json # Wails configuration +``` + +--- + +## ๐ŸŽจ Quick Reference + +### Color Palette +- **Primary**: `#3b82f6` (Blue) +- **Success**: `#10b981` (Green) +- **Warning**: `#f59e0b` (Amber) +- **Error**: `#ef4444` (Red) + +### Keyboard Shortcuts +| Action | Shortcut | +|--------|----------| +| Run query | `Ctrl/Cmd + Enter` | +| New connection | `Ctrl/Cmd + N` | +| Save | `Ctrl/Cmd + S` | +| Find | `Ctrl/Cmd + F` | +| Close tab | `Ctrl/Cmd + W` | + +### Supported Databases +- โœ… MySQL 5.7+ +- โœ… PostgreSQL 12+ +- โœ… SQLite 3.x + +--- + +## ๐Ÿ‘ฅ Team Roles + +### UI/UX Designer +- Responsible for visual design and user experience +- Maintains design system consistency +- Creates wireframes and prototypes + +**See:** [.qoder/agents/uiux-designer.md](../.qoder/agents/uiux-designer.md) + +### Frontend Developer +- Implements React components +- Integrates with Wails runtime +- Ensures responsive design + +### Backend Developer +- Implements database drivers +- Handles Go business logic +- Manages connections and queries + +--- + +## ๐Ÿ“‹ Development Workflow + +### 1. Design Phase +1. Review feature requirements +2. Create/update wireframes +3. Define component specs +4. Get team approval + +### 2. Implementation Phase +1. Create React components +2. Implement Go handlers +3. Write tests +4. Integration testing + +### 3. Review Phase +1. Design review against specs +2. Code review +3. QA testing +4. Bug fixes + +--- + +## ๐Ÿ”— Related Resources + +### Design Tools +- [Figma](https://figma.com/) - UI design and prototyping +- [Lucide Icons](https://lucide.dev/) - Icon library +- [Inter Font](https://rsms.me/inter/) - Primary typeface + +### Development +- [Wails Documentation](https://wails.io/docs/) +- [React Docs](https://react.dev/) +- [TypeScript Handbook](https://www.typescriptlang.org/docs/) + +### Inspiration +- [DBeaver](https://dbeaver.io/) - Feature reference +- [Navicat](https://www.navicat.com/) - UX reference +- [TablePlus](https://tableplus.com/) - Modern design reference + +--- + +## ๐Ÿ“ Contributing + +When adding new features: + +1. **Update documentation** in this directory +2. **Follow design system** for consistency +3. **Consider accessibility** requirements +4. **Test with real data** before merging + +--- + +## ๐Ÿ“ž Contact + +For questions about design or features: +- Open an issue on GitHub +- Tag @uiux-designer for design questions +- Tag @maintainer for feature discussions + +--- + +*Last updated: 2026-03-29* diff --git a/doc/design-system.md b/doc/design-system.md new file mode 100644 index 0000000..319836f --- /dev/null +++ b/doc/design-system.md @@ -0,0 +1,217 @@ +# uzdb Design System + +## Overview +uzdb is a lightweight database management tool inspired by DBeaver and Navicat, built with Wails (Go + React). This document defines the visual design language and component system. + +--- + +## Color Palette + +### Primary Colors +| Token | Value | Usage | +|-------|-------|-------| +| `--primary` | `#3b82f6` | Main actions, active states, links | +| `--primary-hover` | `#2563eb` | Hover states | +| `--primary-active` | `#1d4ed8` | Active/pressed states | + +### Semantic Colors +| Token | Value | Usage | +|-------|-------|-------| +| `--success` | `#10b981` | Successful operations, connected status | +| `--warning` | `#f59e0b` | Warnings, pending states | +| `--error` | `#ef4444` | Errors, disconnected status, destructive actions | +| `--info` | `#06b6d4` | Informational messages | + +### Neutral Colors +| Token | Value | Usage | +|-------|-------|-------| +| `--bg-primary` | `#ffffff` | Main background | +| `--bg-secondary` | `#f8fafc` | Secondary backgrounds, sidebars | +| `--bg-tertiary` | `#f1f5f9` | Cards, panels | +| `--border` | `#e2e8f0` | Borders, dividers | +| `--text-primary` | `#0f172a` | Primary text | +| `--text-secondary` | `#64748b` | Secondary text, labels | +| `--text-muted` | `#94a3b8` | Placeholder text, hints | + +### Database Type Colors +| Token | Value | Usage | +|-------|-------|-------| +| `--db-mysql` | `#00758f` | MySQL connections | +| `--db-postgresql` | `#336791` | PostgreSQL connections | +| `--db-sqlite` |#003b57` | SQLite connections | +| `--db-mariadb` | `#003541` | MariaDB connections | + +--- + +## Typography + +### Font Family +```css +--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; +``` + +### Font Sizes +| Token | Size | Line Height | Usage | +|-------|------|-------------|-------| +| `--text-xs` | 12px | 16px | Captions, hints | +| `--text-sm` | 14px | 20px | Body text, labels | +| `--text-base` | 16px | 24px | Default text | +| `--text-lg` | 18px | 28px | Section titles | +| `--text-xl` | 20px | 28px | Panel titles | +| `--text-2xl` | 24px | 32px | Modal titles | + +--- + +## Spacing + +### Base Scale (4px grid) +| Token | Value | +|-------|-------| +| `--space-1` | 4px | +| `--space-2` | 8px | +| `--space-3` | 12px | +| `--space-4` | 16px | +| `--space-5` | 20px | +| `--space-6` | 24px | +| `--space-8` | 32px | +| `--space-10` | 40px | +| `--space-12` | 48px | + +--- + +## Layout + +### Application Shell +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Menu Bar (File, Edit, View, Tools, Help) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Toolbar (Quick actions, search) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ โ”‚ +โ”‚ Sidebar โ”‚ Main Content Area โ”‚ +โ”‚ (200px) โ”‚ (Query editor, โ”‚ +โ”‚ โ”‚ data grid, โ”‚ +โ”‚ - Conn โ”‚ schema viewer) โ”‚ +โ”‚ - DBs โ”‚ โ”‚ +โ”‚ - Tablesโ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Status Bar (Connection info, query time, row count) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Breakpoints +- **Sidebar collapsible**: < 768px width +- **Responsive panels**: Stack vertically on small screens + +--- + +## Components + +### Button Styles +```css +.btn-primary { + background: var(--primary); + color: white; + padding: var(--space-2) var(--space-4); + border-radius: 6px; + font-size: var(--text-sm); +} + +.btn-secondary { + background: transparent; + border: 1px solid var(--border); + color: var(--text-primary); + padding: var(--space-2) var(--space-4); + border-radius: 6px; +} +``` + +### Input Fields +```css +.input { + border: 1px solid var(--border); + border-radius: 6px; + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); +} + +.input:focus { + outline: 2px solid var(--primary); + border-color: transparent; +} +``` + +### Data Grid +- Row height: 36px +- Header height: 40px +- Cell padding: 8px 12px +- Border: 1px solid var(--border) +- Alternating row colors for readability + +### Tabs +- Active tab: Primary color underline (2px) +- Inactive tab: Transparent background +- Tab height: 36px + +--- + +## Icons + +Use [Lucide Icons](https://lucide.dev/) or [Heroicons](https://heroicons.com/) + +Common icons: +- `database` - Connection/database +- `table` - Table view +- `search` - Search/query +- `play` - Execute query +- `download` - Export data +- `upload` - Import data +- `settings` - Settings/preferences +- `plus` - Add new +- `trash-2` - Delete + +--- + +## Interactions + +### Hover States +- Buttons: Darken background by 10% +- Links: Underline + primary color +- Table rows: Background `--bg-tertiary` + +### Focus States +- All interactive elements: 2px primary outline +- Visible focus ring for keyboard navigation + +### Loading States +- Spinner for async operations +- Skeleton screens for data loading +- Disabled state during execution + +### Transitions +- Duration: 150ms +- Easing: `ease-in-out` +- Properties: color, background-color, transform + +--- + +## Accessibility + +### Requirements +- Minimum contrast ratio: 4.5:1 (AA standard) +- Focus indicators on all interactive elements +- Keyboard navigation support +- Screen reader friendly labels +- Resizable text up to 200% + +### Keyboard Shortcuts +| Action | Shortcut | +|--------|----------| +| Execute query | Ctrl/Cmd + Enter | +| New connection | Ctrl/Cmd + N | +| Save | Ctrl/Cmd + S | +| Find | Ctrl/Cmd + F | +| Close tab | Ctrl/Cmd + W | diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 0000000..37386ee --- /dev/null +++ b/doc/features.md @@ -0,0 +1,377 @@ +# uzdb Feature Specification + +## Overview +uzdb is a lightweight database management tool built with Wails (Go + React). It provides essential database operations without the complexity of enterprise tools like DBeaver or Navicat. + +--- + +## Target Users + +1. **Developers** - Need quick database access for debugging and development +2. **Small Teams** - Want a simple, fast tool for daily database tasks +3. **Students/Learners** - Need an easy-to-use SQL client for learning + +--- + +## Supported Databases (MVP) + +### Phase 1 (Initial Release) +- โœ… MySQL 5.7+ / 8.0+ +- โœ… PostgreSQL 12+ +- โœ… SQLite 3.x + +### Phase 2 (Future) +- โณ MariaDB 10.3+ +- โณ Microsoft SQL Server +- โณ MongoDB (basic support) + +--- + +## Core Features + +### 1. Connection Management + +#### 1.1 Create Connection +**Priority:** P0 +**Description:** Users can create new database connections +**Requirements:** +- Support MySQL, PostgreSQL, SQLite +- Store connection details securely +- Test connection before saving +- Support SSH tunneling (Phase 2) + +**UI Components:** +- Connection dialog with form fields +- Database type selector +- Test connection button +- Advanced options accordion + +#### 1.2 Connection List +**Priority:** P0 +**Description:** View and manage saved connections +**Requirements:** +- Display all saved connections +- Show connection status (connected/disconnected) +- Quick connect double-click +- Right-click context menu + +**UI Components:** +- Sidebar connection tree +- Status indicators +- Context menu + +#### 1.3 Edit/Delete Connection +**Priority:** P0 +**Description:** Modify or remove existing connections +**Requirements:** +- Edit connection properties +- Duplicate connection +- Delete with confirmation +- Cannot delete active connection + +--- + +### 2. Schema Explorer + +#### 2.1 Database Navigation +**Priority:** P0 +**Description:** Browse database structure in sidebar +**Requirements:** +- Expand/collapse databases +- Show schemas (PostgreSQL) +- List tables, views, procedures +- Icon differentiation by object type + +**UI Components:** +- Tree view component +- Lazy loading for large schemas +- Search/filter capability (Phase 2) + +#### 2.2 Table Metadata +**Priority:** P0 +**Description:** View table structure and information +**Requirements:** +- Column list with types and constraints +- Indexes display +- Foreign keys relationships +- Triggers list + +**UI Components:** +- Right sidebar panel +- Tabbed interface for different metadata types + +--- + +### 3. Query Editor + +#### 3.1 SQL Editor +**Priority:** P0 +**Description:** Write and execute SQL queries +**Requirements:** +- Syntax highlighting (SQL keywords, strings, comments) +- Line numbers +- Basic autocomplete (table/column names) +- Multiple tabs support +- Query history (Phase 2) + +**UI Components:** +- Monaco Editor or CodeMirror +- Tab bar for multiple queries +- Toolbar with run/save buttons + +#### 3.2 Query Execution +**Priority:** P0 +**Description:** Execute SQL and view results +**Requirements:** +- Execute selected text or full query +- Show execution time +- Display row count +- Cancel long-running queries +- Error message display with line number + +**Keyboard Shortcuts:** +- `Ctrl+Enter`: Execute query +- `Ctrl+R`: Refresh results + +#### 3.3 Query Snippets +**Priority:** P2 +**Description:** Save and reuse common queries +**Requirements:** +- Save query as snippet +- Organize snippets by category +- Insert snippet into editor +- Share snippets (Phase 3) + +--- + +### 4. Data Grid + +#### 4.1 Results Display +**Priority:** P0 +**Description:** Show query results in tabular format +**Requirements:** +- Virtual scrolling for large datasets +- Column resizing +- Sort by clicking headers +- Fixed header row +- Alternating row colors + +**Performance:** +- Render 1000+ rows smoothly +- Load data in chunks (100 rows at a time) + +#### 4.2 Data Editing +**Priority:** P1 +**Description:** Edit cell values inline +**Requirements:** +- Double-click to edit +- Validate data types +- Show NULL indicator +- Commit on blur or Enter +- Escape to cancel + +**Cell Editors:** +- Text input for strings +- Number input for integers/decimals +- Date picker for dates +- Checkbox for booleans +- Dropdown for enums + +#### 4.3 Pagination & Navigation +**Priority:** P0 +**Description:** Navigate through result sets +**Requirements:** +- Page size selector (25, 50, 100, 500) +- Jump to page input +- First/Previous/Next/Last buttons +- Total row count display +- Limit/offset in query + +--- + +### 5. Data Export/Import + +#### 5.1 Export Data +**Priority:** P0 +**Description:** Export table/query results to file +**Requirements:** +- Formats: CSV, JSON, SQL INSERT +- Select destination path +- Configure export options +- Show progress for large exports + +**Export Options (CSV):** +- Delimiter selection (comma, semicolon, tab) +- Include/exclude headers +- Quote character +- Encoding (UTF-8 default) + +**Export Options (JSON):** +- Pretty print vs compact +- Array of objects vs arrays + +#### 5.2 Import Data +**Priority:** P1 +**Description:** Import data from files +**Requirements:** +- Import CSV/JSON to table +- Map columns +- Preview before import +- Handle errors gracefully +- Transaction support (rollback on error) + +--- + +### 6. Additional Features + +#### 6.1 Find in Results +**Priority:** P1 +**Description:** Search within query results +**Requirements:** +- Case-sensitive toggle +- Match whole word +- Highlight matches +- Navigate between matches + +#### 6.2 Query History +**Priority:** P2 +**Description:** Track executed queries +**Requirements:** +- Store last 100 queries per connection +- Search history +- Re-execute from history +- Pin important queries + +#### 6.3 Bookmarks +**Priority:** P2 +**Description:** Save frequently used queries +**Requirements:** +- Bookmark query with name +- Organize in folders +- Quick access from sidebar + +--- + +## Non-Functional Requirements + +### Performance +- App startup: < 2 seconds +- Connection open: < 1 second (local) +- Query results render: < 100ms for 100 rows +- Memory usage: < 200MB idle + +### Security +- Passwords stored encrypted (keyring/keystore) +- No plain-text credentials in config files +- SSL/TLS support for connections +- Sanitize SQL inputs to prevent injection in logs + +### Accessibility +- WCAG 2.1 AA compliance +- Keyboard navigation throughout +- Screen reader support +- High contrast mode (Phase 2) + +### Internationalization +- English (initial) +- Chinese (Simplified) (Phase 2) +- Spanish (Phase 3) + +--- + +## Technical Architecture + +### Frontend (React + TypeScript) +``` +src/ +โ”œโ”€โ”€ components/ +โ”‚ โ”œโ”€โ”€ Sidebar/ +โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionTree.tsx +โ”‚ โ”‚ โ””โ”€โ”€ SchemaExplorer.tsx +โ”‚ โ”œโ”€โ”€ Editor/ +โ”‚ โ”‚ โ”œโ”€โ”€ QueryEditor.tsx +โ”‚ โ”‚ โ””โ”€โ”€ SQLEditor.tsx +โ”‚ โ”œโ”€โ”€ Grid/ +โ”‚ โ”‚ โ”œโ”€โ”€ DataGrid.tsx +โ”‚ โ”‚ โ””โ”€โ”€ CellEditor.tsx +โ”‚ โ”œโ”€โ”€ Dialogs/ +โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionDialog.tsx +โ”‚ โ”‚ โ””โ”€โ”€ ExportDialog.tsx +โ”‚ โ””โ”€โ”€ common/ +โ”‚ โ”œโ”€โ”€ Button.tsx +โ”‚ โ”œโ”€โ”€ Input.tsx +โ”‚ โ””โ”€โ”€ Modal.tsx +โ”œโ”€โ”€ hooks/ +โ”‚ โ”œโ”€โ”€ useQuery.ts +โ”‚ โ””โ”€โ”€ useConnection.ts +โ”œโ”€โ”€ stores/ +โ”‚ โ”œโ”€โ”€ connectionStore.ts +โ”‚ โ””โ”€โ”€ queryStore.ts +โ””โ”€โ”€ utils/ + โ”œโ”€โ”€ formatters.ts + โ””โ”€โ”€ validators.ts +``` + +### Backend (Go) +``` +internal/ +โ”œโ”€โ”€ database/ +โ”‚ โ”œโ”€โ”€ mysql.go +โ”‚ โ”œโ”€โ”€ postgres.go +โ”‚ โ”œโ”€โ”€ sqlite.go +โ”‚ โ””โ”€โ”€ connection.go +โ”œโ”€โ”€ models/ +โ”‚ โ”œโ”€โ”€ connection.go +โ”‚ โ”œโ”€โ”€ query.go +โ”‚ โ””โ”€โ”€ table.go +โ””โ”€โ”€ handlers/ + โ”œโ”€โ”€ connection_handler.go + โ”œโ”€โ”€ query_handler.go + โ””โ”€โ”€ export_handler.go +``` + +--- + +## Out of Scope (for MVP) + +โŒ Visual query builder +โŒ ER diagrams +โŒ Database comparison/sync +โŒ User management +โŒ Backup/restore +โŒ Query profiling/optimization +โŒ Multi-tab result sets +โŒ Stored procedure debugger +โŒ Real-time collaboration + +--- + +## Success Metrics + +1. **Usability**: New user can connect and run query in < 2 minutes +2. **Performance**: Query results load in < 500ms for typical queries +3. **Stability**: < 1 crash per 100 hours of usage +4. **Adoption**: 100+ GitHub stars in first month + +--- + +## Future Roadmap + +### Phase 2 (v0.2) +- MariaDB support +- SSH tunneling +- Query history +- Data import +- Dark theme + +### Phase 3 (v0.3) +- SQL Server support +- Visual query builder (basic) +- Snippet sharing +- Plugin system + +### Phase 4 (v1.0) +- MongoDB support +- ER diagram viewer +- Team features (shared connections) +- Auto-update diff --git a/doc/layout-design.md b/doc/layout-design.md new file mode 100644 index 0000000..b606c31 --- /dev/null +++ b/doc/layout-design.md @@ -0,0 +1,691 @@ +# uzdb ๅธƒๅฑ€่ฎพ่ฎกๆ–‡ๆกฃ + +## Overview + +ๆœฌๆ–‡ๆกฃ่ฏฆ็ป†ๆ่ฟฐ uzdb ๆ•ฐๆฎๅบ“็ฎก็†ๅทฅๅ…ท็š„ๆ•ดไฝ“ๅธƒๅฑ€่ฎพ่ฎก๏ผŒๅŒ…ๆ‹ฌๅทฆไพง่ฟžๆŽฅ้ขๆฟใ€ๅณไพงๅŠŸ่ƒฝๅŒบๅŸŸ็š„็•Œ้ข่ฎพ่ฎกๅ’Œไบคไบ’่ง„่Œƒใ€‚ + +--- + +## ๆ•ดไฝ“ๅธƒๅฑ€ๆžถๆž„ + +### ็ปๅ…ธไธคๆ ๅธƒๅฑ€ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ˜ฐ File Edit View Query Tools Help โ”‚ โ† ่œๅ•ๆ  +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [โ–ถ Run] [๐Ÿ’พ Save] [๐Ÿ“ค Export] [๐Ÿ” Find] [๐Ÿ—„๏ธ MySQL @ localhost โ–ผ] โ”‚ โ† ๅทฅๅ…ทๆ  +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ โ”Œโ”€ ๐Ÿ“‘ query_1.sql โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ ๐Ÿ—„๏ธ Connections โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”œโ”€ โšซ MySQL โ”‚ โ”‚ SELECT * FROM users โ”‚ โ”‚ +โ”‚ โ”‚ โ”œโ”€ ๐Ÿ”ต public โ”‚ โ”‚ WHERE active = true; โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ users โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ products โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€ ๐Ÿ“‹ orders โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€ ๐Ÿ“Š information_schema โ”‚ โ”‚ โ”‚ +โ”‚ โ”œโ”€ โšซ PostgreSQLโ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ””โ”€ ๐Ÿ”ต public โ”‚ โ”Œโ”€ Results โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ””โ”€ โšซ SQLite โ”‚ โ”‚ id โ”‚ name โ”‚ email โ”‚ active โ”‚ โ”‚ +โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ [+ New Conn] โ”‚ โ”‚ 1 โ”‚ Alice โ”‚ alice@mail.com โ”‚ โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ 2 โ”‚ Bob โ”‚ bob@mail.com โ”‚ โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ 3 โ”‚ Carol โ”‚ carol@mail.com โ”‚ โœ— โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ [โ—€] [1] [2] [3] [โ–ถ] Per page: [25โ–ผ] 127 rows โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ“ Connected to MySQL@localhost:3306 โ”‚ UTF-8 โ”‚ LF โ”‚ ins โ”‚ โ† ็Šถๆ€ๆ  +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ๅธƒๅฑ€ๅฐบๅฏธ่ง„่Œƒ + +| ๅŒบๅŸŸ | ้ป˜่ฎคๅฎฝๅบฆ/้ซ˜ๅบฆ | ๅฏ่ฐƒ่Š‚่Œƒๅ›ด | ่ฏดๆ˜Ž | +|------|--------------|-----------|------| +| ๅทฆไพง้ขๆฟ | 240px | 180px - 400px | ๅฏๆ‹–ๆ‹ฝ่ฐƒ่Š‚๏ผŒๆ”ฏๆŒๆŠ˜ๅ  | +| ่œๅ•ๆ  | 32px (้ซ˜) | ๅ›บๅฎš | ๆ ‡ๅ‡†่œๅ•้ซ˜ๅบฆ | +| ๅทฅๅ…ทๆ  | 40px (้ซ˜) | ๅ›บๅฎš | ๅŒ…ๅซๅฟซๆทๆ“ไฝœ | +| ็Šถๆ€ๆ  | 28px (้ซ˜) | ๅ›บๅฎš | ๆ˜พ็คบ่ฟžๆŽฅๅ’Œไฟกๆฏ็Šถๆ€ | +| Tab ๆ ‡็ญพๆ  | 36px (้ซ˜) | ๅ›บๅฎš | ๆฏไธชๆ ‡็ญพ้กต้ซ˜ๅบฆ | + +--- + +## ๅทฆไพง่ฟžๆŽฅ้ขๆฟ่ฎพ่ฎก + +### 1. ้ขๆฟ็ป“ๆž„ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ—„๏ธ Connections [โš™๏ธ] โ”‚ โ† ้ขๆฟๆ ‡้ข˜ + ่ฎพ็ฝฎๆŒ‰้’ฎ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ”ต Active Connection โ”‚ โ† ๅฝ“ๅ‰ๆดป่ทƒ่ฟžๆŽฅ๏ผˆ้ซ˜ไบฎ๏ผ‰ +โ”‚ โ”œโ”€ ๐Ÿ—„๏ธ database_name โ”‚ +โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“Š schema_name โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ table1 โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ table2 โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€ ๐Ÿ“‹ table3 โ”‚ +โ”‚ โ”‚ โ””โ”€ ๐Ÿ“Š another_schema โ”‚ +โ”‚ โ”œโ”€ ๐Ÿ“‹ views (5) โ”‚ +โ”‚ โ”œโ”€ โšก functions (12) โ”‚ +โ”‚ โ””โ”€ ๐Ÿ“ procedures (3) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โšซ saved_connection_1 โ”‚ โ† ๅทฒไฟๅญ˜ไฝ†ๆœช่ฟžๆŽฅ +โ”‚ โšซ saved_connection_2 โ”‚ +โ”‚ โšช saved_connection_3 โ”‚ โ† ่ฟžๆŽฅ้”™่ฏฏ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [+ New Connection] โ”‚ โ† ๆ–ฐๅปบ่ฟžๆŽฅๆŒ‰้’ฎ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 2. ่ฟžๆŽฅ็Šถๆ€ๆŒ‡็คบๅ™จ + +#### ็Šถๆ€็ฑปๅž‹ไธŽ่ง†่ง‰่กจ็Žฐ + +| ็Šถๆ€ | ๅ›พๆ ‡ | ้ขœ่‰ฒ | ่ฏดๆ˜Ž | ไบคไบ’ | +|------|------|------|------|------| +| **ๅทฒ่ฟžๆŽฅ** | `๐Ÿ”ต` / `โ—` | `#10b981` (็ปฟ่‰ฒ) | ่ฟžๆŽฅๆˆๅŠŸไธ”ๅฏ็”จ | ๅฏๅฑ•ๅผ€ๆŸฅ็œ‹็ป“ๆž„ | +| **ไฝฟ็”จไธญ** | `๐ŸŸข` / `โ—‰` | `#3b82f6` (่“่‰ฒ) | ๅฝ“ๅ‰ๆญฃๅœจไฝฟ็”จ็š„่ฟžๆŽฅ | ้ซ˜ไบฎๆ˜พ็คบ๏ผŒๅธฆๅ…‰ๆ™•ๆ•ˆๆžœ | +| **ๆœช่ฟžๆŽฅ** | `โšซ` / `โ—‹` | `#94a3b8` (็ฐ่‰ฒ) | ๅทฒไฟๅญ˜ไฝ†ๆœชๅปบ็ซ‹่ฟžๆŽฅ | ๅŒๅ‡ปๆˆ–็‚นๅ‡ปๅฑ•ๅผ€่ฟžๆŽฅ | +| **่ฟžๆŽฅไธญ** | `โŸณ` / `โŒ›` | `#f59e0b` (ๆฉ™่‰ฒ) | ๆญฃๅœจๅปบ็ซ‹่ฟžๆŽฅ | ๆ—‹่ฝฌๅŠจ็”ป๏ผŒไธๅฏไบคไบ’ | +| **้”™่ฏฏ** | `โšช` / `โœ•` | `#ef4444` (็บข่‰ฒ) | ่ฟžๆŽฅๅคฑ่ดฅ | ๆ‚ฌๅœๆ˜พ็คบ้”™่ฏฏไฟกๆฏ | + +#### CSS ๅฎž็Žฐ็คบไพ‹ + +```css +/* ็Šถๆ€ๆŒ‡็คบๅ™จๅŸบ็ก€ๆ ทๅผ */ +.connection-status { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; + margin-right: 8px; + transition: all 0.15s ease-in-out; +} + +/* ๅ„็Šถๆ€ๆ ทๅผ */ +.status-connected { + background-color: var(--success); + box-shadow: 0 0 4px var(--success); +} + +.status-active { + background-color: var(--primary); + box-shadow: 0 0 8px var(--primary); + animation: pulse 2s infinite; +} + +.status-disconnected { + background-color: var(--text-muted); + border: 1px solid var(--border); +} + +.status-connecting { + border: 2px solid var(--warning); + border-top-color: transparent; + animation: spin 1s linear infinite; +} + +.status-error { + background-color: var(--error); + cursor: help; +} + +/* ๅŠจ็”ปๅฎšไน‰ */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +@keyframes spin { + to { transform: rotate(360deg); } +} +``` + +### 3. ๆ•ฐๆฎๅบ“็ฑปๅž‹ๅ›พๆ ‡ + +| ๆ•ฐๆฎๅบ“็ฑปๅž‹ | ๅ›พๆ ‡ | ้ขœ่‰ฒ | ไฝฟ็”จๅœบๆ™ฏ | +|-----------|------|------|----------| +| MySQL | `๐Ÿ—„๏ธ` / `๐Ÿฌ` | `#00758f` | MySQL ่ฟžๆŽฅ | +| PostgreSQL | `๐Ÿ˜` / `๐Ÿ—„๏ธ` | `#336791` | PostgreSQL ่ฟžๆŽฅ | +| SQLite | `โ—ช` / `๐Ÿ—„๏ธ` | `#003b57` | SQLite ่ฟžๆŽฅ | +| MariaDB | `๐Ÿ—„๏ธ` | `#003541` | MariaDB ่ฟžๆŽฅ | + +### 4. ไบคไบ’ๆ–นๅผ + +#### ๅ•ๅ‡ปไบคไบ’ +- **ๅ•ๅ‡ป่ฟžๆŽฅๅ**: ้€‰ไธญ่ฟžๆŽฅ๏ผŒๅฆ‚ๆžœๆœช่ฟžๆŽฅๅˆ™ๅฐ่ฏ•่ฟžๆŽฅ +- **ๅ•ๅ‡ปๅฑ•ๅผ€็ฎญๅคด**: ๅฑ•ๅผ€/ๆŠ˜ๅ ๆ•ฐๆฎๅบ“็ป“ๆž„ๆ ‘ +- **ๅ•ๅ‡ป็Šถๆ€ๅ›พๆ ‡**: ๆ˜พ็คบ่ฟžๆŽฅ่ฏฆๆƒ… tooltip + +#### ๅŒๅ‡ปไบคไบ’ +- **ๅŒๅ‡ป่ฟžๆŽฅ**: ๅฟซ้€Ÿ่ฟžๆŽฅ/ๆ–ญๅผ€่ฟžๆŽฅ +- **ๅŒๅ‡ปๆ•ฐๆฎๅบ“/่กจ**: ๅœจๆ–ฐๆ ‡็ญพ้กตๆ‰“ๅผ€ๆ•ฐๆฎๆต่งˆๆˆ–็ป“ๆž„ๆŸฅ็œ‹ + +#### ๅณ้”ฎ่œๅ• + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ—„๏ธ MySQL @ localhost โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ–ถ Connect โ”‚ +โ”‚ โ–ถ Disconnect โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ–ถ Edit Connection... โ”‚ +โ”‚ โ–ถ Duplicate Connection โ”‚ +โ”‚ โ–ถ Refresh Schema โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ–ถ Test Connection โ”‚ +โ”‚ โ–ถ Copy Connection String โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ–ถ Delete Connection โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### ้”ฎ็›˜ๅฏผ่ˆช +| ๅฟซๆท้”ฎ | ๅŠŸ่ƒฝ | +|--------|------| +| `โ†‘` / `โ†“` | ไธŠไธ‹็งปๅŠจ้€‰ๆ‹ฉ | +| `โ†’` | ๅฑ•ๅผ€่Š‚็‚น / ่ฟžๆŽฅๆ•ฐๆฎๅบ“ | +| `โ†` | ๆŠ˜ๅ ่Š‚็‚น | +| `Enter` | ็กฎ่ฎคๆ“ไฝœ / ่ฟžๆŽฅ | +| `Space` | ๅˆ‡ๆขๅฑ•ๅผ€/ๆŠ˜ๅ  | +| `F2` | ้‡ๅ‘ฝๅ่ฟžๆŽฅ | +| `Delete` | ๅˆ ้™ค่ฟžๆŽฅ๏ผˆ้œ€็กฎ่ฎค๏ผ‰ | +| `Ctrl+R` | ๅˆทๆ–ฐ Schema | +| `Context Menu` | ๆ˜พ็คบๅณ้”ฎ่œๅ• | + +### 5. ๆ–ฐๅปบ่ฟžๆŽฅๆŒ‰้’ฎๅŒบๅŸŸ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ + New Connection โ”‚ โ”‚ โ† ไธปๆŒ‰้’ฎ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ Recent: โ”‚ +โ”‚ โ€ข MySQL @ localhost โ”‚ +โ”‚ โ€ข PG @ prod-db โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๅณไพงๅŠŸ่ƒฝๅŒบๅŸŸ่ฎพ่ฎก + +### 1. SQL ็ผ–่พ‘ๅ™จๆจกๅ— + +``` +โ”Œโ”€ ๐Ÿ“‘ unsaved_query.sql โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [ร—] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ -- Get active users with their orders โ”‚ +โ”‚ 2 โ”‚ SELECT โ”‚ +โ”‚ 3 โ”‚ u.id, โ”‚ +โ”‚ 4 โ”‚ u.name, โ”‚ +โ”‚ 5 โ”‚ u.email, โ”‚ +โ”‚ 6 โ”‚ COUNT(o.id) as order_count โ”‚ +โ”‚ 7 โ”‚ FROM users u โ”‚ +โ”‚ 8 โ”‚ LEFT JOIN orders o ON u.id = o.user_id โ”‚ +โ”‚ 9 โ”‚ WHERE u.active = true โ”‚ +โ”‚ 10 โ”‚ AND u.created_at >= '2024-01-01' โ”‚ +โ”‚ 11 โ”‚ GROUP BY u.id, u.name, u.email โ”‚ +โ”‚ 12 โ”‚ HAVING COUNT(o.id) > 0 โ”‚ +โ”‚ 13 โ”‚ ORDER BY order_count DESC โ”‚ +โ”‚ 14 โ”‚ LIMIT 100; โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ–ถ Run (Ctrl+Enter) โ”‚ โ–ถ Explain โ”‚ โœจ Format โ”‚ ๐Ÿ“œ History โ”‚ +โ”‚ ๐Ÿ’พ Save โ”‚ ๐Ÿ“ค Export Results โ”‚ ๐Ÿ” Find in Query โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### SQL ็ผ–่พ‘ๅ™จ็‰นๆ€ง + +| ๅŠŸ่ƒฝ | ๆ่ฟฐ | ๅฎž็Žฐๅปบ่ฎฎ | +|------|------|----------| +| **่ฏญๆณ•้ซ˜ไบฎ** | SQL ๅ…ณ้”ฎๅญ—ใ€ๅญ—็ฌฆไธฒใ€ๆณจ้‡Šใ€ๅ‡ฝๆ•ฐ็ญ‰ | Monaco Editor / CodeMirror | +| **ๆ™บ่ƒฝๆ็คบ** | ่กจๅใ€ๅˆ—ๅใ€ๅ‡ฝๆ•ฐ่‡ชๅŠจ่กฅๅ…จ | ๅŸบไบŽๅฝ“ๅ‰่ฟžๆŽฅ schema | +| **ไปฃ็ ๆ ผๅผๅŒ–** | ็พŽๅŒ– SQL ๆ ผๅผ | sql-formatter ๅบ“ | +| **ๅคšๆ ‡็ญพๆ”ฏๆŒ** | ๅŒๆ—ถ็ผ–่พ‘ๅคšไธชๆŸฅ่ฏข | Tab ็ป„ไปถ็ฎก็† | +| **ๆŸฅ่ฏขๅކๅฒ** | ่ฎฐๅฝ•ๆ‰ง่กŒ่ฟ‡็š„ SQL | ๆœฌๅœฐๅญ˜ๅ‚จ + ๆœ็ดข | +| **ไปฃ็ ็‰‡ๆฎต** | ๅธธ็”จ SQL ๆจกๆฟๅฟซ้€Ÿๆ’ๅ…ฅ | ้ข„ๅฎšไน‰ snippets | + +#### ๆ‰ง่กŒ็ป“ๆžœๅฑ•็คบๅŒบ + +``` +โ”Œโ”€ Results โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Query executed successfully in 0.045s (127 rows affected) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [Filters โ–ผ] [Sort โ–ผ] [Group By โ–ผ] [๐Ÿ”„ Refresh] [๐Ÿ“ค Export] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ id โ”‚ name โ”‚ email โ”‚ created_at โ”‚ active โ”‚ ... โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ Alice โ”‚ alice@mail.com โ”‚ 2024-01-15 โ”‚ โœ“ โ”‚ ... โ”‚ +โ”‚ 2 โ”‚ Bob โ”‚ bob@mail.com โ”‚ 2024-01-16 โ”‚ โœ“ โ”‚ ... โ”‚ +โ”‚ 3 โ”‚ Charlie โ”‚ carol@mail.com โ”‚ 2024-01-17 โ”‚ โœ— โ”‚ ... โ”‚ +โ”‚ 4 โ”‚ Diana โ”‚ diana@mail.com โ”‚ 2024-01-18 โ”‚ โœ“ โ”‚ ... โ”‚ +โ”‚ 5 โ”‚ Eve โ”‚ eve@mail.com โ”‚ 2024-01-19 โ”‚ โœ“ โ”‚ ... โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +โ”‚ Showing 1-25 of 127 rows [โ—€โ—€] [โ—€] [1] [2] [3] [...] [โ–ถ] [โ–ถโ–ถ] โ”‚ +โ”‚ Per page: [25 โ–ผ] [๐Ÿ“‹ Copy Row] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 2. ๆ•ฐๆฎๆต่งˆๆจกๅ— + +``` +โ”Œโ”€ ๐Ÿ“‹ public.users โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [ร—] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Table: users โ”‚ Database: public โ”‚ Connection: MySQL@local โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [Data] [Structure] [Indexes] [Foreign Keys] [Triggers] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [โœ๏ธ Add Row] [๐Ÿ—‘๏ธ Delete] [๐Ÿ” Filter] [๐Ÿ”„ Refresh] [๐Ÿ“ค Export]โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ˜ โ”‚ id โ”‚ name โ”‚ email โ”‚ created_at โ”‚ active โ”‚ โœŽ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค +โ”‚ โ˜ โ”‚ 1 โ”‚ Alice โ”‚ alice@mail.com โ”‚ 2024-01-15 โ”‚ โœ“ โ”‚ โœŽ โ”‚ +โ”‚ โ˜ โ”‚ 2 โ”‚ Bob โ”‚ bob@mail.com โ”‚ 2024-01-16 โ”‚ โœ“ โ”‚ โœŽ โ”‚ +โ”‚ โ˜ โ”‚ 3 โ”‚ Charlie โ”‚ carol@mail.com โ”‚ 2024-01-17 โ”‚ โœ— โ”‚ โœŽ โ”‚ +โ”‚ โ˜ โ”‚ 4 โ”‚ Diana โ”‚ diana@mail.com โ”‚ 2024-01-18 โ”‚ โœ“ โ”‚ โœŽ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +โ”‚ Selected: 0 rows Total: 1,247 rows โ”‚ +โ”‚ [โ—€โ—€] [โ—€] [1] [2] [3] [...] [โ–ถ] [โ–ถโ–ถ] Per page: [50 โ–ผ] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### ๆ•ฐๆฎ็ผ–่พ‘ๅŠŸ่ƒฝ + +| ๆ“ไฝœ | ไบคไบ’ๆ–นๅผ | ่ฏดๆ˜Ž | +|------|----------|------| +| **ๅ•ๅ…ƒๆ ผ็ผ–่พ‘** | ๅŒๅ‡ปๅ•ๅ…ƒๆ ผ | ๆ นๆฎๆ•ฐๆฎ็ฑปๅž‹ๅผนๅ‡บ็›ธๅบ”็ผ–่พ‘ๅ™จ | +| **่กŒ้€‰ๆ‹ฉ** | ็‚นๅ‡ปๅค้€‰ๆก† | ๆ”ฏๆŒๅคš้€‰ๆ‰น้‡ๆ“ไฝœ | +| **ๆทปๅŠ ่กŒ** | ็‚นๅ‡ป"Add Row" | ๅœจ่กจๆ ผๆœซๅฐพๆทปๅŠ ๆ–ฐ่กŒ | +| **ๅˆ ้™ค่กŒ** | ้€‰ๆ‹ฉๅŽ็‚นๅˆ ้™ค | ้œ€่ฆ็กฎ่ฎคๅฏน่ฏๆก† | +| **ๆ’ค้”€/้‡ๅš** | Ctrl+Z / Ctrl+Y | ๆ”ฏๆŒๅคš็บงๆ’ค้”€ | + +#### ๅ•ๅ…ƒๆ ผ็ผ–่พ‘ๅ™จ็ฑปๅž‹ + +| ๆ•ฐๆฎ็ฑปๅž‹ | ็ผ–่พ‘ๅ™จ | ่ฏดๆ˜Ž | +|----------|--------|------| +| VARCHAR/TEXT | ๆ–‡ๆœฌ่พ“ๅ…ฅๆก† | ๆ”ฏๆŒๅคš่กŒ็ผ–่พ‘ | +| INT/BIGINT | ๆ•ฐๅญ—่พ“ๅ…ฅๆก† | ๆ•ดๆ•ฐ้ชŒ่ฏ | +| DECIMAL/FLOAT | ๆ•ฐๅญ—่พ“ๅ…ฅๆก† | ๅฐๆ•ฐ็ฒพๅบฆๆŽงๅˆถ | +| DATE | ๆ—ฅๆœŸ้€‰ๆ‹ฉๅ™จ | ๆ—ฅๅކๆŽงไปถ | +| DATETIME/TIMESTAMP | ๆ—ฅๆœŸๆ—ถ้—ด้€‰ๆ‹ฉๅ™จ | ๅŒ…ๅซๆ—ถ้—ด | +| BOOLEAN | ๅค้€‰ๆก† | ๅ‹พ้€‰/ๅ–ๆถˆ | +| ENUM | ไธ‹ๆ‹‰้€‰ๆ‹ฉๆก† | ้ข„ๅฎšไน‰้€‰้กน | +| JSON | JSON ็ผ–่พ‘ๅ™จ | ่ฏญๆณ•้ซ˜ไบฎ | +| BLOB | ๆ–‡ไปถไธŠไผ /้ข„่งˆ | ไบŒ่ฟ›ๅˆถๆ•ฐๆฎๅค„็† | + +### 3. ่กจ็ป“ๆž„ๆŸฅ็œ‹ๆจกๅ— + +``` +โ”Œโ”€ ๐Ÿ“‹ Table Structure: users โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [ร—] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [Data] [Structure] [Indexes] [Foreign Keys] [Triggers] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Columns (5) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Name โ”‚ Type โ”‚ Null โ”‚ Key โ”‚ Default โ”‚ Extra โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ id โ”‚ INT โ”‚ โŒ โ”‚ PK โ”‚ โ”‚ auto_incโ”‚ โ”‚ +โ”‚ โ”‚ name โ”‚ VARCHAR(100) โ”‚ โŒ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ email โ”‚ VARCHAR(255) โ”‚ โŒ โ”‚ UK โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ created_at โ”‚ TIMESTAMP โ”‚ โœ“ โ”‚ โ”‚ NOW() โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ active โ”‚ BOOLEAN โ”‚ โŒ โ”‚ โ”‚ TRUE โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Indexes (2) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Name โ”‚ Type โ”‚ Columns โ”‚ Unique โ”‚ Method โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ PRIMARY โ”‚ BTREE โ”‚ id โ”‚ โœ“ โ”‚ BTREE โ”‚ โ”‚ +โ”‚ โ”‚ idx_email โ”‚ BTREE โ”‚ email โ”‚ โœ“ โ”‚ BTREE โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Foreign Keys (1) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Name โ”‚ Column โ”‚ References โ”‚ On Update โ”‚ On Deleteโ”‚โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ fk_user_role โ”‚ role_id โ”‚ roles(id) โ”‚ CASCADE โ”‚ SET NULL โ”‚โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Table Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Engine: InnoDB โ”‚ Collation: utf8mb4_unicode_ci โ”‚ โ”‚ +โ”‚ โ”‚ Rows: 1,247 โ”‚ Size: 256 KB โ”‚ โ”‚ +โ”‚ โ”‚ Auto Increment: 1248 โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 4. ๅ…ถไป–ๅŠŸ่ƒฝๆจกๅ— + +#### 4.1 ๆŸฅ่ฏขๅކๅฒ + +``` +โ”Œโ”€ ๐Ÿ“œ Query History โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [ร—] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ” Search history... [๐Ÿ—‘๏ธ Clear All] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Today โ”‚ +โ”‚ โ”œโ”€ SELECT * FROM users WHERE active = true... (10:45 AM) โ”‚ +โ”‚ โ”œโ”€ UPDATE products SET price = price * 1.1... (10:32 AM) โ”‚ +โ”‚ โ””โ”€ DELETE FROM temp_data WHERE created_at < ... (09:15 AM) โ”‚ +โ”‚ โ”‚ +โ”‚ Yesterday โ”‚ +โ”‚ โ”œโ”€ CREATE INDEX idx_email ON users(email)... (03:22 PM) โ”‚ +โ”‚ โ””โ”€ SELECT COUNT(*) FROM orders... (02:10 PM) โ”‚ +โ”‚ โ”‚ +โ”‚ Last 7 Days โ”‚ +โ”‚ โ””โ”€ ... โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 4.2 ๅฏผๅ‡บๅ‘ๅฏผ + +``` +โ”Œโ”€ Export Data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [ร—] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Source: [โœ“ users (entire table) โ–ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ Format: [CSV Comma-separated (,) โ–ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โ˜‘ Include column headers โ”‚ โ”‚ +โ”‚ โ”‚ โ˜ Quote all fields โ”‚ โ”‚ +โ”‚ โ”‚ Delimiter: [, โ–ผ] Quote: [" โ–ผ] Escape: [\ โ–ผ] โ”‚ โ”‚ +โ”‚ โ”‚ Encoding: [UTF-8 ] โ”‚ โ”‚ +โ”‚ โ”‚ Line endings: [Unix (LF) โ–ผ] โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ Output: (โ—‹) Copy to clipboard โ”‚ +โ”‚ (โ—) Save to file โ”‚ +โ”‚ [ /home/user/exports/users.csv ] [Browse] โ”‚ +โ”‚ โ”‚ +โ”‚ Preview (first 5 rows): โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ id,name,email,created_at,active โ”‚ โ”‚ +โ”‚ โ”‚ 1,Alice,alice@mail.com,2024-01-15,true โ”‚ โ”‚ +โ”‚ โ”‚ 2,Bob,bob@mail.com,2024-01-16,true โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ [Cancel] [Export] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ็Šถๆ€ๆ ่ฎพ่ฎก่ง„่Œƒ + +### ็Šถๆ€ๆ ๅธƒๅฑ€ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [็Šถๆ€] [่ฟžๆŽฅไฟกๆฏ] โ”‚ [็ผ–็ ] โ”‚ [ๆข่กŒ] โ”‚ [ๆจกๅผ] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ็Šถๆ€ๆŒ‡็คบ + +| ๅŒบๅŸŸ | ๅ†…ๅฎน | ็คบไพ‹ | +|------|------|------| +| **่ฟžๆŽฅ็Šถๆ€** | ๅฝ“ๅ‰่ฟžๆŽฅ็Šถๆ€ๅ’Œ่ฏฆๆƒ… | `โœ“ Connected to MySQL@localhost:3306` | +| **ๆŸฅ่ฏขไฟกๆฏ** | ๆ‰ง่กŒๆ—ถ้—ดๅ’Œๅฝฑๅ“่กŒๆ•ฐ | `โœ“ 127 rows in 0.045s` | +| **้”™่ฏฏไฟกๆฏ** | ้”™่ฏฏๆ่ฟฐ | `โœ• Error: Connection timeout` | +| **็ผ–็ ๆ ผๅผ** | ๆ–‡ไปถ/่ฟžๆŽฅ็ผ–็  | `UTF-8` | +| **ๆข่กŒ็ฌฆ** | ่กŒ็ป“ๆŸ็ฌฆ็ฑปๅž‹ | `LF` / `CRLF` | +| **็ผ–่พ‘ๆจกๅผ** | ็ผ–่พ‘ๅ™จๆจกๅผ | `ins` (ๆ’ๅ…ฅ) / `ovr` (่ฆ†็›–) | + +### ็Šถๆ€ๆ ้ขœ่‰ฒ + +| ็Šถๆ€ | ่ƒŒๆ™ฏ่‰ฒ | ๆ–‡ๅญ—่‰ฒ | ๅ›พๆ ‡ | +|------|--------|--------|------| +| ๆญฃๅธธ | `#f8fafc` | `#64748b` | - | +| ๆˆๅŠŸ | `#d1fae5` | `#065f46` | `โœ“` | +| ่ญฆๅ‘Š | `#fef3c7` | `#92400e` | `โš ` | +| ้”™่ฏฏ | `#fee2e2` | `#991b1b` | `โœ•` | +| ไฟกๆฏ | `#dbeafe` | `#1e40af` | `โ„น` | + +--- + +## ๅ“ๅบ”ๅผ่ฎพ่ฎก + +### ๆ–ญ็‚นๅฎšไน‰ + +| ๆ–ญ็‚น | ๅฎฝๅบฆ่Œƒๅ›ด | ๅธƒๅฑ€่ฐƒๆ•ด | +|------|----------|----------| +| **Small** | < 768px | ไพง่พนๆ ้š่—ไธบๆŠฝๅฑ‰๏ผŒๅ•ๆ ๅธƒๅฑ€ | +| **Medium** | 768px - 1024px | ไพง่พนๆ ๅฏๆŠ˜ๅ ๏ผŒๅŒๆ ๅธƒๅฑ€ | +| **Large** | > 1024px | ๅฎŒๆ•ดๅŒๆ ๅธƒๅฑ€๏ผŒๆ‰€ๆœ‰ๅŠŸ่ƒฝๅฏ่ง | + +### ๅฐๅฑๅน•้€‚้… + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ˜ฐ uzdb [โš™๏ธ] โ”‚ โ† ๆฑ‰ๅ ก่œๅ• +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [โ–ถ] [๐Ÿ’พ] [๐Ÿ“ค] [๐Ÿ”] [โ–ผ] โ”‚ โ† ็ฎ€ๅŒ– toolbar +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ [ๅ…จๅฑ็ผ–่พ‘ๅ™จ/ๆ•ฐๆฎ็ฝ‘ๆ ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ“ Connected โ”‚ UTF-8 โ”‚ ins โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ไธป้ข˜ไธŽ้…่‰ฒ + +### ๆต…่‰ฒไธป้ข˜๏ผˆ้ป˜่ฎค๏ผ‰ + +ๅ‚่€ƒ `/root/codes/project/self/uzdb/doc/design-system.md` ไธญ็š„้ขœ่‰ฒๅฎšไน‰๏ผš + +| ๅ…ƒ็ด  | ้ขœ่‰ฒๅ€ผ | ็”จ้€” | +|------|--------|------| +| ไธป่ƒŒๆ™ฏ | `#ffffff` | ไธปๅ†…ๅฎนๅŒบๅŸŸ | +| ๆฌก็บง่ƒŒๆ™ฏ | `#f8fafc` | ไพง่พนๆ ใ€้ขๆฟ | +| ็ฌฌไธ‰่ƒŒๆ™ฏ | `#f1f5f9` | ๅก็‰‡ใ€ๅˆ†็ป„ | +| ่พนๆก† | `#e2e8f0` | ๅˆ†้š”็บฟใ€่พนๆก† | +| ไธปๆ–‡ๅญ— | `#0f172a` | ๆ ‡้ข˜ใ€ๆญฃๆ–‡ | +| ๆฌก็บงๆ–‡ๅญ— | `#64748b` | ๆ ‡็ญพใ€่ฏดๆ˜Ž | +| ๅผบ่ฐƒ่‰ฒ | `#3b82f6` | ้“พๆŽฅใ€ๆŒ‰้’ฎใ€ๆฟ€ๆดป็Šถๆ€ | + +### ๆทฑ่‰ฒไธป้ข˜๏ผˆๆœชๆฅๆ‰ฉๅฑ•๏ผ‰ + +้ข„็•™ๆทฑ่‰ฒไธป้ข˜ๆ”ฏๆŒ๏ผŒไฝฟ็”จ CSS ๅ˜้‡ๅฎž็Žฐไธป้ข˜ๅˆ‡ๆขใ€‚ + +--- + +## ไบคไบ’็ป†่Š‚่ง„่Œƒ + +### 1. ๆ‚ฌๅœๆ•ˆๆžœ + +| ๅ…ƒ็ด  | ๆ‚ฌๅœๆ•ˆๆžœ | ่ฟ‡ๆธกๆ—ถ้—ด | +|------|----------|----------| +| ๆŒ‰้’ฎ | ่ƒŒๆ™ฏๅ˜ๆทฑ 10% | 150ms | +| ้“พๆŽฅ | ไธ‹ๅˆ’็บฟ + ๅผบ่ฐƒ่‰ฒ | 150ms | +| ่กจๆ ผ่กŒ | ่ƒŒๆ™ฏ่‰ฒ `#f1f5f9` | 100ms | +| ๆ ‘่Š‚็‚น | ่ƒŒๆ™ฏ่‰ฒ `#f1f5f9` | 100ms | + +### 2. ็„ฆ็‚น็Šถๆ€ + +ๆ‰€ๆœ‰ๅฏไบคไบ’ๅ…ƒ็ด ๅฟ…้กปๅ…ทๆœ‰ๅฏ่ง็š„็„ฆ็‚น็Žฏ๏ผš + +```css +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} +``` + +### 3. ๅŠ ่ฝฝ็Šถๆ€ + +| ๅœบๆ™ฏ | ๅŠ ่ฝฝๆŒ‡็คบ | ่ฏดๆ˜Ž | +|------|----------|------| +| ่ฟžๆŽฅๅปบ็ซ‹ | ๆ—‹่ฝฌ Spinner | ็Šถๆ€ๅ˜ไธบ"่ฟžๆŽฅไธญ" | +| ๆŸฅ่ฏขๆ‰ง่กŒ | ่ฟ›ๅบฆๆก + Spinner | ๆ˜พ็คบๆ‰ง่กŒ่ฟ›ๅบฆ | +| ๆ•ฐๆฎๅŠ ่ฝฝ | Skeleton ้ชจๆžถๅฑ | ๆธ่ฟ›ๅผๅŠ ่ฝฝ | +| ๅคงๆ•ฐๆฎๅฏผๅ‡บ | ่ฟ›ๅบฆ็™พๅˆ†ๆฏ” | ๅฏๅ–ๆถˆๆ“ไฝœ | + +### 4. ็ฉบ็Šถๆ€ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ”‚ +โ”‚ ๐Ÿ—„๏ธ โ”‚ +โ”‚ โ”‚ +โ”‚ No Database Connections โ”‚ +โ”‚ โ”‚ +โ”‚ Get started by connecting to your โ”‚ +โ”‚ first database โ”‚ +โ”‚ โ”‚ +โ”‚ [+ New Connection] โ”‚ +โ”‚ โ”‚ +โ”‚ Supported: MySQL, PostgreSQL, โ”‚ +โ”‚ SQLite, MariaDB โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 5. ้”™่ฏฏๆ็คบ + +- **Inline ้”™่ฏฏ**: ่กจๅ•ๅญ—ๆฎตไธ‹ๆ–น็บข่‰ฒๆ–‡ๅญ— +- **Toast ้€š็Ÿฅ**: ๅณไธŠ่ง’ๅผนๅ‡บๆ็คบ๏ผŒ3 ็ง’่‡ชๅŠจๆถˆๅคฑ +- **ๆจกๆ€ๅฏน่ฏๆก†**: ไธฅ้‡้”™่ฏฏ้œ€่ฆ็”จๆˆท็กฎ่ฎค + +--- + +## ๆ— ้šœ็ข่ฎพ่ฎก (Accessibility) + +### WCAG 2.1 AA ๅˆ่ง„ + +- โœ… ๅฏนๆฏ”ๅบฆ่‡ณๅฐ‘ 4.5:1 +- โœ… ้”ฎ็›˜ๅฏผ่ˆชๆ”ฏๆŒ +- โœ… ๅฑๅน•้˜…่ฏปๅ™จๆ ‡็ญพ +- โœ… ็„ฆ็‚นๆŒ‡็คบๅ™จๅฏ่ง +- โœ… ๅฏ่ฐƒๆ•ดๅญ—ไฝ“ๅคงๅฐ๏ผˆๆœ€้ซ˜ 200%๏ผ‰ + +### ้”ฎ็›˜ๅฟซๆท้”ฎๆ€ป่งˆ + +| ๅฟซๆท้”ฎ | ๅŠŸ่ƒฝ | ไฝœ็”จๅŸŸ | +|--------|------|--------| +| `Ctrl+Enter` | ๆ‰ง่กŒๆŸฅ่ฏข | SQL ็ผ–่พ‘ๅ™จ | +| `Ctrl+S` | ไฟๅญ˜ | ๅ…จๅฑ€ | +| `Ctrl+N` | ๆ–ฐๅปบ่ฟžๆŽฅ | ๅ…จๅฑ€ | +| `Ctrl+F` | ๆŸฅๆ‰พ | ็ผ–่พ‘ๅ™จ/ๆ•ฐๆฎ็ฝ‘ๆ ผ | +| `Ctrl+Z` | ๆ’ค้”€ | ็ผ–่พ‘ๅ™จ | +| `Ctrl+Y` | ้‡ๅš | ็ผ–่พ‘ๅ™จ | +| `Ctrl+W` | ๅ…ณ้—ญๆ ‡็ญพ | ๆ ‡็ญพ้กต | +| `F2` | ้‡ๅ‘ฝๅ | ๆ ‘่Š‚็‚น | +| `F5` | ๅˆทๆ–ฐ | ๅ…จๅฑ€ | +| `Delete` | ๅˆ ้™ค | ้€‰ไธญ้กน | +| `Esc` | ๅ–ๆถˆ/ๅ…ณ้—ญ | ๆจกๆ€ๆก† | + +--- + +## ๆ€ง่ƒฝไผ˜ๅŒ–ๅปบ่ฎฎ + +### 1. ่™šๆ‹ŸๆปšๅŠจ + +ๅฏนไบŽๅคงๆ•ฐๆฎ้›†๏ผˆ>100 ่กŒ๏ผ‰๏ผŒไฝฟ็”จ่™šๆ‹ŸๆปšๅŠจๆŠ€ๆœฏ๏ผš +- ๅชๆธฒๆŸ“ๅฏ่งๅŒบๅŸŸ็š„ๆ•ฐๆฎ +- ๆปšๅŠจๆ—ถๅŠจๆ€ๅŠ ่ฝฝๆ•ฐๆฎๅ— +- ไฟๆŒๆปšๅŠจๆกๆ€ป้•ฟๅบฆๆญฃ็กฎ + +### 2. ๆ‡’ๅŠ ่ฝฝ + +- ๆ ‘ๅฝข็ป“ๆž„ๆŒ‰้œ€ๅŠ ่ฝฝๅญ่Š‚็‚น +- ๅ›พ็‰‡/ๅ›พๆ ‡ๅปถ่ฟŸๅŠ ่ฝฝ +- ้žๅ…ณ้”ฎ็ป„ไปถๅผ‚ๆญฅๅŠ ่ฝฝ + +### 3. ็ผ“ๅญ˜็ญ–็•ฅ + +- ๆŸฅ่ฏข็ป“ๆžœ็ŸญๆœŸ็ผ“ๅญ˜๏ผˆ5 ๅˆ†้’Ÿ๏ผ‰ +- Schema ไฟกๆฏไธญๆœŸ็ผ“ๅญ˜๏ผˆ30 ๅˆ†้’Ÿ๏ผ‰ +- ่ฟžๆŽฅไฟกๆฏ้•ฟๆœŸ็ผ“ๅญ˜๏ผˆ็›ดๅˆฐๆ–ญๅผ€๏ผ‰ + +--- + +## ๅผ€ๅ‘ๅฎž็Žฐๅปบ่ฎฎ + +### ๆŽจ่็ป„ไปถๅบ“ + +| ็ป„ไปถ็ฑปๅž‹ | ๆŽจ่ๅบ“ | ่ฏดๆ˜Ž | +|----------|--------|------| +| UI ็ป„ไปถ | Radix UI / Headless UI | ๆ— ๆ ทๅผ็ป„ไปถ๏ผŒ็ตๆดปๅฎšๅˆถ | +| ๆ•ฐๆฎ็ฝ‘ๆ ผ | TanStack Table / AG Grid | ้ซ˜ๆ€ง่ƒฝ่กจๆ ผ | +| ไปฃ็ ็ผ–่พ‘ | Monaco Editor / CodeMirror | SQL ่ฏญๆณ•ๆ”ฏๆŒ | +| ๅ›พ่กจ | Recharts / Chart.js | ๆ•ฐๆฎ็ปŸ่ฎกๅฑ•็คบ | +| ๅ›พๆ ‡ | Lucide Icons / Heroicons | ไธ€่‡ด้ฃŽๆ ผ | + +### Wails ้›†ๆˆ่ฆ็‚น + +1. **ๅ‰ๅŽ็ซฏ้€šไฟก**: ไฝฟ็”จ Wails Events ๅฎž็Žฐๅฎžๆ—ถ้€š็Ÿฅ +2. **็Šถๆ€็ฎก็†**: Zustand / Jotai ่ฝป้‡็บงๆ–นๆกˆ +3. **่ทฏ็”ฑ**: ๅ•้กตๅบ”็”จ๏ผŒๅŸบไบŽ็Šถๆ€็š„่ง†ๅ›พๅˆ‡ๆข +4. **ๆ ทๅผ**: TailwindCSS + CSS Variables ไธป้ข˜็ณป็ปŸ + +--- + +## ้™„ๅฝ•๏ผš็ป„ไปถๅฑ‚็บง็ป“ๆž„ + +``` +App +โ”œโ”€โ”€ MenuBar +โ”œโ”€โ”€ ToolBar +โ”œโ”€โ”€ MainContent +โ”‚ โ”œโ”€โ”€ Sidebar +โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionPanel +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionHeader +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionList +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ConnectionItem +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ StatusIndicator +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ConnectionName +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ContextMenu +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ NewConnectionButton +โ”‚ โ”‚ โ””โ”€โ”€ SchemaExplorer +โ”‚ โ”‚ โ””โ”€โ”€ SchemaTree +โ”‚ โ””โ”€โ”€ Workspace +โ”‚ โ”œโ”€โ”€ TabBar +โ”‚ โ””โ”€โ”€ TabContent +โ”‚ โ”œโ”€โ”€ SQLEditor +โ”‚ โ”‚ โ”œโ”€โ”€ EditorToolbar +โ”‚ โ”‚ โ”œโ”€โ”€ CodeEditor +โ”‚ โ”‚ โ””โ”€โ”€ ResultsPanel +โ”‚ โ”œโ”€โ”€ DataTable +โ”‚ โ”‚ โ”œโ”€โ”€ TableToolbar +โ”‚ โ”‚ โ”œโ”€โ”€ DataGrid +โ”‚ โ”‚ โ””โ”€โ”€ Pagination +โ”‚ โ””โ”€โ”€ TableStructure +โ”‚ โ”œโ”€โ”€ StructureTabs +โ”‚ โ””โ”€โ”€ StructureContent +โ””โ”€โ”€ StatusBar +``` + +--- + +## ็‰ˆๆœฌๅކๅฒ + +| ็‰ˆๆœฌ | ๆ—ฅๆœŸ | ไฝœ่€… | ๅ˜ๆ›ด่ฏดๆ˜Ž | +|------|------|------|----------| +| 1.0 | 2026-03-29 | Design Team | ๅˆๅง‹็‰ˆๆœฌ๏ผŒๅฎŒๆ•ดๅธƒๅฑ€่ฎพ่ฎก | + +--- + +## ๅ‚่€ƒๆ–‡ๆกฃ + +- [Design System](./design-system.md) - ่ง†่ง‰่ฎพ่ฎก่ง„่Œƒ +- [Wireframes](./wireframes.md) - ๅŽŸๅง‹็บฟๆก†ๅ›พ +- [Features](./features.md) - ๅŠŸ่ƒฝ่ง„ๆ ผ่ฏดๆ˜Ž +- [User Flows](./user-flows.md) - ็”จๆˆทๆต็จ‹ diff --git a/doc/user-flows.md b/doc/user-flows.md new file mode 100644 index 0000000..9b4ae89 --- /dev/null +++ b/doc/user-flows.md @@ -0,0 +1,396 @@ +# uzdb User Flows + +## 1. First-Time User Experience + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Launch App โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Empty State: โ”‚ +โ”‚ "No Connections"โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Click "New Connection" + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Connection โ”‚ +โ”‚ Wizard โ”‚ +โ”‚ 1. Select Type โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 2. Configure โ”‚ +โ”‚ Connection โ”‚ +โ”‚ (Host, Port, โ”‚ +โ”‚ Credentials) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Click "Test Connection" + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Test Result โ”‚ +โ”‚ โœ“ Success โ”‚ +โ”‚ โœ• Error + Help โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Save & Connect + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Main Interface โ”‚ +โ”‚ with Schema โ”‚ +โ”‚ Explorer โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 2. Query Execution Flow + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Open Query Tab โ”‚ +โ”‚ (Ctrl+T) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Write/Edit SQL โ”‚ +โ”‚ - Syntax โ”‚ +โ”‚ highlighting โ”‚ +โ”‚ - Autocomplete โ”‚ +โ”‚ - Snippets โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Ctrl+Enter or Click Run + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Execute Query โ”‚ +โ”‚ Show loading โ”‚ +โ”‚ indicator โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Display Results โ”‚ +โ”‚ - Data grid โ”‚ +โ”‚ - Row count โ”‚ +โ”‚ - Execution timeโ”‚ +โ”‚ - Status โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Export Data โ”‚ โ”‚ Edit Cell โ”‚ โ”‚ Save Query โ”‚ +โ”‚ (CSV, JSON) โ”‚ โ”‚ (Inline) โ”‚ โ”‚ (.sql file) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 3. Table Browsing Flow + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Expand Database โ”‚ +โ”‚ in Sidebar โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Expand Schema โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Click Table โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Preview Top โ”‚ โ”‚ View โ”‚ โ”‚ Open Table โ”‚ +โ”‚ 100 Rows โ”‚ โ”‚ Structure โ”‚ โ”‚ Details โ”‚ +โ”‚ (Double-click)โ”‚ โ”‚ (Right panel)โ”‚ โ”‚ Panel โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Columns, Indexesโ”‚ + โ”‚ Foreign Keys, โ”‚ + โ”‚ Triggers โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 4. Connection Management Flow + +### Add New Connection +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Click "+" โ”‚ +โ”‚ or File > New โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Select DB Type โ”‚ +โ”‚ โ—‹ MySQL โ”‚ +โ”‚ โ—‹ PostgreSQL โ”‚ +โ”‚ โ—‹ SQLite โ”‚ +โ”‚ โ—‹ MariaDB โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Fill Form โ”‚ +โ”‚ - Name โ”‚ +โ”‚ - Host/Port โ”‚ +โ”‚ - Auth โ”‚ +โ”‚ - Advanced opts โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Test Connection + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Connection Test โ”‚ +โ”‚ Result โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Success โ†’ Save + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Added to โ”‚ +โ”‚ Connection List โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Edit/Delete Connection +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Right-click โ”‚ +โ”‚ Connection โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Edit โ”‚ โ”‚ Duplicate โ”‚ โ”‚ Delete โ”‚ +โ”‚ Connection โ”‚ โ”‚ Connection โ”‚ โ”‚ Connection โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Confirm Delete โ”‚ + โ”‚ "Are you sure?" โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Connection โ”‚ + โ”‚ Removed โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 5. Data Export Flow + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Select Data โ”‚ +โ”‚ (table/query โ”‚ +โ”‚ results) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Click Export button + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Export Dialog โ”‚ +โ”‚ - Format: โ”‚ +โ”‚ CSV/JSON/SQL โ”‚ +โ”‚ - Options โ”‚ +โ”‚ - Destination โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Configure options + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Review Settings โ”‚ +โ”‚ - Preview โ”‚ +โ”‚ - Row count โ”‚ +โ”‚ - File size est.โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Click Export + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Export Progress โ”‚ +โ”‚ [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘] 80%โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Export Complete โ”‚ +โ”‚ โœ“ 1,234 rows โ”‚ +โ”‚ exported to โ”‚ +โ”‚ /path/file.csvโ”‚ +โ”‚ โ”‚ +โ”‚ [Open File] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 6. Error Handling Flow + +### Connection Error +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Attempt Connect โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Fails + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Error Dialog โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โš  Connectionโ”‚ โ”‚ +โ”‚ โ”‚ Failed โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ ECONNREFUSEDโ”‚ โ”‚ +โ”‚ โ”‚ localhost: โ”‚ โ”‚ +โ”‚ โ”‚ 3306 โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ Common causes: โ”‚ +โ”‚ โ€ข Server not โ”‚ +โ”‚ running โ”‚ +โ”‚ โ€ข Wrong port โ”‚ +โ”‚ โ€ข Firewall โ”‚ +โ”‚ โ”‚ +โ”‚ [Retry] [Help] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Query Error +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Execute Query โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ Syntax Error + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Results Panel โ”‚ +โ”‚ Shows error: โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โŒ Error โ”‚ โ”‚ +โ”‚ โ”‚ Line 3: โ”‚ โ”‚ +โ”‚ โ”‚ Unknown col โ”‚ โ”‚ +โ”‚ โ”‚ 'emai' โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ SELECT id, โ”‚ โ”‚ +โ”‚ โ”‚ emai โ”‚ โ”‚ +โ”‚ โ”‚ ^^^^ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 7. Keyboard Navigation Flow + +``` +Global Shortcuts: +- Ctrl+N: New connection +- Ctrl+T: New query tab +- Ctrl+S: Save +- Ctrl+W: Close tab +- Ctrl+Enter: Run query +- Ctrl+F: Find in editor +- Ctrl+,: Settings + +Within Data Grid: +- Arrow keys: Navigate cells +- Enter: Edit cell +- Esc: Cancel edit +- Ctrl+C: Copy selection +- Ctrl+F: Filter column + +Within Query Editor: +- Ctrl+Space: Autocomplete +- Tab: Insert snippet +- Ctrl+/: Toggle comment +- Ctrl+D: Duplicate line +``` + +--- + +## 8. Settings/Preferences Flow + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Tools > Settingsโ”‚ +โ”‚ or Ctrl+, โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Settings Modal โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ General โ”‚ โ”‚ โ† Selected +โ”‚ โ”‚ Editor โ”‚ โ”‚ +โ”‚ โ”‚ Data Grid โ”‚ โ”‚ +โ”‚ โ”‚ Keybindings โ”‚ โ”‚ +โ”‚ โ”‚ Themes โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Font size โ”‚ โ”‚ +โ”‚ โ”‚ [14 โ–ผ] โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ Theme โ”‚ โ”‚ +โ”‚ โ”‚ [Light โ–ผ] โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ Auto-save โ”‚ โ”‚ +โ”‚ โ”‚ [โœ“] โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ [Reset] [OK] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## Edge Cases Handled + +1. **Lost Connection During Query** + - Show error message + - Offer to reconnect + - Preserve query text + +2. **Large Result Sets (>10k rows)** + - Warn user before loading all + - Offer to limit results + - Provide streaming option + +3. **Unsaved Changes on Close** + - Prompt to save + - Auto-save to temp file + - Restore on relaunch + +4. **Multiple Tabs with Same Connection** + - Share connection instance + - Independent transactions + - Clear tab indicators + +5. **Long-running Queries** + - Show progress if available + - Provide cancel button + - Timeout configuration diff --git a/doc/wireframes.md b/doc/wireframes.md new file mode 100644 index 0000000..ac23a6c --- /dev/null +++ b/doc/wireframes.md @@ -0,0 +1,228 @@ +# uzdb UI Wireframes + +## Main Application Window + +### 1. Connection Manager (Startup Screen) +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ uzdb - Connect โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ + New Connection โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ ๐Ÿ—„๏ธ Local MySQL โ”‚ โ”‚ +โ”‚ โ”‚ localhost:3306 โ€ข mysql โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ ๐Ÿ˜ Production PostgreSQL โ”‚ โ”‚ +โ”‚ โ”‚ prod-db.example.com:5432 โ€ข postgres โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ—ช Local SQLite โ”‚ โ”‚ +โ”‚ โ”‚ ~/data/app.db โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ [New Connection] [Edit] [Delete] [Connect] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 2. Main Application Layout +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ File Edit View Query Tools Help โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [โ–ถ Run] [๐Ÿ’พ Save] [๐Ÿ“ค Export] [๐Ÿ” Find] [Connection: โ–ผ] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ โ”Œโ”€ Query Editor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ ๐Ÿ—„๏ธ Database โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”œโ”€ ๐Ÿ—„๏ธ MySQL โ”‚ โ”‚ SELECT * FROM users โ”‚ โ”‚ +โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“Š public โ”‚ WHERE active = 1; โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ users โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€ ๐Ÿ“‹ products โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€ ๐Ÿ“‹ orders โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€ ๐Ÿ“Š information_schema โ”‚ โ”‚ โ”‚ +โ”‚ โ”œโ”€ ๐Ÿ˜ PostgreSQLโ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ””โ”€ ๐Ÿ“Š public โ”‚ 127 rows โ”‚ 0.045s โ”‚ +โ”‚ โ””โ”€ โ—ช SQLite โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ Results โ”‚ +โ”‚ Connections โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ (drag to add) โ”‚ โ”‚ id โ”‚ name โ”‚ email โ”‚ active โ”‚ โ”‚ +โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ 1 โ”‚ Alice โ”‚ alice@mail.com โ”‚ โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ 2 โ”‚ Bob โ”‚ bob@mail.com โ”‚ โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ 3 โ”‚ Charlie โ”‚ charlie@mail.c โ”‚ โœ— โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ [<] [1] [2] [3] [...] [10] [>] Per page: [25โ–ผ] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ“ Connected to MySQL@localhost:3306 โ”‚ UTF-8 โ”‚ LF โ”‚ ins โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 3. Query Editor Tab +``` +โ”Œโ”€ Untitled.sql โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1 โ”‚ SELECT u.id, u.name, COUNT(o.id) as order_count โ”‚ +โ”‚ 2 โ”‚ FROM users u โ”‚ +โ”‚ 3 โ”‚ LEFT JOIN orders o ON u.id = o.user_id โ”‚ +โ”‚ 4 โ”‚ WHERE u.created_at > '2024-01-01' โ”‚ +โ”‚ 5 โ”‚ GROUP BY u.id, u.name โ”‚ +โ”‚ 6 โ”‚ HAVING COUNT(o.id) > 5 โ”‚ +โ”‚ 7 โ”‚ ORDER BY order_count DESC โ”‚ +โ”‚ 8 โ”‚ LIMIT 100; โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ–ถ Run (Ctrl+Enter) โ”‚ Explain โ”‚ Format โ”‚ History โ”‚ Snippets +``` + +### 4. Table Data View +``` +โ”Œโ”€ public.users โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ [Filters] [Sort] [Group By] [Refresh] [Export] [Import] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ˜‘ โ”‚ id โ”‚ name โ”‚ email โ”‚ created_at โ”‚ [actions] โ”‚ +โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ“ โ”‚ 1 โ”‚ Alice โ”‚ alice@mail.com โ”‚ 2024-01-15 โ”‚ โœ๏ธ ๐Ÿ—‘๏ธ โ”‚ +โ”‚ โœ“ โ”‚ 2 โ”‚ Bob โ”‚ bob@mail.com โ”‚ 2024-01-16 โ”‚ โœ๏ธ ๐Ÿ—‘๏ธ โ”‚ +โ”‚ โœ“ โ”‚ 3 โ”‚ Charlie โ”‚ charlie@mail.co โ”‚ 2024-01-17 โ”‚ โœ๏ธ ๐Ÿ—‘๏ธ โ”‚ +โ”‚ โœ“ โ”‚ 4 โ”‚ Diana โ”‚ diana@mail.com โ”‚ 2024-01-18 โ”‚ โœ๏ธ ๐Ÿ—‘๏ธ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +โ”‚ Showing 1-25 of 1,247 rows [First] [Prev] [1] [2] [Next] [Last]โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 5. Table Details Panel (Right Sidebar) +``` +โ”Œโ”€ Table Info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ“‹ users โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Columns (5) โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ name โ”‚ type โ”‚ null โ”‚ key โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ id โ”‚ int โ”‚ โŒ โ”‚ PK โ”‚ โ”‚ +โ”‚ โ”‚ name โ”‚ varchar โ”‚ โŒ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ email โ”‚ varchar โ”‚ โŒ โ”‚ UK โ”‚ โ”‚ +โ”‚ โ”‚ created_at โ”‚ timestamp โ”‚ โœ“ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ active โ”‚ boolean โ”‚ โŒ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ Indexes (2) โ”‚ +โ”‚ โ”œโ”€ PRIMARY (id) โ”‚ +โ”‚ โ””โ”€ idx_email (email) โ”‚ +โ”‚ โ”‚ +โ”‚ Foreign Keys (0) โ”‚ +โ”‚ โ”‚ +โ”‚ Triggers (1) โ”‚ +โ”‚ โ””โ”€ update_timestamp โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 6. New Connection Dialog +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ New Connection [โœ•] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Database Type: [MySQL โ–ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Connection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Name: [Local Development ] โ”‚ โ”‚ +โ”‚ โ”‚ Host: [localhost ] โ”‚ โ”‚ +โ”‚ โ”‚ Port: [3306 ] โ”‚ โ”‚ +โ”‚ โ”‚ Username: [root ] โ”‚ โ”‚ +โ”‚ โ”‚ Password: [โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข ] ๐Ÿ‘ โ”‚ โ”‚ +โ”‚ โ”‚ Database: [ ] โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Advanced โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ SSL: [โ–ผ] โ”‚ โ”‚ +โ”‚ โ”‚ Timeout: [30] seconds โ”‚ โ”‚ +โ”‚ โ”‚ Max connections: [10] โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ [Test Connection] [Cancel] [Save & Connect] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 7. Export Data Dialog +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Export Data [โœ•] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Source: [โœ“ users (entire table) โ–ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ Format: [CSV Comma-separated (.) โ–ผ] โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โ˜‘ Include column headers โ”‚ โ”‚ +โ”‚ โ”‚ โ˜ Quote all fields โ”‚ โ”‚ +โ”‚ โ”‚ Encoding: [UTF-8 ] โ”‚ โ”‚ +โ”‚ โ”‚ Line endings: [Unix (LF) โ–ผ] โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ Output: (โ—‹) Copy to clipboard โ”‚ +โ”‚ (โ—) Save to file โ”‚ +โ”‚ [ /home/user/exports/users.csv ] [...] โ”‚ +โ”‚ โ”‚ +โ”‚ [Cancel] [Export] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 8. Empty State - No Connections +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ—„๏ธ โ”‚ +โ”‚ โ”‚ +โ”‚ No Database Connections โ”‚ +โ”‚ โ”‚ +โ”‚ Get started by connecting to your first database โ”‚ +โ”‚ โ”‚ +โ”‚ โ”‚ +โ”‚ [+ New Connection] โ”‚ +โ”‚ โ”‚ +โ”‚ Supported: MySQL, PostgreSQL, SQLite, MariaDB โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Responsive Considerations + +### Small Screens (< 768px) +- Sidebar becomes collapsible drawer +- Tabs stack vertically +- Toolbar items move to overflow menu +- Data grid shows horizontal scroll + +### Medium Screens (768px - 1024px) +- Sidebar can be collapsed +- Single query editor tab visible +- Reduced panel padding + +--- + +## Component States + +### Button States +- **Default**: Primary color background +- **Hover**: Darken 10% +- **Active**: Darken 20%, slight inset shadow +- **Disabled**: 50% opacity, not clickable +- **Loading**: Spinner replaces icon/text + +### Input States +- **Default**: Light border +- **Focus**: Primary color ring +- **Error**: Red border + error message +- **Disabled**: Gray background, not editable +- **Read-only**: Subtle background, copyable + +### Connection Status +- **Connected**: Green dot + "Connected" +- **Connecting**: Spinning loader +- **Disconnected**: Gray dot + "Disconnected" +- **Error**: Red dot + error tooltip diff --git a/uzdb/.gitignore b/uzdb/.gitignore new file mode 100644 index 0000000..d933552 --- /dev/null +++ b/uzdb/.gitignore @@ -0,0 +1,46 @@ +# Binaries +*.exe +*.test +*.out +uzdb.exe +build/bin/ + +# Dependency directories +vendor/ + +# Node modules (frontend) +node_modules/ +frontend/dist/ +frontend/.vite/ + +# Wails build artifacts +wails_build/ + +# Data directory (contains user data) +data/ +*.db +*.sqlite +*.sqlite3 + +# Log files +*.log +logs/ + +# Environment variables +.env +.env.local + +# Temporary files +tmp/ +temp/ +*.tmp + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*~ + +# OS files +.DS_Store +Thumbs.db diff --git a/uzdb/API_TEST.md b/uzdb/API_TEST.md new file mode 100644 index 0000000..cb06210 --- /dev/null +++ b/uzdb/API_TEST.md @@ -0,0 +1,164 @@ +# API Testing Guide + +## Base URL +``` +Development: http://localhost:8080/api +``` + +## Connection Management + +### List all connections +```bash +curl http://localhost:8080/api/connections +``` + +### Create a new connection +```bash +curl -X POST http://localhost:8080/api/connections \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Database", + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "password123", + "database": "mydb" + }' +``` + +### Update a connection +```bash +curl -X PUT http://localhost:8080/api/connections/{id} \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Updated Name", + "host": "new-host.com" + }' +``` + +### Delete a connection +```bash +curl -X DELETE http://localhost:8080/api/connections/{id} +``` + +### Test connection +```bash +curl -X POST http://localhost:8080/api/connections/{id}/test +``` + +## Query Operations + +### Execute a query +```bash +curl -X POST http://localhost:8080/api/query \ + -H "Content-Type: application/json" \ + -d '{ + "connectionId": "{connection-id}", + "sql": "SELECT * FROM users LIMIT 10" + }' +``` + +### Get tables from a connection +```bash +curl http://localhost:8080/api/connections/{id}/tables +``` + +### Get table data +```bash +curl "http://localhost:8080/api/connections/{id}/tables/users/data?limit=20&offset=0" +``` + +### Get table structure +```bash +curl http://localhost:8080/api/connections/{id}/tables/users/structure +``` + +## Saved Queries + +### List saved queries +```bash +curl http://localhost:8080/api/saved-queries +``` + +### Save a query +```bash +curl -X POST http://localhost:8080/api/saved-queries \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Daily Users Report", + "sql": "SELECT * FROM users WHERE created_at > NOW() - INTERVAL 1 DAY", + "connectionId": "{connection-id}" + }' +``` + +### Execute saved query +```bash +curl -X POST http://localhost:8080/api/saved-queries/{id}/execute +``` + +## Query History + +### Get query history +```bash +curl http://localhost:8080/api/history?limit=50 +``` + +### Clear query history +```bash +curl -X DELETE http://localhost:8080/api/history +``` + +## Response Format + +All responses follow this format: + +### Success +```json +{ + "success": true, + "data": { ... }, + "message": "" +} +``` + +### Error +```json +{ + "success": false, + "data": null, + "error": "Error message here", + "code": "CONNECTION_NOT_FOUND" +} +``` + +## Using Wails Bindings (from Frontend) + +```javascript +// Import Wails runtime +import { Events } from "@wailsio/runtime"; + +// Get all connections +const connections = await window.go.app.GetConnections(); + +// Create connection +await window.go.app.CreateConnection({ + name: "Test DB", + type: "mysql", + host: "localhost", + port: 3306, + username: "root", + password: "secret", + database: "test" +}); + +// Execute query +const result = await window.go.app.ExecuteQuery( + connectionId, + "SELECT * FROM users" +); + +console.log("Rows:", result.rows); +console.log("Columns:", result.columns); +console.log("Execution time:", result.executionTimeMs); +``` diff --git a/uzdb/README.md b/uzdb/README.md new file mode 100644 index 0000000..9a8af52 --- /dev/null +++ b/uzdb/README.md @@ -0,0 +1,228 @@ +# uzdb - Lightweight Database Client + +A modern, lightweight database management tool inspired by DBeaver and Navicat, built with Wails (Go + React). + +## ๐Ÿš€ Features + +- **Multi-Database Support**: MySQL, PostgreSQL, SQLite +- **Modern UI**: Clean interface with dark/light themes +- **SQL Editor**: Syntax highlighting, autocomplete, query history +- **Data Grid**: Sortable, filterable, editable data view +- **Connection Management**: Secure credential storage with encryption +- **Fast & Lightweight**: Minimal resource usage, quick startup + +## ๐Ÿ› ๏ธ Tech Stack + +### Frontend +- **Framework**: React 18 + TypeScript +- **Build Tool**: Vite +- **Styling**: CSS Variables + Modern CSS +- **Icons**: Lucide Icons + +### Backend +- **Language**: Go 1.23+ +- **Web Framework**: Gin +- **ORM**: GORM +- **Database**: SQLite3 (for app data) +- **Logging**: Zap +- **Desktop**: Wails v2 + +## ๐Ÿ“ Project Structure + +``` +uzdb/ +โ”œโ”€โ”€ frontend/ # React application +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # UI components +โ”‚ โ”‚ โ”œโ”€โ”€ mock/ # Mock data for development +โ”‚ โ”‚ โ””โ”€โ”€ index.css # Global styles +โ”‚ โ””โ”€โ”€ package.json +โ”œโ”€โ”€ internal/ # Go backend (private packages) +โ”‚ โ”œโ”€โ”€ app/ # Wails bindings +โ”‚ โ”œโ”€โ”€ config/ # Configuration management +โ”‚ โ”œโ”€โ”€ models/ # Data models +โ”‚ โ”œโ”€โ”€ database/ # Database connections +โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”œโ”€โ”€ handler/ # HTTP handlers +โ”‚ โ”œโ”€โ”€ middleware/ # HTTP middleware +โ”‚ โ””โ”€โ”€ utils/ # Utility functions +โ”œโ”€โ”€ doc/ # Design documentation +โ”‚ โ”œโ”€โ”€ features.md # Feature specifications +โ”‚ โ”œโ”€โ”€ design-system.md # Visual design system +โ”‚ โ”œโ”€โ”€ wireframes.md # UI wireframes +โ”‚ โ”œโ”€โ”€ user-flows.md # User interaction flows +โ”‚ โ””โ”€โ”€ layout-design.md # Layout specifications +โ”œโ”€โ”€ main.go # Application entry point +โ””โ”€โ”€ go.mod # Go dependencies +``` + +## ๐Ÿšฆ Getting Started + +### Prerequisites + +- **Go** 1.23 or higher +- **Node.js** 18 or higher +- **Wails CLI** (`go install github.com/wailsapp/wails/v2/cmd/wails@latest`) + +### Installation + +1. **Clone the repository** +```bash +git clone https://github.com/your-org/uzdb.git +cd uzdb +``` + +2. **Install Go dependencies** +```bash +go mod tidy +``` + +3. **Install frontend dependencies** +```bash +cd frontend +npm install +cd .. +``` + +### Development + +#### Option 1: Run with Wails Dev Server (Recommended) +```bash +wails dev +``` + +This starts both the Go backend and React frontend with hot reload. + +#### Option 2: Run Components Separately + +**Frontend only:** +```bash +cd frontend +npm run dev +``` + +**Backend HTTP API only:** +```bash +go run . -http-port 8080 +``` + +Then access API at `http://localhost:8080/api` + +### Building for Production + +```bash +wails build +``` + +The compiled binary will be in `build/bin/`. + +## ๐Ÿ“– Usage + +### Connecting to a Database + +1. Click **"New Connection"** in the left sidebar +2. Select database type (MySQL, PostgreSQL, or SQLite) +3. Fill in connection details: + - **Name**: Friendly name for the connection + - **Host**: Database server hostname + - **Port**: Database port (default: 3306 for MySQL, 5432 for PostgreSQL) + - **Username/Password**: Authentication credentials + - **Database**: Default database name +4. Click **"Test Connection"** to verify +5. Click **"Save & Connect"** + +### Executing Queries + +1. Select a connection from the left sidebar +2. Open a new query tab (Ctrl+T) +3. Write your SQL query +4. Press **Ctrl+Enter** or click **Run** to execute +5. View results in the data grid below + +### Keyboard Shortcuts + +| Action | Shortcut | +|--------|----------| +| New Connection | Ctrl+N | +| New Query Tab | Ctrl+T | +| Execute Query | Ctrl+Enter | +| Save Query | Ctrl+S | +| Find | Ctrl+F | +| Close Tab | Ctrl+W | +| Settings | Ctrl+, | + +## ๐Ÿ”Œ API Reference + +The application exposes both Wails bindings (for the desktop app) and HTTP API (for debugging/integration). + +### Wails Bindings (JavaScript) +```javascript +// Get all connections +const connections = await window.go.app.GetConnections(); + +// Execute a query +const result = await window.go.app.ExecuteQuery(connectionId, sql); + +// Get table data +const data = await window.go.app.GetTableData(connectionId, tableName, limit, offset); +``` + +### HTTP API +See [API_TEST.md](./API_TEST.md) for detailed API documentation. + +## ๐Ÿ”’ Security + +- All database passwords are encrypted using AES-256-GCM before storage +- Encryption key is derived from a master password (future feature) or system keyring +- No credentials stored in plain text +- SSL/TLS support for database connections + +## ๐Ÿ“Š Supported Databases + +| Database | Version | Status | +|----------|---------|--------| +| MySQL | 5.7+, 8.0+ | โœ… Supported | +| PostgreSQL | 12+ | โœ… Supported | +| SQLite | 3.x | โœ… Supported | +| MariaDB | 10.3+ | ๐Ÿ”„ Planned | +| SQL Server | 2019+ | ๐Ÿ”„ Planned | + +## ๐Ÿค Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +### Development Guidelines + +- Follow existing code style +- Add tests for new features +- Update documentation as needed +- Ensure linting passes (`go vet`, `npm run lint`) + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## ๐Ÿ™ Acknowledgments + +- [Wails](https://wails.io/) - Desktop framework +- [Gin](https://gin-gonic.com/) - Web framework +- [GORM](https://gorm.io/) - ORM library +- [React](https://react.dev/) - UI library +- [DBeaver](https://dbeaver.io/) - Inspiration +- [Navicat](https://www.navicat.com/) - Inspiration + +## ๐Ÿ“ž Support + +- **Issues**: [GitHub Issues](https://github.com/your-org/uzdb/issues) +- **Discussions**: [GitHub Discussions](https://github.com/your-org/uzdb/discussions) +- **Documentation**: `/doc` directory + +--- + +**Built with โค๏ธ using Go + React** diff --git a/uzdb/app.go b/uzdb/app.go new file mode 100644 index 0000000..af53038 --- /dev/null +++ b/uzdb/app.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" +) + +// App struct +type App struct { + ctx context.Context +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts. The context is saved +// so we can call the runtime methods +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +// Greet returns a greeting for the given name +func (a *App) Greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) +} diff --git a/uzdb/build.sh b/uzdb/build.sh new file mode 100644 index 0000000..9daaa77 --- /dev/null +++ b/uzdb/build.sh @@ -0,0 +1,5 @@ +# Build the application for development +go build -tags dev -o uzdb.exe + +# Or run directly with Wails dev server +wails dev diff --git a/uzdb/build/README.md b/uzdb/build/README.md new file mode 100644 index 0000000..1ae2f67 --- /dev/null +++ b/uzdb/build/README.md @@ -0,0 +1,35 @@ +# Build Directory + +The build directory is used to house all the build files and assets for your application. + +The structure is: + +* bin - Output directory +* darwin - macOS specific files +* windows - Windows specific files + +## Mac + +The `darwin` directory holds files specific to Mac builds. +These may be customised and used as part of the build. To return these files to the default state, simply delete them +and +build with `wails build`. + +The directory contains the following files: + +- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. + +## Windows + +The `windows` directory contains the manifest and rc files used when building with `wails build`. +These may be customised for your application. To return these files to the default state, simply delete them and +build with `wails build`. + +- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to + use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file + will be created using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, + as well as the application itself (right click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file diff --git a/uzdb/build/appicon.png b/uzdb/build/appicon.png new file mode 100644 index 0000000..63617fe Binary files /dev/null and b/uzdb/build/appicon.png differ diff --git a/uzdb/build/darwin/Info.dev.plist b/uzdb/build/darwin/Info.dev.plist new file mode 100644 index 0000000..14121ef --- /dev/null +++ b/uzdb/build/darwin/Info.dev.plist @@ -0,0 +1,68 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + diff --git a/uzdb/build/darwin/Info.plist b/uzdb/build/darwin/Info.plist new file mode 100644 index 0000000..d17a747 --- /dev/null +++ b/uzdb/build/darwin/Info.plist @@ -0,0 +1,63 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} + + diff --git a/uzdb/build/windows/icon.ico b/uzdb/build/windows/icon.ico new file mode 100644 index 0000000..f334798 Binary files /dev/null and b/uzdb/build/windows/icon.ico differ diff --git a/uzdb/build/windows/info.json b/uzdb/build/windows/info.json new file mode 100644 index 0000000..9727946 --- /dev/null +++ b/uzdb/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.Info.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.Info.ProductVersion}}", + "CompanyName": "{{.Info.CompanyName}}", + "FileDescription": "{{.Info.ProductName}}", + "LegalCopyright": "{{.Info.Copyright}}", + "ProductName": "{{.Info.ProductName}}", + "Comments": "{{.Info.Comments}}" + } + } +} \ No newline at end of file diff --git a/uzdb/build/windows/installer/project.nsi b/uzdb/build/windows/installer/project.nsi new file mode 100644 index 0000000..654ae2e --- /dev/null +++ b/uzdb/build/windows/installer/project.nsi @@ -0,0 +1,114 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the ProjectInfo file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" +## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" +## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" +## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/uzdb/build/windows/installer/wails_tools.nsh b/uzdb/build/windows/installer/wails_tools.nsh new file mode 100644 index 0000000..2f6d321 --- /dev/null +++ b/uzdb/build/windows/installer/wails_tools.nsh @@ -0,0 +1,249 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "{{.Name}}" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "{{.Info.CompanyName}}" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "{{.Info.ProductName}}" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "{{.Info.Copyright}}" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "tmp\MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + {{range .Info.FileAssociations}} + !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + File "..\{{.IconName}}.ico" + {{end}} +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + {{range .Info.FileAssociations}} + !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" + + Delete "$INSTDIR\{{.IconName}}.ico" + {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} +!macroend diff --git a/uzdb/build/windows/wails.exe.manifest b/uzdb/build/windows/wails.exe.manifest new file mode 100644 index 0000000..17e1a23 --- /dev/null +++ b/uzdb/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/uzdb/frontend/index.html b/uzdb/frontend/index.html new file mode 100644 index 0000000..75112e3 --- /dev/null +++ b/uzdb/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + uzdb + + +
+ + + + diff --git a/uzdb/frontend/package-lock.json b/uzdb/frontend/package-lock.json new file mode 100644 index 0000000..f68b857 --- /dev/null +++ b/uzdb/frontend/package-lock.json @@ -0,0 +1,2121 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.0.1", + "typescript": "^4.6.4", + "vite": "^3.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", + "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.19.6", + "@babel/plugin-transform-react-jsx": "^7.19.0", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.26.7", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^3.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", + "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.328", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", + "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", + "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "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 + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", + "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true + }, + "@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "requires": { + "@babel/types": "^7.27.3" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, + "@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true + }, + "@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "requires": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + } + }, + "@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "requires": { + "@babel/types": "^7.29.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } + }, + "@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + } + }, + "@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + } + }, + "@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "dev": true, + "optional": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "requires": {} + }, + "@vitejs/plugin-react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", + "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", + "dev": true, + "requires": { + "@babel/core": "^7.19.6", + "@babel/plugin-transform-react-jsx": "^7.19.0", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.26.7", + "react-refresh": "^0.14.0" + } + }, + "baseline-browser-mapping": { + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", + "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "dev": true + }, + "browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "requires": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "electron-to-chromium": { + "version": "1.5.328", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", + "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "dev": true + }, + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "dev": true, + "optional": true + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "magic-string": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", + "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, + "node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "requires": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true + }, + "resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "requires": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "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 + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, + "vite": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", + "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", + "dev": true, + "requires": { + "esbuild": "^0.15.9", + "fsevents": "~2.3.2", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } +} diff --git a/uzdb/frontend/package.json b/uzdb/frontend/package.json new file mode 100644 index 0000000..f0106ca --- /dev/null +++ b/uzdb/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.0.1", + "typescript": "^4.6.4", + "vite": "^3.0.7" + } +} \ No newline at end of file diff --git a/uzdb/frontend/package.json.md5 b/uzdb/frontend/package.json.md5 new file mode 100755 index 0000000..1ab71cf --- /dev/null +++ b/uzdb/frontend/package.json.md5 @@ -0,0 +1 @@ +f26173c7304a0bf8ea5c86eb567e7db2 \ No newline at end of file diff --git a/uzdb/frontend/src/App.css b/uzdb/frontend/src/App.css new file mode 100644 index 0000000..37ac04c --- /dev/null +++ b/uzdb/frontend/src/App.css @@ -0,0 +1,105 @@ +/** + * App Component Styles + */ + +.main-content { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +/* View Tabs */ +.view-tabs { + display: flex; + gap: var(--space-1); + padding: var(--space-2) var(--space-4) 0; + background-color: var(--bg-primary); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.view-tab { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-family: var(--font-sans); + color: var(--text-secondary); + background: transparent; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all var(--transition-fast) var(--ease-in-out); + white-space: nowrap; +} + +.view-tab:hover { + color: var(--text-primary); + background-color: var(--bg-tertiary); +} + +.view-tab.active { + color: var(--primary); + border-bottom-color: var(--primary); +} + +.tab-icon { + font-size: var(--text-sm); +} + +/* View Content */ +.view-content { + flex: 1; + overflow: hidden; + position: relative; +} + +/* Global scrollbar improvements */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); + border-left: 1px solid var(--border); +} + +::-webkit-scrollbar-thumb { + background: var(--text-muted); + border: 2px solid var(--bg-secondary); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +/* Focus visible improvements */ +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* Selection color */ +::selection { + background-color: rgba(59, 130, 246, 0.2); + color: var(--text-primary); +} + +/* Print styles */ +@media print { + .view-tabs, + .layout-menubar, + .layout-toolbar, + .layout-sidebar, + .layout-statusbar { + display: none !important; + } + + .view-content { + overflow: visible !important; + } +} diff --git a/uzdb/frontend/src/App.tsx b/uzdb/frontend/src/App.tsx new file mode 100644 index 0000000..d593da4 --- /dev/null +++ b/uzdb/frontend/src/App.tsx @@ -0,0 +1,307 @@ +/** + * uzdb - Database Management Tool + * + * Main application component integrating all UI components. + */ + +import { useState, useCallback } from 'react'; +import './index.css'; + +// Import components +import AppLayout from './components/Layout/AppLayout'; +import MenuBar from './components/MenuBar/MenuBar'; +import ToolBar from './components/Layout/ToolBar'; +import StatusBar from './components/Layout/StatusBar'; +import ConnectionPanel, { DatabaseConnection } from './components/Sidebar/ConnectionPanel'; +import QueryEditor, { QueryTab, QueryResult } from './components/MainArea/QueryEditor'; +import DataGrid from './components/MainArea/DataGrid'; +import TableStructure from './components/MainArea/TableStructure'; + +// Import mock data +import { mockConnections } from './mock/connections'; +import { + mockQueryResults, + mockQueryTabs, + mockDataGridColumns, + mockDataGridRows, + mockTableColumns, + mockIndexes, + mockForeignKeys, + mockTableInfo, +} from './mock/queryResults'; + +type MainView = 'query' | 'data' | 'structure'; + +function App() { + // Application state + const [connections] = useState(mockConnections); + const [activeConnectionId, setActiveConnectionId] = useState('conn-1'); + const [selectedConnectionId, setSelectedConnectionId] = useState('conn-1'); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + + // Query editor state + const [queryTabs, setQueryTabs] = useState(mockQueryTabs); + const [activeTabId, setActiveTabId] = useState('tab-1'); + const [queryResults, setQueryResults] = useState(null); + const [isQueryLoading, setIsQueryLoading] = useState(false); + + // Main view state + const [mainView, setMainView] = useState('query'); + + // Handler: Connection click + const handleConnectionClick = useCallback((connection: DatabaseConnection) => { + setSelectedConnectionId(connection.id); + if (connection.status === 'disconnected') { + console.log(`Connecting to ${connection.name}...`); + // In real app: call backend to connect + } else if (connection.status === 'connected' || connection.status === 'active') { + setActiveConnectionId(connection.id); + } + }, []); + + // Handler: New connection + const handleNewConnection = useCallback(() => { + console.log('Opening new connection dialog...'); + // In real app: open connection dialog + }, []); + + // Handler: Table double-click + const handleTableDoubleClick = useCallback(( + table: any, + schema: any, + connection: DatabaseConnection + ) => { + console.log(`Opening table ${table.name} from schema ${schema.name}`); + setMainView('data'); + }, []); + + // Handler: Query execution + const handleExecuteQuery = useCallback((query: string) => { + setIsQueryLoading(true); + console.log('Executing query:', query); + + // Simulate async query execution + setTimeout(() => { + setQueryResults(mockQueryResults); + setIsQueryLoading(false); + }, 500); + }, []); + + // Handler: Tab content change + const handleContentChange = useCallback((tabId: string, content: string) => { + setQueryTabs(prev => prev.map(tab => + tab.id === tabId ? { ...tab, content, isDirty: true } : tab + )); + }, []); + + // Handler: Save query + const handleSaveQuery = useCallback((tabId: string) => { + console.log('Saving query:', tabId); + setQueryTabs(prev => prev.map(tab => + tab.id === tabId ? { ...tab, isDirty: false } : tab + )); + }, []); + + // Handler: Close tab + const handleCloseTab = useCallback((tabId: string) => { + setQueryTabs(prev => prev.filter(tab => tab.id !== tabId)); + if (activeTabId === tabId) { + setActiveTabId(queryTabs[0]?.id || ''); + } + }, [activeTabId, queryTabs]); + + // Handler: New tab + const handleNewTab = useCallback(() => { + const newTab: QueryTab = { + id: `tab-${Date.now()}`, + title: 'untitled.sql', + content: '', + isDirty: false, + }; + setQueryTabs(prev => [...prev, newTab]); + setActiveTabId(newTab.id); + }, []); + + // Handler: Format SQL + const handleFormatSQL = useCallback(() => { + console.log('Formatting SQL...'); + // In real app: format SQL using sql-formatter + }, []); + + // Handler: Menu item click + const handleMenuItemClick = useCallback((menuId: string, itemId: string) => { + console.log(`Menu "${menuId}" -> Item "${itemId}"`); + + switch (itemId) { + case 'new-connection': + handleNewConnection(); + break; + case 'run-query': + if (activeTabId) { + const tab = queryTabs.find(t => t.id === activeTabId); + if (tab) handleExecuteQuery(tab.content); + } + break; + case 'toggle-sidebar': + setSidebarCollapsed(prev => !prev); + break; + default: + console.log('Unhandled menu item:', itemId); + } + }, [activeTabId, queryTabs, handleNewConnection, handleExecuteQuery]); + + // Get active connection + const activeConnection = connections.find(c => c.id === activeConnectionId); + + return ( + + } + toolbar={ + { + if (activeTabId) { + const tab = queryTabs.find(t => t.id === activeTabId); + if (tab) handleExecuteQuery(tab.content); + } + }, + }, + { + id: 'save', + icon: '๐Ÿ’พ', + label: 'Save', + tooltip: 'Save query (Ctrl+S)', + onClick: () => activeTabId && handleSaveQuery(activeTabId), + disabled: !queryTabs.find(t => t.id === activeTabId)?.isDirty, + }, + { + id: 'export', + icon: '๐Ÿ“ค', + label: 'Export', + tooltip: 'Export results', + disabled: !queryResults, + }, + { + id: 'find', + icon: '๐Ÿ”', + label: 'Find', + tooltip: 'Find in query (Ctrl+F)', + }, + ]} + /> + } + sidebar={ + + } + mainContent={ +
+ {/* View tabs */} +
+ + + +
+ + {/* Main view content */} +
+ {mainView === 'query' && ( + + )} + + {mainView === 'data' && ( + console.log('Refreshing data...')} + onExport={() => console.log('Exporting data...')} + onAddRow={() => console.log('Adding row...')} + /> + )} + + {mainView === 'structure' && ( + setMainView('data')} + onEditTable={() => console.log('Edit table...')} + onRefresh={() => console.log('Refreshing structure...')} + /> + )} +
+
+ } + statusBar={ + + } + /> + ); +} + +export default App; diff --git a/uzdb/frontend/src/assets/fonts/OFL.txt b/uzdb/frontend/src/assets/fonts/OFL.txt new file mode 100644 index 0000000..9cac04c --- /dev/null +++ b/uzdb/frontend/src/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/uzdb/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/uzdb/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000..2f9cc59 Binary files /dev/null and b/uzdb/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ diff --git a/uzdb/frontend/src/assets/images/logo-universal.png b/uzdb/frontend/src/assets/images/logo-universal.png new file mode 100644 index 0000000..99ac71f Binary files /dev/null and b/uzdb/frontend/src/assets/images/logo-universal.png differ diff --git a/uzdb/frontend/src/components/Layout/AppLayout.css b/uzdb/frontend/src/components/Layout/AppLayout.css new file mode 100644 index 0000000..bc8ba83 --- /dev/null +++ b/uzdb/frontend/src/components/Layout/AppLayout.css @@ -0,0 +1,134 @@ +/** + * AppLayout Component Styles + */ + +.app-layout { + display: flex; + flex-direction: column; + height: 100vh; + width: 100vw; + overflow: hidden; +} + +/* Menu Bar */ +.layout-menubar { + flex-shrink: 0; + height: var(--menubar-height); +} + +/* Toolbar */ +.layout-toolbar { + flex-shrink: 0; + height: var(--toolbar-height); + background-color: var(--bg-primary); + border-bottom: 1px solid var(--border); +} + +/* Main area (sidebar + workspace) */ +.layout-main { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Sidebar */ +.layout-sidebar { + flex-shrink: 0; + height: 100%; + overflow: hidden; + transition: width var(--transition-normal) var(--ease-in-out); +} + +.layout-sidebar.collapsed { + width: 0; + overflow: hidden; +} + +/* Resize handle */ +.layout-resize-handle { + width: 4px; + cursor: col-resize; + background-color: transparent; + transition: background-color var(--transition-fast) var(--ease-in-out); + flex-shrink: 0; + z-index: 10; +} + +.layout-resize-handle:hover { + background-color: var(--primary); +} + +.layout-resize-handle:active { + background-color: var(--primary-active); +} + +/* Sidebar toggle button */ +.layout-sidebar-toggle { + width: 48px; + background-color: var(--bg-secondary); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + align-items: center; + padding-top: var(--space-2); + gap: var(--space-2); + flex-shrink: 0; +} + +.toggle-button { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; +} + +/* Workspace */ +.layout-workspace { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: var(--bg-primary); +} + +/* Status Bar */ +.layout-statusbar { + flex-shrink: 0; + height: var(--statusbar-height); + background-color: var(--bg-secondary); + border-top: 1px solid var(--border); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .layout-sidebar:not(.collapsed) { + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 100; + box-shadow: var(--shadow-lg); + } + + .layout-resize-handle { + display: none; + } +} + +/* Animation for sidebar transitions */ +@keyframes slideInLeft { + from { + transform: translateX(-100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.layout-sidebar:not(.collapsed) { + animation: fadeIn 0.2s ease-out; +} diff --git a/uzdb/frontend/src/components/Layout/AppLayout.tsx b/uzdb/frontend/src/components/Layout/AppLayout.tsx new file mode 100644 index 0000000..c78b034 --- /dev/null +++ b/uzdb/frontend/src/components/Layout/AppLayout.tsx @@ -0,0 +1,155 @@ +/** + * AppLayout Component + * + * Main application layout with menu bar, toolbar, sidebar, main content area, and status bar. + * Based on layout-design.md "ๆ•ดไฝ“ๅธƒๅฑ€ๆžถๆž„" + */ + +import React, { useState, useCallback } from 'react'; +import './AppLayout.css'; + +export interface AppLayoutProps { + /** Menu bar component */ + menuBar?: React.ReactNode; + /** Toolbar component */ + toolbar?: React.ReactNode; + /** Sidebar/connection panel component */ + sidebar?: React.ReactNode; + /** Main content area component */ + mainContent?: React.ReactNode; + /** Status bar component */ + statusBar?: React.ReactNode; + /** Sidebar collapsed state */ + sidebarCollapsed?: boolean; + /** Handler when sidebar collapse state changes */ + onSidebarToggle?: (collapsed: boolean) => void; + /** Children (alternative to explicit props) */ + children?: React.ReactNode; +} + +/** + * AppLayout - Main application shell component + */ +export const AppLayout: React.FC = ({ + menuBar, + toolbar, + sidebar, + mainContent, + statusBar, + sidebarCollapsed = false, + onSidebarToggle, + children, +}) => { + const [isResizing, setIsResizing] = useState(false); + const [sidebarWidth, setSidebarWidth] = useState(240); + const [isCollapsed, setIsCollapsed] = useState(sidebarCollapsed); + + // Handle sidebar resize start + const handleResizeStart = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setIsResizing(true); + }, []); + + // Handle mouse move for resizing + useCallback(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isResizing) return; + + const newWidth = Math.max(180, Math.min(400, e.clientX)); + setSidebarWidth(newWidth); + }; + + const handleMouseUp = () => { + setIsResizing(false); + }; + + if (isResizing) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [isResizing]); + + // Toggle sidebar collapse + const toggleSidebar = useCallback(() => { + const newCollapsed = !isCollapsed; + setIsCollapsed(newCollapsed); + onSidebarToggle?.(newCollapsed); + }, [isCollapsed, onSidebarToggle]); + + // Expose toggle function via custom event for keyboard shortcuts + React.useEffect(() => { + const handleToggleRequest = () => toggleSidebar(); + window.addEventListener('toggle-sidebar', handleToggleRequest); + return () => window.removeEventListener('toggle-sidebar', handleToggleRequest); + }, [toggleSidebar]); + + return ( +
+ {/* Menu Bar */} + {menuBar &&
{menuBar}
} + + {/* Toolbar */} + {toolbar &&
{toolbar}
} + + {/* Main content area (sidebar + workspace) */} +
+ {/* Sidebar */} + {sidebar && ( + <> +
+ {sidebar} +
+ + {/* Resize handle */} + {!isCollapsed && ( +
+ )} + + {/* Collapse toggle button (when collapsed) */} + {isCollapsed && sidebar && ( +
+ +
+ )} + + )} + + {/* Workspace (main content) */} +
+ {mainContent || children} +
+
+ + {/* Status Bar */} + {statusBar &&
{statusBar}
} +
+ ); +}; + +export default AppLayout; diff --git a/uzdb/frontend/src/components/Layout/StatusBar.css b/uzdb/frontend/src/components/Layout/StatusBar.css new file mode 100644 index 0000000..ea88c63 --- /dev/null +++ b/uzdb/frontend/src/components/Layout/StatusBar.css @@ -0,0 +1,120 @@ +/** + * StatusBar Component Styles + */ + +.statusbar { + display: flex; + align-items: center; + justify-content: space-between; + height: var(--statusbar-height); + padding: 0 var(--space-4); + background-color: var(--bg-secondary); + border-top: 1px solid var(--border); + font-size: var(--text-xs); + flex-shrink: 0; +} + +/* Status type variations */ +.statusbar.status-normal { + background-color: var(--bg-secondary); + color: var(--text-secondary); +} + +.statusbar.status-success { + background-color: #d1fae5; + color: #065f46; +} + +.statusbar.status-warning { + background-color: #fef3c7; + color: #92400e; +} + +.statusbar.status-error { + background-color: #fee2e2; + color: #991b1b; +} + +.statusbar.status-info { + background-color: #dbeafe; + color: #1e40af; +} + +/* Left section */ +.statusbar-left { + display: flex; + align-items: center; + gap: var(--space-4); + overflow: hidden; +} + +.status-icon { + font-size: var(--text-sm); + font-weight: bold; +} + +.status-item { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.connection-info { + color: inherit; +} + +.query-info { + font-weight: 500; +} + +/* Right section */ +.statusbar-right { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.status-button { + padding: var(--space-1) var(--space-2); + font-size: var(--text-xs); + font-family: var(--font-sans); + color: inherit; + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast) var(--ease-in-out); +} + +.status-button:hover { + background-color: var(--bg-tertiary); + border-color: var(--border); +} + +.editor-mode { + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--text-muted); + min-width: 30px; + text-align: center; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .statusbar { + padding: 0 var(--space-2); + font-size: 11px; + } + + .statusbar-left { + gap: var(--space-2); + } + + .statusbar-right { + gap: var(--space-2); + } + + .status-button { + padding: var(--space-1); + } +} diff --git a/uzdb/frontend/src/components/Layout/StatusBar.tsx b/uzdb/frontend/src/components/Layout/StatusBar.tsx new file mode 100644 index 0000000..cecc8ef --- /dev/null +++ b/uzdb/frontend/src/components/Layout/StatusBar.tsx @@ -0,0 +1,126 @@ +/** + * StatusBar Component + * + * Bottom status bar showing connection info, encoding, and editor mode. + * Based on layout-design.md section "็Šถๆ€ๆ ่ฎพ่ฎก่ง„่Œƒ" + */ + +import React from 'react'; +import './StatusBar.css'; + +export type StatusType = 'normal' | 'success' | 'warning' | 'error' | 'info'; + +export interface StatusBarProps { + /** Connection status message */ + connectionInfo?: string; + /** Query execution info */ + queryInfo?: string; + /** Encoding format */ + encoding?: string; + /** Line ending type */ + lineEnding?: 'LF' | 'CRLF'; + /** Editor mode */ + editorMode?: 'ins' | 'ovr'; + /** Status type for coloring */ + statusType?: StatusType; + /** Handler when encoding is clicked */ + onEncodingClick?: () => void; + /** Handler when line ending is clicked */ + onLineEndingClick?: () => void; +} + +/** + * StatusBar component + */ +export const StatusBar: React.FC = ({ + connectionInfo, + queryInfo, + encoding = 'UTF-8', + lineEnding = 'LF', + editorMode = 'ins', + statusType = 'normal', + onEncodingClick, + onLineEndingClick, +}) => { + // Get status class based on type + const getStatusClass = (): string => { + switch (statusType) { + case 'success': + return 'status-success'; + case 'warning': + return 'status-warning'; + case 'error': + return 'status-error'; + case 'info': + return 'status-info'; + default: + return 'status-normal'; + } + }; + + // Get status icon + const getStatusIcon = (): string => { + switch (statusType) { + case 'success': + return 'โœ“'; + case 'warning': + return 'โš '; + case 'error': + return 'โœ•'; + case 'info': + return 'โ„น'; + default: + return ''; + } + }; + + return ( +
+
+ {/* Status icon and message */} + {getStatusIcon() && ( + + {getStatusIcon()} + + )} + + {/* Connection info */} + {connectionInfo && ( + {connectionInfo} + )} + + {/* Query info (takes priority over connection info when present) */} + {queryInfo && ( + {queryInfo} + )} +
+ +
+ {/* Encoding */} + + + {/* Line ending */} + + + {/* Editor mode */} + + {editorMode} + +
+
+ ); +}; + +export default StatusBar; diff --git a/uzdb/frontend/src/components/Layout/ToolBar.css b/uzdb/frontend/src/components/Layout/ToolBar.css new file mode 100644 index 0000000..d5906ef --- /dev/null +++ b/uzdb/frontend/src/components/Layout/ToolBar.css @@ -0,0 +1,124 @@ +/** + * ToolBar Component Styles + */ + +.toolbar { + display: flex; + align-items: center; + height: var(--toolbar-height); + padding: 0 var(--space-4); + background-color: var(--bg-primary); + border-bottom: 1px solid var(--border); + gap: var(--space-4); + flex-shrink: 0; +} + +.toolbar-buttons { + display: flex; + gap: var(--space-1); +} + +.toolbar-button { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + font-family: var(--font-sans); + color: var(--text-primary); + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast) var(--ease-in-out); + white-space: nowrap; +} + +.toolbar-button:hover:not(.disabled) { + background-color: var(--bg-tertiary); + border-color: var(--border); +} + +.toolbar-button.disabled { + color: var(--text-muted); + cursor: not-allowed; +} + +.button-icon { + font-size: var(--text-base); +} + +.button-label { + font-weight: 500; +} + +.toolbar-content { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.toolbar-spacer { + flex: 1; +} + +/* Connection indicator */ +.toolbar-connection { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast) var(--ease-in-out); +} + +.toolbar-connection:hover { + background-color: var(--bg-tertiary); + border-color: var(--text-secondary); +} + +.connection-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--text-muted); +} + +.connection-status-dot.connected { + background-color: var(--success); + box-shadow: 0 0 4px var(--success); +} + +.connection-name { + font-size: var(--text-sm); + font-weight: 500; + color: var(--text-primary); +} + +.dropdown-arrow { + font-size: var(--text-xs); + color: var(--text-muted); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .toolbar { + padding: 0 var(--space-2); + gap: var(--space-2); + } + + .toolbar-button { + padding: var(--space-2); + } + + .button-label { + display: none; + } + + .connection-name { + display: none; + } +} diff --git a/uzdb/frontend/src/components/Layout/ToolBar.tsx b/uzdb/frontend/src/components/Layout/ToolBar.tsx new file mode 100644 index 0000000..0bc3dbc --- /dev/null +++ b/uzdb/frontend/src/components/Layout/ToolBar.tsx @@ -0,0 +1,104 @@ +/** + * ToolBar Component + * + * Quick access toolbar with common actions. + * Based on layout-design.md toolbar specifications + */ + +import React from 'react'; +import './ToolBar.css'; + +export interface ToolButton { + id: string; + icon: string; + label: string; + tooltip?: string; + disabled?: boolean; + onClick?: () => void; +} + +export interface ToolBarProps { + /** Toolbar buttons */ + buttons?: ToolButton[]; + /** Additional content (e.g., search box) */ + children?: React.ReactNode; + /** Handler when button is clicked */ + onButtonClick?: (buttonId: string) => void; +} + +/** + * Default toolbar buttons + */ +const defaultButtons: ToolButton[] = [ + { + id: 'run', + icon: 'โ–ถ', + label: 'Run', + tooltip: 'Execute query (Ctrl+Enter)', + }, + { + id: 'save', + icon: '๐Ÿ’พ', + label: 'Save', + tooltip: 'Save query (Ctrl+S)', + }, + { + id: 'export', + icon: '๐Ÿ“ค', + label: 'Export', + tooltip: 'Export results', + }, + { + id: 'find', + icon: '๐Ÿ”', + label: 'Find', + tooltip: 'Find in query (Ctrl+F)', + }, +]; + +/** + * ToolBar component + */ +export const ToolBar: React.FC = ({ + buttons = defaultButtons, + children, + onButtonClick, +}) => { + const handleButtonClick = (button: ToolButton) => { + button.onClick?.(); + onButtonClick?.(button.id); + }; + + return ( +
+
+ {buttons.map((button) => ( + + ))} +
+ + {children &&
{children}
} + +
+ + {/* Connection indicator */} +
+ + ๐Ÿ—„๏ธ MySQL @ localhost + โ–ผ +
+
+ ); +}; + +export default ToolBar; diff --git a/uzdb/frontend/src/components/MainArea/DataGrid.css b/uzdb/frontend/src/components/MainArea/DataGrid.css new file mode 100644 index 0000000..5ccc562 --- /dev/null +++ b/uzdb/frontend/src/components/MainArea/DataGrid.css @@ -0,0 +1,330 @@ +/** + * DataGrid Component Styles + */ + +.data-grid { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--bg-primary); + overflow: hidden; +} + +/* Toolbar */ +.data-grid-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-3) var(--space-4); + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.data-grid-info { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.table-name { + font-size: var(--text-sm); + font-weight: 600; + color: var(--text-primary); +} + +.data-grid-actions { + display: flex; + gap: var(--space-2); +} + +/* Filter Bar */ +.data-grid-filters { + display: flex; + gap: var(--space-1); + padding: var(--space-2) var(--space-3); + background-color: var(--bg-secondary); + border-bottom: 1px solid var(--border); + overflow-x: auto; + flex-shrink: 0; +} + +.filter-input { + padding: var(--space-1) var(--space-2); + font-size: var(--text-xs); + font-family: var(--font-sans); + color: var(--text-primary); + background-color: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + outline: none; + transition: all var(--transition-fast) var(--ease-in-out); +} + +.filter-input:hover { + border-color: var(--text-secondary); +} + +.filter-input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.filter-input::placeholder { + color: var(--text-muted); +} + +/* Table Wrapper */ +.data-grid-wrapper { + flex: 1; + overflow: auto; + position: relative; +} + +/* Data Table */ +.data-grid-table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + font-family: var(--font-sans); + table-layout: fixed; +} + +.data-grid-table thead { + position: sticky; + top: 0; + z-index: 10; +} + +.data-grid-table th { + padding: var(--space-2) var(--space-3); + text-align: left; + background-color: var(--bg-tertiary); + border: 1px solid var(--border); + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.data-grid-table th.sortable { + cursor: pointer; + user-select: none; +} + +.data-grid-table th.sortable:hover { + background-color: var(--border); +} + +.th-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); +} + +.sort-icon { + font-size: var(--text-xs); + color: var(--text-muted); +} + +.data-grid-table td { + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.data-grid-table tbody tr { + transition: background-color var(--transition-fast) var(--ease-in-out); +} + +.data-grid-table tbody tr:hover { + background-color: var(--bg-secondary); +} + +.data-grid-table tbody tr.selected { + background-color: rgba(59, 130, 246, 0.1); +} + +/* Special Columns */ +.select-column, +.action-column { + width: 40px; + text-align: center; +} + +.select-cell, +.action-cell { + text-align: center; +} + +.select-cell input[type="checkbox"], +.action-cell input[type="checkbox"] { + cursor: pointer; +} + +.editable-cell { + cursor: pointer; +} + +.editable-cell:hover { + background-color: rgba(59, 130, 246, 0.05); +} + +.cell-value { + display: block; + overflow: hidden; + text-overflow: ellipsis; +} + +.null-value { + color: var(--text-muted); + font-style: italic; + font-size: var(--text-xs); +} + +/* Cell Editor */ +.cell-editor { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.cell-editor input[type="text"], +.cell-editor input[type="number"], +.cell-editor input[type="datetime-local"] { + flex: 1; + padding: var(--space-1); + font-size: var(--text-sm); + font-family: var(--font-sans); + border: 1px solid var(--primary); + border-radius: var(--radius-sm); + outline: none; +} + +.cell-editor-number { + width: 100px !important; +} + +.cell-editor-actions { + display: flex; + gap: var(--space-1); +} + +/* Loading & Empty States */ +.loading-cell, +.empty-cell { + text-align: center; + padding: var(--space-10); + color: var(--text-muted); +} + +.loading-spinner { + display: inline-block; + width: 24px; + height: 24px; + border: 2px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: var(--space-2); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Pagination */ +.data-grid-pagination { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-2) var(--space-4); + background-color: var(--bg-tertiary); + border-top: 1px solid var(--border); + flex-shrink: 0; +} + +.pagination-info { + display: flex; + gap: var(--space-4); + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.selected-info { + color: var(--primary); + font-weight: 500; +} + +.pagination-controls { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.page-indicator { + font-size: var(--text-sm); + color: var(--text-primary); + min-width: 60px; + text-align: center; +} + +.page-size-selector { + display: flex; + align-items: center; + gap: var(--space-2); + margin-left: var(--space-4); +} + +.page-size-selector label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.page-size-selector select { + padding: var(--space-1) var(--space-2); + font-size: var(--text-sm); + font-family: var(--font-sans); + color: var(--text-primary); + background-color: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + cursor: pointer; + outline: none; +} + +.page-size-selector select:hover { + border-color: var(--text-secondary); +} + +.page-size-selector select:focus { + border-color: var(--primary); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .data-grid-toolbar { + flex-wrap: wrap; + gap: var(--space-2); + } + + .data-grid-actions { + flex-wrap: wrap; + } + + .pagination-info { + flex-direction: column; + gap: var(--space-1); + } + + .pagination-controls { + flex-wrap: wrap; + justify-content: center; + } +} diff --git a/uzdb/frontend/src/components/MainArea/DataGrid.tsx b/uzdb/frontend/src/components/MainArea/DataGrid.tsx new file mode 100644 index 0000000..71d877a --- /dev/null +++ b/uzdb/frontend/src/components/MainArea/DataGrid.tsx @@ -0,0 +1,495 @@ +/** + * DataGrid Component + * + * Data table viewer with sorting, filtering, and inline editing capabilities. + * Based on layout-design.md section "ๆ•ฐๆฎๆต่งˆๆจกๅ—" + */ + +import React, { useState, useMemo } from 'react'; +import './DataGrid.css'; + +export interface Column { + id: string; + name: string; + type?: string; + width?: number; + sortable?: boolean; + editable?: boolean; +} + +export interface DataRow { + id: string | number; + [key: string]: any; +} + +export interface PaginationState { + currentPage: number; + pageSize: number; + totalRows: number; +} + +export interface SortState { + columnId: string; + direction: 'asc' | 'desc'; +} + +export interface FilterState { + columnId: string; + value: string; +} + +export interface DataGridProps { + /** Table/column data */ + columns: Column[]; + rows: DataRow[]; + /** Total row count (for pagination) */ + totalRows?: number; + /** Loading state */ + isLoading?: boolean; + /** Enable row selection */ + selectable?: boolean; + /** Enable inline editing */ + editable?: boolean; + /** Current pagination state */ + pagination?: PaginationState; + /** Current sort state */ + sort?: SortState; + /** Active filters */ + filters?: FilterState[]; + /** Handler when page changes */ + onPageChange?: (page: number) => void; + /** Handler when page size changes */ + onPageSizeChange?: (size: number) => void; + /** Handler when sort changes */ + onSortChange?: (sort: SortState) => void; + /** Handler when filter changes */ + onFilterChange?: (filter: FilterState) => void; + /** Handler when row is selected */ + onRowSelect?: (rowIds: (string | number)[]) => void; + /** Handler when cell is edited */ + onCellEdit?: (rowId: string | number, columnId: string, value: any) => void; + /** Handler when add row is requested */ + onAddRow?: () => void; + /** Handler when delete rows is requested */ + onDeleteRows?: (rowIds: (string | number)[]) => void; + /** Handler when refresh is requested */ + onRefresh?: () => void; + /** Handler when export is requested */ + onExport?: () => void; + /** Table name for display */ + tableName?: string; + /** Schema name for display */ + schemaName?: string; +} + +/** + * Cell Editor component based on data type + */ +interface CellEditorProps { + value: any; + column: Column; + onSave: (value: any) => void; + onCancel: () => void; +} + +const CellEditor: React.FC = ({ value, column, onSave, onCancel }) => { + const [editValue, setEditValue] = useState(value); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + onSave(editValue); + } else if (e.key === 'Escape') { + onCancel(); + } + }; + + // Render appropriate editor based on column type + const renderEditor = () => { + const type = column.type?.toUpperCase() || ''; + + if (type.includes('BOOL')) { + return ( + setEditValue(e.target.checked)} + autoFocus + /> + ); + } + + if (type.includes('INT') || type.includes('DECIMAL') || type.includes('FLOAT')) { + return ( + setEditValue(e.target.value ? Number(e.target.value) : null)} + onKeyDown={handleKeyDown} + autoFocus + className="cell-editor-number" + /> + ); + } + + if (type.includes('DATE') || type.includes('TIME')) { + return ( + setEditValue(e.target.value)} + onKeyDown={handleKeyDown} + autoFocus + /> + ); + } + + // Default: text input + return ( + setEditValue(e.target.value)} + onKeyDown={handleKeyDown} + autoFocus + className="cell-editor-text" + /> + ); + }; + + return ( +
+ {renderEditor()} +
+ + +
+
+ ); +}; + +/** + * DataGrid component + */ +export const DataGrid: React.FC = ({ + columns, + rows, + totalRows, + isLoading = false, + selectable = true, + editable = true, + pagination = { currentPage: 1, pageSize: 25, totalRows: 0 }, + sort, + filters = [], + onPageChange, + onPageSizeChange, + onSortChange, + onFilterChange, + onRowSelect, + onCellEdit, + onAddRow, + onDeleteRows, + onRefresh, + onExport, + tableName, + schemaName, +}) => { + const [selectedRows, setSelectedRows] = useState>(new Set()); + const [editingCell, setEditingCell] = useState<{ + rowId: string | number; + columnId: string; + } | null>(null); + const [localFilters, setLocalFilters] = useState(filters); + + // Calculate total pages + const totalPages = Math.ceil((totalRows || rows.length) / pagination.pageSize); + + // Handle header click for sorting + const handleHeaderClick = (columnId: string) => { + if (!onSortChange) return; + + if (sort?.columnId === columnId) { + // Toggle direction or remove sort + if (sort.direction === 'asc') { + onSortChange({ columnId, direction: 'desc' }); + } else { + onSortChange({ columnId: '', direction: 'asc' }); + } + } else { + onSortChange({ columnId, direction: 'asc' }); + } + }; + + // Handle row selection + const handleSelectAll = (e: React.ChangeEvent) => { + if (e.target.checked) { + const allIds = rows.map((r) => r.id); + setSelectedRows(new Set(allIds)); + onRowSelect?.(allIds); + } else { + setSelectedRows(new Set()); + onRowSelect?.([]); + } + }; + + const handleSelectRow = (rowId: string | number) => { + const newSelected = new Set(selectedRows); + if (newSelected.has(rowId)) { + newSelected.delete(rowId); + } else { + newSelected.add(rowId); + } + setSelectedRows(newSelected); + onRowSelect?.(Array.from(newSelected)); + }; + + // Handle cell edit + const handleCellDoubleClick = (rowId: string | number, columnId: string) => { + if (editable) { + setEditingCell({ rowId, columnId }); + } + }; + + const handleCellSave = (value: any) => { + if (editingCell) { + onCellEdit?.(editingCell.rowId, editingCell.columnId, value); + setEditingCell(null); + } + }; + + const handleCellCancel = () => { + setEditingCell(null); + }; + + // Handle filter change + const handleFilterChange = (columnId: string, value: string) => { + const newFilters = localFilters.filter((f) => f.columnId !== columnId); + if (value) { + newFilters.push({ columnId, value }); + } + setLocalFilters(newFilters); + onFilterChange?.({ columnId, value }); + }; + + // Get sort icon + const getSortIcon = (columnId: string) => { + if (!sort || sort.columnId !== columnId) return 'โ‡…'; + return sort.direction === 'asc' ? 'โ†‘' : 'โ†“'; + }; + + return ( +
+ {/* Toolbar */} +
+
+ {tableName && ( + + ๐Ÿ“‹ {schemaName && `${schemaName}.`}{tableName} + + )} +
+
+ + + + +
+
+ + {/* Filter bar */} +
+ {columns.map((column) => ( + f.columnId === column.id)?.value || ''} + onChange={(e) => handleFilterChange(column.id, e.target.value)} + /> + ))} +
+ + {/* Data Table */} +
+ + + + {selectable && ( + + )} + {columns.map((column) => ( + + ))} + {editable && } + + + + {isLoading ? ( + + + + ) : rows.length === 0 ? ( + + + + ) : ( + rows.map((row) => ( + + {selectable && ( + + )} + {columns.map((column) => ( + + ))} + {editable && ( + + )} + + )) + )} + +
+ 0} + onChange={handleSelectAll} + /> + column.sortable && handleHeaderClick(column.id)} + > +
+ {column.name} + {column.sortable && ( + {getSortIcon(column.id)} + )} +
+
โœŽ
+
+ Loading data... +
+ No data available +
+ handleSelectRow(row.id)} + /> + handleCellDoubleClick(row.id, column.id)} + > + {editingCell?.rowId === row.id && + editingCell?.columnId === column.id ? ( + + ) : ( + + {row[column.id] === null ? ( + NULL + ) : ( + String(row[column.id]) + )} + + )} + + +
+
+ + {/* Pagination */} +
+
+ {selectedRows.size > 0 && ( + Selected: {selectedRows.size} rows + )} + Total: {totalRows || rows.length} rows +
+
+ + + + {pagination.currentPage} / {totalPages || 1} + + + +
+ + +
+
+
+
+ ); +}; + +export default DataGrid; diff --git a/uzdb/frontend/src/components/MainArea/QueryEditor.css b/uzdb/frontend/src/components/MainArea/QueryEditor.css new file mode 100644 index 0000000..86ddeb5 --- /dev/null +++ b/uzdb/frontend/src/components/MainArea/QueryEditor.css @@ -0,0 +1,347 @@ +/** + * QueryEditor Component Styles + */ + +.query-editor { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--bg-primary); + overflow: hidden; +} + +/* Tab Bar */ +.query-tabs { + display: flex; + align-items: center; + height: var(--tab-height); + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border); + padding: 0 var(--space-2); + gap: var(--space-1); + flex-shrink: 0; +} + +.query-tab-list { + display: flex; + gap: var(--space-1); + flex: 1; + overflow-x: auto; +} + +.query-tab { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-3); + min-width: 120px; + max-width: 200px; + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-bottom: none; + border-radius: var(--radius-sm) var(--radius-sm) 0 0; + cursor: pointer; + font-size: var(--text-sm); + color: var(--text-secondary); + transition: all var(--transition-fast) var(--ease-in-out); + user-select: none; +} + +.query-tab:hover { + background-color: var(--bg-primary); +} + +.query-tab.active { + background-color: var(--bg-primary); + border-bottom-color: var(--bg-primary); + color: var(--text-primary); +} + +.query-tab.dirty .tab-title::after { + content: ' โ—'; + color: var(--primary); + font-size: var(--text-xs); +} + +.tab-icon { + font-size: var(--text-sm); +} + +.tab-title { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tab-close { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: var(--text-muted); + background: transparent; + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + opacity: 0; + transition: all var(--transition-fast) var(--ease-in-out); +} + +.query-tab:hover .tab-close { + opacity: 1; +} + +.tab-close:hover { + background-color: var(--error); + color: white; +} + +.new-tab-btn { + font-size: var(--text-lg); + font-weight: bold; +} + +/* Toolbar */ +.query-toolbar { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-2) var(--space-4); + background-color: var(--bg-primary); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.toolbar-group { + display: flex; + gap: var(--space-2); + align-items: center; +} + +.toolbar-spacer { + flex: 1; +} + +.connection-select { + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + font-family: var(--font-sans); + color: var(--text-primary); + background-color: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-md); + cursor: pointer; + outline: none; +} + +.connection-select:hover { + border-color: var(--text-secondary); +} + +.connection-select:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +/* Editor Container */ +.editor-container { + display: flex; + flex: 1; + overflow: hidden; + border-bottom: 1px solid var(--border); +} + +.line-numbers { + display: flex; + flex-direction: column; + padding: var(--space-3) var(--space-2); + background-color: var(--bg-tertiary); + border-right: 1px solid var(--border); + user-select: none; + overflow: hidden; +} + +.line-number { + font-family: var(--font-mono); + font-size: var(--text-sm); + line-height: 1.5; + color: var(--text-muted); + text-align: right; + min-height: 24px; +} + +.sql-editor { + flex: 1; + padding: var(--space-3); + font-family: var(--font-mono); + font-size: var(--text-sm); + line-height: 1.5; + color: var(--text-primary); + background-color: var(--bg-primary); + border: none; + resize: none; + outline: none; + overflow: auto; + white-space: pre; +} + +.sql-editor::placeholder { + color: var(--text-muted); +} + +.cursor-position { + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + color: var(--text-muted); + background-color: var(--bg-tertiary); + border-top: 1px solid var(--border); + text-align: right; +} + +/* Results Panel */ +.results-panel { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + background-color: var(--bg-primary); +} + +.results-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-2) var(--space-4); + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border); +} + +.results-title { + font-size: var(--text-sm); + font-weight: 600; + margin: 0; +} + +.results-message { + font-size: var(--text-sm); + color: var(--success); +} + +.results-message.error { + color: var(--error); +} + +.results-table-wrapper { + flex: 1; + overflow: auto; +} + +.results-table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + font-family: var(--font-sans); +} + +.results-table th, +.results-table td { + padding: var(--space-2) var(--space-3); + text-align: left; + border: 1px solid var(--border); + white-space: nowrap; +} + +.results-table th { + background-color: var(--bg-tertiary); + font-weight: 600; + position: sticky; + top: 0; + z-index: 10; +} + +.results-table tr:hover { + background-color: var(--bg-secondary); +} + +.results-limit-notice { + padding: var(--space-2) var(--space-4); + font-size: var(--text-xs); + color: var(--text-muted); + text-align: center; + background-color: var(--bg-warning); +} + +.results-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-2) var(--space-4); + background-color: var(--bg-tertiary); + border-top: 1px solid var(--border); +} + +.results-info { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.results-actions { + display: flex; + gap: var(--space-1); +} + +/* Error Message */ +.error-message { + padding: var(--space-4); + background-color: #fef2f2; + border-left: 3px solid var(--error); + margin: var(--space-3); +} + +.error-message pre { + margin: 0; + font-family: var(--font-mono); + font-size: var(--text-sm); + color: var(--error); + white-space: pre-wrap; + word-break: break-word; +} + +/* Loading Overlay */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: rgba(255, 255, 255, 0.8); + z-index: 100; + gap: var(--space-4); +} + +.loading-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loading-overlay span { + font-size: var(--text-sm); + color: var(--text-secondary); +} diff --git a/uzdb/frontend/src/components/MainArea/QueryEditor.tsx b/uzdb/frontend/src/components/MainArea/QueryEditor.tsx new file mode 100644 index 0000000..3aa9793 --- /dev/null +++ b/uzdb/frontend/src/components/MainArea/QueryEditor.tsx @@ -0,0 +1,301 @@ +/** + * QueryEditor Component + * + * SQL query editor with syntax highlighting and execution controls. + * Based on layout-design.md section "SQL ็ผ–่พ‘ๅ™จๆจกๅ—" + */ + +import React, { useState, KeyboardEvent } from 'react'; +import './QueryEditor.css'; + +export interface QueryTab { + id: string; + title: string; + content: string; + isDirty?: boolean; +} + +export interface QueryResult { + columns: string[]; + rows: any[][]; + rowCount?: number; + executionTime?: number; + message?: string; + error?: string; +} + +export interface QueryEditorProps { + /** Current query tabs */ + tabs?: QueryTab[]; + /** Active tab ID */ + activeTabId?: string; + /** Query results */ + results?: QueryResult | null; + /** Loading state */ + isLoading?: boolean; + /** Handler when tab is clicked */ + onTabClick?: (tabId: string) => void; + /** Handler when tab is closed */ + onCloseTab?: (tabId: string) => void; + /** Handler when new tab is requested */ + onNewTab?: () => void; + /** Handler when query content changes */ + onContentChange?: (tabId: string, content: string) => void; + /** Handler when query is executed */ + onExecute?: (query: string) => void; + /** Handler when query is saved */ + onSave?: (tabId: string) => void; + /** Handler when format is requested */ + onFormat?: () => void; +} + +/** + * QueryEditor component + */ +export const QueryEditor: React.FC = ({ + tabs = [], + activeTabId, + results, + isLoading = false, + onTabClick, + onCloseTab, + onNewTab, + onContentChange, + onExecute, + onSave, + onFormat, +}) => { + const [editorContent, setEditorContent] = useState(''); + const [cursorPosition, setCursorPosition] = useState({ line: 1, column: 1 }); + + // Get active tab + const activeTab = tabs.find((t) => t.id === activeTabId); + + // Handle keyboard shortcuts + const handleKeyDown = (e: KeyboardEvent) => { + // Ctrl+Enter to execute + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + onExecute?.(editorContent); + } + + // Ctrl+S to save + if (e.ctrlKey && e.key === 's') { + e.preventDefault(); + if (activeTabId) { + onSave?.(activeTabId); + } + } + + // Tab key for indentation + if (e.key === 'Tab') { + e.preventDefault(); + const textarea = e.currentTarget; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const newValue = + editorContent.substring(0, start) + ' ' + editorContent.substring(end); + setEditorContent(newValue); + // Restore cursor position + setTimeout(() => { + textarea.selectionStart = textarea.selectionEnd = start + 2; + }, 0); + } + }; + + // Update cursor position + const handleCursorChange = (e: React.ChangeEvent) => { + const content = e.target.value; + const cursorPos = e.target.selectionStart; + + const lines = content.substring(0, cursorPos).split('\n'); + const line = lines.length; + const column = lines[lines.length - 1].length + 1; + + setCursorPosition({ line, column }); + setEditorContent(content); + onContentChange?.(activeTabId || 'default', content); + }; + + return ( +
+ {/* Tab Bar */} +
+
+ {tabs.map((tab) => ( +
onTabClick?.(tab.id)} + > + ๐Ÿ“‘ + {tab.title} + {tab.isDirty && โ—} + +
+ ))} +
+ +
+ + {/* Editor Toolbar */} +
+
+ + +
+
+ + + +
+
+
+ +
+
+ + {/* SQL Editor */} +
+
+ {editorContent.split('\n').map((_, index) => ( +
+ {index + 1} +
+ ))} +
+