mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +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:
109
.github/workflows/build.yml
vendored
Normal file
109
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: 'macos-latest'
|
||||
args: '--target aarch64-apple-darwin'
|
||||
- platform: 'macos-latest'
|
||||
args: '--target x86_64-apple-darwin'
|
||||
- platform: 'ubuntu-22.04'
|
||||
args: ''
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: ${{ matrix.args }}
|
||||
|
||||
- name: Upload artifacts (macOS)
|
||||
if: matrix.platform == 'macos-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-${{ contains(matrix.args, 'aarch64') && 'aarch64' || 'x86_64' }}
|
||||
path: |
|
||||
src-tauri/target/*/release/bundle/dmg/*.dmg
|
||||
src-tauri/target/*/release/bundle/macos/*.app
|
||||
|
||||
- name: Upload artifacts (Ubuntu)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
path: |
|
||||
src-tauri/target/release/bundle/deb/*.deb
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage
|
||||
src-tauri/target/release/bundle/rpm/*.rpm
|
||||
|
||||
- name: Upload artifacts (Windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
src-tauri/target/release/bundle/msi/*.msi
|
||||
src-tauri/target/release/bundle/nsis/*.exe
|
||||
137
.github/workflows/release.yml
vendored
Normal file
137
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.result }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Get version
|
||||
run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
|
||||
|
||||
- name: Create release
|
||||
id: create-release
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.createRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: `v${process.env.PACKAGE_VERSION}`,
|
||||
name: `haex-hub v${process.env.PACKAGE_VERSION}`,
|
||||
body: 'Take a look at the assets to download and install this app.',
|
||||
draft: true,
|
||||
prerelease: false
|
||||
})
|
||||
return data.id
|
||||
|
||||
build-tauri:
|
||||
needs: create-release
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: 'macos-latest'
|
||||
args: '--target aarch64-apple-darwin'
|
||||
- platform: 'macos-latest'
|
||||
args: '--target x86_64-apple-darwin'
|
||||
- platform: 'ubuntu-22.04'
|
||||
args: ''
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.15.1
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build and release Tauri app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
releaseId: ${{ needs.create-release.outputs.release_id }}
|
||||
args: ${{ matrix.args }}
|
||||
|
||||
publish-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [create-release, build-tauri]
|
||||
|
||||
steps:
|
||||
- name: Publish release
|
||||
id: publish-release
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
release_id: ${{ needs.create-release.outputs.release_id }}
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: process.env.release_id,
|
||||
draft: false,
|
||||
prerelease: false
|
||||
})
|
||||
13
CLAUDE.md
13
CLAUDE.md
@ -1,13 +0,0 @@
|
||||
# Verwandte Projekte
|
||||
|
||||
## SDK
|
||||
|
||||
- /home/haex/Projekte/haexhub-sdk
|
||||
|
||||
## Erweiterung HaexPass (Password Manager)
|
||||
|
||||
- /home/haex/Projekte/haex-pass
|
||||
|
||||
# Codingstyle
|
||||
|
||||
- alle asynchronen Funktionen bitte mit Async prependen
|
||||
@ -49,9 +49,11 @@ tauri-plugin-os = "2.3.2"
|
||||
tauri-plugin-persisted-scope = "2.3.4"
|
||||
tauri-plugin-store = "2.4.1"
|
||||
thiserror = "2.0.17"
|
||||
trash = "5.2.0"
|
||||
ts-rs = { version = "11.1.0", features = ["serde-compat"] }
|
||||
uhlc = "0.8.2"
|
||||
url = "2.5.7"
|
||||
uuid = { version = "1.18.1", features = ["v4"] }
|
||||
zip = "6.0.0"
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
trash = "5.2.0"
|
||||
|
||||
@ -20,6 +20,7 @@ use std::time::UNIX_EPOCH;
|
||||
use std::{fs, sync::Arc};
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use trash;
|
||||
use ts_rs::TS;
|
||||
|
||||
@ -219,36 +220,50 @@ pub fn move_vault_to_trash(
|
||||
app_handle: AppHandle,
|
||||
vault_name: String,
|
||||
) -> Result<String, DatabaseError> {
|
||||
let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
let vault_shm_path = format!("{}-shm", vault_path);
|
||||
let vault_wal_path = format!("{}-wal", vault_path);
|
||||
|
||||
if !Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: vault_path,
|
||||
reason: "Vault does not exist".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try to move to trash first (works on desktop systems)
|
||||
let moved_to_trash = trash::delete(&vault_path).is_ok();
|
||||
|
||||
if moved_to_trash {
|
||||
// Also try to move auxiliary files to trash (ignore errors as they might not exist)
|
||||
let _ = trash::delete(&vault_shm_path);
|
||||
let _ = trash::delete(&vault_wal_path);
|
||||
|
||||
Ok(format!(
|
||||
"Vault '{}' successfully moved to trash",
|
||||
vault_name
|
||||
))
|
||||
} else {
|
||||
// Fallback: Permanent deletion (e.g., on mobile devices without trash)
|
||||
// On Android, trash is not available, so delete permanently
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
println!(
|
||||
"Trash not available, falling back to permanent deletion for vault '{}'",
|
||||
"Android platform detected, permanently deleting vault '{}'",
|
||||
vault_name
|
||||
);
|
||||
delete_vault(app_handle, vault_name)
|
||||
return delete_vault(app_handle, vault_name);
|
||||
}
|
||||
|
||||
// On non-Android platforms, try to use trash
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
let vault_shm_path = format!("{}-shm", vault_path);
|
||||
let vault_wal_path = format!("{}-wal", vault_path);
|
||||
|
||||
if !Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: vault_path,
|
||||
reason: "Vault does not exist".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try to move to trash first (works on desktop systems)
|
||||
let moved_to_trash = trash::delete(&vault_path).is_ok();
|
||||
|
||||
if moved_to_trash {
|
||||
// Also try to move auxiliary files to trash (ignore errors as they might not exist)
|
||||
let _ = trash::delete(&vault_shm_path);
|
||||
let _ = trash::delete(&vault_wal_path);
|
||||
|
||||
Ok(format!(
|
||||
"Vault '{}' successfully moved to trash",
|
||||
vault_name
|
||||
))
|
||||
} else {
|
||||
// Fallback: Permanent deletion if trash fails
|
||||
println!(
|
||||
"Trash not available, falling back to permanent deletion for vault '{}'",
|
||||
vault_name
|
||||
);
|
||||
delete_vault(app_handle, vault_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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