diff --git a/package.json b/package.json index 56f327d..b3033eb 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nuxt/ui": "4.1.0", "@nuxtjs/i18n": "10.0.6", "@pinia/nuxt": "^0.11.2", + "@supabase/supabase-js": "^2.79.0", "@tailwindcss/vite": "^4.1.16", "@tauri-apps/api": "^2.9.0", "@tauri-apps/plugin-dialog": "^2.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e0c895..8a1190b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@pinia/nuxt': specifier: ^0.11.2 version: 0.11.2(magicast@0.5.1)(pinia@3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))) + '@supabase/supabase-js': + specifier: ^2.79.0 + version: 2.79.0 '@tailwindcss/vite': specifier: ^4.1.16 version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) @@ -1894,6 +1897,30 @@ packages: peerDependencies: eslint: '>=9.0.0' + '@supabase/auth-js@2.79.0': + resolution: {integrity: sha512-p2GKvdbF9d/6C+dtS6iNcSicPr6eUfkvovD60HWlWsD+oOjC483DzFWrzGjNpBwnswhfMRP8Qn3rYA0VWaOfjw==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.79.0': + resolution: {integrity: sha512-WaiU6b+Z+ZfJOjFhpMKdajt42weiFUrA6TVW5oGd6WfPGajFiKZJJIAvuK0g7KDKaYowtQrOo5+Ais+PcuZ1qA==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.79.0': + resolution: {integrity: sha512-2i8EFm3/49ecjt6dk/TGVROBbtOmhryiC4NL3u0FBIrm2hqj+FvbELv1jjM6r+a6abnh+uzIV/bFsWHAa/k3/w==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.79.0': + resolution: {integrity: sha512-foaZujNBycAqLizUcuLyyFyDitfPnEMVO4CiKXNwaMCDVMoVX4QR6n4gpJLUC5BGzc20Mte6vSJLbk4MN90Prw==} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.79.0': + resolution: {integrity: sha512-PLSeKX1/BZhGWCT972w4TvVOCcw/xh4TsowtUBiZvPx4OdHT7dB1q0DXKwVUfKbWk5UUC+6XAq4ZU/ZCtdgn6w==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.79.0': + resolution: {integrity: sha512-x9ndEaBSwoRnFOOZGhh2CeV69Uz4B/EOSGCbKysDhTiYakiCAdDXaNuLPluviKU/Aot+F7BglXZDZ0YJ3GpGrw==} + engines: {node: '>=20.0.0'} + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -2116,6 +2143,9 @@ packages: resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -7556,6 +7586,43 @@ snapshots: estraverse: 5.3.0 picomatch: 4.0.3 + '@supabase/auth-js@2.79.0': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.79.0': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.79.0': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.79.0': + dependencies: + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.79.0': + dependencies: + tslib: 2.8.1 + + '@supabase/supabase-js@2.79.0': + dependencies: + '@supabase/auth-js': 2.79.0 + '@supabase/functions-js': 2.79.0 + '@supabase/postgrest-js': 2.79.0 + '@supabase/realtime-js': 2.79.0 + '@supabase/storage-js': 2.79.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -7740,6 +7807,8 @@ snapshots: dependencies: parse-path: 7.1.0 + '@types/phoenix@1.6.6': {} + '@types/resolve@1.20.2': {} '@types/web-bluetooth@0.0.20': {} diff --git a/src-tauri/database/migrations/0003_luxuriant_deathstrike.sql b/src-tauri/database/migrations/0003_luxuriant_deathstrike.sql new file mode 100644 index 0000000..0868df0 --- /dev/null +++ b/src-tauri/database/migrations/0003_luxuriant_deathstrike.sql @@ -0,0 +1,10 @@ +CREATE TABLE `haex_sync_backends` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `server_url` text NOT NULL, + `enabled` integer DEFAULT true NOT NULL, + `priority` integer DEFAULT 0 NOT NULL, + `created_at` text DEFAULT (CURRENT_TIMESTAMP), + `updated_at` 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..c272a8a --- /dev/null +++ b/src-tauri/database/migrations/meta/0003_snapshot.json @@ -0,0 +1,843 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "bf82259e-9264-44e7-a60f-8cc14a1f22e2", + "prevId": "3aedf10c-2266-40f4-8549-0ff8b0588853", + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_type": { + "name": "item_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system_window_id": { + "name": "system_window_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "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_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_desktop_items_workspace_id_haex_workspaces_id_fk": { + "name": "haex_desktop_items_workspace_id_haex_workspaces_id_fk", + "tableFrom": "haex_desktop_items", + "tableTo": "haex_workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "haex_desktop_items_extension_id_haex_extensions_id_fk": { + "name": "haex_desktop_items_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_desktop_items", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "item_reference": { + "name": "item_reference", + "value": "(\"haex_desktop_items\".\"item_type\" = 'extension' AND \"haex_desktop_items\".\"extension_id\" IS NOT NULL AND \"haex_desktop_items\".\"system_window_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'system' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'file' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL) OR (\"haex_desktop_items\".\"item_type\" = 'folder' AND \"haex_desktop_items\".\"system_window_id\" IS NOT NULL AND \"haex_desktop_items\".\"extension_id\" IS NULL)" + } + } + }, + "haex_devices": { + "name": "haex_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "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_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_devices_device_id_unique": { + "name": "haex_devices_device_id_unique", + "columns": [ + "device_id" + ], + "isUnique": true + } + }, + "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": true, + "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_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": "cascade", + "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": false, + "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 + }, + "single_instance": { + "name": "single_instance", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 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_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 + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "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_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_settings_device_id_key_type_unique": { + "name": "haex_settings_device_id_key_type_unique", + "columns": [ + "device_id", + "key", + "type" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_settings_device_id_haex_devices_id_fk": { + "name": "haex_settings_device_id_haex_devices_id_fk", + "tableFrom": "haex_settings", + "tableTo": "haex_devices", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_sync_backends": { + "name": "haex_sync_backends", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "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_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_workspaces": { + "name": "haex_workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "background": { + "name": "background", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_workspaces_position_unique": { + "name": "haex_workspaces_position_unique", + "columns": [ + "position" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "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 c0a785b..90fb73e 100644 --- a/src-tauri/database/migrations/meta/_journal.json +++ b/src-tauri/database/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1762263814375, "tag": "0002_loose_quasimodo", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1762300795436, + "tag": "0003_luxuriant_deathstrike", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/database/schemas/haex.ts b/src/database/schemas/haex.ts index 07c1711..fe2defc 100644 --- a/src/database/schemas/haex.ts +++ b/src/database/schemas/haex.ts @@ -205,3 +205,30 @@ export const haexDesktopItems = sqliteTable( ) export type InsertHaexDesktopItems = typeof haexDesktopItems.$inferInsert export type SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect + +export const haexSyncBackends = sqliteTable( + tableNames.haex.sync_backends.name, + withCrdtColumns({ + id: text(tableNames.haex.sync_backends.columns.id) + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + name: text(tableNames.haex.sync_backends.columns.name).notNull(), + serverUrl: text(tableNames.haex.sync_backends.columns.serverUrl).notNull(), + enabled: integer(tableNames.haex.sync_backends.columns.enabled, { + mode: 'boolean', + }) + .default(true) + .notNull(), + priority: integer(tableNames.haex.sync_backends.columns.priority) + .default(0) + .notNull(), + createdAt: text(tableNames.haex.sync_backends.columns.createdAt).default( + sql`(CURRENT_TIMESTAMP)`, + ), + updatedAt: integer(tableNames.haex.sync_backends.columns.updatedAt, { + mode: 'timestamp', + }).$onUpdate(() => new Date()), + }), +) +export type InsertHaexSyncBackends = typeof haexSyncBackends.$inferInsert +export type SelectHaexSyncBackends = typeof haexSyncBackends.$inferSelect diff --git a/src/database/tableNames.json b/src/database/tableNames.json index 813bbb6..8aa5ebe 100644 --- a/src/database/tableNames.json +++ b/src/database/tableNames.json @@ -102,6 +102,20 @@ "haexTimestamp": "haex_timestamp" } }, + "sync_backends": { + "name": "haex_sync_backends", + "columns": { + "id": "id", + "name": "name", + "serverUrl": "server_url", + "enabled": "enabled", + "priority": "priority", + "createdAt": "created_at", + "updatedAt": "updated_at", + + "haexTimestamp": "haex_timestamp" + } + }, "crdt": { "logs": { diff --git a/src/stores/desktop/index.ts b/src/stores/desktop/index.ts index 6697252..7693e0e 100644 --- a/src/stores/desktop/index.ts +++ b/src/stores/desktop/index.ts @@ -91,31 +91,34 @@ export const useDesktopStore = defineStore('desktopStore', () => { iconHeight?: number, ) => { const cellSize = gridCellSize.value + const halfCell = cellSize / 2 - // Adjust y for grid offset - const adjustedY = Math.max(0, y + iconPadding) + // Use provided dimensions or fall back to the effective icon size (not cell size!) + const actualIconWidth = iconWidth || effectiveIconSize.value + const actualIconHeight = iconHeight || effectiveIconSize.value // Calculate which grid cell the position falls into - const col = Math.floor(x / cellSize) - const row = Math.floor(adjustedY / cellSize) + // Add half the icon size to x/y to get the center point for snapping + const centerX = x + actualIconWidth / 2 + const centerY = y + actualIconHeight / 2 - // Use provided dimensions or fall back to cell size - const actualIconWidth = iconWidth || cellSize - const actualIconHeight = iconHeight || cellSize + // Find nearest grid cell center + // Grid cells are centered at: halfCell, halfCell + cellSize, halfCell + 2*cellSize, ... + // Which is: halfCell + (n * cellSize) for n = 0, 1, 2, ... + const col = Math.round((centerX - halfCell) / cellSize) + const row = Math.round((centerY - halfCell) / cellSize) - // Center the icon in the cell(s) it occupies - const cellsWide = Math.max(1, Math.ceil(actualIconWidth / cellSize)) - const cellsHigh = Math.max(1, Math.ceil(actualIconHeight / cellSize)) + // Calculate the center of the target grid cell + const gridCenterX = halfCell + col * cellSize + const gridCenterY = halfCell + row * cellSize - const totalWidth = cellsWide * cellSize - const totalHeight = cellsHigh * cellSize - - const paddingX = (totalWidth - actualIconWidth) / 2 - const paddingY = (totalHeight - actualIconHeight) / 2 + // Calculate the top-left position that centers the icon in the cell + const snappedX = gridCenterX - actualIconWidth / 2 + const snappedY = gridCenterY - actualIconHeight / 2 return { - x: col * cellSize + paddingX - iconPadding, - y: row * cellSize + paddingY - iconPadding, + x: snappedX, + y: snappedY, } } diff --git a/src/stores/sync/backends.ts b/src/stores/sync/backends.ts new file mode 100644 index 0000000..b87e3f6 --- /dev/null +++ b/src/stores/sync/backends.ts @@ -0,0 +1,130 @@ +import { eq } from 'drizzle-orm' +import { + haexSyncBackends, + type InsertHaexSyncBackends, + type SelectHaexSyncBackends, +} from '~/database/schemas' + +export const useSyncBackendsStore = defineStore('syncBackendsStore', () => { + const { currentVault } = storeToRefs(useVaultStore()) + + const backends = ref([]) + + const enabledBackends = computed(() => + backends.value.filter((b) => b.enabled), + ) + + const sortedBackends = computed(() => + [...backends.value].sort((a, b) => (b.priority || 0) - (a.priority || 0)), + ) + + // Load all sync backends from database + const loadBackendsAsync = async () => { + if (!currentVault.value?.drizzle) { + console.error('No vault opened') + return + } + + try { + const result = await currentVault.value.drizzle + .select() + .from(haexSyncBackends) + + backends.value = result + } catch (error) { + console.error('Failed to load sync backends:', error) + throw error + } + } + + // Add a new sync backend + const addBackendAsync = async (backend: InsertHaexSyncBackends) => { + if (!currentVault.value?.drizzle) { + throw new Error('No vault opened') + } + + try { + const result = await currentVault.value.drizzle + .insert(haexSyncBackends) + .values(backend) + .returning() + + if (result.length > 0 && result[0]) { + backends.value.push(result[0]) + return result[0] + } + } catch (error) { + console.error('Failed to add sync backend:', error) + throw error + } + } + + // Update an existing sync backend + const updateBackendAsync = async ( + id: string, + updates: Partial, + ) => { + if (!currentVault.value?.drizzle) { + throw new Error('No vault opened') + } + + try { + const result = await currentVault.value.drizzle + .update(haexSyncBackends) + .set(updates) + .where(eq(haexSyncBackends.id, id)) + .returning() + + if (result.length > 0 && result[0]) { + const index = backends.value.findIndex((b) => b.id === id) + if (index !== -1) { + backends.value[index] = result[0] + } + return result[0] + } + } catch (error) { + console.error('Failed to update sync backend:', error) + throw error + } + } + + // Delete a sync backend + const deleteBackendAsync = async (id: string) => { + if (!currentVault.value?.drizzle) { + throw new Error('No vault opened') + } + + try { + await currentVault.value.drizzle + .delete(haexSyncBackends) + .where(eq(haexSyncBackends.id, id)) + + backends.value = backends.value.filter((b) => b.id !== id) + } catch (error) { + console.error('Failed to delete sync backend:', error) + throw error + } + } + + // Enable/disable a backend + const toggleBackendAsync = async (id: string, enabled: boolean) => { + return updateBackendAsync(id, { enabled }) + } + + // Update backend priority (for sync order) + const updatePriorityAsync = async (id: string, priority: number) => { + return updateBackendAsync(id, { priority }) + } + + return { + backends, + enabledBackends, + sortedBackends, + loadBackendsAsync, + addBackendAsync, + updateBackendAsync, + deleteBackendAsync, + toggleBackendAsync, + updatePriorityAsync, + } +})