mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
Add workspace background customization and fix launcher drawer drag
- Add workspace background image support with file-based storage - Store background images in $APPLOCALDATA/files directory - Save file paths in database (text column in haex_workspaces) - Use convertFileSrc for secure asset:// URL conversion - Add context menu to workspaces with "Hintergrund ändern" option - Implement background management in settings - File selection dialog for PNG, JPG, JPEG, WebP images - Copy selected images to app data directory - Remove background with file cleanup - Multilingual UI (German/English) - Fix launcher drawer drag interference - Add :handle-only="true" to UDrawer to restrict drag to handle - Simplify drag handlers (removed complex state tracking) - Items can now be dragged to desktop without drawer interference - Extend Tauri asset protocol scope to include $APPLOCALDATA/** for background image loading
This commit is contained in:
@ -64,7 +64,7 @@
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["$APPDATA", "$RESOURCE"]
|
||||
"scope": ["$APPDATA", "$RESOURCE", "$APPLOCALDATA/**"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -23,191 +23,198 @@
|
||||
:key="workspace.id"
|
||||
class="w-full h-full"
|
||||
>
|
||||
<div
|
||||
class="w-full h-full relative"
|
||||
@click.self.stop="handleDesktopClick"
|
||||
@mousedown.left.self="handleAreaSelectStart"
|
||||
@dragover.prevent="handleDragOver"
|
||||
@drop.prevent="handleDrop($event, workspace.id)"
|
||||
>
|
||||
<!-- Grid Pattern Background -->
|
||||
<UContextMenu :items="getWorkspaceContextMenuItems(workspace.id)">
|
||||
<div
|
||||
class="absolute inset-0 pointer-events-none opacity-30"
|
||||
:style="{
|
||||
backgroundImage:
|
||||
'linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)',
|
||||
backgroundSize: '32px 32px',
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- Snap Dropzones (only visible when window drag near edge) -->
|
||||
|
||||
<div
|
||||
class="absolute left-0 top-0 bottom-0 border-blue-500 pointer-events-none backdrop-blur-sm z-50 transition-all duration-500 ease-in-out"
|
||||
: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-50 transition-all duration-500 ease-in-out"
|
||||
:class="showRightSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'"
|
||||
/>
|
||||
|
||||
<!-- Area Selection Box -->
|
||||
<div
|
||||
v-if="isAreaSelecting"
|
||||
class="absolute bg-blue-500/20 border-2 border-blue-500 pointer-events-none z-30"
|
||||
:style="selectionBoxStyle"
|
||||
/>
|
||||
|
||||
<!-- Icons for this workspace -->
|
||||
<HaexDesktopIcon
|
||||
v-for="item in getWorkspaceIcons(workspace.id)"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
:item-type="item.itemType"
|
||||
:reference-id="item.referenceId"
|
||||
:initial-x="item.positionX"
|
||||
:initial-y="item.positionY"
|
||||
:label="item.label"
|
||||
:icon="item.icon"
|
||||
class="no-swipe"
|
||||
@position-changed="handlePositionChanged"
|
||||
@drag-start="handleDragStart"
|
||||
@drag-end="handleDragEnd"
|
||||
/>
|
||||
|
||||
<!-- Windows for this workspace -->
|
||||
<template
|
||||
v-for="window in getWorkspaceWindows(workspace.id)"
|
||||
:key="window.id"
|
||||
class="w-full h-full relative"
|
||||
:style="getWorkspaceBackgroundStyle(workspace)"
|
||||
@click.self.stop="handleDesktopClick"
|
||||
@mousedown.left.self="handleAreaSelectStart"
|
||||
@dragover.prevent="handleDragOver"
|
||||
@drop.prevent="handleDrop($event, workspace.id)"
|
||||
>
|
||||
<!-- Overview Mode: Teleport to window preview -->
|
||||
<Teleport
|
||||
v-if="
|
||||
windowManager.showWindowOverview &&
|
||||
overviewWindowState.has(window.id)
|
||||
"
|
||||
:to="`#window-preview-${window.id}`"
|
||||
>
|
||||
<div
|
||||
class="absolute origin-top-left"
|
||||
:style="{
|
||||
transform: `scale(${overviewWindowState.get(window.id)!.scale})`,
|
||||
width: `${overviewWindowState.get(window.id)!.width}px`,
|
||||
height: `${overviewWindowState.get(window.id)!.height}px`,
|
||||
}"
|
||||
>
|
||||
<HaexWindow
|
||||
v-show="
|
||||
windowManager.showWindowOverview || !window.isMinimized
|
||||
"
|
||||
:id="window.id"
|
||||
v-model:x="overviewWindowState.get(window.id)!.x"
|
||||
v-model:y="overviewWindowState.get(window.id)!.y"
|
||||
v-model:width="overviewWindowState.get(window.id)!.width"
|
||||
v-model:height="overviewWindowState.get(window.id)!.height"
|
||||
:title="window.title"
|
||||
:icon="window.icon"
|
||||
:is-active="windowManager.isWindowActive(window.id)"
|
||||
:source-x="window.sourceX"
|
||||
:source-y="window.sourceY"
|
||||
:source-width="window.sourceWidth"
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find(
|
||||
(ext) => ext.id === window.sourceId,
|
||||
)?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
@activate="windowManager.activateWindow(window.id)"
|
||||
@position-changed="
|
||||
(x, y) =>
|
||||
windowManager.updateWindowPosition(window.id, x, y)
|
||||
"
|
||||
@size-changed="
|
||||
(width, height) =>
|
||||
windowManager.updateWindowSize(window.id, width, height)
|
||||
"
|
||||
@drag-start="handleWindowDragStart(window.id)"
|
||||
@drag-end="handleWindowDragEnd"
|
||||
>
|
||||
<!-- System Window: Render Vue Component -->
|
||||
<component
|
||||
:is="getSystemWindowComponent(window.sourceId)"
|
||||
v-if="window.type === 'system'"
|
||||
/>
|
||||
<!-- Grid Pattern Background -->
|
||||
<div
|
||||
class="absolute inset-0 pointer-events-none opacity-30"
|
||||
:style="{
|
||||
backgroundImage:
|
||||
'linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)',
|
||||
backgroundSize: '32px 32px',
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- Extension Window: Render iFrame -->
|
||||
<HaexDesktopExtensionFrame
|
||||
v-else
|
||||
:extension-id="window.sourceId"
|
||||
:window-id="window.id"
|
||||
/>
|
||||
</HaexWindow>
|
||||
</div>
|
||||
</Teleport>
|
||||
<!-- Snap Dropzones (only visible when window drag near edge) -->
|
||||
|
||||
<!-- Desktop Mode: Render directly in workspace -->
|
||||
<HaexWindow
|
||||
v-else
|
||||
v-show="windowManager.showWindowOverview || !window.isMinimized"
|
||||
:id="window.id"
|
||||
v-model:x="window.x"
|
||||
v-model:y="window.y"
|
||||
v-model:width="window.width"
|
||||
v-model:height="window.height"
|
||||
:title="window.title"
|
||||
:icon="window.icon"
|
||||
:is-active="windowManager.isWindowActive(window.id)"
|
||||
:source-x="window.sourceX"
|
||||
:source-y="window.sourceY"
|
||||
:source-width="window.sourceWidth"
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find((ext) => ext.id === window.sourceId)
|
||||
?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
<div
|
||||
class="absolute left-0 top-0 bottom-0 border-blue-500 pointer-events-none backdrop-blur-sm z-50 transition-all duration-500 ease-in-out"
|
||||
: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-50 transition-all duration-500 ease-in-out"
|
||||
:class="
|
||||
showRightSnapZone ? 'w-1/2 bg-blue-500/20 border-2' : 'w-0'
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- Area Selection Box -->
|
||||
<div
|
||||
v-if="isAreaSelecting"
|
||||
class="absolute bg-blue-500/20 border-2 border-blue-500 pointer-events-none z-30"
|
||||
:style="selectionBoxStyle"
|
||||
/>
|
||||
|
||||
<!-- Icons for this workspace -->
|
||||
<HaexDesktopIcon
|
||||
v-for="item in getWorkspaceIcons(workspace.id)"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
:item-type="item.itemType"
|
||||
:reference-id="item.referenceId"
|
||||
:initial-x="item.positionX"
|
||||
:initial-y="item.positionY"
|
||||
:label="item.label"
|
||||
:icon="item.icon"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
@activate="windowManager.activateWindow(window.id)"
|
||||
@position-changed="
|
||||
(x, y) => windowManager.updateWindowPosition(window.id, x, y)
|
||||
"
|
||||
@size-changed="
|
||||
(width, height) =>
|
||||
windowManager.updateWindowSize(window.id, width, height)
|
||||
"
|
||||
@drag-start="handleWindowDragStart(window.id)"
|
||||
@drag-end="handleWindowDragEnd"
|
||||
>
|
||||
<!-- System Window: Render Vue Component -->
|
||||
<component
|
||||
:is="getSystemWindowComponent(window.sourceId)"
|
||||
v-if="window.type === 'system'"
|
||||
/>
|
||||
@position-changed="handlePositionChanged"
|
||||
@drag-start="handleDragStart"
|
||||
@drag-end="handleDragEnd"
|
||||
/>
|
||||
|
||||
<!-- Extension Window: Render iFrame -->
|
||||
<HaexDesktopExtensionFrame
|
||||
<!-- Windows for this workspace -->
|
||||
<template
|
||||
v-for="window in getWorkspaceWindows(workspace.id)"
|
||||
:key="window.id"
|
||||
>
|
||||
<!-- Overview Mode: Teleport to window preview -->
|
||||
<Teleport
|
||||
v-if="
|
||||
windowManager.showWindowOverview &&
|
||||
overviewWindowState.has(window.id)
|
||||
"
|
||||
:to="`#window-preview-${window.id}`"
|
||||
>
|
||||
<div
|
||||
class="absolute origin-top-left"
|
||||
:style="{
|
||||
transform: `scale(${overviewWindowState.get(window.id)!.scale})`,
|
||||
width: `${overviewWindowState.get(window.id)!.width}px`,
|
||||
height: `${overviewWindowState.get(window.id)!.height}px`,
|
||||
}"
|
||||
>
|
||||
<HaexWindow
|
||||
v-show="
|
||||
windowManager.showWindowOverview || !window.isMinimized
|
||||
"
|
||||
:id="window.id"
|
||||
v-model:x="overviewWindowState.get(window.id)!.x"
|
||||
v-model:y="overviewWindowState.get(window.id)!.y"
|
||||
v-model:width="overviewWindowState.get(window.id)!.width"
|
||||
v-model:height="overviewWindowState.get(window.id)!.height"
|
||||
:title="window.title"
|
||||
:icon="window.icon"
|
||||
:is-active="windowManager.isWindowActive(window.id)"
|
||||
:source-x="window.sourceX"
|
||||
:source-y="window.sourceY"
|
||||
:source-width="window.sourceWidth"
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find(
|
||||
(ext) => ext.id === window.sourceId,
|
||||
)?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
@activate="windowManager.activateWindow(window.id)"
|
||||
@position-changed="
|
||||
(x, y) =>
|
||||
windowManager.updateWindowPosition(window.id, x, y)
|
||||
"
|
||||
@size-changed="
|
||||
(width, height) =>
|
||||
windowManager.updateWindowSize(window.id, width, height)
|
||||
"
|
||||
@drag-start="handleWindowDragStart(window.id)"
|
||||
@drag-end="handleWindowDragEnd"
|
||||
>
|
||||
<!-- System Window: Render Vue Component -->
|
||||
<component
|
||||
:is="getSystemWindowComponent(window.sourceId)"
|
||||
v-if="window.type === 'system'"
|
||||
/>
|
||||
|
||||
<!-- Extension Window: Render iFrame -->
|
||||
<HaexDesktopExtensionFrame
|
||||
v-else
|
||||
:extension-id="window.sourceId"
|
||||
:window-id="window.id"
|
||||
/>
|
||||
</HaexWindow>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- Desktop Mode: Render directly in workspace -->
|
||||
<HaexWindow
|
||||
v-else
|
||||
:extension-id="window.sourceId"
|
||||
:window-id="window.id"
|
||||
/>
|
||||
</HaexWindow>
|
||||
</template>
|
||||
</div>
|
||||
v-show="windowManager.showWindowOverview || !window.isMinimized"
|
||||
:id="window.id"
|
||||
v-model:x="window.x"
|
||||
v-model:y="window.y"
|
||||
v-model:width="window.width"
|
||||
v-model:height="window.height"
|
||||
:title="window.title"
|
||||
:icon="window.icon"
|
||||
:is-active="windowManager.isWindowActive(window.id)"
|
||||
:source-x="window.sourceX"
|
||||
:source-y="window.sourceY"
|
||||
:source-width="window.sourceWidth"
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find((ext) => ext.id === window.sourceId)
|
||||
?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
@activate="windowManager.activateWindow(window.id)"
|
||||
@position-changed="
|
||||
(x, y) => windowManager.updateWindowPosition(window.id, x, y)
|
||||
"
|
||||
@size-changed="
|
||||
(width, height) =>
|
||||
windowManager.updateWindowSize(window.id, width, height)
|
||||
"
|
||||
@drag-start="handleWindowDragStart(window.id)"
|
||||
@drag-end="handleWindowDragEnd"
|
||||
>
|
||||
<!-- System Window: Render Vue Component -->
|
||||
<component
|
||||
:is="getSystemWindowComponent(window.sourceId)"
|
||||
v-if="window.type === 'system'"
|
||||
/>
|
||||
|
||||
<!-- Extension Window: Render iFrame -->
|
||||
<HaexDesktopExtensionFrame
|
||||
v-else
|
||||
:extension-id="window.sourceId"
|
||||
:window-id="window.id"
|
||||
/>
|
||||
</HaexWindow>
|
||||
</template>
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
|
||||
@ -239,6 +246,8 @@ const {
|
||||
allowSwipe,
|
||||
isOverviewMode,
|
||||
} = storeToRefs(workspaceStore)
|
||||
const { getWorkspaceBackgroundStyle, getWorkspaceContextMenuItems } =
|
||||
workspaceStore
|
||||
|
||||
const { x: mouseX } = useMouse()
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
direction="right"
|
||||
:title="t('launcher.title')"
|
||||
:description="t('launcher.description')"
|
||||
:overlay="false"
|
||||
:modal="false"
|
||||
:handle-only="true"
|
||||
:ui="{
|
||||
content: 'w-dvw max-w-md sm:max-w-fit',
|
||||
}"
|
||||
@ -30,7 +33,7 @@
|
||||
size="lg"
|
||||
variant="ghost"
|
||||
:ui="{
|
||||
base: 'size-24 flex flex-wrap text-sm items-center justify-center overflow-visible cursor-grab active:cursor-grabbing',
|
||||
base: 'size-24 flex flex-wrap text-sm items-center justify-center overflow-visible cursor-grab',
|
||||
leadingIcon: 'size-10',
|
||||
label: 'w-full',
|
||||
}"
|
||||
@ -241,10 +244,6 @@ const handleDragStart = (event: DragEvent, item: LauncherItem) => {
|
||||
event.dataTransfer.setDragImage(dragImage, 20, 20)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
// Cleanup if needed
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -33,6 +33,20 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="p-2">{{ t('workspaceBackground.label') }}</div>
|
||||
<div class="flex gap-2">
|
||||
<UiButton
|
||||
:label="t('workspaceBackground.choose')"
|
||||
@click="selectBackgroundImage"
|
||||
/>
|
||||
<UiButton
|
||||
v-if="currentWorkspace?.background"
|
||||
:label="t('workspaceBackground.remove.label')"
|
||||
color="error"
|
||||
@click="removeBackgroundImage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="h-full"/>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,6 +54,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Locale } from 'vue-i18n'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { readFile, writeFile, mkdir, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { appLocalDataDir, join } from '@tauri-apps/api/path'
|
||||
|
||||
const { t, setLocale } = useI18n()
|
||||
|
||||
@ -77,6 +94,10 @@ const { requestNotificationPermissionAsync } = useNotificationStore()
|
||||
const { deviceName } = storeToRefs(useDeviceStore())
|
||||
const { updateDeviceNameAsync, readDeviceNameAsync } = useDeviceStore()
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const { currentWorkspace } = storeToRefs(workspaceStore)
|
||||
const { updateWorkspaceBackgroundAsync } = workspaceStore
|
||||
|
||||
onMounted(async () => {
|
||||
await readDeviceNameAsync()
|
||||
})
|
||||
@ -92,6 +113,72 @@ const onUpdateDeviceNameAsync = async () => {
|
||||
add({ description: t('deviceName.update.error'), color: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const selectBackgroundImage = async () => {
|
||||
if (!currentWorkspace.value) return
|
||||
|
||||
try {
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters: [{
|
||||
name: 'Images',
|
||||
extensions: ['png', 'jpg', 'jpeg', 'webp']
|
||||
}]
|
||||
})
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
const fileData = await readFile(selected)
|
||||
|
||||
// Create files directory if it doesn't exist
|
||||
const appDataPath = await appLocalDataDir()
|
||||
const filesDir = await join(appDataPath, 'files')
|
||||
|
||||
if (!await exists(filesDir)) {
|
||||
await mkdir(filesDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Generate unique filename for the background image
|
||||
const ext = selected.split('.').pop()?.toLowerCase() || 'png'
|
||||
const fileName = `workspace-${currentWorkspace.value.id}-background.${ext}`
|
||||
const targetPath = await join(filesDir, fileName)
|
||||
|
||||
// Copy file to app data directory
|
||||
await writeFile(targetPath, fileData)
|
||||
|
||||
// Store the absolute file path in database
|
||||
await updateWorkspaceBackgroundAsync(currentWorkspace.value.id, targetPath)
|
||||
add({ description: t('workspaceBackground.update.success'), color: 'success' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error selecting background:', error)
|
||||
add({ description: t('workspaceBackground.update.error'), color: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const removeBackgroundImage = async () => {
|
||||
if (!currentWorkspace.value) return
|
||||
|
||||
try {
|
||||
// Delete the background file if it exists
|
||||
if (currentWorkspace.value.background) {
|
||||
try {
|
||||
// The background field contains the absolute file path
|
||||
if (await exists(currentWorkspace.value.background)) {
|
||||
await remove(currentWorkspace.value.background)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Could not delete background file:', err)
|
||||
// Continue anyway to clear the database entry
|
||||
}
|
||||
}
|
||||
|
||||
await updateWorkspaceBackgroundAsync(currentWorkspace.value.id, null)
|
||||
add({ description: t('workspaceBackground.remove.success'), color: 'success' })
|
||||
} catch (error) {
|
||||
console.error('Error removing background:', error)
|
||||
add({ description: t('workspaceBackground.remove.error'), color: 'error' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
@ -112,6 +199,16 @@ de:
|
||||
update:
|
||||
success: Gerätename wurde erfolgreich aktualisiert
|
||||
error: Gerätename konnte nich aktualisiert werden
|
||||
workspaceBackground:
|
||||
label: Workspace-Hintergrund
|
||||
choose: Bild auswählen
|
||||
update:
|
||||
success: Hintergrund erfolgreich aktualisiert
|
||||
error: Fehler beim Aktualisieren des Hintergrunds
|
||||
remove:
|
||||
label: Hintergrund entfernen
|
||||
success: Hintergrund erfolgreich entfernt
|
||||
error: Fehler beim Entfernen des Hintergrunds
|
||||
en:
|
||||
language: Language
|
||||
design: Design
|
||||
@ -129,4 +226,14 @@ en:
|
||||
update:
|
||||
success: Device name has been successfully updated
|
||||
error: Device name could not be updated
|
||||
workspaceBackground:
|
||||
label: Workspace Background
|
||||
choose: Choose Image
|
||||
update:
|
||||
success: Background successfully updated
|
||||
error: Error updating background
|
||||
remove:
|
||||
label: Remove Background
|
||||
success: Background successfully removed
|
||||
error: Error removing background
|
||||
</i18n>
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
type AnySQLiteColumn,
|
||||
type SQLiteColumnBuilderBase,
|
||||
} from 'drizzle-orm/sqlite-core'
|
||||
import tableNames from '~/database/tableNames.json'
|
||||
import tableNames from '@/database/tableNames.json'
|
||||
|
||||
const crdtColumnNames = {
|
||||
haexTimestamp: 'haex_timestamp',
|
||||
@ -137,6 +137,7 @@ export const haexWorkspaces = sqliteTable(
|
||||
position: integer(tableNames.haex.workspaces.columns.position)
|
||||
.notNull()
|
||||
.default(0),
|
||||
background: text(),
|
||||
}),
|
||||
(table) => [unique().on(table.position)],
|
||||
)
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
type SelectHaexWorkspaces,
|
||||
} from '~/database/schemas'
|
||||
import type { Swiper } from 'swiper/types'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
|
||||
export type IWorkspace = SelectHaexWorkspaces
|
||||
|
||||
@ -203,12 +204,86 @@ export const useWorkspaceStore = defineStore('workspaceStore', () => {
|
||||
isOverviewMode.value = false
|
||||
}
|
||||
|
||||
const updateWorkspaceBackgroundAsync = async (
|
||||
workspaceId: string,
|
||||
base64Image: string | null,
|
||||
) => {
|
||||
if (!currentVault.value?.drizzle) {
|
||||
throw new Error('Kein Vault geöffnet')
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await currentVault.value.drizzle
|
||||
.update(haexWorkspaces)
|
||||
.set({ background: base64Image })
|
||||
.where(eq(haexWorkspaces.id, workspaceId))
|
||||
.returning()
|
||||
|
||||
if (result.length > 0 && result[0]) {
|
||||
const index = workspaces.value.findIndex((ws) => ws.id === workspaceId)
|
||||
if (index !== -1) {
|
||||
workspaces.value[index] = result[0]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Workspace-Hintergrunds:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getWorkspaceBackgroundStyle = (workspace: IWorkspace) => {
|
||||
if (!workspace.background) return {}
|
||||
|
||||
// The background field contains the absolute file path
|
||||
// Convert it to an asset URL
|
||||
const assetUrl = convertFileSrc(workspace.background)
|
||||
|
||||
return {
|
||||
backgroundImage: `url(${assetUrl})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
}
|
||||
|
||||
const getWorkspaceContextMenuItems = (workspaceId: string) => {
|
||||
const windowManager = useWindowManagerStore()
|
||||
|
||||
return [[
|
||||
{
|
||||
label: 'Hintergrund ändern',
|
||||
icon: 'i-mdi-image',
|
||||
onSelect: async () => {
|
||||
// Store the workspace ID for settings to use
|
||||
currentWorkspaceIndex.value = workspaces.value.findIndex(
|
||||
(ws) => ws.id === workspaceId,
|
||||
)
|
||||
// Get settings window info
|
||||
const settingsWindow = windowManager.getAllSystemWindows()
|
||||
.find((win) => win.id === 'settings')
|
||||
|
||||
if (settingsWindow) {
|
||||
await windowManager.openWindowAsync({
|
||||
type: 'system',
|
||||
sourceId: settingsWindow.id,
|
||||
title: settingsWindow.name,
|
||||
icon: settingsWindow.icon || undefined,
|
||||
workspaceId,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
]]
|
||||
}
|
||||
|
||||
return {
|
||||
addWorkspaceAsync,
|
||||
allowSwipe,
|
||||
closeWorkspaceAsync,
|
||||
currentWorkspace,
|
||||
currentWorkspaceIndex,
|
||||
getWorkspaceBackgroundStyle,
|
||||
getWorkspaceContextMenuItems,
|
||||
isOverviewMode,
|
||||
slideToWorkspace,
|
||||
loadWorkspacesAsync,
|
||||
@ -218,6 +293,7 @@ export const useWorkspaceStore = defineStore('workspaceStore', () => {
|
||||
switchToNext,
|
||||
switchToPrevious,
|
||||
switchToWorkspace,
|
||||
updateWorkspaceBackgroundAsync,
|
||||
workspaces,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user