desktopicons now with foreign key to extensions

This commit is contained in:
2025-10-26 00:19:15 +02:00
parent 86b65f117d
commit 5ee5ced8c0
10 changed files with 184 additions and 39 deletions

View File

@ -28,11 +28,14 @@ CREATE TABLE `haex_desktop_items` (
`id` text PRIMARY KEY NOT NULL, `id` text PRIMARY KEY NOT NULL,
`workspace_id` text NOT NULL, `workspace_id` text NOT NULL,
`item_type` text NOT NULL, `item_type` text NOT NULL,
`reference_id` text NOT NULL, `extension_id` text,
`system_window_id` text,
`position_x` integer DEFAULT 0 NOT NULL, `position_x` integer DEFAULT 0 NOT NULL,
`position_y` integer DEFAULT 0 NOT NULL, `position_y` integer DEFAULT 0 NOT NULL,
`haex_timestamp` text, `haex_timestamp` text,
FOREIGN KEY (`workspace_id`) REFERENCES `haex_workspaces`(`id`) ON UPDATE no action ON DELETE cascade FOREIGN KEY (`workspace_id`) REFERENCES `haex_workspaces`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`extension_id`) REFERENCES `haex_extensions`(`id`) ON UPDATE no action ON DELETE cascade,
CONSTRAINT "item_reference" CHECK(("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))
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE `haex_extension_permissions` ( CREATE TABLE `haex_extension_permissions` (

View File

@ -1,7 +1,7 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "21ca1268-1057-48c1-8647-29bd7cb67d49", "id": "bcdd9ad3-a87a-4a43-9eba-673f94b10287",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"haex_crdt_configs": { "haex_crdt_configs": {
@ -179,11 +179,18 @@
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"reference_id": { "extension_id": {
"name": "reference_id", "name": "extension_id",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": false,
"autoincrement": false
},
"system_window_id": {
"name": "system_window_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false "autoincrement": false
}, },
"position_x": { "position_x": {
@ -224,11 +231,29 @@
], ],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "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": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {}, "uniqueConstraints": {},
"checkConstraints": {} "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_extension_permissions": { "haex_extension_permissions": {
"name": "haex_extension_permissions", "name": "haex_extension_permissions",

View File

@ -5,8 +5,8 @@
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "6",
"when": 1761216357702, "when": 1761430560028,
"tag": "0000_bumpy_valkyrie", "tag": "0000_secret_ender_wiggin",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@ -1,5 +1,6 @@
import { sql } from 'drizzle-orm' import { sql } from 'drizzle-orm'
import { import {
check,
integer, integer,
sqliteTable, sqliteTable,
text, text,
@ -158,9 +159,13 @@ export const haexDesktopItems = sqliteTable(
itemType: text(tableNames.haex.desktop_items.columns.itemType, { itemType: text(tableNames.haex.desktop_items.columns.itemType, {
enum: ['system', 'extension', 'file', 'folder'], enum: ['system', 'extension', 'file', 'folder'],
}).notNull(), }).notNull(),
referenceId: text( // Für Extensions (wenn itemType = 'extension')
tableNames.haex.desktop_items.columns.referenceId, extensionId: text(tableNames.haex.desktop_items.columns.extensionId)
).notNull(), // systemId für system windows, extensionId für extensions, filePath für files/folders .references((): AnySQLiteColumn => haexExtensions.id, {
onDelete: 'cascade',
}),
// Für System Windows (wenn itemType = 'system')
systemWindowId: text(tableNames.haex.desktop_items.columns.systemWindowId),
positionX: integer(tableNames.haex.desktop_items.columns.positionX) positionX: integer(tableNames.haex.desktop_items.columns.positionX)
.notNull() .notNull()
.default(0), .default(0),
@ -170,6 +175,12 @@ export const haexDesktopItems = sqliteTable(
}, },
tableNames.haex.desktop_items.columns, tableNames.haex.desktop_items.columns,
), ),
(table) => [
check(
'item_reference',
sql`(${table.itemType} = 'extension' AND ${table.extensionId} IS NOT NULL AND ${table.systemWindowId} IS NULL) OR (${table.itemType} = 'system' AND ${table.systemWindowId} IS NOT NULL AND ${table.extensionId} IS NULL) OR (${table.itemType} = 'file' AND ${table.systemWindowId} IS NOT NULL AND ${table.extensionId} IS NULL) OR (${table.itemType} = 'folder' AND ${table.systemWindowId} IS NOT NULL AND ${table.extensionId} IS NULL)`,
),
],
) )
export type InsertHaexDesktopItems = typeof haexDesktopItems.$inferInsert export type InsertHaexDesktopItems = typeof haexDesktopItems.$inferInsert
export type SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect export type SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect

View File

@ -80,7 +80,8 @@
"id": "id", "id": "id",
"workspaceId": "workspace_id", "workspaceId": "workspace_id",
"itemType": "item_type", "itemType": "item_type",
"referenceId": "reference_id", "extensionId": "extension_id",
"systemWindowId": "system_window_id",
"positionX": "position_x", "positionX": "position_x",
"positionY": "position_y", "positionY": "position_y",

Binary file not shown.

View File

@ -41,18 +41,17 @@
/> />
<!-- Snap Dropzones (only visible when window drag near edge) --> <!-- Snap Dropzones (only visible when window drag near edge) -->
<Transition name="fade">
<div <div
v-if="showLeftSnapZone" class="absolute left-0 top-0 bottom-0 border-blue-500 pointer-events-none backdrop-blur-sm z-40 transition-all duration-500"
class="absolute left-0 top-0 bottom-0 w-1/2 bg-blue-500/20 border-2 border-blue-500 pointer-events-none backdrop-blur-sm z-40" :class="showLeftSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'"
/> />
</Transition>
<Transition name="fade"> <div
<div class="absolute right-0 top-0 bottom-0 border-blue-500 pointer-events-none backdrop-blur-sm z-40 transition-all duration-500 ease-in-out"
v-if="showRightSnapZone" :class="showRightSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'"
class="absolute right-0 top-0 bottom-0 w-1/2 bg-blue-500/20 border-2 border-blue-500 pointer-events-none backdrop-blur-sm z-40" />
/> <!-- </Transition> -->
</Transition>
<!-- Area Selection Box --> <!-- Area Selection Box -->
<div <div
@ -494,6 +493,34 @@ const handleWindowDragStart = (windowId: string) => {
const handleWindowDragEnd = async () => { const handleWindowDragEnd = async () => {
console.log('[Desktop] handleWindowDragEnd') console.log('[Desktop] handleWindowDragEnd')
// Check if window should snap to left or right
const draggingWindowId = windowManager.draggingWindowId
if (draggingWindowId) {
if (showLeftSnapZone.value) {
// Snap to left half
windowManager.updateWindowPosition(draggingWindowId, 0, 0)
windowManager.updateWindowSize(
draggingWindowId,
viewportWidth.value / 2,
viewportHeight.value,
)
} else if (showRightSnapZone.value) {
// Snap to right half
windowManager.updateWindowPosition(
draggingWindowId,
viewportWidth.value / 2,
0,
)
windowManager.updateWindowSize(
draggingWindowId,
viewportWidth.value / 2,
viewportHeight.value,
)
}
}
isWindowDragging.value = false isWindowDragging.value = false
windowManager.draggingWindowId = null // Clear from store windowManager.draggingWindowId = null // Clear from store
allowSwipe.value = true // Re-enable Swiper after drag allowSwipe.value = true // Re-enable Swiper after drag

View File

@ -55,9 +55,23 @@
</ul> </ul>
</template> </template>
</UPopover> </UPopover>
<!-- Uninstall Confirmation Dialog -->
<UiDialogConfirm
v-model:open="showUninstallDialog"
:title="t('uninstall.confirm.title')"
:description="t('uninstall.confirm.description', { name: extensionToUninstall?.name || '' })"
:confirm-label="t('uninstall.confirm.button')"
confirm-icon="i-heroicons-trash"
@confirm="confirmUninstall"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({
inheritAttrs: false,
})
const extensionStore = useExtensionsStore() const extensionStore = useExtensionsStore()
const windowManagerStore = useWindowManagerStore() const windowManagerStore = useWindowManagerStore()
@ -65,6 +79,10 @@ const { t } = useI18n()
const open = ref(false) const open = ref(false)
// Uninstall dialog state
const showUninstallDialog = ref(false)
const extensionToUninstall = ref<LauncherItem | null>(null)
// Unified launcher item type // Unified launcher item type
interface LauncherItem { interface LauncherItem {
id: string id: string
@ -127,17 +145,44 @@ const openItem = async (item: LauncherItem) => {
} }
} }
// Uninstall extension // Uninstall extension - shows confirmation dialog first
const uninstallExtension = async (item: LauncherItem) => { const uninstallExtension = async (item: LauncherItem) => {
extensionToUninstall.value = item
showUninstallDialog.value = true
}
// Confirm uninstall - actually removes the extension
const confirmUninstall = async () => {
if (!extensionToUninstall.value) return
try { try {
const extension = extensionStore.availableExtensions.find(ext => ext.id === item.id) const extension = extensionStore.availableExtensions.find(
(ext) => ext.id === extensionToUninstall.value!.id,
)
if (!extension) return if (!extension) return
// Close all windows of this extension first
const extensionWindows = windowManagerStore.windows.filter(
(win) => win.type === 'extension' && win.sourceId === extension.id,
)
for (const win of extensionWindows) {
windowManagerStore.closeWindow(win.id)
}
// Uninstall the extension
await extensionStore.removeExtensionAsync( await extensionStore.removeExtensionAsync(
extension.publicKey, extension.publicKey,
extension.name, extension.name,
extension.version extension.version,
) )
// Refresh available extensions list
await extensionStore.loadExtensionsAsync()
// Close dialog and reset state
showUninstallDialog.value = false
extensionToUninstall.value = null
} catch (error) { } catch (error) {
console.error('Failed to uninstall extension:', error) console.error('Failed to uninstall extension:', error)
} }
@ -149,8 +194,8 @@ const getContextMenuItems = (item: LauncherItem) => {
{ {
label: t('contextMenu.open'), label: t('contextMenu.open'),
icon: 'i-heroicons-arrow-top-right-on-square', icon: 'i-heroicons-arrow-top-right-on-square',
click: () => openItem(item), onSelect: () => openItem(item),
} },
] ]
// Add uninstall option for extensions // Add uninstall option for extensions
@ -158,7 +203,7 @@ const getContextMenuItems = (item: LauncherItem) => {
items.push({ items.push({
label: t('contextMenu.uninstall'), label: t('contextMenu.uninstall'),
icon: 'i-heroicons-trash', icon: 'i-heroicons-trash',
click: () => uninstallExtension(item), onSelect: () => uninstallExtension(item),
}) })
} }
@ -171,7 +216,10 @@ const handleDragStart = (event: DragEvent, item: LauncherItem) => {
// Store the launcher item data // Store the launcher item data
event.dataTransfer.effectAllowed = 'copy' event.dataTransfer.effectAllowed = 'copy'
event.dataTransfer.setData('application/haex-launcher-item', JSON.stringify(item)) event.dataTransfer.setData(
'application/haex-launcher-item',
JSON.stringify(item),
)
// Set drag image (optional - uses default if not set) // Set drag image (optional - uses default if not set)
const dragImage = event.target as HTMLElement const dragImage = event.target as HTMLElement
@ -192,6 +240,11 @@ de:
contextMenu: contextMenu:
open: Öffnen open: Öffnen
uninstall: Deinstallieren uninstall: Deinstallieren
uninstall:
confirm:
title: Erweiterung deinstallieren
description: Möchtest du wirklich "{name}" deinstallieren? Diese Aktion kann nicht rückgängig gemacht werden.
button: Deinstallieren
en: en:
disabled: Disabled disabled: Disabled
@ -199,4 +252,9 @@ en:
contextMenu: contextMenu:
open: Open open: Open
uninstall: Uninstall uninstall: Uninstall
uninstall:
confirm:
title: Uninstall Extension
description: Do you really want to uninstall "{name}"? This action cannot be undone.
button: Uninstall
</i18n> </i18n>

View File

@ -8,7 +8,7 @@
> >
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<!-- Icon --> <!-- Icon -->
<div class="flex-shrink-0"> <div class="shrink-0">
<div <div
v-if="extension.icon" v-if="extension.icon"
class="w-16 h-16 rounded-lg bg-primary/10 flex items-center justify-center" class="w-16 h-16 rounded-lg bg-primary/10 flex items-center justify-center"

View File

@ -12,6 +12,7 @@ export type DesktopItemType = 'extension' | 'file' | 'folder' | 'system'
export interface IDesktopItem extends SelectHaexDesktopItems { export interface IDesktopItem extends SelectHaexDesktopItems {
label?: string label?: string
icon?: string icon?: string
referenceId: string // Computed: extensionId or systemWindowId
} }
export const useDesktopStore = defineStore('desktopStore', () => { export const useDesktopStore = defineStore('desktopStore', () => {
@ -45,7 +46,10 @@ export const useDesktopStore = defineStore('desktopStore', () => {
.from(haexDesktopItems) .from(haexDesktopItems)
.where(eq(haexDesktopItems.workspaceId, currentWorkspace.value.id)) .where(eq(haexDesktopItems.workspaceId, currentWorkspace.value.id))
desktopItems.value = items desktopItems.value = items.map(item => ({
...item,
referenceId: item.itemType === 'extension' ? item.extensionId! : item.systemWindowId!,
}))
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Desktop-Items:', error) console.error('Fehler beim Laden der Desktop-Items:', error)
throw error throw error
@ -72,7 +76,8 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const newItem: InsertHaexDesktopItems = { const newItem: InsertHaexDesktopItems = {
workspaceId: targetWorkspaceId, workspaceId: targetWorkspaceId,
itemType: itemType, itemType: itemType,
referenceId: referenceId, extensionId: itemType === 'extension' ? referenceId : null,
systemWindowId: itemType === 'system' || itemType === 'file' || itemType === 'folder' ? referenceId : null,
positionX: positionX, positionX: positionX,
positionY: positionY, positionY: positionY,
} }
@ -83,8 +88,12 @@ export const useDesktopStore = defineStore('desktopStore', () => {
.returning() .returning()
if (result.length > 0 && result[0]) { if (result.length > 0 && result[0]) {
desktopItems.value.push(result[0]) const itemWithRef = {
return result[0] ...result[0],
referenceId: itemType === 'extension' ? result[0].extensionId! : result[0].systemWindowId!,
}
desktopItems.value.push(itemWithRef)
return itemWithRef
} }
} catch (error) { } catch (error) {
console.error('Fehler beim Hinzufügen des Desktop-Items:', { console.error('Fehler beim Hinzufügen des Desktop-Items:', {
@ -126,7 +135,11 @@ export const useDesktopStore = defineStore('desktopStore', () => {
if (result.length > 0 && result[0]) { if (result.length > 0 && result[0]) {
const index = desktopItems.value.findIndex((item) => item.id === id) const index = desktopItems.value.findIndex((item) => item.id === id)
if (index !== -1) { if (index !== -1) {
desktopItems.value[index] = result[0] const item = result[0]
desktopItems.value[index] = {
...item,
referenceId: item.itemType === 'extension' ? item.extensionId! : item.systemWindowId!,
}
} }
} }
} catch (error) { } catch (error) {
@ -159,7 +172,14 @@ export const useDesktopStore = defineStore('desktopStore', () => {
referenceId: string, referenceId: string,
) => { ) => {
return desktopItems.value.find( return desktopItems.value.find(
(item) => item.itemType === itemType && item.referenceId === referenceId, (item) => {
if (item.itemType !== itemType) return false
if (itemType === 'extension') {
return item.extensionId === referenceId
} else {
return item.systemWindowId === referenceId
}
},
) )
} }