Skip to content

Commit 287c2b6

Browse files
NewtTheWolfdebba
andauthored
feat: per-connection icon & accent color override (#189) (#241)
* feat(models): add ConnectionAppearance and IconOverride (#189) * fix(commands): preserve appearance on update_connection + complete serde roundtrips (#189) * feat(types): mirror ConnectionAppearance / IconOverride in TS (#189) * feat(connections): add save/delete_connection_icon tauri commands (#189) * fix(icon-validation): close SVG scanner bypasses + tmp cleanup (#189) * feat(tauri): register appearance commands + fs capability (#189) * feat(connections): cascade-delete custom icon with connection (#189) * feat(ui): add getConnectionAccent/getConnectionIcon resolvers (#189) * fix(ui): tree-shake icon pack + ConnectionIconImage state regressions (#189) - Extract curated 30-icon subset into connectionIconPack.ts (src-of-truth for picker + resolver) - Replace import * as LucideIcons with named pack import to enable tree-shaking - Reset failed state on path change so ConnectionIconImage recovers from prior failures - Guard onError against post-unmount setState via mountedRef - Add aria-hidden to emoji span; narrow getConnectionIcon Pick to "appearance" only - Add ConnectionIconImage.test.tsx with regression test for sticky-failed fix - Update global lucide-react mock in tests/setup.ts with pack icons * feat(ui): route 4 connection views through new resolvers (#189) Replace getDriverColor/getDriverIcon with getConnectionAccent/getConnectionIcon in OpenConnectionItem, ConnectionCard, ConnectionListItem, and VisualExplainModal so per-connection accent colours and icon overrides are honoured everywhere. * feat(ui): AppearanceSection color picker (#189) * fix(ui): sync displayed hex input to normalized value (#189) * feat(ui): AppearanceSection icon picker tabs (default/pack/emoji/image) (#189) * fix(ui): AppearanceSection race + empty-emoji UX + remove a11y (#189) * feat(ui): mount AppearanceSection in NewConnectionModal (#189) Add set_connection_appearance Rust command to patch the stored appearance field after save. Mount AppearanceSection at the bottom of the General tab, wired with appearance state pre-populated from initialConnection on edit. For new connections a stable UUID is generated on mount and used as the icon upload target; after save the appearance (with its icon path) is persisted under the real backend-minted id via set_connection_appearance. * fix(ui): clean up orphan icon files + tests for set_connection_appearance (#189) - Delete previous image when user picks a new one in AppearanceSection (intra-session orphan) - Delete uploaded-but-unsaved image on modal cancel via useEffect cleanup + wasSavedRef - Extract set_appearance_impl pure helper; add 3 unit tests (update, clear, missing-id) * i18n: add connectionAppearance strings to all locales (#189) * fix(ui): sync custom-color draft + remove unused i18n keys (#189) When the Custom hex input is opened after a palette swatch was picked, the draft now mirrors the current accentColor so closing the field can't silently revert it. Also drops i18n keys for errors that aren't wired up yet (tooLarge, invalidFormat) and the preview label that has no UI. * feat(ui): integrate react-colorful for full color picker (#189) Replace the manual hex text input and regex validation in AppearanceSection with HexColorPicker (saturation/hue picker) + HexColorInput (lib-validated hex input), keeping the 12-swatch curated palette intact. Removes HEX_RE, customDraft, hexError, and commitCustom (~30 lines). Updates tests to cover the new panel open/close and live-emit behaviour. * test(ui): make hex-input filter test meaningful instead of tautological (#189) * fix(image-upload): enable asset protocol scope for connection-icons directory The root cause of image upload silently breaking at display time: Tauri 2's assetProtocol was disabled (default), so convertFileSrc() produced URLs the webview refused to load. Fix: - Enable assetProtocol in tauri.conf.json, scoped to $APPDATA/connection-icons/** - Add fs:scope-appdata-recursive to capabilities so appDataDir() resolves - Add DEV-only console.error in ConnectionIconImage.onError to surface future failures * feat(ui): overhaul AppearanceSection UX — tab sync, preview, icon search, frimousse, image preview (#189) - Derive active tab reactively from value.icon.type (approach B: userTab ?? derivedTab) so reopening an edited connection shows the correct tab without stale useState - Add preview row at top showing current accent color dot + connection icon + name; threads driverManifest and connectionName props from NewConnectionModal - Add search input above icon pack grid (case-insensitive id filter) - Replace plain emoji <input> with Frimousse headless picker (built-in search, only emits valid single emoji) — removes graphemeCount, emojiDraft, emojiError, commitEmoji dead code - Show ConnectionIconImage thumbnail in Image tab after upload or when editing an existing image connection - Split pickImage() error handling into two distinct try/catch blocks with console.error logging so failures point to the exact failing step * test(appearance): update tests for Frimousse picker, icon search, and tab sync - Remove old emoji text-input tests (blur/validation no longer apply) - Add Frimousse mock to avoid async emoji data fetching in JSDOM - Add: emoji picker renders with search input, onEmojiSelect emits correctly - Add: pack icon search filters results by search term - Add: tab syncs to value.icon.type on external prop change (edit mode) * fix(ui): userTab stuck-tab regression + protocol-asset cargo feature (#189) Tab state used to stay on the user's last click even after value.icon changed externally (parent re-render with new initialConnection). Resets userTab to null when value.icon.type differs from the previous render. Also enables the protocol-asset Cargo feature on tauri — required at the crate level for assetProtocol to actually serve files via tauri:// URLs. Without it, assetProtocol in tauri.conf.json is a no-op. Minor: memoize filteredPackIcons, route "No preview" through i18n. * feat(ui): swap to emoji-picker-react + open icon pack to all lucide icons (#189) - Replace Frimousse with emoji-picker-react (dark theme, categories, skin tone selector, recent emojis, preview disabled) - Expand icon picker from 30 curated icons to all ~1900 lucide icons via dynamicIconImports with lazy loading + 120-result display cap - Add camelCase→kebab-case normalization for legacy stored icon ids in both resolver and picker - Wrap lazy icon components in Suspense in driverUI and AppearanceSection pack grid - Update test mocks: frimousse → emoji-picker-react, dynamicIconImports proxy in setup.ts - Add camelCase normalization tests in driverUI.test.tsx * fix(security): path-traversal guard in cascade_delete_if_image + copy_icon_for_duplicate (#189) - cascade_delete_if_image: canonicalize both paths and reject any file not under <app_data>/connection-icons/ before deletion - Add copy_icon_for_duplicate helper that copies icon files with the same path-containment guard, so duplicate_connection gives each duplicate its own icon file rather than sharing the original path - Wire copy_icon_for_duplicate into duplicate_connection in commands.rs; falls back to dropping the icon on copy failure instead of sharing - Add regression tests: cascade_delete_rejects_path_traversal, copy_icon_for_duplicate_produces_new_filename, copy_icon_for_duplicate_rejects_path_traversal * fix(ui): defer icon-file deletion until connection save (#189) Root cause of user data-loss bug: AppearanceSection eagerly called delete_connection_icon when the user picked a replacement image, so if the session was later cancelled the "previous" file was already gone but connections.json still referenced it. - AppearanceSection: remove eager delete_connection_icon from pickImage and removeImage; add onImageUploaded prop so the parent can track uploads - NewConnectionModal: track all uploaded paths in uploadedPathsRef - On cancel: delete every session upload except the original image (handles "pick A → B → C → cancel" correctly) - On save: delete every uploaded path except the final one, plus the original if the user replaced it - Add test: "does not eagerly delete previous image on pick" * fix(ui): proxy symbol guard + explicit HexColorInput prefixed (#189) - CONNECTION_ICON_PACK proxy: guard get trap with typeof key !== "string" so Symbol accesses (Symbol.toStringTag, Symbol.iterator, etc.) from Object.prototype.toString.call(), React DevTools, etc. return undefined instead of throwing TypeError in camelToKebab's .replace() call - HexColorInput: add explicit prefixed={false} to future-proof against react-colorful changing its default - Add tests: Symbol.toStringTag and Symbol.iterator proxy access * docs(readme): per-connection appearance + connection-icons/ storage (#189) * chore(types,lint): drop any usage, fix set-state-in-effect, jest-dom vitest matchers (#189) - connectionIconPack: typed IconLoader instead of `any` for dynamicIconImports loader - ConnectionIconImage: reset src/failed via render-phase comparison (avoids react-hooks/set-state-in-effect lint warning) and the matching test still passes - driverUI.test/AppearanceSection.test: remove `any` casts and unused locals - tests/setup.ts: switch to `@testing-library/jest-dom/vitest` so the type augmentation applies under TSC - tsconfig.app.json: include tests/setup.ts so the augmentation is picked up No behavior changes; TSC and ESLint now run clean. * feat(ui): move Appearance to its own tab in NewConnectionModal (#189) Previously the AppearanceSection lived inline at the bottom of the General tab. Promote it to a peer tab alongside SSL/SSH so per-connection icon and accent color get the same first-class treatment as other settings groups. - new "appearance" entry in the activeTab union and tab bar (always shown, no driver restriction) - pull the AppearanceSection JSX out of generalTabContent into its own appearanceTabContent - add newConnection.appearance i18n key across all 7 locales * style(ui): polish Appearance tab to match modal design system (#189) Align styling with the General tab and improve visual hierarchy: - preview row with accent gradient + status label - color swatches with Check on selected + custom button with Pipette icon - icon tabs gain identifying icons (Sparkles/Grid3x3/Smile/Image) - Default tab click is now the reset action (one-click revert) - pack icon grid in a bordered surface with cleaner selected state - emoji picker themed via --epr-* CSS variables to match dark theme, plus a selected-emoji status card with Clear button - image tab redesigned as a thumbnail + actions card --------- Co-authored-by: Andrea Debernardi <andrea@debbaweb.it>
1 parent bbf73c6 commit 287c2b6

43 files changed

Lines changed: 2164 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.de.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Die vollständige Galerie findest du auf [tabularis.dev](https://tabularis.dev).
9191
- Lokal gespeicherte Verbindungsprofile.
9292
- SSH-Tunnel und Passwortspeicherung im System-Keychain.
9393
- Verbindungsseite mit Grid-/Listenansicht und Echtzeitsuche.
94+
- Individuelles Erscheinungsbild pro Verbindung: eigenes Icon (Lucide, Emoji oder eigenes Bild) und Akzentfarbe.
9495

9596
### Datenbank-Explorer
9697

@@ -162,6 +163,7 @@ Wichtige Dateien:
162163
- `config.json`
163164
- `themes/`
164165
- `preferences/`
166+
- `connection-icons/` (eigene Bilder für Verbindungs-Icons)
165167

166168
In `config.json` unterstützt das Feld `language` die Werte `auto`, `en`, `it`, `es`, `zh`, `fr`, `de`.
167169

README.es.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ La galería completa está en [tabularis.dev](https://tabularis.dev).
9191
- Perfiles de conexión guardados localmente.
9292
- Túneles SSH y almacenamiento de contraseñas en el llavero del sistema.
9393
- Página de conexiones con vista de cuadrícula/lista y búsqueda en tiempo real.
94+
- Apariencia personalizada por conexión: icono propio (Lucide, emoji o imagen) y color de acento.
9495

9596
### Explorador de base de datos
9697

@@ -162,6 +163,7 @@ Archivos principales:
162163
- `config.json`
163164
- `themes/`
164165
- `preferences/`
166+
- `connection-icons/` (imágenes personalizadas para iconos de conexiones)
165167

166168
En `config.json`, el campo `language` admite `auto`, `en`, `it`, `es`, `zh`, `fr`, `de`.
167169

README.fr.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ La galerie complète est disponible sur [tabularis.dev](https://tabularis.dev).
9191
- Profils de connexion enregistrés localement.
9292
- Tunnels SSH et stockage des mots de passe dans le trousseau système.
9393
- Page de connexions avec vues grille/liste et recherche en temps réel.
94+
- Apparence personnalisée par connexion : icône (Lucide, emoji ou image) et couleur d'accent.
9495

9596
### Explorateur de base de données
9697

@@ -162,6 +163,7 @@ Fichiers principaux :
162163
- `config.json`
163164
- `themes/`
164165
- `preferences/`
166+
- `connection-icons/` (images personnalisées pour les icônes de connexion)
165167

166168
Dans `config.json`, le champ `language` prend en charge `auto`, `en`, `it`, `es`, `zh`, `fr`, `de`.
167169

README.it.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ La galleria completa è disponibile su [tabularis.dev](https://tabularis.dev).
9191
- Profili connessione salvati localmente.
9292
- Tunneling SSH e archiviazione password nel keychain di sistema.
9393
- Pagina connessioni con vista griglia/lista e ricerca in tempo reale.
94+
- Aspetto personalizzato per ogni connessione: icona (Lucide, emoji o immagine) e colore di accento.
9495

9596
### Esplora database
9697

@@ -162,6 +163,7 @@ File principali:
162163
- `config.json`
163164
- `themes/`
164165
- `preferences/`
166+
- `connection-icons/` (immagini personalizzate per le icone di connessione)
165167

166168
In `config.json`, il campo `language` supporta `auto`, `en`, `it`, `es`, `zh`, `fr`, `de`.
167169

README.ja.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ yay -S tabularis-bin
9191
- 接続プロファイルをローカルに保存。
9292
- SSH トンネルとシステムキーチェーンによるパスワード保存。
9393
- グリッド/リスト表示とリアルタイム検索を備えた接続ページ。
94+
- 接続ごとの外観カスタマイズ:アイコン(Lucide/絵文字/画像)とアクセントカラーを個別に設定可能。
9495

9596
### データベースエクスプローラー
9697

@@ -162,6 +163,7 @@ yay -S tabularis-bin
162163
- `config.json`
163164
- `themes/`
164165
- `preferences/`
166+
- `connection-icons/`(接続アイコン用のカスタム画像)
165167

166168
`config.json``language` フィールドは `auto``en``it``es``zh``fr``de` をサポートします。
167169

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ Join our [Discord server](https://discord.com/invite/K2hmhfHRSt) to talk with th
199199
- **Multi-Database Selection:** Select multiple MySQL/MariaDB databases in a single connection — each appears as its own collapsible node in the sidebar.
200200
- Save, manage, and clone connection profiles with secure local persistence.
201201
- **Redesigned Connections Page:** Grid and list view modes, real-time search, branded driver icons (PostgreSQL elephant, MySQL dolphin, SQLite cylinder) in their official colors.
202+
- **Per-Connection Appearance:** Override the icon and accent color of each saved connection. Choose any [Lucide icon](https://lucide.dev/icons/) (~1900, searchable), an emoji from the full Unicode picker, or upload a custom image (PNG/JPEG/WebP/GIF/SVG, ≤512 KB). Color is picked from a 12-swatch palette or a full hex picker.
202203
- **Open in Editor:** Right-click any connection in the sidebar to open it directly in the editor via context menu.
203204
- Manage **SSH Connections** from the connection manager.
204205
- Optional secure password storage in system **Keychain**.
@@ -315,6 +316,7 @@ Configuration is stored in `~/.config/tabularis/` (Linux), `~/Library/Applicatio
315316
- `config.json`: App settings (theme, language, page size).
316317
- `themes/`: Custom themes.
317318
- `preferences/`: Editor preferences per connection (tabs, queries, layout).
319+
- `connection-icons/`: Custom images uploaded as per-connection icons (see [Per-Connection Appearance](#connection-management)).
318320

319321
#### Editor Preferences
320322

README.zh-CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ yay -S tabularis-bin
9191
- 本地保存连接配置。
9292
- 支持 SSH 隧道和系统钥匙串密码存储。
9393
- 连接页面支持网格/列表视图与实时搜索。
94+
- 每个连接可单独自定义外观:自选图标(Lucide、Emoji 或自定义图片)和强调色。
9495

9596
### 数据库浏览器
9697

@@ -161,6 +162,7 @@ yay -S tabularis-bin
161162
- `config.json`
162163
- `themes/`
163164
- `preferences/`
165+
- `connection-icons/`(连接图标的自定义图片)
164166

165167
`config.json` 中的 `language` 字段支持 `auto``en``it``es``zh``fr``de`
166168

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
"buffer": "^6.0.3",
4444
"clsx": "^2.1.1",
4545
"dagre": "^0.8.5",
46+
"emoji-picker-react": "^4.19.1",
4647
"i18next": "^25.10.10",
4748
"i18next-browser-languagedetector": "^8.2.1",
4849
"json-edit-react": "^1.29.1",
4950
"lucide-react": "^0.563.0",
5051
"monaco-editor": "^0.55.1",
5152
"process": "^0.11.10",
5253
"react": "^19.2.4",
54+
"react-colorful": "^5.7.0",
5355
"react-dom": "^19.2.4",
5456
"react-i18next": "^16.6.6",
5557
"react-markdown": "^10.1.0",

pnpm-lock.yaml

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)