adjust for mobile

This commit is contained in:
2025-09-29 17:06:14 +02:00
parent c7d29cb2be
commit f1daa6b576
26 changed files with 386 additions and 271 deletions

View File

@ -4,12 +4,17 @@ pub mod core;
pub mod error;
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Mutex;
use std::time::UNIX_EPOCH;
use std::{fs, sync::Arc};
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
use tauri_plugin_fs::FsExt;
use thiserror::Error;
use ts_rs::TS;
use crate::crdt::hlc::HlcService;
use crate::database::error::DatabaseError;
@ -17,6 +22,9 @@ use crate::table_names::TABLE_CRDT_CONFIGS;
use crate::AppState;
pub struct DbConnection(pub Arc<Mutex<Option<Connection>>>);
const VAULT_EXTENSION: &str = ".db";
const VAULT_DIRECTORY: &str = "vaults";
#[tauri::command]
pub fn sql_select(
sql: String,
@ -35,97 +43,251 @@ pub fn sql_execute(
core::execute(sql, params, &state.db)
}
/// Resolves a database name to the full vault path
fn get_vault_path(app_handle: &AppHandle, vault_name: &str) -> Result<String, DatabaseError> {
// Sicherstellen, dass der Name eine .db Endung hat
let vault_file_name = if vault_name.ends_with(VAULT_EXTENSION) {
vault_name.to_string()
} else {
format!("{}{VAULT_EXTENSION}", vault_name)
};
let vault_directory = get_vaults_directory(app_handle)?;
let vault_path = app_handle
.path()
.resolve(
format!("{vault_directory}/{}", vault_file_name),
BaseDirectory::AppLocalData,
)
.map_err(|e| DatabaseError::PathResolutionError {
reason: format!(
"Failed to resolve vault path for '{}': {}",
vault_file_name, e
),
})?;
// Sicherstellen, dass das vaults-Verzeichnis existiert
if let Some(parent) = vault_path.parent() {
fs::create_dir_all(parent).map_err(|e| DatabaseError::IoError {
path: parent.display().to_string(),
reason: format!("Failed to create vaults directory: {}", e),
})?;
}
Ok(vault_path.to_string_lossy().to_string())
}
/// Returns the vaults directory path
#[tauri::command]
pub fn get_vaults_directory(app_handle: &AppHandle) -> Result<String, DatabaseError> {
let vaults_dir = app_handle
.path()
.resolve(VAULT_DIRECTORY, BaseDirectory::AppLocalData)
.map_err(|e| DatabaseError::PathResolutionError {
reason: e.to_string(),
})?;
Ok(vaults_dir.to_string_lossy().to_string())
}
//#[serde(tag = "type", content = "details")]
#[derive(Debug, Serialize, Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct VaultInfo {
name: String,
last_access: u64,
path: String,
}
/// Lists all vault databases in the vaults directory
#[tauri::command]
pub fn list_vaults(app_handle: AppHandle) -> Result<Vec<VaultInfo>, DatabaseError> {
let vaults_dir_str = get_vaults_directory(&app_handle)?;
let vaults_dir = Path::new(&vaults_dir_str);
println!("Suche vaults in {}", vaults_dir.display());
let mut vaults: Vec<VaultInfo> = vec![];
if !vaults_dir.exists() {
println!("Vaults-Verzeichnis existiert nicht, gebe leere Liste zurück.");
return Ok(vec![]);
}
for entry in fs::read_dir(vaults_dir).map_err(|e| DatabaseError::IoError {
path: "vaults directory".to_string(),
reason: e.to_string(),
})? {
let entry = entry.map_err(|e| DatabaseError::IoError {
path: "vaults directory entry".to_string(),
reason: e.to_string(),
})?;
println!("Suche entry {}", entry.path().to_string_lossy());
let path = entry.path();
if path.is_file() {
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename.ends_with(VAULT_EXTENSION) {
// Entferne .db Endung für die Rückgabe
println!("Vault gefunden {}", filename.to_string());
let metadata = fs::metadata(&path).map_err(|e| DatabaseError::IoError {
path: path.to_string_lossy().to_string(),
reason: format!("Metadaten konnten nicht gelesen werden: {}", e),
})?;
let last_access_timestamp = metadata
.accessed()
.map_err(|e| DatabaseError::IoError {
path: path.to_string_lossy().to_string(),
reason: format!("Zugriffszeit konnte nicht gelesen werden: {}", e),
})?
.duration_since(UNIX_EPOCH)
.unwrap_or_default() // Fallback für den seltenen Fall einer Zeit vor 1970
.as_secs();
let vault_name = filename.trim_end_matches(VAULT_EXTENSION).to_string();
vaults.push(VaultInfo {
name: vault_name,
last_access: last_access_timestamp,
path: path.to_string_lossy().to_string(),
});
}
}
}
}
Ok(vaults)
}
/// Checks if a vault with the given name exists
#[tauri::command]
pub fn vault_exists(app_handle: AppHandle, db_name: String) -> Result<bool, DatabaseError> {
let vault_path = get_vault_path(&app_handle, &db_name)?;
Ok(Path::new(&vault_path).exists())
}
/// Deletes a vault database file
#[tauri::command]
pub fn delete_vault(app_handle: AppHandle, db_name: String) -> Result<String, DatabaseError> {
let vault_path = get_vault_path(&app_handle, &db_name)?;
if !Path::new(&vault_path).exists() {
return Err(DatabaseError::IoError {
path: vault_path,
reason: "Vault does not exist".to_string(),
});
}
fs::remove_file(&vault_path).map_err(|e| DatabaseError::IoError {
path: vault_path.clone(),
reason: format!("Failed to delete vault: {}", e),
})?;
Ok(format!("Vault '{}' successfully deleted", db_name))
}
#[tauri::command]
pub fn create_encrypted_database(
app_handle: AppHandle,
path: String,
vault_name: String,
key: String,
state: State<'_, AppState>,
) -> Result<String, DatabaseError> {
// Ressourcenpfad zur eingebundenen Datenbank auflösen
println!("Creating encrypted vault with name: {}", vault_name);
println!("Arbeitsverzeichnis: {:?}", std::env::current_dir());
println!(
"Ressourcenverzeichnis: {:?}",
app_handle.path().resource_dir()
);
let vault_path = get_vault_path(&app_handle, &vault_name)?;
println!("Resolved vault path: {}", vault_path);
// Prüfen, ob bereits eine Vault mit diesem Namen existiert
if Path::new(&vault_path).exists() {
return Err(DatabaseError::IoError {
path: vault_path,
reason: format!("A vault with the name '{}' already exists", vault_name),
});
}
/* let resource_path = app_handle
.path()
.resolve("database/vault.db", BaseDirectory::Resource)
.map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?; */
let resource_path = app_handle
let template_path = app_handle
.path()
.resolve("database/vault.db", BaseDirectory::Resource)
.map_err(|e| DatabaseError::PathResolutionError {
reason: format!("Failed to resolve template database: {}", e),
})?;
let template_content =
app_handle
.fs()
.read(&template_path)
.map_err(|e| DatabaseError::IoError {
path: template_path.display().to_string(),
reason: format!("Failed to read template database from resources: {}", e),
})?;
let temp_path = app_handle
.path()
.resolve("temp_vault.db", BaseDirectory::AppLocalData)
.map_err(|e| DatabaseError::PathResolutionError {
reason: e.to_string(),
reason: format!("Failed to resolve temp database: {}", e),
})?;
// Prüfen, ob die Ressourcendatei existiert
if !resource_path.exists() {
let temp_path_clone = temp_path.to_owned();
fs::write(temp_path, template_content).map_err(|e| DatabaseError::IoError {
path: vault_path.to_string(),
reason: format!("Failed to write temporary template database: {}", e),
})?;
/* if !template_path.exists() {
return Err(DatabaseError::IoError {
path: resource_path.display().to_string(),
reason: "Ressourcendatenbank wurde nicht gefunden.".to_string(),
path: template_path.display().to_string(),
reason: "Template database not found in resources".to_string(),
});
}
// Sicherstellen, dass das Zielverzeichnis existiert
/* if let Some(parent) = Path::new(&path).parent() {
if !parent.exists() {
std::fs::create_dir_all(parent).map_err(|e| {
format!(
"Fehler beim Erstellen des Zielverzeichnisses: {}\n mit Fehler {}",
path, e
)
})?;
}
} */
let target = Path::new(&path);
if target.exists() & target.is_file() {
fs::remove_file(target).map_err(|e| DatabaseError::IoError {
path: target.display().to_string(),
reason: format!("Bestehende Zieldatei konnte nicht gelöscht werden: {}", e),
})?;
}
println!(
"Öffne unverschlüsselte Datenbank: {}",
resource_path.as_path().display()
);
let conn = Connection::open(&resource_path).map_err(|e| DatabaseError::ConnectionFailed {
path: resource_path.display().to_string(),
println!("Öffne Temp-Datenbank direkt: {}", temp_path_clone.display());
let conn = Connection::open(&temp_path_clone).map_err(|e| DatabaseError::ConnectionFailed {
path: temp_path_clone.display().to_string(),
reason: format!(
"Fehler beim Öffnen der unverschlüsselten Quelldatenbank: {}",
e
),
})?;
println!("Hänge neue, verschlüsselte Datenbank an unter '{}'", &path);
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
conn.execute("ATTACH DATABASE ?1 AS encrypted KEY ?2;", [&path, &key])
.map_err(|e| DatabaseError::ExecutionError {
sql: "ATTACH DATABASE ...".to_string(),
reason: e.to_string(),
table: None,
})?;
println!(
"Exportiere Daten von 'main' nach 'encrypted' mit password {} ...",
&key
"Hänge neue, verschlüsselte Datenbank an unter '{}'",
&vault_path
);
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
conn.execute(
"ATTACH DATABASE ?1 AS encrypted KEY ?2;",
[&vault_path, &key],
)
.map_err(|e| DatabaseError::ExecutionError {
sql: "ATTACH DATABASE ...".to_string(),
reason: e.to_string(),
table: None,
})?;
println!("Exportiere Daten von 'main' nach 'encrypted' ...");
if let Err(e) = conn.query_row("SELECT sqlcipher_export('encrypted');", [], |_| Ok(())) {
// Versuche aufzuräumen, ignoriere Fehler dabei
let _ = conn.execute("DETACH DATABASE encrypted;", []);
// Lösche auch die eventuell teilweise erstellte Datei
let _ = fs::remove_file(&vault_path);
let _ = fs::remove_file(&temp_path_clone);
return Err(DatabaseError::QueryError {
reason: format!("Fehler während sqlcipher_export: {}", e),
});
}
println!("Löse die verschlüsselte Datenbank vom Handle...");
conn.execute("DETACH DATABASE encrypted;", [])
.map_err(|e| DatabaseError::ExecutionError {
sql: "DETACH DATABASE ...".to_string(),
@ -133,13 +295,12 @@ pub fn create_encrypted_database(
table: None,
})?;
println!("Datenbank erfolgreich nach '{}' verschlüsselt.", &path);
println!(
"Die Originaldatei '{}' ist unverändert.",
resource_path.as_path().display()
"Datenbank erfolgreich nach '{}' verschlüsselt.",
&vault_path
);
// 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN
// SQLCipher-Verifizierung
println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'...");
match conn.query_row("PRAGMA cipher_version;", [], |row| {
let version: String = row.get(0)?;
@ -155,62 +316,42 @@ pub fn create_encrypted_database(
}
}
println!("resource_path: {}", resource_path.display());
conn.close()
.map_err(|(_, e)| DatabaseError::ConnectionFailed {
path: resource_path.display().to_string(),
path: template_path.display().to_string(),
reason: format!("Fehler beim Schließen der Quelldatenbank: {}", e),
})?;
initialize_session(&app_handle, &path, &key, &state)?;
let _ = fs::remove_file(&temp_path_clone);
/* let new_conn = core::open_and_init_db(&path, &key, false)?;
initialize_session(&app_handle, &vault_path, &key, &state)?;
// Aktualisieren der Datenbankverbindung im State
let mut db = state.db.0.lock().map_err(|e| DatabaseError::LockError {
reason: e.to_string(),
})?;
*db = Some(new_conn); */
Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",))
Ok(vault_path)
}
#[tauri::command]
pub fn open_encrypted_database(
app_handle: AppHandle,
path: String,
vault_path: String,
key: String,
state: State<'_, AppState>,
) -> Result<String, DatabaseError> {
if !Path::new(&path).exists() {
println!("Opening encrypted database vault_path: {}", vault_path);
// Vault-Pfad aus dem Namen ableiten
//let vault_path = get_vault_path(&app_handle, &vault_name)?;
println!("Resolved vault path: {}", vault_path);
if !Path::new(&vault_path).exists() {
return Err(DatabaseError::IoError {
path,
reason: "Database file not found.".to_string(),
path: vault_path.to_string(),
reason: format!("Vault '{}' does not exist", vault_path),
});
}
/* let vault_path = app_handle
.path()
.resolve(format!("vaults/{}", path), BaseDirectory::AppLocalData)
.map_err(|e| format!("Fehler {}", e))?
.into_os_string()
.into_string()
.unwrap(); */
/* if !std::path::Path::new(&path).exists() {
return Err(format!("File not found {}", path).into());
} */
/* let conn = core::open_and_init_db(&path, &key, false)
.map_err(|e| format!("Error during open: {}", e))?;
initialize_session(&app_handle, &vault_path, &key, &state)?;
let mut db = state.db.0.lock().map_err(|e| e.to_string())?;
*db = Some(conn); */
initialize_session(&app_handle, &path, &key, &state)?;
Ok(format!("success"))
Ok(format!("Vault '{}' opened successfully", vault_path))
}
/// Opens the DB, initializes the HLC service, and stores both in the AppState.

View File

@ -78,11 +78,14 @@ pub fn run() {
//.plugin(tauri_plugin_android_fs::init())
.invoke_handler(tauri::generate_handler![
database::create_encrypted_database,
database::delete_vault,
database::list_vaults,
database::open_encrypted_database,
database::sql_execute,
database::sql_select,
extension::database::extension_sql_select,
database::vault_exists,
extension::database::extension_sql_execute,
extension::database::extension_sql_select,
//database::update_hlc_from_remote,
/* extension::copy_directory,
extension::database::extension_sql_select, */

View File

@ -16,5 +16,5 @@
}
:root {
--ui-header-height: 48px; /* oder was auch immer deine Header-Höhe ist */
--ui-header-height: 74px;
}

View File

@ -4,6 +4,8 @@
icon="material-symbols:apps"
color="neutral"
variant="outline"
v-bind="$attrs"
size="xl"
/>
<template #content>

View File

@ -1,5 +1,8 @@
<template>
<UCard v-if="group">
<UCard
v-if="group"
:ui="{ root: [''] }"
>
<template #header>
<div class="flex items-center gap-2">
<Icon

View File

@ -1,7 +1,7 @@
<template>
<div
v-if="menuItems?.length"
class="flex-1"
class="flex-1 h-full"
>
<ul
ref="listRef"
@ -43,7 +43,7 @@
</div>
<div
v-else
class="flex justify-center items-center px-20 flex-1 my-auto"
class="flex justify-center items-center flex-1"
>
<UiIconNoData class="text-primary size-24 shrink-0" />
</div>

View File

@ -53,33 +53,23 @@
</template>
<script setup lang="ts">
import { save } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, readFile, writeFile } from '@tauri-apps/plugin-fs'
import { resolveResource } from '@tauri-apps/api/path'
import { vaultSchema } from './schema'
//import type { FormSubmitEvent } from '@nuxt/ui'
const { t } = useI18n()
//type Schema = z.output<typeof vaultSchema>
const vault = reactive<{
name: string
password: string
path: string | null
type: 'password' | 'text'
}>({
name: 'HaexVault',
password: '',
path: '',
type: 'password',
})
const initVault = () => {
vault.name = 'HaexVault'
vault.password = ''
vault.path = ''
vault.type = 'password'
}
@ -95,34 +85,16 @@ const onCreateAsync = async () => {
const nameCheck = vaultSchema.name.safeParse(vault.name)
const passwordCheck = vaultSchema.password.safeParse(vault.password)
console.log('checks', vault.name, nameCheck, vault.password, passwordCheck)
if (!nameCheck.success || !passwordCheck.success) return
open.value = false
try {
const template_vault_path = await resolveResource('database/vault.db')
const template_vault = await readFile(template_vault_path)
vault.path = await save({
defaultPath: vault.name.endsWith('.db') ? vault.name : `${vault.name}.db`,
})
if (!vault.path) return
await writeFile('temp_vault.db', template_vault, {
baseDir: BaseDirectory.AppLocalData,
})
console.log('data', vault)
if (vault.path && vault.password) {
if (vault.name && vault.password) {
const vaultId = await createAsync({
path: vault.path,
vaultName: vault.name,
password: vault.password,
})
console.log('vaultId', vaultId)
if (vaultId) {
initVault()
await navigateTo(
@ -132,7 +104,7 @@ const onCreateAsync = async () => {
}
} catch (error) {
console.error(error)
add({ color: 'error', description: `${error}` })
add({ color: 'error', description: JSON.stringify(error) })
}
}
</script>

View File

@ -5,7 +5,7 @@
:description="vault.path || path"
@confirm="onOpenDatabase"
>
<UiButton
<!-- <UiButton
:label="t('vault.open')"
:ui="{
base: 'px-3 py-2',
@ -15,7 +15,7 @@
variant="outline"
block
@click.stop="onLoadDatabase"
/>
/> -->
<template #title>
<i18n-t
@ -51,9 +51,14 @@
</template>
<script setup lang="ts">
import { open as openVault } from '@tauri-apps/plugin-dialog'
/* import { open as openVault } from '@tauri-apps/plugin-dialog' */
import { vaultSchema } from './schema'
const open = defineModel<boolean>('open', { default: false })
const props = defineProps<{
path?: string
}>()
const { t } = useI18n()
const vault = reactive<{
@ -68,11 +73,7 @@ const vault = reactive<{
type: 'password',
})
const open = defineModel('open', { type: Boolean })
const { add } = useToast()
const onLoadDatabase = async () => {
/* const onLoadDatabase = async () => {
try {
vault.path = await openVault({
multiple: false,
@ -97,15 +98,11 @@ const onLoadDatabase = async () => {
console.error('handleError', error, typeof error)
add({ color: 'error', description: `${error}` })
}
}
} */
const { syncLocaleAsync, syncThemeAsync, syncVaultNameAsync } =
useVaultSettingsStore()
const props = defineProps<{
path: string
}>()
const check = ref(false)
const initVault = () => {
@ -120,13 +117,17 @@ const onAbort = () => {
open.value = false
}
const { add } = useToast()
const onOpenDatabase = async () => {
try {
if (!props.path) return
const { openAsync } = useVaultStore()
const localePath = useLocalePath()
check.value = true
const path = vault.path || props.path
const path = props.path
const pathCheck = vaultSchema.path.safeParse(path)
const passwordCheck = vaultSchema.password.safeParse(vault.password)

View File

@ -3,7 +3,11 @@
<UTooltip :text="buttonProps?.tooltip">
<UButton
class="pointer-events-auto"
v-bind="{ ...buttonProps, ...$attrs }"
v-bind="{
...{ size: isSmallScreen ? 'lg' : 'md' },
...buttonProps,
...$attrs,
}"
@click="(e) => $emit('click', e)"
>
<template
@ -28,4 +32,6 @@ interface IButtonProps extends /* @vue-ignore */ ButtonProps {
}
const buttonProps = defineProps<IButtonProps>()
defineEmits<{ click: [Event] }>()
const { isSmallScreen } = storeToRefs(useUiStore())
</script>

View File

@ -3,8 +3,6 @@
v-model:open="open"
:title
:description
:fullscreen="isSmallScreen"
:ui="{ header: 'pt-10 sm:pt-0', footer: 'mb-10 sm:mb-0' }"
>
<slot>
<!-- <UiButton
@ -49,8 +47,6 @@
</template>
<script setup lang="ts">
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
defineProps<{
abortIcon?: string
abortLabel?: string
@ -64,11 +60,6 @@ const open = defineModel<boolean>('open', { default: false })
const { t } = useI18n()
defineEmits(['confirm'])
const breakpoints = useBreakpoints(breakpointsTailwind)
// "smAndDown" gilt für sm, xs usw.
const isSmallScreen = breakpoints.smaller('sm')
</script>
<i18n lang="yaml">

View File

@ -3,6 +3,7 @@
arrow
:items
:ui="{}"
:size="isSmallScreen ? 'lg' : 'md'"
>
<UButton
:icon="items.find((item) => item.label === locale)?.icon"
@ -35,4 +36,6 @@ const items = computed<DropdownMenuItem[]>(() =>
},
})),
)
const { isSmallScreen } = storeToRefs(useUiStore())
</script>

View File

@ -4,6 +4,8 @@
icon="mdi:menu"
color="neutral"
variant="outline"
v-bind="$attrs"
size="xl"
/>
</UDropdownMenu>
</template>

View File

@ -5,6 +5,7 @@
:readonly="props.readOnly"
:leading-icon="props.leadingIcon"
:ui="{ base: 'peer' }"
:size="isSmallScreen ? 'lg' : 'md'"
@change="(e) => $emit('change', e)"
@blur="(e) => $emit('blur', e)"
@keyup="(e: KeyboardEvent) => $emit('keyup', e)"
@ -48,7 +49,8 @@
</template>
<script setup lang="ts">
import type { AcceptableValue, InputProps } from '@nuxt/ui'
import type { InputProps } from '@nuxt/ui'
import type { AcceptableValue } from '@nuxt/ui/runtime/types/utils.js'
const value = defineModel<AcceptableValue | undefined>()
@ -83,6 +85,8 @@ const filteredSlots = computed(() => {
})
watchImmediate(props, () => console.log('props', props))
const { isSmallScreen } = storeToRefs(useUiStore())
</script>
<i18n lang="yaml">

View File

@ -25,7 +25,7 @@
</template>
<script setup lang="ts">
import type { AcceptableValue } from '@nuxt/ui'
import type { AcceptableValue } from '@nuxt/ui/runtime/types/utils.js'
defineProps<{
label?: string

View File

@ -1,15 +1,21 @@
<template>
<div class="min-h-screen flex flex-col">
<header
class="bg-default/90 backdrop-blur border-b border-accented h-(--ui-header-height) sticky top-0 z-50 flex"
<div class="">
<!-- class="" -->
<UPageHeader
as="header"
:ui="{
root: [
'bg-default border-b border-accented sticky top-0 z-50 py-0 px-8',
],
wrapper: [
'pt-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4',
],
}"
>
<div class="px-2 bg-primary rounded-br-xs">
<UiLogoHaexhub class="p-2 size-12 shrink-0" />
</div>
<div
class="w-full max-w-(--ui-container) px-4 sm:px-6 lg:px-8 mx-auto flex items-center justify-between gap-3 h-full"
>
<div class="lg:flex-1 flex items-center gap-1.5 min-w-0">
<template #title>
<div class="flex items-center">
<UiLogoHaexhub class="size-12 shrink-0" />
<NuxtLinkLocale
class="link text-base-content link-neutral text-xl font-semibold no-underline flex items-center"
:to="{ name: 'vaultOverview' }"
@ -19,48 +25,24 @@
</UiTextGradient>
</NuxtLinkLocale>
</div>
</template>
<div class="hidden lg:flex"></div>
<template #links>
<HaexMenuApplications :block="isSmallScreen" />
<UiDropdownVault :block="isSmallScreen" />
</template>
</UPageHeader>
<div class="flex items-center justify-end lg:flex-1 gap-1.5">
<HaexMenuApplications />
<UiDropdownVault />
</div>
</div>
</header>
<div class="flex flex-1">
<!-- <aside
id="sidebar"
class="border-r border-accented transition-all shrink-0 sticky top-(--ui-header-height) h-[calc(100vh-var(--ui-header-height))]"
:class="[!isVisible ? 'w-0' : 'w-16']"
role="dialog"
tabindex="-1"
>
<ul class="p-0 h-full flex flex-col gap-2">
<UiSidebarLink
v-for="item in menu"
v-bind="item"
:key="item.id"
/>
<UiSidebarLink
v-for="item in extensionLinks"
:key="item.id"
v-bind="item"
icon-type="svg"
/>
</ul>
</aside> -->
<main class="flex-1 bg-elevated">
<NuxtPage />
</main>
</div>
<main class="overflow-scroll flex bg-elevated">
<NuxtPage />
</main>
</div>
</template>
<script setup lang="ts">
const { currentVaultName } = storeToRefs(useVaultStore())
const { isSmallScreen } = storeToRefs(useUiStore())
</script>
<i18n lang="yaml">

View File

@ -1,5 +1,5 @@
<template>
<div class="bg-default isolate h-dvh py-4 sm:py-0">
<div class="bg-default isolate h-dvh">
<slot />
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div class="items-center justify-center flex w-full h-full relative">
<div class="absolute top-2 right-4">
<div class="absolute top-8 right-8 sm:top-4 sm:right-4">
<UiDropdownLocale @select="onSelectLocale" />
</div>
@ -16,11 +16,11 @@
</span>
<div class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto">
<VaultButtonCreate />
<HaexVaultCreate />
<VaultButtonOpen
<HaexVaultOpen
v-model:open="passwordPromptOpen"
:path="vaultPath"
:path="selectedVault?.path"
/>
</div>
@ -37,7 +37,7 @@
>
<div
v-for="vault in lastVaults"
:key="vault.path"
:key="vault.name"
class="flex items-center justify-between group overflow-x-scroll"
>
<UButton
@ -47,16 +47,13 @@
@click="
() => {
passwordPromptOpen = true
vaultPath = vault.path
selectedVault = vault
}
"
>
<span class="block md:hidden">
<span class="block">
{{ vault.name }}
</span>
<span class="hidden md:block">
{{ vault.path }}
</span>
</UButton>
<UButton
color="error"
@ -65,7 +62,7 @@
>
<Icon
name="mdi:trash-can-outline"
@click="removeVaultAsync(vault.path)"
@click="removeVaultAsync(vault.name)"
/>
</UButton>
</div>
@ -90,19 +87,21 @@
<script setup lang="ts">
import { openUrl } from '@tauri-apps/plugin-opener'
import type { Locale } from 'vue-i18n'
definePageMeta({
name: 'vaultOpen',
})
const { t, setLocale } = useI18n()
const passwordPromptOpen = ref(false)
const vaultPath = ref('')
const { t, setLocale } = useI18n()
const selectedVault = ref<IVaultInfo>()
const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
const { lastVaults } = storeToRefs(useLastVaultStore())
await syncLastVaultsAsync()
onMounted(async () => {
await syncLastVaultsAsync()
})
const onSelectLocale = async (locale: Locale) => {
await setLocale(locale)

View File

@ -1,17 +1,22 @@
<template>
<div class="h-full">
<UPage
:ui="{
root: ['h-full w-full bg-elevated'],
center: ['h-full w-full'],
}"
>
<NuxtLayout name="app">
<NuxtPage />
</NuxtLayout>
<div class="hidden">
<UiDialogConfirm
v-model:open="showNewDeviceDialog"
:confirm-label="t('newDevice.save')"
:title="t('newDevice.title')"
confirm-icon="mdi:content-save-outline"
@abort="showNewDeviceDialog = false"
@confirm="onSetDeviceNameAsync"
confirm-icon="mdi:content-save-outline"
v-model:open="showNewDeviceDialog"
>
<template #body>
<div class="flex flex-col gap-4">
@ -29,7 +34,7 @@
</template>
</UiDialogConfirm>
</div>
</div>
</UPage>
</template>
<script setup lang="ts">
@ -76,6 +81,7 @@ const onSetDeviceNameAsync = async () => {
showNewDeviceDialog.value = false
add({ color: 'success', description: t('newDevice.success') })
} catch (error) {
console.error(error)
add({ color: 'error', description: t('newDevice.error') })
}
}

View File

@ -1,13 +1,12 @@
<template>
<div class="text-base-content flex flex-col">
<div class="h-screen bg-amber-300">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
<UPage>
<div class="h-screen bg-amber-300 flex-1 flex-wrap">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div class="h-screen bg-teal-300">
abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
availableThemes:{{ uiStore.availableThemes }}
<div class="h-screen bg-teal-300 flex-1">
abbbbbbbbbbbbbbbbbbbbb availableThemes:{{ uiStore.availableThemes }}
</div>
</div>
</UPage>
</template>
<script setup lang="ts">

View File

@ -1,5 +1,5 @@
<template>
<div class="flex-1 p-2 relative h-full">
<div class="flex-1 p-2">
<NuxtPage />
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="">
<HaexPassGroup
v-model="group"
mode="create"

View File

@ -1,21 +1,22 @@
<template>
<div class="flex-1 h-full">
<div class="h-full flex flex-col">
<div class="flex flex-1">
<!-- <div class="h-screen bg-accented">aaa</div> -->
<div class="flex flex-col flex-1">
<HaexPassGroupBreadcrumbs
v-show="breadCrumbs.length"
:items="breadCrumbs"
class="px-2 sticky -top-2 z-10"
/>
<div class="flex-1 py-1 flex">
<HaexPassMobileMenu
ref="listRef"
v-model:selected-items="selectedItems"
:menu-items="groupItems"
/>
</div>
<!-- <div class="flex-1 py-1 flex"> -->
<HaexPassMobileMenu
ref="listRef"
v-model:selected-items="selectedItems"
:menu-items="groupItems"
/>
<!-- </div> -->
<div
class="fixed bottom-4 flex justify-between transition-all w-full sm:items-center items-end px-8"
class="fixed bottom-16 flex justify-between transition-all w-full sm:items-center items-end px-8 z-40"
>
<div class="w-full" />
@ -26,7 +27,7 @@
<UButton
icon="mdi:plus"
:ui="{
base: 'rotate-45 ',
base: 'rotate-45 z-40',
leadingIcon: [open ? 'rotate-0' : 'rotate-45', 'transition-all'],
}"
size="xl"

View File

@ -1,4 +1,5 @@
//import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import { breakpointsTailwind } from '@vueuse/core'
import de from './de.json'
import en from './en.json'
@ -15,6 +16,11 @@ export const useUiStore = defineStore('uiStore', () => {
breakpoints.active().value.length > 0 ? breakpoints.active().value : 'xs',
) */
const breakpoints = useBreakpoints(breakpointsTailwind)
// "smAndDown" gilt für sm, xs usw.
const isSmallScreen = breakpoints.smaller('sm')
const { $i18n } = useNuxtApp()
$i18n.setLocaleMessage('de', {
@ -70,5 +76,6 @@ export const useUiStore = defineStore('uiStore', () => {
currentTheme,
currentThemeName,
defaultTheme,
isSmallScreen,
}
})

View File

@ -40,21 +40,19 @@ export const useVaultStore = defineStore('vaultStore', () => {
password: string
}) => {
try {
const result = await invoke<string>('open_encrypted_database', {
path,
await invoke<string>('open_encrypted_database', {
vaultPath: path,
key: password,
})
if (result !== 'success') throw new Error(result)
const vaultId = await getVaultIdAsync(path)
const fileName = getFileName(path)
const fileName = getFileName(path) ?? path
openVaults.value = {
...openVaults.value,
[vaultId]: {
name: fileName ?? path,
name: fileName,
drizzle: drizzle<typeof schema>(
async (sql, params: unknown[], method) => {
let rows: any[] = []
@ -92,8 +90,6 @@ export const useVaultStore = defineStore('vaultStore', () => {
},
}
const { addVaultAsync } = useLastVaultStore()
await addVaultAsync({ path })
return vaultId
} catch (error) {
console.error('Error openAsync ', error)
@ -102,17 +98,17 @@ export const useVaultStore = defineStore('vaultStore', () => {
}
const createAsync = async ({
path,
vaultName,
password,
}: {
path: string
vaultName: string
password: string
}) => {
await invoke('create_encrypted_database', {
path,
const vaultPath = await invoke<string>('create_encrypted_database', {
vaultName,
key: password,
})
return await openAsync({ path, password })
return await openAsync({ path: vaultPath, password })
}
const closeAsync = async () => {
@ -147,17 +143,3 @@ const isSelectQuery = (sql: string) => {
const selectRegex = /^\s*SELECT\b/i
return selectRegex.test(sql)
}
/* const trackAllHaexTablesAsync = async (vaultId: string) => {
const { openVaults } = useVaultStore()
const promises = Object.values(schema)
.filter(isTable)
.map((table) => {
const stmt = `SELECT crsql_as_crr('${getTableConfig(table).name}');`
console.log('track table', getTableConfig(table).name)
return openVaults?.[vaultId]?.drizzle.run(sql`${stmt}`.getSQL())
})
await Promise.allSettled(promises)
} */

View File

@ -1,9 +1,16 @@
import { invoke } from '@tauri-apps/api/core'
import { load } from '@tauri-apps/plugin-store'
interface ILastVault {
/* interface ILastVault {
lastUsed: Date
name: string
path: string
} */
export interface IVaultInfo {
name: string
path: string
lastAccess: Date
}
export const useLastVaultStore = defineStore('lastVaultStore', () => {
@ -11,7 +18,7 @@ export const useLastVaultStore = defineStore('lastVaultStore', () => {
public: { haexVault },
} = useRuntimeConfig()
const lastVaults = ref<ILastVault[]>([])
const lastVaults = ref<IVaultInfo[]>([])
const keyName = 'lastVaults'
@ -20,15 +27,19 @@ export const useLastVaultStore = defineStore('lastVaultStore', () => {
}
const syncLastVaultsAsync = async () => {
const store = await getStoreAsync()
lastVaults.value =
(await store.get<ILastVault[]>(keyName))?.sort(
(a, b) => +new Date(b.lastUsed) - +new Date(a.lastUsed),
(await listVaultsAsync()).sort(
(a, b) => +new Date(b.lastAccess) - +new Date(a.lastAccess),
) ?? []
return lastVaults.value
}
const listVaultsAsync = async () => {
lastVaults.value = await invoke<IVaultInfo[]>('list_vaults')
return lastVaults.value
}
const addVaultAsync = async ({
name,
path,
@ -40,7 +51,7 @@ export const useLastVaultStore = defineStore('lastVaultStore', () => {
const saveName = name || getFileNameFromPath(path)
lastVaults.value = lastVaults.value.filter((vault) => vault.path !== path)
lastVaults.value.push({ lastUsed: new Date(), name: saveName, path })
lastVaults.value.push({ lastAccess: new Date(), name: saveName, path })
await saveLastVaultsAsync()
}