diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..88fc3db --- /dev/null +++ b/docs/README.md @@ -0,0 +1,83 @@ +# Do It - Developer Documentation + +This document provides an overview of the Do It project architecture and how to navigate its source code. + +## Project Overview + +**Do It** is a GNOME task manager application written in TypeScript, now being refactored for multiplatform support. The codebase follows a clean architecture pattern that separates platform-specific code from core business logic. + +## Architecture + +``` +src/ +├── core/ # Platform-agnostic business logic & interfaces +├── platform/ # Platform-specific implementations +│ ├── gnome/ # GNOME/GTK4/Adw implementation +│ └── web/ # Web platform (future) +├── hooks/ # Reusable hooks (mostly platform-agnostic) +├── utils/ # Utility functions +└── *.ts # App entry points and shared types +``` + +## Key Concepts + +### Core Layer (`src/core/`) + +Contains platform-agnostic interfaces and implementations: + +- **interfaces/**: Abstract interfaces for settings, persistence, and task views +- **persistence/**: Data storage implementations + +### Platform Layer (`src/platform/`) + +Contains platform-specific implementations: + +- Each platform has its own subfolder with platform-specific code +- Platforms expose a public API via `index.ts` + +### Shared Types (`src/`) + +- `app.types.ts`: Core types like `ITask`, `IProject` +- `app.enums.ts`: Shared enums like `SortingField`, `SortingStrategy` +- `app.strings.ts`: Localized strings +- `app.static.ts`: Static configuration + +## Platform-Specific Documentation + +- [GNOME Platform](platform-gnome.md) - Current active platform +- [Web Platform](platform-web.md) - Future web implementation + +## Development Guidelines + +### Adding a New Platform + +1. Create `src/platform//` +2. Implement core interfaces (`ISettings`, `IPersistence`, `ITaskView`) +3. Create UI components using platform-specific frameworks +4. Export public API via `index.ts` +5. Add to build system (Meson for GNOME, Vite/Webpack for web) + +### Dependency Injection + +The project uses factory patterns for dependency injection. Core interfaces should never import platform-specific code. + +### Import Aliases + +The project uses TypeScript path aliases: + +- `~core` → `src/core` +- `~gnome` → `src/platform/gnome` +- `~web` → `src/platform/web` +- `~hooks` → `src/hooks` +- `~utils` → `src/utils` +- `~actions` → `src/platform/gnome/actions` + +## Building + +- **GNOME**: Use Meson (`meson setup build && ninja -C build`) +- **Web**: Use Vite (future) + +## Resources + +- [GNOME Human Interface Guidelines](https://developer.gnome.org/hig/) +- [Adwaita Widgets](https://gnome.pages.gitlab.gnome.org/libadwaita/) diff --git a/docs/core.md b/docs/core.md new file mode 100644 index 0000000..07a926d --- /dev/null +++ b/docs/core.md @@ -0,0 +1,136 @@ +# Core Layer Documentation + +The `src/core/` directory contains platform-agnostic business logic and interfaces that can be shared across all platforms. + +## Directory Structure + +``` +src/core/ +├── interfaces/ +│ ├── settings.ts # Settings abstraction +│ ├── persistence.ts # Data persistence abstraction +│ └── task-view.ts # Task view abstraction +└── persistence/ + └── file-persistence.ts # File-based persistence implementation +``` + +## Interfaces + +### ISettings (`interfaces/settings.ts`) + +Abstract interface for application settings. Platforms must implement this to provide persistent key-value storage. + +```typescript +export interface ISettings { + get_int(key: string): number; + set_int(key: string, value: number): void; + get_string(key: string): string; + set_string(key: string, value: string): void; + get_enum(key: string): number; + set_enum(key: string, value: number): void; +} +``` + +**Implementations:** + +- GNOME: `src/hooks/settings.ts` (uses `Gio.Settings`) + +### IPersistence (`interfaces/persistence.ts`) + +Abstract interface for task data persistence. Defines async load/save operations. + +```typescript +import type { ITask } from '../../app.types.js'; + +export interface IPersistence { + load(): Promise; + save(tasks: ITask[]): Promise; +} +``` + +**Implementations:** + +- GNOME: `src/core/persistence/file-persistence.ts` (file-based JSON) +- Web (planned): IndexedDB + +### ITaskView (`interfaces/task-view.ts`) + +Abstract interface for task display widgets. Provides a bridge between task data and UI components. + +```typescript +import type { ITask } from '../../app.types.js'; + +export interface ITaskView { + taskId: number; + title: string; + done: boolean; + deleted: boolean; + project: string; + created: string; + + update(task: ITask): void; + onTaskUpdated(callback: (task: ITask) => void): void; + onTaskDeleted(callback: (task: ITask) => void): void; + to_object(): ITask; +} +``` + +**Implementations:** + +- GNOME: `src/platform/gnome/views/task-item.ts` + +## Implementations + +### FilePersistence (`persistence/file-persistence.ts`) + +File-based JSON persistence implementation for GNOME. + +```typescript +import type { ITask } from '../../app.types.js'; +import type { IPersistence } from '../interfaces/persistence.js'; + +export class FilePersistence implements IPersistence { + // Loads tasks from JSON file + async load(): Promise; + + // Saves tasks to JSON file + async save(tasks: ITask[]): Promise; +} +``` + +## Design Principles + +### Platform Agnostic + +The core layer must never import platform-specific code: + +- No GTK, Adw, Gio imports +- No browser-specific APIs +- Only TypeScript types and interfaces + +### Dependency Injection + +Platforms inject their implementations: + +```typescript +// Example: Creating platform-specific instances +let settings: ISettings; +let persistence: IPersistence; + +if (platform === 'gnome') { + settings = new GnomeSettings(); + persistence = new FilePersistence(); +} else if (platform === 'web') { + settings = new WebSettings(); + persistence = new IndexedDBPersistence(); +} +``` + +### Extensibility + +To add a new interface: + +1. Create `src/core/interfaces/.ts` +2. Define TypeScript interface +3. Implement in each platform +4. Document in this file diff --git a/docs/hooks-utils.md b/docs/hooks-utils.md new file mode 100644 index 0000000..b9e8622 --- /dev/null +++ b/docs/hooks-utils.md @@ -0,0 +1,125 @@ +# Hooks and Utils Documentation + +This document describes the helper functions and utilities in `src/hooks/` and `src/utils/`. + +## Hooks (`src/hooks/`) + +Hooks are reusable functions that provide platform-agnostic functionality. They should remain free of platform-specific imports. + +### tasks.sort.ts + +Provides task sorting functionality. + +```typescript +// Sorts tasks by specified field and strategy +sortTasks(tasks: ITask[], field: SortingField, strategy: SortingStrategy): ITask[] +``` + +**Dependencies:** + +- Imports `ITask` from `app.types` +- Uses `ITaskView` from core interfaces (for comparison) + +### settings.ts + +> ⚠️ **Note**: This hook currently uses GNOME-specific `Gio.Settings`. + +```typescript +// Provides access to application settings +getSettings(): ISettings +``` + +**Status:** Needs abstraction for web platform. + +### autocomplete.ts + +Provides project name autocomplete suggestions. + +```typescript +// Returns matching project names +getSuggestions(input: string): string[] +``` + +## Utils (`src/utils/`) + +Utility functions, some platform-specific. + +### application.js + +GNOME application bootstrap. + +```javascript +// Returns path to UI template +get_template_path(filename: string): string + +// Initializes the GTK application +activate(application: Gtk.Application): void +``` + +**Platform:** GNOME only + +### project-manager.ts + +Manages project CRUD operations. + +```typescript +class ProjectManager { + getProjects(): string[]; + addProject(name: string): void; + removeProject(name: string): void; +} +``` + +**Platform:** GNOME (uses Adw/Gtk) + +### persistence.ts + +Legacy persistence utilities. + +> ⚠️ **Note**: Should migrate to use `core/persistence/file-persistence.ts` + +### log-manager.ts + +Logging utilities. + +```typescript +class LogManager { + debug(message: string): void; + info(message: string): void; + error(message: string): void; +} +``` + +## Guidelines + +### Keeping Hooks Platform-Agnostic + +When modifying hooks: + +1. **DO**: Use interfaces from `src/core/interfaces/` +2. **DO**: Import types from `src/app.types.ts` +3. **DON'T**: Import GTK, Adw, Gio, or browser APIs +4. **DON'T**: Import from `src/platform/gnome/` + +### Example: Refactoring for Platform Agnosticism + +Before (GNOME-specific): + +```typescript +import { Settings } from 'gi://Gio'; + +export function getSettings() { + return new Settings({ schema: 'io.github.andrepg.Doit' }); +} +``` + +After (Platform-agnostic): + +```typescript +import type { ISettings } from '~core/interfaces/settings'; + +export function createSettings(): ISettings { + // Platform-specific implementation injected + return platform.createSettings(); +} +``` diff --git a/docs/platform-gnome.md b/docs/platform-gnome.md new file mode 100644 index 0000000..1f6ea05 --- /dev/null +++ b/docs/platform-gnome.md @@ -0,0 +1,176 @@ +# GNOME Platform Documentation + +The GNOME platform is the current primary implementation of the Do It task manager, using GTK4 and libadwaita. + +## Directory Structure + +``` +src/platform/gnome/ +├── index.ts # Public API exports +├── enums.ts # GNOME-specific enums +├── actions/ # GAction implementations +│ ├── index.ts +│ ├── backup.ts # Import/export database +│ ├── toast.ts # Toast notifications +│ ├── task-edit.ts # Task editing actions +│ ├── purge-deleted.ts # Purge deleted tasks +│ ├── quit.ts # Quit application +│ ├── shortcuts.ts # Shortcuts dialog +│ ├── sidebar.ts # Sidebar actions +│ ├── projects.ts # Project management +│ ├── projects-sidebar.ts +│ ├── new-task.ts # New task creation +│ └── about.ts # About dialog +├── views/ # GTK widget implementations +│ ├── index.ts +│ ├── doit.ts # Main window controller +│ ├── task-list-store.ts # ListStore for tasks +│ ├── task-list.ts # Task list widget +│ ├── task-item.ts # Individual task row +│ ├── task-group.ts # Grouped tasks +│ ├── task-form.ts # Task edit form +│ ├── popover-sort.ts # Sorting popover +│ ├── sidebar-button.ts # Sidebar button widget +│ └── ... +└── widgets/ # UI definitions (.ui files) + ├── *.ui # GTK UI templates + ├── meson.build # Build config for widgets + └── io.github.andrepg.Doit.data.gresource.xml.in +``` + +## Key Components + +### Actions (`actions/`) + +All actions use GNOME's `GAction` system and interact with Adwaita components: + +- **backup.ts**: Export/import database as JSON +- **task-edit.ts**: Open task editor, save/discard changes +- **new-task.ts**: Create new tasks with quick entry +- **toast.ts**: Show AdwToast notifications + +### Views (`views/`) + +Views implement GTK4 widgets that display and interact with tasks: + +- **doit.ts**: Main window controller, initializes split view +- **task-list.ts**: `Gtk.ListView` backed by `Gtk.ListStore` +- **task-item.ts**: Individual task row with checkbox, title, actions +- **task-form.ts**: Full task editing form with all fields + +### Widgets (`widgets/`) + +UI templates in GTK4's `.ui` format: + +- **application.ui**: Main application window +- **task.ui**: Task row template +- **task-form.ui**: Task editing form +- **sidebar-button.ui**: Sidebar item template +- **popover-sort.ui**: Sorting options popover + +## Enums (`enums.ts`) + +GNOME-specific enums that shouldn't be shared: + +- **AppSignals**: GObject signal names +- **ActionNames**: GAction names +- **CssClasses**: CSS class names +- **WidgetIds**: Widget identifier strings + +## Build System + +The GNOME platform uses Meson: + +1. `.ui` files are compiled into GResources via `gresource.xml` +2. TypeScript is compiled to JavaScript and bundled +3. Resources are embedded in the final binary + +### Resource Paths + +When adding new `.ui` files: + +1. Add to `widgets/meson.build` +2. Update `io.github.andrepg.Doit.data.gresource.xml.in` +3. Update `get_template_path()` in `src/utils/application.js` + +## Interface Implementations + +The GNOME platform implements these core interfaces: + +### ISettings + +Uses `Gio.Settings` for persistent key-value storage: + +```typescript +// Implemented via Gio.Settings in hooks/settings.ts +``` + +### IPersistence + +Uses file-based JSON storage: + +```typescript +// src/core/persistence/file-persistence.ts +``` + +### ITaskView + +Implemented in `views/task-item.ts`: + +```typescript +// Updates GTK widgets when task data changes +``` + +## Customizations + +### Settings Hook + +`src/hooks/settings.ts` wraps `Gio.Settings`: + +- Uses schema from `io.github.andrepg.Doit` +- Provides typed get/set for each setting + +### Task Sorting + +`src/hooks/tasks.sort.ts`: + +- Uses core interfaces (`ITaskView`) +- Implements sorting by various fields + +### Autocomplete + +`src/hooks/autocomplete.ts`: + +- Provides project name suggestions +- Uses GTK `Gtk.EntryCompletion` + +## Common Tasks + +### Adding a New Action + +1. Create `src/platform/gnome/actions/my-action.ts` +2. Register in `actions/index.ts` +3. Connect to UI via `activate()` callback or signal + +### Adding a New View + +1. Create `src/platform/gnome/views/my-view.ts` +2. Extend appropriate GTK widget class +3. Implement required interfaces +4. Export in `views/index.ts` + +### Adding a New UI Template + +1. Create `src/platform/gnome/widgets/my-widget.ui` +2. Add to `widgets/meson.build` (ui_files) +3. Update gresource XML with path +4. Use `get_template_path()` in JavaScript + +## Migration Notes + +When working with this platform: + +- **DO NOT** import GTK/Adw/Gio in `src/core/` or `src/hooks/` +- Keep platform-specific code in `src/platform/gnome/` +- Use dependency injection for cross-platform compatibility +- Test on both GNOME 45+ and newer versions diff --git a/docs/platform-web.md b/docs/platform-web.md new file mode 100644 index 0000000..a120a17 --- /dev/null +++ b/docs/platform-web.md @@ -0,0 +1,78 @@ +# Web Platform Documentation + +> **Note**: The web platform is planned for Phase 3 of the migration and has not been implemented yet. + +## Planned Architecture + +``` +src/platform/web/ +├── index.ts # Public API exports +├── enums.ts # Web-specific enums (if any) +├── components/ # Web components +│ ├── task-list.ts # Task list component +│ ├── task-item.ts # Task row component +│ ├── task-form.ts # Task editing form +│ └── ... +├── services/ # Web-specific services +│ ├── storage.ts # IndexedDB persistence +│ └── router.ts # URL routing +└── styles/ # CSS styles + └── *.css +``` + +## Planned Implementations + +### ISettings + +Will use `localStorage` or `IndexedDB`: + +```typescript +// Planned: WebSettings implementation +``` + +### IPersistence + +Will use IndexedDB via `idb` library: + +```typescript +// Planned: IndexedDBPersistence implementation +``` + +### ITaskView + +Will use web components or framework components: + +```typescript +// Planned: WebTaskItem implementation +``` + +## Build System + +The web platform will likely use: + +- **Vite**: Build tool and dev server +- **TypeScript**: Type checking +- **CSS Modules** or **Tailwind**: Styling + +## Migration from GNOME + +Key differences from GNOME platform: + +| Aspect | GNOME | Web | +| ------- | ------------------------ | ------------------------ | +| UI | GTK4/Adwaita | React/Vue/Web Components | +| Storage | Gio.Settings + JSON file | IndexedDB | +| Routing | GTK actions | URL router | +| Build | Meson | Vite/Webpack | + +## Implementation Checklist + +When implementing the web platform: + +- [ ] Create `src/platform/web/` directory structure +- [ ] Implement `ISettings` using localStorage +- [ ] Implement `IPersistence` using IndexedDB +- [ ] Create web UI components +- [ ] Set up Vite build configuration +- [ ] Add CSS styling +- [ ] Test cross-platform compatibility diff --git a/docs/shared-types.md b/docs/shared-types.md new file mode 100644 index 0000000..c48177c --- /dev/null +++ b/docs/shared-types.md @@ -0,0 +1,125 @@ +# Shared Types Documentation + +The `src/` root contains shared types, enums, and entry points used across all platforms. + +## Types (`app.types.ts`) + +Core domain types that are platform-agnostic. + +### ITask + +```typescript +export interface ITask { + id?: number; + title: string; + created_at: number; + project?: string; + tags?: string[]; + deleted?: boolean; + done?: boolean; +} +``` + +The main task entity. Used by all platforms for task data. + +### ISortingFieldOption + +```typescript +export interface ISortingFieldOption { + label: string; + icon?: string; + mode: SortingField; +} +``` + +UI configuration for sorting field options. + +### ISortingStrategyOption + +```typescript +export interface ISortingStrategyOption { + icon: string; + strategy: SortingStrategy; +} +``` + +UI configuration for sorting strategy options. + +## Enums (`app.enums.ts`) + +Platform-agnostic enumerations. + +### SortingField + +```typescript +export enum SortingField { + byDate = 0, + byStatus = 1, + byTitle = 2, + byProject = 3, +} +``` + +Fields by which tasks can be sorted. + +### SortingStrategy + +```typescript +export enum SortingStrategy { + ascending = 0, + descending = 1, +} +``` + +Sort direction. + +### DoItSettings + +```typescript +export const DoItSettings = { + windowHeight: 'window-height', + windowWidth: 'window-width', +}; +``` + +Settings keys for window dimensions. + +## Static Data (`app.static.ts`) + +Static configuration constants. + +## Strings (`app.strings.ts`) + +Localized UI strings. + +## Entry Points + +### app.entrypoint.ts + +Application entry point. + +### app.wrapper.ts + +Wrapper that initializes platform-specific components. + +## Platform-Specific Enums + +> **Note**: Platform-specific enums are in their respective platform directories: +> +> - GNOME: `src/platform/gnome/enums.ts` (AppSignals, ActionNames, CssClasses, WidgetIds) +> - Web: `src/platform/web/enums.ts` (future) + +## Design Guidelines + +### What Goes in Root `src/` + +- Types used by all platforms +- Enums that are truly shared +- Entry points that bootstrap the app +- Localization strings + +### What Goes in Platform Directories + +- Platform-specific enums +- UI components +- Platform-specific implementations of core interfaces diff --git a/flatpak/io.github.andrepg.Doit.Devel.json b/flatpak/io.github.andrepg.Doit.Devel.json index 76170ef..9418a90 100644 --- a/flatpak/io.github.andrepg.Doit.Devel.json +++ b/flatpak/io.github.andrepg.Doit.Devel.json @@ -14,7 +14,6 @@ "--device=dri", "--socket=wayland", "--filesystem=host", - "--bind-mount=/usr/share/icons=/run/host/usr/share/icons", "--env=LC_ALL=pt_BR.UTF-8" ], "build-options": { diff --git a/meson.build b/meson.build index 658b207..72e00db 100644 --- a/meson.build +++ b/meson.build @@ -53,7 +53,7 @@ message('## Application Data Dir: ' + app_datadir) # ## Process Source Code and Javascript buildable subdir('data') -subdir('ui') +subdir('src/platform/gnome/widgets') subdir('src') subdir('po') diff --git a/src/app.enums.ts b/src/app.enums.ts index 97e53e3..e4b3419 100644 --- a/src/app.enums.ts +++ b/src/app.enums.ts @@ -17,9 +17,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -/** - * Defines the available fields by which tasks can be sorted. - */ export enum SortingField { byDate = 0, byStatus = 1, @@ -27,127 +24,17 @@ export enum SortingField { byProject = 3, } -/** - * Defines the direction of the sorting strategy. - */ export enum SortingStrategy { ascending = 0, descending = 1, } -/** - * GSettings keys for sorting mode preferences. - */ export const SortingModeSchema = { MODE: 'sorting-mode', STRATEGY: 'sorting-strategy', }; -/** - * GSettings keys for main window application settings. - */ export const DoItSettings = { windowHeight: 'window-height', windowWidth: 'window-width', }; - -/** - * Global signal names used throughout the project. - */ -export enum AppSignals { - // Standard GTK/GObject interactions - Apply = 'apply', - Clicked = 'clicked', - Toggled = 'toggled', - NotifyActive = 'notify::active', - Activate = 'activate', - Activated = 'activated', - ItemsChanged = 'items-changed', - - // Custom Project state signals - ProjectAdded = 'project-added', - ProjectRemoved = 'project-removed', - FilterChanged = 'filter-changed', - - // Custom Task and Window states - SortingChanged = 'sorting-changed', - TaskUpdated = 'task-updated', - TaskDeleted = 'task-deleted', - TaskFormClosed = 'task-form-closed', -} - -/** - * Names of standard GIO Actions. - */ -export enum ActionNames { - // Application wide general actions - About = 'about', - Quit = 'quit', - Shortcuts = 'shortcuts', - - // Background jobs or management - ExportDatabase = 'export_database', - ImportDatabase = 'import_database', - PurgeDeletedTasks = 'purge_deleted_tasks', - - // UI toggles and layout control - ToggleSidebar = 'toggle-sidebar', - CollapseSidebar = 'collapse-sidebar', - ShowToast = 'show-toast', - TaskEdit = 'task-edit', - TaskEditClose = 'task-edit-close', -} - -/** - * Standard CSS classes used in the application. - */ -export enum CssClasses { - SuggestedAction = 'suggested-action', - Devel = 'devel', -} - -/** - * Common template child IDs across the application. - */ -export enum WidgetIds { - // Main UI layouts and menus - WindowSplitView = 'split_view', - WindowListContainer = 'list_container', - WindowToastOverlay = 'toast_overlay', - WindowSidebarProjectList = 'sidebar_project_list', - - // Header bar widgets - WindowButtonOpenSidebar = 'button_open_sidebar', - WindowButtonToggleSidebar = 'button_toggle_sidebar', - WindowButtonNewTask = 'button_new_task', - WindowTaskNewEntry = 'task_new_entry', - WindowButtonSorting = 'button_sorting', - - // Popover Sort toggle group IDs - PopoverSortToggleGroupSortField = 'toggle-group-sort-field', - PopoverSortToggleGroupSortStrategy = 'toggle-group-sort-strategy', - PopoverSortLabelStrategy = 'label_strategy', - - // Sidebar button Components - SidebarButtonContent = 'button_content', - SidebarButtonIcon = 'button_icon', - - // Task Item components - TaskItemTaskDone = 'task_done', - TaskItemTaskDelete = 'task_delete', - - // Task Form components - TaskFormEntryTitle = 'task_form_entry_title', - TaskFormEntryProject = 'task_form_entry_project', - TaskFormCheckDone = 'task_form_check_done', - TaskFormBtnDelete = 'task_form_btn_delete', - TaskFormBtnSave = 'task_form_btn_save', - TaskFormBtnDiscard = 'task_form_btn_discard', - - // Window Bottom Sheet - WindowBottomSheet = 'window_bottom_sheet', - WindowBottomSheetContent = 'window_bottom_sheet_content', - - // Task Form - TaskFormWidget = 'task_form', -} diff --git a/src/app.wrapper.ts b/src/app.wrapper.ts index 7ff78cc..f3ba523 100644 --- a/src/app.wrapper.ts +++ b/src/app.wrapper.ts @@ -22,9 +22,9 @@ import Adw from 'gi://Adw'; import { is_development_mode, APPLICATION_ID, APPLICATION_RES } from './utils/application.js'; import { log } from './utils/log-manager.js'; -import * as Actions from './actions/index.js'; +import * as Actions from './platform/gnome/actions/index.js'; -import { DoItMainWindow } from './ui-handler/doit.js'; +import { DoItMainWindow } from './platform/gnome/views/doit.js'; const options = { GTypeName: 'DoitApplication' }; diff --git a/src/core/interfaces/persistence.ts b/src/core/interfaces/persistence.ts new file mode 100644 index 0000000..f6f72f1 --- /dev/null +++ b/src/core/interfaces/persistence.ts @@ -0,0 +1,24 @@ +/* persistence.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +import type { ITask } from '../../app.types.js'; + +export interface IPersistence { + load(): Promise; + save(tasks: ITask[]): Promise; +} diff --git a/src/core/interfaces/settings.ts b/src/core/interfaces/settings.ts new file mode 100644 index 0000000..338a90e --- /dev/null +++ b/src/core/interfaces/settings.ts @@ -0,0 +1,26 @@ +/* settings.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +export interface ISettings { + get_int(key: string): number; + set_int(key: string, value: number): void; + get_string(key: string): string; + set_string(key: string, value: string): void; + get_enum(key: string): number; + set_enum(key: string, value: number): void; +} diff --git a/src/core/interfaces/task-view.ts b/src/core/interfaces/task-view.ts new file mode 100644 index 0000000..3fc5eee --- /dev/null +++ b/src/core/interfaces/task-view.ts @@ -0,0 +1,33 @@ +/* task-view.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +import type { ITask } from '../../app.types.js'; + +export interface ITaskView { + taskId: number; + title: string; + done: boolean; + deleted: boolean; + project: string; + created: string; + + update(task: ITask): void; + onTaskUpdated(callback: (task: ITask) => void): void; + onTaskDeleted(callback: (task: ITask) => void): void; + to_object(): ITask; +} diff --git a/src/core/persistence/file-persistence.ts b/src/core/persistence/file-persistence.ts new file mode 100644 index 0000000..c385fb7 --- /dev/null +++ b/src/core/persistence/file-persistence.ts @@ -0,0 +1,74 @@ +/* file-persistence.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; + +import type { ITask } from '../../app.types.js'; +import type { IPersistence } from '../interfaces/persistence.js'; + +export class FilePersistence implements IPersistence { + private databaseFileName = 'data.json'; + + private encoder = new TextEncoder(); + private decoder = new TextDecoder(); + + private databaseLocation: Gio.File = Gio.File.new_for_path(GLib.get_user_data_dir() as string); + + private databaseFilePath = GLib.build_filenamev([ + this.databaseLocation.get_path() as string, + this.databaseFileName, + ]) as string; + + private databaseFileHandler = Gio.File.new_for_path(this.databaseFilePath); + + constructor() { + this.check_database_existence(); + } + + check_database_existence() { + if ( + !this.databaseLocation?.query_exists(null) || + !this.databaseFileHandler?.query_exists(null) + ) { + try { + this.databaseLocation.make_directory_with_parents(null); + this.databaseFileHandler.create(Gio.FileCreateFlags.PRIVATE, null); + } catch { + console.error('[persistence] Error creating database'); + } + } + } + + async load(): Promise { + this.check_database_existence(); + + const [, content] = this.databaseFileHandler.load_contents(null); + + const file_content = this.decoder.decode(content).trim(); + return file_content === '' ? [] : (JSON.parse(file_content) as ITask[]); + } + + async save(tasks: ITask[]): Promise { + this.check_database_existence(); + + const file = this.encoder.encode(JSON.stringify(tasks)); + + this.databaseFileHandler.replace_contents(file, null, true, Gio.FileCreateFlags.PRIVATE, null); + } +} diff --git a/src/hooks/tasks.sort.ts b/src/hooks/tasks.sort.ts index edfc1da..57c868e 100644 --- a/src/hooks/tasks.sort.ts +++ b/src/hooks/tasks.sort.ts @@ -17,12 +17,10 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ import { SortingField, SortingModeSchema, SortingStrategy } from '~/app.enums.js'; -import { TaskItem } from '~/ui-handler/task-item.js'; +import type { ITaskView } from '~/core/interfaces/task-view.js'; import { useSettings } from './settings.js'; -interface IExtractorFunction { - (item: T): unknown; -} +type ExtractorFunction = (item: ITaskView) => unknown; /** * React-like hook that provides task sorting capabilities and state management. @@ -37,13 +35,13 @@ export const useTaskSort = () => { const settings = useSettings(); - const create_comparator = (extractors: IExtractorFunction[], strategy: SortingStrategy) => { + const create_comparator = (extractors: ExtractorFunction[], strategy: SortingStrategy) => { const isAscending = strategy === SortingStrategy.ascending; const compare_numeric = (a: number, b: number) => a - b; const compare_string = (a: string, b: string) => a.localeCompare(b); - return (a: T, b: T) => { + return (a: ITaskView, b: ITaskView) => { for (const extractor of extractors) { const value_of_a = extractor(a); const value_of_b = extractor(b); @@ -65,15 +63,15 @@ export const useTaskSort = () => { }; const sort_by_date = (strategy: SortingStrategy) => { - return create_comparator([(item) => (item as TaskItem).to_object().created_at], strategy); + return create_comparator([(item: ITaskView) => new Date(item.created).getTime()], strategy); }; const sort_by_status = (strategy: SortingStrategy) => { - return create_comparator([(item) => ((item as TaskItem).to_object().done ? 1 : 0)], strategy); + return create_comparator([(item: ITaskView) => (item.done ? 1 : 0)], strategy); }; const sort_by_title = (strategy: SortingStrategy) => { - return create_comparator([(item) => (item as TaskItem).to_object().title], strategy); + return create_comparator([(item: ITaskView) => item.title], strategy); }; const sort_by_project_name = (strategy: SortingStrategy) => { @@ -89,9 +87,9 @@ export const useTaskSort = () => { }; const sort_by_project = (strategy: SortingStrategy) => { - return (a: TaskItem, b: TaskItem) => { - const project_a = a.to_object().project || ''; - const project_b = b.to_object().project || ''; + return (a: ITaskView, b: ITaskView) => { + const project_a = a.project || ''; + const project_b = b.project || ''; return sort_by_project_name(strategy)(project_a, project_b); }; }; diff --git a/src/io.github.andrepg.Doit.src.gresource.xml.in b/src/io.github.andrepg.Doit.src.gresource.xml.in index 39d6bf5..42d68b2 100644 --- a/src/io.github.andrepg.Doit.src.gresource.xml.in +++ b/src/io.github.andrepg.Doit.src.gresource.xml.in @@ -15,30 +15,38 @@ utils/project-manager.js application.js - ui-handler/task-form.js - ui-handler/doit.js - ui-handler/popover-sort.js - ui-handler/sidebar-button.js - ui-handler/task-group.js - ui-handler/task-item.js - ui-handler/task-list.js - ui-handler/task-list-store.js + core/interfaces/settings.js + core/interfaces/persistence.js + core/interfaces/task-view.js + core/persistence/file-persistence.js + + platform/gnome/views/task-form.js + platform/gnome/views/doit.js + platform/gnome/views/popover-sort.js + platform/gnome/views/sidebar-button.js + platform/gnome/views/task-group.js + platform/gnome/views/task-item.js + platform/gnome/views/task-list.js + platform/gnome/views/task-list-store.js + platform/gnome/views/index.js + platform/gnome/enums.js + platform/gnome/index.js hooks/autocomplete.js hooks/settings.js hooks/tasks.sort.js - actions/about.js - actions/new-task.js - actions/quit.js - actions/backup.js - actions/sidebar.js - actions/task-edit.js - actions/toast.js - actions/projects.js - actions/projects-sidebar.js - actions/purge-deleted.js - actions/shortcuts.js - actions/index.js + platform/gnome/actions/about.js + platform/gnome/actions/new-task.js + platform/gnome/actions/quit.js + platform/gnome/actions/backup.js + platform/gnome/actions/sidebar.js + platform/gnome/actions/task-edit.js + platform/gnome/actions/toast.js + platform/gnome/actions/projects.js + platform/gnome/actions/projects-sidebar.js + platform/gnome/actions/purge-deleted.js + platform/gnome/actions/shortcuts.js + platform/gnome/actions/index.js diff --git a/src/actions/about.ts b/src/platform/gnome/actions/about.ts similarity index 97% rename from src/actions/about.ts rename to src/platform/gnome/actions/about.ts index 5a1dff5..bbf4615 100644 --- a/src/actions/about.ts +++ b/src/platform/gnome/actions/about.ts @@ -20,7 +20,7 @@ import Gio from 'gi://Gio'; import Adw from 'gi://Adw'; import Gtk from 'gi://Gtk'; -import { ActionNames, AppSignals } from '~/app.enums.js'; +import { ActionNames, AppSignals } from '../enums.js'; import { AppLocale } from '~/app.strings.js'; import { APPLICATION_ID, APPLICATION_NAME } from '~/utils/application.js'; diff --git a/src/actions/backup.ts b/src/platform/gnome/actions/backup.ts similarity index 89% rename from src/actions/backup.ts rename to src/platform/gnome/actions/backup.ts index b46b95e..171967c 100644 --- a/src/actions/backup.ts +++ b/src/platform/gnome/actions/backup.ts @@ -19,12 +19,12 @@ import Adw from 'gi://Adw'; import Gio from 'gi://Gio'; import Gtk from 'gi://Gtk'; -import { ActionNames, AppSignals } from '~/app.enums.js'; -import { ITask } from '~/app.types.js'; -import { AppLocale } from '~/app.strings.js'; +import { ActionNames, AppSignals } from '../enums.js'; +import { ITask } from '../../../app.types.js'; +import { AppLocale } from '../../../app.strings.js'; -import { log } from '~/utils/log-manager.js'; -import { Persistence } from '~/utils/persistence.js'; +import { log } from '../../../utils/log-manager.js'; +import { FilePersistence } from '../../../core/persistence/file-persistence.js'; import { showToast } from './toast.js'; /** @@ -66,7 +66,7 @@ const backup = () => { const dialog = createFileChooser(AppLocale.app.backup.export); dialog.save(parent, null, (dialog, result) => { - const tasks = new Persistence().read_database(); + const tasks = new FilePersistence().load(); const file = dialog?.save_finish(result); try { @@ -98,7 +98,7 @@ const backup = () => { const [ok, content] = file?.load_contents(null) || [false, null]; if (!ok || !content) return; const tasks = JSON.parse(decoder.decode(content)) as ITask[]; - new Persistence().write_database(tasks); + new FilePersistence().save(tasks); showToast(AppLocale.app.backup.importSuccess); } catch (error) { diff --git a/src/actions/index.ts b/src/platform/gnome/actions/index.ts similarity index 100% rename from src/actions/index.ts rename to src/platform/gnome/actions/index.ts diff --git a/src/actions/new-task.ts b/src/platform/gnome/actions/new-task.ts similarity index 92% rename from src/actions/new-task.ts rename to src/platform/gnome/actions/new-task.ts index 96b7b29..8d472a7 100644 --- a/src/actions/new-task.ts +++ b/src/platform/gnome/actions/new-task.ts @@ -15,14 +15,14 @@ * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later -*/ + */ import Gtk40 from 'gi://Gtk'; -import { AppSignals, WidgetIds } from '~/app.enums.js'; -import { AppLocale } from '~/app.strings.js'; +import { AppSignals, WidgetIds } from '../enums.js'; +import { AppLocale } from '../../../app.strings.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; -import { TaskListStore } from '~/ui-handler/task-list-store.js'; +import { DoItMainWindow } from '../views/doit.js'; +import { TaskListStore } from '../views/task-list-store.js'; import { showToast } from './toast.js'; diff --git a/src/actions/projects-sidebar.ts b/src/platform/gnome/actions/projects-sidebar.ts similarity index 90% rename from src/actions/projects-sidebar.ts rename to src/platform/gnome/actions/projects-sidebar.ts index 1bf690c..5e8fb25 100644 --- a/src/actions/projects-sidebar.ts +++ b/src/platform/gnome/actions/projects-sidebar.ts @@ -18,14 +18,15 @@ */ import Gtk40 from 'gi://Gtk'; -import { AppSignals, SortingStrategy, WidgetIds } from '~/app.enums.js'; -import { AppLocale } from '~/app.strings.js'; +import { AppSignals, WidgetIds } from '../enums.js'; +import { SortingStrategy } from '../../../app.enums.js'; +import { AppLocale } from '../../../app.strings.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; -import { SidebarButton } from '~/ui-handler/sidebar-button.js'; +import { DoItMainWindow } from '../views/doit.js'; +import { SidebarButton } from '../views/sidebar-button.js'; -import { ProjectManager } from '~/utils/project-manager.js'; -import { useTaskSort } from '../hooks/tasks.sort.js'; +import { ProjectManager } from '../../../utils/project-manager.js'; +import { useTaskSort } from '../../../hooks/tasks.sort.js'; /** * Initializes and manages the sidebar list of discovered projects. @@ -135,10 +136,7 @@ export default function projectSidebar(projectManager: ProjectManager) { projectSidebarItems.forEach((button, project) => { const isAllTasksButton = project === ALL_TASKS; - button.set_active( - (isAllTasksButton && currentFilter === null) || - (project === currentFilter), - ); + button.set_active((isAllTasksButton && currentFilter === null) || project === currentFilter); }); } diff --git a/src/actions/projects.ts b/src/platform/gnome/actions/projects.ts similarity index 91% rename from src/actions/projects.ts rename to src/platform/gnome/actions/projects.ts index 340cd7d..efb36a2 100644 --- a/src/actions/projects.ts +++ b/src/platform/gnome/actions/projects.ts @@ -18,14 +18,15 @@ */ import Adw from 'gi://Adw'; -import { AppSignals, SortingField, WidgetIds } from '~/app.enums.js'; -import { useTaskSort } from '~/hooks/tasks.sort.js'; +import { AppSignals, WidgetIds } from '../enums.js'; +import { SortingField } from '../../../app.enums.js'; +import { useTaskSort } from '../../../hooks/tasks.sort.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; -import { TaskGroup } from '~/ui-handler/task-group.js'; -import { TaskListStore } from '~/ui-handler/task-list-store.js'; +import { DoItMainWindow } from '../views/doit.js'; +import { TaskGroup } from '../views/task-group.js'; +import { TaskListStore } from '../views/task-list-store.js'; -import { ProjectManager } from '~/utils/project-manager.js'; +import { ProjectManager } from '../../../utils/project-manager.js'; /** * Initializes and manages the grouping of tasks by project in the main view. diff --git a/src/actions/purge-deleted.ts b/src/platform/gnome/actions/purge-deleted.ts similarity index 92% rename from src/actions/purge-deleted.ts rename to src/platform/gnome/actions/purge-deleted.ts index 52ff8a3..dcf28f3 100644 --- a/src/actions/purge-deleted.ts +++ b/src/platform/gnome/actions/purge-deleted.ts @@ -19,9 +19,9 @@ import Adw1 from 'gi://Adw'; import Gio from 'gi://Gio'; -import { ActionNames, AppSignals } from '~/app.enums.js'; +import { ActionNames, AppSignals } from '../enums.js'; -import { TaskListStore } from '~/ui-handler/task-list-store.js'; +import { TaskListStore } from '../views/task-list-store.js'; /** * Provides an action to permanently remove soft-deleted tasks from the database. diff --git a/src/actions/quit.ts b/src/platform/gnome/actions/quit.ts similarity index 96% rename from src/actions/quit.ts rename to src/platform/gnome/actions/quit.ts index 60ce021..59b3902 100644 --- a/src/actions/quit.ts +++ b/src/platform/gnome/actions/quit.ts @@ -19,7 +19,7 @@ import Adw from 'gi://Adw'; import Gio from 'gi://Gio'; -import { ActionNames, AppSignals } from '~/app.enums.js'; +import { ActionNames, AppSignals } from '../enums.js'; /** * Provides the application quit action and keyboard shortcut bindings. diff --git a/src/actions/shortcuts.ts b/src/platform/gnome/actions/shortcuts.ts similarity index 96% rename from src/actions/shortcuts.ts rename to src/platform/gnome/actions/shortcuts.ts index d2edb4e..88b17ef 100644 --- a/src/actions/shortcuts.ts +++ b/src/platform/gnome/actions/shortcuts.ts @@ -20,7 +20,7 @@ import Gio from 'gi://Gio'; import Adw from 'gi://Adw'; import Gtk from 'gi://Gtk'; -import { ActionNames, AppSignals } from '~/app.enums.js'; +import { ActionNames, AppSignals } from '../enums.js'; import { APPLICATION_RES } from '~/utils/application.js'; /** diff --git a/src/actions/sidebar.ts b/src/platform/gnome/actions/sidebar.ts similarity index 94% rename from src/actions/sidebar.ts rename to src/platform/gnome/actions/sidebar.ts index 770f369..c6aa366 100644 --- a/src/actions/sidebar.ts +++ b/src/platform/gnome/actions/sidebar.ts @@ -19,8 +19,8 @@ import Adw from 'gi://Adw'; import Gio from 'gi://Gio'; -import { ActionNames, AppSignals, WidgetIds } from '~/app.enums.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; +import { ActionNames, AppSignals, WidgetIds } from '../enums.js'; +import { DoItMainWindow } from '../views/doit.js'; /** * Retrieves the split_view template child from the window. diff --git a/src/actions/task-edit.ts b/src/platform/gnome/actions/task-edit.ts similarity index 92% rename from src/actions/task-edit.ts rename to src/platform/gnome/actions/task-edit.ts index c4bf93b..accb225 100644 --- a/src/actions/task-edit.ts +++ b/src/platform/gnome/actions/task-edit.ts @@ -20,9 +20,9 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Adw1 from 'gi://Adw'; -import { ActionNames, AppSignals, WidgetIds } from '~/app.enums.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; -import { TaskForm } from '~/ui-handler/task-form.js'; +import { ActionNames, AppSignals, WidgetIds } from '../enums.js'; +import { DoItMainWindow } from '../views/doit.js'; +import { TaskForm } from '../views/task-form.js'; import { log } from '~/utils/log-manager.js'; /** diff --git a/src/actions/toast.ts b/src/platform/gnome/actions/toast.ts similarity index 95% rename from src/actions/toast.ts rename to src/platform/gnome/actions/toast.ts index 78957c4..1aa6dba 100644 --- a/src/actions/toast.ts +++ b/src/platform/gnome/actions/toast.ts @@ -19,8 +19,8 @@ import Adw from 'gi://Adw'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; -import { ActionNames, AppSignals, WidgetIds } from '~/app.enums.js'; -import { DoItMainWindow } from '~/ui-handler/doit.js'; +import { ActionNames, AppSignals, WidgetIds } from '../enums.js'; +import { DoItMainWindow } from '../views/doit.js'; /** * Displays an overlay toast on the currently active window. diff --git a/src/platform/gnome/enums.ts b/src/platform/gnome/enums.ts new file mode 100644 index 0000000..eced206 --- /dev/null +++ b/src/platform/gnome/enums.ts @@ -0,0 +1,81 @@ +/* enums.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +export enum AppSignals { + Apply = 'apply', + Clicked = 'clicked', + Toggled = 'toggled', + NotifyActive = 'notify::active', + Activate = 'activate', + Activated = 'activated', + ItemsChanged = 'items-changed', + ProjectAdded = 'project-added', + ProjectRemoved = 'project-removed', + FilterChanged = 'filter-changed', + SortingChanged = 'sorting-changed', + TaskUpdated = 'task-updated', + TaskDeleted = 'task-deleted', + TaskFormClosed = 'task-form-closed', +} + +export enum ActionNames { + About = 'about', + Quit = 'quit', + Shortcuts = 'shortcuts', + ExportDatabase = 'export_database', + ImportDatabase = 'import_database', + PurgeDeletedTasks = 'purge_deleted_tasks', + ToggleSidebar = 'toggle-sidebar', + CollapseSidebar = 'collapse-sidebar', + ShowToast = 'show-toast', + TaskEdit = 'task-edit', + TaskEditClose = 'task-edit-close', +} + +export enum CssClasses { + SuggestedAction = 'suggested-action', + Devel = 'devel', +} + +export enum WidgetIds { + WindowSplitView = 'split_view', + WindowListContainer = 'list_container', + WindowToastOverlay = 'toast_overlay', + WindowSidebarProjectList = 'sidebar_project_list', + WindowButtonOpenSidebar = 'button_open_sidebar', + WindowButtonToggleSidebar = 'button_toggle_sidebar', + WindowButtonNewTask = 'button_new_task', + WindowTaskNewEntry = 'task_new_entry', + WindowButtonSorting = 'button_sorting', + PopoverSortToggleGroupSortField = 'toggle-group-sort-field', + PopoverSortToggleGroupSortStrategy = 'toggle-group-sort-strategy', + PopoverSortLabelStrategy = 'label_strategy', + SidebarButtonContent = 'button_content', + SidebarButtonIcon = 'button_icon', + TaskItemTaskDone = 'task_done', + TaskItemTaskDelete = 'task_delete', + TaskFormEntryTitle = 'task_form_entry_title', + TaskFormEntryProject = 'task_form_entry_project', + TaskFormCheckDone = 'task_form_check_done', + TaskFormBtnDelete = 'task_form_btn_delete', + TaskFormBtnSave = 'task_form_btn_save', + TaskFormBtnDiscard = 'task_form_btn_discard', + WindowBottomSheet = 'window_bottom_sheet', + WindowBottomSheetContent = 'window_bottom_sheet_content', + TaskFormWidget = 'task_form', +} diff --git a/src/platform/gnome/index.ts b/src/platform/gnome/index.ts new file mode 100644 index 0000000..03f5cf1 --- /dev/null +++ b/src/platform/gnome/index.ts @@ -0,0 +1,21 @@ +/* index.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +export * from './enums.js'; +export * as actions from './actions/index.js'; +export * from './views/index.js'; diff --git a/src/ui-handler/doit.ts b/src/platform/gnome/views/doit.ts similarity index 91% rename from src/ui-handler/doit.ts rename to src/platform/gnome/views/doit.ts index c418f3d..442cc57 100644 --- a/src/ui-handler/doit.ts +++ b/src/platform/gnome/views/doit.ts @@ -20,15 +20,16 @@ import Adw from 'gi://Adw'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; -import { AppSignals, DoItSettings, WidgetIds } from '~/app.enums.js'; +import { AppSignals, WidgetIds } from '../enums.js'; +import { DoItSettings } from '../../../app.enums.js'; -import * as Actions from '~/actions/index.js'; +import * as Actions from '../actions/index.js'; -import { APPLICATION_NAME, get_template_path } from '~/utils/application.js'; -import { log } from '~/utils/log-manager.js'; +import { APPLICATION_NAME, get_template_path } from '../../../utils/application.js'; +import { log } from '../../../utils/log-manager.js'; -import { useSettings } from '~/hooks/settings.js'; -import { ProjectManager } from '~/utils/project-manager.js'; +import { useSettings } from '../../../hooks/settings.js'; +import { ProjectManager } from '../../../utils/project-manager.js'; import { TaskListStore } from './task-list-store.js'; import { TaskForm } from './task-form.js'; diff --git a/src/platform/gnome/views/index.ts b/src/platform/gnome/views/index.ts new file mode 100644 index 0000000..614d9bc --- /dev/null +++ b/src/platform/gnome/views/index.ts @@ -0,0 +1,26 @@ +/* index.ts + * Copyright 2025 André Paul Grandsire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +export { DoItMainWindow } from './doit.js'; +export { PopoverSort } from './popover-sort.js'; +export { SidebarButton } from './sidebar-button.js'; +export { TaskForm } from './task-form.js'; +export { TaskGroup } from './task-group.js'; +export { TaskItem } from './task-item.js'; +export { TaskList } from './task-list.js'; +export { TaskListStore } from './task-list-store.js'; diff --git a/src/ui-handler/popover-sort.ts b/src/platform/gnome/views/popover-sort.ts similarity index 96% rename from src/ui-handler/popover-sort.ts rename to src/platform/gnome/views/popover-sort.ts index 74c6dc1..15429a5 100644 --- a/src/ui-handler/popover-sort.ts +++ b/src/platform/gnome/views/popover-sort.ts @@ -21,14 +21,15 @@ import GObject from 'gi://GObject'; import Adw1 from 'gi://Adw'; -import { AppSignals, SortingField, SortingStrategy, WidgetIds } from '~/app.enums.js'; +import { AppSignals, WidgetIds } from '../enums.js'; +import { SortingField, SortingStrategy } from '../../../app.enums.js'; import { ISortingFieldOption, ISortingStrategyOption } from '~/app.types.js'; import { get_template_path } from '~/utils/application.js'; import { SortingFieldOptions, SortingModeOptions } from '~/app.static.js'; -import { useTaskSort } from '../hooks/tasks.sort.js'; +import { useTaskSort } from '../../../hooks/tasks.sort.js'; import { AppLocale } from '~/app.strings.js'; const GObjectProperties = { diff --git a/src/ui-handler/sidebar-button.ts b/src/platform/gnome/views/sidebar-button.ts similarity index 98% rename from src/ui-handler/sidebar-button.ts rename to src/platform/gnome/views/sidebar-button.ts index aac846c..74e2bdd 100644 --- a/src/ui-handler/sidebar-button.ts +++ b/src/platform/gnome/views/sidebar-button.ts @@ -19,7 +19,7 @@ import Gtk40 from 'gi://Gtk'; import GObject from 'gi://GObject'; -import { CssClasses, WidgetIds } from '~/app.enums.js'; +import { CssClasses, WidgetIds } from '../enums.js'; import { AppLocale } from '~/app.strings.js'; import { SymbolicIcons } from '~/app.static.js'; diff --git a/src/ui-handler/task-form.ts b/src/platform/gnome/views/task-form.ts similarity index 97% rename from src/ui-handler/task-form.ts rename to src/platform/gnome/views/task-form.ts index 5836f00..9665ab5 100644 --- a/src/ui-handler/task-form.ts +++ b/src/platform/gnome/views/task-form.ts @@ -20,9 +20,9 @@ import Adw from 'gi://Adw'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; -import { showToast } from '~/actions/toast.js'; +import { showToast } from '../actions/toast.js'; -import { AppSignals, WidgetIds } from '~/app.enums.js'; +import { AppSignals, WidgetIds } from '../enums.js'; import { AppLocale } from '~/app.strings.js'; import { ITask } from '~/app.types.js'; @@ -34,7 +34,6 @@ import { TaskListStore } from './task-list-store.js'; import { ProjectManager } from '~/utils/project-manager.js'; - const TaskFormProperties = { GTypeName: 'TaskForm', Template: get_template_path('task-form.ui'), @@ -165,7 +164,7 @@ export class TaskForm extends Gtk.Box { /** * Setup project autocomplete on project entry */ - private setup_project_autocomplete(): void { } + private setup_project_autocomplete(): void {} /* diff --git a/src/ui-handler/task-group.ts b/src/platform/gnome/views/task-group.ts similarity index 98% rename from src/ui-handler/task-group.ts rename to src/platform/gnome/views/task-group.ts index 065d379..ab962c1 100644 --- a/src/ui-handler/task-group.ts +++ b/src/platform/gnome/views/task-group.ts @@ -20,7 +20,7 @@ import Adw from 'gi://Adw'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; -import { AppSignals } from '~/app.enums.js'; +import { AppSignals } from '../enums.js'; import { AppLocale } from '~/app.strings.js'; import { get_template_path } from '~/utils/application.js'; diff --git a/src/ui-handler/task-item.ts b/src/platform/gnome/views/task-item.ts similarity index 88% rename from src/ui-handler/task-item.ts rename to src/platform/gnome/views/task-item.ts index 27e0701..6b18d26 100644 --- a/src/ui-handler/task-item.ts +++ b/src/platform/gnome/views/task-item.ts @@ -20,12 +20,13 @@ import Adw from 'gi://Adw'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; -import { showToast } from '~/actions/toast.js'; +import { showToast } from '../actions/toast.js'; -import { AppSignals, WidgetIds } from '~/app.enums.js'; +import { AppSignals, WidgetIds } from '../enums.js'; import { AppLocale } from '~/app.strings.js'; import { TaskDeleteButtonIcon, TaskEntryStyle } from '~/app.static.js'; import { ITask } from '~/app.types.js'; +import type { ITaskView } from '~/core/interfaces/task-view.js'; import { get_template_path } from '~/utils/application.js'; @@ -98,7 +99,7 @@ const TaskItemProperties = { * creation date as a subtitle, and provides interactions such as a checkbox * for marking the task as done and a button for deleting it. */ -export class TaskItem extends Adw.ActionRow { +export class TaskItem extends Adw.ActionRow implements ITaskView { static { GObject.registerClass(TaskItemProperties, this); } @@ -282,4 +283,26 @@ export class TaskItem extends Adw.ActionRow { tags: this._tags, }; } + + public update(task: ITask): void { + this._taskId = task.id ?? 0; + this.title = task.title; + this.done = task.done ?? false; + this.deleted = task.deleted ?? false; + this.project = task.project ?? ''; + this.created = new Date(task.created_at).toISOString(); + this._update_interface(); + } + + public onTaskUpdated(callback: (task: ITask) => void): void { + this.connect(AppSignals.TaskUpdated, () => { + callback(this.to_object()); + }); + } + + public onTaskDeleted(callback: (task: ITask) => void): void { + this.connect(AppSignals.TaskDeleted, () => { + callback(this.to_object()); + }); + } } diff --git a/src/ui-handler/task-list-store.ts b/src/platform/gnome/views/task-list-store.ts similarity index 93% rename from src/ui-handler/task-list-store.ts rename to src/platform/gnome/views/task-list-store.ts index e695d31..32a12d2 100644 --- a/src/ui-handler/task-list-store.ts +++ b/src/platform/gnome/views/task-list-store.ts @@ -22,11 +22,11 @@ import GLib from 'gi://GLib'; import { useTaskSort } from '~/hooks/tasks.sort.js'; -import { ActionNames, AppSignals } from '~/app.enums.js'; -import { ITask } from '~/app.types.js'; +import { ActionNames, AppSignals } from '../enums.js'; +import { ITask } from '../../../app.types.js'; -import { log } from '~/utils/log-manager.js'; -import { Persistence } from '~/utils/persistence.js'; +import { log } from '../../../utils/log-manager.js'; +import { FilePersistence } from '../../../core/persistence/file-persistence.js'; import { TaskItem } from './task-item.js'; import { DoItMainWindow } from './doit.js'; @@ -54,7 +54,7 @@ export class TaskListStore extends Gio.ListStore { GObject.registerClass(TaskListStoreType, this); } - private persistence = new Persistence(); + private persistence = new FilePersistence(); private task_sort = useTaskSort(); /** @@ -176,7 +176,7 @@ export class TaskListStore extends Gio.ListStore { log('list-store', 'Saving tasks to database'); const tasks = this.get_all().filter((item) => (keep_deleted ? true : !item.deleted)); - this.persistence.write_database(tasks); + this.persistence.save(tasks); } /** @@ -198,10 +198,10 @@ export class TaskListStore extends Gio.ListStore { /** * Initializes the application state, reading tasks from the disk and appending them to the store. */ - load() { + async load() { log('list-store', 'Loading tasks from database'); - const tasks = this.persistence.read_database() as ITask[]; + const tasks = await this.persistence.load(); tasks.forEach((item) => { log('list-store', `Loading task ${item.title}`); diff --git a/src/ui-handler/task-list.ts b/src/platform/gnome/views/task-list.ts similarity index 97% rename from src/ui-handler/task-list.ts rename to src/platform/gnome/views/task-list.ts index 8ca11f8..1cdc051 100644 --- a/src/ui-handler/task-list.ts +++ b/src/platform/gnome/views/task-list.ts @@ -20,7 +20,7 @@ import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import Gio from 'gi://Gio'; -import { AppSignals } from '~/app.enums.js'; +import { AppSignals } from '../enums.js'; import { get_template_path } from '~/utils/application.js'; import { TaskItem } from './task-item.js'; diff --git a/ui/application.ui b/src/platform/gnome/widgets/application.ui similarity index 100% rename from ui/application.ui rename to src/platform/gnome/widgets/application.ui diff --git a/ui/empty-list.ui b/src/platform/gnome/widgets/empty-list.ui similarity index 100% rename from ui/empty-list.ui rename to src/platform/gnome/widgets/empty-list.ui diff --git a/ui/io.github.andrepg.Doit.data.gresource.xml.in b/src/platform/gnome/widgets/io.github.andrepg.Doit.data.gresource.xml.in similarity index 90% rename from ui/io.github.andrepg.Doit.data.gresource.xml.in rename to src/platform/gnome/widgets/io.github.andrepg.Doit.data.gresource.xml.in index a014089..9dba7b1 100644 --- a/ui/io.github.andrepg.Doit.data.gresource.xml.in +++ b/src/platform/gnome/widgets/io.github.andrepg.Doit.data.gresource.xml.in @@ -1,6 +1,6 @@ - + empty-list.ui shortcuts-dialog.ui popover-sort.ui diff --git a/ui/meson.build b/src/platform/gnome/widgets/meson.build similarity index 100% rename from ui/meson.build rename to src/platform/gnome/widgets/meson.build diff --git a/ui/popover-sort.ui b/src/platform/gnome/widgets/popover-sort.ui similarity index 100% rename from ui/popover-sort.ui rename to src/platform/gnome/widgets/popover-sort.ui diff --git a/ui/shortcuts-dialog.ui b/src/platform/gnome/widgets/shortcuts-dialog.ui similarity index 100% rename from ui/shortcuts-dialog.ui rename to src/platform/gnome/widgets/shortcuts-dialog.ui diff --git a/ui/sidebar-button.ui b/src/platform/gnome/widgets/sidebar-button.ui similarity index 100% rename from ui/sidebar-button.ui rename to src/platform/gnome/widgets/sidebar-button.ui diff --git a/ui/task-form.ui b/src/platform/gnome/widgets/task-form.ui similarity index 100% rename from ui/task-form.ui rename to src/platform/gnome/widgets/task-form.ui diff --git a/ui/task-group.ui b/src/platform/gnome/widgets/task-group.ui similarity index 100% rename from ui/task-group.ui rename to src/platform/gnome/widgets/task-group.ui diff --git a/ui/task-list.ui b/src/platform/gnome/widgets/task-list.ui similarity index 100% rename from ui/task-list.ui rename to src/platform/gnome/widgets/task-list.ui diff --git a/ui/task.ui b/src/platform/gnome/widgets/task.ui similarity index 100% rename from ui/task.ui rename to src/platform/gnome/widgets/task.ui diff --git a/ui/window-v2.ui b/src/platform/gnome/widgets/window-v2.ui similarity index 100% rename from ui/window-v2.ui rename to src/platform/gnome/widgets/window-v2.ui diff --git a/src/utils/application.js b/src/utils/application.js index 5a9cfff..9bf077d 100644 --- a/src/utils/application.js +++ b/src/utils/application.js @@ -12,7 +12,7 @@ function get_resource_path() { } function get_template_path(path) { - return `resource://${APPLICATION_RES}/ui/${path}`; + return `resource://${APPLICATION_RES}/platform/gnome/widgets/${path}`; } function is_development_mode() { diff --git a/src/utils/project-manager.ts b/src/utils/project-manager.ts index accd583..e6399b7 100644 --- a/src/utils/project-manager.ts +++ b/src/utils/project-manager.ts @@ -19,10 +19,10 @@ import GObject from 'gi://GObject'; import GLib from 'gi://GLib'; -import { AppSignals } from '~/app.enums.js'; +import { AppSignals } from '~/platform/gnome/enums.js'; -import { TaskItem } from '~/ui-handler/task-item.js'; -import { TaskListStore } from '~/ui-handler/task-list-store.js'; +import { TaskItem } from '~/platform/gnome/views/task-item.js'; +import { TaskListStore } from '~/platform/gnome/views/task-list-store.js'; /** * Manages the dynamic discovery of task groups based on projects in the store. diff --git a/tsconfig.json b/tsconfig.json index 3075e86..494fecf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,13 @@ "allowJs": true, "pretty": true, "paths": { - "~/*": ["./src/*"] + "~/*": ["./src/*"], + "~gnome": ["./src/platform/gnome/"], + "~core": ["./src/core/"], + "~actions": ["./src/actions/"], + "~hooks": ["./src/hooks/"], + "~utils": ["./src/utils/"], + "~web": ["./src/web/"] }, "types": [ "@girs/gjs",