mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
laden von erweiterungen implementiert
This commit is contained in:
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
# .prettierrc.toml
|
||||
|
||||
useTabs = false
|
||||
tabWidth = 2
|
||||
printWidth = 100
|
||||
endOfLine = "lf"
|
||||
|
||||
# Not supported yet
|
||||
# trailingComma = "es5"
|
||||
# embeddedLanguageFormatting = "auto"
|
||||
7
app.config.ts
Normal file
7
app.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// ~/app.config.ts
|
||||
export default defineAppConfig({
|
||||
icon: {
|
||||
mode: "css",
|
||||
cssLayer: "base",
|
||||
},
|
||||
});
|
||||
@ -1,3 +1,5 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2024-11-01",
|
||||
@ -5,22 +7,21 @@ export default defineNuxtConfig({
|
||||
modules: [
|
||||
"nuxt-zod-i18n",
|
||||
"@nuxtjs/i18n",
|
||||
"@nuxtjs/tailwindcss",
|
||||
"@pinia/nuxt",
|
||||
"@vueuse/nuxt",
|
||||
"@nuxt/icon",
|
||||
"nuxt-snackbar",
|
||||
"@nuxt/image",
|
||||
"nuxt-svgo-loader",
|
||||
],
|
||||
|
||||
imports: {
|
||||
dirs: ["composables/**", "stores/**", "components/**", "pages/**"],
|
||||
dirs: ["composables/**", "stores/**", "components/**", "pages/**", "types/**"],
|
||||
},
|
||||
|
||||
i18n: {
|
||||
strategy: "prefix_and_default",
|
||||
defaultLocale: "de",
|
||||
vueI18n: "../src/i18n/i18n.config.ts",
|
||||
vueI18n: "~/i18n/i18n.config.ts",
|
||||
|
||||
locales: [
|
||||
{ code: "de", language: "de-DE", isCatchallLocale: true },
|
||||
@ -54,13 +55,18 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
|
||||
css: ["~/assets/css/main.css"],
|
||||
|
||||
devtools: { enabled: true },
|
||||
|
||||
srcDir: "./src",
|
||||
// Enable SSG
|
||||
ssr: false,
|
||||
// Enables the development server to be discoverable by other devices when running on iOS physical devices
|
||||
devServer: { host: process.env.TAURI_DEV_HOST || "localhost", port: 3003 },
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
// Better support for Tauri CLI output
|
||||
clearScreen: false,
|
||||
// Enable environment variables
|
||||
@ -71,11 +77,5 @@ export default defineNuxtConfig({
|
||||
// Tauri requires a consistent port
|
||||
strictPort: true,
|
||||
},
|
||||
|
||||
/* plugins: [wasm(), topLevelAwait()],
|
||||
worker: {
|
||||
format: 'es',
|
||||
plugins: () => [wasm(), topLevelAwait()],
|
||||
}, */
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"@nuxt/image": "1.10.0",
|
||||
"@nuxtjs/i18n": "^9.5.3",
|
||||
"@pinia/nuxt": "^0.10.1",
|
||||
"@tailwindcss/vite": "^4.1.5",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||
"@tauri-apps/plugin-fs": "^2.2.1",
|
||||
@ -30,21 +31,23 @@
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"@vueuse/nuxt": "^13.1.0",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"flyonui": "^2.1.0",
|
||||
"nuxt": "^3.17.0",
|
||||
"nuxt-snackbar": "1.3.0",
|
||||
"nuxt-svgo-loader": "0.5.0",
|
||||
"nuxt-zod-i18n": "^1.11.5",
|
||||
"tailwindcss": "^4.1.5",
|
||||
"vue": "^3.5.13",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@egoist/tailwindcss-icons": "^1.9.0",
|
||||
"@iconify/json": "^2.2.332",
|
||||
"@iconify/tailwind": "^1.2.0",
|
||||
"@iconify/tailwind4": "^1.0.6",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"flyonui": "^1.3.1",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^6.3.3",
|
||||
"vue-tsc": "^2.2.10"
|
||||
|
||||
530
pnpm-lock.yaml
generated
530
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id, extension_id, resource, operation, path \n FROM haex_extensions_permissions \n WHERE extension_id = ? AND resource = ? AND operation = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "extension_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "resource",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "operation",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "a73e92ff12dca9b046a6440b9a68b002662b594f7f569ee71de11e00c23ca625"
|
||||
}
|
||||
5
src-tauri/Cargo.lock
generated
5
src-tauri/Cargo.lock
generated
@ -1536,6 +1536,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"fs_extra",
|
||||
"hex",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"rusqlite",
|
||||
@ -4516,9 +4517,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.1"
|
||||
version = "1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
||||
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
||||
@ -24,15 +24,16 @@ rusqlite = { version = "0.35.0", features = [
|
||||
] }
|
||||
#libsqlite3-sys = { version = "0.28", features = ["bundled-sqlcipher"] }
|
||||
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1.45", features = ["macros", "rt-multi-thread"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
hex = "0.4"
|
||||
serde_json = "1"
|
||||
base64 = "0.22"
|
||||
mime_guess = "2.0"
|
||||
mime = "0.3"
|
||||
fs_extra = "1.3.0"
|
||||
sqlparser = { version = "0.56.0", features = [] }
|
||||
tauri = { version = "2.5", features = ["protocol-asset", "custom-protocol"] }
|
||||
tauri = { version = "2.5", features = ["protocol-asset"] }
|
||||
tauri-plugin-dialog = "2.2"
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-opener = "2.2"
|
||||
|
||||
@ -227,5 +227,5 @@ pub fn open_encrypted_database(
|
||||
let mut db = state.0.lock().map_err(|e| e.to_string())?;
|
||||
*db = Some(conn);
|
||||
|
||||
Ok(format!("Verschlüsselte CRDT-Datenbank geöffnet: {}", path))
|
||||
Ok(format!("success"))
|
||||
}
|
||||
|
||||
@ -1,12 +1,70 @@
|
||||
use mime;
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use tauri::{
|
||||
http::{Request, Response, Uri},
|
||||
AppHandle, Error as TauriError, Manager, Runtime,
|
||||
http::{Request, Response},
|
||||
AppHandle, Error as TauriError, Manager, Runtime, UriSchemeContext,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ExtensionInfo {
|
||||
id: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DataProcessingError {
|
||||
HexDecoding(hex::FromHexError),
|
||||
Utf8Conversion(std::string::FromUtf8Error),
|
||||
JsonParsing(serde_json::Error),
|
||||
}
|
||||
|
||||
// Implementierung von Display für benutzerfreundliche Fehlermeldungen
|
||||
impl fmt::Display for DataProcessingError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DataProcessingError::HexDecoding(e) => write!(f, "Hex-Dekodierungsfehler: {}", e),
|
||||
DataProcessingError::Utf8Conversion(e) => {
|
||||
write!(f, "UTF-8-Konvertierungsfehler: {}", e)
|
||||
}
|
||||
DataProcessingError::JsonParsing(e) => write!(f, "JSON-Parsing-Fehler: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementierung von std::error::Error (optional, aber gute Praxis für bibliotheksähnlichen Code)
|
||||
impl std::error::Error for DataProcessingError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
DataProcessingError::HexDecoding(e) => Some(e),
|
||||
DataProcessingError::Utf8Conversion(e) => Some(e),
|
||||
DataProcessingError::JsonParsing(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementierung von From-Traits für einfache Verwendung des '?'-Operators
|
||||
impl From<hex::FromHexError> for DataProcessingError {
|
||||
fn from(err: hex::FromHexError) -> Self {
|
||||
DataProcessingError::HexDecoding(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for DataProcessingError {
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
DataProcessingError::Utf8Conversion(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for DataProcessingError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
DataProcessingError::JsonParsing(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_directory(source: String, destination: String) -> Result<(), String> {
|
||||
println!(
|
||||
"Kopiere Verzeichnis von '{}' nach '{}'",
|
||||
@ -46,6 +104,7 @@ pub fn copy_directory(source: String, destination: String) -> Result<(), String>
|
||||
pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
extension_id: &str,
|
||||
extension_version: &str,
|
||||
requested_asset_path: &str,
|
||||
) -> Result<PathBuf, String> {
|
||||
// 1. Validiere die Extension ID
|
||||
@ -57,6 +116,17 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||
return Err(format!("Ungültige Extension ID: {}", extension_id));
|
||||
}
|
||||
|
||||
if extension_version.is_empty()
|
||||
|| !extension_version
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.')
|
||||
{
|
||||
return Err(format!(
|
||||
"Ungültige Extension Version: {}",
|
||||
extension_version
|
||||
));
|
||||
}
|
||||
|
||||
// 2. Bestimme das Basisverzeichnis für alle Erweiterungen (Resource Directory)
|
||||
let base_extensions_dir = app_handle
|
||||
.path()
|
||||
@ -66,7 +136,8 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||
.join("extensions");
|
||||
|
||||
// 3. Verzeichnis für die spezifische Erweiterung
|
||||
let specific_extension_dir = base_extensions_dir.join(extension_id);
|
||||
let specific_extension_dir =
|
||||
base_extensions_dir.join(format!("{}/{}", extension_id, extension_version));
|
||||
|
||||
// 4. Bereinige den angeforderten Asset-Pfad
|
||||
let clean_relative_path = requested_asset_path
|
||||
@ -112,70 +183,106 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_extension_protocol<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
pub fn extension_protocol_handler<R: Runtime>(
|
||||
context: &UriSchemeContext<'_, R>,
|
||||
request: &Request<Vec<u8>>,
|
||||
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||
let uri_ref = request.uri(); // uri_ref ist &Uri
|
||||
let uri_ref = request.uri();
|
||||
println!("Protokoll Handler für: {}", uri_ref);
|
||||
|
||||
let uri_string = uri_ref.to_string(); // Konvertiere zu String
|
||||
let parsed_uri = Uri::from_str(&uri_string)?; // Parse aus &str
|
||||
|
||||
let extension_id = parsed_uri
|
||||
let host = uri_ref
|
||||
.host()
|
||||
.ok_or("Kein Host (Extension ID) in URI gefunden")?
|
||||
.to_string();
|
||||
|
||||
let requested_asset_path = parsed_uri.path();
|
||||
let path_str = uri_ref.path();
|
||||
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_path_to_load = if requested_asset_path == "/" || requested_asset_path.is_empty() {
|
||||
"index.html"
|
||||
} else {
|
||||
requested_asset_path
|
||||
};
|
||||
match process_hex_encoded_json(&host) {
|
||||
Ok(info) => {
|
||||
println!("Daten erfolgreich verarbeitet:");
|
||||
println!(" ID: {}", info.id);
|
||||
println!(" Version: {}", info.version);
|
||||
let absolute_secure_path = resolve_secure_extension_asset_path(
|
||||
context.app_handle(),
|
||||
&info.id,
|
||||
&info.version,
|
||||
&raw_asset_path,
|
||||
)?;
|
||||
|
||||
// Sicheren Dateisystempfad auflösen (nutzt jetzt AppHandle)
|
||||
let absolute_secure_path =
|
||||
resolve_secure_extension_asset_path(app_handle, &extension_id, asset_path_to_load)?;
|
||||
println!("absolute_secure_path: {}", absolute_secure_path.display());
|
||||
|
||||
// Datei lesen und Response erstellen (Code wie vorher)
|
||||
match fs::read(&absolute_secure_path) {
|
||||
Ok(content) => {
|
||||
let mime_type = mime_guess::from_path(&absolute_secure_path)
|
||||
.first_or(mime::APPLICATION_OCTET_STREAM)
|
||||
.to_string();
|
||||
println!(
|
||||
"Liefere {} ({}) für Extension '{}'",
|
||||
absolute_secure_path.display(),
|
||||
mime_type,
|
||||
extension_id
|
||||
);
|
||||
// *** KORREKTUR: Verwende Response::builder() ***
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", mime_type) // Setze Header über .header()
|
||||
.body(content) // body() gibt Result<Response<Vec<u8>>, Error> zurück
|
||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||
if absolute_secure_path.exists() && absolute_secure_path.is_file() {
|
||||
match fs::read(&absolute_secure_path) {
|
||||
Ok(content) => {
|
||||
let mime_type = mime_guess::from_path(&absolute_secure_path)
|
||||
.first_or(mime::APPLICATION_OCTET_STREAM)
|
||||
.to_string();
|
||||
println!(
|
||||
"Liefere {} ({}) ",
|
||||
absolute_secure_path.display(),
|
||||
mime_type
|
||||
);
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", mime_type)
|
||||
.body(content)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Fehler beim Lesen der Datei {}: {}",
|
||||
absolute_secure_path.display(),
|
||||
e
|
||||
);
|
||||
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
|
||||
404
|
||||
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
403
|
||||
} else {
|
||||
500
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.status(status_code)
|
||||
.body(Vec::new()) // Leerer Body für Fehler
|
||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Datei nicht gefunden oder es ist keine Datei
|
||||
eprintln!(
|
||||
"Asset nicht gefunden oder ist kein File: {}",
|
||||
absolute_secure_path.display()
|
||||
);
|
||||
Response::builder()
|
||||
.status(404) // HTTP 404 Not Found
|
||||
.body(Vec::new())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Fehler beim Lesen der Datei {}: {}",
|
||||
absolute_secure_path.display(),
|
||||
e
|
||||
);
|
||||
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
|
||||
404
|
||||
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
403
|
||||
} else {
|
||||
500
|
||||
};
|
||||
// *** KORREKTUR: Verwende Response::builder() auch für Fehler ***
|
||||
eprintln!("Fehler bei der Datenverarbeitung: {}", e);
|
||||
|
||||
Response::builder()
|
||||
.status(status_code)
|
||||
.status("500")
|
||||
.body(Vec::new()) // Leerer Body für Fehler
|
||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_hex_encoded_json(hex_input: &str) -> Result<ExtensionInfo, DataProcessingError> {
|
||||
// Schritt 1: Hex-String zu Bytes dekodieren
|
||||
let bytes = hex::decode(hex_input)?; // Konvertiert hex::FromHexError automatisch
|
||||
|
||||
// Schritt 2: Bytes zu UTF-8-String konvertieren
|
||||
let json_string = String::from_utf8(bytes)?; // Konvertiert FromUtf8Error automatisch
|
||||
|
||||
// Schritt 3: JSON-String zu Struktur parsen
|
||||
let extension_info: ExtensionInfo = serde_json::from_str(&json_string)?; // Konvertiert serde_json::Error automatisch
|
||||
|
||||
Ok(extension_info)
|
||||
}
|
||||
|
||||
@ -12,30 +12,37 @@ pub fn run() {
|
||||
let protocol_name = "haex-extension";
|
||||
|
||||
tauri::Builder::default()
|
||||
/* .register_uri_scheme_protocol(protocol_name, move |app_handle, request| {
|
||||
// Extrahiere den Request aus dem Kontext
|
||||
//let request = context.request();
|
||||
// Rufe die Handler-Logik auf
|
||||
match extension::core::handle_extension_protocol(0, &request) {
|
||||
Ok(response) => response, // Gib die erfolgreiche Response zurück
|
||||
.register_uri_scheme_protocol(protocol_name, move |context, request| {
|
||||
match extension::core::extension_protocol_handler(&context, &request) {
|
||||
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
|
||||
Err(e) => {
|
||||
// Logge den Fehler
|
||||
eprintln!("Fehler im Protokoll-Handler für '{}': {}", request.uri(), e);
|
||||
// Gib eine generische 500er Fehler-Response zurück
|
||||
Response::builder()
|
||||
.status(500)
|
||||
.mimetype("text/plain") // Einfacher Text für die Fehlermeldung
|
||||
.body(format!("Internal Server Error: {}", e).into_bytes()) // Body als Vec<u8>
|
||||
.unwrap() // .body() kann hier nicht fehlschlagen
|
||||
// Wenn der Handler einen Fehler zurückgibt, logge ihn und erstelle eine Fehler-Response
|
||||
eprintln!(
|
||||
"Fehler im Custom Protocol Handler für URI '{}': {}",
|
||||
request.uri(),
|
||||
e
|
||||
);
|
||||
// Erstelle eine HTTP 500 Fehler-Response
|
||||
// Du kannst hier auch spezifischere Fehler-Responses bauen, falls gewünscht.
|
||||
tauri::http::Response::builder()
|
||||
.status(500)
|
||||
.header("Content-Type", "text/plain") // Optional, aber gut für Klarheit
|
||||
.body(Vec::from(format!(
|
||||
"Interner Serverfehler im Protokollhandler: {}",
|
||||
e
|
||||
)))
|
||||
.unwrap_or_else(|build_err| {
|
||||
// Fallback, falls selbst das Erstellen der Fehler-Response fehlschlägt
|
||||
eprintln!("Konnte Fehler-Response nicht erstellen: {}", build_err);
|
||||
tauri::http::Response::builder()
|
||||
.status(500)
|
||||
.body(Vec::new())
|
||||
.expect("Konnte minimale Fallback-Response nicht erstellen")
|
||||
})
|
||||
}
|
||||
}
|
||||
}) */
|
||||
/* .setup(move |app| {
|
||||
// Der .setup Hook ist jetzt nur noch für andere Initialisierungen da
|
||||
// Der AppHandle ist hier nicht mehr nötig für die Protokoll-Registrierung
|
||||
println!("App Setup abgeschlossen.");
|
||||
Ok(())
|
||||
}) */
|
||||
//extension::core::extension_protocol_handler(&context, &request)
|
||||
})
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.manage(DbConnection(Mutex::new(None)))
|
||||
.manage(ExtensionState::default())
|
||||
@ -57,3 +64,29 @@ pub fn run() {
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
/* fn extension_protocol_handler(
|
||||
app_handle: &tauri::AppHandle, // Beachten Sie die Signaturänderung in neueren Tauri-Versionen
|
||||
request: &tauri::http::Request<Vec<u8>>,
|
||||
) -> Result<tauri::http::Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let uri_str = request.uri().to_string();
|
||||
let parsed_url = match Url::parse(&uri_str) {
|
||||
Ok(url) => url,
|
||||
Err(e) => {
|
||||
eprintln!("Fehler beim Parsen der URL '{}': {}", uri_str, e);
|
||||
return Ok(tauri::http::ResponseBuilder::new().status(400).body(Vec::from("Ungültige URL"))?);
|
||||
}
|
||||
};
|
||||
|
||||
let plugin_id = parsed_url.host_str().ok_or_else(|| "Fehlende Plugin-ID in der URL".to_string())?;
|
||||
let path_segments: Vec<&str> = parsed_url.path_segments().ok_or_else(|| "URL hat keinen Pfad".to_string())?.collect();
|
||||
|
||||
if path_segments.len() < 2 {
|
||||
eprintln!("Unvollständiger Pfad in URL: {}", uri_str);
|
||||
return Ok(tauri::http::Response::new().status(400).body(Vec::from("Unvollständiger Pfad"))?);
|
||||
}
|
||||
|
||||
let version = path_segments;
|
||||
let file_path = path_segments[1..].join("/");
|
||||
Ok(tauri::http::Response::builder()::new().status(404).body(Vec::new())?)
|
||||
} */
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://localhost:3003",
|
||||
"beforeBuildCommand": "pnpm generate",
|
||||
"frontendDist": "../dist"
|
||||
"frontendDist": "../.output/public"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
@ -18,7 +18,14 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
|
||||
"csp": {
|
||||
"default-src": ["'self'", "haex-extensions"],
|
||||
"script-src": ["'self'", "haex-extensions"],
|
||||
"style-src": ["'self'", "haex-extensions"],
|
||||
"connect-src": ["'self'", "haex-extensions"],
|
||||
"img-src": ["'self'", "haex-extensions", "data:"],
|
||||
"font-src": ["'self'", "haex-extensions", "data:"]
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["$RESOURCE/extensions/**"]
|
||||
|
||||
9
src/assets/css/main.css
Normal file
9
src/assets/css/main.css
Normal file
@ -0,0 +1,9 @@
|
||||
@import "tailwindcss";
|
||||
@import "flyonui/variants.css";
|
||||
|
||||
@plugin "@iconify/tailwind4";
|
||||
@plugin "flyonui" {
|
||||
themes: all;
|
||||
}
|
||||
|
||||
@source "../../node_modules/flyonui/flyonui.js";
|
||||
53
src/components/haex/extension/card.vue
Normal file
53
src/components/haex/extension/card.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<slot name="image" />
|
||||
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||
const emit = defineEmits(["close", "submit", "remove"]);
|
||||
|
||||
defineProps<IHaexHubExtension>();
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
watchEffect(async () => {
|
||||
if (escape.value) {
|
||||
await nextTick();
|
||||
emit("close");
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
if (enter.value) {
|
||||
await nextTick();
|
||||
emit("submit");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
54
src/components/haex/extension/dialog/remove.vue
Normal file
54
src/components/haex/extension/dialog/remove.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #name>
|
||||
<span class="font-bold text-primary">{{ extension?.name }}</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||
|
||||
const emit = defineEmits(["confirm"]);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineProps<{ extension?: IHaexHubExtension }>();
|
||||
|
||||
const open = defineModel<boolean>("open");
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung löschen",
|
||||
"question": "Soll {name} wirklich gelöscht werden?",
|
||||
"abort": "Abbrechen",
|
||||
"remove": "Löschen"
|
||||
},
|
||||
"en": {
|
||||
"title": "Remove Extension",
|
||||
"question": "Soll {name} wirklich gelöscht werden?",
|
||||
"abort": "Abort",
|
||||
"remove": "Remove"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@ -54,10 +54,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
defineProps<{ manifest?: IHaexHubExtensionManifest }>();
|
||||
defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||
|
||||
const emit = defineEmits(["deny", "confirm"]);
|
||||
|
||||
|
||||
@ -1,25 +1,14 @@
|
||||
<template>
|
||||
<button
|
||||
class="btn join-item"
|
||||
:class="{
|
||||
'btn-sm':
|
||||
currentScreenSize === 'sm' ||
|
||||
currentScreenSize === '' ||
|
||||
currentScreenSize === 'xs',
|
||||
}"
|
||||
:type
|
||||
>
|
||||
<button class="btn join-item" :type>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
|
||||
defineProps({
|
||||
type: {
|
||||
type: String as PropType<'reset' | 'submit' | 'button'>,
|
||||
default: 'button',
|
||||
type: String as PropType<"reset" | "submit" | "button">,
|
||||
default: "button",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -50,27 +50,10 @@ export interface IDom {
|
||||
const id = useId();
|
||||
|
||||
defineProps({
|
||||
trigger: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
|
||||
description: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
@ -84,8 +67,8 @@ watch(open, async () => {
|
||||
if (open.value) {
|
||||
await modal.value?.open();
|
||||
} else {
|
||||
const res = await modal.value?.close(true);
|
||||
console.log("close dialog", res);
|
||||
await modal.value?.close(true);
|
||||
console.log("close dialog");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span>
|
||||
<fieldset class="join w-full">
|
||||
<!-- <fieldset class="join w-full">
|
||||
<slot name="prepend" />
|
||||
|
||||
<span class="input-group join-item">
|
||||
@ -66,30 +66,45 @@
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</UiButton>
|
||||
<!-- <button
|
||||
v-if="withCopyButton"
|
||||
class="btn btn-outline btn-accent join-item h-auto"
|
||||
:class="{
|
||||
'btn-sm':
|
||||
currentScreenSize === 'sm' ||
|
||||
currentScreenSize === '' ||
|
||||
currentScreenSize === 'xs',
|
||||
}"
|
||||
|
||||
</fieldset> -->
|
||||
<fieldset class="join w-full p-1">
|
||||
<slot name="prepend" />
|
||||
|
||||
<div class="input join-item">
|
||||
<Icon :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"
|
||||
/>
|
||||
<label class="input-floating-label" :for="id">{{ label }}</label>
|
||||
</div>
|
||||
|
||||
<Icon :name="appendIcon" class="my-auto shrink-0" />
|
||||
</div>
|
||||
|
||||
<slot name="append" class="h-auto" />
|
||||
|
||||
<UiButton
|
||||
class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)"
|
||||
type="button"
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</button> -->
|
||||
</UiButton>
|
||||
</fieldset>
|
||||
|
||||
<span
|
||||
class="flex flex-col px-2 pt-0.5"
|
||||
v-show="errors"
|
||||
>
|
||||
<span
|
||||
v-for="error in errors"
|
||||
class="label-text-alt text-error"
|
||||
>
|
||||
<span class="flex flex-col px-2 pt-0.5" v-show="errors">
|
||||
<span v-for="error in errors" class="label-text-alt text-error">
|
||||
{{ error }}
|
||||
</span>
|
||||
</span>
|
||||
@ -97,9 +112,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ZodSchema } from 'zod';
|
||||
import { type ZodSchema } from "zod";
|
||||
|
||||
const inputRef = useTemplateRef('inputRef');
|
||||
const inputRef = useTemplateRef("inputRef");
|
||||
defineExpose({ inputRef });
|
||||
|
||||
defineOptions({
|
||||
@ -109,45 +124,45 @@ defineOptions({
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
| "button"
|
||||
| "checkbox"
|
||||
| "color"
|
||||
| "date"
|
||||
| "datetime-local"
|
||||
| "email"
|
||||
| "file"
|
||||
| "hidden"
|
||||
| "image"
|
||||
| "month"
|
||||
| "number"
|
||||
| "password"
|
||||
| "radio"
|
||||
| "range"
|
||||
| "reset"
|
||||
| "search"
|
||||
| "submit"
|
||||
| "tel"
|
||||
| "text"
|
||||
| "time"
|
||||
| "url"
|
||||
| "week"
|
||||
>,
|
||||
default: 'text',
|
||||
default: "text",
|
||||
},
|
||||
label: String,
|
||||
name: String,
|
||||
prependIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
prependLabel: String,
|
||||
appendIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
appendLabel: String,
|
||||
rules: Object as PropType<ZodSchema>,
|
||||
@ -158,7 +173,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const input = defineModel<string | number | undefined | null>({
|
||||
default: '',
|
||||
default: "",
|
||||
required: true,
|
||||
});
|
||||
|
||||
@ -167,7 +182,7 @@ onMounted(() => {
|
||||
if (props.autofocus && inputRef.value) inputRef.value.focus();
|
||||
});
|
||||
|
||||
const errors = defineModel<string[] | undefined>('errors');
|
||||
const errors = defineModel<string[] | undefined>("errors");
|
||||
|
||||
const id = useId();
|
||||
|
||||
@ -180,7 +195,7 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['error']);
|
||||
const emit = defineEmits(["error"]);
|
||||
|
||||
const checkInput = () => {
|
||||
if (props.rules) {
|
||||
@ -188,7 +203,7 @@ const checkInput = () => {
|
||||
//console.log('check result', result.error, props.rules);
|
||||
if (!result.success) {
|
||||
errors.value = result.error.errors.map((error) => error.message);
|
||||
emit('error', errors.value);
|
||||
emit("error", errors.value);
|
||||
} else {
|
||||
errors.value = [];
|
||||
}
|
||||
|
||||
@ -9,10 +9,7 @@
|
||||
v-model="value"
|
||||
>
|
||||
<template #append>
|
||||
<UiButton
|
||||
class="btn-outline btn-accent h-auto"
|
||||
@click="tooglePasswordType"
|
||||
>
|
||||
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType">
|
||||
<Icon :name="type === 'password' ? 'mdi:eye' : 'mdi:eye-off'" />
|
||||
</UiButton>
|
||||
</template>
|
||||
@ -20,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZodSchema } from 'zod';
|
||||
import type { ZodSchema } from "zod";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
@ -35,10 +32,10 @@ defineProps({
|
||||
autofocus: Boolean,
|
||||
});
|
||||
|
||||
const type = ref<'password' | 'text'>('password');
|
||||
const type = ref<"password" | "text">("password");
|
||||
|
||||
const tooglePasswordType = () => {
|
||||
type.value = type.value === 'password' ? 'text' : 'password';
|
||||
type.value = type.value === "password" ? "text" : "password";
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<li
|
||||
@click="triggerNavigate"
|
||||
class="hover:text-primary rounded"
|
||||
:class="{ ['bg-base-300']: isActive }"
|
||||
>
|
||||
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
|
||||
<NuxtLinkLocale
|
||||
:to="{ name: 'haexExtension', params: { extensionId: props.id } }"
|
||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||
ref="link"
|
||||
>
|
||||
<div v-html="icon" class="shrink-0 size-6" />
|
||||
<!-- <Icon mode="svg" :name="icon" class="shrink-0 size-6" /> -->
|
||||
</NuxtLinkLocale>
|
||||
</UiTooltip>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ISidebarItem } from "#imports";
|
||||
|
||||
const props = defineProps<ISidebarItem>();
|
||||
console.log("image", props.icon);
|
||||
const router = useRouter();
|
||||
|
||||
const isActive = computed(() => {
|
||||
if (props.to?.name === "haexExtension") {
|
||||
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
||||
} else {
|
||||
return props.to?.name === router.currentRoute.value.meta.name;
|
||||
}
|
||||
});
|
||||
|
||||
const link = useTemplateRef("link");
|
||||
|
||||
const triggerNavigate = () => link.value?.$el.click();
|
||||
|
||||
/* computed(() => {
|
||||
const found = useRouter()
|
||||
.getRoutes()
|
||||
.find((route) => route.name === useLocaleRoute()(props.to)?.name);
|
||||
console.log('found route', found, useRoute());
|
||||
return (
|
||||
found?.name === useRoute().name ||
|
||||
found?.children.some((child) => child.name === useRoute().name)
|
||||
);
|
||||
}); */
|
||||
</script>
|
||||
@ -8,9 +8,10 @@
|
||||
<NuxtLinkLocale
|
||||
:to
|
||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||
ref="link"
|
||||
ref="linkRef"
|
||||
>
|
||||
<Icon :name="icon" class="shrink-0 size-6" />
|
||||
<div v-if="iconType === 'svg'" v-html="icon" class="shrink-0 size-5" />
|
||||
<Icon v-else :name="icon" size="1.5em" />
|
||||
</NuxtLinkLocale>
|
||||
</UiTooltip>
|
||||
</li>
|
||||
@ -23,6 +24,7 @@ const props = defineProps<ISidebarItem>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
console.log("to", props.to);
|
||||
const isActive = computed(() => {
|
||||
if (props.to?.name === "haexExtension") {
|
||||
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
||||
@ -31,9 +33,9 @@ const isActive = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const link = useTemplateRef("link");
|
||||
const linkRef = useTemplateRef("linkRef");
|
||||
|
||||
const triggerNavigate = () => link.value?.$el.click();
|
||||
const triggerNavigate = () => linkRef.value?.$el.click();
|
||||
|
||||
/* computed(() => {
|
||||
const found = useRouter()
|
||||
|
||||
@ -1,52 +1,51 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<template #trigger="{ id }">
|
||||
<button
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
||||
@click="open = true"
|
||||
>
|
||||
<Icon name="mdi:plus" />
|
||||
{{ t("database.create") }}
|
||||
</button>
|
||||
</template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<template #trigger="{ id }">
|
||||
<button
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
||||
@click="open = true"
|
||||
>
|
||||
<Icon name="mdi:plus" />
|
||||
{{ t("database.create") }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<form class="flex flex-col gap-4" @submit="onCreateAsync">
|
||||
<!-- @keyup.enter="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">
|
||||
<!-- @keyup.enter="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"
|
||||
/>
|
||||
|
||||
<UiInputPassword
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
prepend-icon="mdi:key-outline"
|
||||
v-model="database.password"
|
||||
/>
|
||||
</form>
|
||||
<UiInputPassword
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
prepend-icon="mdi:key-outline"
|
||||
v-model="database.password"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton class="btn-error" @click="onClose">
|
||||
{{ t("abort") }}
|
||||
</UiButton>
|
||||
<template #buttons>
|
||||
<UiButton class="btn-error" @click="onClose">
|
||||
{{ t("abort") }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton class="btn-primary" @click="onCreateAsync">
|
||||
{{ t("create") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
<UiButton class="btn-primary" @click="onCreateAsync">
|
||||
{{ t("create") }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
import { useVaultStore } from "~/stores/vault";
|
||||
import { vaultDatabaseSchema } from "./schema";
|
||||
import Database from "@tauri-apps/plugin-sql";
|
||||
|
||||
const check = ref(false);
|
||||
const open = ref();
|
||||
@ -54,22 +53,22 @@ const open = ref();
|
||||
const { t } = useI18n();
|
||||
|
||||
const database = reactive<{
|
||||
name: string;
|
||||
password: string;
|
||||
path: string | null;
|
||||
type: "password" | "text";
|
||||
name: string;
|
||||
password: string;
|
||||
path: string | null;
|
||||
type: "password" | "text";
|
||||
}>({
|
||||
name: "",
|
||||
password: "",
|
||||
path: "",
|
||||
type: "password",
|
||||
name: "",
|
||||
password: "",
|
||||
path: "",
|
||||
type: "password",
|
||||
});
|
||||
|
||||
const initDatabase = () => {
|
||||
database.name = t("database.name");
|
||||
database.password = "";
|
||||
database.path = "";
|
||||
database.type = "password";
|
||||
database.name = t("database.name");
|
||||
database.password = "";
|
||||
database.path = "";
|
||||
database.type = "password";
|
||||
};
|
||||
|
||||
initDatabase();
|
||||
@ -78,69 +77,71 @@ const { add } = useSnackbar();
|
||||
const { createAsync } = useVaultStore();
|
||||
|
||||
const onCreateAsync = async () => {
|
||||
check.value = true;
|
||||
check.value = true;
|
||||
|
||||
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
||||
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
||||
|
||||
console.log("checks", database.name, nameCheck, database.password, passwordCheck);
|
||||
if (!nameCheck.success || !passwordCheck.success) return;
|
||||
console.log("checks", database.name, nameCheck, database.password, passwordCheck);
|
||||
if (!nameCheck.success || !passwordCheck.success) return;
|
||||
|
||||
open.value = false;
|
||||
try {
|
||||
database.path = await save({
|
||||
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`,
|
||||
});
|
||||
open.value = false;
|
||||
try {
|
||||
database.path = await save({
|
||||
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`,
|
||||
});
|
||||
|
||||
console.log("data", database);
|
||||
console.log("data", database);
|
||||
|
||||
if (database.path && database.password) {
|
||||
const vaultId = await createAsync({
|
||||
path: database.path,
|
||||
password: database.password,
|
||||
});
|
||||
if (database.path && database.password) {
|
||||
const vaultId = await createAsync({
|
||||
path: database.path,
|
||||
password: database.password,
|
||||
});
|
||||
|
||||
console.log("vaultId", vaultId);
|
||||
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
console.log("vaultId", vaultId);
|
||||
if (vaultId) {
|
||||
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
open.value = false;
|
||||
initDatabase();
|
||||
open.value = false;
|
||||
initDatabase();
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"database": {
|
||||
"label": "Datenbankname",
|
||||
"placeholder": "Passwörter",
|
||||
"create": "Neue Vault anlegen",
|
||||
"name": "Passwörter"
|
||||
},
|
||||
"title": "Neue Datenbank anlegen",
|
||||
"create": "Erstellen",
|
||||
"abort": "Abbrechen",
|
||||
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
||||
"de": {
|
||||
"database": {
|
||||
"label": "Datenbankname",
|
||||
"placeholder": "Passwörter",
|
||||
"create": "Neue Vault anlegen",
|
||||
"name": "Passwörter"
|
||||
},
|
||||
"title": "Neue Datenbank anlegen",
|
||||
"create": "Erstellen",
|
||||
"abort": "Abbrechen",
|
||||
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
||||
},
|
||||
|
||||
"en": {
|
||||
"database": {
|
||||
"label": "Databasename",
|
||||
"placeholder": "Databasename",
|
||||
"create": "Create new Vault",
|
||||
"name": "Passwords"
|
||||
},
|
||||
"title": "Create New Database",
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"description": "Haex Vault for your most secret secrets"
|
||||
}
|
||||
"en": {
|
||||
"database": {
|
||||
"label": "Databasename",
|
||||
"placeholder": "Databasename",
|
||||
"create": "Create new Vault",
|
||||
"name": "Passwords"
|
||||
},
|
||||
"title": "Create New Database",
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"description": "Haex Vault for your most secret secrets"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -102,6 +102,7 @@ const onLoadDatabase = async () => {
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
const { currentVault, currentVaultId } = storeToRefs(useVaultStore());
|
||||
const onOpenDatabase = async () => {
|
||||
try {
|
||||
check.value = true;
|
||||
@ -110,7 +111,10 @@ const onOpenDatabase = async () => {
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
||||
|
||||
if (!pathCheck.success || !passwordCheck.success || !path) {
|
||||
add({ type: "error", text: "params falsch" });
|
||||
add({
|
||||
type: "error",
|
||||
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -131,15 +135,14 @@ const onOpenDatabase = async () => {
|
||||
|
||||
onClose();
|
||||
|
||||
currentVaultId.value = vaultId;
|
||||
console.log("vault before navigation", currentVault.value, currentVaultId.value, vaultId);
|
||||
await navigateTo(
|
||||
localePath({
|
||||
name: "vaultOverview",
|
||||
params: {
|
||||
vaultId,
|
||||
},
|
||||
query: {
|
||||
showSidebar: "true",
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@ -3,14 +3,19 @@
|
||||
<slot name="image" />
|
||||
|
||||
<div class="card-header">
|
||||
<h5 class="card-title" v-if="$slots.title">
|
||||
<slot name="title" />
|
||||
</h5>
|
||||
<div v-if="$slots.title || title">
|
||||
<Icon :name="icon" />
|
||||
<h5 v-if="title" class="card-title mb-0">
|
||||
{{ title }}
|
||||
</h5>
|
||||
<slot v-else name="title" />
|
||||
</div>
|
||||
<div class="text-base-content/50">Your journey starts here</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
|
||||
aaaaaaaaa
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
@ -36,6 +41,8 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(["close", "submit"]);
|
||||
|
||||
defineProps<{ title?: string; icon?: string }>();
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
watchEffect(async () => {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { H3Error } from "h3";
|
||||
|
||||
export const bytesToBase64DataUrlAsync = async (
|
||||
bytes: Uint8Array,
|
||||
type = "application/octet-stream"
|
||||
@ -82,9 +80,36 @@ export const isRouteActive = (to: RouteLocationRawI18n, exact: boolean = false)
|
||||
return exact
|
||||
? found?.name === useRouter().currentRoute.value.name
|
||||
: found?.name === useRouter().currentRoute.value.name ||
|
||||
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
||||
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
||||
});
|
||||
|
||||
export const isKey = <T extends object>(x: T, k: PropertyKey): k is keyof T => {
|
||||
return k in x;
|
||||
};
|
||||
|
||||
export const filterAsync = async <T>(
|
||||
arr: T[],
|
||||
predicate: (value: T, index: number, array: T[]) => Promise<boolean>
|
||||
) => {
|
||||
// 1. Mappe jedes Element auf ein Promise, das zu true/false auflöst
|
||||
const results = await Promise.all(arr.map(predicate));
|
||||
|
||||
// 2. Filtere das ursprüngliche Array basierend auf den Ergebnissen
|
||||
return arr.filter((_value, index) => results[index]);
|
||||
};
|
||||
|
||||
export const stringToHex = (str: string) =>
|
||||
str
|
||||
.split("")
|
||||
.map((char) => char.charCodeAt(0).toString(16).padStart(2, "0"))
|
||||
.join(""); // Join array into a single string
|
||||
|
||||
export const hexToString = (hex: string) => {
|
||||
if (!hex) return "";
|
||||
const parsedValue = hex
|
||||
.match(/.{1,2}/g) // Split hex into pairs
|
||||
?.map((byte) => String.fromCharCode(parseInt(byte, 16))) // Convert hex to char
|
||||
.join(""); // Join array into a single string
|
||||
|
||||
return parsedValue ? parsedValue : "";
|
||||
};
|
||||
|
||||
@ -3,17 +3,22 @@
|
||||
<nav
|
||||
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
>
|
||||
<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="mage:dash-menu" size="28" />
|
||||
</button>
|
||||
<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' : 'tabler:layout-sidebar-filled'"
|
||||
size="28"
|
||||
/>
|
||||
</button>
|
||||
</UiTooltip>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
<NuxtLinkLocale
|
||||
@ -156,35 +161,28 @@
|
||||
<div class="flex h-full">
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="sm:shadow-none drawer max-w-14 transition-all"
|
||||
:class="[!isVisible ? 'w-0' : 'w-14']"
|
||||
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 px-0">
|
||||
<ul class="menu p-0">
|
||||
<div class="drawer-body h-full">
|
||||
<ul class="menu p-0 h-full rounded-none">
|
||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<UiSidebarLinkExtension
|
||||
v-bind="item"
|
||||
v-for="item in availableExtensions"
|
||||
<UiSidebarLink
|
||||
v-for="item in extensionLinks"
|
||||
:key="item.id"
|
||||
v-bind="item"
|
||||
icon-type="svg"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="overflow-hidden transition-all relative w-full">
|
||||
<div
|
||||
class="h-full overflow-scroll transition-all pl-0"
|
||||
:class="[isVisible ? 'sm:pl-14 ' : ' pl-0']"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<main class="w-full">
|
||||
<NuxtPage :transition="{ name: 'fade' }" />
|
||||
</main>
|
||||
</div>
|
||||
<!-- <main class="sm:pl-14">
|
||||
<NuxtPage />
|
||||
</main> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -194,18 +192,19 @@ import { NuxtLinkLocale } from "#components";
|
||||
const { t } = useI18n();
|
||||
const { menu, isVisible } = storeToRefs(useSidebarStore());
|
||||
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
|
||||
onClickOutside(sidebarToogleRef, () => {
|
||||
|
||||
/* onClickOutside(sidebarToogleRef, () => {
|
||||
if (currentScreenSize.value === "xs") {
|
||||
isVisible.value = false;
|
||||
}
|
||||
});
|
||||
}); */
|
||||
const { notifications } = storeToRefs(useNotificationStore());
|
||||
|
||||
const { isActive } = useExtensionsStore();
|
||||
const { closeAsync } = useVaultStore();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
const onExtensionSelectAsync = async (id: string) => {};
|
||||
const { availableExtensions } = storeToRefs(useExtensionsStore());
|
||||
const { extensionLinks } = storeToRefs(useExtensionsStore());
|
||||
const toogleSidebar = () => {
|
||||
isVisible.value = !isVisible.value;
|
||||
};
|
||||
@ -223,10 +222,16 @@ de:
|
||||
view_all: Alle ansehen
|
||||
vault:
|
||||
close: Vault schließen
|
||||
sidebar:
|
||||
close: Sidebar schließen
|
||||
show: Sidebar anzeigen
|
||||
en:
|
||||
notifications:
|
||||
label: Notifications
|
||||
view_all: View all
|
||||
vault:
|
||||
close: Close vault
|
||||
sidebar:
|
||||
close: close sidebar
|
||||
show: show sidebar
|
||||
</i18n>
|
||||
|
||||
9
src/middleware/database.ts
Normal file
9
src/middleware/database.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
const { openVaults } = storeToRefs(useVaultStore());
|
||||
|
||||
const toVaultId = getSingleRouteParam(to.params.vaultId);
|
||||
|
||||
if (!openVaults.value?.[toVaultId]) {
|
||||
return await navigateTo(useLocalePath()({ name: "vaultOpen" }));
|
||||
}
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="items-center justify-center min-h-full flex w-full">
|
||||
<div class="flex flex-col justify-center items-center gap-4 max-w-3xl">
|
||||
<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" />
|
||||
|
||||
<span class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center">
|
||||
@ -10,7 +10,7 @@
|
||||
<UiTextGradient>Haex Hub</UiTextGradient>
|
||||
</span>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4 w-full">
|
||||
<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" />
|
||||
@ -52,7 +52,7 @@
|
||||
:key="vault.path"
|
||||
>
|
||||
<button
|
||||
class="link link-accent flex items-center no-underline justify-between text-nowrap text-xs md:text-base shrink w-full py-2 px-4"
|
||||
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;
|
||||
|
||||
@ -6,4 +6,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: "database",
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
browser {{ useRouter().currentRoute.value.meta.name }}
|
||||
<HaexBrowser
|
||||
:tabs="tabs"
|
||||
:activeTabId="activeTabId"
|
||||
@createTab="createNewTab"
|
||||
@closeTab="closeTab"
|
||||
@navigate="navigateToUrl"
|
||||
@goBack="goBack"
|
||||
@goForward="goForward"
|
||||
/>
|
||||
</div>
|
||||
<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">
|
||||
@ -20,16 +20,16 @@ import { Window, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { Webview } from "@tauri-apps/api/webview";
|
||||
|
||||
definePageMeta({
|
||||
name: "haexBrowser",
|
||||
name: "haexBrowser",
|
||||
});
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
isLoading: boolean;
|
||||
isActive: boolean;
|
||||
window_label: string;
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
isLoading: boolean;
|
||||
isActive: boolean;
|
||||
window_label: string;
|
||||
}
|
||||
|
||||
const tabs = ref<Tab[]>([]);
|
||||
@ -39,43 +39,43 @@ let unlistenTabCreated: UnlistenFn | null = null;
|
||||
let unlistenTabClosed: UnlistenFn | null = null;
|
||||
|
||||
onMounted(async () => {
|
||||
// Erstelle einen ersten Tab beim Start
|
||||
//createNewTab("https://www.google.com");
|
||||
// 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;
|
||||
// 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,
|
||||
}));
|
||||
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);
|
||||
}
|
||||
if (!tabs.value.some((tab) => tab.id === newTab.id)) {
|
||||
tabs.value.push(newTab);
|
||||
}
|
||||
|
||||
activeTabId.value = newTab.id;
|
||||
});
|
||||
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);
|
||||
});
|
||||
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();
|
||||
if (unlistenTabCreated) unlistenTabCreated();
|
||||
if (unlistenTabClosed) unlistenTabClosed();
|
||||
});
|
||||
|
||||
const createNewTab = async (url: string = "about:blank") => {
|
||||
try {
|
||||
/* const appWindow = new Window('uniqueLabel111', {
|
||||
try {
|
||||
/* const appWindow = new Window('uniqueLabel111', {
|
||||
fullscreen: true,
|
||||
});
|
||||
*/
|
||||
/* const appWindow = getCurrentWindow();
|
||||
/* const appWindow = getCurrentWindow();
|
||||
|
||||
const webview = new Webview(appWindow, 'theUniqueLabel', {
|
||||
url: 'https://github.com/tauri-apps/tauri',
|
||||
@ -85,43 +85,43 @@ const createNewTab = async (url: string = "about:blank") => {
|
||||
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);
|
||||
}
|
||||
//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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
try {
|
||||
//await invoke('go_forward', { tabId });
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Vorwärtsgehen:", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
{{ iframeSrc }}
|
||||
<div class="w-full h-full">
|
||||
<div class="w-full h-full overflow-scroll bg-red-300">
|
||||
<div>
|
||||
{{ iframeSrc }}
|
||||
</div>
|
||||
<iframe
|
||||
v-if="iframeSrc"
|
||||
class="w-full h-full"
|
||||
@ -10,7 +12,7 @@
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
>
|
||||
</iframe>
|
||||
<p v-else>{{ t("loading") }}</p>
|
||||
<!-- <p v-else>{{ t("loading") }}</p> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -28,11 +30,11 @@ const extensionStore = useExtensionsStore();
|
||||
watch(iframeSrc, () => console.log("iframeSrc", iframeSrc.value), { immediate: true });
|
||||
|
||||
onMounted(async () => {
|
||||
const minfest = await extensionStore.readManifestFileAsync(
|
||||
/* const minfest = await extensionStore.readManifestFileAsync(
|
||||
currentExtension.value!.id,
|
||||
currentExtension.value!.version
|
||||
);
|
||||
console.log("manifest", minfest, extensionStore.extensionEntry);
|
||||
console.log("manifest", minfest, extensionStore.extensionEntry); */
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,15 +1,38 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h1>{{ t("title") }}</h1>
|
||||
<UiButton @click="loadExtensionManifestAsync">
|
||||
{{ t("extension.add") }}
|
||||
<div class="flex flex-col p-1 relative">
|
||||
<UiButton
|
||||
class="fixed top-20 right-4 btn-square btn-primary"
|
||||
@click="loadExtensionManifestAsync"
|
||||
>
|
||||
<Icon name="mdi:plus" size="1.5em" />
|
||||
</UiButton>
|
||||
<h1>{{ t("title") }}</h1>
|
||||
|
||||
<div class="flex">
|
||||
<HaexExtensionCard
|
||||
v-for="extension in extensionStore.availableExtensions"
|
||||
v-bind="extension"
|
||||
@remove="onShowRemoveDialog(extension)"
|
||||
>
|
||||
</HaexExtensionCard>
|
||||
</div>
|
||||
<!-- <UiButton @click="loadExtensionManifestAsync">
|
||||
{{ t("extension.add") }}
|
||||
</UiButton> -->
|
||||
|
||||
<HaexExtensionManifestConfirm
|
||||
:manifest="extension.manifest!"
|
||||
:manifest="extension.manifest"
|
||||
v-model:open="showConfirmation"
|
||||
@confirm="addExtensionAsync"
|
||||
/>
|
||||
|
||||
{{ showRemoveDialog }}
|
||||
<HaexExtensionDialogRemove
|
||||
v-model:open="showRemoveDialog"
|
||||
:extension="extensionToBeRemoved"
|
||||
@confirm="removeExtensionAsync"
|
||||
>
|
||||
</HaexExtensionDialogRemove>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -17,6 +40,7 @@
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import type { IHaexHubExtension, IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
|
||||
definePageMeta({
|
||||
name: "extensionOverview",
|
||||
@ -35,6 +59,8 @@ const extension = reactive<{
|
||||
path: "",
|
||||
});
|
||||
|
||||
onMounted(() => console.log("extension overview"));
|
||||
|
||||
const loadExtensionManifestAsync = async () => {
|
||||
try {
|
||||
extension.path = await open({ directory: true, recursive: true });
|
||||
@ -50,7 +76,8 @@ const loadExtensionManifestAsync = async () => {
|
||||
extension.manifest = manifestFile;
|
||||
showConfirmation.value = true;
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
console.error("Fehler loadExtensionManifestAsync:", error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
|
||||
@ -67,26 +94,64 @@ const addExtensionAsync = async () => {
|
||||
text: t("extension.success.text"),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
console.error("Fehler addExtensionAsync:", error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
|
||||
const showRemoveDialog = ref(false);
|
||||
const extensionToBeRemoved = ref<IHaexHubExtension>();
|
||||
|
||||
const onShowRemoveDialog = (extension: IHaexHubExtension) => {
|
||||
extensionToBeRemoved.value = extension;
|
||||
showRemoveDialog.value = true;
|
||||
};
|
||||
|
||||
const removeExtensionAsync = async () => {
|
||||
if (!extensionToBeRemoved.value?.id || !extensionToBeRemoved.value?.version) {
|
||||
add({ type: "error", text: "Erweiterung kann nicht gelöscht werden" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await extensionStore.removeExtensionAsync(
|
||||
extensionToBeRemoved.value.id,
|
||||
extensionToBeRemoved.value.version
|
||||
);
|
||||
await extensionStore.loadExtensionsAsync();
|
||||
add({
|
||||
type: "success",
|
||||
title: t("extension.remove.success.title", {
|
||||
extensionName: extensionToBeRemoved.value.name,
|
||||
}),
|
||||
text: t("extension.remove.success.text", { extensionName: extensionToBeRemoved.value.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="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung installieren",
|
||||
"extension": {
|
||||
"add": "Erweiterung hinzufügen",
|
||||
"success": {
|
||||
"title": "{extension} hinzugefügt",
|
||||
"text": "Die Erweiterung wurde erfolgreich hinzugefügt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"title": "Install extension"
|
||||
}
|
||||
}
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: "Erweiterung installieren"
|
||||
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}"
|
||||
|
||||
add: "Erweiterung hinzufügen"
|
||||
success:
|
||||
title: "{extension} hinzugefügt"
|
||||
text: "Die Erweiterung wurde erfolgreich hinzugefügt"
|
||||
en:
|
||||
title: "Install extension"
|
||||
</i18n>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="bg-blue-200 h-full">aaaaa</div>
|
||||
<div class="h-full"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@ -1,47 +1,32 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import { join, resourceDir } from "@tauri-apps/api/path";
|
||||
import { readTextFile, readDir } from "@tauri-apps/plugin-fs";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
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";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const manifestFileName = "manifest.json";
|
||||
const logoFileName = "logo.svg";
|
||||
|
||||
export interface IHaexHubExtensionLink {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
version: string;
|
||||
manifest?: IHaexHubExtensionManifest;
|
||||
}
|
||||
|
||||
export interface IHaexHubExtensionManifest {
|
||||
name: string;
|
||||
id: string;
|
||||
entry: string;
|
||||
author: string;
|
||||
url: string;
|
||||
version: string;
|
||||
icon: string;
|
||||
permissions: {
|
||||
database?: {
|
||||
read?: string[];
|
||||
write?: string[];
|
||||
create?: string[];
|
||||
};
|
||||
http?: string[];
|
||||
filesystem?: {
|
||||
read?: string[];
|
||||
write?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
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 ?? "",
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
tooltip: extension.name ?? "",
|
||||
to: { name: "haexExtension", params: { extensionId: extension.id } },
|
||||
}))
|
||||
);
|
||||
|
||||
const currentRoute = useRouter().currentRoute;
|
||||
|
||||
const isActive = (id: string) =>
|
||||
@ -82,6 +67,16 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
||||
const errors = [];
|
||||
|
||||
@ -130,8 +125,10 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const readManifestFileAsync = async (extensionId: string, version: string | number) => {
|
||||
const readManifestFileAsync = async (extensionId: string, version: string) => {
|
||||
try {
|
||||
if (!(await isExtensionInstalledAsync({ id: extensionId, version }))) return null;
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
||||
const manifestPath = await join(extensionPath, manifestFileName);
|
||||
const manifest = (await JSON.parse(
|
||||
@ -139,7 +136,7 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
)) as IHaexHubExtensionManifest;
|
||||
|
||||
/*
|
||||
TODO implement await checkManifets(manifest);
|
||||
TODO implement check, that manifest has valid data
|
||||
*/
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
@ -287,77 +284,83 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
|
||||
const extensionEntry = computedAsync(
|
||||
async () => {
|
||||
console.log("extensionEntry start", currentExtension.value);
|
||||
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;
|
||||
}
|
||||
//return "wadahadedzdz";
|
||||
try {
|
||||
console.log("extensionEntry start", currentExtension.value);
|
||||
const regex = /((href|src)=["'])([^"']+)(["'])/g;
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
currentExtension.value?.id,
|
||||
currentExtension.value?.version
|
||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
|
||||
console.log("extensionEntry extensionPath", extensionPath);
|
||||
const manifest = await readManifestFileAsync(
|
||||
currentExtension.value.id,
|
||||
currentExtension.value.version
|
||||
);
|
||||
|
||||
if (!manifest) return "no manifest readable";
|
||||
|
||||
const entryPath = await join(extensionPath, manifest.entry);
|
||||
|
||||
return `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 });
|
||||
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
||||
console.log("extension id or entry missing", currentExtension.value);
|
||||
return "no mani: " + currentExtension.value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const { match, resource, prefix, suffix } of replacements) {
|
||||
const fileContent = await readTextFile(await join(extensionPath, resource));
|
||||
const blob = new Blob([fileContent], { type: getMimeType(resource) });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
console.log("blob", resource, blobUrl);
|
||||
entryHtml = entryHtml.replace(match, `${prefix}${blobUrl}${suffix}`);
|
||||
}
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
currentExtension.value?.id,
|
||||
currentExtension.value?.version
|
||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
console.log("extensionEntry extensionPath", extensionPath);
|
||||
const manifest = await readManifestFileAsync(
|
||||
currentExtension.value.id,
|
||||
currentExtension.value.version
|
||||
);
|
||||
|
||||
const blob = new Blob([entryHtml], { type: "text/html" });
|
||||
const iframeSrc = URL.createObjectURL(blob);
|
||||
if (!manifest) return "no manifest readable";
|
||||
|
||||
console.log("iframeSrc", iframeSrc);
|
||||
const entryPath = await join(extensionPath, manifest.entry);
|
||||
|
||||
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
||||
const hexName = stringToHex(
|
||||
JSON.stringify({
|
||||
id: currentExtension.value.id,
|
||||
version: currentExtension.value.version,
|
||||
})
|
||||
);
|
||||
|
||||
return `haex-extension://${hexName}/index.html`;
|
||||
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(
|
||||
//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', {
|
||||
// Modul-Datei laden
|
||||
//const modulePathFull = await join(basePath, manifest.main);
|
||||
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
||||
manifestPath,
|
||||
}); */
|
||||
/* const iframe = document.createElement('iframe');
|
||||
/* 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 = {
|
||||
/* const addonApi = {
|
||||
db_execute: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_execute', {
|
||||
addonId: manifest.name,
|
||||
@ -373,24 +376,27 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
});
|
||||
},
|
||||
}; */
|
||||
/* iframe.onload = () => {
|
||||
/* 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.`); */
|
||||
);
|
||||
};
|
||||
|
||||
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.`); */
|
||||
} catch (error) {
|
||||
console.error("ERROR extensionEntry", error);
|
||||
}
|
||||
},
|
||||
null,
|
||||
{ lazy: true }
|
||||
@ -399,28 +405,36 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
const loadExtensionsAsync = async () => {
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
|
||||
/* const query = db
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
//.where(sql`${haexExtensions.enabled} = "1"`);
|
||||
.where(eq(haexExtensions.enabled, true)); */
|
||||
const extensions = await currentVault.value?.drizzle
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
.where(eq(haexExtensions.enabled, true));
|
||||
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 manifest = readTextFile(manifestFileName)
|
||||
//const { sql, params } = query.toSQL();
|
||||
//const extensions = await invoke<any>("sql_select", { sql, params });
|
||||
console.log("loadExtensionsAsync ", extensions);
|
||||
availableExtensions.value =
|
||||
extensions?.map((extension) => ({
|
||||
extensions.map((extension) => ({
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
icon: extension.icon ?? "",
|
||||
tooltip: extension.name ?? "",
|
||||
author: extension.author ?? "",
|
||||
version: extension.version ?? "",
|
||||
enabled: extension.enabled ? true : false,
|
||||
installed: installedExtensions.includes(extension),
|
||||
})) ?? [];
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
throw new Error(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
@ -428,10 +442,13 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
checkManifest,
|
||||
currentExtension,
|
||||
extensionEntry,
|
||||
extensionLinks,
|
||||
installAsync,
|
||||
isActive,
|
||||
loadExtensionsAsync,
|
||||
readManifestFileAsync,
|
||||
removeExtensionAsync,
|
||||
getExtensionPathAsync,
|
||||
};
|
||||
});
|
||||
|
||||
@ -440,3 +457,36 @@ const getMimeType = (file: string) => {
|
||||
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");
|
||||
|
||||
if (!version)
|
||||
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||
|
||||
const { currentVault } = useVaultStore();
|
||||
const removedExtensions = await currentVault?.drizzle
|
||||
.delete(haexExtensions)
|
||||
.where(and(eq(haexExtensions.id, id), eq(haexExtensions.version, version)));
|
||||
return removedExtensions;
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
if (!version)
|
||||
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||
|
||||
const extensionDirectory = await getExtensionPathAsync(id, version);
|
||||
await remove(extensionDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("ERROR removeExtensionFilesAsync", error);
|
||||
throw new Error(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
const replaceUrlWithAssetProtocolAsync = () => { };
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { getSingleRouteParam } from "~/composables/helper";
|
||||
import type { RouteLocationRaw, RouteLocationAsRelativeGeneric } from "vue-router";
|
||||
import type { RouteLocationAsRelativeGeneric } from "vue-router";
|
||||
|
||||
export interface ISidebarItem {
|
||||
name: string;
|
||||
@ -7,6 +6,7 @@ export interface ISidebarItem {
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
iconType?: "icon" | "svg";
|
||||
}
|
||||
|
||||
export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
|
||||
@ -4,18 +4,15 @@ 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 { count } from "drizzle-orm";
|
||||
import { and, count, eq } from "drizzle-orm";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
|
||||
interface IVault {
|
||||
//database: Database;
|
||||
path: string;
|
||||
password: string;
|
||||
name: string;
|
||||
drizzle: SqliteRemoteDatabase<typeof schema>;
|
||||
}
|
||||
interface IOpenVaults {
|
||||
[vaultPath: string]: IVault;
|
||||
[vaultId: string]: IVault;
|
||||
}
|
||||
|
||||
export const useVaultStore = defineStore("vaultStore", () => {
|
||||
@ -58,27 +55,20 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
|
||||
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
||||
try {
|
||||
console.log("try to open db", path, password);
|
||||
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
path,
|
||||
key: password,
|
||||
});
|
||||
|
||||
console.log("open vault from store", result);
|
||||
//const db = await Database.load(sqlitePath);
|
||||
if (result !== "success") throw new Error(result);
|
||||
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
console.log("opened db fileName", fileName, vaultId);
|
||||
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
[vaultId]: {
|
||||
//database: db,
|
||||
path,
|
||||
password,
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params: unknown[], method) => {
|
||||
@ -102,24 +92,15 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
return { rows: [] };
|
||||
}
|
||||
|
||||
/* rows = rows.map((row: any) => {
|
||||
return Object.values(row);
|
||||
}); */
|
||||
|
||||
console.log("select after map", rows);
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0];
|
||||
|
||||
return { rows: results };
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
{ schema: schema, logger: true }
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
//if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
await addVaultAsync({ path });
|
||||
|
||||
@ -127,15 +108,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
} catch (error) {
|
||||
console.error("Error openAsync ", error);
|
||||
return false;
|
||||
//if (error === "file is not a database") throw new Error("Passwort falsch");
|
||||
}
|
||||
};
|
||||
|
||||
const testDatabaseReadAsync = async () => {
|
||||
try {
|
||||
return currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -198,7 +170,7 @@ const getVaultIdAsync = async (path: string) => {
|
||||
return hashHex;
|
||||
};
|
||||
|
||||
function isSelectQuery(sql: string): boolean {
|
||||
const isSelectQuery = (sql: string) => {
|
||||
const selectRegex = /^\s*SELECT\b/i;
|
||||
return selectRegex.test(sql);
|
||||
}
|
||||
};
|
||||
|
||||
19
src/types/haexhub.d.ts
vendored
19
src/types/haexhub.d.ts
vendored
@ -1,6 +1,11 @@
|
||||
export interface IHaexHubExtensionManifest {
|
||||
name: string;
|
||||
id: string;
|
||||
entry: string;
|
||||
author: string;
|
||||
url: string;
|
||||
version: string;
|
||||
icon: string;
|
||||
permissions: {
|
||||
database?: {
|
||||
read?: string[];
|
||||
@ -14,3 +19,17 @@ export interface IHaexHubExtensionManifest {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IHaexHubExtensionLink extends IHaexHubExtension {
|
||||
installed: boolean;
|
||||
}
|
||||
|
||||
export interface IHaexHubExtension {
|
||||
author?: string | null;
|
||||
enabled?: boolean | null;
|
||||
icon?: string | null;
|
||||
id: string;
|
||||
manifest?: IHaexHubExtensionManifest;
|
||||
name: string | null;
|
||||
version?: string | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user