From a291619f6326b974b416d5555380ac95c47c514e Mon Sep 17 00:00:00 2001 From: haex Date: Thu, 16 Oct 2025 20:56:21 +0200 Subject: [PATCH] add desktop --- .claude-session.json | 9 + package.json | 2 +- .../migrations/0003_daily_polaris.sql | 9 + .../migrations/meta/0003_snapshot.json | 991 ++++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + src-tauri/database/schemas/haex.ts | 35 + src-tauri/database/tableNames.json | 12 + src-tauri/database/vault.db | Bin 139264 -> 143360 bytes src-tauri/src/database/core.rs | 89 +- src-tauri/src/database/mod.rs | 5 +- src/components/haex/desktop/icon.vue | 119 +++ src/components/haex/desktop/index.vue | 250 +++++ .../haex/extension/dialog/install.vue | 16 +- src/composables/extensionMessageHandler.ts | 9 +- src/layouts/app.vue | 5 +- src/layouts/default.vue | 2 +- src/pages/index.vue | 6 +- src/pages/vault.vue | 25 +- .../[vaultId]/extensions/[extensionId].vue | 2 +- .../vault/[vaultId]/extensions/index.vue | 12 +- src/pages/vault/[vaultId]/index.vue | 11 +- src/plugins/console-interceptor.ts | 14 +- src/stores/vault/desktop.ts | 170 +++ src/stores/vault/device.ts | 3 +- src/stores/vault/index.ts | 72 +- src/stores/vault/settings.ts | 2 +- todos.md | 2 + 27 files changed, 1755 insertions(+), 124 deletions(-) create mode 100644 .claude-session.json create mode 100644 src-tauri/database/migrations/0003_daily_polaris.sql create mode 100644 src-tauri/database/migrations/meta/0003_snapshot.json create mode 100644 src/components/haex/desktop/icon.vue create mode 100644 src/components/haex/desktop/index.vue create mode 100644 src/stores/vault/desktop.ts diff --git a/.claude-session.json b/.claude-session.json new file mode 100644 index 0000000..e319b68 --- /dev/null +++ b/.claude-session.json @@ -0,0 +1,9 @@ +{ + "lastUpdated": "2025-10-16T00:00:00.000Z", + "todos": [], + "context": { + "description": "Session context file for Claude Code. This file is automatically updated to persist state across sessions.", + "currentFocus": null, + "notes": [] + } +} diff --git a/package.json b/package.json index 7890255..1d4606e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "tauri-app", + "name": "haex-hub", "private": true, "version": "0.1.0", "type": "module", diff --git a/src-tauri/database/migrations/0003_daily_polaris.sql b/src-tauri/database/migrations/0003_daily_polaris.sql new file mode 100644 index 0000000..ea0b6c5 --- /dev/null +++ b/src-tauri/database/migrations/0003_daily_polaris.sql @@ -0,0 +1,9 @@ +CREATE TABLE `haex_desktop_items` ( + `id` text PRIMARY KEY NOT NULL, + `item_type` text NOT NULL, + `reference_id` text NOT NULL, + `position_x` integer DEFAULT 0 NOT NULL, + `position_y` integer DEFAULT 0 NOT NULL, + `haex_tombstone` integer, + `haex_timestamp` text +); diff --git a/src-tauri/database/migrations/meta/0003_snapshot.json b/src-tauri/database/migrations/meta/0003_snapshot.json new file mode 100644 index 0000000..ec9e583 --- /dev/null +++ b/src-tauri/database/migrations/meta/0003_snapshot.json @@ -0,0 +1,991 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2f40a42e-9b3f-42be-8951-8e94baadcd65", + "prevId": "5387568f-75b3-4a85-86c5-67f539c3fedf", + "tables": { + "haex_crdt_configs": { + "name": "haex_crdt_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_logs": { + "name": "haex_crdt_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "row_pks": { + "name": "row_pks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op_type": { + "name": "op_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_haex_timestamp": { + "name": "idx_haex_timestamp", + "columns": [ + "haex_timestamp" + ], + "isUnique": false + }, + "idx_table_row": { + "name": "idx_table_row", + "columns": [ + "table_name", + "row_pks" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epoch_hlc": { + "name": "epoch_hlc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_url": { + "name": "location_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_desktop_items": { + "name": "haex_desktop_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_type": { + "name": "item_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_x": { + "name": "position_x", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "position_y": { + "name": "position_y", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "constraints": { + "name": "constraints", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'denied'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extension_permissions_extension_id_resource_type_action_target_unique": { + "name": "haex_extension_permissions_extension_id_resource_type_action_target_unique", + "columns": [ + "extension_id", + "resource_type", + "action", + "target" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_extension_permissions_extension_id_haex_extensions_id_fk": { + "name": "haex_extension_permissions_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_extension_permissions", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extensions": { + "name": "haex_extensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry": { + "name": "entry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'index.html'" + }, + "homepage": { + "name": "homepage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extensions_public_key_name_unique": { + "name": "haex_extensions_public_key_name_unique", + "columns": [ + "public_key", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_notifications": { + "name": "haex_notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_settings": { + "name": "haex_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_group_items": { + "name": "haex_passwords_group_items", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "haex_passwords_group_items_item_id_group_id_pk": { + "columns": [ + "item_id", + "group_id" + ], + "name": "haex_passwords_group_items_item_id_group_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_groups": { + "name": "haex_passwords_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_groups", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_details": { + "name": "haex_passwords_item_details", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_history": { + "name": "haex_passwords_item_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_property": { + "name": "changed_property", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_history", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_key_values": { + "name": "haex_passwords_item_key_values", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_key_values", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/_journal.json b/src-tauri/database/migrations/meta/_journal.json index 4bd43e1..9e9686b 100644 --- a/src-tauri/database/migrations/meta/_journal.json +++ b/src-tauri/database/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1760272083150, "tag": "0002_amazing_iron_fist", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1760611690801, + "tag": "0003_daily_polaris", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src-tauri/database/schemas/haex.ts b/src-tauri/database/schemas/haex.ts index 4a7f77b..12d8fe9 100644 --- a/src-tauri/database/schemas/haex.ts +++ b/src-tauri/database/schemas/haex.ts @@ -5,9 +5,20 @@ import { text, unique, type AnySQLiteColumn, + type SQLiteColumnBuilderBase, } from 'drizzle-orm/sqlite-core' import tableNames from '../tableNames.json' +// Helper function to add common CRDT columns (haexTombstone and haexTimestamp) +export const withCrdtColumns = >( + columns: T, + columnNames: { haexTombstone: string; haexTimestamp: string }, +) => ({ + ...columns, + haexTombstone: integer(columnNames.haexTombstone, { mode: 'boolean' }), + haexTimestamp: text(columnNames.haexTimestamp), +}) + export const haexSettings = sqliteTable(tableNames.haex.settings.name, { id: text() .primaryKey() @@ -120,3 +131,27 @@ export const haexNotifications = sqliteTable( ) export type InsertHaexNotifications = typeof haexNotifications.$inferInsert export type SelectHaexNotifications = typeof haexNotifications.$inferSelect + +export const haexDesktopItems = sqliteTable( + tableNames.haex.desktop_items.name, + withCrdtColumns( + { + id: text(tableNames.haex.desktop_items.columns.id) + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), + itemType: text(tableNames.haex.desktop_items.columns.itemType, { + enum: ['extension', 'file', 'folder'], + }).notNull(), + referenceId: text(tableNames.haex.desktop_items.columns.referenceId).notNull(), // extensionId für extensions, filePath für files/folders + positionX: integer(tableNames.haex.desktop_items.columns.positionX) + .notNull() + .default(0), + positionY: integer(tableNames.haex.desktop_items.columns.positionY) + .notNull() + .default(0), + }, + tableNames.haex.desktop_items.columns, + ), +) +export type InsertHaexDesktopItems = typeof haexDesktopItems.$inferInsert +export type SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect diff --git a/src-tauri/database/tableNames.json b/src-tauri/database/tableNames.json index 6f077de..7e9f62a 100644 --- a/src-tauri/database/tableNames.json +++ b/src-tauri/database/tableNames.json @@ -63,6 +63,18 @@ "haexTimestamp": "haex_timestamp" } }, + "desktop_items": { + "name": "haex_desktop_items", + "columns": { + "id": "id", + "itemType": "item_type", + "referenceId": "reference_id", + "positionX": "position_x", + "positionY": "position_y", + "haexTombstone": "haex_tombstone", + "haexTimestamp": "haex_timestamp" + } + }, "passwords": { "groups": "haex_passwords_groups", "group_items": "haex_passwords_group_items", diff --git a/src-tauri/database/vault.db b/src-tauri/database/vault.db index 2982f873511ceb875e18f0f58909cb7d38177c3d..714902eff4d6c856fdaf660f423da967dd759a8a 100644 GIT binary patch delta 652 zcmZoTz|ru4V}i6GD+2?AG7!Un*hC#;M%Il9^Z5luc!7c}d}kQ=H}j|QOY)uBEGV#t z&)1uUm7$TBHPJlTAj!c-z`)ER)gn2~Jjv1`CE3{2IMpmQ*)-AI z)GRs0D9PB^)XdD>(%8_#IK{-+fN|!VMVW?^CFNi9u<*ZS;Qzz_o&OX6+s%RscllXa zm>C#ZHh%B2uBI44BKAjAeQ|MRlG|4A>7aT`C&oC&p&aY)%J`D zjGGu)xD^>Kw#!XoWaebyO=LJWxtj4%aFc~1ySSktV~cc2Vp2|OMq+A3d`fC@c1eCg zd}c{%ZZW2afOC+mV~DFlh@+E_t3m>Xg6R+DGHS4bG{;ZQWfA8oN=-{GO3h17jn7P( zT*xBqUXWj$S(2Hb7hjQ}keOGKnx0yu;Nt4$80r(EV4&dVAEMwF>f@us#hCzCS&2pE z^g9z7?bvMP*~A02r(c}GC_DKq7w7c;d5l8rN(>B)>wMKAqGwnorq7wnc#3%qnz{xi eXQj#WSrnB}JREO`;iF>Z?IH6S*EB405C8y}dB(Q@ delta 331 zcmZp8z|nAkV}i6G3j+g#5)cajF(VL*PSi1GWZ9T7pP!$X7szMkmt^4I%%8?Dxmi$w zn}4#T{A(^|{!a}2fB3)if7&c)@PvQ!Cw;yH69c#z`GMT`{1XFM7X>K5(X<9e7A{`) zMh1R%{`Guz6N`3D+>o+er;$;OX>uMj@8)=BJ4P1%A52d+FJ$3hWaIzAc*9qH`o$TH zvYXGa>T?-#03E@=%PYnp$WX<=JBK%sNs8wpw<6aWPBD(t>>?Z`tTJqSnS)sNGgR>^ z@rUq=O4a+4UDIX9OxZeX0w)XL~8#Fxy#6Kl@MkeQc~YAeqs z9;gkrZ1Pzy&guR07=;*>w%4~YGBL6Nt^J55*)V;=Bu2LB))N`^C%3T)Y!8{wxTXOB D#y49B diff --git a/src-tauri/src/database/core.rs b/src-tauri/src/database/core.rs index 30abd75..6c1c4f2 100644 --- a/src-tauri/src/database/core.rs +++ b/src-tauri/src/database/core.rs @@ -12,7 +12,6 @@ use serde_json::Value as JsonValue; use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement, TableFactor, TableObject}; use sqlparser::dialect::SQLiteDialect; use sqlparser::parser::Parser; -use std::collections::HashMap; /// Öffnet und initialisiert eine Datenbank mit Verschlüsselung /// @@ -121,7 +120,7 @@ pub fn execute( sql: String, params: Vec, connection: &DbConnection, -) -> Result { +) -> Result>, DatabaseError> { // Konvertiere Parameter let params_converted: Vec = params .iter() @@ -130,19 +129,56 @@ pub fn execute( let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect(); with_connection(connection, |conn| { - let affected_rows = conn.execute(&sql, ¶ms_sql[..]).map_err(|e| { - // "Lazy Parsing": Extrahiere den Tabellennamen nur, wenn ein Fehler auftritt, - // um den Overhead bei erfolgreichen Operationen zu vermeiden. - let table_name = extract_primary_table_name_from_sql(&sql).unwrap_or(None); + // Check if the SQL contains RETURNING clause + let has_returning = sql.to_uppercase().contains("RETURNING"); - DatabaseError::ExecutionError { - sql: sql.clone(), + if has_returning { + // Use prepare + query for RETURNING statements + let mut stmt = conn.prepare(&sql).map_err(|e| DatabaseError::PrepareError { reason: e.to_string(), - table: table_name, - } - })?; + })?; - Ok(affected_rows) + let num_columns = stmt.column_count(); + let mut rows = stmt + .query(¶ms_sql[..]) + .map_err(|e| DatabaseError::QueryError { + reason: e.to_string(), + })?; + + let mut result_vec: Vec> = Vec::new(); + + while let Some(row) = rows.next().map_err(|e| DatabaseError::RowProcessingError { + reason: format!("Row iteration error: {}", e), + })? { + let mut row_values: Vec = Vec::with_capacity(num_columns); + + for i in 0..num_columns { + let value_ref = row.get_ref(i).map_err(|e| { + DatabaseError::RowProcessingError { + reason: format!("Failed to get column {}: {}", i, e), + } + })?; + + let json_val = convert_value_ref_to_json(value_ref)?; + row_values.push(json_val); + } + result_vec.push(row_values); + } + + Ok(result_vec) + } else { + // For non-RETURNING statements, just execute and return empty array + conn.execute(&sql, ¶ms_sql[..]).map_err(|e| { + let table_name = extract_primary_table_name_from_sql(&sql).unwrap_or(None); + DatabaseError::ExecutionError { + sql: sql.clone(), + reason: e.to_string(), + table: table_name, + } + })?; + + Ok(vec![]) + } }) } @@ -150,7 +186,7 @@ pub fn select( sql: String, params: Vec, connection: &DbConnection, -) -> Result>, DatabaseError> { +) -> Result>, DatabaseError> { // Validiere SQL-Statement let statement = parse_single_statement(&sql)?; @@ -176,12 +212,7 @@ pub fn select( reason: e.to_string(), })?; - let column_names: Vec = stmt - .column_names() - .into_iter() - .map(|s| s.to_string()) - .collect(); - let num_columns = column_names.len(); + let num_columns = stmt.column_count(); let mut rows = stmt .query(¶ms_sql[..]) @@ -189,34 +220,24 @@ pub fn select( reason: e.to_string(), })?; - let mut result_vec: Vec> = Vec::new(); + let mut result_vec: Vec> = Vec::new(); while let Some(row) = rows.next().map_err(|e| DatabaseError::RowProcessingError { reason: format!("Row iteration error: {}", e), })? { - let mut row_map: HashMap = HashMap::with_capacity(num_columns); + let mut row_values: Vec = Vec::with_capacity(num_columns); for i in 0..num_columns { - let col_name = &column_names[i]; - - /* println!( - "--- Processing Column --- Index: {}, Name: '{}'", - i, col_name - ); */ - let value_ref = row .get_ref(i) .map_err(|e| DatabaseError::RowProcessingError { - reason: format!("Failed to get column {} ('{}'): {}", i, col_name, e), + reason: format!("Failed to get column {}: {}", i, e), })?; let json_val = convert_value_ref_to_json(value_ref)?; - - //println!("Column: {} = {}", column_names[i], json_val); - - row_map.insert(col_name.clone(), json_val); + row_values.push(json_val); } - result_vec.push(row_map); + result_vec.push(row_values); } Ok(result_vec) diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index 6db7b47..3b35c28 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -11,7 +11,6 @@ use crate::AppState; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use std::collections::HashMap; use std::path::Path; use std::sync::Mutex; use std::time::UNIX_EPOCH; @@ -30,7 +29,7 @@ pub fn sql_select( sql: String, params: Vec, state: State<'_, AppState>, -) -> Result>, DatabaseError> { +) -> Result>, DatabaseError> { core::select(sql, params, &state.db) } @@ -39,7 +38,7 @@ pub fn sql_execute( sql: String, params: Vec, state: State<'_, AppState>, -) -> Result { +) -> Result>, DatabaseError> { core::execute(sql, params, &state.db) } diff --git a/src/components/haex/desktop/icon.vue b/src/components/haex/desktop/icon.vue new file mode 100644 index 0000000..dc0ebb2 --- /dev/null +++ b/src/components/haex/desktop/icon.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/components/haex/desktop/index.vue b/src/components/haex/desktop/index.vue new file mode 100644 index 0000000..5822339 --- /dev/null +++ b/src/components/haex/desktop/index.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/components/haex/extension/dialog/install.vue b/src/components/haex/extension/dialog/install.vue index a68e792..edb96b8 100644 --- a/src/components/haex/extension/dialog/install.vue +++ b/src/components/haex/extension/dialog/install.vue @@ -67,6 +67,12 @@ + + +

@@ -140,6 +146,7 @@ const open = defineModel('open', { default: false }) const preview = defineModel('preview', { default: null, }) +const addToDesktop = ref(true) const databasePermissions = computed({ get: () => preview.value?.editable_permissions?.database || [], @@ -217,7 +224,10 @@ const permissionAccordionItems = computed(() => { return items }) -const emit = defineEmits(['deny', 'confirm']) +const emit = defineEmits<{ + deny: [] + confirm: [addToDesktop: boolean] +}>() const onDeny = () => { open.value = false @@ -226,7 +236,7 @@ const onDeny = () => { const onConfirm = () => { open.value = false - emit('confirm') + emit('confirm', addToDesktop.value) } @@ -235,6 +245,7 @@ de: title: Erweiterung installieren version: Version author: Autor + addToDesktop: Zum Desktop hinzufügen signature: valid: Signatur verifiziert invalid: Signatur ungültig @@ -249,6 +260,7 @@ en: title: Install Extension version: Version author: Author + addToDesktop: Add to Desktop signature: valid: Signature verified invalid: Invalid signature diff --git a/src/composables/extensionMessageHandler.ts b/src/composables/extensionMessageHandler.ts index 054946f..4cfcc89 100644 --- a/src/composables/extensionMessageHandler.ts +++ b/src/composables/extensionMessageHandler.ts @@ -422,7 +422,7 @@ async function handleContextMethodAsync(request: ExtensionRequest) { return { theme: contextGetters.getTheme(), locale: contextGetters.getLocale(), - platform: detectPlatform(), + platform: contextGetters.getPlatform(), } default: @@ -430,13 +430,6 @@ async function handleContextMethodAsync(request: ExtensionRequest) { } } -function detectPlatform(): 'desktop' | 'mobile' | 'tablet' { - const width = window.innerWidth - if (width < 768) return 'mobile' - if (width < 1024) return 'tablet' - return 'desktop' -} - // ========================================== // Storage Methods // ========================================== diff --git a/src/layouts/app.vue b/src/layouts/app.vue index 54c68fb..07d071b 100644 --- a/src/layouts/app.vue +++ b/src/layouts/app.vue @@ -1,6 +1,5 @@