mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
Add sync backend infrastructure and improve grid snapping
- Add haexSyncBackends table with CRDT support for multi-backend sync - Implement useSyncBackendsStore for managing sync server configurations - Fix desktop icon grid snapping for all icon sizes (small to extra-large) - Add Supabase client dependency for future sync implementation - Generate database migration for sync_backends table
This commit is contained in:
@ -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
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
130
src/stores/sync/backends.ts
Normal file
130
src/stores/sync/backends.ts
Normal file
@ -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<SelectHaexSyncBackends[]>([])
|
||||
|
||||
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<InsertHaexSyncBackends>,
|
||||
) => {
|
||||
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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user