Add GitHub Actions CI/CD pipelines

- Add build pipeline for Windows, macOS, and Linux
- Add release pipeline for automated releases
- Remove CLAUDE.md from git tracking
This commit is contained in:
2025-11-01 14:46:01 +01:00
parent 4ff6aee4d8
commit 121dd9dd00
13 changed files with 514 additions and 270 deletions

View File

@ -1,5 +1,13 @@
<template>
<UPopover v-model:open="open">
<UDrawer
v-model:open="open"
direction="right"
:title="t('launcher.title')"
:description="t('launcher.description')"
:ui="{
content: 'w-dvw max-w-md sm:max-w-fit',
}"
>
<UButton
icon="material-symbols:apps"
color="neutral"
@ -9,58 +17,64 @@
/>
<template #content>
<ul class="p-4 max-h-96 grid grid-cols-3 gap-2 overflow-scroll">
<!-- All launcher items (system windows + enabled extensions, alphabetically sorted) -->
<UContextMenu
v-for="item in launcherItems"
:key="item.id"
:items="getContextMenuItems(item)"
>
<div class="p-4 h-full overflow-y-auto">
<div class="flex flex-wrap">
<!-- All launcher items (system windows + enabled extensions, alphabetically sorted) -->
<UContextMenu
v-for="item in launcherItems"
:key="item.id"
:items="getContextMenuItems(item)"
>
<UiButton
square
size="lg"
variant="ghost"
:ui="{
base: 'size-24 flex flex-wrap text-sm items-center justify-center overflow-visible cursor-grab active:cursor-grabbing',
leadingIcon: 'size-10',
label: 'w-full',
}"
:icon="item.icon"
:label="item.name"
:tooltip="item.name"
draggable="true"
@click="openItem(item)"
@dragstart="handleDragStart($event, item)"
@dragend="handleDragEnd"
/>
</UContextMenu>
<!-- Disabled Extensions (grayed out) -->
<UiButton
v-for="extension in disabledExtensions"
:key="extension.id"
square
size="lg"
size="xl"
variant="ghost"
:disabled="true"
: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 opacity-40',
leadingIcon: 'size-10',
label: 'w-full',
}"
:icon="item.icon"
:label="item.name"
:tooltip="item.name"
draggable="true"
@click="openItem(item)"
@dragstart="handleDragStart($event, item)"
@dragend="handleDragEnd"
:icon="extension.icon || 'i-heroicons-puzzle-piece-solid'"
:label="extension.name"
:tooltip="`${extension.name} (${t('disabled')})`"
/>
</UContextMenu>
<!-- Disabled Extensions (grayed out) -->
<UiButton
v-for="extension in disabledExtensions"
:key="extension.id"
square
size="xl"
variant="ghost"
:disabled="true"
:ui="{
base: 'size-24 flex flex-wrap text-sm items-center justify-center overflow-visible opacity-40',
leadingIcon: 'size-10',
label: 'w-full',
}"
:icon="extension.icon || 'i-heroicons-puzzle-piece-solid'"
:label="extension.name"
:tooltip="`${extension.name} (${t('disabled')})`"
/>
</ul>
</div>
</div>
</template>
</UPopover>
</UDrawer>
<!-- Uninstall Confirmation Dialog -->
<UiDialogConfirm
v-model:open="showUninstallDialog"
:title="t('uninstall.confirm.title')"
:description="t('uninstall.confirm.description', { name: extensionToUninstall?.name || '' })"
:description="
t('uninstall.confirm.description', {
name: extensionToUninstall?.name || '',
})
"
:confirm-label="t('uninstall.confirm.button')"
confirm-icon="i-heroicons-trash"
@confirm="confirmUninstall"
@ -237,6 +251,9 @@ const handleDragEnd = () => {
de:
disabled: Deaktiviert
marketplace: Marketplace
launcher:
title: App Launcher
description: Wähle eine App zum Öffnen
contextMenu:
open: Öffnen
uninstall: Deinstallieren
@ -249,6 +266,9 @@ de:
en:
disabled: Disabled
marketplace: Marketplace
launcher:
title: App Launcher
description: Select an app to open
contextMenu:
open: Open
uninstall: Uninstall

View File

@ -2,6 +2,7 @@
<UiDialogConfirm
:confirm-label="t('create')"
@confirm="onCreateAsync"
:description="t('description')"
>
<UiButton
:label="t('vault.create')"
@ -55,7 +56,9 @@
<script setup lang="ts">
import { vaultSchema } from './schema'
const { t } = useI18n()
const { t } = useI18n({
useScope: 'local',
})
const vault = reactive<{
name: string
@ -118,6 +121,7 @@ de:
name: HaexVault
title: Neue {haexvault} erstellen
create: Erstellen
description: Erstelle eine neue Vault für deine Daten
en:
vault:
@ -127,4 +131,5 @@ en:
name: HaexVault
title: Create new {haexvault}
create: Create
description: Create a new vault for your data
</i18n>

View File

@ -58,7 +58,9 @@ const props = defineProps<{
path?: string
}>()
const { t } = useI18n()
const { t } = useI18n({
useScope: 'local',
})
const vault = reactive<{
name: string

View File

@ -1,90 +1,76 @@
<template>
<UModal
<UDrawer
v-model:open="localShowWindowOverview"
direction="bottom"
:title="t('modal.title')"
:description="t('modal.description')"
fullscreen
>
<template #content>
<div class="flex flex-col h-full">
<!-- Header -->
<div class="h-full overflow-y-auto p-6 justify-center flex">
<!-- Window Thumbnails Flex Layout -->
<div
class="flex items-center justify-end border-b p-2 border-gray-200 dark:border-gray-700"
v-if="windows.length > 0"
class="flex flex-wrap gap-6 justify-center-safe items-start"
>
<UButton
icon="i-heroicons-x-mark"
color="error"
variant="soft"
@click="localShowWindowOverview = false"
/>
</div>
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto p-6 justify-center flex">
<!-- Window Thumbnails Flex Layout -->
<div
v-if="windows.length > 0"
class="flex flex-wrap gap-6 justify-center-safe items-start"
v-for="window in windows"
:key="window.id"
class="relative group cursor-pointer"
>
<div
v-for="window in windows"
:key="window.id"
class="relative group cursor-pointer"
>
<!-- Window Title Bar -->
<div class="flex items-center gap-3 mb-2 px-2">
<UIcon
v-if="window.icon"
:name="window.icon"
class="size-5 shrink-0"
/>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm truncate">
{{ window.title }}
</p>
</div>
<!-- Minimized Badge -->
<UBadge
v-if="window.isMinimized"
color="info"
size="xs"
:title="t('minimized')"
/>
<!-- Window Title Bar -->
<div class="flex items-center gap-3 mb-2 px-2">
<UIcon
v-if="window.icon"
:name="window.icon"
class="size-5 shrink-0"
/>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm truncate">
{{ window.title }}
</p>
</div>
<!-- Minimized Badge -->
<UBadge
v-if="window.isMinimized"
color="info"
size="xs"
:title="t('minimized')"
/>
</div>
<!-- Scaled Window Preview Container / Teleport Target -->
<!-- Scaled Window Preview Container / Teleport Target -->
<div
:id="`window-preview-${window.id}`"
class="relative bg-gray-100 dark:bg-gray-900 rounded-xl overflow-hidden border-2 border-gray-200 dark:border-gray-700 group-hover:border-primary-500 transition-all shadow-lg"
:style="getCardStyle(window)"
@click="handleRestoreAndActivateWindow(window.id)"
>
<!-- Hover Overlay -->
<div
:id="`window-preview-${window.id}`"
class="relative bg-gray-100 dark:bg-gray-900 rounded-xl overflow-hidden border-2 border-gray-200 dark:border-gray-700 group-hover:border-primary-500 transition-all shadow-lg"
:style="getCardStyle(window)"
@click="handleRestoreAndActivateWindow(window.id)"
>
<!-- Hover Overlay -->
<div
class="absolute inset-0 bg-primary-500/10 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-40"
/>
</div>
class="absolute inset-0 bg-primary-500/10 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-40"
/>
</div>
</div>
</div>
<!-- Empty State -->
<div
v-else
class="flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400"
>
<UIcon
name="i-heroicons-window"
class="size-16 mb-4 shrink-0"
/>
<p class="text-lg font-medium">No windows open</p>
<p class="text-sm">
Open an extension or system window to see it here
</p>
</div>
<!-- Empty State -->
<div
v-else
class="flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400"
>
<UIcon
name="i-heroicons-window"
class="size-16 mb-4 shrink-0"
/>
<p class="text-lg font-medium">No windows open</p>
<p class="text-sm">
Open an extension or system window to see it here
</p>
</div>
</div>
</template>
</UModal>
</UDrawer>
</template>
<script setup lang="ts">

View File

@ -4,11 +4,10 @@
<UButton
class="pointer-events-auto"
v-bind="{
...{ size: isSmallScreen ? 'lg' : 'md' },
...buttonProps,
...$attrs,
}"
@click="(e) => $emit('click', e)"
@click="$emit('click', $event)"
>
<template
v-for="(_, slotName) in $slots"

View File

@ -1,5 +1,5 @@
<template>
<div class="w-full h-full flex flex-col">
<div class="w-full h-dvh flex flex-col">
<UPageHeader
ref="headerEl"
as="header"
@ -25,7 +25,7 @@
variant="outline"
icon="i-bi-person-workspace"
size="lg"
:tooltip="t('header.workspaces')"
:tooltip="t('workspaces.label')"
@click="isOverviewMode = !isOverviewMode"
/>
</div>
@ -54,7 +54,7 @@
</template>
</UPageHeader>
<main class="flex-1 overflow-hidden bg-elevated flex flex-col relative">
<main class="overflow-hidden relative bg-elevated h-full">
<slot />
</main>
@ -95,7 +95,7 @@
class="mt-6"
@click="handleAddWorkspace"
icon="i-heroicons-plus"
:label="t('add')"
:label="t('workspaces.add')"
>
</UButton>
</div>
@ -142,14 +142,14 @@ de:
search:
label: Suche
header:
workspaces: Workspaces
workspaces:
label: Workspaces
add: Workspace hinzufügen
en:
search:
label: Search
header:
workspaces: Workspaces
workspaces:
label: Workspaces
add: Add Workspace
</i18n>

View File

@ -1,120 +1,101 @@
<template>
<div>
<div class="h-full">
<NuxtLayout>
<UDashboardPanel
id="inbox-1"
resizable
class=""
<div
class="flex flex-col justify-center items-center gap-5 mx-auto h-full overflow-scroll"
>
<template #body>
<div class="items-center justify-center flex relative flex-1">
<!-- <div class="absolute top-0 right-0">
<UiDropdownLocale @select="onSelectLocale" />
</div> -->
<UiLogoHaexhub class="bg-primary p-3 size-16 rounded-full shrink-0" />
<span
class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"
>
<p class="whitespace-nowrap">
{{ t('welcome') }}
</p>
<UiTextGradient>Haex Hub</UiTextGradient>
</span>
<div
class="flex flex-col justify-center items-center gap-5 max-w-3xl"
>
<UiLogoHaexhub
class="bg-primary p-3 size-16 rounded-full shrink-0"
/>
<span
class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"
>
<p class="whitespace-nowrap">
{{ t('welcome') }}
</p>
<UiTextGradient>Haex Hub</UiTextGradient>
</span>
<div class="flex flex-col gap-4 h-24 items-stretch justify-center">
<HaexVaultCreate />
<div
class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto"
>
<HaexVaultCreate />
<HaexVaultOpen
v-model:open="passwordPromptOpen"
:path="selectedVault?.path"
/>
</div>
<HaexVaultOpen
v-model:open="passwordPromptOpen"
:path="selectedVault?.path"
/>
</div>
<div
v-show="lastVaults.length"
class="w-full"
>
<div class="font-thin text-sm justify-start px-2 pb-1">
{{ t('lastUsed') }}
</div>
<div
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border overflow-scroll"
>
<div
v-for="vault in lastVaults"
:key="vault.name"
class="flex items-center justify-between group overflow-x-scroll"
>
<UiButtonContext
variant="ghost"
color="neutral"
class="flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full px-3"
:context-menu-items="[
{
icon: 'mdi:trash-can-outline',
label: t('remove.button'),
onSelect: () => prepareRemoveVault(vault.name),
color: 'error',
},
]"
@click="
() => {
passwordPromptOpen = true
selectedVault = vault
}
"
>
<span class="block">
{{ vault.name }}
</span>
</UiButtonContext>
<UButton
color="error"
square
class="absolute right-2 hidden group-hover:flex min-w-6"
>
<Icon
name="mdi:trash-can-outline"
@click="prepareRemoveVault(vault.name)"
/>
</UButton>
</div>
</div>
</div>
<div class="flex flex-col items-center gap-2">
<h4>{{ t('sponsors') }}</h4>
<div>
<UButton
variant="link"
@click="openUrl('https://itemis.com')"
>
<UiLogoItemis class="text-[#00457C]" />
</UButton>
</div>
</div>
</div>
<UiDialogConfirm
v-model:open="showRemoveDialog"
:title="t('remove.title')"
:description="
t('remove.description', { vaultName: vaultToBeRemoved })
"
@confirm="onConfirmRemoveAsync"
/>
<div
v-show="lastVaults.length"
class="max-w-md w-full sm:px-5"
>
<div class="font-thin text-sm pb-1 w-full">
{{ t('lastUsed') }}
</div>
</template>
</UDashboardPanel>
<div
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border overflow-scroll"
>
<div
v-for="vault in lastVaults"
:key="vault.name"
class="flex items-center justify-between group overflow-x-scroll"
>
<UiButtonContext
variant="ghost"
color="neutral"
size="xl"
class="flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full hover:bg-default"
:context-menu-items="[
{
icon: 'mdi:trash-can-outline',
label: t('remove.button'),
onSelect: () => prepareRemoveVault(vault.name),
color: 'error',
},
]"
@click="
() => {
passwordPromptOpen = true
selectedVault = vault
}
"
>
<span class="block">
{{ vault.name }}
</span>
</UiButtonContext>
<UButton
color="error"
square
class="absolute right-2 hidden group-hover:flex min-w-6"
>
<Icon
name="mdi:trash-can-outline"
@click="prepareRemoveVault(vault.name)"
/>
</UButton>
</div>
</div>
</div>
<div class="flex flex-col items-center gap-2">
<h4>{{ t('sponsors') }}</h4>
<div>
<UButton
variant="link"
@click="openUrl('https://itemis.com')"
>
<UiLogoItemis class="text-[#00457C]" />
</UButton>
</div>
</div>
</div>
<UiDialogConfirm
v-model:open="showRemoveDialog"
:title="t('remove.title')"
:description="t('remove.description', { vaultName: vaultToBeRemoved })"
@confirm="onConfirmRemoveAsync"
/>
</NuxtLayout>
</div>
</template>

View File

@ -9,6 +9,7 @@
v-model:open="showNewDeviceDialog"
:confirm-label="t('newDevice.save')"
:title="t('newDevice.title')"
:description="t('newDevice.setName')"
confirm-icon="mdi:content-save-outline"
@abort="showNewDeviceDialog = false"
@confirm="onSetDeviceNameAsync"