switch to nuxt ui

This commit is contained in:
2025-09-11 00:58:55 +02:00
parent 3975d26caa
commit 0a7de8b78b
143 changed files with 19019 additions and 9899 deletions

3
.gitignore vendored
View File

@ -22,6 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.nuxt .nuxt
.output src-tauri/target

View File

@ -1,6 +1,4 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs' import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt( export default withNuxt()
// Your custom configs here // Your custom configs here
)

View File

@ -1,25 +1,30 @@
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
// https://nuxt.com/docs/api/configuration/nuxt-config
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
srcDir: './src',
app: { app: {
pageTransition: { pageTransition: {
name: 'fade', name: 'fade',
}, },
}, },
modules: [ modules: [
'nuxt-zod-i18n', 'nuxt-zod-i18n',
'@nuxtjs/i18n', '@nuxtjs/i18n',
'@pinia/nuxt', '@pinia/nuxt',
'@vueuse/nuxt', '@vueuse/nuxt',
'@nuxt/icon', '@nuxt/icon',
'nuxt-snackbar',
'@nuxt/eslint', '@nuxt/eslint',
'@nuxt/image', //"@nuxt/image",
'@nuxt/fonts',
'@nuxt/ui',
], ],
compatibilityDate: '2024-11-01',
imports: { imports: {
dirs: [ dirs: [
'composables/**', 'composables/**',
@ -30,7 +35,7 @@ export default defineNuxtConfig({
], ],
}, },
css: ['./assets/css/tailwind.css'], css: ['./assets/css/main.css'],
icon: { icon: {
provider: 'server', provider: 'server',
@ -55,7 +60,6 @@ export default defineNuxtConfig({
i18n: { i18n: {
strategy: 'prefix_and_default', strategy: 'prefix_and_default',
defaultLocale: 'de', defaultLocale: 'de',
vueI18n: '~/i18n/i18n.config.ts',
locales: [ locales: [
{ code: 'de', language: 'de-DE', isCatchallLocale: true }, { code: 'de', language: 'de-DE', isCatchallLocale: true },
@ -68,9 +72,6 @@ export default defineNuxtConfig({
redirectOn: 'root', // recommended redirectOn: 'root', // recommended
}, },
types: 'composition', types: 'composition',
bundle: {
optimizeTranslationDirective: false,
},
}, },
zodI18n: { zodI18n: {
@ -90,14 +91,10 @@ export default defineNuxtConfig({
}, },
}, },
devtools: { enabled: true },
srcDir: './src',
// Enable SSG
ssr: false, ssr: false,
// Enables the development server to be discoverable by other devices when running on iOS physical devices // Enables the development server to be discoverable by other devices when running on iOS physical devices
devServer: { devServer: {
host: process.env.TAURI_DEV_HOST || 'localhost', host: '0',
port: 3003, port: 3003,
}, },
@ -114,4 +111,5 @@ export default defineNuxtConfig({
strictPort: true, strictPort: true,
}, },
}, },
ignore: ['**/src-tauri/**'],
}) })

View File

@ -1,5 +1,5 @@
{ {
"name": "haex-hub", "name": "tauri-app",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.0",
"type": "module", "type": "module",
@ -16,57 +16,48 @@
"eslint:fix": "eslint --fix" "eslint:fix": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@libsql/client": "^0.15.9", "@nuxt/eslint": "1.9.0",
"@nuxt/eslint": "1.4.1", "@nuxt/fonts": "0.11.4",
"@nuxt/icon": "^1.14.0", "@nuxt/icon": "2.0.0",
"@nuxt/image": "1.10.0", "@nuxt/ui": "^3.3.2",
"@nuxtjs/i18n": "^9.5.5", "@nuxtjs/i18n": "10.0.6",
"@pinia/nuxt": "^0.11.1", "@pinia/nuxt": "^0.11.1",
"@tailwindcss/vite": "^4.1.10", "@tailwindcss/vite": "^4.1.10",
"@tauri-apps/api": "^2.5.0", "@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.2.2", "@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.3.0", "@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-http": "~2.4.4", "@tauri-apps/plugin-http": "2.5.2",
"@tauri-apps/plugin-notification": "~2.2.3", "@tauri-apps/plugin-notification": "2.3.1",
"@tauri-apps/plugin-opener": "^2.3.0", "@tauri-apps/plugin-opener": "^2.3.0",
"@tauri-apps/plugin-os": "^2.2.2", "@tauri-apps/plugin-os": "^2.2.2",
"@tauri-apps/plugin-sql": "~2.2.1", "@tauri-apps/plugin-sql": "2.3.0",
"@tauri-apps/plugin-store": "^2.2.1", "@tauri-apps/plugin-store": "^2.2.1",
"@vlcn.io/crsqlite": "^0.16.3", "@vueuse/components": "^13.9.0",
"@vueuse/components": "^13.4.0",
"@vueuse/core": "^13.4.0", "@vueuse/core": "^13.4.0",
"@vueuse/nuxt": "^13.4.0", "@vueuse/nuxt": "^13.4.0",
"drizzle-orm": "^0.44.2", "drizzle-orm": "^0.44.2",
"eslint": "^9.29.0", "eslint": "^9.34.0",
"flyonui": "^2.2.0",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"nuxt": "^3.17.5", "nuxt": "^4.0.3",
"nuxt-snackbar": "1.3.0",
"nuxt-zod-i18n": "^1.12.0", "nuxt-zod-i18n": "^1.12.0",
"tailwindcss": "^4.1.10", "tailwindcss": "^4.1.10",
"tailwindcss-intersect": "^2.2.0", "vue": "^3.5.20",
"tailwindcss-motion": "^1.1.1", "vue-router": "^4.5.1",
"vue": "^3.5.17", "zod": "4.1.5"
"zod": "^3.25.67"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/proicons": "^1.2.17",
"@iconify/json": "^2.2.351", "@iconify/json": "^2.2.351",
"@iconify/tailwind4": "^1.0.6", "@iconify/tailwind4": "^1.0.6",
"@tauri-apps/cli": "^2.5.0", "@tauri-apps/cli": "^2.5.0",
"@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "^3.5.17", "@vue/compiler-sfc": "^3.5.17",
"drizzle-kit": "^0.31.2", "drizzle-kit": "^0.31.2",
"globals": "^16.2.0", "globals": "^16.2.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"typescript": "^5.8.3" "tw-animate-css": "^1.3.8",
}, "typescript": "^5.8.3",
"packageManager": "pnpm@10.12.2", "vite": "7.1.3",
"pnpm": { "vue-tsc": "3.0.6"
"ignoredBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"vue-demi"
]
}, },
"prettier": { "prettier": {
"trailingComma": "all", "trailingComma": "all",
@ -74,5 +65,11 @@
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true,
"singleAttributePerLine": true "singleAttributePerLine": true
} },
"pnpm": {
"overrides": {
"zod": "^3.22.4"
}
},
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67"
} }

7492
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,7 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

363
src-tauri/Cargo.lock generated
View File

@ -117,17 +117,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "async-fs"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.4.0" version = "2.4.0"
@ -451,7 +440,7 @@ dependencies = [
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.12", "thiserror 2.0.16",
] ]
[[package]] [[package]]
@ -461,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
dependencies = [ dependencies = [
"serde", "serde",
"toml", "toml 0.8.20",
] ]
[[package]] [[package]]
@ -801,7 +790,7 @@ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -835,9 +824,9 @@ dependencies = [
[[package]] [[package]]
name = "dlopen2" name = "dlopen2"
version = "0.7.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff"
dependencies = [ dependencies = [
"dlopen2_derive", "dlopen2_derive",
"libc", "libc",
@ -910,7 +899,7 @@ dependencies = [
"cc", "cc",
"memchr", "memchr",
"rustc_version", "rustc_version",
"toml", "toml 0.8.20",
"vswhom", "vswhom",
"winreg", "winreg",
] ]
@ -1563,7 +1552,7 @@ dependencies = [
"tauri-plugin-os", "tauri-plugin-os",
"tauri-plugin-persisted-scope", "tauri-plugin-persisted-scope",
"tauri-plugin-store", "tauri-plugin-store",
"thiserror 2.0.12", "thiserror 2.0.16",
"tokio", "tokio",
"ts-rs", "ts-rs",
"uhlc", "uhlc",
@ -1733,7 +1722,7 @@ dependencies = [
"http-body", "http-body",
"hyper", "hyper",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.8",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -2122,9 +2111,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.171" version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -2316,7 +2305,7 @@ dependencies = [
"once_cell", "once_cell",
"png", "png",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.16",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -2358,9 +2347,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.29.0" version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"cfg-if", "cfg-if",
@ -2611,6 +2600,17 @@ dependencies = [
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
] ]
[[package]]
name = "objc2-security"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3126341c65c5d5728423ae95d788e1b660756486ad0592307ab87ba02d9a7268"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "objc2-ui-kit" name = "objc2-ui-kit"
version = "0.3.0" version = "0.3.0"
@ -2635,6 +2635,7 @@ dependencies = [
"objc2-app-kit", "objc2-app-kit",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
"objc2-security",
] ]
[[package]] [[package]]
@ -3127,8 +3128,8 @@ dependencies = [
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2 0.5.8",
"thiserror 2.0.12", "thiserror 2.0.16",
"tokio", "tokio",
"tracing", "tracing",
"web-time", "web-time",
@ -3148,7 +3149,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror 2.0.12", "thiserror 2.0.16",
"tinyvec", "tinyvec",
"tracing", "tracing",
"web-time", "web-time",
@ -3163,7 +3164,7 @@ dependencies = [
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"once_cell", "once_cell",
"socket2", "socket2 0.5.8",
"tracing", "tracing",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -3337,7 +3338,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [ dependencies = [
"getrandom 0.2.15", "getrandom 0.2.15",
"libredox", "libredox",
"thiserror 2.0.12", "thiserror 2.0.16",
] ]
[[package]] [[package]]
@ -3687,9 +3688,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -3717,6 +3718,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -3761,9 +3771,9 @@ dependencies = [
[[package]] [[package]]
name = "serialize-to-javascript" name = "serialize-to-javascript"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@ -3772,13 +3782,13 @@ dependencies = [
[[package]] [[package]]
name = "serialize-to-javascript-impl" name = "serialize-to-javascript-impl"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -3860,6 +3870,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "softbuffer" name = "softbuffer"
version = "0.4.6" version = "0.4.6"
@ -3916,9 +3936,9 @@ checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]] [[package]]
name = "sqlparser" name = "sqlparser"
version = "0.57.0" version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07c5f081b292a3d19637f0b32a79e28ff14a9fd23ef47bd7fce08ff5de221eca" checksum = "ec4b661c54b1e4b603b37873a18c59920e4c51ea8ea2cf527d925424dbd4437c"
dependencies = [ dependencies = [
"log", "log",
"recursive", "recursive",
@ -4090,17 +4110,18 @@ dependencies = [
"cfg-expr", "cfg-expr",
"heck 0.5.0", "heck 0.5.0",
"pkg-config", "pkg-config",
"toml", "toml 0.8.20",
"version-compare", "version-compare",
] ]
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.34.0" version = "0.34.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"block2 0.6.0",
"core-foundation 0.10.0", "core-foundation 0.10.0",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
@ -4152,12 +4173,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.6.2" version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"cookie",
"dirs", "dirs",
"dunce", "dunce",
"embed_plist", "embed_plist",
@ -4176,6 +4198,7 @@ dependencies = [
"objc2-app-kit", "objc2-app-kit",
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit",
"percent-encoding", "percent-encoding",
"plist", "plist",
"raw-window-handle", "raw-window-handle",
@ -4190,7 +4213,7 @@ dependencies = [
"tauri-runtime", "tauri-runtime",
"tauri-runtime-wry", "tauri-runtime-wry",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.16",
"tokio", "tokio",
"tray-icon", "tray-icon",
"url", "url",
@ -4203,9 +4226,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.3.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@ -4219,15 +4242,15 @@ dependencies = [
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"tauri-winres", "tauri-winres",
"toml", "toml 0.9.5",
"walkdir", "walkdir",
] ]
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"brotli", "brotli",
@ -4243,7 +4266,7 @@ dependencies = [
"sha2", "sha2",
"syn 2.0.100", "syn 2.0.100",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.16",
"time", "time",
"url", "url",
"uuid", "uuid",
@ -4252,9 +4275,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -4266,9 +4289,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@ -4277,15 +4300,15 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"toml", "toml 0.9.5",
"walkdir", "walkdir",
] ]
[[package]] [[package]]
name = "tauri-plugin-android-fs" name = "tauri-plugin-android-fs"
version = "9.5.0" version = "12.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70913be3272e29ada966e8158ffb4f1b3985041635bf4563baade6178309231a" checksum = "1387b55109ae9b8ad0521ac11f8ce827740f53c0e0ce74648d1cb2efe0fd9c09"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
"serde", "serde",
@ -4293,14 +4316,14 @@ dependencies = [
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"tauri-plugin-fs", "tauri-plugin-fs",
"thiserror 2.0.12", "thiserror 2.0.16",
] ]
[[package]] [[package]]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28" checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e"
dependencies = [ dependencies = [
"log", "log",
"raw-window-handle", "raw-window-handle",
@ -4310,15 +4333,15 @@ dependencies = [
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"tauri-plugin-fs", "tauri-plugin-fs",
"thiserror 2.0.12", "thiserror 2.0.16",
"url", "url",
] ]
[[package]] [[package]]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.0" version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dunce", "dunce",
@ -4331,16 +4354,16 @@ dependencies = [
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.16",
"toml", "toml 0.9.5",
"url", "url",
] ]
[[package]] [[package]]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.5.0" version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7" checksum = "938a3d7051c9a82b431e3a0f3468f85715b3442b3c3a3913095e9fa509e2652c"
dependencies = [ dependencies = [
"bytes", "bytes",
"cookie_store", "cookie_store",
@ -4354,7 +4377,7 @@ dependencies = [
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"tauri-plugin-fs", "tauri-plugin-fs",
"thiserror 2.0.12", "thiserror 2.0.16",
"tokio", "tokio",
"url", "url",
"urlpattern", "urlpattern",
@ -4362,28 +4385,28 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-notification" name = "tauri-plugin-notification"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe06ed89cff6d0ec06ff4f544fb961e4718348a33309f56ccb2086e77bc9116" checksum = "d2fbc86b929b5376ab84b25c060f966d146b2fbd59b6af8264027b343c82c219"
dependencies = [ dependencies = [
"log", "log",
"notify-rust", "notify-rust",
"rand 0.8.5", "rand 0.9.0",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"thiserror 2.0.12", "thiserror 2.0.16",
"time", "time",
"url", "url",
] ]
[[package]] [[package]]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321" checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5"
dependencies = [ dependencies = [
"dunce", "dunce",
"glob", "glob",
@ -4395,7 +4418,7 @@ dependencies = [
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"thiserror 2.0.12", "thiserror 2.0.16",
"url", "url",
"windows", "windows",
"zbus", "zbus",
@ -4403,9 +4426,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-os" name = "tauri-plugin-os"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05bccb4c6de4299beec5a9b070878a01bce9e2c945aa7a75bcea38bcba4c675d" checksum = "77a1c77ebf6f20417ab2a74e8c310820ba52151406d0c80fbcea7df232e3f6ba"
dependencies = [ dependencies = [
"gethostname", "gethostname",
"log", "log",
@ -4416,14 +4439,14 @@ dependencies = [
"sys-locale", "sys-locale",
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"thiserror 2.0.12", "thiserror 2.0.16",
] ]
[[package]] [[package]]
name = "tauri-plugin-persisted-scope" name = "tauri-plugin-persisted-scope"
version = "2.3.0" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7380eff2525adcf7f6b1cf3de191ccd3fdbe2a42281e4659604a26749c77bfbd" checksum = "41f1fed7dc3c24a4bdb183ce7c18490466985e5b3aaef05825bd62588c507ae2"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bincode", "bincode",
@ -4432,30 +4455,30 @@ dependencies = [
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-plugin-fs", "tauri-plugin-fs",
"thiserror 2.0.12", "thiserror 2.0.16",
] ]
[[package]] [[package]]
name = "tauri-plugin-store" name = "tauri-plugin-store"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916c609664a56c82aeaefffca9851fd072d4d41f73d63f22ee3ee451508194f" checksum = "d85dd80d60a76ee2c2fdce09e9ef30877b239c2a6bb76e6d7d03708aa5f13a19"
dependencies = [ dependencies = [
"dunce", "dunce",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-plugin", "tauri-plugin",
"thiserror 2.0.12", "thiserror 2.0.16",
"tokio", "tokio",
"tracing", "tracing",
] ]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.7.0" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@ -4464,20 +4487,23 @@ dependencies = [
"jni", "jni",
"objc2 0.6.0", "objc2 0.6.0",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit",
"raw-window-handle", "raw-window-handle",
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.16",
"url", "url",
"webkit2gtk",
"webview2-com",
"windows", "windows",
] ]
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.7.1" version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@ -4502,9 +4528,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.5.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brotli", "brotli",
@ -4530,8 +4556,8 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"swift-rs", "swift-rs",
"thiserror 2.0.12", "thiserror 2.0.16",
"toml", "toml 0.9.5",
"url", "url",
"urlpattern", "urlpattern",
"uuid", "uuid",
@ -4545,7 +4571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0"
dependencies = [ dependencies = [
"embed-resource", "embed-resource",
"toml", "toml 0.8.20",
] ]
[[package]] [[package]]
@ -4555,7 +4581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
dependencies = [ dependencies = [
"quick-xml 0.37.5", "quick-xml 0.37.5",
"thiserror 2.0.12", "thiserror 2.0.16",
"windows", "windows",
"windows-version", "windows-version",
] ]
@ -4604,11 +4630,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.12" version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [ dependencies = [
"thiserror-impl 2.0.12", "thiserror-impl 2.0.16",
] ]
[[package]] [[package]]
@ -4624,9 +4650,9 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.12" version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4691,9 +4717,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.1" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -4703,10 +4729,10 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.6.0",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -4750,11 +4776,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned 0.6.8",
"toml_datetime", "toml_datetime 0.6.8",
"toml_edit 0.22.24", "toml_edit 0.22.24",
] ]
[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"indexmap 2.8.0",
"serde",
"serde_spanned 1.0.0",
"toml_datetime 0.7.0",
"toml_parser",
"toml_writer",
"winnow 0.7.13",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.8" version = "0.6.8"
@ -4764,6 +4805,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_datetime"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.15" version = "0.19.15"
@ -4771,7 +4821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap 2.8.0", "indexmap 2.8.0",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@ -4782,7 +4832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [ dependencies = [
"indexmap 2.8.0", "indexmap 2.8.0",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@ -4794,11 +4844,26 @@ checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [ dependencies = [
"indexmap 2.8.0", "indexmap 2.8.0",
"serde", "serde",
"serde_spanned", "serde_spanned 0.6.8",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.7.4", "winnow 0.7.13",
] ]
[[package]]
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow 0.7.13",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@ -4875,7 +4940,7 @@ dependencies = [
"once_cell", "once_cell",
"png", "png",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.16",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -4891,7 +4956,7 @@ version = "11.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
dependencies = [ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.16",
"ts-rs-macros", "ts-rs-macros",
] ]
@ -5053,9 +5118,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [ dependencies = [
"getrandom 0.3.2", "getrandom 0.3.2",
"js-sys", "js-sys",
@ -5329,7 +5394,7 @@ version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
dependencies = [ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.16",
"windows", "windows",
"windows-core 0.61.0", "windows-core 0.61.0",
] ]
@ -5480,7 +5545,7 @@ checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [ dependencies = [
"windows-result", "windows-result",
"windows-strings 0.3.1", "windows-strings 0.3.1",
"windows-targets 0.53.0", "windows-targets 0.53.2",
] ]
[[package]] [[package]]
@ -5546,6 +5611,15 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.42.2" version = "0.42.2"
@ -5594,9 +5668,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.0" version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.53.0", "windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0", "windows_aarch64_msvc 0.53.0",
@ -5808,9 +5882,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.4" version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -5848,14 +5922,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.52.1" version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"block2 0.6.0", "block2 0.6.0",
"cookie", "cookie",
"crossbeam-channel", "crossbeam-channel",
"dirs",
"dpi", "dpi",
"dunce", "dunce",
"gdkx11", "gdkx11",
@ -5879,7 +5954,7 @@ dependencies = [
"sha2", "sha2",
"soup3", "soup3",
"tao-macros", "tao-macros",
"thiserror 2.0.12", "thiserror 2.0.16",
"url", "url",
"webkit2gtk", "webkit2gtk",
"webkit2gtk-sys", "webkit2gtk-sys",
@ -5911,16 +5986,6 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "xdg-home"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.5" version = "0.7.5"
@ -5947,13 +6012,12 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.5.0" version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236" checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
"async-fs",
"async-io", "async-io",
"async-lock", "async-lock",
"async-process", "async-process",
@ -5970,13 +6034,11 @@ dependencies = [
"ordered-stream", "ordered-stream",
"serde", "serde",
"serde_repr", "serde_repr",
"static_assertions",
"tokio", "tokio",
"tracing", "tracing",
"uds_windows", "uds_windows",
"windows-sys 0.59.0", "windows-sys 0.60.2",
"winnow 0.7.4", "winnow 0.7.13",
"xdg-home",
"zbus_macros", "zbus_macros",
"zbus_names", "zbus_names",
"zvariant", "zvariant",
@ -5984,9 +6046,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.5.0" version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0" checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca"
dependencies = [ dependencies = [
"proc-macro-crate 3.3.0", "proc-macro-crate 3.3.0",
"proc-macro2", "proc-macro2",
@ -6005,7 +6067,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"winnow 0.7.4", "winnow 0.7.13",
"zvariant", "zvariant",
] ]
@ -6080,25 +6142,24 @@ dependencies = [
[[package]] [[package]]
name = "zvariant" name = "zvariant"
version = "5.4.0" version = "5.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac" checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db"
dependencies = [ dependencies = [
"endi", "endi",
"enumflags2", "enumflags2",
"serde", "serde",
"static_assertions",
"url", "url",
"winnow 0.7.4", "winnow 0.7.13",
"zvariant_derive", "zvariant_derive",
"zvariant_utils", "zvariant_utils",
] ]
[[package]] [[package]]
name = "zvariant_derive" name = "zvariant_derive"
version = "5.4.0" version = "5.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f" checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e"
dependencies = [ dependencies = [
"proc-macro-crate 3.3.0", "proc-macro-crate 3.3.0",
"proc-macro2", "proc-macro2",
@ -6118,5 +6179,5 @@ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"syn 2.0.100", "syn 2.0.100",
"winnow 0.7.4", "winnow 0.7.13",
] ]

View File

@ -25,28 +25,28 @@ rusqlite = { version = "0.37.0", features = [
] } ] }
#libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] } #libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] }
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] } #sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
tokio = { version = "1.46", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
hex = "0.4" hex = "0.4"
serde_json = "1" serde_json = "1.0.143"
base64 = "0.22" base64 = "0.22"
mime_guess = "2.0" mime_guess = "2.0"
mime = "0.3" mime = "0.3"
fs_extra = "1.3.0" fs_extra = "1.3.0"
sqlparser = { version = "0.57.0", features = ["visitor"] } sqlparser = { version = "0.58.0", features = ["visitor"] }
uhlc = "0.8" uhlc = "0.8"
tauri = { version = "2.6.2", features = ["protocol-asset", "devtools"] } tauri = { version = "2.8.5", features = ["protocol-asset", "devtools"] }
tauri-plugin-dialog = "2.3" tauri-plugin-dialog = "2.4.0"
tauri-plugin-fs = "2.4.0" tauri-plugin-fs = "2.4.0"
tauri-plugin-opener = "2.4.0" tauri-plugin-opener = "2.5.0"
tauri-plugin-os = "2.3" tauri-plugin-os = "2.3"
tauri-plugin-store = "2.3" tauri-plugin-store = "2.4.0"
tauri-plugin-http = "2.5" tauri-plugin-http = "2.5.2"
tauri-plugin-notification = "2.3" tauri-plugin-notification = "2.3.1"
tauri-plugin-persisted-scope = "2.0.0" tauri-plugin-persisted-scope = "2.3.2"
tauri-plugin-android-fs = "9.5.0" tauri-plugin-android-fs = "12.0.1"
uuid = { version = "1.17.0", features = ["v4"] } uuid = { version = "1.18.1", features = ["v4"] }
ts-rs = "11.0.1" ts-rs = "11.0.1"
thiserror = "2.0.12" thiserror = "2.0.16"
#tauri-plugin-sql = { version = "2", features = ["sqlite"] } #tauri-plugin-sql = { version = "2", features = ["sqlite"] }

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>crsqlite.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>crsqlite.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>crsqlite</string>
<key>CFBundleIdentifier</key>
<string>io.vlcn.crsqlite</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
</dict>
</plist>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>crsqlite</string>
<key>CFBundleIdentifier</key>
<string>io.vlcn.crsqlite</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
</dict>
</plist>

View File

@ -1,23 +1,23 @@
import { drizzle } from "drizzle-orm/sqlite-proxy"; // Adapter für Query Building ohne direkte Verbindung import { drizzle } from 'drizzle-orm/sqlite-proxy' // Adapter für Query Building ohne direkte Verbindung
import * as schema from "./schemas/vault"; // Importiere alles aus deiner Schema-Datei import * as schema from './schemas/vault' // Importiere alles aus deiner Schema-Datei
// sqlite-proxy benötigt eine (dummy) Ausführungsfunktion als Argument. // sqlite-proxy benötigt eine (dummy) Ausführungsfunktion als Argument.
// Diese wird in unserem Tauri-Workflow nie aufgerufen, da wir nur .toSQL() verwenden. // Diese wird in unserem Tauri-Workflow nie aufgerufen, da wir nur .toSQL() verwenden.
// Sie muss aber vorhanden sein, um drizzle() aufrufen zu können. // Sie muss aber vorhanden sein, um drizzle() aufrufen zu können.
const dummyExecutor = async ( const dummyExecutor = async (
sql: string, sql: string,
params: any[], params: unknown[],
method: "all" | "run" | "get" | "values" method: 'all' | 'run' | 'get' | 'values',
) => { ) => {
console.warn( console.warn(
`Frontend Drizzle Executor wurde aufgerufen (Methode: ${method}). Das sollte im Tauri-Invoke-Workflow nicht passieren!` `Frontend Drizzle Executor wurde aufgerufen (Methode: ${method}). Das sollte im Tauri-Invoke-Workflow nicht passieren!`,
); )
// Wir geben leere Ergebnisse zurück, um die Typen zufriedenzustellen, falls es doch aufgerufen wird. // Wir geben leere Ergebnisse zurück, um die Typen zufriedenzustellen, falls es doch aufgerufen wird.
return { rows: [] }; // Für 'run' (z.B. bei INSERT/UPDATE) return { rows: [] } // Für 'run' (z.B. bei INSERT/UPDATE)
}; }
// Erstelle die Drizzle-Instanz für den SQLite-Dialekt // Erstelle die Drizzle-Instanz für den SQLite-Dialekt
// Übergib den dummyExecutor und das importierte Schema // Übergib den dummyExecutor und das importierte Schema
export const db = drizzle(dummyExecutor, { schema }); export const db = drizzle(dummyExecutor, { schema })
// Exportiere auch alle Schema-Definitionen weiter, damit man alles aus einer Datei importieren kann // Exportiere auch alle Schema-Definitionen weiter, damit man alles aus einer Datei importieren kann

View File

@ -1,4 +1,4 @@
import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
export const haexCrdtLogs = sqliteTable('haex_crdt_logs', { export const haexCrdtLogs = sqliteTable('haex_crdt_logs', {
hlc_timestamp: text().primaryKey(), hlc_timestamp: text().primaryKey(),

View File

@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
} }
android { android {
compileSdk = 34 compileSdk = 36
namespace = "space.haex.hub" namespace = "space.haex.hub"
defaultConfig { defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false" manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "space.haex.hub" applicationId = "space.haex.hub"
minSdk = 30 minSdk = 24
targetSdk = 34 targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
} }
@ -58,9 +58,10 @@ rust {
} }
dependencies { dependencies {
implementation("androidx.webkit:webkit:1.6.1") implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.8.0") implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4") androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- AndroidTV support --> <!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />
@ -11,8 +9,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.haex_hub" android:theme="@style/Theme.haex_hub"
android:usesCleartextTraffic="${usesCleartextTraffic}" android:usesCleartextTraffic="${usesCleartextTraffic}">
android:requestLegacyExternalStorage="true">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:launchMode="singleTask" android:launchMode="singleTask"
@ -28,13 +25,13 @@
</activity> </activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>

View File

@ -1,20 +1,11 @@
package space.haex.hub package space.haex.hub
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.content.Context import androidx.activity.enableEdgeToEdge
import android.content.Intent
import android.net.Uri
import android.provider.Settings
class MainActivity : TauriActivity(){ class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
/* override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
startActivity( }
Intent(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:${BuildConfig.APPLICATION_ID}")
)
)
} */
} }

View File

@ -1,17 +0,0 @@
package space.haex.hub
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
class SettingsOpener {
companion object {
@JvmStatic
fun openManageAllFilesAccessSettings(context: Context) {
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:" + context.packageName)
context.startActivity(intent)
}
}
}

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.5.1") classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
} }
} }

View File

@ -18,6 +18,6 @@ repositories {
dependencies { dependencies {
compileOnly(gradleApi()) compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.5.1") implementation("com.android.tools.build:gradle:8.11.0")
} }

View File

@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022 #Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-webview-show","core:webview:default","core:window:allow-create","core:window:allow-get-all-windows","core:window:allow-show","core:window:default","dialog:default","fs:allow-appconfig-read-recursive","fs:allow-appconfig-write-recursive","fs:allow-appdata-read-recursive","fs:allow-appdata-write-recursive","fs:allow-read-file","fs:allow-read-dir","fs:allow-resource-read-recursive","fs:allow-resource-write-recursive","fs:allow-download-read-recursive","fs:allow-download-write-recursive","fs:default","android-fs:default",{"identifier":"fs:scope","allow":[{"path":"**"}]},"http:allow-fetch-send","http:allow-fetch","http:default","notification:allow-create-channel","notification:allow-list-channels","notification:allow-notify","notification:default","opener:allow-open-url","opener:default","os:allow-hostname","os:default","store:default"]}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
use crdt::trigger::TriggerManager;
use rusqlite::{Connection, Result};
// anpassen an dein Crate-Modul
fn main() -> Result<()> {
// Vault-Datenbank öffnen
let conn = Connection::open("vault.db")?;
println!("🔄 Setup CRDT triggers...");
// Tabellen aus der DB holen
let mut stmt =
conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'haex_%' AND NOT LIKE 'haex_crdt_%';")?;
let table_iter = stmt.query_map([], |row| row.get::<_, String>(0))?;
for table_name in table_iter {
let table_name = table_name?;
println!("➡️ Processing table: {}", table_name);
// Trigger für die Tabelle neu anlegen
match TriggerManager::setup_triggers_for_table(&conn, &table_name) {
Ok(_) => println!(" ✅ Triggers created for {}", table_name),
Err(e) => println!(
" ⚠️ Could not create triggers for {}: {:?}",
table_name, e
),
}
}
println!("✨ Done setting up CRDT triggers.");
Ok(())
}

View File

@ -195,7 +195,7 @@ impl SqlProxy {
} */ } */
Statement::Update { Statement::Update {
table, table,
assignments: assignments, assignments,
from, from,
selection, selection,
returning, returning,

View File

@ -1,4 +1,3 @@
use crate::crdt::hlc;
use rusqlite::{Connection, Result, Row}; use rusqlite::{Connection, Result, Row};
use serde::Serialize; use serde::Serialize;
use std::fmt::Write; use std::fmt::Write;
@ -76,6 +75,7 @@ impl TriggerManager {
let insert_trigger_sql = self.generate_insert_trigger_sql(table_name, &pks, &cols_to_track); let insert_trigger_sql = self.generate_insert_trigger_sql(table_name, &pks, &cols_to_track);
let update_trigger_sql = self.generate_update_trigger_sql(table_name, &pks, &cols_to_track); let update_trigger_sql = self.generate_update_trigger_sql(table_name, &pks, &cols_to_track);
let drop_insert_trigger_sql = self.drop_trigger_sql(table_name, "insert");
let tx = conn.transaction()?; let tx = conn.transaction()?;
tx.execute_batch(&format!("{}\n{}", insert_trigger_sql, update_trigger_sql))?; tx.execute_batch(&format!("{}\n{}", insert_trigger_sql, update_trigger_sql))?;
@ -127,6 +127,10 @@ impl TriggerManager {
) )
} }
fn drop_trigger_sql(&self, table: &str, action: &str) -> String {
format!("DROP TRIGGER IF EXISTS z_crdt_{table}_{action};")
}
fn generate_update_trigger_sql( fn generate_update_trigger_sql(
&self, &self,
table_name: &str, table_name: &str,

View File

@ -1,6 +1,6 @@
//mod browser; //mod browser;
mod android_storage; mod android_storage;
mod crdt; pub mod crdt;
mod database; mod database;
mod extension; mod extension;
mod models; mod models;

View File

8
src/app.config.ts Normal file
View File

@ -0,0 +1,8 @@
export default defineAppConfig({
ui: {
colors: {
primary: 'sky',
secondary: 'purple',
},
},
})

View File

@ -1,20 +1,14 @@
<template> <template>
<div> <UApp :locale="locales[locale]">
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage />
<NuxtSnackbar />
</NuxtLayout> </NuxtLayout>
</div> </UApp>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { currentThemeValue } = storeToRefs(useUiStore()) import * as locales from '@nuxt/ui/locale'
const { locale } = useI18n()
useHead({
htmlAttrs: {
'data-theme': currentThemeValue,
},
})
</script> </script>
<style> <style>

20
src/assets/css/main.css Normal file
View File

@ -0,0 +1,20 @@
@import 'tailwindcss';
@import '@nuxt/ui';
@import 'tw-animate-css';
/* Custom Colors */
@layer base {
button,
[role='button'] {
@apply cursor-pointer;
}
:disabled,
[disabled] {
@apply cursor-not-allowed;
}
}
:root {
--ui-header-height: 48px; /* oder was auch immer deine Header-Höhe ist */
}

View File

@ -1,11 +0,0 @@
@import 'tailwindcss';
@import 'flyonui/variants.css';
@import 'tailwindcss-intersect';
@plugin "tailwindcss-motion";
@plugin "@iconify/tailwind4";
@plugin "flyonui" {
themes: all;
}
@source "../../../node_modules/flyonui/flyonui.js";

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,156 +0,0 @@
<template>
<div class="browser">
<div class="browser-controls">
<button :disabled="!activeTabId" @click="$emit('goBack', activeTabId)">
</button>
<button :disabled="!activeTabId" @click="$emit('goForward', activeTabId)">
</button>
<button @click="$emit('createTab')">+</button>
<HaexBrowserUrlBar
:url="activeTab?.url || ''"
:is-loading="activeTab?.isLoading || false"
@submit="handleUrlSubmit"
/>
</div>
<HaexBrowserTabBar
:tabs="tabs"
:active-tab-id="activeTabId"
@close-tab="$emit('closeTab', $event)"
@activate-tab="$emit('activateTab', $event)"
/>
<div ref="contentRef" class="browser-content">
<!-- Die eigentlichen Webview-Inhalte werden von Tauri verwaltet -->
<div v-if="!activeTabId" class="empty-state">
<p>
Kein Tab geöffnet. Erstellen Sie einen neuen Tab mit dem + Button.
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Webview } from '@tauri-apps/api/webview'
import { Window } from '@tauri-apps/api/window'
/* const appWindow = new Window('uniqueLabel');
const webview = new Webview(appWindow, 'theUniqueLabel', {
url: 'https://www.google.de',
x: 0,
y: 0,
height: 1000,
width: 1000,
});
webview.once('tauri://created', function () {
console.log('create new webview');
}); */
interface Tab {
id: string
title: string
url: string
isLoading: boolean
isActive: boolean
window_label: string
}
interface Props {
tabs: Tab[]
activeTabId: string | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'createTab'): void
(e: 'closeTab', tabId: string): void
(e: 'navigate', tabId: string, url: string): void
(e: 'goBack', tabId: string | null): void
(e: 'goForward', tabId: string | null): void
(e: 'activateTab', tabId: string | null): void
}>()
const { initializeAsync, processNavigation, injectContentScripts } =
useBrowserExtensionStore()
const contentRef = ref<HTMLDivElement | null>(null)
//const extensionManager = ref<ExtensionManager>(new ExtensionManager());
const activeTab = computed(() =>
props.tabs?.find((tab) => tab.id === props.activeTabId)
)
onMounted(async () => {
// Initialisiere das Erweiterungssystem
await initializeAsync()
// Aktualisiere die Webview-Größe
await updateWebviewBoundsAsync()
//window.addEventListener('resize', updateWebviewBounds);
})
// Wenn ein neuer Tab aktiviert wird, injiziere Content-Scripts
/* watch(
() => props.activeTabId,
async (newTabId) => {
if (newTabId && props.tabs.length > 0) {
const activeTab = props.tabs.find((tab) => tab.id === newTabId);
if (activeTab) {
// Warte kurz, bis die Seite geladen ist
setTimeout(() => {
injectContentScripts(activeTab.window_label);
}, 500);
// Aktualisiere die Webview-Größe
updateWebviewBounds();
}
}
}
); */
const createNewTabAsync = async () => {
const appWindow = new Window(crypto.randomUUID())
appWindow.setAlwaysOnTop(true)
appWindow.setDecorations(false)
const webview = new Webview(appWindow, 'theUniqueLabel', {
url: 'https://www.google.de',
x: 0,
y: 0,
height: 1000,
width: 1000,
})
}
const handleUrlSubmit = (url: string) => {
createNewTabAsync()
if (props.activeTabId) {
// Prüfe URL mit Erweiterungen vor der Navigation
/* if (processNavigation(url)) {
//emit('navigate', props.activeTabId, url);
} else {
console.log('Navigation blockiert durch Erweiterung')
// Hier könnten Sie eine Benachrichtigung anzeigen
} */
}
}
const updateWebviewBoundsAsync = async () => {
if (!contentRef.value) return
const rect = contentRef.value.getBoundingClientRect()
const bounds = {
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
}
/* await invoke('update_window_bounds', {
contentBounds: { x: bounds.x, y: bounds.y },
contentSize: { width: bounds.width, height: bounds.height },
}); */
}
</script>

View File

@ -1,40 +0,0 @@
<template>
<div class="tab-bar">
<div
v-for="tab in tabs"
:key="tab.id"
class="tab"
:class="{ active: tab.id === activeTabId }"
@click="$emit('activateTab', tab.id)"
>
<span class="tab-title">
{{ tab.title || 'Neuer Tab' }}
</span>
<button class="tab-close" @click.stop="$emit('closeTab', tab.id)">
×
</button>
</div>
</div>
</template>
<script setup lang="ts">
interface Tab {
id: string
title: string
url: string
isLoading: boolean
isActive: boolean
}
interface Props {
tabs: Tab[]
activeTabId: string | null
}
defineProps<Props>()
defineEmits<{
(e: 'closeTab', tabId: string): void
(e: 'activateTab', tabId: string): void
}>()
</script>

View File

@ -1,41 +0,0 @@
<template>
<form class="url-bar" @submit.prevent="handleSubmit">
<input v-model="inputValue" type="text" placeholder="URL eingeben" >
<span v-if="isLoading" class="loading-indicator">Laden...</span>
<button v-else type="submit">Go</button>
</form>
</template>
<script setup lang="ts">
const props = defineProps({
url: {
type: String,
default: '',
},
isLoading: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['submit'])
const inputValue = ref(props.url)
watch(
() => props.url,
(newUrl) => {
inputValue.value = newUrl
}
)
const handleSubmit = () => {
// URL validieren und ggf. Protokoll hinzufügen
let processedUrl = inputValue.value.trim()
if (processedUrl && !processedUrl.match(/^[a-zA-Z]+:\/\//)) {
processedUrl = 'https://' + processedUrl
}
emit('submit', processedUrl)
}
</script>

View File

@ -1,14 +0,0 @@
<template>
<button class="btn join-item" :type>
<slot />
</button>
</template>
<script setup lang="ts">
defineProps({
type: {
type: String as PropType<"reset" | "submit" | "button">,
default: "button",
},
});
</script>

View File

@ -1,16 +0,0 @@
<template>
<button
:class="cn(
`relative flex items-center justify-center min-w-28 min-h-10 overflow-hidden outline-2 outline-offset-2 rounded cursor-pointer`, className
)
">
<span class="btn-content inline-flex size-full items-center justify-center px-4 py-2 gap-2">
<slot />
</span>
</button>
</template>
<script setup lang="ts">
import { cn } from "@/lib/utils";
const { className = "primary" } = defineProps<{ className?: string }>()
</script>

View File

@ -1,7 +1,8 @@
<template> <template>
<div <div
class="card border-4 shadow-md shadow-accent h-48 w-48 overflow-hidden hover:shadow-xl transition-shadow " class="card border-4 shadow-md shadow-accent h-48 w-48 overflow-hidden hover:shadow-xl transition-shadow"
v-bind="$attrs"> v-bind="$attrs"
>
<div class="absolute top-2 right-2"> <div class="absolute top-2 right-2">
<UiDropdown class="btn btn-sm btn-text btn-circle"> <UiDropdown class="btn btn-sm btn-text btn-circle">
<template #activator> <template #activator>
@ -9,27 +10,44 @@ class="card border-4 shadow-md shadow-accent h-48 w-48 overflow-hidden hover:s
</template> </template>
<template #items> <template #items>
<UiButton class="btn-error btn-outline btn-sm " @click="showRemoveDialog = true"> <UiButton
<Icon name="mdi:trash" /> {{ t("remove") }} class="btn-error btn-outline btn-sm"
@click="showRemoveDialog = true"
>
<Icon name="mdi:trash" /> {{ t('remove') }}
</UiButton> </UiButton>
</template> </template>
</UiDropdown> </UiDropdown>
</div> </div>
<div class="card-header"> <div class="card-header">
<h5 v-if="name" class="card-title"> <h5
v-if="name"
class="card-title"
>
{{ name }} {{ name }}
</h5> </h5>
</div> </div>
<div <div
class="card-body relative cursor-pointer" class="card-body relative cursor-pointer"
@click="navigateTo(useLocalePath()({ name: 'haexExtension', params: { extensionId: id } }))"> @click="
navigateTo(
useLocalePath()({
name: 'haexExtension',
params: { extensionId: id },
}),
)
"
>
<!-- <slot /> <!-- <slot />
<div class="card-actions" v-if="$slots.action"> <div class="card-actions" v-if="$slots.action">
<slot name="action" /> <slot name="action" />
</div> --> </div> -->
<div class="size-20 absolute bottom-2 right-2" v-html="icon" /> <div
class="size-20 absolute bottom-2 right-2"
v-html="icon"
/>
</div> </div>
<!-- <div class="card-footer"> <!-- <div class="card-footer">
@ -37,63 +55,69 @@ class="card-body relative cursor-pointer"
</div> --> </div> -->
</div> </div>
<HaexExtensionDialogRemove v-model:open="showRemoveDialog" :extension @confirm="removeExtensionAsync" /> <HaexExtensionDialogRemove
v-model:open="showRemoveDialog"
:extension
@confirm="removeExtensionAsync"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { IHaexHubExtension } from "~/types/haexhub"; import type { IHaexHubExtension } from '~/types/haexhub'
const emit = defineEmits(["close", "submit", "remove"]); const emit = defineEmits(['close', 'submit', 'remove'])
const extension = defineProps<IHaexHubExtension>(); const extension = defineProps<IHaexHubExtension>()
const { escape, enter } = useMagicKeys(); const { escape, enter } = useMagicKeys()
watchEffect(async () => { watchEffect(async () => {
if (escape.value) { if (escape?.value) {
await nextTick(); await nextTick()
emit("close"); emit('close')
} }
}); })
watchEffect(async () => { watchEffect(async () => {
if (enter.value) { if (enter?.value) {
await nextTick(); await nextTick()
emit("submit"); emit('submit')
} }
}); })
const showRemoveDialog = ref(false) const showRemoveDialog = ref(false)
const { add } = useSnackbar() const { add } = useToast()
const { t } = useI18n() const { t } = useI18n()
const extensionStore = useExtensionsStore() const extensionStore = useExtensionsStore()
const removeExtensionAsync = async () => { const removeExtensionAsync = async () => {
if (!extension?.id || !extension?.version) { if (!extension?.id || !extension?.version) {
add({ type: 'error', text: 'Erweiterung kann nicht gelöscht werden' }) add({
color: 'error',
description: 'Erweiterung kann nicht gelöscht werden',
})
return return
} }
try { try {
await extensionStore.removeExtensionAsync( await extensionStore.removeExtensionAsync(extension.id, extension.version)
extension.id,
extension.version
)
await extensionStore.loadExtensionsAsync() await extensionStore.loadExtensionsAsync()
add({ add({
type: 'success', color: 'success',
title: t('extension.remove.success.title', { title: t('extension.remove.success.title', {
extensionName: extension.name, extensionName: extension.name,
}), }),
text: t('extension.remove.success.text', { description: t('extension.remove.success.text', {
extensionName: extension.name, extensionName: extension.name,
}), }),
}) })
} catch (error) { } catch (error) {
add({ add({
type: 'error', color: 'error',
title: t('extension.remove.error.title'), title: t('extension.remove.error.title'),
text: t('extension.remove.error.text', { error: JSON.stringify(error) }), description: t('extension.remove.error.text', {
error: JSON.stringify(error),
}),
}) })
} }
} }
@ -111,7 +135,6 @@ de:
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}" text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
title: 'Fehler beim Entfernen von {extensionName}' title: 'Fehler beim Entfernen von {extensionName}'
en: en:
remove: Remove remove: Remove
extension: extension:
@ -122,6 +145,4 @@ en:
error: error:
text: "Extension {extensionName} couldn't be removed. \n {error}" text: "Extension {extensionName} couldn't be removed. \n {error}"
title: 'Exception during uninstall {extensionName}' title: 'Exception during uninstall {extensionName}'
</i18n> </i18n>

View File

@ -1,7 +1,14 @@
<template> <template>
<UiDialogConfirm v-model:open="open" @abort="onDeny" @confirm="onConfirm"> <UiDialogConfirm
v-model:open="open"
@abort="onDeny"
@confirm="onConfirm"
>
<template #title> <template #title>
<i18n-t keypath="question" tag="p"> <i18n-t
keypath="question"
tag="p"
>
<template #extension> <template #extension>
<span class="font-bold text-primary">{{ manifest?.name }}</span> <span class="font-bold text-primary">{{ manifest?.name }}</span>
</template> </template>
@ -9,94 +16,138 @@
</template> </template>
<div class="flex flex-col"> <div class="flex flex-col">
<nav class="tabs tabs-bordered" aria-label="Tabs" role="tablist" aria-orientation="horizontal"> <nav
class="tabs tabs-bordered"
aria-label="Tabs"
role="tablist"
aria-orientation="horizontal"
>
<button <button
v-show="manifest?.permissions?.database" id="tabs-basic-item-1" type="button" v-show="manifest?.permissions?.database"
class="tab active-tab:tab-active active" data-tab="#tabs-basic-1" aria-controls="tabs-basic-1" role="tab" aria-selected="true"> id="tabs-basic-item-1"
{{ t("database") }} type="button"
class="tab active-tab:tab-active active"
data-tab="#tabs-basic-1"
aria-controls="tabs-basic-1"
role="tab"
aria-selected="true"
>
{{ t('database') }}
</button> </button>
<button <button
v-show="manifest?.permissions?.filesystem" id="tabs-basic-item-2" type="button" v-show="manifest?.permissions?.filesystem"
class="tab active-tab:tab-active" data-tab="#tabs-basic-2" aria-controls="tabs-basic-2" role="tab" aria-selected="false"> id="tabs-basic-item-2"
{{ t("filesystem") }} type="button"
class="tab active-tab:tab-active"
data-tab="#tabs-basic-2"
aria-controls="tabs-basic-2"
role="tab"
aria-selected="false"
>
{{ t('filesystem') }}
</button> </button>
<button <button
v-show="manifest?.permissions?.http" id="tabs-basic-item-3" type="button" v-show="manifest?.permissions?.http"
class="tab active-tab:tab-active" data-tab="#tabs-basic-3" aria-controls="tabs-basic-3" role="tab" aria-selected="false"> id="tabs-basic-item-3"
{{ t("http") }} type="button"
class="tab active-tab:tab-active"
data-tab="#tabs-basic-3"
aria-controls="tabs-basic-3"
role="tab"
aria-selected="false"
>
{{ t('http') }}
</button> </button>
</nav> </nav>
<div class="mt-3 min-h-40"> <div class="mt-3 min-h-40">
<div id="tabs-basic-1" role="tabpanel" aria-labelledby="tabs-basic-item-1"> <div
<HaexExtensionManifestPermissionsDatabase :database="permissions?.database" /> id="tabs-basic-1"
role="tabpanel"
aria-labelledby="tabs-basic-item-1"
>
<HaexExtensionManifestPermissionsDatabase
:database="permissions?.database"
/>
</div> </div>
<div id="tabs-basic-2" class="hidden" role="tabpanel" aria-labelledby="tabs-basic-item-2"> <div
<HaexExtensionManifestPermissionsFilesystem :filesystem="permissions?.filesystem" /> id="tabs-basic-2"
class="hidden"
role="tabpanel"
aria-labelledby="tabs-basic-item-2"
>
<HaexExtensionManifestPermissionsFilesystem
:filesystem="permissions?.filesystem"
/>
</div> </div>
<div id="tabs-basic-3" class="hidden" role="tabpanel" aria-labelledby="tabs-basic-item-3"> <div
id="tabs-basic-3"
class="hidden"
role="tabpanel"
aria-labelledby="tabs-basic-item-3"
>
<HaexExtensionManifestPermissionsHttp :http="permissions?.http" /> <HaexExtensionManifestPermissionsHttp :http="permissions?.http" />
</div> </div>
</div> </div>
</div> </div>
</UiDialogConfirm> </UiDialogConfirm>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { IHaexHubExtensionManifest } from "~/types/haexhub"; import type { IHaexHubExtensionManifest } from '~/types/haexhub'
const { t } = useI18n(); const { t } = useI18n()
const open = defineModel<boolean>("open", { default: false }); const open = defineModel<boolean>('open', { default: false })
const { manifest } = defineProps<{ manifest?: IHaexHubExtensionManifest | null }>(); const { manifest } = defineProps<{
manifest?: IHaexHubExtensionManifest | null
}>()
const permissions = computed(() => ({ const permissions = computed(() => ({
database: { database: {
read: manifest?.permissions.database?.read?.map(read => ({ read: manifest?.permissions.database?.read?.map((read) => ({
[read]: true [read]: true,
})), })),
write: manifest?.permissions.database?.read?.map(write => ({ write: manifest?.permissions.database?.read?.map((write) => ({
[write]: true [write]: true,
})), })),
create: manifest?.permissions.database?.read?.map(create => ({ create: manifest?.permissions.database?.read?.map((create) => ({
[create]: true [create]: true,
})), })),
}, },
filesystem: { filesystem: {
read: manifest?.permissions.filesystem?.read?.map(read => ({ read: manifest?.permissions.filesystem?.read?.map((read) => ({
[read]: true [read]: true,
})), })),
write: manifest?.permissions.filesystem?.write?.map(write => ({ write: manifest?.permissions.filesystem?.write?.map((write) => ({
[write]: true [write]: true,
})), })),
}, },
http: manifest?.permissions.http?.map(http => ({ http: manifest?.permissions.http?.map((http) => ({
[http]: true [http]: true,
})), })),
})) }))
watch(permissions, () => console.log("permissions", permissions.value)) watch(permissions, () => console.log('permissions', permissions.value))
const emit = defineEmits(["deny", "confirm"]); const emit = defineEmits(['deny', 'confirm'])
const onDeny = () => { const onDeny = () => {
open.value = false; open.value = false
console.log("onDeny open", open.value); console.log('onDeny open', open.value)
emit("deny"); emit('deny')
}; }
const onConfirm = () => { const onConfirm = () => {
open.value = false; open.value = false
console.log("onConfirm open", open.value); console.log('onConfirm open', open.value)
emit("confirm"); emit('confirm')
}; }
</script> </script>
<i18n lang="json">{ <i18n lang="json">
{
"de": { "de": {
"title": "Erweiterung hinzufügen", "title": "Erweiterung hinzufügen",
"question": "Erweiterung {extension} hinzufügen?", "question": "Erweiterung {extension} hinzufügen?",
@ -115,4 +166,5 @@ const onConfirm = () => {
"http": "Internet", "http": "Internet",
"filesystem": "Filesystem" "filesystem": "Filesystem"
} }
}</i18n> }
</i18n>

View File

@ -0,0 +1,56 @@
<template>
<UPopover v-model:open="open">
<UButton
icon="material-symbols:apps"
color="neutral"
variant="outline"
/>
<template #content>
<ul
class="p-4 max-h-96 grid grid-cols-3 gap-2 overflow-scroll"
@click="open = false"
>
<UiButton
v-for="item in menu"
:key="item.id"
square
size="xl"
variant="ghost"
:ui="{
base: 'size-24 flex flex-wrap text-sm items-center justify-center overflow-visible',
leadingIcon: 'size-10',
label: 'w-full',
}"
:icon="item.icon"
:label="item.name"
:tooltip="item.name"
@click="item.onSelect"
/>
<!-- <UiButton
v-for="item in extensionLinks"
:key="item.id"
v-bind="item"
icon-type="svg"
/> -->
</ul>
</template>
</UPopover>
</template>
<script setup lang="ts">
//const { extensionLinks } = storeToRefs(useExtensionsStore())
const { menu } = storeToRefs(useSidebarStore())
const open = ref(false)
</script>
<i18n lang="yaml">
de:
settings: 'Einstellungen'
close: 'Vault schließen'
en:
settings: 'Settings'
close: 'Close Vault'
</i18n>

View File

@ -1,63 +0,0 @@
<template>
<UiDropdown offset="[--offset:20]">
<template #activator>
<div
class="size-9.5 rounded-full items-center justify-center text-base-content text-base"
>
<Icon
name="mdi:format-list-bulleted"
class="size-full p-2"
/>
</div>
</template>
<!-- <ul class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60" role="menu" aria-orientation="vertical"
aria-labelledby="dropdown-avatar"> -->
<template #items>
<li>
<NuxtLinkLocale
class="dropdown-item"
:to="{ name: 'settings' }"
>
<span class="icon-[tabler--settings]" />
{{ t('settings') }}
</NuxtLinkLocale>
</li>
<li class="dropdown-footer gap-2">
<button
class="btn btn-error btn-soft btn-block"
@click="onVaultCloseAsync"
>
<span class="icon-[tabler--logout]" />
{{ t('vault.close') }}
</button>
</li>
</template>
<!--
</ul> -->
</UiDropdown>
</template>
<script setup lang="ts">
const { t } = useI18n()
const { closeAsync } = useVaultStore()
const onVaultCloseAsync = async () => {
await closeAsync()
await navigateTo(useLocalePath()({ name: 'vaultOpen' }))
}
</script>
<i18n lang="yaml">
de:
settings: 'Einstellungen'
vault:
close: 'Vault schließen'
en:
settings: 'Settings'
vault:
close: 'Close Vault'
</i18n>

View File

@ -1,90 +0,0 @@
<template>
<div
class="dropdown relative inline-flex [--auto-close:inside] [--offset:18] [--placement:bottom]"
>
<UiTooltip :tooltip="t('notifications.label')">
<button
id="dropdown-scrollable"
type="button"
class="dropdown-toggle btn btn-text btn-circle dropdown-open:bg-base-content/10"
aria-haspopup="menu"
aria-expanded="false"
aria-label="Dropdown"
>
<div class="indicator">
<span
v-show="notifications.length"
class="indicator-item bg-error size-2 rounded-full text-sm"
/>
<span class="icon-[tabler--bell] text-base-content size-[1.375rem]" />
</div>
</button>
</UiTooltip>
<div
class="dropdown-menu dropdown-open:opacity-100 hidden w-full max-w-96 shadow"
role="menu"
aria-orientation="vertical"
aria-labelledby="dropdown-scrollable"
>
<div class="dropdown-header justify-center">
<h6 class="text-base-content text-base">
{{ t('notifications.label') }}
</h6>
</div>
<div
class="vertical-scrollbar horizontal-scrollbar rounded-scrollbar text-base-content/80 max-h-56 overflow-auto"
>
<div
v-for="notification in notifications"
:key="notification.id"
class="dropdown-item"
>
<div class="avatar">
<div class="w-10 rounded-full">
<img
v-if="notification.image"
:src="notification.image"
:alt="notification.alt ?? 'notification avatar'"
/>
<Icon
v-else-if="notification.icon"
:name="notification.icon"
/>
</div>
</div>
<div class="w-60">
<h6 class="truncate text-base">
{{ notification.title }}
</h6>
<small class="text-base-content/50 truncate">
{{ notification.text }}
</small>
</div>
</div>
</div>
<NuxtLinkLocale
:to="{ name: 'notifications' }"
class="dropdown-footer justify-center gap-1 hover:bg-base-content/10"
>
<span class="icon-[tabler--eye] size-4" />
{{ t('notifications.view_all') }}
</NuxtLinkLocale>
</div>
</div>
</template>
<script setup lang="ts">
const { t } = useI18n()
const { notifications } = storeToRefs(useNotificationStore())
</script>
<i18n lang="yaml">
de:
notifications:
label: Benachrichtigungen
view_all: Alle ansehen
en:
notifications:
label: Notifications
view_all: View all
</i18n>

View File

@ -6,9 +6,11 @@
@abort="$emit('abort')" @abort="$emit('abort')"
@confirm="$emit('confirm')" @confirm="$emit('confirm')"
> >
{{ <template #body>
final ? t('final.question', { itemName }) : t('question', { itemName }) {{
}} final ? t('final.question', { itemName }) : t('question', { itemName })
}}
</template>
</UiDialogConfirm> </UiDialogConfirm>
</template> </template>

View File

@ -1,12 +1,16 @@
<template> <template>
<UiDialogConfirm <UiDialogConfirm
v-model:open="showUnsavedChangesDialog"
:confirm-label="t('label')" :confirm-label="t('label')"
:title="t('title')" :title="t('title')"
@abort="$emit('abort')" @abort="$emit('abort')"
@confirm="onConfirm" @confirm="onConfirm"
v-model:open="showUnsavedChangesDialog"
> >
{{ t('question') }} <template #body>
<div class="flex items-center h-full">
{{ t('question') }}
</div>
</template>
</UiDialogConfirm> </UiDialogConfirm>
</template> </template>
@ -43,5 +47,5 @@ de:
en: en:
title: Unsaved changes title: Unsaved changes
question: Should the changes be discarded? question: Should the changes be discarded?
label: discard label: Discard
</i18n> </i18n>

View File

@ -1,48 +1,46 @@
<template> <template>
<div class="breadcrumbs"> <ul class="flex items-center gap-2 p-2">
<ul> <li>
<li> <NuxtLinkLocale :to="{ name: 'passwordGroupItems' }">
<NuxtLinkLocale :to="{ name: 'passwordGroupItems' }"> <Icon
<Icon name="mdi:safe"
name="mdi:safe" size="24"
size="24" />
/> </NuxtLinkLocale>
</NuxtLinkLocale> </li>
</li>
<template v-for="item in items">
<li class="breadcrumbs-separator rtl:rotate-180">
<Icon name="tabler:chevron-right" />
</li>
<li> <li
<NuxtLinkLocale v-for="item in items"
:to="{ name: 'passwordGroupItems', params: { groupId: item.id } }" :key="item.id"
> class="flex items-center gap-2"
{{ item.name }} >
</NuxtLinkLocale> <Icon
</li> name="tabler:chevron-right"
</template> class="rtl:rotate-180"
<li class="ml-2"> />
<UiTooltip <NuxtLinkLocale
:tooltip="t('edit')" :to="{ name: 'passwordGroupItems', params: { groupId: item.id } }"
class="[--placement:bottom]" >
{{ item.name }}
</NuxtLinkLocale>
</li>
<li class="ml-2">
<UTooltip :text="t('edit')">
<NuxtLinkLocale
:to="{
name: 'passwordGroupEdit',
params: { groupId: lastGroup?.id },
}"
> >
<NuxtLinkLocale <Icon name="mdi:pencil" />
:to="{ </NuxtLinkLocale>
name: 'passwordGroupEdit', </UTooltip>
params: { groupId: lastGroup?.id }, </li>
}" </ul>
>
<Icon name="mdi:pencil" />
</NuxtLinkLocale>
</UiTooltip>
</li>
</ul>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { UiTooltip } from '#components'
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault' import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
const groups = defineProps<{ items: SelectHaexPasswordsGroups[] }>() const groups = defineProps<{ items: SelectHaexPasswordsGroups[] }>()

View File

@ -1,11 +1,10 @@
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
export const usePasswordGroup = () => { export const usePasswordGroup = () => {
const areItemsEqual = ( const areItemsEqual = (
groupA: unknown | unknown[] | null, groupA: unknown | unknown[] | null,
groupB: unknown | unknown[] | null, groupB: unknown | unknown[] | null,
) => { ) => {
if (groupA === null && groupB === null) return true console.log('compare values', groupA, groupB)
if (groupA === groupB) return true
if (Array.isArray(groupA) && Array.isArray(groupB)) { if (Array.isArray(groupA) && Array.isArray(groupB)) {
console.log('compare object arrays', groupA, groupB) console.log('compare object arrays', groupA, groupB)
@ -18,7 +17,55 @@ export const usePasswordGroup = () => {
return areObjectsEqual(groupA, groupB) return areObjectsEqual(groupA, groupB)
} }
const deepEqual = (obj1: unknown, obj2: unknown) => {
console.log('compare values', obj1, obj2)
if (obj1 === obj2) return true
// Null/undefined Check
if (obj1 == null || obj2 == null) return obj1 === obj2
// Typ-Check
if (typeof obj1 !== typeof obj2) return false
// Primitive Typen
if (typeof obj1 !== 'object') return obj1 === obj2
// Arrays
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false
if (Array.isArray(obj1)) {
if (obj1.length !== obj2.length) return false
for (let i = 0; i < obj1.length; i++) {
if (!deepEqual(obj1[i], obj2[i])) return false
}
return true
}
// Date Objekte
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime()
}
// RegExp Objekte
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
return obj1.toString() === obj2.toString()
}
// Objekte
const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)
if (keys1.length !== keys2.length) return false
for (const key of keys1) {
if (!keys2.includes(key)) return false
if (!deepEqual(obj1[key], obj2[key])) return false
}
return true
}
return { return {
areItemsEqual, areItemsEqual,
deepEqual,
} }
} }

View File

@ -1,84 +1,78 @@
<template> <template>
<div class="p-1"> <UCard v-if="group">
<UiCard <template #header>
v-if="group" <div class="flex items-center gap-2">
:title="mode === 'edit' ? t('title.edit') : t('title.create')" <Icon
icon="mdi:folder-plus-outline" :name="
@close="$emit('close')" mode === 'edit'
body-class="px-0" ? 'mdi:folder-edit-outline'
> : 'mdi:folder-plus-outline'
<form "
class="flex flex-col gap-4 w-full p-4" size="24"
@submit.prevent="$emit('submit')"
>
<UiInput
:label="t('name')"
:placeholder="t('name')"
:read_only
autofocus
v-model="group.name"
ref="nameRef"
@keyup.enter="$emit('submit')"
/> />
<span>{{ mode === 'edit' ? t('title.edit') : t('title.create') }}</span>
</div>
</template>
<UiInput <form class="flex flex-col gap-4 w-full p-4">
v-model="group.description" <UiInput
:label="t('description')" ref="nameRef"
:placeholder="t('description')" v-model="group.name"
:read_only :label="t('name')"
@keyup.enter="$emit('submit')" :placeholder="t('name')"
/> :read-only
autofocus
@keyup.enter="$emit('submit')"
/>
<div class="flex flex-wrap gap-4"> <UiInput
<UiSelectIcon v-model="group.description"
:label="t('description')"
:placeholder="t('description')"
:read-only
@keyup.enter="$emit('submit')"
/>
<div class="flex flex-wrap gap-4">
<!-- <UiSelectIcon
v-model="group.icon" v-model="group.icon"
default-icon="mdi:folder-outline" default-icon="mdi:folder-outline"
:read_only :readOnly
/> />
<UiSelectColor <UiSelectColor
v-model="group.color" v-model="group.color"
:read_only :readOnly
/> /> -->
</div> </div>
</form>
<!-- <div class="flex flex-wrap justify-end gap-4"> </UCard>
<UiButton
class="btn-error btn-outline flex-1"
@click="$emit('close')"
>
{{ t('abort') }}
<Icon name="mdi:close" />
</UiButton>
<UiButton
class="btn-primary flex-1"
@click="$emit('submit')"
>
{{ mode === 'create' ? t('create') : t('save') }}
<Icon name="mdi:check" />
</UiButton>
</div> -->
</form>
</UiCard>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault' import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
const group = defineModel<SelectHaexPasswordsGroups | null>() const group = defineModel<SelectHaexPasswordsGroups | null>()
const { read_only = false } = defineProps<{ const { readOnly = false } = defineProps<{
read_only?: boolean readOnly?: boolean
mode: 'create' | 'edit' mode: 'create' | 'edit'
}>() }>()
defineEmits(['close', 'submit']) const emit = defineEmits(['close', 'submit'])
const { t } = useI18n() const { t } = useI18n()
const nameRef = useTemplateRef('nameRef') const nameRef = useTemplateRef('nameRef')
onStartTyping(() => { onStartTyping(() => {
nameRef.value?.inputRef?.focus() nameRef.value?.$el.focus()
})
const { escape } = useMagicKeys()
watchEffect(async () => {
if (escape?.value) {
await nextTick()
emit('close')
}
}) })
</script> </script>

View File

@ -5,73 +5,73 @@
@submit.prevent="$emit('submit')" @submit.prevent="$emit('submit')"
> >
<UiInput <UiInput
v-show="!read_only || itemDetails.title" v-show="!readOnly || itemDetails.title"
ref="titleRef"
v-model.trim="itemDetails.title"
:check-input="check" :check-input="check"
:label="t('item.title')" :label="t('item.title')"
:placeholder="t('item.title')" :placeholder="t('item.title')"
:read_only :read-only
:with-copy-button :with-copy-button
autofocus autofocus
ref="titleRef"
v-model.trim="itemDetails.title"
@keyup.enter="$emit('submit')" @keyup.enter="$emit('submit')"
/> />
<UiInput <UiInput
v-show="!read_only || itemDetails.username" v-show="!readOnly || itemDetails.username"
v-model.trim="itemDetails.username"
:check-input="check" :check-input="check"
:label="t('item.username')" :label="t('item.username')"
:placeholder="t('item.username')" :placeholder="t('item.username')"
:with-copy-button :with-copy-button
:read_only :read-only
v-model.trim="itemDetails.username"
@keyup.enter="$emit('submit')" @keyup.enter="$emit('submit')"
/> />
<UiInputPassword <UiInputPassword
v-show="!read_only || itemDetails.password" v-show="!readOnly || itemDetails.password"
:check-input="check"
:read_only
:with-copy-button
v-model.trim="itemDetails.password" v-model.trim="itemDetails.password"
:check-input="check"
:read-only
:with-copy-button
@keyup.enter="$emit('submit')" @keyup.enter="$emit('submit')"
> >
<template #append> <template #append>
<UiDialogPasswordGenerator <!-- <UiDialogPasswordGenerator
v-if="!read_only" v-if="!readOnly"
class="join-item" class="join-item"
:password="itemDetails.password" :password="itemDetails.password"
v-model="preventClose" v-model="preventClose"
/> /> -->
</template> </template>
</UiInputPassword> </UiInputPassword>
<UiInputUrl <UiInputUrl
v-show="!read_only || itemDetails.url" v-show="!readOnly || itemDetails.url"
v-model="itemDetails.url"
:label="t('item.url')" :label="t('item.url')"
:placeholder="t('item.url')" :placeholder="t('item.url')"
:read_only :read-only
:with-copy-button :with-copy-button
v-model="itemDetails.url"
@keyup.enter="$emit('submit')" @keyup.enter="$emit('submit')"
/> />
<UiSelectIcon <!-- <UiSelectIcon
v-show="!read_only" v-show="!readOnly"
:default-icon="defaultIcon || 'mdi:key-outline'" :default-icon="defaultIcon || 'mdi:key-outline'"
:read_only :readOnly
v-model="itemDetails.icon" v-model="itemDetails.icon"
/> /> -->
<UiTextarea <UiTextarea
v-show="!read_only || itemDetails.note" v-show="!readOnly || itemDetails.note"
v-model="itemDetails.note" v-model="itemDetails.note"
:label="t('item.note')" :label="t('item.note')"
:placeholder="t('item.note')" :placeholder="t('item.note')"
:read_only :readOnly
:with-copy-button :with-copy-button
@keyup.enter.stop @keyup.enter.stop
class="h-52" color="error"
/> />
</form> </form>
</div> </div>
@ -82,7 +82,7 @@ import type { SelectHaexPasswordsItemDetails } from '~~/src-tauri/database/schem
defineProps<{ defineProps<{
defaultIcon?: string | null defaultIcon?: string | null
read_only?: boolean readOnly?: boolean
withCopyButton?: boolean withCopyButton?: boolean
}>() }>()
@ -93,7 +93,7 @@ const itemDetails = defineModel<SelectHaexPasswordsItemDetails>({
required: true, required: true,
}) })
const preventClose = defineModel<boolean>('preventClose') //const preventClose = defineModel<boolean>('preventClose')
const check = defineModel<boolean>('check-input', { default: false }) const check = defineModel<boolean>('check-input', { default: false })
@ -104,7 +104,7 @@ const check = defineModel<boolean>('check-input', { default: false })
const titleRef = useTemplateRef('titleRef') const titleRef = useTemplateRef('titleRef')
onStartTyping(() => { onStartTyping(() => {
titleRef.value?.inputRef?.focus() titleRef.value?.$el?.focus()
}) })
</script> </script>

View File

@ -1,70 +1,41 @@
<template> <template>
<div class="p-1"> <div class="p-1">
<UiCard <UCard
body-class="rounded overflow-auto p-0 h-full" class="rounded overflow-auto p-0 h-full"
@close="onClose" @close="onClose"
> >
<div class=""> <div class="">
<nav <UTabs
aria-label="Tabs Password Item" :items="tabs"
aria-orientation="horizontal" variant="link"
class="tabs tabs-bordered w-full transition-all duration-700 sticky top-0 z-10" :ui="{ trigger: 'grow' }"
role="tablist" class="gap-4 w-full"
> >
<button <template #details>
:id="id.details" <HaexPassItemDetails
aria-controls="vaultDetailsId" v-if="details"
aria-selected="true" v-model="details"
class="tab active-tab:tab-active active w-full" with-copy-button
data-tab="#vaultDetailsId" :read-only
role="tab" :defaultIcon
type="button" v-model:prevent-close="preventClose"
> @submit="$emit('submit')"
<Icon
name="material-symbols:key-outline"
class="me-2"
/> />
<span class="hidden sm:block"> </template>
{{ t('tab.details') }}
</span>
</button>
<button
:id="id.keyValue"
aria-controls="tabs-basic-2"
aria-selected="false"
class="tab active-tab:tab-active w-full"
data-tab="#tabs-basic-2"
role="tab"
type="button"
>
<Icon
name="fluent:group-list-20-filled"
class="me-2"
/>
<span class="hidden sm:block">
{{ t('tab.keyValue') }}
</span>
</button>
<button
:id="id.history"
aria-controls="tabs-basic-3"
aria-selected="false"
class="tab active-tab:tab-active w-full"
data-tab="#tabs-basic-3"
role="tab"
type="button"
>
<Icon
name="material-symbols:history"
class="me-2"
/>
<span class="hidden sm:block">
{{ t('tab.history') }}
</span>
</button>
</nav>
<div class="h-full pb-8"> <template #keyValue>
<HaexPassItemKeyValue
v-if="keyValues"
v-model="keyValues"
v-model:items-to-add="keyValuesAdd"
v-model:items-to-delete="keyValuesDelete"
:read-only
:item-id="details!.id"
/>
</template>
</UTabs>
<!-- <div class="h-full pb-8">
<div <div
id="vaultDetailsId" id="vaultDetailsId"
role="tabpanel" role="tabpanel"
@ -104,15 +75,16 @@
role="tabpanel" role="tabpanel"
:aria-labelledby="id.history" :aria-labelledby="id.history"
> >
<!-- <HaexPassItemHistory v-model="itemHistory" /> --> <HaexPassItemHistory />
</div> </div>
</div> </div> -->
</div> </div>
</UiCard> </UCard>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
import type { import type {
SelectHaexPasswordsItemDetails, SelectHaexPasswordsItemDetails,
SelectHaexPasswordsItemHistory, SelectHaexPasswordsItemHistory,
@ -125,13 +97,13 @@ defineProps<{
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
close: [void] close: []
addKeyValue: [void] addKeyValue: []
removeKeyValue: [string] removeKeyValue: [string]
submit: [void] submit: []
}>() }>()
const read_only = defineModel<boolean>('read_only', { default: false }) const readOnly = defineModel<boolean>('readOnly', { default: false })
const details = defineModel<SelectHaexPasswordsItemDetails | null>('details', { const details = defineModel<SelectHaexPasswordsItemDetails | null>('details', {
required: true, required: true,
@ -152,12 +124,12 @@ const keyValuesDelete = defineModel<SelectHaexPasswordsItemKeyValues[]>(
const { t } = useI18n() const { t } = useI18n()
const id = reactive({ /* const id = reactive({
details: useId(), details: useId(),
keyValue: useId(), keyValue: useId(),
history: useId(), history: useId(),
content: {}, content: {},
}) }) */
const preventClose = ref(false) const preventClose = ref(false)
@ -166,6 +138,24 @@ const onClose = () => {
emit('close') emit('close')
} }
const tabs = ref<TabsItem[]>([
{
label: t('tab.details'),
icon: 'material-symbols:key-outline',
slot: 'details' as const,
},
{
label: t('tab.keyValue'),
icon: 'fluent:group-list-20-filled',
slot: 'keyValue' as const,
},
{
label: t('tab.history'),
icon: 'material-symbols:history',
slot: 'history' as const,
},
])
</script> </script>
<i18n lang="json"> <i18n lang="json">

View File

@ -12,47 +12,44 @@
class="flex gap-2 hover:bg-primary/20 px-4 items-center" class="flex gap-2 hover:bg-primary/20 px-4 items-center"
@click="currentSelected = item" @click="currentSelected = item"
> >
<button class="link flex items-center no-underline w-full py-2"> <button class="flex items-center no-underline w-full py-2">
<input <input
v-model="item.key" v-model="item.key"
:readonly="currentSelected !== item || read_only" :readonly="currentSelected !== item || readOnly"
class="flex-1 cursor-pointer" class="flex-1 cursor-pointer"
/> />
</button> </button>
<UiButton <UiButton
v-if="!read_only" v-if="!readOnly"
:class="[currentSelected === item ? 'visible' : 'invisible']" :class="[currentSelected === item ? 'visible' : 'invisible']"
class="inline-flex btn-square btn-error btn-outline" variant="outline"
color="error"
icon="mdi:trash-outline"
@click="deleteItem(item.id)" @click="deleteItem(item.id)"
> />
<Icon
name="mdi:trash-outline"
class="size-5"
/>
</UiButton>
</li> </li>
</UiList> </UiList>
<UiTextarea <UTextarea
v-if="items.length || itemsToAdd.length" v-if="items.length || itemsToAdd.length"
:read_only="read_only || !currentSelected" :readOnly="readOnly || !currentSelected"
class="flex-1 min-w-52 border-base-content/25" class="flex-1 min-w-52 border-base-content/25"
rows="6"
v-model="currentValue" v-model="currentValue"
with-copy-button with-copy-button
/> />
</div> </div>
<div <div
v-show="!read_only" v-show="!readOnly"
class="flex py-4 gap-2 justify-center items-end flex-wrap" class="flex py-4 gap-2 justify-center items-end flex-wrap"
> >
<UiButton <UiButton
@click="addItem" @click="addItem"
class="btn-primary btn-outline flex-1-1 min-w-40" class="btn-primary btn-outline flex-1-1 min-w-40"
icon="mdi:plus"
> >
<Icon name="mdi:plus" /> <!-- <Icon name="mdi:plus" />
<p class="hidden sm:inline-block">{{ t('add') }}</p> <p class="hidden sm:inline-block">{{ t('add') }}</p> -->
</UiButton> </UiButton>
</div> </div>
</div> </div>
@ -61,7 +58,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectHaexPasswordsItemKeyValues } from '~~/src-tauri/database/schemas/vault' import type { SelectHaexPasswordsItemKeyValues } from '~~/src-tauri/database/schemas/vault'
const { itemId } = defineProps<{ read_only?: boolean; itemId: string }>() const { itemId } = defineProps<{ readOnly?: boolean; itemId: string }>()
const items = defineModel<SelectHaexPasswordsItemKeyValues[]>({ default: [] }) const items = defineModel<SelectHaexPasswordsItemKeyValues[]>({ default: [] })
@ -74,9 +71,9 @@ const itemsToAdd = defineModel<SelectHaexPasswordsItemKeyValues[]>(
{ default: [] }, { default: [] },
) )
defineEmits<{ add: [void]; remove: [string] }>() defineEmits<{ add: []; remove: [string] }>()
const { t } = useI18n() //const { t } = useI18n()
const currentSelected = ref<SelectHaexPasswordsItemKeyValues | undefined>( const currentSelected = ref<SelectHaexPasswordsItemKeyValues | undefined>(
items.value?.at(0), items.value?.at(0),
@ -101,6 +98,7 @@ const addItem = () => {
key: '', key: '',
value: '', value: '',
updateAt: null, updateAt: null,
haex_tombstone: null,
}) })
} }

View File

@ -1,70 +1,60 @@
<template> <template>
<div <div
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end" class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end h-12"
:class="[isVisible ? 'left-15 ' : 'left-0']" :class="[isVisible ? 'left-16' : 'left-0']"
> >
<div class="flex items-center justify-center flex-1"> <div class="flex items-center justify-center flex-1">
<UiButton <UiButton
v-show="showCloseButton" v-show="showCloseButton"
:tooltip="t('abort')" :tooltip="t('abort')"
icon="mdi:close"
color="error"
variant="ghost"
class="pointer-events-auto"
@click="$emit('close')" @click="$emit('close')"
class="btn-accent btn-square" />
>
<Icon name="mdi:close" />
</UiButton>
</div> </div>
<div> <div>
<UiButton <UiButton
v-show="showEditButton" v-show="showEditButton"
icon="mdi:pencil-outline"
class="pointer-events-auto"
size="xl"
:tooltip="t('edit')" :tooltip="t('edit')"
@click="$emit('edit')" @click="$emit('edit')"
class="btn-xl btn-square btn-primary" />
>
<Icon
name="mdi:pencil-outline"
class="size-11 shrink-0"
/>
</UiButton>
<UiButton <UiButton
v-show="showReadonlyButton" v-show="showReadonlyButton"
icon="mdi:pencil-off-outline"
class="pointer-events-auto"
size="xl"
:tooltip="t('readonly')" :tooltip="t('readonly')"
class="btn-xl btn-square btn-primary"
@click="$emit('readonly')" @click="$emit('readonly')"
> />
<Icon
name="mdi:pencil-off-outline"
class="size-11 shrink-0"
/>
</UiButton>
<UiButton <UiButton
v-show="showSaveButton" v-show="showSaveButton"
icon="mdi:content-save-outline"
size="xl"
class="pointer-events-auto"
:class="{ 'animate-pulse': hasChanges }"
:tooltip="t('save')" :tooltip="t('save')"
class="btn-xl btn-square btn-primary motion-duration-2000"
:class="{ 'motion-preset-pulse-sm': hasChanges }"
@click="$emit('save')" @click="$emit('save')"
> />
<Icon
name="mdi:content-save-outline"
class="size-11 shrink-0"
/>
</UiButton>
</div> </div>
<div class="flex items-center justify-center flex-1"> <div class="flex items-center justify-center flex-1">
<UiButton <UiButton
v-show="showDeleteButton" v-show="showDeleteButton"
color="error"
icon="mdi:trash-outline"
class="pointer-events-auto"
variant="ghost"
:tooltip="t('delete')" :tooltip="t('delete')"
class="btn-square btn-error"
@click="$emit('delete')" @click="$emit('delete')"
> />
<Icon
name="mdi:trash-outline"
class="shrink-0"
/>
</UiButton>
</div> </div>
</div> </div>
</template> </template>
@ -74,12 +64,12 @@ const { isVisible } = storeToRefs(useSidebarStore())
const { t } = useI18n() const { t } = useI18n()
defineProps<{ defineProps<{
hasChanges?: boolean
showCloseButton?: boolean showCloseButton?: boolean
showDeleteButton?: boolean showDeleteButton?: boolean
showEditButton?: boolean showEditButton?: boolean
showReadonlyButton?: boolean showReadonlyButton?: boolean
showSaveButton?: boolean showSaveButton?: boolean
hasChanges?: boolean
}>() }>()
defineEmits(['close', 'edit', 'readonly', 'save', 'delete']) defineEmits(['close', 'edit', 'readonly', 'save', 'delete'])

View File

@ -4,15 +4,21 @@
class="flex-1" class="flex-1"
> >
<ul <ul
class="flex flex-col w-full h-full gap-y-2 first:rounded-t-md last:rounded-b-md p-1"
ref="listRef" ref="listRef"
class="flex flex-col w-full h-full gap-y-2 first:rounded-t-md last:rounded-b-md p-1"
> >
<li <li
v-for="(item, index) in menuItems" v-for="(item, index) in menuItems"
:key="item.id" :key="item.id"
class="bg-base-100 rounded-lg hover:bg-base-content/20 origin-to intersect:motion-preset-slide-down intersect:motion-ease-spring-bouncier intersect:motion-delay ease-in-out shadow" v-on-long-press="[
onLongPressCallbackHook,
{
delay: 1000,
},
]"
class="bg-accented rounded-lg hover:bg-base-content/20 origin-to intersect:motion-preset-slide-down intersect:motion-ease-spring-bouncier intersect:motion-delay ease-in-out shadow"
:class="{ :class="{
'bg-base-content/30 outline outline-accent hover:bg-base-content/20': 'bg-elevated/30 outline outline-accent hover:bg-base-content/20':
selectedItems.has(item) || selectedItems.has(item) ||
(currentSelectedItem?.id === item.id && (currentSelectedItem?.id === item.id &&
longPressedHook && longPressedHook &&
@ -22,12 +28,6 @@
), ),
}" }"
:style="{ '--motion-delay': `${50 * index}ms` }" :style="{ '--motion-delay': `${50 * index}ms` }"
v-on-long-press="[
onLongPressCallbackHook,
{
delay: 1000,
},
]"
@mousedown=" @mousedown="
longPressedHook longPressedHook
? (currentSelectedItem = null) ? (currentSelectedItem = null)
@ -85,7 +85,7 @@ const { search } = storeToRefs(useSearchStore())
const onClickItemAsync = async (item: IPasswordMenuItem) => { const onClickItemAsync = async (item: IPasswordMenuItem) => {
currentSelectedItem.value = null currentSelectedItem.value = null
if (longPressedHook.value || selectedItems.value.size || ctrl.value) { if (longPressedHook.value || selectedItems.value.size || ctrl?.value) {
if (selectedItems.value?.has(item)) { if (selectedItems.value?.has(item)) {
selectedItems.value.delete(item) selectedItems.value.delete(item)
} else { } else {

View File

@ -1,188 +0,0 @@
<template>
<aside
:id
ref="sidebarRef"
class="flex sm:shadow-none w-full md:max-w-64"
tabindex="-1"
>
<div class="drawer-body w-full">
<ul class="menu space-y-0.5 p-0 rounded-none md:rounded">
<li>
<a href="#">
<span class="icon-[tabler--home] size-5" />
Home
</a>
</li>
<li class="space-y-0.5">
<a
id="menu-app"
class="collapse-toggle collapse-open:bg-base-content/10"
data-collapse="#menu-app-collapse"
>
<span class="icon-[tabler--apps] size-5" />
Apps
<span
class="icon-[tabler--chevron-down] collapse-open:rotate-180 size-4 transition-all duration-300"
/>
</a>
<ul
id="menu-app-collapse"
class="collapse hidden w-auto space-y-0.5 overflow-hidden transition-[height] duration-300"
aria-labelledby="menu-app"
>
<li>
<a href="#">
<span class="icon-[tabler--message] size-5" />
Chat
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--calendar] size-5" />
Calendar
</a>
</li>
<li class="space-y-0.5">
<a
id="sub-menu-academy"
class="collapse-toggle collapse-open:bg-base-content/10"
data-collapse="#sub-menu-academy-collapse"
>
<span class="icon-[tabler--book] size-5" />
Academy
<span
class="icon-[tabler--chevron-down] collapse-open:rotate-180 size-4"
/>
</a>
<ul
id="sub-menu-academy-collapse"
class="collapse hidden w-auto space-y-0.5 overflow-hidden transition-[height] duration-300"
aria-labelledby="sub-menu-academy"
>
<li>
<a href="#">
<span class="icon-[tabler--books] size-5" />
Courses
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--list-details] size-5" />
Course details
</a>
</li>
<li class="space-y-0.5">
<a
id="sub-menu-academy-stats"
class="collapse-toggle collapse-open:bg-base-content/10"
data-collapse="#sub-menu-academy-stats-collapse"
>
<span class="icon-[tabler--chart-bar] size-5" />
Stats
<span
class="icon-[tabler--chevron-down] collapse-open:rotate-180 size-4"
/>
</a>
<ul
id="sub-menu-academy-stats-collapse"
class="collapse hidden w-auto space-y-0.5 overflow-hidden transition-[height] duration-300"
aria-labelledby="sub-menu-academy-stats"
>
<li>
<a href="#">
<span class="icon-[tabler--chart-donut] size-5" />
Goals
</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<a href="#">
<span class="icon-[tabler--settings] size-5" />
Settings
</a>
</li>
<div class="divider text-base-content/50 py-6 after:border-0">
Account
</div>
<li>
<a href="#">
<span class="icon-[tabler--login] size-5" />
Sign In
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--logout-2] size-5" />
Sign Out
</a>
</li>
<div class="divider text-base-content/50 py-6 after:border-0">
Miscellaneous
</div>
<li>
<a href="#">
<span class="icon-[tabler--users-group] size-5" />
Support
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--files] size-5" />
Documentation
</a>
</li>
</ul>
</div>
</aside>
</template>
<script setup lang="ts">
import type { HSOverlay } from 'flyonui/flyonui'
defineProps<{ title?: string; label?: string }>()
defineEmits(['open', 'close'])
const id = useId()
const open = defineModel<boolean>('open', { default: true })
const { t } = useI18n()
const sidebarRef = useTemplateRef('sidebarRef')
const modal = ref<HSOverlay>()
watch(open, async () => {
if (open.value) {
await modal.value?.open()
} else {
await modal.value?.close(true)
}
})
onMounted(async () => {
if (!sidebarRef.value) return
modal.value = new window.HSOverlay(sidebarRef.value, {
isClosePrev: true,
})
modal.value.on('close', () => {
open.value = false
})
})
</script>
<i18n lang="yaml">
de:
close: Schließen
en:
close: Close
</i18n>

View File

@ -1,42 +0,0 @@
<script setup lang="ts">
const onClick = () => {
console.log("click")
}
</script>
<template>
<Dialog>
<DialogTrigger as-child>
<Button variant="outline" @click="onClick">
Edit Profile
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">
Name
</Label>
<Input id="name" default-value="Pedro Duarte" class="col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">
Username
</Label>
<Input id="username" default-value="@peduarte" class="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">
Save changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@ -1,48 +0,0 @@
<template>
<div
class="accordion divide-neutral/20 divide-y accordion-shadow *:accordion-item-active:shadow-md"
>
<div
:id="itemId"
ref="accordionRef"
class="accordion-item active"
>
<button
class="accordion-toggle inline-flex items-center gap-x-4 text-start"
:aria-controls="collapseId"
aria-expanded="true"
type="button"
>
<span
class="icon-[tabler--chevron-right] accordion-item-active:rotate-90 size-5 shrink-0 transition-transform duration-300 rtl:rotate-180"
/>
<slot name="title" />
</button>
<div
:id="collapseId"
class="accordion-content w-full overflow-hidden transition-[height] duration-300"
:aria-labelledby="itemId"
role="region"
>
<slot />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { HSAccordion } from 'flyonui/flyonui'
const itemId = useId()
const collapseId = useId()
const accordionRef = useTemplateRef('accordionRef')
const accordion = ref<HSAccordion>()
onMounted(() => {
if (accordionRef.value) {
accordion.value = new window.HSAccordion(accordionRef.value)
accordion.value.hide()
}
})
</script>

View File

@ -1,107 +0,0 @@
<template>
<div class="z-10 pointer-events-auto">
<div
class="dropdown relative inline-flex [--placement:top] [--strategy:absolute]"
>
<button
:id
class="dropdown-toggle btn btn-primary btn-xl btn-square dropdown-open:rotate-45 transition-transform"
aria-haspopup="menu"
aria-expanded="false"
aria-label="Menu"
>
<Icon
:name="icon"
class="size-11 shrink-0"
/>
</button>
<ul
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60 bg-transparent shadow-none"
data-dropdown-transition
role="menu"
aria-orientation="vertical"
:aria-labelledby="id"
>
<li
v-for="link in menu"
class="dropdown-item hover:bg-transparent px-0 py-1"
>
<NuxtLinkLocale
v-if="link.to"
:to="link.to"
class="btn btn-primary flex items-center no-underline rounded-lg flex-nowrap w-full"
>
<Icon
v-if="link.icon"
:name="link.icon"
class="me-3"
/>
{{ te(link.label) ? t(link.label) : link.label }}
</NuxtLinkLocale>
<button
v-else
@click="link.action"
class="link hover:link-primary flex items-center no-underline w-full"
>
<Icon
v-if="link.icon"
:name="link.icon"
class="me-3"
/>
{{ link.label }}
</button>
</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import type { IActionMenuItem } from './types'
defineProps({
menu: {
type: Array as PropType<IActionMenuItem[]>,
},
icon: {
type: String,
default: 'mdi:plus',
},
})
const id = useId()
const { t, te } = useI18n()
</script>
<style lang="css" scoped>
@keyframes fadeInStagger {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 2. Die Listenelemente sind standardmäßig unsichtbar, damit sie nicht aufblitzen */
.stagger-menu li {
opacity: 0;
}
/* 3. Wenn das Menü geöffnet wird, weise die Animation zu */
:global(.dropdown-open) .stagger-menu li {
animation-name: fadeInStagger;
animation-duration: 0.4s;
animation-timing-function: ease-out;
/* SEHR WICHTIG: Sorgt dafür, dass die Elemente nach der Animation sichtbar bleiben (den Zustand von 'to' beibehalten) */
animation-fill-mode: forwards;
/* Die individuelle animation-delay wird per :style im Template gesetzt. */
}
</style>

View File

@ -1,22 +1,31 @@
<template> <template>
<button <div>
class="btn join-item pointer-events-auto" <UTooltip :text="buttonProps?.tooltip">
:type <UButton
> class="pointer-events-auto"
<UiTooltip v-bind="{ ...buttonProps, ...$attrs }"
:tooltip @click="(e) => $emit('click', e)"
v-if="tooltip" >
> <template
<slot /> v-for="(_, slotName) in $slots"
</UiTooltip> #[slotName]="slotProps"
>
<slot v-else /> <slot
</button> :name="slotName"
v-bind="slotProps"
/>
</template>
</UButton>
</UTooltip>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { type = 'button' } = defineProps<{ import type { ButtonProps } from '@nuxt/ui'
type?: 'reset' | 'submit' | 'button'
interface IButtonProps extends /* @vue-ignore */ ButtonProps {
tooltip?: string tooltip?: string
}>() }
const buttonProps = defineProps<IButtonProps>()
defineEmits<{ click: [Event] }>()
</script> </script>

View File

@ -1,8 +0,0 @@
import type { RouteLocationRaw } from 'vue-router'
export interface IActionMenuItem {
label: string
icon?: string
action?: () => Promise<unknown>
to?: RouteLocationRaw
}

View File

@ -1,81 +0,0 @@
<template>
<div class="card min-w-56">
<slot name="image" />
<div
class="card-header"
v-if="$slots.title || title"
>
<slot name="header">
<div
v-if="$slots.title || title"
class="flex items-center gap-2"
>
<Icon
v-if="icon"
:name="icon"
size="28"
/>
<h5
v-if="title"
class="card-title mb-0"
>
{{ title }}
</h5>
<slot
v-else
name="title"
/>
</div>
<div class="text-base-content/45">{{ subtitle }}</div>
</slot>
</div>
<div
class="card-body"
:class="bodyClass"
>
<slot />
<div
v-if="$slots.action"
class="card-actions"
>
<slot name="action" />
</div>
</div>
<div
v-if="$slots.footer"
class="card-footer"
>
<slot name="footer" />
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['close', 'submit'])
defineProps<{
title?: string
subtitle?: string
icon?: string
bodyClass?: string
}>()
const { escape, enter } = useMagicKeys()
watchEffect(async () => {
if (escape.value) {
await nextTick()
emit('close')
}
})
watchEffect(async () => {
if (enter.value) {
await nextTick()
emit('submit')
}
})
</script>

View File

@ -1,61 +1,73 @@
<template> <template>
<UiDialog <UModal
:title
@close="onAbort"
v-model:open="open" v-model:open="open"
:title
:description
:fullscreen="isSmallScreen"
> >
<template #trigger> <slot>
<slot name="trigger" /> <!-- <UiButton
</template> color="primary"
variant="outline"
icon="mdi:menu"
:ui="{
base: '',
}"
/> -->
</slot>
<template #title> <template #title>
<slot name="title" /> <slot name="title" />
</template> </template>
<slot /> <template #body>
<slot name="body" />
<template #buttons>
<slot name="buttons">
<UiButton
class="btn-error btn-outline w-full sm:w-auto"
@click="onAbort"
>
<Icon :name="abortIcon || 'mdi:close'" />
{{ abortLabel ?? t('abort') }}
</UiButton>
<UiButton
class="btn-primary w-full sm:w-auto"
@click="onConfirm"
>
<Icon :name="confirmIcon || 'mdi:check'" />
{{ confirmLabel ?? t('confirm') }}
</UiButton>
</slot>
</template> </template>
</UiDialog>
<template #footer>
<div class="flex flex-col sm:flex-row gap-4 justify-end w-full">
<UiButton
:icon="abortIcon || 'mdi:close'"
:label="abortLabel || t('abort')"
block
color="error"
variant="outline"
@click="open = false"
/>
<UiButton
:icon="confirmIcon || 'mdi:check'"
:label="confirmLabel || t('confirm')"
block
color="primary"
varaint="solid"
@click="$emit('confirm')"
/>
</div>
</template>
</UModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
defineProps<{ defineProps<{
confirmLabel?: string
abortLabel?: string
title?: string
abortIcon?: string abortIcon?: string
abortLabel?: string
confirmIcon?: string confirmIcon?: string
confirmLabel?: string
description?: string
title?: string
}>() }>()
const open = defineModel<boolean>('open', { default: false }) const open = defineModel<boolean>('open', { default: false })
const { t } = useI18n() const { t } = useI18n()
const emit = defineEmits(['confirm', 'abort']) defineEmits(['confirm'])
const onAbort = () => { const breakpoints = useBreakpoints(breakpointsTailwind)
emit('abort')
}
const onConfirm = () => { // "smAndDown" gilt für sm, xs usw.
emit('confirm') const isSmallScreen = breakpoints.smaller('sm')
}
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,116 +0,0 @@
<template>
<button
v-if="$slots.trigger || label"
v-bind="$attrs"
type="button"
aria-haspopup="dialog"
aria-expanded="false"
:aria-label="label"
@click="$emit('open')"
>
<slot name="trigger">
{{ label }}
</slot>
</button>
<div class="hidden">
<Teleport to="body">
<div
:id
ref="modalRef"
class="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden modal-middle p-0 xs:p-2 --prevent-on-load-init pointer-events-auto max-w-none"
role="dialog"
tabindex="-1"
>
<div
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
>
<div class="modal-content justify-between">
<div class="modal-header py-0 sm:py-4">
<div
v-if="title || $slots.title"
class="modal-title py-4 break-all"
>
<slot name="title">
{{ title }}
</slot>
</div>
<button
type="button"
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
:aria-label="t('close')"
tabindex="1"
@click="open = false"
>
<Icon
name="mdi:close"
size="18"
/>
</button>
</div>
<div class="modal-body text-sm sm:text-base grow mt-0 pt-0">
<slot />
</div>
<div class="modal-footer flex-col sm:flex-row">
<slot name="buttons" />
</div>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
import type { HSOverlay } from 'flyonui/flyonui'
const { currentTheme } = storeToRefs(useUiStore())
defineProps<{ title?: string; label?: string }>()
const emit = defineEmits(['open', 'close'])
const id = useId()
const open = defineModel<boolean>('open', { default: false })
const { t } = useI18n()
const modalRef = useTemplateRef('modalRef')
defineExpose({ modalRef })
const modal = ref<HSOverlay>()
watch(open, async () => {
if (!modal.value) return
if (open.value) {
await modal.value.open()
} else {
await modal.value.close(true)
emit('close')
}
})
onMounted(async () => {
if (!modalRef.value) return
modal.value = new window.HSOverlay(modalRef.value)
modal.value.isLayoutAffect = true
modal.value.on('close', () => {
open.value = false
})
})
</script>
<i18n lang="yaml">
de:
close: Schließen
en:
close: Close
</i18n>

View File

@ -1,55 +0,0 @@
<template>
<UiDialogConfirm
:confirm-label="t('apply')"
:title="t('title')"
@abort="open = false"
@click="open = true"
class="btn btn-square btn-accent btn-outline"
v-model:open="open"
>
<template #trigger>
<Icon name="mdi:dice" />
</template>
<form class="flex flex-col gap-4">
<UiInputPassword
v-model="newPassword"
prepend-icon="mdi:key-outline"
with-copy-button
>
<template #append>
<UiButton class="btn-square btn-accent btn-outline">
<Icon name="mdi:refresh" />
</UiButton>
</template>
</UiInputPassword>
</form>
</UiDialogConfirm>
</template>
<script setup lang="ts">
const open = defineModel<boolean>()
const { t } = useI18n()
const { password } = defineProps<{
autofocus?: boolean
checkInput?: boolean
label?: string
placeholder?: string
withCopyButton?: boolean
password: string | null
}>()
const newPassword = computed(() => password)
</script>
<i18n lang="yaml">
de:
title: Passwortgenerator
apply: Übernehmen
en:
title: Passwordgenerator
apply: Apply
</i18n>

View File

@ -1,70 +0,0 @@
<template>
<div
:class="offset"
class="dropdown relative inline-flex"
>
<button
:aria-label="label"
:id
aria-expanded="false"
aria-haspopup="menu"
class="dropdown-toggle"
type="button"
v-bind="$attrs"
>
<slot name="activator">
{{ label }}
<span
class="icon-[tabler--chevron-down] dropdown-open:rotate-180 size-4"
/>
</slot>
</button>
<ul
:aria-labelledby="id"
aria-orientation="vertical"
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28 z-20 shadow shadow-primary"
role="menu"
>
<slot
name="items"
:items
>
<li
:is="itemIs"
@click="read_only ? '' : $emit('select', item)"
class="dropdown-item"
v-for="item in items"
>
<slot
:item
name="item"
>
{{ item }}
</slot>
</li>
</slot>
</ul>
</div>
</template>
<script setup lang="ts" generic="T">
defineOptions({
inheritAttrs: false,
})
const { itemIs = 'li', offset = '[--offset:0]' } = defineProps<{
label?: string
items?: T[]
itemIs?: string
activatorClass?: string
offset?: string
read_only?: boolean
}>()
defineEmits<{ select: [T] }>()
const id = useId()
//const offset = '[--offset:30]'
</script>

View File

@ -1,40 +1,38 @@
<template> <template>
<UiDropdown <UDropdownMenu
:items="availableLocales" arrow
class="btn btn-primary btn-outline" :items
@select="(locale) => $emit('select', locale)" :ui="{}"
> >
<template #activator> <UButton
<Icon :name="flags[locale]" /> :icon="items.find((item) => item.label === locale)?.icon"
<Icon :label="locale"
name="tabler:chevron-down" color="neutral"
class="dropdown-open:rotate-180 size-4" variant="outline"
/> />
</template> </UDropdownMenu>
<template #item="{ item }">
<div class="flex gap-2 justify-center">
<Icon
:name="flags[item]"
class="my-auto"
/>
<p>
{{ item }}
</p>
</div>
</template>
</UiDropdown>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
import type { Locale } from 'vue-i18n' import type { Locale } from 'vue-i18n'
const { locales, locale } = useI18n()
const flags = { const flags = {
de: 'emojione:flag-for-germany', de: 'circle-flags:de',
en: 'emojione:flag-for-united-kingdom', en: 'circle-flags:uk',
} }
const { availableLocales, locale } = useI18n() const emit = defineEmits<{ select: [Locale] }>()
defineEmits<{ select: [Locale] }>() const items = computed<DropdownMenuItem[]>(() =>
locales.value.map((locale) => ({
label: locale.code,
icon: flags[locale.code],
onSelect() {
emit('select', locale.code)
},
})),
)
</script> </script>

View File

@ -1,22 +1,24 @@
<template> <template>
<UiDropdown :items="availableThemes" class="btn btn-primary btn-outline" @select="(theme) => $emit('select', theme)"> <UDropdownMenu :items>
<template #activator> <UButton :icon="currentTheme?.icon" />
<Icon :name="currentTheme.icon" /> </UDropdownMenu>
</template>
<template #item="{ item }">
<div class="flex gap-2 justify-center">
<Icon :name="item.icon" class="my-auto" />
<p>
{{ item.name }}
</p>
</div>
</template>
</UiDropdown>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const { availableThemes, currentTheme } = storeToRefs(useUiStore()) const { availableThemes, currentTheme } = storeToRefs(useUiStore())
defineEmits<{ select: [ITheme] }>() const emit = defineEmits<{ select: [string] }>()
watchImmediate(availableThemes, () =>
console.log('availableThemes', availableThemes),
)
const items = computed<DropdownMenuItem[]>(() =>
availableThemes?.value.map((theme) => ({
...theme,
onSelect: () => emit('select', theme.value),
})),
)
</script> </script>

View File

@ -0,0 +1,45 @@
<template>
<UDropdownMenu :items>
<UButton
icon="mdi:menu"
color="neutral"
variant="outline"
/>
</UDropdownMenu>
</template>
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const { t } = useI18n()
const { closeAsync } = useVaultStore()
const onVaultCloseAsync = async () => {
await closeAsync()
await navigateTo(useLocalePath()({ name: 'vaultOpen' }))
}
const items: DropdownMenuItem[] = [
{
icon: 'tabler:settings',
label: t('settings'),
to: useLocalePath()({ name: 'settings' }),
},
{
icon: 'tabler:logout',
label: t('close'),
onSelect: () => onVaultCloseAsync(),
color: 'error',
},
]
</script>
<i18n lang="yaml">
de:
settings: 'Einstellungen'
close: 'Vault schließen'
en:
settings: 'Settings'
close: 'Close Vault'
</i18n>

View File

@ -1,181 +1,88 @@
<template> <template>
<div> <UInput
<fieldset v-model="value"
class="join w-full" :placeholder="props.placeholder || ' '"
:class="{ 'pt-1.5': label }" :readonly="props.readOnly"
v-bind="$attrs" :leading-icon="props.leadingIcon"
> :ui="{ base: 'peer' }"
<slot name="prepend" /> @change="(e) => $emit('change', e)"
@blur="(e) => $emit('blur', e)"
<div class="input join-item"> @keyup="(e: KeyboardEvent) => $emit('keyup', e)"
<Icon @keydown="(e: KeyboardEvent) => $emit('keydown', e)"
v-if="prependIcon" >
:name="prependIcon" <label
class="my-auto shrink-0" class="absolute pointer-events-none -top-2.5 left-0 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal"
/>
<div class="input-floating grow">
<input
:autofocus
:id
:name="name ?? id"
:placeholder="placeholder || label"
:readonly="read_only"
:type
class="ps-2"
ref="inputRef"
v-model="input"
@keyup="(e:KeyboardEvent) => $emit('keyup', e)"
/>
<label
:for="id"
class="input-floating-label"
>
{{ label }}
</label>
</div>
<Icon
v-if="appendIcon"
:name="appendIcon"
class="my-auto shrink-0"
/>
</div>
<UiButton
v-if="withClearButton"
class="btn-outline btn-square"
@click="input = ''"
>
<Icon name="mdi:close" />
</UiButton>
<slot name="append" />
<UiButton
v-if="withCopyButton"
:tooltip="t('copy')"
class="btn-outline btn-accent btn-square"
@click="copy(`${input}`)"
>
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
</UiButton>
</fieldset>
<span
v-show="errors"
class="flex flex-col px-2 pt-0.5"
> >
<span <span
v-for="error in errors" class="inline-flex bg-default px-1"
class="label-text-alt text-error" :class="props?.leadingIcon ? 'mx-6' : 'mx-0'"
> >
{{ error }} {{ props?.label }}
</span> </span>
</span> </label>
</div>
<template #trailing>
<slot name="trailing" />
<UiButton
v-show="props.withCopyButton"
:color="copied ? 'success' : 'neutral'"
:tooltip="t('copy')"
:icon="copied ? 'mdi:check' : 'mdi:content-copy'"
size="sm"
variant="link"
@click="copy(`${value}`)"
/>
</template>
<template
v-for="(_, slotName) in filteredSlots"
#[slotName]="slotProps"
>
<slot
:name="slotName"
v-bind="slotProps"
/>
</template>
</UInput>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ZodSchema } from 'zod' import type { AcceptableValue, InputProps } from '@nuxt/ui'
const input = defineModel<string | number | undefined | null>({ const value = defineModel<AcceptableValue | undefined>()
required: true,
})
const inputRef = useTemplateRef('inputRef') interface IInputProps extends /* @vue-ignore */ InputProps {
defineExpose({ inputRef }) tooltip?: string
const emit = defineEmits<{
error: [string[]]
keyup: [KeyboardEvent]
}>()
const props = defineProps({
placeholder: {
type: String,
default: '',
},
type: {
type: String as PropType<
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week'
>,
default: 'text',
},
label: String,
name: String,
prependIcon: {
type: String,
default: '',
},
prependLabel: String,
appendIcon: {
type: String,
default: '',
},
appendLabel: String,
rules: Object as PropType<ZodSchema>,
checkInput: Boolean,
withCopyButton: Boolean,
withClearButton: Boolean,
autofocus: Boolean,
read_only: Boolean,
})
onMounted(() => {
if (props.autofocus && inputRef.value) inputRef.value.focus()
})
const errors = defineModel<string[] | undefined>('errors')
const id = useId()
watch(input, () => checkInput())
watch(
() => props.checkInput,
() => {
checkInput()
},
)
const checkInput = () => {
if (props.rules) {
const result = props.rules.safeParse(input.value)
//console.log('check result', result.error, props.rules);
if (!result.success) {
errors.value = result.error.errors.map((error) => error.message)
emit('error', errors.value)
} else {
errors.value = []
}
}
} }
const props = defineProps<
IInputProps & {
withCopyButton?: boolean
readOnly?: boolean
label?: string
leadingIcon?: string
}
>()
defineEmits<{
change: [Event]
blur: [Event]
keyup: [KeyboardEvent]
keydown: [KeyboardEvent]
}>()
const { copy, copied } = useClipboard() const { copy, copied } = useClipboard()
const { t } = useI18n() const { t } = useI18n()
const filteredSlots = computed(() => {
return Object.fromEntries(
Object.entries(useSlots()).filter(([name]) => name !== 'trailing'),
)
})
watchImmediate(props, () => console.log('props', props))
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,62 +1,53 @@
<template> <template>
<UiInput <UiInput
v-model="value" v-model="value"
:autofocus :label="t('label')"
:check-input :leading-icon
:label="label || t('password')" :placeholder="placeholder || ' '"
:placeholder="placeholder || t('password')" :read-only
:rules :type="show ? 'text' : 'password'"
:type="type"
:with-copy-button :with-copy-button
@keyup="(e) => $emit('keyup', e)"
> >
<template #append> <template #trailing>
<slot name="append" />
<UiButton <UiButton
class="btn-outline btn-accent btn-square join-item" aria-controls="password"
@click="tooglePasswordType" color="neutral"
> variant="link"
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" /> :aria-label="show ? t('hide') : t('show')"
</UiButton> :aria-pressed="show"
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
:tooltip="show ? t('hide') : t('show')"
size="sm"
@click="show = !show"
/>
</template> </template>
</UiInput> </UiInput>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ZodSchema } from 'zod' import type { AcceptableValue } from '@nuxt/ui'
const { t } = useI18n()
const value = defineModel<string | number | null | undefined>()
defineProps<{ defineProps<{
autofocus?: boolean
checkInput?: boolean
label?: string label?: string
placeholder?: string placeholder?: string
rules?: ZodSchema leadingIcon?: string
withCopyButton?: boolean withCopyButton?: boolean
readOnly?: boolean
}>() }>()
const value = defineModel<AcceptableValue | undefined>()
defineEmits<{ const show = ref(false)
keyup: [KeyboardEvent] const { t } = useI18n()
}>()
const type = ref<'password' | 'text'>('password')
const tooglePasswordType = () => {
type.value = type.value === 'password' ? 'text' : 'password'
}
</script> </script>
<i18n lang="json"> <i18n lang="yaml">
{ de:
"de": { show: Passwort ansehen
"password": "Passwort" hide: Passwort verstecken
}, label: Passwort
"en": {
"password": "Password" en:
} show: Show password
} hide: Hide password
label: Password
</i18n> </i18n>

View File

@ -1,5 +1,6 @@
<template> <template>
<UiInput <UiInput
v-model.trim="value"
:autofocus :autofocus
:check-input="checkInput" :check-input="checkInput"
:label="label || t('url')" :label="label || t('url')"
@ -7,17 +8,18 @@
:read_only :read_only
:rules :rules
:with-copy-button :with-copy-button
v-model.trim="value"
@keyup="(e) => $emit('keyup', e)" @keyup="(e) => $emit('keyup', e)"
> >
<template #append> <template #trailing>
<UiButton <UiButton
color="neutral"
variant="link"
size="sm"
icon="streamline:web"
:disabled="!value?.length" :disabled="!value?.length"
:tooltip="t('browse')"
@click="openUrl(`${value}`)" @click="openUrl(`${value}`)"
class="btn-outline btn-accent btn-square" />
>
<Icon name="streamline:web" />
</UiButton>
</template> </template>
</UiInput> </UiInput>
</template> </template>
@ -45,13 +47,12 @@ defineEmits<{
}>() }>()
</script> </script>
<i18n lang="json"> <i18n lang="yaml">
{ de:
"de": { url: Url
"url": "Url" browse: Url öffnen
},
"en": { en:
"url": "Url" url: Url
} browse: Open url
}
</i18n> </i18n>

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="flex flex-col h-fit border border-base-content/25 divide-base-content/25 divide-y rounded-md first:rounded-t-md last:rounded-b-md" class="flex flex-col h-fit border border-default/25 divide-default/25 divide-y rounded-md first:rounded-t-md last:rounded-b-md"
> >
<slot /> <slot />
</div> </div>

View File

@ -2,7 +2,6 @@
<svg <svg
viewBox="122 107 263 292" viewBox="122 107 263 292"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
style="max-height: 500px"
> >
<g <g
stroke-width="0.3" stroke-width="0.3"

View File

@ -10,24 +10,23 @@
<input <input
:id :id
:readonly="read_only" ref="colorRef"
:disabled="read_only" v-model="model"
:readonly="readOnly"
:disabled="readOnly"
:title="t('pick')" :title="t('pick')"
class="top-0 left-0 absolute size-0" class="top-0 left-0 absolute size-0"
type="color" type="color"
v-model="model"
ref="colorRef"
/> />
<UiTooltip :tooltip="t('reset')"> <UiTooltip :tooltip="t('reset')">
<button <UiButton
color="error"
:class="{ 'btn-disabled': readOnly }"
icon="mdi:refresh"
:disabled="readOnly"
@click="model = ''" @click="model = ''"
class="btn btn-sm text-sm btn-outline btn-error" />
:class="{ 'btn-disabled': read_only }"
type="button"
>
<Icon name="mdi:refresh" />
</button>
</UiTooltip> </UiTooltip>
</div> </div>
</template> </template>
@ -39,12 +38,12 @@ const { t } = useI18n()
const model = defineModel<string | null>() const model = defineModel<string | null>()
const colorRef = useTemplateRef('colorRef') const colorRef = useTemplateRef('colorRef')
defineProps({ defineProps({
read_only: Boolean, readOnly: Boolean,
}) })
const { currentTheme } = storeToRefs(useUiStore()) const { currentTheme } = storeToRefs(useUiStore())
const textColorClass = computed(() => { const textColorClass = computed(() => {
if (!model.value) if (!model.value && currentTheme.value)
return currentTheme.value.value === 'dark' ? 'text-black' : 'text-white' return currentTheme.value.value === 'dark' ? 'text-black' : 'text-white'
const color = getContrastingTextColor(model.value) const color = getContrastingTextColor(model.value)

View File

@ -1,5 +1,5 @@
<template> <template>
<UiDropdown <UDropdownMenu
:items="icons" :items="icons"
class="btn" class="btn"
@select="(newIcon) => (iconName = newIcon)" @select="(newIcon) => (iconName = newIcon)"
@ -23,7 +23,7 @@
</li> </li>
</div> </div>
</template> </template>
</UiDropdown> </UDropdownMenu>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -0,0 +1,60 @@
<template>
<li
class="rounded hover:bg-elevated py-2 cursor-pointer"
:class="{
['bg-base-content/20 ']: isActive,
}"
@click="triggerNavigate"
>
<UTooltip :tooltip="tooltip ?? name">
<NuxtLinkLocale
ref="linkRef"
:to
class="flex items-center justify-center cursor-pointer tooltip-toogle"
>
<div
v-if="iconType === 'svg'"
class="shrink-0 size-5"
v-html="icon"
/>
<Icon
v-else
:name="icon"
size="1.5em"
/>
</NuxtLinkLocale>
</UTooltip>
</li>
</template>
<script setup lang="ts">
import type { ISidebarItem } from '#imports'
const props = defineProps<ISidebarItem>()
const router = useRouter()
console.log('to', props.to)
const isActive = computed(() => {
if (props.to?.name === 'haexExtension') {
return (
getSingleRouteParam(router.currentRoute.value.params.extensionId) ===
props.id
)
} else {
return (
props.to?.name === router.currentRoute.value.meta.name ||
router
.getRoutes()
.find((route) => route.meta.name === props.to?.name)
?.children.some(
(route) => route.meta?.name === router.currentRoute.value.meta.name,
)
)
}
})
const linkRef = useTemplateRef('linkRef')
const triggerNavigate = () => linkRef.value?.$el.click()
</script>

View File

@ -1,7 +0,0 @@
<template>
<input v-model="value" />
</template>
<script setup lang="ts">
const value = defineModel()
</script>

View File

@ -1,6 +1,6 @@
<template> <template>
<p <p
class="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent font-black" class="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent font-black"
> >
<slot /> <slot />
</p> </p>

View File

@ -1,41 +1,54 @@
<template> <template>
<div class="relative"> <div>
<UiButton <UTextarea
v-if="withCopyButton" :id
:tooltip="t('copy')" v-model="value"
class="btn-square btn-outline btn-accent absolute z-10 top-2 right-2" :ui="{ base: 'peer' }"
@click="copy(`${value}`)" :readonly="readOnly"
class="w-full"
v-bind="$attrs"
> >
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
</UiButton>
<div class="textarea-floating">
<textarea
:class="{ 'pr-10': withCopyButton }"
:id
:placeholder
:readonly="read_only"
class="textarea"
v-bind="$attrs"
v-model="value"
></textarea>
<label <label
class="textarea-floating-label" class="absolute pointer-events-none -top-2.5 left-0 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal"
:for="id"
> >
{{ label }} <span class="inline-flex bg-default px-1">
{{ props.label }}
</span>
</label> </label>
</div>
<template #trailing>
<UiButton
v-show="withCopyButton"
:color="copied ? 'success' : 'neutral'"
:tooltip="t('copy')"
:icon="copied ? 'mdi:check' : 'mdi:content-copy'"
size="sm"
variant="link"
@click="copy(`${value}`)"
/>
</template>
</UTextarea>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ import type { TextareaProps } from '@nuxt/ui'
interface ITextareaProps extends /* @vue-ignore */ TextareaProps {
tooltip?: string
withCopyButton?: boolean
readOnly?: boolean
label?: string
}
const props = defineProps<ITextareaProps>()
/* defineProps<{
placeholder?: string placeholder?: string
label?: string label?: string
read_only?: boolean readOnly?: boolean
withCopyButton?: boolean withCopyButton?: boolean
}>() }>() */
const id = useId() const id = useId()

View File

@ -1,57 +0,0 @@
<template>
<div class="tooltip [--prevent-popper:false]">
<div
class="tooltip-toggle"
:aria-label="tooltip"
>
<slot>
<button class="btn btn-square">
<Icon name="mdi:chevron-up-box-outline" />
</button>
</slot>
<span
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible pointer-events-none z-50"
role="tooltip"
>
<span class="tooltip-body">
{{ tooltip }}
</span>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import type { PropType } from 'vue'
const props = defineProps({
direction: {
type: String as PropType<
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'right'
| 'right-start'
| 'right-end'
| 'left'
| 'left-start'
| 'left-end'
>,
default: 'top',
},
tooltip: {
type: String,
default: '',
},
trigger: {
type: String as PropType<'focus' | 'hover' | 'click'>,
default: 'hover',
},
})
</script>

View File

@ -1,26 +0,0 @@
<template>
<div
class="tree-view-selected:bg-base-200/60 dragged:bg-primary/20 dragged:rounded nested-4 cursor-pointer rounded-md px-2"
role="treeitem" :data-tree-view-item="JSON.stringify({
value,
isDir: false,
})
">
<div class="flex items-center gap-x-3">
<span class="icon-[tabler--file] text-base-content size-4 flex-shrink-0"/>
<div class="grow">
<span class="text-base-content">{{ value }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
value: String,
});
const id = useId();
const controlId = useId();
const isActive = ref(false);
</script>

View File

@ -1,204 +0,0 @@
<template>
<div :id ref="folderRef" data-nested-draggable="" :value class="">
<div
isDir :data-tree-view-item="JSON.stringify({ value })"
class="accordion-item active motion-preset-slide-left motion-ease-spring-bouncier" :class="{
'selected': isActive?.value,
'text-base-content': !color,
}" role="treeitem" :style="{ color: color || '' }">
<div
class="accordion-heading tree-view-selected:bg-primary/80 flex items-center gap-x-0.5 rounded-md hover:bg-primary/20 group">
<button class="accordion-toggle btn btn-sm btn-circle btn-text shrink-0" :aria-controls="controlId">
<Icon name="tabler:plus" class="accordion-item-active:rotate-45 size-4 transition-all duration-300" />
</button>
<button class="cursor-pointer rounded-md px-1.5 w-full" @click.stop="$emit('click', value)">
<div class="flex items-center gap-x-3">
<Icon v-if="icon" :name="icon || 'mdi:folder-outline'" class="shrink-0" />
<div class="flex whitespace-nowrap">
{{ value }}
</div>
</div>
</button>
<button
class="sticky right-2 btn btn-sm btn-circle btn-text shrink-0 group-hover:flex hidden ml-auto"
@click.stop="$emit('edit', value)">
<Icon name="mdi:pencil-outline" class="size-4 transition-all duration-300" />
</button>
</div>
<div
:id="controlId" class="accordion-content w-full transition-[height] duration-300" role="group"
:aria-labelledby="id">
<div ref="childRef" class="tree-view-space min-h-1" data-nested-draggable="">
<slot>
<template
v-for="(item, index) in children?.sort(
(a, b) => a.order ?? 0 - (b.order ?? 0)
)" :key="item.id!" :data-tree-view-item="JSON.stringify({ value: item.value })">
<UiTreeFolder
v-if="item.type === 'folder'" :icon="item.icon || 'tabler:folder'"
v-bind="item" @click="(value) => $emit('click', value)" @edit="(value) => $emit('edit', value)" />
<UiTreeFile v-if="item.type === 'file'" v-bind="item" />
</template>
</slot>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { HSAccordion } from 'flyonui/flyonui';
import Sortable from 'sortablejs';
const props = defineProps({
value: String,
icon: {
type: [String, null],
default: 'tabler:folder',
},
children: {
type: Array as PropType<ITreeItem[] | null>,
default: () => [],
},
name: String,
color: [String, null],
isActive: Object as PropType<ComputedRef<boolean>>,
});
const id = useId();
const controlId = useId();
const folderRef = ref<HTMLElement>();
const childRef = ref<HTMLElement>();
defineEmits<{
click: [value: string | undefined];
edit: [value: string | undefined];
}>();
const { groups } = storeToRefs(useVaultGroupStore());
const sorty = ref([]);
onMounted(() => {
if (folderRef.value && childRef.value)
[folderRef.value, childRef.value].forEach((element) => {
const create = Sortable.create(element, {
animation: 150,
ghostClass: 'bg-opacity-20',
group: 'vault',
swapThreshold: 0.65,
fallbackOnBody: true,
fallbackTolerance: 3,
onEnd: (evt) => {
const { item } = evt;
/* if (item.classList.contains('accordion')) {
let existingInstance = HSAccordion.getInstance(item, true);
let updatedInstance;
existingInstance.element.update();
updatedInstance = HSAccordion.getInstance(item, true);
window.$hsAccordionCollection.map((el) => {
if (
el.element.el !== existingInstance.element.el &&
el.element.group === existingInstance.element.group &&
el.element.el.closest('.accordion') &&
el.element.el.classList.contains('active') &&
existingInstance.element.el.classList.contains('active')
)
el.element.hide();
return el;
});
}
if (!!item.hasAttribute('data-tree-view-item')) {
const treeViewItem = HSTreeView.getInstance(
item.closest('[data-tree-view]'),
true
);
treeViewItem.element.update();
} */
},
onUpdate: (evt) => {
console.log('update', evt.item, props.value, sorty.value);
},
});
/* const sortable = new Sortable(element, {
animation: 150,
ghostClass: 'bg-opacity-20',
group: 'vault',
swapThreshold: 0.65,
fallbackOnBody: true,
fallbackTolerance: 3,
onEnd: (evt) => {
console.log(
'end',
evt.item,
props.value,
sorty.value.at(0).toArray(),
sorty.value.at(1).toArray()
);
},
onUpdate: (evt) => {
console.log('update', evt.item, props.value, sorty.value);
},
});
sorty.value.push(sortable); */
});
});
onMounted(() => {
const draggable = document.querySelectorAll('[data-nested-draggable]');
draggable.forEach((el) => {
const options = {
group: 'nested',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
ghostClass: 'dragged',
onEnd: (evt) => {
const { item, items } = evt;
console.log('standard', item, evt);
if (item.classList.contains('accordion')) {
const existingInstance = HSAccordion.getInstance(item, true);
let updatedInstance;
existingInstance.element.update();
updatedInstance = HSAccordion.getInstance(item, true);
window.$hsAccordionCollection.map((el) => {
if (
el.element.el !== existingInstance.element.el &&
el.element.group === existingInstance.element.group &&
el.element.el.closest('.accordion') &&
el.element.el.classList.contains('active') &&
existingInstance.element.el.classList.contains('active')
)
el.element.hide();
return el;
});
}
if (item.hasAttribute('data-tree-view-item')) {
const treeViewItem = HSTreeView.getInstance(
item.closest('[data-tree-view]'),
true
);
treeViewItem.element.update();
}
},
};
const data = el.getAttribute('data-nested-draggable');
const dataOptions = data ? JSON.parse(data) : {};
const sortable = new Sortable(el, options);
console.log('stand', sortable.toArray());
});
});
</script>

View File

@ -1,5 +0,0 @@
<template>
<div data-tree-view role="tree" aria-orientation="vertical" class="rounded min-w-fit w-full">
<slot/>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More