zwischenstand

This commit is contained in:
Martin Drechsel
2025-05-06 11:09:56 +02:00
parent 410a885d21
commit b729c8ebbe
40 changed files with 2252 additions and 376 deletions

View File

@ -2,19 +2,29 @@ import { 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 { 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[];
@ -30,80 +40,142 @@ export interface IHaexHubExtensionManifest {
}
export const useExtensionsStore = defineStore("extensionsStore", () => {
const availableExtensions = ref<IHaexHubExtensionLink[]>([
{
id: "haex-browser",
name: "Haex Browser",
icon: "solar:global-outline",
},
{
id: "extensions",
name: "sidebar.extensions",
icon: "gg:extension",
},
{
id: "settings",
name: "sidebar.settings",
icon: "ph:gear-six",
},
]);
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
const currentRoute = useRouter().currentRoute;
const isActive = (id: string) =>
computed(
() =>
currentRoute.value.name === "extension" &&
currentRoute.value.params.extensionId === id
() => currentRoute.value.name === "extension" && currentRoute.value.params.extensionId === id
);
const currentExtension = computed(() => {
if (currentRoute.value.name !== "haexExtension") return;
const extensionId = getSingleRouteParam(
currentRoute.value.params.extensionId
);
console.log("computed currentExtension", currentRoute.value.params);
if (currentRoute.value.meta.name !== "haexExtension") return;
const extensionId = getSingleRouteParam(currentRoute.value.params.extensionId);
console.log("extensionId from param", extensionId);
if (!extensionId) return;
return availableExtensions.value.find(
(extension) => extension.id === extensionId
);
const extension = availableExtensions.value.find((extension) => extension.id === extensionId);
console.log("currentExtension", extension);
return extension;
});
const checkExtensionDirectoryAsync = async (extensionDirectory: string) => {
const getExtensionPathAsync = async (extensionId?: string, version?: string) => {
if (!extensionId || !version) return "";
return await join(await resourceDir(), "extensions", extensionId, version);
};
const checkSourceExtensionDirectoryAsync = async (extensionDirectory: string) => {
try {
const dir = await readDir(extensionDirectory);
const manifest = dir.find(
(entry) => entry.name === manifestFileName && entry.isFile
);
const manifest = dir.find((entry) => entry.name === manifestFileName && entry.isFile);
if (!manifest) throw new Error("Kein Manifest für Erweiterung gefunden");
const logo = dir.find((item) => item.isFile && item.name === logoFileName);
if (!logo) throw new Error("Logo fehlt");
return true;
} catch (error) {
throw new Error(
`Keine Leseberechtigung für Ordner ${extensionDirectory}`
);
throw new Error(`Keine Leseberechtigung für Ordner ${extensionDirectory}`);
}
};
const installAsync = async (
extensionDirectory: string | null,
global: boolean = true
): Promise<void> => {
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
const errors = [];
if (typeof manifestFile !== "object" || manifestFile === null) {
errors.push("Manifest ist falsch");
return false;
}
if (!("id" in manifestFile) || typeof manifestFile.id !== "string")
errors.push("Keine ID vergeben");
if (!("name" in manifestFile) || typeof manifestFile.name !== "string")
errors.push("Name fehlt");
if (!("entry" in manifestFile) || typeof manifestFile.entry !== "string")
errors.push("Entry fehlerhaft");
if (!("author" in manifestFile) || typeof manifestFile.author !== "string")
errors.push("Author fehlt");
if (!("url" in manifestFile) || typeof manifestFile.url !== "string") errors.push("Url fehlt");
if (!("version" in manifestFile) || typeof manifestFile.version !== "string")
errors.push("Version fehlt");
if (
!("permissions" in manifestFile) ||
typeof manifestFile.permissions !== "object" ||
manifestFile.permissions === null
) {
errors.push("Berechtigungen fehlen");
}
if (errors.length) throw errors;
/* const permissions = manifestFile.permissions as Partial<IHaexHubExtensionManifest["permissions"]>;
if (
("database" in permissions &&
(typeof permissions.database !== "object" || permissions.database === null)) ||
("filesystem" in permissions && typeof permissions.filesystem !== "object") ||
permissions.filesystem === null
) {
return false;
} */
return true;
};
const readManifestFileAsync = async (extensionId: string, version: string | number) => {
try {
if (!extensionDirectory)
throw new Error("Kein Ordner für Erweiterung angegeben");
const checkDirectory = await checkExtensionDirectoryAsync(
extensionDirectory
);
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
const manifestPath = await join(extensionPath, manifestFileName);
const manifest = (await JSON.parse(
await readTextFile(manifestPath)
)) as IHaexHubExtensionManifest;
const manifestPath = await join(extensionDirectory, "manifest.json");
const manifest = await JSON.parse(await readTextFile(manifestPath));
/*
TODO implement await checkManifets(manifest);
*/
return manifest;
} catch (error) {
console.error("ERROR readManifestFileAsync", error);
}
};
console.log("manifest", manifest);
return;
const installAsync = async (extensionDirectory: string | null, global: boolean = true) => {
try {
if (!extensionDirectory) throw new Error("Kein Ordner für Erweiterung angegeben");
const manifestPath = await join(extensionDirectory, manifestFileName);
const manifest = (await JSON.parse(
await readTextFile(manifestPath)
)) as IHaexHubExtensionManifest;
const destination = await getExtensionPathAsync(manifest.id, manifest.version);
await checkSourceExtensionDirectoryAsync(extensionDirectory);
await invoke("copy_directory", { source: extensionDirectory, destination });
const logoFilePath = await join(destination, "logo.svg");
const logoSvg = await readTextFile(logoFilePath);
const { currentVault } = storeToRefs(useVaultStore());
const res = await currentVault.value?.drizzle.insert(haexExtensions).values({
id: manifest.id,
name: manifest.name,
author: manifest.author,
enabled: true,
url: manifest.url,
version: manifest.version,
icon: logoSvg,
});
console.log("insert extensions", res);
} catch (error) {
throw error;
/*
@ -213,9 +285,158 @@ 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";
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 });
}
}
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}`);
}
console.log("entryHtml", entryHtml);
const blob = new Blob([entryHtml], { type: "text/html" });
const iframeSrc = URL.createObjectURL(blob);
console.log("iframeSrc", iframeSrc);
/* const path = convertFileSrc(extensionDir, manifest.entry);
console.log("final path", path); */
//manifest.entry = iframeSrc;
return iframeSrc;
/* await join(
path, //`file:/${extensionDirectory}`,
manifest.entry
); */
// Modul-Datei laden
//const modulePathFull = await join(basePath, manifest.main);
/* const manifest: PluginManifest = await invoke('load_plugin', {
manifestPath,
}); */
/* const iframe = document.createElement('iframe');
iframe.src = manifest.entry;
iframe.setAttribute('sandbox', 'allow-scripts');
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none'; */
/* const addonApi = {
db_execute: async (sql: string, params: string[] = []) => {
return invoke('db_execute', {
addonId: manifest.name,
sql,
params,
});
},
db_select: async (sql: string, params: string[] = []) => {
return invoke('db_select', {
addonId: manifest.name,
sql,
params,
});
},
}; */
/* iframe.onload = () => {
iframe.contentWindow?.postMessage(
{ type: 'init', payload: addonApi },
'*'
);
};
window.addEventListener('message', (event) => {
if (event.source === iframe.contentWindow) {
const { type } = event.data;
if (type === 'ready') {
console.log(`Plugin ${manifest.name} ist bereit`);
}
}
}); */
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
console.log(`Plugin ${manifest.name} geladen.`); */
},
null,
{ lazy: true }
);
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 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) => ({
id: extension.id,
name: extension.name ?? "",
icon: extension.icon ?? "",
tooltip: extension.name ?? "",
version: extension.version ?? "",
})) ?? [];
};
return {
availableExtensions,
checkManifest,
currentExtension,
extensionEntry,
installAsync,
isActive,
loadExtensionsAsync,
readManifestFileAsync,
};
});
const getMimeType = (file: string) => {
if (file.endsWith(".css")) return "text/css";
if (file.endsWith(".js")) return "text/javascript";
return "text/plain";
};

View File

@ -2,31 +2,33 @@ import { getSingleRouteParam } from "~/composables/helper";
import type { RouteLocationRaw, RouteLocationAsRelativeGeneric } from "vue-router";
export interface ISidebarItem {
name: string;
icon: string;
tooltip?: string;
id: string;
to?: RouteLocationAsRelativeGeneric;
name: string;
icon: string;
tooltip?: string;
id: string;
to?: RouteLocationAsRelativeGeneric;
}
export const useSidebarStore = defineStore("sidebarStore", () => {
const menu = ref<ISidebarItem[]>([
{
id: "haex-browser",
name: "Haex Browser",
icon: "solar:global-outline",
to: { name: "haexBrowser" },
},
const isVisible = ref(true);
{
id: "haex-extensions-add",
name: "Haex Extensions",
icon: "gg:extension",
to: { name: "haexExtensionAdd" },
},
]);
const menu = ref<ISidebarItem[]>([
{
id: "haex-browser",
name: "Haex Browser",
icon: "solar:global-outline",
to: { name: "haexBrowser" },
},
/* const loadAsync = async (id: string) => {
{
id: "haex-extensions-add",
name: "Haex Extensions",
icon: "gg:extension",
to: { name: "extensionOverview" },
},
]);
/* const loadAsync = async (id: string) => {
extensions.value.some(async (extension) => {
if (extension.id === id) {
await navigateTo(
@ -37,8 +39,9 @@ export const useSidebarStore = defineStore("sidebarStore", () => {
});
}; */
return {
menu,
//loadAsync,
};
return {
menu,
isVisible,
//loadAsync,
};
});

View File

@ -57,85 +57,83 @@ export const useVaultStore = defineStore("vaultStore", () => {
);
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
const sqlitePath = path?.startsWith("sqlite:") ? path : `sqlite:${path}`;
try {
console.log("try to open db", path, password);
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);
if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
//const db = await Database.load(sqlitePath);
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,
const result = await invoke<string>("open_encrypted_database", {
path,
password,
name: fileName ?? path,
drizzle: drizzle<typeof schema>(
async (sql, params, method) => {
let rows: any = [];
let results = [];
key: password,
});
// If the query is a SELECT, use the select method
if (isSelectQuery(sql)) {
rows = await invoke("db_select", { sql, params }).catch((e) => {
console.error("SQL Error:", e);
return [];
});
} else {
// Otherwise, use the execute method
rows = await invoke("db_execute", { sql, params }).catch((e) => {
console.error("SQL Error:", e);
return [];
});
return { rows: [] };
}
console.log("open vault from store", result);
//const db = await Database.load(sqlitePath);
rows = rows.map((row: any) => {
return Object.values(row);
});
const vaultId = await getVaultIdAsync(path);
const seperator = platform() === "windows" ? "\\" : "/";
const fileName = path.split(seperator).pop();
console.log("opened db fileName", fileName, vaultId);
// If the method is "all", return all rows
results = method === "all" ? rows : rows[0];
openVaults.value = {
...openVaults.value,
[vaultId]: {
//database: db,
path,
password,
name: fileName ?? path,
drizzle: drizzle<typeof schema>(
async (sql, params: unknown[], method) => {
let rows: any = [];
let results = [];
return { rows: results };
},
// Pass the schema to the drizzle instance
{ schema: schema, logger: true }
),
},
};
// If the query is a SELECT, use the select method
if (isSelectQuery(sql)) {
console.log("sql_select", sql, params);
rows = await invoke("sql_select", { sql, params }).catch((e) => {
console.error("SQL select Error:", e, sql, params);
return [];
});
console.log("select", rows);
} else {
// Otherwise, use the execute method
rows = await invoke("sql_execute", { sql, params }).catch((e) => {
console.error("SQL execute Error:", e, sql, params);
return [];
});
return { rows: [] };
}
const { addVaultAsync } = useLastVaultStore();
await addVaultAsync({ path });
/* rows = rows.map((row: any) => {
return Object.values(row);
}); */
return vaultId;
};
console.log("select after map", rows);
// If the method is "all", return all rows
results = method === "all" ? rows : rows[0];
const createTable = () => {
console.log("ddd", schema.testTable.getSQL().queryChunks);
return { rows: results };
},
// Pass the schema to the drizzle instance
{ schema: schema, logger: true }
),
},
};
schema.testTable.getSQL().queryChunks.forEach((chunk) => {
const res = currentVault.value?.drizzle.run(chunk);
console.log("create table", res);
});
//if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
const { addVaultAsync } = useLastVaultStore();
await addVaultAsync({ path });
return vaultId;
} 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 {
currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
return true;
return currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
} catch (error) {
return false;
}
@ -186,7 +184,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
openVaults,
refreshDatabaseAsync,
read_only,
createTable,
};
});