zwischenstand
@ -1,6 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
6
eslint.config.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
)
|
||||
@ -8,6 +8,7 @@ export default defineNuxtConfig({
|
||||
'@vueuse/nuxt',
|
||||
'@nuxt/icon',
|
||||
'nuxt-snackbar',
|
||||
'@nuxt/eslint',
|
||||
],
|
||||
|
||||
compatibilityDate: '2024-11-01',
|
||||
|
||||
16
package.json
@ -12,10 +12,12 @@
|
||||
"tauri": "tauri",
|
||||
"tauri:build:debug": "tauri build --debug",
|
||||
"drizzle:generate": "drizzle-kit generate",
|
||||
"drizzle:migrate": "drizzle-kit migrate"
|
||||
"drizzle:migrate": "drizzle-kit migrate",
|
||||
"eslint:fix": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.7",
|
||||
"@nuxt/eslint": "1.4.1",
|
||||
"@nuxt/icon": "^1.13.0",
|
||||
"@nuxtjs/i18n": "^9.5.4",
|
||||
"@pinia/nuxt": "^0.11.0",
|
||||
@ -31,6 +33,7 @@
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"@vueuse/nuxt": "^13.2.0",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"eslint": "^9.27.0",
|
||||
"flyonui": "^2.2.0",
|
||||
"nuxt": "^3.17.4",
|
||||
"nuxt-snackbar": "1.3.0",
|
||||
@ -44,7 +47,9 @@
|
||||
"@iconify/json": "^2.2.340",
|
||||
"@iconify/tailwind4": "^1.0.6",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"drizzle-kit": "^0.31.1"
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"globals": "^16.2.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"pnpm": {
|
||||
@ -53,5 +58,12 @@
|
||||
"esbuild",
|
||||
"vue-demi"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"singleAttributePerLine": true
|
||||
}
|
||||
}
|
||||
|
||||
989
pnpm-lock.yaml
generated
@ -20,9 +20,10 @@ tauri-build = { version = "2.2", features = [] }
|
||||
[dependencies]
|
||||
rusqlite = { version = "0.35.0", features = [
|
||||
"load_extension",
|
||||
"bundled-sqlcipher",
|
||||
"bundled-sqlcipher"
|
||||
] }
|
||||
#libsqlite3-sys = { version = "0.28", features = ["bundled-sqlcipher"] }
|
||||
#"bundled-sqlcipher"
|
||||
#libsqlite3-sys = { version = "0.33", features = ["bundled-sqlcipher"] }
|
||||
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
tokio = { version = "1.45", features = ["macros", "rt-multi-thread"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@ -3,43 +3,48 @@ import {
|
||||
sqliteTable,
|
||||
text,
|
||||
unique,
|
||||
type AnySQLiteColumn
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
type AnySQLiteColumn,
|
||||
} from 'drizzle-orm/sqlite-core'
|
||||
|
||||
export const haexSettings = sqliteTable("haex_settings", {
|
||||
export const haexSettings = sqliteTable('haex_settings', {
|
||||
id: text().primaryKey(),
|
||||
key: text(),
|
||||
value: text(),
|
||||
});
|
||||
})
|
||||
|
||||
export const haexExtensions = sqliteTable("haex_extensions", {
|
||||
export const haexExtensions = sqliteTable('haex_extensions', {
|
||||
id: text().primaryKey(),
|
||||
author: text(),
|
||||
enabled: integer({ mode: "boolean" }),
|
||||
icon: text(),
|
||||
name: text(),
|
||||
author: text(),
|
||||
enabled: integer({ mode: 'boolean' }),
|
||||
icon: text(),
|
||||
url: text(),
|
||||
version: text(),
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
export const haexExtensionsPermissions = sqliteTable(
|
||||
"haex_extensions_permissions",
|
||||
'haex_extensions_permissions',
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
extensionId: text("extension_id").references((): AnySQLiteColumn => haexExtensions.id),
|
||||
resource: text({ enum: ["fs", "http", "db", "shell"] }),
|
||||
operation: text({ enum: ["read", "write", "create"] }),
|
||||
extensionId: text('extension_id').references(
|
||||
(): AnySQLiteColumn => haexExtensions.id,
|
||||
),
|
||||
resource: text({ enum: ['fs', 'http', 'db', 'shell'] }),
|
||||
operation: text({ enum: ['read', 'write', 'create'] }),
|
||||
path: text(),
|
||||
},
|
||||
(table) => [unique().on(table.extensionId, table.resource, table.operation, table.path)]
|
||||
);
|
||||
(table) => [
|
||||
unique().on(table.extensionId, table.resource, table.operation, table.path),
|
||||
],
|
||||
)
|
||||
|
||||
export type InsertHaexSettings = typeof haexSettings.$inferInsert;
|
||||
export type SelectHaexSettings = typeof haexSettings.$inferSelect;
|
||||
export type InsertHaexSettings = typeof haexSettings.$inferInsert
|
||||
export type SelectHaexSettings = typeof haexSettings.$inferSelect
|
||||
|
||||
export type InsertHaexExtensions = typeof haexExtensions.$inferInsert;
|
||||
export type SelectHaexExtensions = typeof haexExtensions.$inferSelect;
|
||||
export type InsertHaexExtensions = typeof haexExtensions.$inferInsert
|
||||
export type SelectHaexExtensions = typeof haexExtensions.$inferSelect
|
||||
|
||||
export type InsertHaexExtensionsPermissions = typeof haexExtensionsPermissions.$inferInsert;
|
||||
export type SelectHaexExtensionsPermissions = typeof haexExtensionsPermissions.$inferSelect;
|
||||
export type InsertHaexExtensionsPermissions =
|
||||
typeof haexExtensionsPermissions.$inferInsert
|
||||
export type SelectHaexExtensionsPermissions =
|
||||
typeof haexExtensionsPermissions.$inferSelect
|
||||
|
||||
12
src-tauri/gen/android/.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
19
src-tauri/gen/android/.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
key.properties
|
||||
|
||||
/.tauri
|
||||
/tauri.settings.gradle
|
||||
6
src-tauri/gen/android/app/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/src/main/java/space/haex/hub/generated
|
||||
/src/main/jniLibs/**/*.so
|
||||
/src/main/assets/tauri.conf.json
|
||||
/tauri.build.gradle.kts
|
||||
/proguard-tauri.pro
|
||||
/tauri.properties
|
||||
69
src-tauri/gen/android/app/build.gradle.kts
Normal file
@ -0,0 +1,69 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rust")
|
||||
}
|
||||
|
||||
val tauriProperties = Properties().apply {
|
||||
val propFile = file("tauri.properties")
|
||||
if (propFile.exists()) {
|
||||
propFile.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
namespace = "space.haex.hub"
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "space.haex.hub"
|
||||
minSdk = 24
|
||||
targetSdk = 34
|
||||
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
||||
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
*fileTree(".") { include("**/*.pro") }
|
||||
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
.toList().toTypedArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "../../../"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.webkit:webkit:1.6.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
}
|
||||
|
||||
apply(from = "tauri.build.gradle.kts")
|
||||
21
src-tauri/gen/android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
37
src-tauri/gen/android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.haex_hub"
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/main_activity_title"
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<!-- AndroidTV support -->
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -0,0 +1,3 @@
|
||||
package space.haex.hub
|
||||
|
||||
class MainActivity : TauriActivity()
|
||||
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.haex_hub" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
10
src-tauri/gen/android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">haex-hub</string>
|
||||
<string name="main_activity_title">haex-hub</string>
|
||||
</resources>
|
||||
6
src-tauri/gen/android/app/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.haex_hub" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
22
src-tauri/gen/android/build.gradle.kts
Normal file
@ -0,0 +1,22 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.5.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
delete("build")
|
||||
}
|
||||
|
||||
23
src-tauri/gen/android/buildSrc/build.gradle.kts
Normal file
@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("pluginsForCoolKids") {
|
||||
id = "rust"
|
||||
implementationClass = "RustPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
implementation("com.android.tools.build:gradle:8.5.1")
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import java.io.File
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
open class BuildTask : DefaultTask() {
|
||||
@Input
|
||||
var rootDirRel: String? = null
|
||||
@Input
|
||||
var target: String? = null
|
||||
@Input
|
||||
var release: Boolean? = null
|
||||
|
||||
@TaskAction
|
||||
fun assemble() {
|
||||
val executable = """pnpm""";
|
||||
try {
|
||||
runTauriCli(executable)
|
||||
} catch (e: Exception) {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
runTauriCli("$executable.cmd")
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runTauriCli(executable: String) {
|
||||
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
|
||||
val target = target ?: throw GradleException("target cannot be null")
|
||||
val release = release ?: throw GradleException("release cannot be null")
|
||||
val args = listOf("tauri", "android", "android-studio-script");
|
||||
|
||||
project.exec {
|
||||
workingDir(File(project.projectDir, rootDirRel))
|
||||
executable(executable)
|
||||
args(args)
|
||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
||||
args("-vv")
|
||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
||||
args("-v")
|
||||
}
|
||||
if (release) {
|
||||
args("--release")
|
||||
}
|
||||
args(listOf("--target", target))
|
||||
}.assertNormalExitValue()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.get
|
||||
|
||||
const val TASK_GROUP = "rust"
|
||||
|
||||
open class Config {
|
||||
lateinit var rootDirRel: String
|
||||
}
|
||||
|
||||
open class RustPlugin : Plugin<Project> {
|
||||
private lateinit var config: Config
|
||||
|
||||
override fun apply(project: Project) = with(project) {
|
||||
config = extensions.create("rust", Config::class.java)
|
||||
|
||||
val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64");
|
||||
val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList
|
||||
|
||||
val defaultArchList = listOf("arm64", "arm", "x86", "x86_64");
|
||||
val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList
|
||||
|
||||
val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
@Suppress("UnstableApiUsage")
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList
|
||||
}
|
||||
}
|
||||
defaultArchList.forEachIndexed { index, arch ->
|
||||
create(arch) {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters.add(defaultAbiList[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
for (profile in listOf("debug", "release")) {
|
||||
val profileCapitalized = profile.replaceFirstChar { it.uppercase() }
|
||||
val buildTask = tasks.maybeCreate(
|
||||
"rustBuildUniversal$profileCapitalized",
|
||||
DefaultTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for all targets"
|
||||
}
|
||||
|
||||
tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask)
|
||||
|
||||
for (targetPair in targetsList.withIndex()) {
|
||||
val targetName = targetPair.value
|
||||
val targetArch = archList[targetPair.index]
|
||||
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
|
||||
val targetBuildTask = project.tasks.maybeCreate(
|
||||
"rustBuild$targetArchCapitalized$profileCapitalized",
|
||||
BuildTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for $targetArch"
|
||||
rootDirRel = config.rootDirRel
|
||||
target = targetName
|
||||
release = profile == "release"
|
||||
}
|
||||
|
||||
buildTask.dependsOn(targetBuildTask)
|
||||
tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn(
|
||||
targetBuildTask
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src-tauri/gen/android/gradle.properties
Normal file
@ -0,0 +1,24 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
BIN
src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Tue May 10 19:22:52 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
185
src-tauri/gen/android/gradlew
vendored
Executable file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
src-tauri/gen/android/gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
3
src-tauri/gen/android/settings.gradle
Normal file
@ -0,0 +1,3 @@
|
||||
include ':app'
|
||||
|
||||
apply from: 'tauri.settings.gradle'
|
||||
@ -199,6 +199,7 @@ pub fn extension_protocol_handler<R: Runtime>(
|
||||
let segments_iter = path_str.split('/').filter(|s| !s.is_empty());
|
||||
let resource_segments: Vec<&str> = segments_iter.collect();
|
||||
let raw_asset_path = resource_segments.join("/");
|
||||
let asset_to_load = if raw_asset_path.is_empty() { "index.html"} else {&raw_asset_path};
|
||||
|
||||
match process_hex_encoded_json(&host) {
|
||||
Ok(info) => {
|
||||
@ -209,7 +210,7 @@ pub fn extension_protocol_handler<R: Runtime>(
|
||||
context.app_handle(),
|
||||
&info.id,
|
||||
&info.version,
|
||||
&raw_asset_path,
|
||||
&asset_to_load,
|
||||
)?;
|
||||
|
||||
println!("absolute_secure_path: {}", absolute_secure_path.display());
|
||||
@ -222,9 +223,10 @@ pub fn extension_protocol_handler<R: Runtime>(
|
||||
.to_string();
|
||||
let content_length = content.len();
|
||||
println!(
|
||||
"Liefere {} ({}) ",
|
||||
"Liefere {} ({}, {} bytes) ", // Content-Length zum Log hinzugefügt
|
||||
absolute_secure_path.display(),
|
||||
mime_type
|
||||
mime_type,
|
||||
content_length
|
||||
);
|
||||
Response::builder()
|
||||
.status(200)
|
||||
@ -271,7 +273,7 @@ pub fn extension_protocol_handler<R: Runtime>(
|
||||
eprintln!("Fehler bei der Datenverarbeitung: {}", e);
|
||||
|
||||
Response::builder()
|
||||
.status("500")
|
||||
.status(500)
|
||||
.body(Vec::new()) // Leerer Body für Fehler
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<div class="browser">
|
||||
<div class="browser-controls">
|
||||
<button @click="$emit('goBack', activeTabId)" :disabled="!activeTabId">
|
||||
<button :disabled="!activeTabId" @click="$emit('goBack', activeTabId)">
|
||||
←
|
||||
</button>
|
||||
<button @click="$emit('goForward', activeTabId)" :disabled="!activeTabId">
|
||||
<button :disabled="!activeTabId" @click="$emit('goForward', activeTabId)">
|
||||
→
|
||||
</button>
|
||||
<button @click="$emit('createTab')">+</button>
|
||||
|
||||
<HaexBrowserUrlBar
|
||||
:url="activeTab?.url || ''"
|
||||
:isLoading="activeTab?.isLoading || false"
|
||||
:is-loading="activeTab?.isLoading || false"
|
||||
@submit="handleUrlSubmit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<HaexBrowserTabBar
|
||||
:tabs="tabs"
|
||||
:activeTabId="activeTabId"
|
||||
@closeTab="$emit('closeTab', $event)"
|
||||
@activateTab="$emit('activateTab', $event)"
|
||||
:active-tab-id="activeTabId"
|
||||
@close-tab="$emit('closeTab', $event)"
|
||||
@activate-tab="$emit('activateTab', $event)"
|
||||
/>
|
||||
|
||||
<div class="browser-content" ref="contentRef">
|
||||
<div ref="contentRef" class="browser-content">
|
||||
<!-- Die eigentlichen Webview-Inhalte werden von Tauri verwaltet -->
|
||||
<div v-if="!activeTabId" class="empty-state">
|
||||
<p>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<form class="url-bar" @submit.prevent="handleSubmit">
|
||||
<input type="text" v-model="inputValue" placeholder="URL eingeben" />
|
||||
<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>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<button :class="cn(
|
||||
<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
|
||||
)
|
||||
">
|
||||
|
||||
@ -1,39 +1,50 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<slot name="image" />
|
||||
|
||||
<div
|
||||
class="card border-4 shadow-md shadow-accent h-48 w-48 overflow-hidden hover:shadow-xl transition-shadow "
|
||||
v-bind="$attrs">
|
||||
<div class="absolute top-2 right-2">
|
||||
<UiButton class="btn-error btn-outline btn-sm btn-square" @click="$emit('remove')">
|
||||
<Icon name="mdi:trash" />
|
||||
</UiButton>
|
||||
<UiDropdown class="btn btn-sm btn-text btn-circle">
|
||||
<template #activator>
|
||||
<Icon name="mdi:dots-vertical" />
|
||||
</template>
|
||||
|
||||
<template #items>
|
||||
<UiButton class="btn-error btn-outline btn-sm " @click="showRemoveDialog = true">
|
||||
<Icon name="mdi:trash" /> {{ t("remove") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDropdown>
|
||||
</div>
|
||||
|
||||
<div class="card-header">
|
||||
<div v-if="$slots.title || name">
|
||||
<div class="flex justify-start gap-4">
|
||||
<div v-html="icon" class="shrink-0 size-10" />
|
||||
<h5 v-if="name" class="card-title m-0 my-auto">
|
||||
{{ name }}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-base-content/50">{{ manifest }}</div>
|
||||
<h5 v-if="name" class="card-title">
|
||||
{{ name }}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
<div
|
||||
class="card-body relative cursor-pointer"
|
||||
@click="navigateTo(useLocalePath()({ name: 'haexExtension', params: { extensionId: id } }))">
|
||||
<!-- <slot />
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="size-20 absolute bottom-2 right-2" v-html="icon" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="card-footer">
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<HaexExtensionDialogRemove v-model:open="showRemoveDialog" :extension @confirm="removeExtensionAsync" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||
const emit = defineEmits(["close", "submit", "remove"]);
|
||||
|
||||
defineProps<IHaexHubExtension>();
|
||||
const extension = defineProps<IHaexHubExtension>();
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
@ -50,4 +61,67 @@ watchEffect(async () => {
|
||||
emit("submit");
|
||||
}
|
||||
});
|
||||
|
||||
const showRemoveDialog = ref(false)
|
||||
const { add } = useSnackbar()
|
||||
const { t } = useI18n()
|
||||
const extensionStore = useExtensionsStore()
|
||||
|
||||
const removeExtensionAsync = async () => {
|
||||
if (!extension?.id || !extension?.version) {
|
||||
add({ type: 'error', text: 'Erweiterung kann nicht gelöscht werden' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await extensionStore.removeExtensionAsync(
|
||||
extension.id,
|
||||
extension.version
|
||||
)
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
|
||||
add({
|
||||
type: 'success',
|
||||
title: t('extension.remove.success.title', {
|
||||
extensionName: extension.name,
|
||||
}),
|
||||
text: t('extension.remove.success.text', {
|
||||
extensionName: extension.name,
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
add({
|
||||
type: 'error',
|
||||
title: t('extension.remove.error.title'),
|
||||
text: t('extension.remove.error.text', { error: JSON.stringify(error) }),
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
remove: Löschen
|
||||
extension:
|
||||
remove:
|
||||
success:
|
||||
text: 'Erweiterung {extensionName} wurde erfolgreich entfernt'
|
||||
title: '{extensionName} entfernt'
|
||||
error:
|
||||
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
|
||||
title: 'Fehler beim Entfernen von {extensionName}'
|
||||
|
||||
|
||||
en:
|
||||
remove: Remove
|
||||
extension:
|
||||
remove:
|
||||
success:
|
||||
text: 'Extension {extensionName} was removed'
|
||||
title: '{extensionName} removed'
|
||||
error:
|
||||
text: "Extension {extensionName} couldn't be removed. \n {error}"
|
||||
title: 'Exception during uninstall {extensionName}'
|
||||
|
||||
|
||||
</i18n>
|
||||
|
||||
118
src/components/haex/extension/dialog/install.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<UiDialogConfirm v-model:open="open" @abort="onDeny" @confirm="onConfirm">
|
||||
<template #title>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #extension>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<nav class="tabs tabs-bordered" aria-label="Tabs" role="tablist" aria-orientation="horizontal">
|
||||
<button
|
||||
v-show="manifest?.permissions?.database" id="tabs-basic-item-1" 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
|
||||
v-show="manifest?.permissions?.filesystem" id="tabs-basic-item-2" 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
|
||||
v-show="manifest?.permissions?.http" id="tabs-basic-item-3" 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>
|
||||
</nav>
|
||||
|
||||
<div class="mt-3 min-h-40">
|
||||
<div id="tabs-basic-1" role="tabpanel" aria-labelledby="tabs-basic-item-1">
|
||||
<HaexExtensionManifestPermissionsDatabase :database="permissions?.database" />
|
||||
</div>
|
||||
<div id="tabs-basic-2" class="hidden" role="tabpanel" aria-labelledby="tabs-basic-item-2">
|
||||
<HaexExtensionManifestPermissionsFilesystem :filesystem="permissions?.filesystem" />
|
||||
</div>
|
||||
<div id="tabs-basic-3" class="hidden" role="tabpanel" aria-labelledby="tabs-basic-item-3">
|
||||
<HaexExtensionManifestPermissionsHttp :http="permissions?.http" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
const { manifest } = defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||
|
||||
const permissions = computed(() => ({
|
||||
|
||||
database: {
|
||||
read: manifest?.permissions.database?.read?.map(read => ({
|
||||
[read]: true
|
||||
})),
|
||||
write: manifest?.permissions.database?.read?.map(write => ({
|
||||
[write]: true
|
||||
})),
|
||||
create: manifest?.permissions.database?.read?.map(create => ({
|
||||
[create]: true
|
||||
})),
|
||||
},
|
||||
|
||||
filesystem: {
|
||||
read: manifest?.permissions.filesystem?.read?.map(read => ({
|
||||
[read]: true
|
||||
})),
|
||||
write: manifest?.permissions.filesystem?.write?.map(write => ({
|
||||
[write]: true
|
||||
})),
|
||||
},
|
||||
|
||||
http: manifest?.permissions.http?.map(http => ({
|
||||
[http]: true
|
||||
})),
|
||||
}))
|
||||
|
||||
watch(permissions, () => console.log("permissions", permissions.value))
|
||||
const emit = defineEmits(["deny", "confirm"]);
|
||||
|
||||
const onDeny = () => {
|
||||
open.value = false;
|
||||
console.log("onDeny open", open.value);
|
||||
emit("deny");
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
console.log("onConfirm open", open.value);
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"title": "Erweiterung hinzufügen",
|
||||
"question": "Erweiterung {extension} hinzufügen?",
|
||||
"confirm": "Bestätigen",
|
||||
"deny": "Ablehnen",
|
||||
"database": "Datenbank",
|
||||
"http": "Internet",
|
||||
"filesystem": "Dateisystem"
|
||||
},
|
||||
"en": {
|
||||
"title": "Confirm Permission",
|
||||
"question": "Add Extension {extension}?",
|
||||
"confirm": "Confirm",
|
||||
"deny": "Deny",
|
||||
"database": "Database",
|
||||
"http": "Internet",
|
||||
"filesystem": "Filesystem"
|
||||
}
|
||||
}</i18n>
|
||||
33
src/components/haex/extension/dialog/reinstall.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<UiDialogConfirm v-model:open="open">
|
||||
<template #title>
|
||||
<i18n-t keypath="title" tag="p">
|
||||
<template #extensionName>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<p>{{ t("question", { extensionName: manifest?.name }) }}</p>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
const { manifest } = defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: "{extensionName} bereits installiert"
|
||||
question: Soll die Erweiterung {extensionName} erneut installiert werden?
|
||||
|
||||
en:
|
||||
title: "{extensionName} is already installed"
|
||||
question: Do you want to reinstall {extensionName}?
|
||||
</i18n>
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<UiDialogConfirm v-model:open="open" :title="t('title')" @confirm="onConfirm">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #name>
|
||||
@ -7,16 +7,7 @@
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<template #buttons>
|
||||
<UiButton class="btn-outline btn-error" @click="open = false">
|
||||
<Icon name="mdi:cancel" /> {{ t("abort") }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton class="btn-error" @click="onConfirm">
|
||||
<Icon name="mdi:trash" /> {{ t("remove") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -36,8 +27,7 @@ const onConfirm = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"title": "Erweiterung löschen",
|
||||
"question": "Soll {name} wirklich gelöscht werden?",
|
||||
@ -46,9 +36,8 @@ const onConfirm = () => {
|
||||
},
|
||||
"en": {
|
||||
"title": "Remove Extension",
|
||||
"question": "Soll {name} wirklich gelöscht werden?",
|
||||
"question": "Should {name} really be deleted?",
|
||||
"abort": "Abort",
|
||||
"remove": "Remove"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
}</i18n>
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #extension>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<!-- {{ t("question", { extension: manifest?.name }) }}
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span> zu HaexHub hinzufügen? -->
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<HaexExtensionManifestPermissionsFilesystem
|
||||
v-if="manifest?.permissions?.filesystem"
|
||||
:filesystem="manifest?.permissions?.filesystem"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsDatabase
|
||||
v-if="manifest?.permissions?.database"
|
||||
:database="manifest?.permissions?.database"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsHttp
|
||||
v-if="manifest?.permissions?.http"
|
||||
:http="manifest?.permissions?.http"
|
||||
/>
|
||||
<!-- <VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("filesystem.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.filesystem }}
|
||||
</div>
|
||||
</VaultCard>
|
||||
|
||||
<VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("http.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.http }}
|
||||
</div>
|
||||
</VaultCard> -->
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton @click="onDeny" class="btn-error btn-outline">{{ t("deny") }} </UiButton>
|
||||
<UiButton @click="onConfirm" class="btn-success btn-outline">{{ t("confirm") }}</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||
|
||||
const emit = defineEmits(["deny", "confirm"]);
|
||||
|
||||
const onDeny = () => {
|
||||
open.value = false;
|
||||
console.log("onDeny open", open.value);
|
||||
emit("deny");
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
console.log("onConfirm open", open.value);
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung hinzufügen",
|
||||
"question": "Möchtest du die Erweiterung {extension} hinzufügen?",
|
||||
"confirm": "Bestätigen",
|
||||
"deny": "Ablehnen",
|
||||
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
},
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
},
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -1,63 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("database.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
<UiAccordion v-if="database?.read?.length">
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
|
||||
<div v-if="database?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="read in database?.read" class="flex items-center justify-between px-4 py-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<input :id="Object.keys(read).at(0)" type="checkbox" class="checkbox" :checked="Object.values(read).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(read).at(0)">{{ Object.keys(read).at(0) }}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="read in database?.read">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
<UiAccordion v-if="database?.write?.length">
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<div v-if="database?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="write in database?.write" class="flex items-center justify-between px-4 py-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
:id="Object.keys(write).at(0)" type="checkbox" class="checkbox"
|
||||
:checked="Object.values(write).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(write).at(0)">{{ Object.keys(write).at(0) }}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in database?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
<UiAccordion v-if="database?.create?.length">
|
||||
<template #title>
|
||||
<h3>{{ t("permission.create") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="create in database?.create" class="flex items-center justify-between px-4 py-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
:id="Object.keys(create).at(0)" type="checkbox" class="checkbox"
|
||||
:checked="Object.values(create).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(create).at(0)">{{ Object.keys(create).at(0) }}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ database: IHaexHubExtensionManifest["permissions"]["database"] }>();
|
||||
|
||||
|
||||
defineProps<{ database?: { read?: Record<string, boolean>[], write?: Record<string, boolean>[], create?: Record<string, boolean>[] } }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
permission:
|
||||
read: Lesen
|
||||
write: Schreiben
|
||||
create: Erstellen
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
en:
|
||||
permission:
|
||||
read: Read
|
||||
write: Write
|
||||
create: Create
|
||||
</i18n>
|
||||
|
||||
@ -1,65 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("filesystem.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
<UiAccordion v-if="filesystem?.read?.length">
|
||||
<template #title>
|
||||
<h3>{{ t('permission.read') }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="read in filesystem?.read" class="flex items-center justify-between px-4 py-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<input :id="Object.keys(read).at(0)" type="checkbox" class="checkbox" :checked="Object.values(read).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(read).at(0)">{{
|
||||
Object.keys(read).at(0)
|
||||
}}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
|
||||
<div v-if="filesystem?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="read in filesystem?.read"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
<UiAccordion v-if="filesystem?.write?.length">
|
||||
<template #title>
|
||||
<h3>{{ t('permission.write') }}</h3>
|
||||
</template>
|
||||
|
||||
<div v-if="filesystem?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in filesystem?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="write in filesystem?.write" class="flex items-center justify-between px-4 py-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
:id="Object.keys(write).at(0)" type="checkbox" class="checkbox"
|
||||
:checked="Object.values(write).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(write).at(0)">{{
|
||||
Object.keys(write).at(0)
|
||||
}}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ filesystem: IHaexHubExtensionManifest["permissions"]["filesystem"] }>();
|
||||
const { t } = useI18n();
|
||||
defineProps<{
|
||||
filesystem?: {
|
||||
read?: Record<string, boolean>[]
|
||||
write?: Record<string, boolean>[]
|
||||
}
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
permission:
|
||||
read: Lesen
|
||||
write: Schreiben
|
||||
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
en:
|
||||
permission:
|
||||
read: Read
|
||||
write: Write
|
||||
</i18n>
|
||||
|
||||
@ -1,43 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("http.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("http.access") }}</h3>
|
||||
</template>
|
||||
|
||||
<div v-if="http?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.access") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="access in http">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ access }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-0.5">
|
||||
<li v-for="access in http" class="flex items-center justify-between px-4 py-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
:id="Object.keys(access).at(0)" type="checkbox" class="checkbox"
|
||||
:checked="Object.values(access).at(0)" >
|
||||
<label class="label-text text-base" :for="Object.keys(access).at(0)">{{ Object.keys(access).at(0) }}</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ http: IHaexHubExtensionManifest["permissions"]["http"] }>();
|
||||
defineProps<{ http?: Record<string, boolean>[] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"access": "Zugriff"
|
||||
},
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
http:
|
||||
access: Internet Zugriff
|
||||
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
en:
|
||||
http:
|
||||
access: Internet Access
|
||||
</i18n>
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
|
||||
<!-- <div class="">
|
||||
-->
|
||||
<HaexButton v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
<HaexButton
|
||||
v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)">
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</Haexbutton>
|
||||
@ -19,7 +20,7 @@
|
||||
<label class="floating-label input join-item">
|
||||
<Icon v-if="iconPrepend" :name="iconPrepend" class="my-auto size-6" />
|
||||
<span>Your Email</span>
|
||||
<input type="text" placeholder="mail@site.com" class=" join-item " />
|
||||
<input type="text" placeholder="mail@site.com" class=" join-item " >
|
||||
<Icon v-if="iconAppend" :name="iconAppend" class="my-auto shrink-0" />
|
||||
</label>
|
||||
|
||||
@ -28,13 +29,14 @@
|
||||
|
||||
<slot name="append" class="h-auto" />
|
||||
|
||||
<HaexButton v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
<HaexButton
|
||||
v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)">
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</Haexbutton>
|
||||
</fieldset>
|
||||
|
||||
<span class="flex flex-col px-2 pt-0.5" v-show="errors">
|
||||
<span v-show="errors" class="flex flex-col px-2 pt-0.5">
|
||||
<span v-for="error in errors" class="label-text-alt text-error">
|
||||
{{ error }}
|
||||
</span>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
<li>
|
||||
<NuxtLinkLocale class="dropdown-item" :to="{ name: 'haexSettings' }">
|
||||
<span class="icon-[tabler--settings]"></span>
|
||||
<span class="icon-[tabler--settings]"/>
|
||||
{{ t('settings') }}
|
||||
</NuxtLinkLocale>
|
||||
</li>
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<li class="dropdown-footer gap-2">
|
||||
<button class="btn btn-error btn-soft btn-block" @click="onVaultCloseAsync">
|
||||
<span class="icon-[tabler--logout]"></span>
|
||||
<span class="icon-[tabler--logout]"/>
|
||||
{{ t('vault.close') }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
190
src/components/haex/pass/entry/index.vue
Normal file
@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<VaultCardEdit
|
||||
v-if="vaultEntry.details" v-model:read_only="read_only" :color="currentGroup?.color || 'text-base-content'"
|
||||
:has-changes="hasChanges" :icon="vaultEntry.details?.icon || icon || 'mdi:key-outline'"
|
||||
:title="vaultEntry.details?.title ?? ''" @back="$emit('back')" @close="$emit('close')"
|
||||
@reject="(to) => $emit('reject', to)" @submit="(to) => $emit('submit', to)">
|
||||
<div class="h-full relative overflow-hidden">
|
||||
<nav
|
||||
aria-label="Tabs Vault Entry" aria-orientation="horizontal"
|
||||
class="tabs tabs-bordered w-full transition-all duration-700 sticky top-0 z-10 bg-base-200" role="tablist">
|
||||
<button
|
||||
:id="id.details" aria-controls="vaultDetailsId" aria-selected="true"
|
||||
class="tab active-tab:tab-active active w-full" data-tab="#vaultDetailsId" role="tab" type="button">
|
||||
<Icon name="material-symbols:key-outline" class="me-2" />
|
||||
<span class="hidden sm:block">
|
||||
{{ 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">
|
||||
<div id="vaultDetailsId" role="tabpanel" :aria-labelledby="id.details" class="h-full">
|
||||
<VaultEntryDetails v-if="vaultEntry.details" v-model="vaultEntry.details" :with-copy-button :read_only/>
|
||||
</div>
|
||||
|
||||
<div id="tabs-basic-2" class="hidden" role="tabpanel" :aria-labelledby="id.keyValue">
|
||||
{{ originally }}
|
||||
</div>
|
||||
|
||||
<div id="tabs-basic-3" class="hidden h-full" role="tabpanel" :aria-labelledby="id.history">
|
||||
<VaultEntryHistory v-if="vaultEntry.history" :history="vaultEntry.history" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VaultCardEdit>
|
||||
<!-- <VaultModalSaveChanges
|
||||
v-model="showConfirmation"
|
||||
@reject="onReject"
|
||||
@submit="onSubmit"
|
||||
/> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const vaultEntry = defineModel<IVaultEntryComplete>({ required: true });
|
||||
|
||||
|
||||
const { currentGroup } = storeToRefs(useVaultGroupStore());
|
||||
|
||||
/* watch(
|
||||
() => vaultEntry.value.details,
|
||||
() => {
|
||||
header.value.text = vaultEntry.value.details?.title;
|
||||
header.value.icon =
|
||||
vaultEntry.value.details?.icon || currentGroup.value?.icon;
|
||||
},
|
||||
{ immediate: true }
|
||||
); */
|
||||
|
||||
const id = reactive({
|
||||
details: useId(),
|
||||
keyValue: useId(),
|
||||
history: useId(),
|
||||
content: {},
|
||||
});
|
||||
|
||||
const read_only = defineModel<boolean>('read_only', { default: false });
|
||||
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
originally: Object as PropType<IVaultEntryComplete>,
|
||||
title: String,
|
||||
withCopyButton: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
close: [void];
|
||||
back: [void];
|
||||
reject: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
}>();
|
||||
|
||||
const showConfirmation = ref(false);
|
||||
|
||||
const hasChanges = computed(() => {
|
||||
if (!props.originally?.details) {
|
||||
if (
|
||||
vaultEntry.value.details?.note?.length ||
|
||||
vaultEntry.value.details?.password?.length ||
|
||||
vaultEntry.value.details?.tags?.length ||
|
||||
vaultEntry.value.details?.title?.length ||
|
||||
vaultEntry.value.details?.url?.length ||
|
||||
vaultEntry.value.details?.urlAliases?.length ||
|
||||
vaultEntry.value.details?.username?.length
|
||||
) {
|
||||
console.log('has changes', props.originally, vaultEntry.value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (
|
||||
JSON.stringify(props.originally.details) !==
|
||||
JSON.stringify(vaultEntry.value.details)
|
||||
);
|
||||
});
|
||||
|
||||
const to = ref<RouteLocationNormalizedLoadedGeneric>();
|
||||
|
||||
const isSaved = ref(false);
|
||||
const isRejected = ref(false);
|
||||
|
||||
const onSubmit = () => {
|
||||
console.log('entry onSubmit');
|
||||
showConfirmation.value = false;
|
||||
isSaved.value = true;
|
||||
emit('submit', to.value);
|
||||
};
|
||||
|
||||
const onReject = () => {
|
||||
console.log('entry onReject');
|
||||
showConfirmation.value = false;
|
||||
isRejected.value = true;
|
||||
emit('reject', to.value);
|
||||
};
|
||||
|
||||
const onBack = () => {
|
||||
console.log('entry onBack', read_only.value);
|
||||
if (hasChanges.value) {
|
||||
showConfirmation.value = true;
|
||||
} else {
|
||||
emit('back');
|
||||
}
|
||||
};
|
||||
|
||||
/* onBeforeRouteLeave((_to, _from, next) => {
|
||||
console.log('check before leave', _to, _from);
|
||||
to.value = _to;
|
||||
if (isSaved.value || isRejected.value) {
|
||||
isSaved.value = false;
|
||||
isRejected.value = false;
|
||||
next();
|
||||
} else if (hasChanges.value) {
|
||||
showConfirmation.value = true;
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}); */
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"create": "Anlegen",
|
||||
"abort": "Abbrechen",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "Verlauf"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "History"
|
||||
}
|
||||
}
|
||||
}</i18n>
|
||||
165
src/components/haex/pass/sidebar/index.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
|
||||
|
||||
<aside :id ref="sidebarRef" class=" flex sm:shadow-none w-full md:max-w-64 bg-red-200" 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>
|
||||
@ -1,16 +1,10 @@
|
||||
<template>
|
||||
<li
|
||||
@click="triggerNavigate"
|
||||
class="hover:text-primary rounded"
|
||||
:class="{ ['bg-base-200 text-base-content']: isActive }"
|
||||
>
|
||||
class="hover:text-primary rounded" :class="{ ['bg-base-200 text-base-content']: isActive }"
|
||||
@click="triggerNavigate">
|
||||
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
|
||||
<NuxtLinkLocale
|
||||
:to
|
||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||
ref="linkRef"
|
||||
>
|
||||
<div v-if="iconType === 'svg'" v-html="icon" class="shrink-0 size-5" />
|
||||
<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>
|
||||
</UiTooltip>
|
||||
@ -18,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ISidebarItem } from '#imports'
|
||||
import type { ISidebarItem } from '#imports'
|
||||
|
||||
const props = defineProps<ISidebarItem>()
|
||||
|
||||
|
||||
37
src/components/ui/accordion/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<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>
|
||||
52
src/components/ui/dialog/confirm.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<UiDialog v-model:open="open" @close="onAbort">
|
||||
<template #trigger>
|
||||
<slot name="trigger" />
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
|
||||
<template #buttons>
|
||||
<slot name="buttons">
|
||||
<UiButton class="btn-error btn-outline" @click="onAbort">
|
||||
<Icon name="mdi:close" /> {{ abortLabel ?? t("abort") }}
|
||||
</UiButton>
|
||||
<UiButton class="btn-primary " @click="onConfirm">
|
||||
<Icon name="mdi:check" /> {{ confirmLabel ?? t("confirm") }}
|
||||
</UiButton>
|
||||
</slot>
|
||||
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ confirmLabel?: string, abortLabel?: string }>()
|
||||
const open = defineModel<boolean>("open", { default: false })
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits(["confirm", "abort"])
|
||||
|
||||
const onAbort = () => {
|
||||
open.value = false
|
||||
emit("abort")
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false
|
||||
emit("confirm")
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
abort: Abbrechen
|
||||
confirm: Bestätigen
|
||||
|
||||
en:
|
||||
abort: Abort
|
||||
confirm: Confirm
|
||||
</i18n>
|
||||
@ -1,29 +1,58 @@
|
||||
<template>
|
||||
<button v-bind="$attrs" type="button" aria-haspopup="dialog" aria-expanded="false" :aria-label="label"
|
||||
class="--prevent-on-load-init " @click="$emit('open')">
|
||||
<slot name="trigger">open</slot>
|
||||
<button
|
||||
v-if="$slots.trigger || label"
|
||||
v-bind="$attrs"
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
:aria-label="label"
|
||||
class="--prevent-on-load-init"
|
||||
@click="$emit('open')"
|
||||
>
|
||||
<slot name="trigger">
|
||||
{{ label }}
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
<div :id class="overlay modal overlay-open:opacity-100 hidden overlay-open:duration-300" role="dialog" ref="modalRef"
|
||||
tabindex="-1">
|
||||
<div
|
||||
:id
|
||||
ref="modalRef"
|
||||
class="overlay modal overlay-open:opacity-100 hidden overlay-open:duration-300 modal-middle"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="overlay-animation-target overlay-open:mt-4 overlay-open:duration-500 mt-12 transition-all ease-out modal-dialog overlay-open:opacity-100">
|
||||
<div class="modal-content">
|
||||
class="overlay-animation-target overlay-open:mt-4 overlay-open:duration-500 mt-12 transition-all ease-out modal-dialog overlay-open:opacity-100"
|
||||
>
|
||||
<div class="modal-content gap-2">
|
||||
<div class="modal-header">
|
||||
<slot name="title">
|
||||
<h3 v-if="title" class="modal-title text-base sm:text-lg">
|
||||
<div
|
||||
v-if="title || $slots.title"
|
||||
class="modal-title"
|
||||
>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</slot>
|
||||
</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
|
||||
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 py-1">
|
||||
|
||||
<div class="modal-body text-sm sm:text-base">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="modal-footer flex-wrap">
|
||||
<slot name="buttons" />
|
||||
</div>
|
||||
@ -33,65 +62,50 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HSOverlay } from "flyonui/flyonui";
|
||||
import type { HSOverlay } from 'flyonui/flyonui'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
defineProps<{ title?: string; label?: string }>()
|
||||
|
||||
defineProps<{ title?: string; label?: string }>();
|
||||
const emit = defineEmits(['open', 'close'])
|
||||
|
||||
defineEmits(["open", "close"]);
|
||||
const id = useId()
|
||||
|
||||
const id = useId();
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
const { t } = useI18n()
|
||||
|
||||
const { t } = useI18n();
|
||||
const modalRef = useTemplateRef('modalRef')
|
||||
|
||||
const modalRef = useTemplateRef("modalRef");
|
||||
defineExpose({ modalRef })
|
||||
|
||||
defineExpose({ modalRef });
|
||||
|
||||
const modal = ref<HSOverlay>();
|
||||
const modal = ref<HSOverlay>()
|
||||
|
||||
watch(open, async () => {
|
||||
console.log("watch open modal", open.value, modal.value);
|
||||
if (open.value) {
|
||||
await modal.value?.open();
|
||||
await modal.value?.open()
|
||||
} else {
|
||||
await modal.value?.close(true);
|
||||
//HSOverlay.close(`#${id}`);
|
||||
//console.log("close dialog");
|
||||
await modal.value?.close(true)
|
||||
emit('close')
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!modalRef.value) return;
|
||||
// flyonui has a problem importing HSOverlay at component level due to ssr
|
||||
// that's the workaround I found
|
||||
//const flyonui = await import("flyonui/flyonui");
|
||||
if (!modalRef.value) return
|
||||
|
||||
modal.value = new window.HSOverlay(modalRef.value, {
|
||||
isClosePrev: true,
|
||||
});
|
||||
})
|
||||
|
||||
modal.value.on("close", () => {
|
||||
console.log("on close", open.value);
|
||||
open.value = false;
|
||||
});
|
||||
/* modal.value.on("open", () => {
|
||||
console.log("on open", open.value);
|
||||
open.value = true;
|
||||
}); */
|
||||
});
|
||||
modal.value.on('close', () => {
|
||||
open.value = false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"close": "Schließen"
|
||||
},
|
||||
"en": {
|
||||
"close": "Close"
|
||||
}
|
||||
}</i18n>
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
close: Schließen
|
||||
|
||||
en:
|
||||
close: Close
|
||||
</i18n>
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="dropdown relative inline-flex">
|
||||
<button :id class="dropdown-toggle " v-bind="$attrs" aria-haspopup="menu" aria-expanded="false" :aria-label="label">
|
||||
<button :id class="dropdown-toggle" v-bind="$attrs" aria-haspopup="menu" aria-expanded="false" :aria-label="label">
|
||||
<slot name="activator">
|
||||
{{ label }}
|
||||
<span class="icon-[tabler--chevron-down] dropdown-open:rotate-180 size-4">
|
||||
</span>
|
||||
<span class="icon-[tabler--chevron-down] dropdown-open:rotate-180 size-4"/>
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28" role="menu" aria-orientation="vertical"
|
||||
<ul
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28" role="menu" aria-orientation="vertical"
|
||||
:aria-labelledby="id">
|
||||
|
||||
<slot name="items">
|
||||
|
||||
|
||||
<li v-for="item in items" :is="itemIs" class="dropdown-item" @click="$emit('select', item)">
|
||||
<li :is="itemIs" v-for="item in items" class="dropdown-item" @click="$emit('select', item)">
|
||||
<slot name="item" :item>
|
||||
{{ item }}
|
||||
</slot>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<UiDropdown :items="availableLocales" @select="(locale) => $emit('select', locale)"
|
||||
class="btn btn-primary btn-outline">
|
||||
<UiDropdown
|
||||
:items="availableLocales" class="btn btn-primary btn-outline"
|
||||
@select="(locale) => $emit('select', locale)">
|
||||
<template #activator>
|
||||
|
||||
<Icon :name="flags[locale]" />
|
||||
@ -20,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Locale } from 'vue-i18n'
|
||||
import type { Locale } from 'vue-i18n'
|
||||
|
||||
const flags = {
|
||||
de: 'emojione:flag-for-germany',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UiDropdown :items="availableThemes" @select="(theme) => $emit('select', theme)" class="btn btn-primary btn-outline">
|
||||
<UiDropdown :items="availableThemes" class="btn btn-primary btn-outline" @select="(theme) => $emit('select', theme)">
|
||||
<template #activator>
|
||||
<Icon :name="currentTheme.icon" />
|
||||
</template>
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<fieldset class="join w-full pt-0.5">
|
||||
<fieldset class="join w-full pt-1.5 " v-bind="$attrs">
|
||||
<slot name="prepend" />
|
||||
|
||||
<div class="input join-item">
|
||||
<Icon v-if="prependIcon" :name="prependIcon" class="my-auto shrink-0" />
|
||||
|
||||
<div class="input-floating grow">
|
||||
<input :id :name="name ?? id" :placeholder="placeholder || label" :type :autofocus class="ps-3"
|
||||
v-bind="$attrs" v-model="input" ref="inputRef" :readonly="read_only" />
|
||||
<input
|
||||
:id ref="inputRef" v-model="input" :name="name ?? id" :placeholder="placeholder || label" :type
|
||||
:autofocus class="ps-3" :readonly="read_only" >
|
||||
<label class="input-floating-label" :for="id">{{ label }}</label>
|
||||
</div>
|
||||
|
||||
@ -17,13 +18,14 @@
|
||||
|
||||
<slot name="append" class="h-auto" />
|
||||
|
||||
<UiButton v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
<UiButton
|
||||
v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)">
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</UiButton>
|
||||
</fieldset>
|
||||
|
||||
<span class="flex flex-col px-2 pt-0.5" v-show="errors">
|
||||
<span v-show="errors" class="flex flex-col px-2 pt-0.5">
|
||||
<span v-for="error in errors" class="label-text-alt text-error">
|
||||
{{ error }}
|
||||
</span>
|
||||
@ -32,7 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ZodSchema } from "zod";
|
||||
import type { ZodSchema } from "zod";
|
||||
|
||||
const inputRef = useTemplateRef("inputRef");
|
||||
defineExpose({ inputRef });
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<UiInput :check-input :label="label || t('password')" :placeholder="placeholder || t('password')" :rules :type="type"
|
||||
:autofocus v-model="value">
|
||||
<UiInput
|
||||
v-model="value" :check-input :label="label || t('password')" :placeholder="placeholder || t('password')" :rules
|
||||
:type="type" :autofocus>
|
||||
<template #append>
|
||||
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType">
|
||||
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" />
|
||||
|
||||
@ -1,51 +1,59 @@
|
||||
<template>
|
||||
<svg id="logo" class="fill-current stroke-current w-[160px]" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg
|
||||
id="logo" class="fill-current stroke-current w-[160px]" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 286.3 85" xml:space="preserve">
|
||||
<switch>
|
||||
<g>
|
||||
<g class="logo-imagesss">
|
||||
<circle fill="white" cx="42.5" cy="42.5" r="40"></circle>
|
||||
<path d="M42.3,83.4c-22.6,0-40.9-18.4-40.9-40.9c0-22.6,18.4-40.9,40.9-40.9c22.6,0,40.9,18.4,40.9,40.9
|
||||
<circle fill="white" cx="42.5" cy="42.5" r="40"/>
|
||||
<path
|
||||
d="M42.3,83.4c-22.6,0-40.9-18.4-40.9-40.9c0-22.6,18.4-40.9,40.9-40.9c22.6,0,40.9,18.4,40.9,40.9
|
||||
C83.3,65.1,64.9,83.4,42.3,83.4z M42.3,5.8C22.1,5.8,5.7,22.3,5.7,42.5s16.5,36.7,36.7,36.7S79,62.7,79,42.5S62.6,5.8,42.3,5.8z
|
||||
"></path>
|
||||
"/>
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="38.8,69.8 38.8,31.7 22.3,31.7 22.3,38.5 29.8,38.5 29.8,69.8 "></polygon>
|
||||
<path d="M34.1,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6
|
||||
C39.9,15.9,37.3,13.2,34.1,13.2z"></path>
|
||||
<polygon points="38.8,69.8 38.8,31.7 22.3,31.7 22.3,38.5 29.8,38.5 29.8,69.8 "/>
|
||||
<path
|
||||
d="M34.1,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6
|
||||
C39.9,15.9,37.3,13.2,34.1,13.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon points="45.9,69.8 45.9,31.7 62.4,31.7 62.4,38.5 54.9,38.5 54.9,69.8 "></polygon>
|
||||
<path d="M50.6,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
|
||||
C44.8,15.9,47.4,13.2,50.6,13.2z"></path>
|
||||
<polygon points="45.9,69.8 45.9,31.7 62.4,31.7 62.4,38.5 54.9,38.5 54.9,69.8 "/>
|
||||
<path
|
||||
d="M50.6,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
|
||||
C44.8,15.9,47.4,13.2,50.6,13.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g class="logo-textsss">
|
||||
<path
|
||||
d="M136.1,63.6c-4,0-5.3-2.6-5.3-6V38.5h10.6v-6.7h-10.6v-6.7h-9c0,7,0,29.1,0,32.7
|
||||
c0,4.2,1.6,7.5,3.8,9.7c2.3,2.2,5.6,3.3,9.8,3.3c5.1,0,8.4-1.8,10.6-4.2l-4.7-6C140.2,62.1,138.5,63.6,136.1,63.6z">
|
||||
</path>
|
||||
<path d="M217.7,30.7c-4.9,0-8.2,1.6-10.4,3.8c-2.2-2.2-5.5-3.8-10.4-3.8c-15,0-14.9,12.1-14.9,15
|
||||
c0,4.2,1.6,7.5,3.8,9.7c2.3,2.2,5.6,3.3,9.8,3.3c5.1,0,8.4-1.8,10.6-4.2l-4.7-6C140.2,62.1,138.5,63.6,136.1,63.6z"/>
|
||||
<path
|
||||
d="M217.7,30.7c-4.9,0-8.2,1.6-10.4,3.8c-2.2-2.2-5.5-3.8-10.4-3.8c-15,0-14.9,12.1-14.9,15
|
||||
s0,24.1,0,24.1h9V45.7c0-8.5,4.9-8.3,5.9-8.3c1,0,5.9-0.3,5.9,8.3v24.1h0h9h0V45.7c0-8.5,4.9-8.3,5.9-8.3c1,0,5.9-0.3,5.9,8.3
|
||||
v24.1h9c0,0,0-21.2,0-24.1C232.6,42.8,232.7,30.7,217.7,30.7z"></path>
|
||||
<path d="M273.2,46.4c-4.3-1.4-6-2.5-6-5.2c0-2,1.1-3.8,4.3-3.8c3.2,0,4.5,3.3,5.1,4.8
|
||||
v24.1h9c0,0,0-21.2,0-24.1C232.6,42.8,232.7,30.7,217.7,30.7z"/>
|
||||
<path
|
||||
d="M273.2,46.4c-4.3-1.4-6-2.5-6-5.2c0-2,1.1-3.8,4.3-3.8c3.2,0,4.5,3.3,5.1,4.8
|
||||
c2.7-1.5,5.3-2.9,6.6-3.6c-2.5-6-6.3-7.9-12-7.9c-8,0-11.7,5.5-11.7,10.6c0,6.5,2.9,9.8,11.2,12.2c6,1.8,6.5,4.7,6.2,6.2
|
||||
c-0.3,1.7-1.6,3.6-5.3,3.6c-3.6,0-5.8-3.8-6.8-5.4c-1.8,1.1-3.4,2.1-6.4,3.8c2.1,5,6.8,9.1,13.5,9.1c7.9,0,12.9-5.1,12.9-12.1
|
||||
C284.9,51,279.6,48.5,273.2,46.4z"></path>
|
||||
C284.9,51,279.6,48.5,273.2,46.4z"/>
|
||||
<g>
|
||||
<polygon points="239.7,69.8 239.7,31.7 256.2,31.7 256.2,38.5 248.7,38.5 248.7,69.8 "></polygon>
|
||||
<path d="M244.4,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
|
||||
C238.6,15.9,241.2,13.2,244.4,13.2z"></path>
|
||||
<polygon points="239.7,69.8 239.7,31.7 256.2,31.7 256.2,38.5 248.7,38.5 248.7,69.8 "/>
|
||||
<path
|
||||
d="M244.4,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
|
||||
C238.6,15.9,241.2,13.2,244.4,13.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon points="114.7,69.8 114.7,31.7 98.1,31.7 98.1,38.5 105.7,38.5 105.7,69.8 "></polygon>
|
||||
<path d="M110,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6C115.8,15.9,113.2,13.2,110,13.2
|
||||
z"></path>
|
||||
<polygon points="114.7,69.8 114.7,31.7 98.1,31.7 98.1,38.5 105.7,38.5 105.7,69.8 "/>
|
||||
<path
|
||||
d="M110,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6C115.8,15.9,113.2,13.2,110,13.2
|
||||
z"/>
|
||||
</g>
|
||||
<path d="M176.4,52.4v-3.7c0-12.3-4.7-18-14.8-18c-9.3,0-14.7,6.6-14.7,18v4c0,11.5,5.8,18.2,15.8,18.2
|
||||
<path
|
||||
d="M176.4,52.4v-3.7c0-12.3-4.7-18-14.8-18c-9.3,0-14.7,6.6-14.7,18v4c0,11.5,5.8,18.2,15.8,18.2
|
||||
c6.6,0,10.8-3.7,12.7-7.9c-2.2-1.4-4.6-2.8-6.1-3.8c-1,1.7-2.9,4.4-6.7,4.4c-5.8,0-7-5.9-7-10.9v-0.2H176.4z M155.7,45.7
|
||||
c0.2-7.1,3.3-8.2,6-8.2c2.6,0,5.9,1,6,8.2H155.7z"></path>
|
||||
c0.2-7.1,3.3-8.2,6-8.2c2.6,0,5.9,1,6,8.2H155.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</switch>
|
||||
|
||||
@ -1,14 +1,23 @@
|
||||
<template>
|
||||
<div class="tooltip [--prevent-popper:false]">
|
||||
<div class="tooltip-toggle" aria-label="Tooltip">
|
||||
<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 z-40" role="tooltip">
|
||||
<span class="tooltip-body" v-bind="$attrs">
|
||||
<span
|
||||
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible z-40"
|
||||
role="tooltip"
|
||||
>
|
||||
<span
|
||||
class="tooltip-body"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
{{ tooltip }}
|
||||
</span>
|
||||
</span>
|
||||
@ -17,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
direction: {
|
||||
@ -38,15 +47,18 @@ const props = defineProps({
|
||||
default: 'top',
|
||||
},
|
||||
|
||||
tooltip: String,
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: String as PropType<'focus' | 'hover' | 'click'>,
|
||||
default: 'hover',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
</script>
|
||||
})
|
||||
</script>
|
||||
|
||||
26
src/components/ui/tree/file.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<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>
|
||||
204
src/components/ui/tree/folder.vue
Normal file
@ -0,0 +1,204 @@
|
||||
<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>
|
||||
5
src/components/ui/tree/index.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div data-tree-view role="tree" aria-orientation="vertical" class="rounded min-w-fit w-full">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
19
src/components/ui/tree/types.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
interface ITreeItem {
|
||||
id: string | null
|
||||
value: string
|
||||
name: string | null
|
||||
icon?: string | null
|
||||
children?: ITreeItem[] | null
|
||||
type: 'folder' | 'file'
|
||||
color?: string | null
|
||||
order?: number | null
|
||||
parentId?: string | null
|
||||
}
|
||||
|
||||
interface IVaultGroupTreeItem {
|
||||
id?: string | null
|
||||
name: string
|
||||
icon?: string | null
|
||||
children?: IVaultGroupTreeItem[] | null
|
||||
desciption?: string | null
|
||||
}
|
||||
@ -1,28 +1,48 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open"
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap">
|
||||
<UiDialog
|
||||
v-model:open="open"
|
||||
:title="t('title')"
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
||||
>
|
||||
<template #trigger>
|
||||
|
||||
|
||||
<Icon name="mdi:plus" />
|
||||
{{ t('database.create') }}
|
||||
|
||||
</template>
|
||||
|
||||
<form class="flex flex-col gap-4" @submit="onCreateAsync">
|
||||
<UiInput :check-input="check" :label="t('database.label')" :placeholder="t('database.placeholder')"
|
||||
:rules="vaultDatabaseSchema.name" autofocus prepend-icon="mdi:safe" v-model="database.name" />
|
||||
<form
|
||||
class="flex flex-col gap-4"
|
||||
@submit="onCreateAsync"
|
||||
>
|
||||
<UiInput
|
||||
v-model="database.name"
|
||||
:check-input="check"
|
||||
:label="t('database.label')"
|
||||
:placeholder="t('database.placeholder')"
|
||||
:rules="vaultDatabaseSchema.name"
|
||||
autofocus
|
||||
prepend-icon="mdi:safe"
|
||||
/>
|
||||
|
||||
<UiInputPassword :check-input="check" :rules="vaultDatabaseSchema.password" prepend-icon="mdi:key-outline"
|
||||
v-model="database.password" />
|
||||
<UiInputPassword
|
||||
v-model="database.password"
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
prepend-icon="mdi:key-outline"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton class="btn-error" @click="onClose">
|
||||
<UiButton
|
||||
class="btn-error"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ t('abort') }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton class="btn-primary" @click="onCreateAsync">
|
||||
<UiButton
|
||||
class="btn-primary"
|
||||
@click="onCreateAsync"
|
||||
>
|
||||
{{ t('create') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
@ -74,7 +94,7 @@ const onCreateAsync = async () => {
|
||||
|
||||
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name)
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(
|
||||
database.password
|
||||
database.password,
|
||||
)
|
||||
|
||||
console.log(
|
||||
@ -82,7 +102,7 @@ const onCreateAsync = async () => {
|
||||
database.name,
|
||||
nameCheck,
|
||||
database.password,
|
||||
passwordCheck
|
||||
passwordCheck,
|
||||
)
|
||||
if (!nameCheck.success || !passwordCheck.success) return
|
||||
|
||||
@ -105,7 +125,7 @@ const onCreateAsync = async () => {
|
||||
console.log('vaultId', vaultId)
|
||||
if (vaultId) {
|
||||
await navigateTo(
|
||||
useLocaleRoute()({ name: 'vaultOverview', params: { vaultId } })
|
||||
useLocaleRoute()({ name: 'vaultOverview', params: { vaultId } }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -121,7 +141,8 @@ const onClose = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"database": {
|
||||
"label": "Vaultname",
|
||||
@ -146,4 +167,5 @@ const onClose = () => {
|
||||
"abort": "Abort",
|
||||
"description": "Haex Vault for your most secret secrets"
|
||||
}
|
||||
}</i18n>
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -1,40 +1,52 @@
|
||||
<template>
|
||||
<UiDialog v-model:open="isOpen" class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 "
|
||||
@open="onLoadDatabase">
|
||||
<UiDialogConfirm
|
||||
v-model:open="open"
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1"
|
||||
:confirm-label="t('open')"
|
||||
:abort-label="t('abort')"
|
||||
@open="onLoadDatabase"
|
||||
@abort="onAbort"
|
||||
>
|
||||
<template #title>
|
||||
<i18n-t
|
||||
keypath="title"
|
||||
tag="p"
|
||||
class="flex gap-2"
|
||||
>
|
||||
<template #haexvault>
|
||||
<UiTextGradient>HaexVault</UiTextGradient>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<p class="text-sm">{{ path }}</p>
|
||||
</template>
|
||||
|
||||
<template #trigger>
|
||||
|
||||
<Icon name="mdi:folder-open-outline" />
|
||||
{{ t('database.open') }}
|
||||
|
||||
</template>
|
||||
|
||||
<UiInputPassword :check-input="check" :rules="vaultDatabaseSchema.password" @keyup.enter="onOpenDatabase" autofocus
|
||||
prepend-icon="mdi:key-outline" v-model="database.password" />
|
||||
|
||||
<template #buttons>
|
||||
<UiButton class="btn-error" @click="onClose">
|
||||
{{ t('abort') }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton type="submit" class="btn-primary" @click="onOpenDatabase">
|
||||
{{ t('open') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
<UiInputPassword
|
||||
v-model="database.password"
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
autofocus
|
||||
prepend-icon="mdi:key-outline"
|
||||
@keyup.enter="onOpenDatabase"
|
||||
/>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { open as openVault } from '@tauri-apps/plugin-dialog'
|
||||
import { vaultDatabaseSchema } from './schema'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const isOpen = defineModel('isOpen', { type: Boolean })
|
||||
const open = defineModel('open', { type: Boolean })
|
||||
|
||||
const props = defineProps({
|
||||
path: String,
|
||||
})
|
||||
const props = defineProps<{
|
||||
path: string
|
||||
}>()
|
||||
|
||||
const check = ref(false)
|
||||
|
||||
@ -62,7 +74,7 @@ initDatabase()
|
||||
const { add } = useSnackbar()
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
isOpen.value = false
|
||||
open.value = false
|
||||
console.error('handleError', error, typeof error)
|
||||
add({ type: 'error', text: 'Passwort falsch' })
|
||||
}
|
||||
@ -71,7 +83,7 @@ const { openAsync } = useVaultStore()
|
||||
|
||||
const onLoadDatabase = async () => {
|
||||
try {
|
||||
database.path = await open({
|
||||
database.path = await openVault({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [
|
||||
@ -82,10 +94,9 @@ const onLoadDatabase = async () => {
|
||||
],
|
||||
})
|
||||
|
||||
console.log("database.path", database.path)
|
||||
if (!database.path) return
|
||||
|
||||
isOpen.value = true
|
||||
open.value = true
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
@ -100,7 +111,7 @@ const onOpenDatabase = async () => {
|
||||
const path = database.path || props.path
|
||||
const pathCheck = vaultDatabaseSchema.path.safeParse(path)
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(
|
||||
database.password
|
||||
database.password,
|
||||
)
|
||||
|
||||
if (!pathCheck.success || !passwordCheck.success || !path) {
|
||||
@ -124,7 +135,7 @@ const onOpenDatabase = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
onClose()
|
||||
onAbort()
|
||||
|
||||
await navigateTo(
|
||||
localePath({
|
||||
@ -132,7 +143,7 @@ const onOpenDatabase = async () => {
|
||||
params: {
|
||||
vaultId,
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
await Promise.allSettled([
|
||||
syncLocaleAsync(),
|
||||
@ -144,25 +155,24 @@ const onOpenDatabase = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
const onAbort = () => {
|
||||
initDatabase()
|
||||
isOpen.value = false
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"open": "Öffnen",
|
||||
"abort": "Abbrechen",
|
||||
"database": {
|
||||
"open": "Vault öffnen"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"open": "Open",
|
||||
"abort": "Abort",
|
||||
"database": {
|
||||
"open": "Open Vault"
|
||||
}
|
||||
}
|
||||
}</i18n>
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
open: Öffnen
|
||||
abort: Abbrechen
|
||||
title: '{haexvault} entsperren'
|
||||
database:
|
||||
open: Vault öffnen
|
||||
|
||||
en:
|
||||
open: Open
|
||||
abort: Abort
|
||||
title: Unlock {haexvault}
|
||||
database:
|
||||
open: Open Vault
|
||||
</i18n>
|
||||
|
||||
@ -66,7 +66,6 @@
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center w-full min-h-14 gap-2 py-1"
|
||||
:class="{ '-ml-6': !show }"
|
||||
:style="{ color }"
|
||||
>
|
||||
<Icon
|
||||
@ -91,10 +90,7 @@
|
||||
v-show="!read_only"
|
||||
class="fixed bottom-2 left-0 w-full flex items-center justify-between px-4 md:hidden"
|
||||
>
|
||||
<div
|
||||
class="transition-all duration-500"
|
||||
:class="{ 'pl-96': show }"
|
||||
>
|
||||
<div class="transition-all duration-500">
|
||||
<button
|
||||
class="btn btn-square btn-error btn-outline"
|
||||
@click="onClose"
|
||||
@ -112,7 +108,7 @@
|
||||
<span class="hidden"> {{ t('create') }} </span>
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
<div />
|
||||
</div>
|
||||
<!-- <UiButtonAction
|
||||
class=""
|
||||
@ -129,89 +125,86 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
const { show } = storeToRefs(useSidebarStore());
|
||||
const read_only = defineModel<boolean>('read_only', { default: false })
|
||||
|
||||
const read_only = defineModel<boolean>('read_only', { default: false });
|
||||
|
||||
const props = defineProps({
|
||||
color: String,
|
||||
hasChanges: Boolean,
|
||||
icon: String,
|
||||
title: String,
|
||||
});
|
||||
const props = defineProps<{
|
||||
color: string
|
||||
hasChanges: boolean
|
||||
icon: string
|
||||
title: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: [void];
|
||||
close: [void];
|
||||
reject: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
submit: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
}>();
|
||||
back: [void]
|
||||
close: [void]
|
||||
reject: [to?: RouteLocationNormalizedLoadedGeneric]
|
||||
submit: [to?: RouteLocationNormalizedLoadedGeneric]
|
||||
}>()
|
||||
|
||||
const showConfirmation = ref(false);
|
||||
const showConfirmation = ref(false)
|
||||
|
||||
const to = ref<RouteLocationNormalizedLoadedGeneric>();
|
||||
const to = ref<RouteLocationNormalizedLoadedGeneric>()
|
||||
|
||||
const isApprovedForLeave = ref(false);
|
||||
const isApprovedForLeave = ref(false)
|
||||
|
||||
const wantToGoBack = ref(false);
|
||||
const wantToGoBack = ref(false)
|
||||
|
||||
const onSubmit = () => {
|
||||
showConfirmation.value = false;
|
||||
isApprovedForLeave.value = true;
|
||||
showConfirmation.value = false
|
||||
isApprovedForLeave.value = true
|
||||
if (wantToGoBack.value) {
|
||||
wantToGoBack.value = false;
|
||||
read_only.value = true;
|
||||
emit('submit');
|
||||
wantToGoBack.value = false
|
||||
read_only.value = true
|
||||
emit('submit')
|
||||
} else {
|
||||
emit('submit', to.value);
|
||||
emit('submit', to.value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onReject = () => {
|
||||
showConfirmation.value = false;
|
||||
isApprovedForLeave.value = true;
|
||||
read_only.value = true;
|
||||
showConfirmation.value = false
|
||||
isApprovedForLeave.value = true
|
||||
read_only.value = true
|
||||
|
||||
if (wantToGoBack.value) {
|
||||
wantToGoBack.value = false;
|
||||
emit('back');
|
||||
} else emit('reject', to.value);
|
||||
};
|
||||
wantToGoBack.value = false
|
||||
emit('back')
|
||||
} else emit('reject', to.value)
|
||||
}
|
||||
|
||||
const onBack = () => {
|
||||
if (props.hasChanges) {
|
||||
wantToGoBack.value = true;
|
||||
showConfirmation.value = true;
|
||||
wantToGoBack.value = true
|
||||
showConfirmation.value = true
|
||||
} else {
|
||||
emit('back');
|
||||
emit('back')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
if (props.hasChanges) {
|
||||
showConfirmation.value = true;
|
||||
showConfirmation.value = true
|
||||
} else {
|
||||
emit('close'); //read_only.value = true;
|
||||
emit('close') //read_only.value = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onDelete = () => {};
|
||||
onBeforeRouteLeave((_to, _from, next) => {
|
||||
//console.log('check before leave', _to, _from);
|
||||
to.value = _to;
|
||||
to.value = _to
|
||||
if (isApprovedForLeave.value) {
|
||||
isApprovedForLeave.value = false;
|
||||
next();
|
||||
isApprovedForLeave.value = false
|
||||
next()
|
||||
} else if (props.hasChanges) {
|
||||
showConfirmation.value = true;
|
||||
showConfirmation.value = true
|
||||
} else {
|
||||
next();
|
||||
next()
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
<div class="card-header">
|
||||
<div v-if="$slots.title || title">
|
||||
<Icon :name="icon" />
|
||||
<Icon v-if="icon" :name="icon" />
|
||||
<h5 v-if="title" class="card-title mb-0">
|
||||
{{ title }}
|
||||
</h5>
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
aaaaaaaaa
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<div v-if="$slots.action" class="card-actions">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<VaultCardEdit
|
||||
v-if="vaultGroup"
|
||||
v-model:read_only="read_only"
|
||||
:color="vaultGroup.color ?? 'text-base-content'"
|
||||
:has-changes="hasChanges"
|
||||
:icon="vaultGroup.icon ?? 'mdi:folder-outline'"
|
||||
@ -9,42 +10,41 @@
|
||||
@close="$emit('close')"
|
||||
@reject="(to) => $emit('reject', to)"
|
||||
@submit="(to) => $emit('submit', to)"
|
||||
v-model:read_only="read_only"
|
||||
>
|
||||
<div class="flex flex-col gap-4 w-full p-4">
|
||||
<UiInput
|
||||
v-show="!read_only"
|
||||
v-model.trim="vaultGroup.name"
|
||||
:label="t('vaultGroup.name')"
|
||||
:placeholder="t('vaultGroup.name')"
|
||||
:rules="vaultGroupSchema.name"
|
||||
:with-copy-button="read_only"
|
||||
:read_only
|
||||
autofocus
|
||||
v-model.trim="vaultGroup.name"
|
||||
/>
|
||||
|
||||
<UiInput
|
||||
v-show="!read_only || vaultGroup.description?.length"
|
||||
v-model.trim="vaultGroup.description"
|
||||
:read_only
|
||||
:label="t('vaultGroup.description')"
|
||||
:placeholder="t('vaultGroup.description')"
|
||||
:rules="vaultGroupSchema.description"
|
||||
:with-copy-button="read_only"
|
||||
v-model.trim="vaultGroup.description"
|
||||
/>
|
||||
|
||||
<UiColorPicker
|
||||
v-model="vaultGroup.color"
|
||||
:read_only
|
||||
:label="t('vaultGroup.color')"
|
||||
:placeholder="t('vaultGroup.color')"
|
||||
v-model="vaultGroup.color"
|
||||
/>
|
||||
|
||||
<UiIconPicker
|
||||
v-model="vaultGroup.icon"
|
||||
:read_only
|
||||
:label="t('vaultGroup.icon')"
|
||||
:placeholder="t('vaultGroup.icon')"
|
||||
v-model="vaultGroup.icon"
|
||||
/>
|
||||
</div>
|
||||
</VaultCardEdit>
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<UiListButton
|
||||
v-if="entry"
|
||||
:key="entry.id"
|
||||
@click="navigateToEntryAsync(entry.id)"
|
||||
class="text-base-content"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8">
|
||||
<Icon
|
||||
v-if="entry.icon || groupIcon"
|
||||
:name="entry.icon || groupIcon!"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div v-if="!entry.title && !entry.username && !entry.url">
|
||||
{{ entry.id }}
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ entry.title }}
|
||||
</div>
|
||||
<span class="text-sm">
|
||||
{{ entry.username }}
|
||||
</span>
|
||||
<span class="text-sm">
|
||||
{{ entry.url }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</UiListButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectVaultEntry } from '~/database/schemas/vault';
|
||||
|
||||
defineProps({
|
||||
entry: Object as PropType<SelectVaultEntry>,
|
||||
groupIcon: [String, null],
|
||||
});
|
||||
|
||||
const { navigateToEntryAsync } = useVaultEntryStore();
|
||||
</script>
|
||||
@ -16,9 +16,9 @@
|
||||
</span>
|
||||
</UiButton>
|
||||
<UiButton
|
||||
ref="abortButtonRef"
|
||||
class="btn-outline focus:bg-primary"
|
||||
tabindex="11"
|
||||
ref="abortButtonRef"
|
||||
@click="showConfirmation = false"
|
||||
>
|
||||
<Icon name="mdi:close" />
|
||||
@ -41,17 +41,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const showConfirmation = defineModel<boolean>();
|
||||
const abortButtonRef = useTemplateRef('abortButtonRef');
|
||||
const showConfirmation = defineModel<boolean>()
|
||||
const abortButtonRef = useTemplateRef('abortButtonRef')
|
||||
|
||||
const { t } = useI18n();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
const { t } = useI18n()
|
||||
|
||||
onUpdated(() => {
|
||||
abortButtonRef.value?.$el.focus();
|
||||
});
|
||||
abortButtonRef.value?.$el.focus()
|
||||
})
|
||||
|
||||
defineEmits(['submit', 'reject']);
|
||||
defineEmits(['submit', 'reject'])
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
|
||||
@ -1,50 +1,92 @@
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col min-w-min ">
|
||||
<nav class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2">
|
||||
<div class="w-full h-full flex flex-col min-w-min">
|
||||
<nav
|
||||
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
>
|
||||
<UiTooltip :tooltip="isVisible ? t('sidebar.close') : t('sidebar.show')">
|
||||
<button type="button" class="btn btn-text btn-square me-2 z-50" aria-haspopup="dialog" aria-expanded="false"
|
||||
aria-controls="sidebar" @click="toogleSidebar" ref="sidebarToogleRef">
|
||||
<Icon :name="isVisible
|
||||
? 'tabler:layout-sidebar-filled'
|
||||
: 'tabler:layout-sidebar'
|
||||
" size="28" />
|
||||
<button
|
||||
ref="sidebarToogleRef"
|
||||
type="button"
|
||||
class="btn btn-text btn-square me-2 z-50"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
@click="toogleSidebar"
|
||||
>
|
||||
<Icon
|
||||
:name="
|
||||
isVisible
|
||||
? 'tabler:layout-sidebar-filled'
|
||||
: 'tabler:layout-sidebar'
|
||||
"
|
||||
size="28"
|
||||
/>
|
||||
</button>
|
||||
</UiTooltip>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
<NuxtLinkLocale class="link text-base-content link-neutral text-xl font-semibold no-underline"
|
||||
:to="{ name: 'vaultOverview' }">
|
||||
<UiTextGradient class="text-nowrap">{{
|
||||
currentVaultName
|
||||
}}</UiTextGradient>
|
||||
<NuxtLinkLocale
|
||||
class="link text-base-content link-neutral text-xl font-semibold no-underline"
|
||||
:to="{ name: 'vaultOverview' }"
|
||||
>
|
||||
<UiTextGradient class="text-nowrap">
|
||||
{{ currentVaultName }}
|
||||
</UiTextGradient>
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end flex items-center gap-4 me-4">
|
||||
<div class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]">
|
||||
<button id="dropdown-scrollable" type="button"
|
||||
<div
|
||||
class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]"
|
||||
>
|
||||
<button
|
||||
id="dropdown-scrollable"
|
||||
type="button"
|
||||
class="dropdown-toggle btn btn-text btn-circle dropdown-open:bg-base-content/10 size-10"
|
||||
aria-haspopup="menu" aria-expanded="false" aria-label="Dropdown">
|
||||
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>
|
||||
<span class="icon-[tabler--bell] text-base-content size-[1.375rem]"></span>
|
||||
<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>
|
||||
<div class="dropdown-menu dropdown-open:opacity-100 hidden" role="menu" aria-orientation="vertical"
|
||||
aria-labelledby="dropdown-scrollable">
|
||||
<div
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden"
|
||||
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 max-md:max-w-60">
|
||||
<div class="dropdown-item" v-for="notification in notifications">
|
||||
class="vertical-scrollbar horizontal-scrollbar rounded-scrollbar text-base-content/80 max-h-56 overflow-auto max-md:max-w-60"
|
||||
>
|
||||
<div
|
||||
v-for="notification in notifications"
|
||||
:key="notification.date.toDateString()"
|
||||
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" />
|
||||
<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">
|
||||
@ -57,8 +99,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="dropdown-footer justify-center gap-1">
|
||||
<span class="icon-[tabler--eye] size-4"></span>
|
||||
<a
|
||||
href="#"
|
||||
class="dropdown-footer justify-center gap-1"
|
||||
>
|
||||
<span class="icon-[tabler--eye] size-4" />
|
||||
{{ t('notifications.view_all') }}
|
||||
</a>
|
||||
</div>
|
||||
@ -69,12 +114,26 @@
|
||||
</nav>
|
||||
|
||||
<div class="flex h-full overflow-hidden">
|
||||
<aside id="sidebar" class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300"
|
||||
:class="[!isVisible ? 'w-0' : 'w-16']" role="dialog" tabindex="-1">
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300"
|
||||
:class="[!isVisible ? 'w-0' : 'w-16']"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="drawer-body h-full">
|
||||
<ul class="menu p-0 h-full rounded-none">
|
||||
<HaexSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<HaexSidebarLink v-for="item in extensionLinks" :key="item.id" v-bind="item" icon-type="svg" />
|
||||
<HaexSidebarLink
|
||||
v-for="item in menu"
|
||||
v-bind="item"
|
||||
:key="item.id"
|
||||
/>
|
||||
<HaexSidebarLink
|
||||
v-for="item in extensionLinks"
|
||||
:key="item.id"
|
||||
v-bind="item"
|
||||
icon-type="svg"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@ -4,9 +4,15 @@
|
||||
<UiDropdownLocale @select="setLocale" />
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-center gap-5 max-w-3xl">
|
||||
<img src="/logo.svg" class="bg-primary p-3 size-16 rounded-full" alt="HaexVault Logo" />
|
||||
<img
|
||||
src="/logo.svg"
|
||||
class="bg-primary p-3 size-16 rounded-full"
|
||||
alt="HaexVault Logo"
|
||||
>
|
||||
|
||||
<span class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center">
|
||||
<span
|
||||
class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"
|
||||
>
|
||||
<p class="whitespace-nowrap">
|
||||
{{ t('welcome') }}
|
||||
</p>
|
||||
@ -16,21 +22,32 @@
|
||||
<div class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto">
|
||||
<VaultButtonCreate />
|
||||
|
||||
<VaultButtonOpen v-model:isOpen="passwordPromptOpen" :path="vaultPath" />
|
||||
<VaultButtonOpen
|
||||
v-model:open="passwordPromptOpen"
|
||||
:path="vaultPath"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="lastVaults.length" class="w-full">
|
||||
<div
|
||||
v-show="lastVaults.length"
|
||||
class="w-full"
|
||||
>
|
||||
<div class="font-thin text-sm justify-start px-2 pb-1">
|
||||
{{ t('lastUsed') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border first:*:rounded-t-md last:*:rounded-b-md overflow-scroll">
|
||||
<div class="flex items-center justify-between group h-12 overflow-x-scroll" v-for="vault in lastVaults"
|
||||
:key="vault.path">
|
||||
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border first:*:rounded-t-md last:*:rounded-b-md overflow-scroll"
|
||||
>
|
||||
<div
|
||||
v-for="vault in lastVaults"
|
||||
:key="vault.path"
|
||||
class="flex items-center justify-between group h-12 overflow-x-scroll"
|
||||
>
|
||||
<button
|
||||
class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4"
|
||||
@click="; (passwordPromptOpen = true), (vaultPath = vault.path)">
|
||||
@click=";(passwordPromptOpen = true), (vaultPath = vault.path)"
|
||||
>
|
||||
<span class="block md:hidden">
|
||||
{{ vault.name }}
|
||||
</span>
|
||||
@ -38,8 +55,13 @@
|
||||
{{ vault.path }}
|
||||
</span>
|
||||
</button>
|
||||
<button class="absolute right-2 btn btn-square btn-error btn-xs hidden group-hover:flex min-w-6">
|
||||
<Icon name="mdi:trash-can-outline" @click="removeVaultAsync(vault.path)" />
|
||||
<button
|
||||
class="absolute right-2 btn btn-square btn-error btn-xs hidden group-hover:flex min-w-6"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:trash-can-outline"
|
||||
@click="removeVaultAsync(vault.path)"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -75,7 +97,8 @@ const { lastVaults } = storeToRefs(useLastVaultStore())
|
||||
await syncLastVaultsAsync()
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"welcome": "Viel Spass mit",
|
||||
"lastUsed": "Zuletzt verwendete Vaults",
|
||||
@ -86,4 +109,5 @@ await syncLastVaultsAsync()
|
||||
"lastUsed": "Last used Vaults",
|
||||
"sponsors": "Powered by"
|
||||
}
|
||||
}</i18n>
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<div class="bg-red-400 h-full">
|
||||
browser {{ useRouter().currentRoute.value.meta.name }}
|
||||
<HaexBrowser
|
||||
:tabs="tabs"
|
||||
:activeTabId="activeTabId"
|
||||
@createTab="createNewTab"
|
||||
@closeTab="closeTab"
|
||||
@navigate="navigateToUrl"
|
||||
@goBack="goBack"
|
||||
@goForward="goForward"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { Window, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { Webview } from "@tauri-apps/api/webview";
|
||||
|
||||
definePageMeta({
|
||||
name: "haexBrowser",
|
||||
});
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
isLoading: boolean;
|
||||
isActive: boolean;
|
||||
window_label: string;
|
||||
}
|
||||
|
||||
const tabs = ref<Tab[]>([]);
|
||||
const activeTabId = ref<string | null>(null);
|
||||
|
||||
let unlistenTabCreated: UnlistenFn | null = null;
|
||||
let unlistenTabClosed: UnlistenFn | null = null;
|
||||
|
||||
onMounted(async () => {
|
||||
// Erstelle einen ersten Tab beim Start
|
||||
//createNewTab("https://www.google.com");
|
||||
|
||||
// Höre auf Tab-Events
|
||||
unlistenTabCreated = await listen("tab-created", (event) => {
|
||||
const newTab = event.payload as Tab;
|
||||
|
||||
tabs.value = tabs.value.map((tab) => ({
|
||||
...tab,
|
||||
isActive: tab.id === newTab.id,
|
||||
}));
|
||||
|
||||
if (!tabs.value.some((tab) => tab.id === newTab.id)) {
|
||||
tabs.value.push(newTab);
|
||||
}
|
||||
|
||||
activeTabId.value = newTab.id;
|
||||
});
|
||||
|
||||
unlistenTabClosed = await listen("tab-closed", (event) => {
|
||||
const closedTabId = event.payload as string;
|
||||
tabs.value = tabs.value.filter((tab) => tab.id !== closedTabId);
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unlistenTabCreated) unlistenTabCreated();
|
||||
if (unlistenTabClosed) unlistenTabClosed();
|
||||
});
|
||||
|
||||
const createNewTab = async (url: string = "about:blank") => {
|
||||
try {
|
||||
/* const appWindow = new Window('uniqueLabel111', {
|
||||
fullscreen: true,
|
||||
});
|
||||
*/
|
||||
/* const appWindow = getCurrentWindow();
|
||||
|
||||
const webview = new Webview(appWindow, 'theUniqueLabel', {
|
||||
url: 'https://github.com/tauri-apps/tauri',
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
x: 110,
|
||||
y: 0,
|
||||
});
|
||||
await webview.show(); */
|
||||
//console.log('create webview', webview);
|
||||
const tab_id = "foo";
|
||||
await invoke("create_tab", { url, tabId: "foo" });
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Erstellen des Tabs:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const closeTab = async (tabId: string) => {
|
||||
try {
|
||||
//await invoke('close_tab', { tabId });
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Schließen des Tabs:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToUrl = async (tabId: string, url: string) => {
|
||||
try {
|
||||
//await invoke('navigate_to_url', { tabId, url });
|
||||
} catch (error) {
|
||||
console.error("Fehler bei der Navigation:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const goBack = async (tabId: string | null) => {
|
||||
try {
|
||||
//await invoke('go_back', { tabId });
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Zurückgehen:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const goForward = async (tabId: string | null) => {
|
||||
try {
|
||||
//await invoke('go_forward', { tabId });
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Vorwärtsgehen:", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,73 +1,28 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-scroll">
|
||||
<!-- <div>
|
||||
{{ iframeSrc }}
|
||||
</div> -->
|
||||
<div>
|
||||
{{ iframeIndex }}
|
||||
</div>
|
||||
<iframe
|
||||
v-if="iframeIndex"
|
||||
class="w-full h-full"
|
||||
@load=""
|
||||
ref="iFrameRef"
|
||||
class="w-full h-full"
|
||||
:src="iframeIndex"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
allow="autoplay; speaker-selection; encrypted-media;"
|
||||
>
|
||||
</iframe>
|
||||
|
||||
<UiButton @click="go = true">Go</UiButton>
|
||||
<!-- <p v-else>{{ t("loading") }}</p> -->
|
||||
{{ audioTest }}
|
||||
<audio v-if="go" controls :src="audioTest">
|
||||
Dein Browser unterstützt das Audio-Element nicht.
|
||||
</audio>
|
||||
|
||||
<video v-if="go" controls width="600" :src="demoVideo"></video>
|
||||
<div v-if="audioError">
|
||||
Fehler beim Laden der Audio-Datei: {{ audioError }}
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { appDataDir, join, resourceDir } from '@tauri-apps/api/path'
|
||||
|
||||
definePageMeta({
|
||||
name: 'haexExtension',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const iframeRef = useTemplateRef('iFrameRef')
|
||||
const { extensionEntry: iframeSrc, currentExtension } = storeToRefs(
|
||||
useExtensionsStore()
|
||||
)
|
||||
const audioAssetUrl = ref('')
|
||||
const audioError = ref('')
|
||||
const audioTest = convertFileSrc(
|
||||
await join(await appDataDir(), 'resources/demo.mp3')
|
||||
)
|
||||
const { extensionEntry: iframeSrc } = storeToRefs(useExtensionsStore())
|
||||
|
||||
//computed(() => `${iframeSrc.value}/sounds/music/demo.mp3`)
|
||||
|
||||
const go = ref(false)
|
||||
const iframeIndex = computed(() => `${iframeSrc.value}/index.html`)
|
||||
const demoVideo = computed(() => `${iframeSrc.value}/sounds/music/demo.mp3`)
|
||||
|
||||
const extensionStore = useExtensionsStore()
|
||||
|
||||
watch(
|
||||
demoVideo,
|
||||
async () => {
|
||||
const res = await fetch(
|
||||
'/home/haex/.local/share/space.haex.hub/extensions/pokedemo/1.0/sounds/music/demo.mp3'
|
||||
)
|
||||
console.log('respo', res)
|
||||
|
||||
console.log('iframeSrc', iframeSrc.value)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
const iframeIndex = computed(() =>
|
||||
iframeSrc.value ? `${iframeSrc.value}/index.html` : '',
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,24 +1,31 @@
|
||||
<template>
|
||||
<div class="flex flex-col p-1 relative h-full">
|
||||
<div class="flex" v-if="extensionStore.availableExtensions.length">
|
||||
<div class="flex flex-col p-4 relative h-full">
|
||||
<div
|
||||
v-if="extensionStore.availableExtensions.length"
|
||||
class="flex"
|
||||
>
|
||||
<UiButton
|
||||
class="fixed top-20 right-4 btn-square btn-primary"
|
||||
@click="loadExtensionManifestAsync"
|
||||
@click="prepareInstallExtensionAsyn"
|
||||
>
|
||||
<Icon name="mdi:plus" size="1.5em" />
|
||||
<Icon
|
||||
name="mdi:plus"
|
||||
size="1.5em"
|
||||
/>
|
||||
</UiButton>
|
||||
|
||||
<HaexExtensionCard
|
||||
v-for="extension in extensionStore.availableExtensions"
|
||||
v-bind="extension"
|
||||
@remove="onShowRemoveDialog(extension)"
|
||||
>
|
||||
</HaexExtensionCard>
|
||||
v-for="_extension in extensionStore.availableExtensions"
|
||||
v-bind="_extension"
|
||||
:key="_extension.id"
|
||||
@remove="onShowRemoveDialog(_extension)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- <SvgoExtensionsOverview class="h-screen w-screen" /> -->
|
||||
<!-- <nuxt-icon name="extensions-overview" class="size-full" /> -->
|
||||
<div v-else class="h-full w-full">
|
||||
<div
|
||||
v-else
|
||||
class="h-full w-full"
|
||||
>
|
||||
<Icon
|
||||
name="my-icon:extensions-overview"
|
||||
class="size-full md:size-2/3 md:translate-x-1/5 md:translate-y-1/3"
|
||||
@ -27,17 +34,27 @@
|
||||
<UiTooltip :tooltip="t('extension.add')">
|
||||
<UiButton
|
||||
class="btn-square btn-primary btn-xl btn-gradient rotate-45"
|
||||
@click="loadExtensionManifestAsync"
|
||||
@click="prepareInstallExtensionAsyn"
|
||||
>
|
||||
<Icon name="mdi:plus" size="1.5em" class="rotate-45" />
|
||||
<Icon
|
||||
name="mdi:plus"
|
||||
size="1.5em"
|
||||
class="rotate-45"
|
||||
/>
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HaexExtensionManifestConfirm
|
||||
<HaexExtensionDialogReinstall
|
||||
v-model:open="openOverwriteDialog"
|
||||
:manifest="extension.manifest"
|
||||
@confirm="addExtensionAsync"
|
||||
/>
|
||||
|
||||
<HaexExtensionDialogInstall
|
||||
v-model:open="showConfirmation"
|
||||
:manifest="extension.manifest"
|
||||
@confirm="addExtensionAsync"
|
||||
/>
|
||||
|
||||
@ -45,8 +62,7 @@
|
||||
v-model:open="showRemoveDialog"
|
||||
:extension="extensionToBeRemoved"
|
||||
@confirm="removeExtensionAsync"
|
||||
>
|
||||
</HaexExtensionDialogRemove>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -67,6 +83,7 @@ const { t } = useI18n()
|
||||
const extensionStore = useExtensionsStore()
|
||||
|
||||
const showConfirmation = ref(false)
|
||||
const openOverwriteDialog = ref(false)
|
||||
|
||||
const extension = reactive<{
|
||||
manifest: IHaexHubExtensionManifest | null | undefined
|
||||
@ -82,14 +99,13 @@ const loadExtensionManifestAsync = async () => {
|
||||
if (!extension.path) return
|
||||
|
||||
const manifestFile = JSON.parse(
|
||||
await readTextFile(await join(extension.path, 'manifest.json'))
|
||||
await readTextFile(await join(extension.path, 'manifest.json')),
|
||||
)
|
||||
|
||||
if (!extensionStore.checkManifest(manifestFile))
|
||||
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`)
|
||||
|
||||
extension.manifest = manifestFile
|
||||
showConfirmation.value = true
|
||||
return manifestFile
|
||||
} catch (error) {
|
||||
console.error('Fehler loadExtensionManifestAsync:', error)
|
||||
add({ type: 'error', text: JSON.stringify(error) })
|
||||
@ -98,6 +114,27 @@ const loadExtensionManifestAsync = async () => {
|
||||
|
||||
const { add } = useSnackbar()
|
||||
|
||||
const prepareInstallExtensionAsyn = async () => {
|
||||
try {
|
||||
const manifest = await loadExtensionManifestAsync()
|
||||
if (!manifest) throw new Error('No valid Manifest found')
|
||||
|
||||
extension.manifest = manifest
|
||||
|
||||
const isAlreadyInstalled = await extensionStore.isExtensionInstalledAsync({
|
||||
id: manifest.id,
|
||||
version: manifest.version,
|
||||
})
|
||||
if (isAlreadyInstalled) {
|
||||
openOverwriteDialog.value = true
|
||||
} else {
|
||||
await addExtensionAsync()
|
||||
}
|
||||
} catch (error) {
|
||||
add({ type: 'error', text: JSON.stringify(error) })
|
||||
}
|
||||
}
|
||||
|
||||
const addExtensionAsync = async () => {
|
||||
try {
|
||||
await extensionStore.installAsync(extension.path)
|
||||
@ -133,7 +170,7 @@ const removeExtensionAsync = async () => {
|
||||
try {
|
||||
await extensionStore.removeExtensionAsync(
|
||||
extensionToBeRemoved.value.id,
|
||||
extensionToBeRemoved.value.version
|
||||
extensionToBeRemoved.value.version,
|
||||
)
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
add({
|
||||
@ -173,4 +210,17 @@ de:
|
||||
text: 'Die Erweiterung wurde erfolgreich hinzugefügt'
|
||||
en:
|
||||
title: 'Install extension'
|
||||
extension:
|
||||
remove:
|
||||
success:
|
||||
text: 'Extension {extensionName} was removed'
|
||||
title: '{extensionName} removed'
|
||||
error:
|
||||
text: "Extension {extensionName} couldn't be removed. \n {error}"
|
||||
title: 'Exception during uninstall {extensionName}'
|
||||
|
||||
add: 'Add Extension'
|
||||
success:
|
||||
title: '{extension} added'
|
||||
text: 'Extensions was added successfully'
|
||||
</i18n>
|
||||
|
||||
8
src/pages/vault/[vaultId]/haexpass.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<HaexPassSidebar />
|
||||
<div>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
9
src/pages/vault/[vaultId]/haexpass/index.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>passwords</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
name: "haexpassOverview"
|
||||
})
|
||||
</script>
|
||||
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="h-full text-base-content"></div>
|
||||
<div class="h-full text-base-content flex bg-base-200 p-4">
|
||||
<HaexExtensionCard
|
||||
v-for="extension in extensionStore.availableExtensions"
|
||||
v-bind="extension"
|
||||
:key="extension.id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@ -10,10 +10,16 @@
|
||||
|
||||
<div class="p-2">{{ t('vaultName.label') }}</div>
|
||||
<div>
|
||||
<UiInput v-model="currentVaultName" :placeholder="t('vaultName.label')">
|
||||
<UiInput
|
||||
v-model="currentVaultName"
|
||||
:placeholder="t('vaultName.label')"
|
||||
>
|
||||
<template #append>
|
||||
<UiTooltip :tooltip="t('save')">
|
||||
<UiButton class="btn-primary" @click="onSetVaultNameAsync">
|
||||
<UiButton
|
||||
class="btn-primary"
|
||||
@click="onSetVaultNameAsync"
|
||||
>
|
||||
<Icon name="mdi:content-save-outline" />
|
||||
</UiButton>
|
||||
</UiTooltip>
|
||||
@ -25,7 +31,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type Locale } from 'vue-i18n'
|
||||
import type { Locale } from 'vue-i18n'
|
||||
import { haexSettings } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
definePageMeta({
|
||||
@ -61,6 +67,7 @@ const onSetVaultNameAsync = async () => {
|
||||
await updateVaultNameAsync(currentVaultName.value)
|
||||
add({ text: t('vaultName.update.success'), type: 'success' })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
add({ text: t('vaultName.update.error'), type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import 'flyonui/flyonui'
|
||||
|
||||
import type { HSOverlay, IStaticMethods } from 'flyonui/flyonui'
|
||||
import type { HSOverlay, IStaticMethods, HSAccordion } from 'flyonui/flyonui'
|
||||
declare global {
|
||||
interface Window {
|
||||
HSStaticMethods: IStaticMethods
|
||||
HSOverlay: typeof HSOverlay
|
||||
HSAccordion: typeof HSAccordion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
export interface ResourceRequestDetails {
|
||||
url: string;
|
||||
resourceType: string;
|
||||
tabId?: string;
|
||||
frameId?: number;
|
||||
url: string
|
||||
resourceType: string
|
||||
tabId?: string
|
||||
frameId?: number
|
||||
}
|
||||
|
||||
export interface ResourceRequestResult {
|
||||
cancel: boolean;
|
||||
redirectUrl?: string;
|
||||
cancel: boolean
|
||||
redirectUrl?: string
|
||||
}
|
||||
|
||||
export interface ContentScript {
|
||||
code: string;
|
||||
matches?: string[];
|
||||
runAt?: 'document_start' | 'document_end' | 'document_idle';
|
||||
code: string
|
||||
matches?: string[]
|
||||
runAt?: 'document_start' | 'document_end' | 'document_idle'
|
||||
}
|
||||
|
||||
export interface Extension {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
processNavigation?: (url: string) => boolean;
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
description?: string
|
||||
processNavigation?: (url: string) => boolean
|
||||
processResourceRequest?: (
|
||||
details: ResourceRequestDetails
|
||||
) => ResourceRequestResult;
|
||||
contentScripts?: ContentScript[];
|
||||
details: ResourceRequestDetails,
|
||||
) => ResourceRequestResult
|
||||
contentScripts?: ContentScript[]
|
||||
}
|
||||
|
||||
export const useBrowserExtensionStore = defineStore(
|
||||
'useBrowserExtensionStore',
|
||||
() => {
|
||||
const extensions = ref<Extension[]>([]);
|
||||
const isInitialized = ref<boolean>(false);
|
||||
const extensions = ref<Extension[]>([])
|
||||
const isInitialized = ref<boolean>(false)
|
||||
|
||||
return {
|
||||
extensions,
|
||||
@ -40,28 +40,28 @@ export const useBrowserExtensionStore = defineStore(
|
||||
initializeAsync,
|
||||
processNavigation,
|
||||
injectContentScripts,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const initializeAsync = async () => {
|
||||
const { isInitialized } = storeToRefs(useBrowserExtensionStore());
|
||||
const { isInitialized } = storeToRefs(useBrowserExtensionStore())
|
||||
return
|
||||
if (isInitialized.value) return;
|
||||
if (isInitialized.value) return
|
||||
|
||||
// Lade Erweiterungen aus dem Erweiterungsverzeichnis
|
||||
try {
|
||||
const extensions = await loadExtensionsAsync();
|
||||
const extensions = await loadExtensionsAsync()
|
||||
for (const extension of extensions) {
|
||||
registerExtension(extension);
|
||||
registerExtension(extension)
|
||||
}
|
||||
|
||||
isInitialized.value = true;
|
||||
console.log(`${extensions.length} Erweiterungen geladen`);
|
||||
isInitialized.value = true
|
||||
console.log(`${extensions.length} Erweiterungen geladen`)
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Erweiterungen:', error);
|
||||
console.error('Fehler beim Laden der Erweiterungen:', error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const loadExtensionsAsync = async (): Promise<Extension[]> => {
|
||||
// In einer realen Implementierung würden Sie hier Erweiterungen aus einem Verzeichnis laden
|
||||
@ -69,23 +69,23 @@ const loadExtensionsAsync = async (): Promise<Extension[]> => {
|
||||
/* const adBlocker = (await import('./ad-blocker')).default;
|
||||
const trackerBlocker = (await import('./tracker-blocker')).default; */
|
||||
|
||||
return [];
|
||||
};
|
||||
return []
|
||||
}
|
||||
|
||||
const registerExtension = (extension: Extension): boolean => {
|
||||
const { extensions } = storeToRefs(useBrowserExtensionStore());
|
||||
const { extensions } = storeToRefs(useBrowserExtensionStore())
|
||||
if (!extension.id || !extension.name) {
|
||||
console.error('Ungültige Erweiterung:', extension);
|
||||
return false;
|
||||
console.error('Ungültige Erweiterung:', extension)
|
||||
return false
|
||||
}
|
||||
|
||||
console.log(`Erweiterung registriert: ${extension.name}`);
|
||||
extensions.value.push(extension);
|
||||
return true;
|
||||
};
|
||||
console.log(`Erweiterung registriert: ${extension.name}`)
|
||||
extensions.value.push(extension)
|
||||
return true
|
||||
}
|
||||
|
||||
const processNavigation = (url: string) => {
|
||||
return true;
|
||||
};
|
||||
const processNavigation = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
const injectContentScripts = (t: string) => { };
|
||||
const injectContentScripts = () => {}
|
||||
|
||||
@ -1,116 +1,145 @@
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import { appDataDir, join } from "@tauri-apps/api/path";
|
||||
import { exists, readDir, readTextFile, remove } from "@tauri-apps/plugin-fs";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { appDataDir, join } from '@tauri-apps/api/path'
|
||||
import { exists, readDir, readTextFile, remove } from '@tauri-apps/plugin-fs'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import type {
|
||||
IHaexHubExtension,
|
||||
IHaexHubExtensionLink,
|
||||
IHaexHubExtensionManifest,
|
||||
} from "~/types/haexhub";
|
||||
import { haexExtensions } from "~~/src-tauri/database/schemas/vault";
|
||||
} from '~/types/haexhub'
|
||||
import { haexExtensions } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
const manifestFileName = "manifest.json";
|
||||
const logoFileName = "logo.svg";
|
||||
const manifestFileName = 'manifest.json'
|
||||
const logoFileName = 'icon.svg'
|
||||
|
||||
export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
|
||||
export const useExtensionsStore = defineStore('extensionsStore', () => {
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([])
|
||||
|
||||
const extensionLinks = computed<ISidebarItem[]>(() =>
|
||||
availableExtensions.value
|
||||
.filter((extension) => extension.enabled && extension.installed)
|
||||
.map((extension) => ({
|
||||
icon: extension.icon ?? "",
|
||||
icon: extension.icon ?? '',
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
tooltip: extension.name ?? "",
|
||||
to: { name: "haexExtension", params: { extensionId: extension.id } },
|
||||
}))
|
||||
);
|
||||
name: extension.name ?? '',
|
||||
tooltip: extension.name ?? '',
|
||||
to: { name: 'haexExtension', params: { extensionId: extension.id } },
|
||||
})),
|
||||
)
|
||||
|
||||
const currentRoute = useRouter().currentRoute;
|
||||
const currentRoute = useRouter().currentRoute
|
||||
|
||||
const isActive = (id: string) =>
|
||||
computed(
|
||||
() => currentRoute.value.name === "extension" && currentRoute.value.params.extensionId === id
|
||||
);
|
||||
() =>
|
||||
currentRoute.value.name === 'extension' &&
|
||||
currentRoute.value.params.extensionId === id,
|
||||
)
|
||||
|
||||
const currentExtension = computed(() => {
|
||||
console.log("computed currentExtension", currentRoute.value.params);
|
||||
if (currentRoute.value.meta.name !== "haexExtension") return;
|
||||
console.log('computed currentExtension', currentRoute.value.params)
|
||||
if (currentRoute.value.meta.name !== 'haexExtension') return
|
||||
|
||||
const extensionId = getSingleRouteParam(currentRoute.value.params.extensionId);
|
||||
console.log("extensionId from param", extensionId);
|
||||
if (!extensionId) return;
|
||||
const extensionId = getSingleRouteParam(
|
||||
currentRoute.value.params.extensionId,
|
||||
)
|
||||
console.log('extensionId from param', extensionId)
|
||||
if (!extensionId) return
|
||||
|
||||
const extension = availableExtensions.value.find((extension) => extension.id === extensionId);
|
||||
console.log("currentExtension", extension);
|
||||
return extension;
|
||||
});
|
||||
const extension = availableExtensions.value.find(
|
||||
(extension) => extension.id === extensionId,
|
||||
)
|
||||
console.log('currentExtension', extension)
|
||||
return extension
|
||||
})
|
||||
|
||||
const getExtensionPathAsync = async (extensionId?: string, version?: string) => {
|
||||
if (!extensionId || !version) return "";
|
||||
return await join(await appDataDir(), "extensions", extensionId, version);
|
||||
};
|
||||
const getExtensionPathAsync = async (
|
||||
extensionId?: string,
|
||||
version?: string,
|
||||
) => {
|
||||
if (!extensionId || !version) return ''
|
||||
return await join(await appDataDir(), 'extensions', extensionId, version)
|
||||
}
|
||||
|
||||
const checkSourceExtensionDirectoryAsync = async (extensionDirectory: string) => {
|
||||
const checkSourceExtensionDirectoryAsync = async (
|
||||
extensionDirectory: string,
|
||||
) => {
|
||||
try {
|
||||
const dir = await readDir(extensionDirectory);
|
||||
const manifest = dir.find((entry) => entry.name === manifestFileName && entry.isFile);
|
||||
if (!manifest) throw new Error("Kein Manifest für Erweiterung gefunden");
|
||||
const dir = await readDir(extensionDirectory)
|
||||
const manifest = dir.find(
|
||||
(entry) => entry.name === manifestFileName && entry.isFile,
|
||||
)
|
||||
if (!manifest) throw new Error('Kein Manifest für Erweiterung gefunden')
|
||||
|
||||
const logo = dir.find((item) => item.isFile && item.name === logoFileName);
|
||||
if (!logo) throw new Error("Logo fehlt");
|
||||
const logo = dir.find((item) => item.isFile && item.name === logoFileName)
|
||||
if (!logo) throw new Error('Logo fehlt')
|
||||
console.log('found icon', logo)
|
||||
|
||||
return true;
|
||||
return true
|
||||
} catch (error) {
|
||||
throw new Error(`Keine Leseberechtigung für Ordner ${extensionDirectory}`);
|
||||
console.error(error)
|
||||
//throw error //new Error(`Keine Leseberechtigung für Ordner ${extensionDirectory}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const isExtensionInstalledAsync = async (extension: Partial<IHaexHubExtension>) => {
|
||||
const isExtensionInstalledAsync = async (
|
||||
extension: Partial<IHaexHubExtension>,
|
||||
) => {
|
||||
try {
|
||||
const extensionPath = await getExtensionPathAsync(extension.id, `${extension.version}`);
|
||||
console.log(`extension ${extension.id} is installed ${await exists(extensionPath)}`);
|
||||
return await exists(extensionPath);
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
extension.id,
|
||||
`${extension.version}`,
|
||||
)
|
||||
console.log(
|
||||
`extension ${extension.id} is installed ${await exists(extensionPath)}`,
|
||||
)
|
||||
return await exists(extensionPath)
|
||||
} catch (error) {
|
||||
return false;
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
||||
const errors = [];
|
||||
const checkManifest = (
|
||||
manifestFile: unknown,
|
||||
): manifestFile is IHaexHubExtensionManifest => {
|
||||
const errors = []
|
||||
|
||||
if (typeof manifestFile !== "object" || manifestFile === null) {
|
||||
errors.push("Manifest ist falsch");
|
||||
return false;
|
||||
if (typeof manifestFile !== 'object' || manifestFile === null) {
|
||||
errors.push('Manifest ist falsch')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!("id" in manifestFile) || typeof manifestFile.id !== "string")
|
||||
errors.push("Keine ID vergeben");
|
||||
if (!('id' in manifestFile) || typeof manifestFile.id !== 'string')
|
||||
errors.push('Keine ID vergeben')
|
||||
|
||||
if (!("name" in manifestFile) || typeof manifestFile.name !== "string")
|
||||
errors.push("Name fehlt");
|
||||
if (!('name' in manifestFile) || typeof manifestFile.name !== 'string')
|
||||
errors.push('Name fehlt')
|
||||
|
||||
if (!("entry" in manifestFile) || typeof manifestFile.entry !== "string")
|
||||
errors.push("Entry fehlerhaft");
|
||||
if (!('entry' in manifestFile) || typeof manifestFile.entry !== 'string')
|
||||
errors.push('Entry fehlerhaft')
|
||||
|
||||
if (!("author" in manifestFile) || typeof manifestFile.author !== "string")
|
||||
errors.push("Author fehlt");
|
||||
if (!('author' in manifestFile) || typeof manifestFile.author !== 'string')
|
||||
errors.push('Author fehlt')
|
||||
|
||||
if (!("url" in manifestFile) || typeof manifestFile.url !== "string") errors.push("Url fehlt");
|
||||
|
||||
if (!("version" in manifestFile) || typeof manifestFile.version !== "string")
|
||||
errors.push("Version fehlt");
|
||||
if (!('url' in manifestFile) || typeof manifestFile.url !== 'string')
|
||||
errors.push('Url fehlt')
|
||||
|
||||
if (
|
||||
!("permissions" in manifestFile) ||
|
||||
typeof manifestFile.permissions !== "object" ||
|
||||
!('version' in manifestFile) ||
|
||||
typeof manifestFile.version !== 'string'
|
||||
)
|
||||
errors.push('Version fehlt')
|
||||
|
||||
if (
|
||||
!('permissions' in manifestFile) ||
|
||||
typeof manifestFile.permissions !== 'object' ||
|
||||
manifestFile.permissions === null
|
||||
) {
|
||||
errors.push("Berechtigungen fehlen");
|
||||
errors.push('Berechtigungen fehlen')
|
||||
}
|
||||
|
||||
if (errors.length) throw errors;
|
||||
if (errors.length) throw errors
|
||||
|
||||
/* const permissions = manifestFile.permissions as Partial<IHaexHubExtensionManifest["permissions"]>;
|
||||
if (
|
||||
@ -122,59 +151,75 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
return false;
|
||||
} */
|
||||
|
||||
return true;
|
||||
};
|
||||
return true
|
||||
}
|
||||
|
||||
const readManifestFileAsync = async (extensionId: string, version: string) => {
|
||||
const readManifestFileAsync = async (
|
||||
extensionId: string,
|
||||
version: string,
|
||||
) => {
|
||||
try {
|
||||
if (!(await isExtensionInstalledAsync({ id: extensionId, version }))) return null;
|
||||
if (!(await isExtensionInstalledAsync({ id: extensionId, version })))
|
||||
return null
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
||||
const manifestPath = await join(extensionPath, manifestFileName);
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
extensionId,
|
||||
`${version}`,
|
||||
)
|
||||
const manifestPath = await join(extensionPath, manifestFileName)
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
await readTextFile(manifestPath),
|
||||
)) as IHaexHubExtensionManifest
|
||||
|
||||
/*
|
||||
TODO implement check, that manifest has valid data
|
||||
*/
|
||||
return manifest;
|
||||
return manifest
|
||||
} catch (error) {
|
||||
console.error("ERROR readManifestFileAsync", error);
|
||||
console.error('ERROR readManifestFileAsync', error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const installAsync = async (extensionDirectory: string | null, global: boolean = true) => {
|
||||
const installAsync = async (extensionDirectory: string | null) => {
|
||||
try {
|
||||
if (!extensionDirectory) throw new Error("Kein Ordner für Erweiterung angegeben");
|
||||
const manifestPath = await join(extensionDirectory, manifestFileName);
|
||||
if (!extensionDirectory)
|
||||
throw new Error('Kein Ordner für Erweiterung angegeben')
|
||||
const manifestPath = await join(extensionDirectory, manifestFileName)
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
await readTextFile(manifestPath),
|
||||
)) as IHaexHubExtensionManifest
|
||||
|
||||
const destination = await getExtensionPathAsync(manifest.id, manifest.version);
|
||||
const destination = await getExtensionPathAsync(
|
||||
manifest.id,
|
||||
manifest.version,
|
||||
)
|
||||
|
||||
await checkSourceExtensionDirectoryAsync(extensionDirectory);
|
||||
await checkSourceExtensionDirectoryAsync(extensionDirectory)
|
||||
|
||||
await invoke("copy_directory", { source: extensionDirectory, destination });
|
||||
await invoke('copy_directory', {
|
||||
source: extensionDirectory,
|
||||
destination,
|
||||
})
|
||||
|
||||
const logoFilePath = await join(destination, "logo.svg");
|
||||
const logoSvg = await readTextFile(logoFilePath);
|
||||
const logoFilePath = await join(destination, logoFileName)
|
||||
const logo = await readTextFile(logoFilePath)
|
||||
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
const res = await currentVault.value?.drizzle.insert(haexExtensions).values({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
author: manifest.author,
|
||||
enabled: true,
|
||||
url: manifest.url,
|
||||
version: manifest.version,
|
||||
icon: logoSvg,
|
||||
});
|
||||
const { currentVault } = storeToRefs(useVaultStore())
|
||||
const res = await currentVault.value?.drizzle
|
||||
.insert(haexExtensions)
|
||||
.values({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
author: manifest.author,
|
||||
enabled: true,
|
||||
url: manifest.url,
|
||||
version: manifest.version,
|
||||
icon: logo,
|
||||
})
|
||||
|
||||
console.log("insert extensions", res);
|
||||
console.log('insert extensions', res)
|
||||
} catch (error) {
|
||||
throw error;
|
||||
throw error
|
||||
/*
|
||||
const resourcePath = await resourceDir();
|
||||
//const manifestPath = await join(extensionDirectory, 'manifest.json');
|
||||
@ -280,7 +325,7 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
|
||||
console.log(`Plugin ${manifest.name} geladen.`); */
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const extensionEntry = computedAsync(
|
||||
async () => {
|
||||
@ -289,153 +334,79 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
const regex = /((href|src)=["'])([^"']+)(["'])/g; */
|
||||
|
||||
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
||||
console.log("extension id or entry missing", currentExtension.value);
|
||||
return ""// "no mani: " + currentExtension.value;
|
||||
console.log('extension id or entry missing', currentExtension.value)
|
||||
return '' // "no mani: " + currentExtension.value;
|
||||
}
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
currentExtension.value?.id,
|
||||
currentExtension.value?.version
|
||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
currentExtension.value?.version,
|
||||
) //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
|
||||
console.log("extensionEntry extensionPath", extensionPath);
|
||||
console.log('extensionEntry extensionPath', extensionPath)
|
||||
const manifest = await readManifestFileAsync(
|
||||
currentExtension.value.id,
|
||||
currentExtension.value.version
|
||||
);
|
||||
currentExtension.value.version,
|
||||
)
|
||||
|
||||
if (!manifest) return ""//"no manifest readable";
|
||||
if (!manifest) return '' //"no manifest readable";
|
||||
|
||||
const entryPath = await join(extensionPath, manifest.entry);
|
||||
//const entryPath = await join(extensionPath, manifest.entry)
|
||||
|
||||
const hexName = stringToHex(
|
||||
JSON.stringify({
|
||||
id: currentExtension.value.id,
|
||||
version: currentExtension.value.version,
|
||||
})
|
||||
);
|
||||
}),
|
||||
)
|
||||
|
||||
return `haex-extension://${hexName}`;
|
||||
return convertFileSrc(entryPath); //`asset://localhost/${entryPath}`;
|
||||
let entryHtml = await readTextFile(entryPath);
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
const replacements = [];
|
||||
let match;
|
||||
/* while ((match = regex.exec(entryHtml)) !== null) {
|
||||
const [fullMatch, prefix, attr, resource, suffix] = match;
|
||||
if (!resource.startsWith("http")) {
|
||||
replacements.push({ match: fullMatch, resource, prefix, suffix });
|
||||
}
|
||||
} */
|
||||
|
||||
for (const { match, resource, prefix, suffix } of replacements) {
|
||||
const srcFile = convertFileSrc(await join(extensionPath, resource));
|
||||
entryHtml = entryHtml.replace(match, `${prefix}${srcFile}${suffix}`);
|
||||
}
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
|
||||
const blob = new Blob([entryHtml], { type: "text/html" });
|
||||
const iframeSrc = URL.createObjectURL(blob);
|
||||
|
||||
console.log("iframeSrc", iframeSrc);
|
||||
|
||||
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
||||
console.log("final path", path); */
|
||||
//manifest.entry = iframeSrc;
|
||||
return iframeSrc;
|
||||
/* await join(
|
||||
path, //`file:/${extensionDirectory}`,
|
||||
manifest.entry
|
||||
); */
|
||||
// Modul-Datei laden
|
||||
//const modulePathFull = await join(basePath, manifest.main);
|
||||
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
||||
manifestPath,
|
||||
}); */
|
||||
/* const iframe = document.createElement('iframe');
|
||||
iframe.src = manifest.entry;
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '100%';
|
||||
iframe.style.border = 'none'; */
|
||||
/* const addonApi = {
|
||||
db_execute: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_execute', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
db_select: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_select', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
}; */
|
||||
/* iframe.onload = () => {
|
||||
iframe.contentWindow?.postMessage(
|
||||
{ type: 'init', payload: addonApi },
|
||||
'*'
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source === iframe.contentWindow) {
|
||||
const { type } = event.data;
|
||||
if (type === 'ready') {
|
||||
console.log(`Plugin ${manifest.name} ist bereit`);
|
||||
}
|
||||
}
|
||||
}); */
|
||||
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
|
||||
|
||||
console.log(`Plugin ${manifest.name} geladen.`); */
|
||||
return `haex-extension://${hexName}`
|
||||
} catch (error) {
|
||||
console.error("ERROR extensionEntry", error);
|
||||
console.error('ERROR extensionEntry', error)
|
||||
}
|
||||
},
|
||||
null,
|
||||
{ lazy: true }
|
||||
);
|
||||
{ lazy: true },
|
||||
)
|
||||
|
||||
const loadExtensionsAsync = async () => {
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
const { currentVault } = storeToRefs(useVaultStore())
|
||||
|
||||
const extensions = (await currentVault.value?.drizzle.select().from(haexExtensions)) ?? [];
|
||||
const extensions =
|
||||
(await currentVault.value?.drizzle.select().from(haexExtensions)) ?? []
|
||||
|
||||
//if (!extensions?.length) return false;
|
||||
|
||||
const installedExtensions = await filterAsync(extensions, isExtensionInstalledAsync);
|
||||
console.log("loadExtensionsAsync installedExtensions", installedExtensions);
|
||||
const installedExtensions = await filterAsync(
|
||||
extensions,
|
||||
isExtensionInstalledAsync,
|
||||
)
|
||||
console.log('loadExtensionsAsync installedExtensions', installedExtensions)
|
||||
|
||||
availableExtensions.value =
|
||||
extensions.map((extension) => ({
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
icon: extension.icon ?? "",
|
||||
author: extension.author ?? "",
|
||||
version: extension.version ?? "",
|
||||
name: extension.name ?? '',
|
||||
icon: extension.icon ?? '',
|
||||
author: extension.author ?? '',
|
||||
version: extension.version ?? '',
|
||||
enabled: extension.enabled ? true : false,
|
||||
installed: installedExtensions.includes(extension),
|
||||
})) ?? [];
|
||||
})) ?? []
|
||||
|
||||
console.log("loadExtensionsAsync", availableExtensions.value);
|
||||
return true;
|
||||
};
|
||||
console.log('loadExtensionsAsync', availableExtensions.value)
|
||||
return true
|
||||
}
|
||||
|
||||
const removeExtensionAsync = async (id: string, version: string) => {
|
||||
try {
|
||||
console.log("remove extension", id, version);
|
||||
await removeExtensionFromVaultAsync(id, version);
|
||||
await removeExtensionFilesAsync(id, version);
|
||||
console.log('remove extension', id, version)
|
||||
await removeExtensionFromVaultAsync(id, version)
|
||||
await removeExtensionFilesAsync(id, version)
|
||||
} catch (error) {
|
||||
throw new Error(JSON.stringify(error));
|
||||
throw new Error(JSON.stringify(error))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
availableExtensions,
|
||||
@ -445,48 +416,63 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
extensionLinks,
|
||||
installAsync,
|
||||
isActive,
|
||||
isExtensionInstalledAsync,
|
||||
loadExtensionsAsync,
|
||||
readManifestFileAsync,
|
||||
removeExtensionAsync,
|
||||
getExtensionPathAsync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getMimeType = (file: string) => {
|
||||
if (file.endsWith(".css")) return "text/css";
|
||||
if (file.endsWith(".js")) return "text/javascript";
|
||||
return "text/plain";
|
||||
};
|
||||
/* const getMimeType = (file: string) => {
|
||||
if (file.endsWith('.css')) return 'text/css'
|
||||
if (file.endsWith('.js')) return 'text/javascript'
|
||||
return 'text/plain'
|
||||
} */
|
||||
|
||||
const removeExtensionFromVaultAsync = async (id: string | null, version: string | null) => {
|
||||
if (!id) throw new Error("Erweiterung kann nicht gelöscht werden. Es keine ID angegeben");
|
||||
const removeExtensionFromVaultAsync = async (
|
||||
id: string | null,
|
||||
version: string | null,
|
||||
) => {
|
||||
if (!id)
|
||||
throw new Error(
|
||||
'Erweiterung kann nicht gelöscht werden. Es keine ID angegeben',
|
||||
)
|
||||
|
||||
if (!version)
|
||||
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||
throw new Error(
|
||||
'Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben',
|
||||
)
|
||||
|
||||
const { currentVault } = useVaultStore();
|
||||
const { currentVault } = useVaultStore()
|
||||
const removedExtensions = await currentVault?.drizzle
|
||||
.delete(haexExtensions)
|
||||
.where(and(eq(haexExtensions.id, id), eq(haexExtensions.version, version)));
|
||||
return removedExtensions;
|
||||
};
|
||||
.where(and(eq(haexExtensions.id, id), eq(haexExtensions.version, version)))
|
||||
return removedExtensions
|
||||
}
|
||||
|
||||
const removeExtensionFilesAsync = async (id: string | null, version: string | null) => {
|
||||
const removeExtensionFilesAsync = async (
|
||||
id: string | null,
|
||||
version: string | null,
|
||||
) => {
|
||||
try {
|
||||
const { getExtensionPathAsync } = useExtensionsStore();
|
||||
if (!id) throw new Error("Erweiterung kann nicht gelöscht werden. Es keine ID angegeben");
|
||||
const { getExtensionPathAsync } = useExtensionsStore()
|
||||
if (!id)
|
||||
throw new Error(
|
||||
'Erweiterung kann nicht gelöscht werden. Es keine ID angegeben',
|
||||
)
|
||||
|
||||
if (!version)
|
||||
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||
throw new Error(
|
||||
'Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben',
|
||||
)
|
||||
|
||||
const extensionDirectory = await getExtensionPathAsync(id, version);
|
||||
const extensionDirectory = await getExtensionPathAsync(id, version)
|
||||
await remove(extensionDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("ERROR removeExtensionFilesAsync", error);
|
||||
throw new Error(JSON.stringify(error));
|
||||
console.error('ERROR removeExtensionFilesAsync', error)
|
||||
throw new Error(JSON.stringify(error))
|
||||
}
|
||||
};
|
||||
|
||||
const replaceUrlWithAssetProtocolAsync = () => { };
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
export interface IHaexNotication {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
alt?: string;
|
||||
title: string
|
||||
description?: string
|
||||
icon?: string
|
||||
image?: string
|
||||
alt?: string
|
||||
date: Date
|
||||
}
|
||||
|
||||
export const useNotificationStore = defineStore('notificationStore', () => {
|
||||
@ -13,10 +14,11 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
||||
alt: 'test',
|
||||
description: 'Ganz was tolles',
|
||||
image: 'https://cdn.flyonui.com/fy-assets/avatar/avatar-1.png',
|
||||
date: new Date(),
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
return {
|
||||
notifications,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
import type { RouteLocationAsRelativeGeneric } from "vue-router";
|
||||
import type { RouteLocationAsRelativeGeneric } from 'vue-router'
|
||||
|
||||
export interface ISidebarItem {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
iconType?: "icon" | "svg";
|
||||
name: string
|
||||
icon: string
|
||||
tooltip?: string
|
||||
id: string
|
||||
to?: RouteLocationAsRelativeGeneric
|
||||
iconType?: 'icon' | 'svg'
|
||||
}
|
||||
|
||||
export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
const isVisible = ref(true);
|
||||
export const useSidebarStore = defineStore('sidebarStore', () => {
|
||||
const isVisible = ref(true)
|
||||
|
||||
const menu = ref<ISidebarItem[]>([
|
||||
{
|
||||
id: "haex-extensions-add",
|
||||
name: "Haex Extensions",
|
||||
icon: "gg:extension",
|
||||
to: { name: "extensionOverview" },
|
||||
id: 'haex-pass',
|
||||
name: 'HaexPass',
|
||||
icon: 'mdi:safe',
|
||||
to: { name: 'haexpassOverview' },
|
||||
},
|
||||
]);
|
||||
{
|
||||
id: 'haex-extensions',
|
||||
name: 'Haex Extensions',
|
||||
icon: 'gg:extension',
|
||||
to: { name: 'extensionOverview' },
|
||||
},
|
||||
])
|
||||
|
||||
/* const loadAsync = async (id: string) => {
|
||||
extensions.value.some(async (extension) => {
|
||||
@ -36,5 +42,5 @@ export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
menu,
|
||||
isVisible,
|
||||
//loadAsync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,66 +1,76 @@
|
||||
|
||||
import * as schema from "@/../src-tauri/database/schemas/vault";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle, SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
|
||||
import * as schema from '@/../src-tauri/database/schemas/vault'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy'
|
||||
import { drizzle } from 'drizzle-orm/sqlite-proxy'
|
||||
|
||||
interface IVault {
|
||||
name: string;
|
||||
drizzle: SqliteRemoteDatabase<typeof schema>;
|
||||
name: string
|
||||
drizzle: SqliteRemoteDatabase<typeof schema>
|
||||
}
|
||||
interface IOpenVaults {
|
||||
[vaultId: string]: IVault;
|
||||
[vaultId: string]: IVault
|
||||
}
|
||||
|
||||
export const useVaultStore = defineStore("vaultStore", () => {
|
||||
|
||||
export const useVaultStore = defineStore('vaultStore', () => {
|
||||
const currentVaultId = computed<string | undefined>({
|
||||
get: () => getSingleRouteParam(useRouter().currentRoute.value.params.vaultId),
|
||||
get: () =>
|
||||
getSingleRouteParam(useRouter().currentRoute.value.params.vaultId),
|
||||
set: (newVaultId) => {
|
||||
useRouter().currentRoute.value.params.vaultId = newVaultId ?? "";
|
||||
useRouter().currentRoute.value.params.vaultId = newVaultId ?? ''
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const defaultVaultName = ref("HaexHub")
|
||||
const defaultVaultName = ref('HaexHub')
|
||||
const currentVaultName = ref(defaultVaultName.value)
|
||||
|
||||
const read_only = computed<boolean>({
|
||||
get: () => {
|
||||
console.log("query showSidebar", useRouter().currentRoute.value.query.readonly);
|
||||
console.log(
|
||||
'query showSidebar',
|
||||
useRouter().currentRoute.value.query.readonly,
|
||||
)
|
||||
return JSON.parse(
|
||||
getSingleRouteParam(useRouter().currentRoute.value.query.readonly) || "false"
|
||||
);
|
||||
getSingleRouteParam(useRouter().currentRoute.value.query.readonly) ||
|
||||
'false',
|
||||
)
|
||||
},
|
||||
set: (readonly) => {
|
||||
const router = useRouter();
|
||||
const router = useRouter()
|
||||
router.replace({
|
||||
query: {
|
||||
...router.currentRoute.value.query,
|
||||
readonly: JSON.stringify(readonly ? true : false),
|
||||
},
|
||||
});
|
||||
})
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const openVaults = ref<IOpenVaults>({});
|
||||
const openVaults = ref<IOpenVaults>({})
|
||||
|
||||
const currentVault = computed(() => openVaults.value?.[currentVaultId.value ?? ""])
|
||||
const currentVault = computed(
|
||||
() => openVaults.value?.[currentVaultId.value ?? ''],
|
||||
)
|
||||
|
||||
|
||||
|
||||
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
||||
const openAsync = async ({
|
||||
path = '',
|
||||
password,
|
||||
}: {
|
||||
path: string
|
||||
password: string
|
||||
}) => {
|
||||
try {
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
const result = await invoke<string>('open_encrypted_database', {
|
||||
path,
|
||||
key: password,
|
||||
});
|
||||
})
|
||||
|
||||
if (result !== "success") throw new Error(result);
|
||||
if (result !== 'success') throw new Error(result)
|
||||
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
const vaultId = await getVaultIdAsync(path)
|
||||
const seperator = platform() === 'windows' ? '\\' : '/'
|
||||
const fileName = path.split(seperator).pop()
|
||||
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
@ -68,53 +78,65 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params: unknown[], method) => {
|
||||
let rows: any = [];
|
||||
let results = [];
|
||||
let rows: unknown[] = []
|
||||
let results: any = []
|
||||
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
console.log("sql_select", sql, params);
|
||||
rows = await invoke("sql_select", { sql, params }).catch((e) => {
|
||||
console.error("SQL select Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
console.log("select", rows);
|
||||
console.log('sql_select', sql, params)
|
||||
rows = await invoke<unknown[]>('sql_select', {
|
||||
sql,
|
||||
params,
|
||||
}).catch((e) => {
|
||||
console.error('SQL select Error:', e, sql, params)
|
||||
return []
|
||||
})
|
||||
console.log('select', rows)
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await invoke("sql_execute", { sql, params }).catch((e) => {
|
||||
console.error("SQL execute Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
return { rows: [] };
|
||||
rows = await invoke<unknown[]>('sql_execute', {
|
||||
sql,
|
||||
params,
|
||||
}).catch((e) => {
|
||||
console.error('SQL execute Error:', e, sql, params)
|
||||
return []
|
||||
})
|
||||
return { rows: [] }
|
||||
}
|
||||
|
||||
results = method === "all" ? rows : rows[0];
|
||||
results = method === 'all' ? rows : rows[0]
|
||||
|
||||
return { rows: results };
|
||||
return { rows: results }
|
||||
},
|
||||
{ schema: schema, logger: true }
|
||||
{ schema: schema, logger: true },
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
const { addVaultAsync } = useLastVaultStore()
|
||||
await addVaultAsync({ path })
|
||||
|
||||
return vaultId;
|
||||
return vaultId
|
||||
} catch (error) {
|
||||
console.error("Error openAsync ", error);
|
||||
return false;
|
||||
console.error('Error openAsync ', error)
|
||||
return false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const refreshDatabaseAsync = async () => {
|
||||
console.log("refreshDatabaseAsync");
|
||||
console.log('refreshDatabaseAsync')
|
||||
/* if (!currentVault.value?.database.close) {
|
||||
return navigateTo(useLocaleRoute()({ name: 'vaultOpen' }));
|
||||
} */
|
||||
};
|
||||
}
|
||||
|
||||
const createAsync = async ({ path, password }: { path: string; password: string }) => {
|
||||
const createAsync = async ({
|
||||
path,
|
||||
password,
|
||||
}: {
|
||||
path: string
|
||||
password: string
|
||||
}) => {
|
||||
/* const existDb = await exists('default.db', {
|
||||
baseDir: BaseDirectory.Resource,
|
||||
}); */
|
||||
@ -122,16 +144,16 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
/* const existDb = await resolveResource('resources/default.db');
|
||||
if (!existDb) throw new Error('Keine Datenbank da');
|
||||
await copyFile(existDb, path); */
|
||||
const result = await invoke("create_encrypted_database", {
|
||||
const result = await invoke('create_encrypted_database', {
|
||||
path,
|
||||
key: password,
|
||||
});
|
||||
console.log("create_encrypted_database", result);
|
||||
return await openAsync({ path, password });
|
||||
};
|
||||
})
|
||||
console.log('create_encrypted_database', result)
|
||||
return await openAsync({ path, password })
|
||||
}
|
||||
|
||||
const closeAsync = async () => {
|
||||
if (!currentVaultId.value) return;
|
||||
if (!currentVaultId.value) return
|
||||
|
||||
/* if (
|
||||
typeof openVaults.value?.[currentVaultId.value]?.database?.close ===
|
||||
@ -140,8 +162,8 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
console.log('close db', openVaults.value?.[currentVaultId.value]);
|
||||
return openVaults.value?.[currentVaultId.value]?.database?.close();
|
||||
} */
|
||||
delete openVaults.value?.[currentVaultId.value];
|
||||
};
|
||||
delete openVaults.value?.[currentVaultId.value]
|
||||
}
|
||||
|
||||
const syncLocaleAsync = async () => {
|
||||
try {
|
||||
@ -154,21 +176,25 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
|
||||
if (currentLocaleRow?.[0]?.value) {
|
||||
const currentLocale = app.$i18n.availableLocales.find(
|
||||
(locale) => locale === currentLocaleRow[0].value
|
||||
(locale) => locale === currentLocaleRow[0].value,
|
||||
)
|
||||
await app.$i18n.setLocale(currentLocale ?? app.$i18n.defaultLocale)
|
||||
} else {
|
||||
await currentVault.value?.drizzle
|
||||
.insert(schema.haexSettings)
|
||||
.values({ id: crypto.randomUUID(), key: 'locale', value: app.$i18n.locale.value })
|
||||
await currentVault.value?.drizzle.insert(schema.haexSettings).values({
|
||||
id: crypto.randomUUID(),
|
||||
key: 'locale',
|
||||
value: app.$i18n.locale.value,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("ERROR syncLocaleAsync", error)
|
||||
console.log('ERROR syncLocaleAsync', error)
|
||||
}
|
||||
}
|
||||
|
||||
const syncThemeAsync = async () => {
|
||||
const { availableThemes, defaultTheme, currentTheme } = storeToRefs(useUiStore())
|
||||
const { availableThemes, defaultTheme, currentTheme } = storeToRefs(
|
||||
useUiStore(),
|
||||
)
|
||||
const currentThemeRow = await currentVault.value?.drizzle
|
||||
.select()
|
||||
.from(schema.haexSettings)
|
||||
@ -176,7 +202,7 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
|
||||
if (currentThemeRow?.[0]?.value) {
|
||||
const theme = availableThemes.value.find(
|
||||
(theme) => theme.name === currentThemeRow[0].value
|
||||
(theme) => theme.name === currentThemeRow[0].value,
|
||||
)
|
||||
currentTheme.value = theme ?? defaultTheme.value
|
||||
} else {
|
||||
@ -195,7 +221,8 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
.where(eq(schema.haexSettings.key, 'vaultName'))
|
||||
|
||||
if (currentVaultNameRow?.[0]?.value) {
|
||||
currentVaultName.value = currentVaultNameRow.at(0)?.value ?? defaultVaultName.value
|
||||
currentVaultName.value =
|
||||
currentVaultNameRow.at(0)?.value ?? defaultVaultName.value
|
||||
} else {
|
||||
await currentVault.value?.drizzle.insert(schema.haexSettings).values({
|
||||
id: crypto.randomUUID(),
|
||||
@ -206,8 +233,11 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
}
|
||||
|
||||
const updateVaultNameAsync = async (newVaultName?: string | null) => {
|
||||
console.log("set new vaultName", newVaultName)
|
||||
return currentVault.value?.drizzle.update(schema.haexSettings).set({ value: newVaultName ?? defaultVaultName.value }).where(eq(schema.haexSettings.key, "vaultName"))
|
||||
console.log('set new vaultName', newVaultName)
|
||||
return currentVault.value?.drizzle
|
||||
.update(schema.haexSettings)
|
||||
.set({ value: newVaultName ?? defaultVaultName.value })
|
||||
.where(eq(schema.haexSettings.key, 'vaultName'))
|
||||
}
|
||||
|
||||
return {
|
||||
@ -224,24 +254,21 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
syncThemeAsync,
|
||||
syncVaultNameAsync,
|
||||
updateVaultNameAsync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getVaultIdAsync = async (path: string) => {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(path);
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(path)
|
||||
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
|
||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); // convert bytes to hex string
|
||||
console.log("vaultId", hashHex);
|
||||
return hashHex;
|
||||
};
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
|
||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string
|
||||
console.log('vaultId', hashHex)
|
||||
return hashHex
|
||||
}
|
||||
|
||||
const isSelectQuery = (sql: string) => {
|
||||
const selectRegex = /^\s*SELECT\b/i;
|
||||
return selectRegex.test(sql);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const selectRegex = /^\s*SELECT\b/i
|
||||
return selectRegex.test(sql)
|
||||
}
|
||||
|
||||