commit 346372c5521158756b3a32d1bb7e09fbeaabc24f Author: Adrian Bonpin Date: Sun Jul 26 02:55:26 2026 +0800 init: Initial CommitπŸŽ‰ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..036fd6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/ +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# AI +.pi/ +.superpowers/ + +# Env +.env.* +.env +!.env.example diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..af7027f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,188 @@ +# AGENTS.md + +Guidance for AI coding agents working on **Gridline**. + +--- + +## Project Identity + +Gridline is an **open-source, cross-platform database GUI client** for PostgreSQL (with MySQL, SQLite, and Redis to follow). It is built as a **Tauri 2.0 desktop app** β€” a lightweight native shell (~40MB baseline) around a React web frontend, with a Rust backend handling all database operations, CLI tool orchestration, and local persistence. + +**Core differentiators from commercial alternatives (DB Pro, TablePlus, etc.):** +- No paywalls β€” unlimited tabs, connections, and saved queries by default +- First-class PostgreSQL administration: `pg_dump`, `pg_restore`, DB-to-DB sync +- Full object explorer: Functions, Triggers, Sequences, Enums, Extensions β€” not just tables + +**Target audience:** Developers managing multiple database environments across projects (Personal, Work, Client). The workspace/folder hierarchy is a first-class concept. + +--- + +## Tech Stack + +| Layer | Technology | Notes | +| :--- | :--- | :--- | +| Desktop shell | Tauri 2.0 | Native webview wrapper, Rust backend | +| Frontend | React 19 + TypeScript 5.8 | Vite 7 for bundling/HMR | +| Styling | Tailwind CSS | Dark-first, glassmorphic aesthetic | +| State | Zustand or Jotai | Pick one and stay consistent per feature | +| Editor | Monaco Editor | SQL mode with custom autocomplete providers | +| Data grid | Glide Data Grid or TanStack Virtual | Virtualized, canvas-rendered | +| Backend | Rust (tokio async runtime) | Connection pools, IPC commands, shell execution | +| DB drivers | sqlx + tokio-postgres | Async, pure-Rust PostgreSQL driver | +| Local storage | SQLite via rusqlite | User settings, workspace state, query history | +| Credentials | OS keychain | macOS Keychain, Linux Secret Service, Windows Credential Manager | + +--- + +## Project Structure + +``` +gridline/ +β”œβ”€β”€ src/ # React frontend (TypeScript) +β”‚ β”œβ”€β”€ components/ # Reusable UI components +β”‚ β”‚ β”œβ”€β”€ layout/ # App shell, sidebar, tabs +β”‚ β”‚ β”œβ”€β”€ editor/ # Monaco wrapper, autocomplete +β”‚ β”‚ β”œβ”€β”€ grid/ # Data grid, filters, export +β”‚ β”‚ β”œβ”€β”€ tree/ # Workspace/object explorer tree +β”‚ β”‚ └── ui/ # Primitives (buttons, modals, inputs) +β”‚ β”œβ”€β”€ stores/ # Zustand/Jotai stores +β”‚ β”œβ”€β”€ hooks/ # Custom hooks (useConnection, useQuery, etc.) +β”‚ β”œβ”€β”€ lib/ # Utilities, types, Tauri bindings +β”‚ β”‚ β”œβ”€β”€ commands.ts # Typed wrappers around Tauri invoke() +β”‚ β”‚ β”œβ”€β”€ types.ts # Shared TypeScript interfaces +β”‚ β”‚ └── utils.ts # Formatting, validation helpers +β”‚ β”œβ”€β”€ App.tsx +β”‚ β”œβ”€β”€ main.tsx +β”‚ └── index.css # Tailwind directives + custom theme tokens +β”œβ”€β”€ src-tauri/ # Rust backend +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ main.rs # Binary entry point +β”‚ β”‚ β”œβ”€β”€ lib.rs # Tauri builder, command registration +β”‚ β”‚ β”œβ”€β”€ db/ # Connection pooling, query execution +β”‚ β”‚ β”‚ β”œβ”€β”€ mod.rs +β”‚ β”‚ β”‚ β”œβ”€β”€ pool.rs # Connection pool manager +β”‚ β”‚ β”‚ └── introspection.rs # Schema/system catalog queries +β”‚ β”‚ β”œβ”€β”€ commands/ # Tauri #[tauri::command] handlers +β”‚ β”‚ β”‚ β”œβ”€β”€ mod.rs +β”‚ β”‚ β”‚ β”œβ”€β”€ connections.rs # CRUD for saved connections +β”‚ β”‚ β”‚ β”œβ”€β”€ query.rs # SQL execution +β”‚ β”‚ β”‚ β”œβ”€β”€ schema.rs # Object tree introspection +β”‚ β”‚ β”‚ β”œβ”€β”€ backup.rs # pg_dump / pg_restore wrappers +β”‚ β”‚ β”‚ └── workspace.rs # Workspace/folder persistence +β”‚ β”‚ β”œβ”€β”€ models/ # Serde structs shared across commands +β”‚ β”‚ β”‚ β”œβ”€β”€ mod.rs +β”‚ β”‚ β”‚ β”œβ”€β”€ connection.rs +β”‚ β”‚ β”‚ β”œβ”€β”€ query.rs +β”‚ β”‚ β”‚ └── workspace.rs +β”‚ β”‚ └── store/ # SQLite local persistence layer +β”‚ β”‚ β”œβ”€β”€ mod.rs +β”‚ β”‚ └── migrations.rs +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ β”œβ”€β”€ tauri.conf.json +β”‚ └── capabilities/ # Tauri capability permissions +β”œβ”€β”€ public/ # Static frontend assets +β”œβ”€β”€ package.json +β”œβ”€β”€ tsconfig.json +β”œβ”€β”€ vite.config.ts +β”œβ”€β”€ tailwind.config.ts +└── AGENTS.md # This file +``` + +--- + +## Conventions + +### TypeScript / React +- **Components:** PascalCase files, default exports for page-level, named exports for reusable primitives +- **Hooks:** `use` prefix, one hook per file unless tightly coupled +- **Stores:** One Zustand store per domain (`connectionStore`, `queryStore`, `workspaceStore`) +- **Types:** Define interfaces in `src/lib/types.ts`; use `type` for unions/aliases +- **No `any`:** Always type Tauri `invoke()` calls with explicit generics +- **CSS:** Tailwind utility classes only; no CSS modules unless unavoidable (Monaco configuration is the exception) + +### Rust +- **Modules:** One module file per concern; re-export through `mod.rs` +- **Errors:** Use `anyhow` for application errors, `thiserror` for library-style enums +- **Commands:** Keep `#[tauri::command]` functions thin β€” delegate to `db/` or `store/` modules +- **State:** Use Tauri managed state (`app.manage()`) for connection pool handles +- **Naming:** `snake_case` for functions/modules, `CamelCase` for types/structs + +### General +- **IPC flow:** Frontend calls typed wrapper β†’ wrapper calls `invoke()` β†’ Rust command β†’ Rust logic β†’ returns `Result` +- **Error handling:** Rust commands return `Result` (map errors to user-readable strings before crossing IPC boundary) +- **No secrets in logs:** Never log connection strings, passwords, or query parameters +- **Dark mode first:** All UI components must look correct in dark theme; light theme is secondary + +--- + +## Key Design Decisions + +1. **Tauri over Electron** β€” ~40MB RAM vs 250MB+. Native file dialogs, OS keychain access, and `std::process::Command` for `pg_dump`/`pg_restore` without Node.js overhead. + +2. **Rust-native DB drivers** β€” `sqlx`/`tokio-postgres` connect directly to PostgreSQL from the Rust backend. No Node.js `pg` library, no sidecar Node process. The frontend never touches database connections directly. + +3. **System CLI tools for backup/restore** β€” Rather than implementing `pg_dump` format parsers in Rust (enormous scope), we shell out to the user's installed `pg_dump`/`pg_restore` binaries. The app will detect missing tools and guide installation. + +4. **SQLite for local state** β€” Workspace tree, saved queries, connection metadata (NOT passwords), and query history go into a local SQLite database in the Tauri app data directory. This enables fast full-text search and relational queries without loading everything into memory. + +5. **Virtualized grid from day one** β€” Query results can be 100k+ rows. We must render with canvas/DOM virtualization (Glide Data Grid or TanStack Virtual), never with naive DOM row rendering. + +--- + +## Development Workflow + +### Commands + +```bash +bun install # Install frontend dependencies +bun run dev # Vite dev server only (no Tauri) +bun run tauri dev # Full Tauri app with hot-reload +bun run tauri build # Production build +cargo build # Rust backend only (from src-tauri/) +cargo test # Rust tests +``` + +### Adding a Tauri Command + +1. Define the command function in the appropriate `src-tauri/src/commands/` module +2. Register it in `src-tauri/src/lib.rs` via `.invoke_handler(tauri::generate_handler![...])` +3. Create a typed wrapper function in `src/lib/commands.ts` +4. Call the wrapper from your React component/store + +### Adding a New Dependency + +- **Frontend:** `bun add ` (runtime) or `bun add -d ` (dev) +- **Rust:** Add to `src-tauri/Cargo.toml` under `[dependencies]` + +### Testing Strategy + +- **Rust:** Unit tests for database logic, connection pool management, and command handlers. Use `sqlx::test` with a test PostgreSQL instance for integration tests. +- **Frontend:** Vitest + React Testing Library for component tests. Focus on store logic, command wrappers, and critical UI flows (connection form, query execution). +- **E2E:** (Future) Tauri WebDriver or Playwright for critical paths. + +--- + +## Constraints & Guardrails + +- **Do NOT** implement `pg_dump` file format parsing β€” always shell out to system binaries +- **Do NOT** store passwords in SQLite or local files β€” use OS keychain APIs exclusively +- **Do NOT** render large query results in raw DOM β€” always use the virtualized grid component +- **Do NOT** log credentials, connection strings, or query data +- **Do NOT** introduce Electron, Node.js server processes, or Docker dependencies +- **DO** keep Tauri commands thin β€” business logic lives in `db/` and `store/` modules +- **DO** type all IPC boundaries explicitly +- **DO** validate and sanitize all user-provided SQL and connection parameters before execution + +--- + +## Related Documents + +- [Tauri 2.0 Documentation](https://tauri.app/develop/) +- [sqlx Documentation](https://docs.rs/sqlx) +- [Monaco Editor API](https://microsoft.github.io/monaco-editor/api/) +- [Project README](./README.md) +- [Feature Specification](./README.md#features) + +--- + +*This file is read by AI coding agents (Claude, Cursor, Copilot, etc.) to understand project conventions and architecture before making changes. Keep it current as the project evolves.* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc3c89c --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# Gridline + +A modern, open-source, high-performance database GUI client for PostgreSQL and beyond. Built with Tauri 2.0, Rust, and React β€” lightweight by design, powerful by default. + +> **Inspired by DB Pro's best ideas. Freed from its paywalls.** No caps on tabs, connections, or saved queries. Deep PostgreSQL tooling (`pg_dump`, `pg_restore`, DB-to-DB sync) that commercial alternatives leave to the CLI. + +--- + +## Why Gridline? + +Most database GUI clients either lock essential productivity features behind paywalls or treat PostgreSQL administration as an afterthought. Gridline is different: + +| Capability | DB Pro (Free) | Gridline | +| :--- | :---: | :---: | +| Open tabs | 3 | **Unlimited** | +| Saved connections | 2 | **Unlimited** | +| Saved queries | 5 | **Unlimited** | +| Workspace / folder hierarchy | ❌ | **Multi-level tree** | +| pg_dump / pg_restore GUI | ❌ | **First-class UI** | +| DB-to-DB sync | ❌ | **Built-in diff & migrate** | +| Functions, Triggers, Enums, Sequences | ❌ | **Full object explorer** | +| OS credential vault storage | ❌ | **Keychain / Secret Service** | +| Open source | ❌ | **MIT** | + +--- + +## Features + +### Connection & Workspace Management +- **URI Parser** β€” paste `postgres://`, `mysql://`, `sqlite://`, or `redis://` connection strings and have all fields auto-populated +- **Workspace Tree** β€” multi-level hierarchy: `Workspace β†’ Folder β†’ Connection`, with color-coded tags (red = Production, green = Local) +- **Credential Security** β€” passwords stored in the OS keychain (macOS Keychain / Linux Secret Service / Windows Credential Manager), never as plaintext +- **Production Safeguards** β€” read-only locks and high-visibility warnings on connections tagged `Production` + +### PostgreSQL Object Explorer +Full tree-view navigation of all native PostgreSQL schema objects: +- **Tables & Views** β€” columns, types, defaults, nullability, primary/foreign keys, indexes +- **Functions & Procedures** β€” source code with syntax highlighting, argument signatures, return types +- **Triggers & Rules** β€” event bindings (`BEFORE/AFTER INSERT/UPDATE/DELETE`) with inline definition inspection +- **Sequences & Enums** β€” current values, increments, custom enum options +- **Indexes & Constraints** β€” usage stats, composite keys, `UNIQUE` / `CHECK` definitions +- **Extensions** β€” installed extensions view (`pgvector`, `uuid-ossp`, `postgis`) with enable/disable toggling + +### SQL Editor & Query Workbench +- **Monaco Editor** β€” full SQL syntax highlighting, auto-indentation, error markers +- **Context-aware Autocomplete** β€” real-time schema introspection suggests tables, columns, and function signatures as you type +- **Query Formatter** β€” clean, styled display with keyword highlighting and code folding +- **History & Snippets** β€” automatic query logging with timestamps and execution duration; unlimited saved snippets organized by folder +- **Multi-Tab Workspace** β€” unlimited named tabs, drag-and-drop reorder, session persistence across restarts + +### Data Grid & Schema Browser +- **Virtualized Grid** β€” canvas/DOM-virtualized rendering handles 100k+ rows at 60fps (Glide Data Grid / TanStack Virtual) +- **Inline Editing** β€” double-click cells to edit, delete rows, or insert records directly +- **Visual Filter Builder** β€” multi-column filters without writing raw SQL +- **Export** β€” CSV, JSON, NDJSON, Excel, raw `INSERT` statements +- **Import** β€” load CSV/JSON files into tables with visual column mapping + +### PostgreSQL Administrative Tools +- **Visual Backup** β€” one-click `pg_dump` wrapper: Plain SQL, Custom, or Tar format; scope by full DB, schema-only, data-only, or specific tables +- **Visual Restore** β€” drag-and-drop `pg_restore` with dry-run mode and detailed error reporting +- **DB-to-DB Sync** β€” migrate data between environments with a table diff viewer showing inserted, modified, and missing rows before committing + +### App Portability +- **Export** β€” save all workspaces, folders, saved queries, tags, and non-sensitive metadata to a single JSON archive +- **Import** β€” restore your exact workspace setup on any machine instantly + +--- + +## Tech Stack + +| Layer | Technology | Role | +| :--- | :--- | :--- | +| **Desktop Shell** | [Tauri 2.0](https://tauri.app) | Native desktop container (~40MB RAM baseline) | +| **Backend** | Rust + [tokio](https://tokio.rs) | Async runtime, connection pooling, CLI tool execution | +| **Database Drivers** | [sqlx](https://github.com/launchbadge/sqlx) / [tokio-postgres](https://github.com/sfackler/rust-postgres) | Pure-Rust async PostgreSQL (MySQL, SQLite to follow) | +| **CLI Integration** | `std::process::Command` | Wraps system `pg_dump` / `pg_restore` binaries | +| **Frontend** | [React 19](https://react.dev) + [TypeScript](https://www.typescriptlang.org) | Component-based UI | +| **Styling** | [Tailwind CSS](https://tailwindcss.com) | Utility-first, dark mode, glassmorphic design | +| **State** | [Zustand](https://zustand.docs.pmnd.rs) / [Jotai](https://jotai.org) | Lightweight client-state for tabs, connections, queries | +| **Code Editor** | [Monaco Editor](https://microsoft.github.io/monaco-editor/) | IDE-grade SQL editing with autocomplete | +| **Data Grid** | [Glide Data Grid](https://grid.glideapps.com) / [TanStack Virtual](https://tanstack.com/virtual) | Virtualized 60fps table rendering | +| **Local DB** | SQLite via [rusqlite](https://github.com/rusqlite/rusqlite) | User settings, saved queries, workspace state | + +--- + +## Development + +### Prerequisites +- [Rust](https://rustup.rs) (latest stable) +- [Bun](https://bun.sh) (or Node.js + npm) +- Tauri system dependencies ([see guide](https://tauri.app/start/prerequisites/)) +- PostgreSQL client tools (`pg_dump`, `pg_restore`) for admin features + +### Setup + +```bash +# Clone +git clone https://github.com/adrianbonpin/gridline.git +cd gridline + +# Install frontend dependencies +bun install + +# Run in development mode (hot-reload) +bun run tauri dev + +# Build for production +bun run tauri build +``` + +### Project Structure + +``` +gridline/ +β”œβ”€β”€ src/ # React frontend +β”‚ β”œβ”€β”€ components/ # Reusable UI components +β”‚ β”œβ”€β”€ stores/ # Zustand/Jotai state stores +β”‚ β”œβ”€β”€ hooks/ # Custom React hooks +β”‚ β”œβ”€β”€ lib/ # Utilities, types, Tauri bindings +β”‚ β”œβ”€β”€ App.tsx # Root component +β”‚ └── main.tsx # Entry point +β”œβ”€β”€ src-tauri/ # Rust backend +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ main.rs # Entry point +β”‚ β”‚ β”œβ”€β”€ lib.rs # Tauri command registration +β”‚ β”‚ β”œβ”€β”€ db/ # Database connection & pooling +β”‚ β”‚ β”œβ”€β”€ commands/ # Tauri IPC command handlers +β”‚ β”‚ └── models/ # Data structures & serde types +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── tauri.conf.json +β”œβ”€β”€ public/ # Static assets +β”œβ”€β”€ package.json +β”œβ”€β”€ tsconfig.json +└── vite.config.ts +``` + +### Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Tauri Shell β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ React (WebView)β”‚ β”‚ Rust Backend β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Monaco Editor │◄──►│ β€’ sqlx/tokio-postgres β”‚ β”‚ +β”‚ β”‚ β€’ Glide Grid β”‚IPCβ”‚ β€’ Connection pool β”‚ β”‚ +β”‚ β”‚ β€’ Tailwind UI β”‚ β”‚ β€’ pg_dump/restore β”‚ β”‚ +β”‚ β”‚ β€’ Zustand state β”‚ β”‚ β€’ SQLite (local) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β€’ OS Keychain β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ PostgreSQL / MySQL β”‚ + β”‚ SQLite / Redis β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## Roadmap + +1. **Phase 1 β€” Core Shell & Storage** + - [x] Tauri 2.0 + React project scaffold + - [ ] SQLite persistence layer for workspaces, folders, connections, saved queries + - [ ] Workspace/folder tree UI + +2. **Phase 2 β€” Connection Management** + - [ ] Rust connection pool manager (`sqlx` / `tokio-postgres`) + - [ ] URI parser with auto-population + - [ ] OS Keychain credential storage + +3. **Phase 3 β€” Schema Explorer** + - [ ] PostgreSQL `pg_catalog` / `information_schema` introspection + - [ ] Full object tree (Tables, Views, Functions, Triggers, Enums, Sequences) + +4. **Phase 4 β€” Query Workbench** + - [ ] Monaco Editor integration with SQL autocomplete + - [ ] Virtualized data grid for query results + - [ ] Query history & saved snippets + +5. **Phase 5 β€” Admin Tools** + - [ ] `pg_dump` / `pg_restore` UI wrappers + - [ ] DB-to-DB schema & data sync + +6. **Phase 6 β€” Multi-Database Support** + - [ ] MySQL driver + - [ ] SQLite driver + - [ ] Redis support + +--- + +## License + +MIT β€” see [LICENSE](./LICENSE) for details. + +--- + +

+ Built with β™₯ for developers who believe powerful tools should be free. +

\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..ff93803 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + Tauri + React + Typescript + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b089c37 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "gridline", + "private": true, + "version": "0.1.0", + "description": "An open-source, high-performance database GUI client for PostgreSQL and beyond", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "typescript": "~5.8.3", + "vite": "^7.0.4", + "@tauri-apps/cli": "^2" + } +} diff --git a/public/tauri.svg b/public/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..5a67b2f --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "gridline" +version = "0.1.0" +description = "An open-source, high-performance database GUI client for PostgreSQL and beyond" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "gridline_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..4cdbf49 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,10 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default" + ] +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..4a277ef --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,14 @@ +// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +#[tauri::command] +fn greet(name: &str) -> String { + format!("Hello, {}! You've been greeted from Rust!", name) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .invoke_handler(tauri::generate_handler![greet]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..caa0dd5 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + gridline_lib::run() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..7aaa188 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Gridline", + "version": "0.1.0", + "identifier": "com.adrianbonpin.gridline", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Gridline", + "width": 1200, + "height": 800 + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..85f7a4a --- /dev/null +++ b/src/App.css @@ -0,0 +1,116 @@ +.logo.vite:hover { + filter: drop-shadow(0 0 2em #747bff); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafb); +} +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; +} + +.logo.tauri:hover { + filter: drop-shadow(0 0 2em #24c8db); +} + +.row { + display: flex; + justify-content: center; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +h1 { + text-align: center; +} + +input, +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); +} + +button { + cursor: pointer; +} + +button:hover { + border-color: #396cd8; +} +button:active { + border-color: #396cd8; + background-color: #e8e8e8; +} + +input, +button { + outline: none; +} + +#greet-input { + margin-right: 5px; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } + + a:hover { + color: #24c8db; + } + + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } + button:active { + background-color: #0f0f0f69; + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..8286a76 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import { invoke } from "@tauri-apps/api/core"; +import "./App.css"; + +function App() { + const [greetMsg, setGreetMsg] = useState(""); + const [name, setName] = useState(""); + + async function greet() { + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ + setGreetMsg(await invoke("greet", { name })); + } + + return ( +
+

Welcome to Tauri + React

+ + +

Click on the Tauri, Vite, and React logos to learn more.

+ +
{ + e.preventDefault(); + greet(); + }} + > + setName(e.currentTarget.value)} + placeholder="Enter a name..." + /> + +
+

{greetMsg}

+
+ ); +} + +export default App; diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..2be325e --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ddad22a --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [react()], + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +}));