mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
desktopicons now with foreign key to extensions
This commit is contained in:
@ -28,11 +28,14 @@ CREATE TABLE `haex_desktop_items` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`workspace_id` 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_y` integer DEFAULT 0 NOT NULL,
|
||||
`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
|
||||
CREATE TABLE `haex_extension_permissions` (
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "21ca1268-1057-48c1-8647-29bd7cb67d49",
|
||||
"id": "bcdd9ad3-a87a-4a43-9eba-673f94b10287",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"haex_crdt_configs": {
|
||||
@ -179,11 +179,18 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reference_id": {
|
||||
"name": "reference_id",
|
||||
"extension_id": {
|
||||
"name": "extension_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"system_window_id": {
|
||||
"name": "system_window_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"position_x": {
|
||||
@ -224,11 +231,29 @@
|
||||
],
|
||||
"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": {}
|
||||
"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": {
|
||||
"name": "haex_extension_permissions",
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1761216357702,
|
||||
"tag": "0000_bumpy_valkyrie",
|
||||
"when": 1761430560028,
|
||||
"tag": "0000_secret_ender_wiggin",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
import {
|
||||
check,
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
@ -158,9 +159,13 @@ export const haexDesktopItems = sqliteTable(
|
||||
itemType: text(tableNames.haex.desktop_items.columns.itemType, {
|
||||
enum: ['system', 'extension', 'file', 'folder'],
|
||||
}).notNull(),
|
||||
referenceId: text(
|
||||
tableNames.haex.desktop_items.columns.referenceId,
|
||||
).notNull(), // systemId für system windows, extensionId für extensions, filePath für files/folders
|
||||
// Für Extensions (wenn itemType = 'extension')
|
||||
extensionId: text(tableNames.haex.desktop_items.columns.extensionId)
|
||||
.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)
|
||||
.notNull()
|
||||
.default(0),
|
||||
@ -170,6 +175,12 @@ export const haexDesktopItems = sqliteTable(
|
||||
},
|
||||
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 SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect
|
||||
|
||||
@ -80,7 +80,8 @@
|
||||
"id": "id",
|
||||
"workspaceId": "workspace_id",
|
||||
"itemType": "item_type",
|
||||
"referenceId": "reference_id",
|
||||
"extensionId": "extension_id",
|
||||
"systemWindowId": "system_window_id",
|
||||
"positionX": "position_x",
|
||||
"positionY": "position_y",
|
||||
|
||||
|
||||
Binary file not shown.
@ -41,18 +41,17 @@
|
||||
/>
|
||||
|
||||
<!-- Snap Dropzones (only visible when window drag near edge) -->
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showLeftSnapZone"
|
||||
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"
|
||||
/>
|
||||
</Transition>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showRightSnapZone"
|
||||
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>
|
||||
|
||||
<div
|
||||
class="absolute left-0 top-0 bottom-0 border-blue-500 pointer-events-none backdrop-blur-sm z-40 transition-all duration-500"
|
||||
:class="showLeftSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'"
|
||||
/>
|
||||
|
||||
<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"
|
||||
:class="showRightSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'"
|
||||
/>
|
||||
<!-- </Transition> -->
|
||||
|
||||
<!-- Area Selection Box -->
|
||||
<div
|
||||
@ -494,6 +493,34 @@ const handleWindowDragStart = (windowId: string) => {
|
||||
|
||||
const handleWindowDragEnd = async () => {
|
||||
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
|
||||
windowManager.draggingWindowId = null // Clear from store
|
||||
allowSwipe.value = true // Re-enable Swiper after drag
|
||||
|
||||
@ -55,9 +55,23 @@
|
||||
</ul>
|
||||
</template>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const extensionStore = useExtensionsStore()
|
||||
const windowManagerStore = useWindowManagerStore()
|
||||
|
||||
@ -65,6 +79,10 @@ const { t } = useI18n()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
// Uninstall dialog state
|
||||
const showUninstallDialog = ref(false)
|
||||
const extensionToUninstall = ref<LauncherItem | null>(null)
|
||||
|
||||
// Unified launcher item type
|
||||
interface LauncherItem {
|
||||
id: string
|
||||
@ -127,17 +145,44 @@ const openItem = async (item: LauncherItem) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall extension
|
||||
// Uninstall extension - shows confirmation dialog first
|
||||
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 {
|
||||
const extension = extensionStore.availableExtensions.find(ext => ext.id === item.id)
|
||||
const extension = extensionStore.availableExtensions.find(
|
||||
(ext) => ext.id === extensionToUninstall.value!.id,
|
||||
)
|
||||
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(
|
||||
extension.publicKey,
|
||||
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) {
|
||||
console.error('Failed to uninstall extension:', error)
|
||||
}
|
||||
@ -149,8 +194,8 @@ const getContextMenuItems = (item: LauncherItem) => {
|
||||
{
|
||||
label: t('contextMenu.open'),
|
||||
icon: 'i-heroicons-arrow-top-right-on-square',
|
||||
click: () => openItem(item),
|
||||
}
|
||||
onSelect: () => openItem(item),
|
||||
},
|
||||
]
|
||||
|
||||
// Add uninstall option for extensions
|
||||
@ -158,7 +203,7 @@ const getContextMenuItems = (item: LauncherItem) => {
|
||||
items.push({
|
||||
label: t('contextMenu.uninstall'),
|
||||
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
|
||||
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)
|
||||
const dragImage = event.target as HTMLElement
|
||||
@ -192,6 +240,11 @@ de:
|
||||
contextMenu:
|
||||
open: Öffnen
|
||||
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:
|
||||
disabled: Disabled
|
||||
@ -199,4 +252,9 @@ en:
|
||||
contextMenu:
|
||||
open: Open
|
||||
uninstall: Uninstall
|
||||
uninstall:
|
||||
confirm:
|
||||
title: Uninstall Extension
|
||||
description: Do you really want to uninstall "{name}"? This action cannot be undone.
|
||||
button: Uninstall
|
||||
</i18n>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<div
|
||||
v-if="extension.icon"
|
||||
class="w-16 h-16 rounded-lg bg-primary/10 flex items-center justify-center"
|
||||
|
||||
@ -12,6 +12,7 @@ export type DesktopItemType = 'extension' | 'file' | 'folder' | 'system'
|
||||
export interface IDesktopItem extends SelectHaexDesktopItems {
|
||||
label?: string
|
||||
icon?: string
|
||||
referenceId: string // Computed: extensionId or systemWindowId
|
||||
}
|
||||
|
||||
export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
@ -45,7 +46,10 @@ export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
.from(haexDesktopItems)
|
||||
.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) {
|
||||
console.error('Fehler beim Laden der Desktop-Items:', error)
|
||||
throw error
|
||||
@ -72,7 +76,8 @@ export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
const newItem: InsertHaexDesktopItems = {
|
||||
workspaceId: targetWorkspaceId,
|
||||
itemType: itemType,
|
||||
referenceId: referenceId,
|
||||
extensionId: itemType === 'extension' ? referenceId : null,
|
||||
systemWindowId: itemType === 'system' || itemType === 'file' || itemType === 'folder' ? referenceId : null,
|
||||
positionX: positionX,
|
||||
positionY: positionY,
|
||||
}
|
||||
@ -83,8 +88,12 @@ export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
.returning()
|
||||
|
||||
if (result.length > 0 && result[0]) {
|
||||
desktopItems.value.push(result[0])
|
||||
return result[0]
|
||||
const itemWithRef = {
|
||||
...result[0],
|
||||
referenceId: itemType === 'extension' ? result[0].extensionId! : result[0].systemWindowId!,
|
||||
}
|
||||
desktopItems.value.push(itemWithRef)
|
||||
return itemWithRef
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen des Desktop-Items:', {
|
||||
@ -126,7 +135,11 @@ export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
if (result.length > 0 && result[0]) {
|
||||
const index = desktopItems.value.findIndex((item) => item.id === id)
|
||||
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) {
|
||||
@ -159,7 +172,14 @@ export const useDesktopStore = defineStore('desktopStore', () => {
|
||||
referenceId: string,
|
||||
) => {
|
||||
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
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user