Files
陈大猫 10ede85ae3 feat: Custom terminal themes with .itermcolors import (#245)
* feat: implement custom terminal themes with .itermcolors import (#228)\n\n- Add customThemeStore with CRUD operations and localStorage persistence\n- Create .itermcolors parser with RGB-to-hex conversion and auto theme type detection\n- Add CustomThemeEditor component with inline color pickers\n- Refactor ThemeCustomizeModal with Custom tab for create/import/edit/delete\n- Update all theme consumers (Terminal, TerminalLayer, LogView, ThemeSelectPanel,\n  SettingsTerminalTab, useSettingsState) to resolve custom themes\n- Add i18n keys for custom theme features (en + zh-CN)\n- Add isCustom flag to TerminalTheme model and STORAGE_KEY_CUSTOM_THEMES constant

* feat: add import .itermcolors button to Settings Terminal tab\n\nAllows importing .itermcolors files directly from Settings → Terminal\nwithout opening the theme modal first.

* fix: move delete button from editor to modal footer for clean layout\n\nThe delete button was rendering inside the CustomThemeEditor left panel,\ncausing misalignment with the full-width Cancel/Save footer. Now the\nfooter shows: [Delete] (left) | [Cancel] [Save] (right) when editing\nan existing custom theme.

* refactor: extract custom theme editor into standalone modal\n\nThe inline CustomThemeEditor was causing layout conflicts in the\nThemeCustomizeModal (editor + footer overlapping). Extracted into\na dedicated CustomThemeModal with:\n- Two-column layout: editor panel (left) + terminal preview (right)\n- Own footer: Delete (left) | Cancel + Save (right)\n- z-index 300 layering above the main theme modal\n- Proper scroll containment for the color editor

* fix: correct z-index stacking for custom theme modal\n\nRemoved inline style zIndex: 99999 from ThemeCustomizeModal that was\npushing it above CustomThemeModal. Now uses Tailwind z-[200] for the\nmain modal and z-[300] for the custom theme editor modal.

* feat: add new custom theme button to settings terminal tab\n\nReuses CustomThemeModal from the settings page. Creates a new theme\nbased on the currently selected theme, opens the editor modal, and\nautomatically selects the new theme on save.

* feat: add cross-window IPC sync for custom themes\n\nCustom themes created/imported/deleted in the Settings window are now\nimmediately synced to the main window (and vice versa) using the\nexisting netcatty:settings:changed IPC channel. Each mutation\nbroadcasts the change, and each window listens for incoming changes\nand reloads themes from localStorage.

* fix: show custom themes in ThemeSelectModal\n\nThemeSelectModal was only displaying built-in TERMINAL_THEMES.\nNow imports useCustomThemes hook and renders custom themes in a\nseparate section at the bottom of the theme list.

* feat: add edit/delete buttons for custom themes in settings\n\nWhen a custom theme is selected, Edit and Delete buttons appear next\nto the New/Import buttons. Edit opens the CustomThemeModal in edit\nmode, Delete removes the theme and falls back to the default theme.

* refactor: remove redundant header from CustomThemeEditor\n\nThe inner header with back arrow and title was duplicating the\nparent CustomThemeModal header. Removed the header block,\nArrowLeft import, and prefixed unused props with underscore.

* fix: add missing common.edit i18n key\n\nAdded 'Edit' / '编辑' translations for the common.edit key\nthat was showing as raw key text in the Settings page.

* fix: add error feedback for .itermcolors import in settings\n\nAdded step-by-step console logging for debugging import issues.\nShows user-visible alert on parse failure with localized message.\nAlso added terminal.customTheme.importError i18n keys.

* fix: handle extra keys in .itermcolors color dicts\n\nThe parseColorDict function assumed keys[i] aligned with reals[i],\nbut .itermcolors files with extra keys like 'Alpha Component' (real)\nand 'Color Space' (string) broke the index mapping.\n\nNow iterates through dict children properly, pairing each <key>\nwith its next sibling and skipping non-<real> values.

* fix: subscribe to custom theme store for reactive re-renders\n\nReplaced imperative customThemeStore.getThemeById() calls with reactive\nuseCustomThemes() hook in useMemo dependencies across 5 files:\n- useSettingsState.ts (currentTerminalTheme)\n- Terminal.tsx (effectiveTheme for host-override)\n- TerminalLayer.tsx (composeBarThemeColors)\n- LogView.tsx (currentTheme for log replay)\n- SettingsTerminalTab.tsx (currentTheme)\n\nThis ensures editing a custom theme in-place (same ID) triggers\nre-renders in all consuming components, instead of showing stale colors\nuntil the user switches theme IDs or reloads.

* fix: theme editor hex validation, import error feedback, and Escape propagation\n\n1. ColorInput: Use local state for text field so partial hex values\n   (#1, #abc) are held locally while typing. Only complete #rgb (auto-\n   normalized to #rrggbb) or #rrggbb values are committed to the theme.\n   On blur, partial values revert to the last valid color.\n\n2. ThemeCustomizeModal handleFileSelected: Added error feedback via\n   window.alert when .itermcolors parsing fails, reusing the existing\n   terminal.customTheme.importError i18n key. Also extended filename\n   regex to strip .xml extension.\n\n3. ThemeCustomizeModal Escape handler: Skip parent modal cancelation\n   when editingTheme is active, so pressing Escape only closes the\n   child CustomThemeModal without reverting the parent dialog.

* fix: backdrop click closes CustomThemeModal + remove nested buttons in ThemeItem\n\n1. CustomThemeModal: Attach onClick={onCancel} directly to the backdrop\n   div instead of checking e.target === e.currentTarget on the container.\n   The modal content div now stops event propagation to prevent\n   accidental dismissal when clicking inside the dialog.\n\n2. ThemeItem: Replace outer <button> with <div role=\"button\"> and inner\n   edit <button> with <div role=\"button\"> to eliminate invalid nested\n   interactive elements. Added keyboard handlers (Enter/Space) for\n   accessibility parity.

* fix: restore Escape key in CustomThemeModal + stabilize store snapshots\n\n1. CustomThemeModal: Add Escape key handler (capture phase) so pressing\n   Escape dismisses the child editor. Fixes regression where parent\n   ThemeCustomizeModal skips Escape when editingTheme is active but\n   the child had no handler of its own.\n\n2. customThemeStore: Cache the merged allThemes array (built-in +\n   custom) and only rebuild it when the store is mutated. The previous\n   getAllThemes() created a new array every call, violating the\n   useSyncExternalStore contract that getSnapshot must return a stable\n   reference between mutations.

* fix: accept <integer> plist nodes and guard NaN in itermcolors parser\n\nparseColorDict now accepts both <real> (float 0.0-1.0) and <integer>\n(0-255) plist value types for RGB components. Integer values are\nnormalized by dividing by 255. Also added isNaN guard on parseFloat\nresults to prevent malformed '#NaNNaNNaN' color strings from being\npersisted as custom themes.

* fix: use customThemeStore.getThemeById in HostDetailsPanel\n\nHostDetailsPanel used TERMINAL_THEMES.find() for both SSH and Telnet\ntheme previews, which only searched built-in themes. When a custom\ntheme was selected for a host, the preview fell back to Flexoki Dark\ndefaults. Now uses customThemeStore.getThemeById() which searches\nboth built-in and custom themes.

* chore: remove fake user counts from ThemeSelectPanel\n\nRemoved Math.random() generated fake user counts for Kanagawa and\nHacker themes, 'new' badges for Flexoki themes, and the Users icon.\nOnly meaningful labels remain: 'Default' for netcatty-dark and\n'Light mode' for netcatty-light.
2026-03-03 19:27:37 +08:00
..