mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
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:
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -58,7 +58,9 @@ const props = defineProps<{
|
||||
path?: string
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const vault = reactive<{
|
||||
name: string
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
Reference in New Issue
Block a user