Skip to content

Add native file dialog fs for tauri build#10

Open
CodeWithMa wants to merge 38 commits into
devfrom
feat/native-features-file-dialog-fs
Open

Add native file dialog fs for tauri build#10
CodeWithMa wants to merge 38 commits into
devfrom
feat/native-features-file-dialog-fs

Conversation

@CodeWithMa
Copy link
Copy Markdown
Owner

No description provided.

- Add storage adapter pattern with build-time provider selection
- Implement LocalStorageAdapter for web builds (localStorage)
- Implement TauriFileStorageAdapter for desktop builds (plugin-fs)
- Implement native file dialogs via @tauri-apps/plugin-dialog
- Add dialog and fs Rust plugins to Tauri backend
- Update capabilities for dialog and fs permissions
- Convert sync service methods to async for Tauri compatibility
- Web build continues to use localStorage + blob download
- Tauri build uses file system in AppData directory
The constructor was calling async loadData() without awaiting, causing
data to be null when components tried to access it during initialization.

Using APP_INITIALIZER ensures storage is loaded before any component
is created, providing a clean Angular pattern for initialization.
The hidden file input was causing a second dialog to open because
the adapter now handles its own file picking. Direct button click
to importData() now delegates to the adapter.
The confirmation was being shown at the same time as file dialog
because it was in the component while the adapter also opened a dialog.
Now the confirm dialog is shown in the adapter before the file picker.
Now the flow is: select file -> confirm -> import
The confirm is shown in the service after the adapter returns the data,
not before the file picker opens.
- await updateSettings() in settings component to handle rejections
- add setTimeout delay after confirm to ensure dialog is processed
Now returns true only when import actually completes. Component
shows success message only when importData returns true, not when
it silently returns on user cancellation.
The signal now holds the updated data with fresh timestamp,
not the stale original data that was passed in.
StorageService now handles lastModifiedAt and ensureUngroupedGroup.
Adapters are now simple pass-through for persistence, ensuring
signal and persisted data are identical.
Parse and validation errors are now propagated instead of being
swallowed, allowing the component to display error messages.
User cancellation still returns null/false as expected.
The onchange handler's async function was throwing errors into
a disconnected promise, causing the outer Promise to hang forever.
Now uses reject(error) to properly reject the outer Promise.
Added 'cancel' event listener to resolve Promise with null when
user dismisses the file dialog without selecting a file.
Interface now returns boolean (true=success, false=cancelled).
Tauri adapter returns false when user cancels save dialog.
Local adapter always returns true (download always succeeds).
Service propagates boolean to component.
Component only shows success when true is returned.
Create separate config files to avoid importing Tauri adapters in web builds:
- app.config.ts: base config without adapter imports
- app.config.web.ts: imports only local adapters
- app.config.tauri.ts: imports only tauri adapters
- angular.json: use fileReplacements to swap configs

This ensures web builds don't bundle Tauri plugin dependencies.
Now both environment and app.config are replaced per build target,
ensuring isTauri and enableServiceWorker settings are correct.
Single app.config.ts now uses environment to get adapter classes.
Environment files contain only the adapter class references that differ:
- base: no adapters (fallback for dev)
- web: local adapters only
- tauri: tauri adapters only

This removes need for separate config files per build target.
The development configuration now uses environment.web.ts,
matching the pattern used by development-tauri.
- Add LocalStorageAdapter and LocalImportExportAdapter to environment.ts
- Make properties required (not optional) in Environment interface
- Remove non-null assertions from app.config.ts
- Remove fileReplacements from development config (base is sufficient)
Phase 1: Create shared async action helper
- createAsyncAction() returns busy/error signals
- withAsyncAction() wraps async functions with try/catch/finally
- Supports parameterized functions

Phase 2: Update storage service with resource()
- Replace APP_INITIALIZER with resource() for initial load
- dataResource provides loading/error/value signals
- Remove manual loadData() method

Phase 3: Update components with busy/error handling
- home component: uses resource reload after writes
- item-list component: async action for mark operations
- item-card component: add disabled input for buttons

Phase 4: Update base environment with default adapters
- Base environment now has local adapters as defaults
- Enables direct use without fileReplacements
- development config no longer needs fileReplacements
Components were calling synchronous methods in constructors
before the resource had loaded, causing empty data on initial render.

Added effect() calls in:
- HomeComponent: reactively update next items when data loads
- GroupManagerComponent: reactively load groups when data loads
- SettingsComponent: reactively load settings when data loads

Also removed OnInit implementations that only called constructor logic.
Using reload() after save caused a brief loading state where
data() returned undefined, creating a visual flicker.

Now uses dataResource.set() to directly update the signal
synchronously, preserving the current data while persisting.
Effect() still fires to update derived state in components.
The busy signal still disables buttons to prevent concurrent
operations, but the loading message is no longer shown.
This prevents the visual flicker when operations complete quickly.
The old code called groupService.getAllGroups() in the constructor,
which only had default data (ungrouped) at construction time.

Now uses effect() that tracks this.groups() computed signal, so when
storage data loads asynchronously, the effect fires and expands
all groups.
The message now includes 'success' to properly trigger green styling.
Added error display in template and disabled state to buttons.
Methods now wrapped: createGroup, saveEdit, deleteGroup, moveUp, moveDown.
This provides error handling and prevents concurrent operations.
Adapters are instantiated with new in app.config.ts,
not via Angular DI. The decorator was misleading.
Classes are instantiated with new in app.config.ts,
not via Angular DI, so no decorator needed.
- getData() now throws during loading instead of returning default data
- saveData() blocks and throws if data is still loading
- Removed getDefaultData() - was a data corruption vector

This ensures no writes can overwrite real data with empty
default data during the async resource loading phase.
Effects now check if data exists before calling methods that
depend on getData(). This prevents 'Data not loaded' errors
during the initial async loading phase.
…-manager

Styles mirror the settings component for consistent message appearance.
Now shows errors when settings save fails instead of silently
swallowing them with showError: false.
- Add withAsyncAction to ItemDetailComponent (saveChanges, markWatched, markCompleted, deleteItem)
- Add effect-based data loading to guard against async loading states
- Add onError callback to withAsyncAction for rollback on failure
- Fix updateShowCompleted to revert checkbox on save failure and show error message
Move validateStorageDataStructure, validateGroup, validateItem, validateWatchHistoryEntry,
validateMigratedData, migrateDataOnly, ensureUngroupedGroup, and createDefaultData into
shared/data-validation.ts and shared/data-migration.ts to eliminate duplication across
import-export adapters, storage adapters, and services.
Remove redundant async/await wrapper and IIFE pattern from withAsyncAction calls in:
- GroupManagerComponent (createGroup, saveEdit, deleteGroup, moveUp, moveDown)
- AddItemComponent (onSubmit already correct)
- ItemListComponent (markWatched, markCompleted already correct)

This makes the async handling more direct and readable while preserving identical functionality.
… async error handling

- Rename error signal to message with {text, type} shape to eliminate
  fragile .includes('success') string matching in templates
- Unify SettingsComponent to use withAsyncAction consistently instead
  of manual try/catch with setTimeout
- Remove redundant try/catch in Tauri save and import adapters that
  only logged and re-threw
- Remove duplicate adapter-level validation in import adapters (the
  service already validates more thoroughly)
…data, and fix hanging promise on parse error

- Move effect() from ngOnInit into constructor in ItemDetailComponent
  (Angular effect() requires an injection context)
- Guard groups computed in ItemListComponent with null check to
  prevent 'Data not loaded' throw during initial async resource load
- Add try/catch with reject() in LocalImportExportAdapter so JSON
  parse errors reject the promise instead of hanging indefinitely
CodeWithMa added a commit that referenced this pull request Apr 16, 2026
Replace all inline styles and CSS files with Tailwind utility classes.
Configure Tailwind v4 with custom theme tokens preserving the existing
color palette, class-based dark mode, and PostCSS integration.
CodeWithMa added a commit that referenced this pull request Apr 16, 2026
* Migrate from plain CSS to Tailwind CSS v4 (#10)

Replace all inline styles and CSS files with Tailwind utility classes.
Configure Tailwind v4 with custom theme tokens preserving the existing
color palette, class-based dark mode, and PostCSS integration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant