From 72d97d163678eecaf936197fb5c2789305ecfd9f Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:21:43 +0200 Subject: [PATCH 1/7] File picker --- phenoxtract-ui/.svelte-kit/ambient.d.ts | 256 ++++++++++++++++++ phenoxtract-ui/.svelte-kit/env.d.ts | 1 + .../.svelte-kit/generated/client/app.js | 43 +++ .../.svelte-kit/generated/client/matchers.js | 1 + .../.svelte-kit/generated/client/nodes/0.js | 3 + .../.svelte-kit/generated/client/nodes/1.js | 1 + .../.svelte-kit/generated/client/nodes/2.js | 1 + .../.svelte-kit/generated/client/nodes/3.js | 1 + .../.svelte-kit/generated/client/nodes/4.js | 1 + phenoxtract-ui/.svelte-kit/generated/root.js | 3 + .../.svelte-kit/generated/root.svelte | 68 +++++ .../.svelte-kit/generated/server/internal.js | 55 ++++ .../generated/shared/error-template.js | 1 + phenoxtract-ui/.svelte-kit/non-ambient.d.ts | 45 +++ phenoxtract-ui/.svelte-kit/tsconfig.json | 50 ++++ .../.svelte-kit/types/route_meta_data.json | 12 + .../.svelte-kit/types/src/routes/$types.d.ts | 25 ++ .../src/routes/data_sources_csv/$types.d.ts | 17 ++ .../src/routes/data_sources_excel/$types.d.ts | 17 ++ phenoxtract-ui/package.json | 4 + phenoxtract-ui/pnpm-lock.yaml | 109 ++++++++ phenoxtract-ui/src-tauri/Cargo.toml | 4 +- phenoxtract-ui/src/app/app.component.html | 39 +-- phenoxtract-ui/src/app/app.component.ts | 43 +-- phenoxtract-ui/src/app/app.config.ts | 27 +- .../file-picker/file-picker.component.html | 12 + .../file-picker/file-picker.component.scss | 11 + .../file-picker/file-picker.component.ts | 146 ++++++++++ phenoxtract-ui/src/styles.css | 1 + 29 files changed, 954 insertions(+), 43 deletions(-) create mode 100644 phenoxtract-ui/.svelte-kit/ambient.d.ts create mode 100644 phenoxtract-ui/.svelte-kit/env.d.ts create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/app.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/matchers.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/root.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/root.svelte create mode 100644 phenoxtract-ui/.svelte-kit/generated/server/internal.js create mode 100644 phenoxtract-ui/.svelte-kit/generated/shared/error-template.js create mode 100644 phenoxtract-ui/.svelte-kit/non-ambient.d.ts create mode 100644 phenoxtract-ui/.svelte-kit/tsconfig.json create mode 100644 phenoxtract-ui/.svelte-kit/types/route_meta_data.json create mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts create mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts create mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts create mode 100644 phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html create mode 100644 phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss create mode 100644 phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts diff --git a/phenoxtract-ui/.svelte-kit/ambient.d.ts b/phenoxtract-ui/.svelte-kit/ambient.d.ts new file mode 100644 index 00000000..bc2664bd --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/ambient.d.ts @@ -0,0 +1,256 @@ + +// this file is generated — do not edit it + + +/// + +/** + * This module provides access to environment variables that are injected _statically_ into your bundle at build time and are limited to _private_ access. + * + * | | Runtime | Build time | + * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | + * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | + * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | + * + * Static environment variables are [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env` at build time and then statically injected into your bundle at build time, enabling optimisations like dead code elimination. + * + * **_Private_ access:** + * + * - This module cannot be imported into client-side code + * - This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured) + * + * For example, given the following build time environment: + * + * ```env + * ENVIRONMENT=production + * PUBLIC_BASE_URL=http://site.com + * ``` + * + * With the default `publicPrefix` and `privatePrefix`: + * + * ```ts + * import { ENVIRONMENT, PUBLIC_BASE_URL } from '$env/static/private'; + * + * console.log(ENVIRONMENT); // => "production" + * console.log(PUBLIC_BASE_URL); // => throws error during build + * ``` + * + * The above values will be the same _even if_ different values for `ENVIRONMENT` or `PUBLIC_BASE_URL` are set at runtime, as they are statically replaced in your code with their build time values. + */ +declare module '$env/static/private' { + export const HOMEBREW_PREFIX: string; + export const CONDA_PROMPT_MODIFIER: string; + export const COMMAND_MODE: string; + export const INFOPATH: string; + export const SHELL: string; + export const __CFBundleIdentifier: string; + export const TMPDIR: string; + export const DEBUG_COLORS: string; + export const COLORTERM: string; + export const HOME: string; + export const FORCE_COLOR: string; + export const HOMEBREW_REPOSITORY: string; + export const PATH: string; + export const CONDA_PREFIX: string; + export const LOGNAME: string; + export const XPC_FLAGS: string; + export const __CF_USER_TEXT_ENCODING: string; + export const MOCHA_COLORS: string; + export const PQ_LIB_DIR: string; + export const NVM_CD_FLAGS: string; + export const NVM_DIR: string; + export const LC_CTYPE: string; + export const CONDA_SHLVL: string; + export const CONDA_EXE: string; + export const npm_config_color: string; + export const SSH_AUTH_SOCK: string; + export const OLDPWD: string; + export const CONDA_DEFAULT_ENV: string; + export const XPC_SERVICE_NAME: string; + export const USER: string; + export const HOMEBREW_CELLAR: string; + export const PWD: string; + export const CONDA_PYTHON_EXE: string; + export const PNPM_HOME: string; + export const FPATH: string; + export const NODE_ENV: string; +} + +/** + * This module provides access to environment variables that are injected _statically_ into your bundle at build time and are _publicly_ accessible. + * + * | | Runtime | Build time | + * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | + * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | + * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | + * + * Static environment variables are [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env` at build time and then statically injected into your bundle at build time, enabling optimisations like dead code elimination. + * + * **_Public_ access:** + * + * - This module _can_ be imported into client-side code + * - **Only** variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`) are included + * + * For example, given the following build time environment: + * + * ```env + * ENVIRONMENT=production + * PUBLIC_BASE_URL=http://site.com + * ``` + * + * With the default `publicPrefix` and `privatePrefix`: + * + * ```ts + * import { ENVIRONMENT, PUBLIC_BASE_URL } from '$env/static/public'; + * + * console.log(ENVIRONMENT); // => throws error during build + * console.log(PUBLIC_BASE_URL); // => "http://site.com" + * ``` + * + * The above values will be the same _even if_ different values for `ENVIRONMENT` or `PUBLIC_BASE_URL` are set at runtime, as they are statically replaced in your code with their build time values. + */ +declare module '$env/static/public' { + +} + +/** + * This module provides access to environment variables set _dynamically_ at runtime and that are limited to _private_ access. + * + * | | Runtime | Build time | + * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | + * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | + * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | + * + * Dynamic environment variables are defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. + * + * **_Private_ access:** + * + * - This module cannot be imported into client-side code + * - This module includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured) + * + * > [!NOTE] In `dev`, `$env/dynamic` includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter. + * + * > [!NOTE] To get correct types, environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed: + * > + * > ```env + * > MY_FEATURE_FLAG= + * > ``` + * > + * > You can override `.env` values from the command line like so: + * > + * > ```sh + * > MY_FEATURE_FLAG="enabled" npm run dev + * > ``` + * + * For example, given the following runtime environment: + * + * ```env + * ENVIRONMENT=production + * PUBLIC_BASE_URL=http://site.com + * ``` + * + * With the default `publicPrefix` and `privatePrefix`: + * + * ```ts + * import { env } from '$env/dynamic/private'; + * + * console.log(env.ENVIRONMENT); // => "production" + * console.log(env.PUBLIC_BASE_URL); // => undefined + * ``` + */ +declare module '$env/dynamic/private' { + export const env: { + HOMEBREW_PREFIX: string; + CONDA_PROMPT_MODIFIER: string; + COMMAND_MODE: string; + INFOPATH: string; + SHELL: string; + __CFBundleIdentifier: string; + TMPDIR: string; + DEBUG_COLORS: string; + COLORTERM: string; + HOME: string; + FORCE_COLOR: string; + HOMEBREW_REPOSITORY: string; + PATH: string; + CONDA_PREFIX: string; + LOGNAME: string; + XPC_FLAGS: string; + __CF_USER_TEXT_ENCODING: string; + MOCHA_COLORS: string; + PQ_LIB_DIR: string; + NVM_CD_FLAGS: string; + NVM_DIR: string; + LC_CTYPE: string; + CONDA_SHLVL: string; + CONDA_EXE: string; + npm_config_color: string; + SSH_AUTH_SOCK: string; + OLDPWD: string; + CONDA_DEFAULT_ENV: string; + XPC_SERVICE_NAME: string; + USER: string; + HOMEBREW_CELLAR: string; + PWD: string; + CONDA_PYTHON_EXE: string; + PNPM_HOME: string; + FPATH: string; + NODE_ENV: string; + [key: `PUBLIC_${string}`]: undefined; + [key: `${string}`]: string | undefined; + } +} + +/** + * This module provides access to environment variables set _dynamically_ at runtime and that are _publicly_ accessible. + * + * | | Runtime | Build time | + * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | + * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | + * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | + * + * Dynamic environment variables are defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. + * + * **_Public_ access:** + * + * - This module _can_ be imported into client-side code + * - **Only** variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`) are included + * + * > [!NOTE] In `dev`, `$env/dynamic` includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter. + * + * > [!NOTE] To get correct types, environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed: + * > + * > ```env + * > MY_FEATURE_FLAG= + * > ``` + * > + * > You can override `.env` values from the command line like so: + * > + * > ```sh + * > MY_FEATURE_FLAG="enabled" npm run dev + * > ``` + * + * For example, given the following runtime environment: + * + * ```env + * ENVIRONMENT=production + * PUBLIC_BASE_URL=http://example.com + * ``` + * + * With the default `publicPrefix` and `privatePrefix`: + * + * ```ts + * import { env } from '$env/dynamic/public'; + * console.log(env.ENVIRONMENT); // => undefined, not public + * console.log(env.PUBLIC_BASE_URL); // => "http://example.com" + * ``` + * + * ``` + * + * ``` + */ +declare module '$env/dynamic/public' { + export const env: { + [key: `PUBLIC_${string}`]: string | undefined; + } +} diff --git a/phenoxtract-ui/.svelte-kit/env.d.ts b/phenoxtract-ui/.svelte-kit/env.d.ts new file mode 100644 index 00000000..d7f6c977 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/env.d.ts @@ -0,0 +1 @@ +// See https://svelte.dev/docs/kit/environment-variables for more information \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/app.js b/phenoxtract-ui/.svelte-kit/generated/client/app.js new file mode 100644 index 00000000..402cdde5 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/app.js @@ -0,0 +1,43 @@ +// in dev, this makes Vite inject its client as this module's first dependency, +// so that global constant replacements are installed before any other module +// (including user hooks) evaluates. In build it's inert. +import.meta.hot; + + + + +export { matchers } from './matchers.js'; + +export const nodes = [ + () => import('./nodes/0'), + () => import('./nodes/1'), + () => import('./nodes/2'), + () => import('./nodes/3'), + () => import('./nodes/4') +]; + +export const server_loads = []; + +export const dictionary = { + "/": [2], + "/data_sources_csv": [3], + "/data_sources_excel": [4] + }; + +export const hooks = { + handleError: (({ error }) => { console.error(error) }), + + reroute: (() => {}), + transport: {} +}; + +export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode])); +export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode])); + +export const hash = false; + +export const decode = (type, value) => decoders[type](value); + +export { default as root } from '../root.js'; + +export const get_error_template = () => import('../shared/error-template.js').then(m => m.default); \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/matchers.js b/phenoxtract-ui/.svelte-kit/generated/client/matchers.js new file mode 100644 index 00000000..f6bd30a4 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/matchers.js @@ -0,0 +1 @@ +export const matchers = {}; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js new file mode 100644 index 00000000..b2e56b2b --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js @@ -0,0 +1,3 @@ +import * as universal from "../../../../src/routes/+layout.ts"; +export { universal }; +export { default as component } from "../../../../src/routes/+layout.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js new file mode 100644 index 00000000..c2070619 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js @@ -0,0 +1 @@ +export { default as component } from "../../../../node_modules/.pnpm/@sveltejs+kit@2.67.0_@sveltejs+vite-plugin-svelte@5.1.1_svelte@5.56.4_@typescript-eslin_b3bf91c24becebc0cdee269fdc32be1c/node_modules/@sveltejs/kit/src/runtime/components/svelte-5/error.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js new file mode 100644 index 00000000..1cb4f855 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js @@ -0,0 +1 @@ +export { default as component } from "../../../../src/routes/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js new file mode 100644 index 00000000..9e834e30 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js @@ -0,0 +1 @@ +export { default as component } from "../../../../src/routes/data_sources_csv/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js new file mode 100644 index 00000000..f6c261ed --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js @@ -0,0 +1 @@ +export { default as component } from "../../../../src/routes/data_sources_excel/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/root.js b/phenoxtract-ui/.svelte-kit/generated/root.js new file mode 100644 index 00000000..4d1e8929 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/root.js @@ -0,0 +1,3 @@ +import { asClassComponent } from 'svelte/legacy'; +import Root from './root.svelte'; +export default asClassComponent(Root); \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/root.svelte b/phenoxtract-ui/.svelte-kit/generated/root.svelte new file mode 100644 index 00000000..aef744aa --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/root.svelte @@ -0,0 +1,68 @@ + + + + +{#if constructors[1]} + {@const Pyramid_0 = constructors[0]} + + + + + + +{:else} + {@const Pyramid_0 = constructors[0]} + + + +{/if} + +{#if mounted} +
+ {#if navigated} + {title} + {/if} +
+{/if} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/server/internal.js b/phenoxtract-ui/.svelte-kit/generated/server/internal.js new file mode 100644 index 00000000..03660f19 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/server/internal.js @@ -0,0 +1,55 @@ + +import root from '../root.js'; +import { set_building, set_prerendering } from '$app/env/internal'; +import { set_assets } from '$app/paths/internal/server'; +import { set_manifest, set_read_implementation } from '__sveltekit/server'; +import { set_private_env, set_public_env } from '../../../node_modules/.pnpm/@sveltejs+kit@2.67.0_@sveltejs+vite-plugin-svelte@5.1.1_svelte@5.56.4_@typescript-eslin_b3bf91c24becebc0cdee269fdc32be1c/node_modules/@sveltejs/kit/src/runtime/shared-server.js'; +import error from '../shared/error-template.js'; + +export const options = { + app_template_contains_nonce: false, + async: false, + csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}}, + csrf_check_origin: true, + csrf_trusted_origins: [], + embedded: false, + env_public_prefix: 'PUBLIC_', + env_private_prefix: '', + hash_routing: false, + hooks: null, // added lazily, via `get_hooks` + preload_strategy: "modulepreload", + root, + service_worker: false, + service_worker_options: undefined, + server_error_boundaries: false, + templates: { + app: ({ head, body, assets, nonce, env }) => "\n\n \n \n \n \n Tauri + SvelteKit + Typescript App\n " + head + "\n \n \n
" + body + "
\n \n\n", + error + }, + version_hash: "pcodto" +}; + +export async function get_hooks() { + let handle; + let handleFetch; + let handleError; + let handleValidationError; + let init; + + + let reroute; + let transport; + + + return { + handle, + handleFetch, + handleError, + handleValidationError, + init, + reroute, + transport + }; +} + +export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation }; diff --git a/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js b/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js new file mode 100644 index 00000000..f66e5957 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js @@ -0,0 +1 @@ +export default ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t
\n\t\t\t" + status + "\n\t\t\t
\n\t\t\t\t

" + message + "

\n\t\t\t
\n\t\t
\n\t\n\n"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/non-ambient.d.ts b/phenoxtract-ui/.svelte-kit/non-ambient.d.ts new file mode 100644 index 00000000..14074456 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/non-ambient.d.ts @@ -0,0 +1,45 @@ + +// this file is generated — do not edit it + + +declare module "svelte/elements" { + export interface HTMLAttributes { + 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; + 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; + 'data-sveltekit-preload-code'?: + | true + | '' + | 'eager' + | 'viewport' + | 'hover' + | 'tap' + | 'off' + | undefined + | null; + 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; + 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; + 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; + } +} + +export {}; + + +declare module "$app/types" { + type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; + + export interface AppTypes { + RouteId(): "/" | "/data_sources_csv" | "/data_sources_excel"; + RouteParams(): { + + }; + LayoutParams(): { + "/": Record; + "/data_sources_csv": Record; + "/data_sources_excel": Record + }; + Pathname(): "/" | "/data_sources_csv" | "/data_sources_excel"; + ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`; + Asset(): "/favicon.png" | "/svelte.svg" | "/tauri.svg" | "/vite.svg" | string & {}; + } +} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/tsconfig.json b/phenoxtract-ui/.svelte-kit/tsconfig.json new file mode 100644 index 00000000..f727b03a --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "paths": { + "$app/types": [ + "./types/index.d.ts" + ] + }, + "rootDirs": [ + "..", + "./types" + ], + "verbatimModuleSyntax": true, + "isolatedModules": true, + "lib": [ + "esnext", + "DOM", + "DOM.Iterable" + ], + "moduleResolution": "bundler", + "module": "esnext", + "noEmit": true, + "target": "esnext" + }, + "include": [ + "ambient.d.ts", + "env.d.ts", + "non-ambient.d.ts", + "./types/**/$types.d.ts", + "../vite.config.js", + "../vite.config.ts", + "../src/**/*.js", + "../src/**/*.ts", + "../src/**/*.svelte", + "../test/**/*.js", + "../test/**/*.ts", + "../test/**/*.svelte", + "../tests/**/*.js", + "../tests/**/*.ts", + "../tests/**/*.svelte" + ], + "exclude": [ + "../node_modules/**", + "../src/service-worker.js", + "../src/service-worker/**/*.js", + "../src/service-worker.ts", + "../src/service-worker/**/*.ts", + "../src/service-worker.d.ts", + "../src/service-worker/**/*.d.ts" + ] +} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/route_meta_data.json b/phenoxtract-ui/.svelte-kit/types/route_meta_data.json new file mode 100644 index 00000000..aa2ddaa1 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/types/route_meta_data.json @@ -0,0 +1,12 @@ +{ + "/": [ + "src/routes/+layout.ts", + "src/routes/+layout.ts" + ], + "/data_sources_csv": [ + "src/routes/+layout.ts" + ], + "/data_sources_excel": [ + "src/routes/+layout.ts" + ] +} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts new file mode 100644 index 00000000..4bdb9997 --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts @@ -0,0 +1,25 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; +type RouteParams = { }; +type RouteId = '/'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = EnsureDefined; +type LayoutRouteId = RouteId | "/" | "/data_sources_csv" | "/data_sources_excel" | null +type LayoutParams = RouteParams & { } +type LayoutParentData = EnsureDefined<{}>; + +export type PageServerData = null; +export type PageData = Expand; +export type PageProps = { params: RouteParams; data: PageData } +export type LayoutServerData = null; +export type LayoutLoad = OutputDataShape> = Kit.Load; +export type LayoutLoadEvent = Parameters[0]; +export type LayoutData = Expand> & OptionalUnion>>>; +export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet } \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts new file mode 100644 index 00000000..ec7bd38e --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts @@ -0,0 +1,17 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; +type RouteParams = { }; +type RouteId = '/data_sources_csv'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = EnsureDefined; + +export type PageServerData = null; +export type PageData = Expand; +export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts new file mode 100644 index 00000000..c8467e2d --- /dev/null +++ b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts @@ -0,0 +1,17 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; +type RouteParams = { }; +type RouteId = '/data_sources_excel'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = EnsureDefined; + +export type PageServerData = null; +export type PageData = Expand; +export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/phenoxtract-ui/package.json b/phenoxtract-ui/package.json index 6e7b2cd4..87a9fca5 100644 --- a/phenoxtract-ui/package.json +++ b/phenoxtract-ui/package.json @@ -20,8 +20,12 @@ "@angular/forms": "^20.1.4", "@angular/platform-browser": "^20.1.4", "@angular/router": "^20.1.4", + "@primeuix/themes": "^2.0.3", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-opener": "^2", + "bootstrap-icons": "^1.13.1", + "primeng": "^21.1.9", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.1" diff --git a/phenoxtract-ui/pnpm-lock.yaml b/phenoxtract-ui/pnpm-lock.yaml index 519c9b00..a1c72eba 100644 --- a/phenoxtract-ui/pnpm-lock.yaml +++ b/phenoxtract-ui/pnpm-lock.yaml @@ -23,12 +23,24 @@ importers: '@angular/router': specifier: ^20.1.4 version: 20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@primeuix/themes': + specifier: ^2.0.3 + version: 2.0.3 '@tauri-apps/api': specifier: ^2 version: 2.11.1 + '@tauri-apps/plugin-dialog': + specifier: ^2.7.1 + version: 2.7.1 '@tauri-apps/plugin-opener': specifier: ^2 version: 2.5.4 + bootstrap-icons: + specifier: ^1.13.1 + version: 1.13.1 + primeng: + specifier: ^21.1.9 + version: 21.1.9(d7ad9d8080258453d12745bbca4bb7ff) rxjs: specifier: ~7.8.2 version: 7.8.2 @@ -340,6 +352,14 @@ packages: vitest: optional: true + '@angular/cdk@21.2.14': + resolution: {integrity: sha512-806REq/CLf37nEhmmd8Q+ILN8z/RVG2vk2n8YZ/4TdHpcBCi5ux4AxLbpMmduLwGPOzPagJ6ggRzE5fnX0rmcQ==} + peerDependencies: + '@angular/common': ^21.0.0 || ^22.0.0 + '@angular/core': ^21.0.0 || ^22.0.0 + '@angular/platform-browser': ^21.0.0 || ^22.0.0 + rxjs: ^6.5.3 || ^7.4.0 + '@angular/cli@20.3.31': resolution: {integrity: sha512-pqPhIKRBVlceAwB7KRpCwBhYBXSDQqdY0AYUVwHhhaTqOvzSZ38nWKQFlY/NUbdI1QGYb2ATJ2Bo9RJY/H+Mxg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -1634,6 +1654,28 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@primeuix/motion@0.0.10': + resolution: {integrity: sha512-PsZwOPq79Scp7/ionshRcQ5xKVf9+zuLcyY5mf6onK8chHT5C9JGphmcIZ4CzcqxuGEpsm8AIbTGy+zS3RtzLA==} + engines: {node: '>=12.11.0'} + + '@primeuix/styled@0.7.4': + resolution: {integrity: sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==} + engines: {node: '>=12.11.0'} + + '@primeuix/styles@2.0.3': + resolution: {integrity: sha512-2ykAB6BaHzR/6TwF8ShpJTsZrid6cVIEBVlookSdvOdmlWuevGu5vWOScgIwqWwlZcvkFYAGR/SUV3OHCTBMdw==} + + '@primeuix/themes@2.0.3': + resolution: {integrity: sha512-3fS1883mtCWhgUgNf/feiaaDSOND4EBIOu9tZnzJlJ8QtYyL6eFLcA6V3ymCWqLVXQ1+lTVEZv1gl47FIdXReg==} + + '@primeuix/utils@0.6.4': + resolution: {integrity: sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg==} + engines: {node: '>=12.11.0'} + + '@primeuix/utils@0.7.2': + resolution: {integrity: sha512-pmEbSfP0Phf9W9RweiM66zXnkn73ZeKyYINElbX3uZ2+stzzaba2svLAl3B1pHVcRw5t43O0VciaGe4ye2EXKw==} + engines: {node: '>=12.11.0'} + '@rolldown/binding-android-arm64@1.1.4': resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2201,6 +2243,9 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-dialog@2.7.1': + resolution: {integrity: sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==} + '@tauri-apps/plugin-opener@2.5.4': resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==} @@ -2539,6 +2584,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bootstrap-icons@1.13.1: + resolution: {integrity: sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==} + brace-expansion@2.1.1: resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} @@ -4021,6 +4069,17 @@ packages: resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==} engines: {node: '>=20'} + primeng@21.1.9: + resolution: {integrity: sha512-Z76PtF08X0PNSTCNMobao8Qm71vC56mtZSdUtX/gsn4+q4x0NbUUtkeK1pc33a+kxVm2zkmOwzUIEGmb9VdoIA==} + peerDependencies: + '@angular/cdk': ^21.0.0 + '@angular/common': ^21.0.0 + '@angular/core': ^21.0.7 + '@angular/forms': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/router': ^21.0.0 + rxjs: ^6.0.0 || ^7.8.1 + proc-log@5.0.0: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5354,6 +5413,15 @@ snapshots: - tsx - yaml + '@angular/cdk@21.2.14(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)) + parse5: 8.0.1 + rxjs: 7.8.2 + tslib: 2.8.1 + '@angular/cli@20.3.31(chokidar@4.0.3)': dependencies: '@angular-devkit/architect': 0.2003.31(chokidar@5.0.0) @@ -6391,6 +6459,26 @@ snapshots: '@poppinss/exception@1.2.3': {} + '@primeuix/motion@0.0.10': + dependencies: + '@primeuix/utils': 0.6.4 + + '@primeuix/styled@0.7.4': + dependencies: + '@primeuix/utils': 0.6.4 + + '@primeuix/styles@2.0.3': + dependencies: + '@primeuix/styled': 0.7.4 + + '@primeuix/themes@2.0.3': + dependencies: + '@primeuix/styled': 0.7.4 + + '@primeuix/utils@0.6.4': {} + + '@primeuix/utils@0.7.2': {} + '@rolldown/binding-android-arm64@1.1.4': optional: true @@ -6752,6 +6840,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 '@tauri-apps/cli-win32-x64-msvc': 2.11.4 + '@tauri-apps/plugin-dialog@2.7.1': + dependencies: + '@tauri-apps/api': 2.11.1 + '@tauri-apps/plugin-opener@2.5.4': dependencies: '@tauri-apps/api': 2.11.1 @@ -7170,6 +7262,8 @@ snapshots: boolbase@1.0.0: {} + bootstrap-icons@1.13.1: {} + brace-expansion@2.1.1: dependencies: balanced-match: 1.0.2 @@ -8810,6 +8904,21 @@ snapshots: pretty-bytes@7.1.0: {} + primeng@21.1.9(d7ad9d8080258453d12745bbca4bb7ff): + dependencies: + '@angular/cdk': 21.2.14(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/common': 20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/forms': 20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/platform-browser': 20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/router': 20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.25(@angular/common@20.3.25(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.25(@angular/compiler@20.3.25)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@primeuix/motion': 0.0.10 + '@primeuix/styled': 0.7.4 + '@primeuix/styles': 2.0.3 + '@primeuix/utils': 0.7.2 + rxjs: 7.8.2 + tslib: 2.8.1 + proc-log@5.0.0: {} proc-log@6.1.0: {} diff --git a/phenoxtract-ui/src-tauri/Cargo.toml b/phenoxtract-ui/src-tauri/Cargo.toml index 80f10ce6..116bbffa 100644 --- a/phenoxtract-ui/src-tauri/Cargo.toml +++ b/phenoxtract-ui/src-tauri/Cargo.toml @@ -22,4 +22,6 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" - +tauri-plugin-dialog = "2.7.1" +thiserror = "2.0.18" +directories = "6.0.0" diff --git a/phenoxtract-ui/src/app/app.component.html b/phenoxtract-ui/src/app/app.component.html index 1ca5a0b6..7dc6c958 100644 --- a/phenoxtract-ui/src/app/app.component.html +++ b/phenoxtract-ui/src/app/app.component.html @@ -1,21 +1,28 @@
-

Welcome to Tauri + Angular!

+

Welcome to Tauri + Angular!

- -

Click on the logos to learn more about the frameworks

+
+ + -
- - -
-

{{ greetingMessage }}

+ + + + + + +
+

Click on the logos to learn more about the frameworks

- +
+ + +
+

{{ greetingMessage }}

+ +
diff --git a/phenoxtract-ui/src/app/app.component.ts b/phenoxtract-ui/src/app/app.component.ts index 7e186a6b..df5ad436 100644 --- a/phenoxtract-ui/src/app/app.component.ts +++ b/phenoxtract-ui/src/app/app.component.ts @@ -1,22 +1,33 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; -import { invoke } from '@tauri-apps/api/core'; +import {Component} from '@angular/core'; +import {RouterOutlet} from '@angular/router'; +import {FormsModule} from '@angular/forms'; + +import {invoke} from '@tauri-apps/api/core'; + +import {FilePickerComponent} from './shared/components/file-picker/file-picker.component'; @Component({ - selector: 'app-root', - imports: [RouterOutlet], - templateUrl: './app.component.html', - styleUrl: './app.component.css', + selector: 'app-root', + standalone: true, + imports: [ + RouterOutlet, + FormsModule, + FilePickerComponent + ], + templateUrl: './app.component.html', + styleUrl: './app.component.css' }) export class AppComponent { - greetingMessage = ''; - greet(event: SubmitEvent, name: string): void { - event.preventDefault(); + greetingMessage = ''; + selectedFolder = ''; + + greet(event: SubmitEvent, name: string): void { + event.preventDefault(); + + invoke('greet', {name}).then((text) => { + this.greetingMessage = text; + }); + } - // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ - invoke('greet', { name }).then((text) => { - this.greetingMessage = text; - }); - } -} +} \ No newline at end of file diff --git a/phenoxtract-ui/src/app/app.config.ts b/phenoxtract-ui/src/app/app.config.ts index 414eeaeb..0c29893e 100644 --- a/phenoxtract-ui/src/app/app.config.ts +++ b/phenoxtract-ui/src/app/app.config.ts @@ -1,16 +1,23 @@ import { - ApplicationConfig, - provideBrowserGlobalErrorListeners, - provideZoneChangeDetection, + ApplicationConfig, + provideBrowserGlobalErrorListeners, + provideZoneChangeDetection, } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import {provideRouter} from '@angular/router'; -import { routes } from './app.routes'; +import {routes} from './app.routes'; +import {providePrimeNG} from "primeng/config"; +import Aura from '@primeuix/themes/aura'; export const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), - ], + providers: [ + provideBrowserGlobalErrorListeners(), + provideZoneChangeDetection({eventCoalescing: true}), + provideRouter(routes), + providePrimeNG({ + theme: { + preset: Aura + } + }) + ], }; diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html new file mode 100644 index 00000000..33b853c2 --- /dev/null +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss new file mode 100644 index 00000000..a7e35f00 --- /dev/null +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss @@ -0,0 +1,11 @@ +:host { + display: block; +} + +input { + height: 48px; +} + +button { + width: 48px; +} \ No newline at end of file diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts new file mode 100644 index 00000000..2fd1c569 --- /dev/null +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts @@ -0,0 +1,146 @@ +import {Component, Input, forwardRef} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; + +import {InputGroupModule} from 'primeng/inputgroup'; +import {InputGroupAddonModule} from 'primeng/inputgroupaddon'; +import {InputTextModule} from 'primeng/inputtext'; +import {ButtonModule} from 'primeng/button'; + +import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; + +/** + * A reusable text input with an integrated native Tauri file/directory picker. + * + * This component wraps a PrimeNG input field and opens the operating system's + * file or directory selection dialog using the Tauri dialog plugin. + * + * It implements `ControlValueAccessor`, making it compatible with both + * Angular Reactive Forms and `[(ngModel)]`. + * + * Features: + * - File or directory selection + * - Optional multi-selection + * - File type filtering + * - Editable text input + * - PrimeNG styling + * + * @example + * Template-driven forms + * ```html + * + * + * ``` + * + * @example + * Reactive Forms + * ```ts + * form = this.fb.group({ + * folder: [''] + * }); + * ``` + * + * ```html + *
+ * + * + *
+ * ``` + * + * @example + * Selecting a file + * ```html + * + * + * ``` + */ +@Component({ + selector: 'app-file-picker', + standalone: true, + imports: [ + CommonModule, + InputGroupModule, + InputGroupAddonModule, + InputTextModule, + ButtonModule + ], + templateUrl: './file-picker.component.html', + styleUrls: ['./file-picker.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FilePickerComponent), + multi: true + } + ] +}) +export class FilePickerComponent implements ControlValueAccessor { + + @Input() directory = false; + @Input() multiple = false; + @Input() placeholder = ''; + @Input() filters?: DialogFilter[]; + + value = ''; + + private onChange = (_: string) => { + }; + private onTouched = () => { + }; + + async triggerFileSelect() { + console.log("Button clicked"); + + try { + const selected = await open({ + directory: this.directory, + multiple: this.multiple, + filters: this.filters, + }); + + console.log(selected); + + if (selected !== null) { + this.value = selected as string; + this.onChange(this.value); + } + } catch (err) { + console.error(err); + } + } + + onInput(event: Event) { + this.value = (event.target as HTMLInputElement).value; + this.onChange(this.value); + } + + writeValue(value: string): void { + this.value = value ?? ''; + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(disabled: boolean): void { + } +} \ No newline at end of file diff --git a/phenoxtract-ui/src/styles.css b/phenoxtract-ui/src/styles.css index 90d4ee00..7b4f129b 100644 --- a/phenoxtract-ui/src/styles.css +++ b/phenoxtract-ui/src/styles.css @@ -1 +1,2 @@ /* You can add global styles to this file, and also import other style files */ +@import "bootstrap-icons/font/bootstrap-icons.css"; From 1e66901b53524ac7be04aea86760cfb37c7b638b Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:33:27 +0200 Subject: [PATCH 2/7] mirate backend --- phenoxtract-ui/src-tauri/Cargo.toml | 3 + .../src-tauri/capabilities/default.json | 3 +- phenoxtract-ui/src-tauri/icons/icon.ico | Bin 37710 -> 86642 bytes phenoxtract-ui/src-tauri/src/app_dirs.rs | 84 +++++++++++++++ .../src/app_state_persistence/app_state.rs | 96 ++++++++++++++++++ .../src/app_state_persistence/mod.rs | 2 + .../app_state_persistence/project_panel.rs | 16 +++ phenoxtract-ui/src-tauri/src/commands/mod.rs | 27 +++++ phenoxtract-ui/src-tauri/src/error.rs | 11 ++ phenoxtract-ui/src-tauri/src/lib.rs | 56 ++++++++-- phenoxtract-ui/src-tauri/src/types.rs | 4 + phenoxtract-ui/src-tauri/tauri.conf.json | 4 +- 12 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 phenoxtract-ui/src-tauri/src/app_dirs.rs create mode 100644 phenoxtract-ui/src-tauri/src/app_state_persistence/app_state.rs create mode 100644 phenoxtract-ui/src-tauri/src/app_state_persistence/mod.rs create mode 100644 phenoxtract-ui/src-tauri/src/app_state_persistence/project_panel.rs create mode 100644 phenoxtract-ui/src-tauri/src/commands/mod.rs create mode 100644 phenoxtract-ui/src-tauri/src/error.rs create mode 100644 phenoxtract-ui/src-tauri/src/types.rs diff --git a/phenoxtract-ui/src-tauri/Cargo.toml b/phenoxtract-ui/src-tauri/Cargo.toml index 116bbffa..1b1de6a0 100644 --- a/phenoxtract-ui/src-tauri/Cargo.toml +++ b/phenoxtract-ui/src-tauri/Cargo.toml @@ -25,3 +25,6 @@ serde_json = "1" tauri-plugin-dialog = "2.7.1" thiserror = "2.0.18" directories = "6.0.0" + +[dev-dependencies] +tempfile = "3.27.0" diff --git a/phenoxtract-ui/src-tauri/capabilities/default.json b/phenoxtract-ui/src-tauri/capabilities/default.json index 4cdbf49a..778bfb5a 100644 --- a/phenoxtract-ui/src-tauri/capabilities/default.json +++ b/phenoxtract-ui/src-tauri/capabilities/default.json @@ -5,6 +5,7 @@ "windows": ["main"], "permissions": [ "core:default", - "opener:default" + "opener:default", + "dialog:default" ] } diff --git a/phenoxtract-ui/src-tauri/icons/icon.ico b/phenoxtract-ui/src-tauri/icons/icon.ico index 06c23c82fea615c9394c4c7f487392e3f0a2fd4a..b3636e4b22ba65db9061cd60a77b02c92022dfd6 100644 GIT binary patch literal 86642 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 AKmY&$ literal 37710 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 Self { + Self { + config_dir, + _data_dir: data_dir, + _cache_dir: cache_dir, + } + } + pub fn config_dir(&self) -> &PathBuf { + &self.config_dir + } + pub fn _data_dir(&self) -> &PathBuf { + &self._data_dir + } + pub fn _cache_dir(&self) -> &PathBuf { + &self._cache_dir + } +} +pub fn init_app_dirs() -> Result { + let proj_dirs = ProjectDirs::from("com", "RobinsonLab", env!("CARGO_PKG_NAME")) + .expect("Could not determine project directories"); + + std::fs::create_dir_all(proj_dirs.config_dir()) + .map_err(|err| PhenoxtractBackendError::UnableToInitStateDirs(err.to_string()))?; + std::fs::create_dir_all(proj_dirs.data_dir()) + .map_err(|err| PhenoxtractBackendError::UnableToInitStateDirs(err.to_string()))?; + std::fs::create_dir_all(proj_dirs.cache_dir()) + .map_err(|err| PhenoxtractBackendError::UnableToInitStateDirs(err.to_string()))?; + + Ok(AppDirs::new( + proj_dirs.config_dir().to_path_buf(), + proj_dirs.data_dir().to_path_buf(), + proj_dirs.cache_dir().to_path_buf(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_app_dirs_new_and_getters() { + let mock_config = PathBuf::from("/mock/config/path"); + let mock_data = PathBuf::from("/mock/data/path"); + let mock_cache = PathBuf::from("/mock/cache/path"); + + let app_dirs = AppDirs::new(mock_config.clone(), mock_data.clone(), mock_cache.clone()); + + assert_eq!(app_dirs.config_dir(), &mock_config); + assert_eq!(app_dirs._data_dir(), &mock_data); + assert_eq!(app_dirs._cache_dir(), &mock_cache); + } + + #[test] + fn test_init_app_dirs_succeeds() { + let result = init_app_dirs(); + + assert!(result.is_ok(), "Failed to initialize app directories"); + + let dirs = result.unwrap(); + + assert!( + dirs.config_dir().exists(), + "Config directory was not created" + ); + assert!(dirs._data_dir().exists(), "Data directory was not created"); + assert!( + dirs._cache_dir().exists(), + "Cache directory was not created" + ); + } +} diff --git a/phenoxtract-ui/src-tauri/src/app_state_persistence/app_state.rs b/phenoxtract-ui/src-tauri/src/app_state_persistence/app_state.rs new file mode 100644 index 00000000..cbd914fa --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/app_state_persistence/app_state.rs @@ -0,0 +1,96 @@ +use crate::app_state_persistence::project_panel::ProjectPanel; +use crate::error::PhenoxtractBackendError; +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; +use std::path::Path; + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct AppState { + pub project_panels: VecDeque, +} + +impl AppState { + pub fn save(&self, path: &Path) -> Result<(), PhenoxtractBackendError> { + let path = path.join("app_state.json"); + + let file = std::fs::File::create(path) + .map_err(|err| PhenoxtractBackendError::CantWriteAppState(err.to_string()))?; + serde_json::to_writer_pretty(&file, &self) + .map_err(|err| PhenoxtractBackendError::CantWriteAppState(err.to_string())) + } + + pub fn load(path: &Path) -> Result { + let path = path.join("app_state.json"); + + if path.exists() { + let state_str = std::fs::read_to_string(path) + .map_err(|err| PhenoxtractBackendError::CantReadAppState(err.to_string()))?; + serde_json::from_str(&state_str) + .map_err(|err| PhenoxtractBackendError::CantReadAppState(err.to_string())) + } else { + Ok(Self::default()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app_state_persistence::app_state::AppState; + use std::fs; + use tempfile::tempdir; + + #[test] + fn test_load_non_existent_file_returns_default() { + let dir = tempdir().expect("Failed to create temp dir"); + + let result = AppState::load(dir.path()); + + assert!(result.is_ok()); + let state = result.unwrap(); + assert!(state.project_panels.is_empty()); + } + + #[test] + fn test_save_and_load_roundtrip() { + let dir = tempdir().expect("Failed to create temp dir"); + let state = AppState::default(); + + state.save(dir.path()).expect("Failed to save state"); + let loaded_state = AppState::load(dir.path()).expect("Failed to load state"); + + assert_eq!( + state.project_panels.len(), + loaded_state.project_panels.len() + ); + } + + #[test] + fn test_save_to_invalid_path_returns_error() { + let state = AppState::default(); + let bad_path = Path::new("/some/non/existent/path/that/should/fail"); + + let result = state.save(bad_path); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + PhenoxtractBackendError::CantWriteAppState(_) + )); + } + + #[test] + fn test_load_malformed_json_returns_error() { + let dir = tempdir().expect("Failed to create temp dir"); + let file_path = dir.path().join("app_state.json"); + fs::write(&file_path, "{ this is not valid json ]").expect("Failed to write garbage file"); + + let result = AppState::load(dir.path()); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + PhenoxtractBackendError::CantReadAppState(_) + )); + } +} diff --git a/phenoxtract-ui/src-tauri/src/app_state_persistence/mod.rs b/phenoxtract-ui/src-tauri/src/app_state_persistence/mod.rs new file mode 100644 index 00000000..bb5bf7f5 --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/app_state_persistence/mod.rs @@ -0,0 +1,2 @@ +pub mod app_state; +pub mod project_panel; diff --git a/phenoxtract-ui/src-tauri/src/app_state_persistence/project_panel.rs b/phenoxtract-ui/src-tauri/src/app_state_persistence/project_panel.rs new file mode 100644 index 00000000..5920c1d7 --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/app_state_persistence/project_panel.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ProjectPanel { + pub name: String, + pub directory: String, +} + +impl ProjectPanel { + pub fn new(name: impl Into, directory: impl Into) -> Self { + Self { + name: name.into(), + directory: directory.into(), + } + } +} diff --git a/phenoxtract-ui/src-tauri/src/commands/mod.rs b/phenoxtract-ui/src-tauri/src/commands/mod.rs new file mode 100644 index 00000000..11374b84 --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/commands/mod.rs @@ -0,0 +1,27 @@ +use crate::app_state_persistence::project_panel::ProjectPanel; +use crate::types::SharedAppState; +use std::collections::VecDeque; + +#[tauri::command] +pub fn get_version() -> String { + env!("CARGO_PKG_VERSION").to_string() +} + +#[tauri::command] +pub fn get_project_panels( + state: tauri::State, +) -> Result, String> { + let app_state = state.read().map_err(|e| e.to_string())?; + Ok(app_state.project_panels.clone()) +} + +#[tauri::command] +pub fn post_project_panel( + project_panels: ProjectPanel, + state: tauri::State, +) -> Result<(), String> { + let mut app_state = state.write().map_err(|e| e.to_string())?; + + app_state.project_panels.push_front(project_panels); + Ok(()) +} diff --git a/phenoxtract-ui/src-tauri/src/error.rs b/phenoxtract-ui/src-tauri/src/error.rs new file mode 100644 index 00000000..e5390b06 --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PhenoxtractBackendError { + #[error("Cant load App State: {0}.")] + CantReadAppState(String), + #[error("Cant save App State: {0}.")] + CantWriteAppState(String), + #[error("Unable to initialize app directories: {0}")] + UnableToInitStateDirs(String), +} diff --git a/phenoxtract-ui/src-tauri/src/lib.rs b/phenoxtract-ui/src-tauri/src/lib.rs index 4a277ef3..8407e0cd 100644 --- a/phenoxtract-ui/src-tauri/src/lib.rs +++ b/phenoxtract-ui/src-tauri/src/lib.rs @@ -1,14 +1,54 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} +use crate::app_dirs::{init_app_dirs, AppDirs}; +use crate::app_state_persistence::app_state::AppState; +use crate::app_state_persistence::project_panel::ProjectPanel; +use crate::commands::{get_project_panels, get_version, post_project_panel}; +use std::sync::{Arc, RwLock}; +use tauri::Manager; + +mod app_dirs; +mod app_state_persistence; +mod commands; +mod error; +mod types; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + let dummy_panels = vec![ + ProjectPanel::new("Immunology Data", "~/projects/my-project"), + ProjectPanel::new("prechter_data_analysis", "~/projects/my-project"), + ProjectPanel::new("acuteKidneyInjury", "~/projects/my-project"), + ]; + + let app_dirs = init_app_dirs().expect("Could not determine application directories"); + + let mut state = AppState::load(app_dirs.config_dir()).expect("Could not load app state"); + + state.project_panels = dummy_panels.into_iter().collect(); + tauri::Builder::default() + .manage(Arc::new(RwLock::new(state))) + .manage(app_dirs) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .plugin(tauri_plugin_dialog::init()) + .invoke_handler(tauri::generate_handler![ + get_version, + get_project_panels, + post_project_panel + ]) + .build(tauri::generate_context!()) + .expect("error while building tauri application") + .run(|app_handle, event| match event { + tauri::RunEvent::ExitRequested { .. } => { + let app_dirs = app_handle.state::(); + let state_manager = app_handle.state::>>(); + let state = state_manager.read().expect("Could not read state"); + state + .save(app_dirs.config_dir()) + .expect("Could not save state"); + } + tauri::RunEvent::Exit => { + // cleanup, app is exiting, can't be stopped here + } + _ => {} + }); } diff --git a/phenoxtract-ui/src-tauri/src/types.rs b/phenoxtract-ui/src-tauri/src/types.rs new file mode 100644 index 00000000..9778963e --- /dev/null +++ b/phenoxtract-ui/src-tauri/src/types.rs @@ -0,0 +1,4 @@ +use crate::app_state_persistence::app_state::AppState; +use std::sync::{Arc, RwLock}; + +pub type SharedAppState = Arc>; diff --git a/phenoxtract-ui/src-tauri/tauri.conf.json b/phenoxtract-ui/src-tauri/tauri.conf.json index 260fd6b2..ba88c376 100644 --- a/phenoxtract-ui/src-tauri/tauri.conf.json +++ b/phenoxtract-ui/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "phenoxtract-ui", - "width": 800, - "height": 600 + "width": 1280, + "height": 720 } ], "security": { From d1e60ba4996ec19ae6137e87e35560455f1141a1 Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:36:52 +0200 Subject: [PATCH 3/7] formatting --- phenoxtract-ui/src/app/app.component.html | 45 ++++--- phenoxtract-ui/src/app/app.component.ts | 44 +++--- phenoxtract-ui/src/app/app.config.ts | 32 ++--- .../file-picker/file-picker.component.html | 13 +- .../file-picker/file-picker.component.scss | 2 +- .../file-picker/file-picker.component.ts | 126 ++++++++---------- 6 files changed, 120 insertions(+), 142 deletions(-) diff --git a/phenoxtract-ui/src/app/app.component.html b/phenoxtract-ui/src/app/app.component.html index 7dc6c958..36d60322 100644 --- a/phenoxtract-ui/src/app/app.component.html +++ b/phenoxtract-ui/src/app/app.component.html @@ -1,28 +1,29 @@ diff --git a/phenoxtract-ui/src/app/app.component.ts b/phenoxtract-ui/src/app/app.component.ts index df5ad436..ac6ca5cf 100644 --- a/phenoxtract-ui/src/app/app.component.ts +++ b/phenoxtract-ui/src/app/app.component.ts @@ -1,33 +1,27 @@ -import {Component} from '@angular/core'; -import {RouterOutlet} from '@angular/router'; -import {FormsModule} from '@angular/forms'; +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { FormsModule } from '@angular/forms'; -import {invoke} from '@tauri-apps/api/core'; +import { invoke } from '@tauri-apps/api/core'; -import {FilePickerComponent} from './shared/components/file-picker/file-picker.component'; +import { FilePickerComponent } from './shared/components/file-picker/file-picker.component'; @Component({ - selector: 'app-root', - standalone: true, - imports: [ - RouterOutlet, - FormsModule, - FilePickerComponent - ], - templateUrl: './app.component.html', - styleUrl: './app.component.css' + selector: 'app-root', + standalone: true, + imports: [RouterOutlet, FormsModule, FilePickerComponent], + templateUrl: './app.component.html', + styleUrl: './app.component.css', }) export class AppComponent { + greetingMessage = ''; + selectedFolder = ''; - greetingMessage = ''; - selectedFolder = ''; + greet(event: SubmitEvent, name: string): void { + event.preventDefault(); - greet(event: SubmitEvent, name: string): void { - event.preventDefault(); - - invoke('greet', {name}).then((text) => { - this.greetingMessage = text; - }); - } - -} \ No newline at end of file + invoke('greet', { name }).then((text) => { + this.greetingMessage = text; + }); + } +} diff --git a/phenoxtract-ui/src/app/app.config.ts b/phenoxtract-ui/src/app/app.config.ts index 0c29893e..ef061ed4 100644 --- a/phenoxtract-ui/src/app/app.config.ts +++ b/phenoxtract-ui/src/app/app.config.ts @@ -1,23 +1,23 @@ import { - ApplicationConfig, - provideBrowserGlobalErrorListeners, - provideZoneChangeDetection, + ApplicationConfig, + provideBrowserGlobalErrorListeners, + provideZoneChangeDetection, } from '@angular/core'; -import {provideRouter} from '@angular/router'; +import { provideRouter } from '@angular/router'; -import {routes} from './app.routes'; -import {providePrimeNG} from "primeng/config"; +import { routes } from './app.routes'; +import { providePrimeNG } from 'primeng/config'; import Aura from '@primeuix/themes/aura'; export const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - provideZoneChangeDetection({eventCoalescing: true}), - provideRouter(routes), - providePrimeNG({ - theme: { - preset: Aura - } - }) - ], + providers: [ + provideBrowserGlobalErrorListeners(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + providePrimeNG({ + theme: { + preset: Aura, + }, + }), + ], }; diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html index 33b853c2..9f949a6c 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.html @@ -1,12 +1,5 @@ - + - - - \ No newline at end of file + + diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss index a7e35f00..2b5a6322 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.scss @@ -8,4 +8,4 @@ input { button { width: 48px; -} \ No newline at end of file +} diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts index 2fd1c569..c0267e5c 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts @@ -1,13 +1,13 @@ -import {Component, Input, forwardRef} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import { Component, Input, forwardRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import {InputGroupModule} from 'primeng/inputgroup'; -import {InputGroupAddonModule} from 'primeng/inputgroupaddon'; -import {InputTextModule} from 'primeng/inputtext'; -import {ButtonModule} from 'primeng/button'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; +import { ButtonModule } from 'primeng/button'; -import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; +import { open, DialogFilter } from '@tauri-apps/plugin-dialog'; /** * A reusable text input with an integrated native Tauri file/directory picker. @@ -70,77 +70,67 @@ import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; * ``` */ @Component({ - selector: 'app-file-picker', - standalone: true, - imports: [ - CommonModule, - InputGroupModule, - InputGroupAddonModule, - InputTextModule, - ButtonModule - ], - templateUrl: './file-picker.component.html', - styleUrls: ['./file-picker.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => FilePickerComponent), - multi: true - } - ] + selector: 'app-file-picker', + standalone: true, + imports: [CommonModule, InputGroupModule, InputGroupAddonModule, InputTextModule, ButtonModule], + templateUrl: './file-picker.component.html', + styleUrls: ['./file-picker.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FilePickerComponent), + multi: true, + }, + ], }) export class FilePickerComponent implements ControlValueAccessor { + @Input() directory = false; + @Input() multiple = false; + @Input() placeholder = ''; + @Input() filters?: DialogFilter[]; - @Input() directory = false; - @Input() multiple = false; - @Input() placeholder = ''; - @Input() filters?: DialogFilter[]; + value = ''; - value = ''; + private onChange = (_: string) => {}; + private onTouched = () => {}; - private onChange = (_: string) => { - }; - private onTouched = () => { - }; + async triggerFileSelect() { + console.log('Button clicked'); - async triggerFileSelect() { - console.log("Button clicked"); + try { + const selected = await open({ + directory: this.directory, + multiple: this.multiple, + filters: this.filters, + }); - try { - const selected = await open({ - directory: this.directory, - multiple: this.multiple, - filters: this.filters, - }); + console.log(selected); - console.log(selected); - - if (selected !== null) { - this.value = selected as string; - this.onChange(this.value); - } - } catch (err) { - console.error(err); - } - } - - onInput(event: Event) { - this.value = (event.target as HTMLInputElement).value; + if (selected !== null) { + this.value = selected as string; this.onChange(this.value); + } + } catch (err) { + console.error(err); } + } - writeValue(value: string): void { - this.value = value ?? ''; - } + onInput(event: Event) { + this.value = (event.target as HTMLInputElement).value; + this.onChange(this.value); + } - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } + writeValue(value: string): void { + this.value = value ?? ''; + } - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - setDisabledState(disabled: boolean): void { - } -} \ No newline at end of file + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(disabled: boolean): void {} +} From e72da129c0e05ae0a3d86ac991110f76cee0d323 Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:38:04 +0200 Subject: [PATCH 4/7] Remove svelte --- phenoxtract-ui/.svelte-kit/ambient.d.ts | 256 ------------------ phenoxtract-ui/.svelte-kit/env.d.ts | 1 - .../.svelte-kit/generated/client/app.js | 43 --- .../.svelte-kit/generated/client/matchers.js | 1 - .../.svelte-kit/generated/client/nodes/0.js | 3 - .../.svelte-kit/generated/client/nodes/1.js | 1 - .../.svelte-kit/generated/client/nodes/2.js | 1 - .../.svelte-kit/generated/client/nodes/3.js | 1 - .../.svelte-kit/generated/client/nodes/4.js | 1 - phenoxtract-ui/.svelte-kit/generated/root.js | 3 - .../.svelte-kit/generated/root.svelte | 68 ----- .../.svelte-kit/generated/server/internal.js | 55 ---- .../generated/shared/error-template.js | 1 - phenoxtract-ui/.svelte-kit/non-ambient.d.ts | 45 --- phenoxtract-ui/.svelte-kit/tsconfig.json | 50 ---- .../.svelte-kit/types/route_meta_data.json | 12 - .../.svelte-kit/types/src/routes/$types.d.ts | 25 -- .../src/routes/data_sources_csv/$types.d.ts | 17 -- .../src/routes/data_sources_excel/$types.d.ts | 17 -- 19 files changed, 601 deletions(-) delete mode 100644 phenoxtract-ui/.svelte-kit/ambient.d.ts delete mode 100644 phenoxtract-ui/.svelte-kit/env.d.ts delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/app.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/matchers.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/root.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/root.svelte delete mode 100644 phenoxtract-ui/.svelte-kit/generated/server/internal.js delete mode 100644 phenoxtract-ui/.svelte-kit/generated/shared/error-template.js delete mode 100644 phenoxtract-ui/.svelte-kit/non-ambient.d.ts delete mode 100644 phenoxtract-ui/.svelte-kit/tsconfig.json delete mode 100644 phenoxtract-ui/.svelte-kit/types/route_meta_data.json delete mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts delete mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts delete mode 100644 phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts diff --git a/phenoxtract-ui/.svelte-kit/ambient.d.ts b/phenoxtract-ui/.svelte-kit/ambient.d.ts deleted file mode 100644 index bc2664bd..00000000 --- a/phenoxtract-ui/.svelte-kit/ambient.d.ts +++ /dev/null @@ -1,256 +0,0 @@ - -// this file is generated — do not edit it - - -/// - -/** - * This module provides access to environment variables that are injected _statically_ into your bundle at build time and are limited to _private_ access. - * - * | | Runtime | Build time | - * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | - * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | - * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | - * - * Static environment variables are [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env` at build time and then statically injected into your bundle at build time, enabling optimisations like dead code elimination. - * - * **_Private_ access:** - * - * - This module cannot be imported into client-side code - * - This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured) - * - * For example, given the following build time environment: - * - * ```env - * ENVIRONMENT=production - * PUBLIC_BASE_URL=http://site.com - * ``` - * - * With the default `publicPrefix` and `privatePrefix`: - * - * ```ts - * import { ENVIRONMENT, PUBLIC_BASE_URL } from '$env/static/private'; - * - * console.log(ENVIRONMENT); // => "production" - * console.log(PUBLIC_BASE_URL); // => throws error during build - * ``` - * - * The above values will be the same _even if_ different values for `ENVIRONMENT` or `PUBLIC_BASE_URL` are set at runtime, as they are statically replaced in your code with their build time values. - */ -declare module '$env/static/private' { - export const HOMEBREW_PREFIX: string; - export const CONDA_PROMPT_MODIFIER: string; - export const COMMAND_MODE: string; - export const INFOPATH: string; - export const SHELL: string; - export const __CFBundleIdentifier: string; - export const TMPDIR: string; - export const DEBUG_COLORS: string; - export const COLORTERM: string; - export const HOME: string; - export const FORCE_COLOR: string; - export const HOMEBREW_REPOSITORY: string; - export const PATH: string; - export const CONDA_PREFIX: string; - export const LOGNAME: string; - export const XPC_FLAGS: string; - export const __CF_USER_TEXT_ENCODING: string; - export const MOCHA_COLORS: string; - export const PQ_LIB_DIR: string; - export const NVM_CD_FLAGS: string; - export const NVM_DIR: string; - export const LC_CTYPE: string; - export const CONDA_SHLVL: string; - export const CONDA_EXE: string; - export const npm_config_color: string; - export const SSH_AUTH_SOCK: string; - export const OLDPWD: string; - export const CONDA_DEFAULT_ENV: string; - export const XPC_SERVICE_NAME: string; - export const USER: string; - export const HOMEBREW_CELLAR: string; - export const PWD: string; - export const CONDA_PYTHON_EXE: string; - export const PNPM_HOME: string; - export const FPATH: string; - export const NODE_ENV: string; -} - -/** - * This module provides access to environment variables that are injected _statically_ into your bundle at build time and are _publicly_ accessible. - * - * | | Runtime | Build time | - * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | - * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | - * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | - * - * Static environment variables are [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env` at build time and then statically injected into your bundle at build time, enabling optimisations like dead code elimination. - * - * **_Public_ access:** - * - * - This module _can_ be imported into client-side code - * - **Only** variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`) are included - * - * For example, given the following build time environment: - * - * ```env - * ENVIRONMENT=production - * PUBLIC_BASE_URL=http://site.com - * ``` - * - * With the default `publicPrefix` and `privatePrefix`: - * - * ```ts - * import { ENVIRONMENT, PUBLIC_BASE_URL } from '$env/static/public'; - * - * console.log(ENVIRONMENT); // => throws error during build - * console.log(PUBLIC_BASE_URL); // => "http://site.com" - * ``` - * - * The above values will be the same _even if_ different values for `ENVIRONMENT` or `PUBLIC_BASE_URL` are set at runtime, as they are statically replaced in your code with their build time values. - */ -declare module '$env/static/public' { - -} - -/** - * This module provides access to environment variables set _dynamically_ at runtime and that are limited to _private_ access. - * - * | | Runtime | Build time | - * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | - * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | - * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | - * - * Dynamic environment variables are defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. - * - * **_Private_ access:** - * - * - This module cannot be imported into client-side code - * - This module includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured) - * - * > [!NOTE] In `dev`, `$env/dynamic` includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter. - * - * > [!NOTE] To get correct types, environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed: - * > - * > ```env - * > MY_FEATURE_FLAG= - * > ``` - * > - * > You can override `.env` values from the command line like so: - * > - * > ```sh - * > MY_FEATURE_FLAG="enabled" npm run dev - * > ``` - * - * For example, given the following runtime environment: - * - * ```env - * ENVIRONMENT=production - * PUBLIC_BASE_URL=http://site.com - * ``` - * - * With the default `publicPrefix` and `privatePrefix`: - * - * ```ts - * import { env } from '$env/dynamic/private'; - * - * console.log(env.ENVIRONMENT); // => "production" - * console.log(env.PUBLIC_BASE_URL); // => undefined - * ``` - */ -declare module '$env/dynamic/private' { - export const env: { - HOMEBREW_PREFIX: string; - CONDA_PROMPT_MODIFIER: string; - COMMAND_MODE: string; - INFOPATH: string; - SHELL: string; - __CFBundleIdentifier: string; - TMPDIR: string; - DEBUG_COLORS: string; - COLORTERM: string; - HOME: string; - FORCE_COLOR: string; - HOMEBREW_REPOSITORY: string; - PATH: string; - CONDA_PREFIX: string; - LOGNAME: string; - XPC_FLAGS: string; - __CF_USER_TEXT_ENCODING: string; - MOCHA_COLORS: string; - PQ_LIB_DIR: string; - NVM_CD_FLAGS: string; - NVM_DIR: string; - LC_CTYPE: string; - CONDA_SHLVL: string; - CONDA_EXE: string; - npm_config_color: string; - SSH_AUTH_SOCK: string; - OLDPWD: string; - CONDA_DEFAULT_ENV: string; - XPC_SERVICE_NAME: string; - USER: string; - HOMEBREW_CELLAR: string; - PWD: string; - CONDA_PYTHON_EXE: string; - PNPM_HOME: string; - FPATH: string; - NODE_ENV: string; - [key: `PUBLIC_${string}`]: undefined; - [key: `${string}`]: string | undefined; - } -} - -/** - * This module provides access to environment variables set _dynamically_ at runtime and that are _publicly_ accessible. - * - * | | Runtime | Build time | - * | ------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | - * | Private | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | - * | Public | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | - * - * Dynamic environment variables are defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. - * - * **_Public_ access:** - * - * - This module _can_ be imported into client-side code - * - **Only** variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`) are included - * - * > [!NOTE] In `dev`, `$env/dynamic` includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter. - * - * > [!NOTE] To get correct types, environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed: - * > - * > ```env - * > MY_FEATURE_FLAG= - * > ``` - * > - * > You can override `.env` values from the command line like so: - * > - * > ```sh - * > MY_FEATURE_FLAG="enabled" npm run dev - * > ``` - * - * For example, given the following runtime environment: - * - * ```env - * ENVIRONMENT=production - * PUBLIC_BASE_URL=http://example.com - * ``` - * - * With the default `publicPrefix` and `privatePrefix`: - * - * ```ts - * import { env } from '$env/dynamic/public'; - * console.log(env.ENVIRONMENT); // => undefined, not public - * console.log(env.PUBLIC_BASE_URL); // => "http://example.com" - * ``` - * - * ``` - * - * ``` - */ -declare module '$env/dynamic/public' { - export const env: { - [key: `PUBLIC_${string}`]: string | undefined; - } -} diff --git a/phenoxtract-ui/.svelte-kit/env.d.ts b/phenoxtract-ui/.svelte-kit/env.d.ts deleted file mode 100644 index d7f6c977..00000000 --- a/phenoxtract-ui/.svelte-kit/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -// See https://svelte.dev/docs/kit/environment-variables for more information \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/app.js b/phenoxtract-ui/.svelte-kit/generated/client/app.js deleted file mode 100644 index 402cdde5..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/app.js +++ /dev/null @@ -1,43 +0,0 @@ -// in dev, this makes Vite inject its client as this module's first dependency, -// so that global constant replacements are installed before any other module -// (including user hooks) evaluates. In build it's inert. -import.meta.hot; - - - - -export { matchers } from './matchers.js'; - -export const nodes = [ - () => import('./nodes/0'), - () => import('./nodes/1'), - () => import('./nodes/2'), - () => import('./nodes/3'), - () => import('./nodes/4') -]; - -export const server_loads = []; - -export const dictionary = { - "/": [2], - "/data_sources_csv": [3], - "/data_sources_excel": [4] - }; - -export const hooks = { - handleError: (({ error }) => { console.error(error) }), - - reroute: (() => {}), - transport: {} -}; - -export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode])); -export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode])); - -export const hash = false; - -export const decode = (type, value) => decoders[type](value); - -export { default as root } from '../root.js'; - -export const get_error_template = () => import('../shared/error-template.js').then(m => m.default); \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/matchers.js b/phenoxtract-ui/.svelte-kit/generated/client/matchers.js deleted file mode 100644 index f6bd30a4..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/matchers.js +++ /dev/null @@ -1 +0,0 @@ -export const matchers = {}; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js deleted file mode 100644 index b2e56b2b..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/nodes/0.js +++ /dev/null @@ -1,3 +0,0 @@ -import * as universal from "../../../../src/routes/+layout.ts"; -export { universal }; -export { default as component } from "../../../../src/routes/+layout.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js deleted file mode 100644 index c2070619..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/nodes/1.js +++ /dev/null @@ -1 +0,0 @@ -export { default as component } from "../../../../node_modules/.pnpm/@sveltejs+kit@2.67.0_@sveltejs+vite-plugin-svelte@5.1.1_svelte@5.56.4_@typescript-eslin_b3bf91c24becebc0cdee269fdc32be1c/node_modules/@sveltejs/kit/src/runtime/components/svelte-5/error.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js deleted file mode 100644 index 1cb4f855..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/nodes/2.js +++ /dev/null @@ -1 +0,0 @@ -export { default as component } from "../../../../src/routes/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js deleted file mode 100644 index 9e834e30..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/nodes/3.js +++ /dev/null @@ -1 +0,0 @@ -export { default as component } from "../../../../src/routes/data_sources_csv/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js b/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js deleted file mode 100644 index f6c261ed..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/client/nodes/4.js +++ /dev/null @@ -1 +0,0 @@ -export { default as component } from "../../../../src/routes/data_sources_excel/+page.svelte"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/root.js b/phenoxtract-ui/.svelte-kit/generated/root.js deleted file mode 100644 index 4d1e8929..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/root.js +++ /dev/null @@ -1,3 +0,0 @@ -import { asClassComponent } from 'svelte/legacy'; -import Root from './root.svelte'; -export default asClassComponent(Root); \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/root.svelte b/phenoxtract-ui/.svelte-kit/generated/root.svelte deleted file mode 100644 index aef744aa..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/root.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - - - -{#if constructors[1]} - {@const Pyramid_0 = constructors[0]} - - - - - - -{:else} - {@const Pyramid_0 = constructors[0]} - - - -{/if} - -{#if mounted} -
- {#if navigated} - {title} - {/if} -
-{/if} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/generated/server/internal.js b/phenoxtract-ui/.svelte-kit/generated/server/internal.js deleted file mode 100644 index 03660f19..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/server/internal.js +++ /dev/null @@ -1,55 +0,0 @@ - -import root from '../root.js'; -import { set_building, set_prerendering } from '$app/env/internal'; -import { set_assets } from '$app/paths/internal/server'; -import { set_manifest, set_read_implementation } from '__sveltekit/server'; -import { set_private_env, set_public_env } from '../../../node_modules/.pnpm/@sveltejs+kit@2.67.0_@sveltejs+vite-plugin-svelte@5.1.1_svelte@5.56.4_@typescript-eslin_b3bf91c24becebc0cdee269fdc32be1c/node_modules/@sveltejs/kit/src/runtime/shared-server.js'; -import error from '../shared/error-template.js'; - -export const options = { - app_template_contains_nonce: false, - async: false, - csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}}, - csrf_check_origin: true, - csrf_trusted_origins: [], - embedded: false, - env_public_prefix: 'PUBLIC_', - env_private_prefix: '', - hash_routing: false, - hooks: null, // added lazily, via `get_hooks` - preload_strategy: "modulepreload", - root, - service_worker: false, - service_worker_options: undefined, - server_error_boundaries: false, - templates: { - app: ({ head, body, assets, nonce, env }) => "\n\n \n \n \n \n Tauri + SvelteKit + Typescript App\n " + head + "\n \n \n
" + body + "
\n \n\n", - error - }, - version_hash: "pcodto" -}; - -export async function get_hooks() { - let handle; - let handleFetch; - let handleError; - let handleValidationError; - let init; - - - let reroute; - let transport; - - - return { - handle, - handleFetch, - handleError, - handleValidationError, - init, - reroute, - transport - }; -} - -export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation }; diff --git a/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js b/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js deleted file mode 100644 index f66e5957..00000000 --- a/phenoxtract-ui/.svelte-kit/generated/shared/error-template.js +++ /dev/null @@ -1 +0,0 @@ -export default ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t
\n\t\t\t" + status + "\n\t\t\t
\n\t\t\t\t

" + message + "

\n\t\t\t
\n\t\t
\n\t\n\n"; \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/non-ambient.d.ts b/phenoxtract-ui/.svelte-kit/non-ambient.d.ts deleted file mode 100644 index 14074456..00000000 --- a/phenoxtract-ui/.svelte-kit/non-ambient.d.ts +++ /dev/null @@ -1,45 +0,0 @@ - -// this file is generated — do not edit it - - -declare module "svelte/elements" { - export interface HTMLAttributes { - 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; - 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; - 'data-sveltekit-preload-code'?: - | true - | '' - | 'eager' - | 'viewport' - | 'hover' - | 'tap' - | 'off' - | undefined - | null; - 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; - 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; - 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; - } -} - -export {}; - - -declare module "$app/types" { - type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; - - export interface AppTypes { - RouteId(): "/" | "/data_sources_csv" | "/data_sources_excel"; - RouteParams(): { - - }; - LayoutParams(): { - "/": Record; - "/data_sources_csv": Record; - "/data_sources_excel": Record - }; - Pathname(): "/" | "/data_sources_csv" | "/data_sources_excel"; - ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`; - Asset(): "/favicon.png" | "/svelte.svg" | "/tauri.svg" | "/vite.svg" | string & {}; - } -} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/tsconfig.json b/phenoxtract-ui/.svelte-kit/tsconfig.json deleted file mode 100644 index f727b03a..00000000 --- a/phenoxtract-ui/.svelte-kit/tsconfig.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "$app/types": [ - "./types/index.d.ts" - ] - }, - "rootDirs": [ - "..", - "./types" - ], - "verbatimModuleSyntax": true, - "isolatedModules": true, - "lib": [ - "esnext", - "DOM", - "DOM.Iterable" - ], - "moduleResolution": "bundler", - "module": "esnext", - "noEmit": true, - "target": "esnext" - }, - "include": [ - "ambient.d.ts", - "env.d.ts", - "non-ambient.d.ts", - "./types/**/$types.d.ts", - "../vite.config.js", - "../vite.config.ts", - "../src/**/*.js", - "../src/**/*.ts", - "../src/**/*.svelte", - "../test/**/*.js", - "../test/**/*.ts", - "../test/**/*.svelte", - "../tests/**/*.js", - "../tests/**/*.ts", - "../tests/**/*.svelte" - ], - "exclude": [ - "../node_modules/**", - "../src/service-worker.js", - "../src/service-worker/**/*.js", - "../src/service-worker.ts", - "../src/service-worker/**/*.ts", - "../src/service-worker.d.ts", - "../src/service-worker/**/*.d.ts" - ] -} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/route_meta_data.json b/phenoxtract-ui/.svelte-kit/types/route_meta_data.json deleted file mode 100644 index aa2ddaa1..00000000 --- a/phenoxtract-ui/.svelte-kit/types/route_meta_data.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "/": [ - "src/routes/+layout.ts", - "src/routes/+layout.ts" - ], - "/data_sources_csv": [ - "src/routes/+layout.ts" - ], - "/data_sources_excel": [ - "src/routes/+layout.ts" - ] -} \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts deleted file mode 100644 index 4bdb9997..00000000 --- a/phenoxtract-ui/.svelte-kit/types/src/routes/$types.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type * as Kit from '@sveltejs/kit'; - -type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; -type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; -type RouteParams = { }; -type RouteId = '/'; -type MaybeWithVoid = {} extends T ? T | void : T; -export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; -type OutputDataShape = MaybeWithVoid> & Partial> & Record> -type EnsureDefined = T extends null | undefined ? {} : T; -type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; -export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; -type LayoutRouteId = RouteId | "/" | "/data_sources_csv" | "/data_sources_excel" | null -type LayoutParams = RouteParams & { } -type LayoutParentData = EnsureDefined<{}>; - -export type PageServerData = null; -export type PageData = Expand; -export type PageProps = { params: RouteParams; data: PageData } -export type LayoutServerData = null; -export type LayoutLoad = OutputDataShape> = Kit.Load; -export type LayoutLoadEvent = Parameters[0]; -export type LayoutData = Expand> & OptionalUnion>>>; -export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet } \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts deleted file mode 100644 index ec7bd38e..00000000 --- a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_csv/$types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type * as Kit from '@sveltejs/kit'; - -type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; -type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; -type RouteParams = { }; -type RouteId = '/data_sources_csv'; -type MaybeWithVoid = {} extends T ? T | void : T; -export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; -type OutputDataShape = MaybeWithVoid> & Partial> & Record> -type EnsureDefined = T extends null | undefined ? {} : T; -type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; -export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; - -export type PageServerData = null; -export type PageData = Expand; -export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts b/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts deleted file mode 100644 index c8467e2d..00000000 --- a/phenoxtract-ui/.svelte-kit/types/src/routes/data_sources_excel/$types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type * as Kit from '@sveltejs/kit'; - -type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; -type MatcherParam = M extends (param : string) => param is (infer U extends string) ? U : string; -type RouteParams = { }; -type RouteId = '/data_sources_excel'; -type MaybeWithVoid = {} extends T ? T | void : T; -export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; -type OutputDataShape = MaybeWithVoid> & Partial> & Record> -type EnsureDefined = T extends null | undefined ? {} : T; -type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; -export type Snapshot = Kit.Snapshot; -type PageParentData = EnsureDefined; - -export type PageServerData = null; -export type PageData = Expand; -export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file From 9e3c72495aac5089dee3c1fac6e97434fdd51f8c Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:39:30 +0200 Subject: [PATCH 5/7] Litning --- .../file-picker/file-picker.component.ts | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts index c0267e5c..c39718f9 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts @@ -1,13 +1,13 @@ -import { Component, Input, forwardRef } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import {Component, Input, forwardRef} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import { InputGroupModule } from 'primeng/inputgroup'; -import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; -import { InputTextModule } from 'primeng/inputtext'; -import { ButtonModule } from 'primeng/button'; +import {InputGroupModule} from 'primeng/inputgroup'; +import {InputGroupAddonModule} from 'primeng/inputgroupaddon'; +import {InputTextModule} from 'primeng/inputtext'; +import {ButtonModule} from 'primeng/button'; -import { open, DialogFilter } from '@tauri-apps/plugin-dialog'; +import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; /** * A reusable text input with an integrated native Tauri file/directory picker. @@ -70,67 +70,70 @@ import { open, DialogFilter } from '@tauri-apps/plugin-dialog'; * ``` */ @Component({ - selector: 'app-file-picker', - standalone: true, - imports: [CommonModule, InputGroupModule, InputGroupAddonModule, InputTextModule, ButtonModule], - templateUrl: './file-picker.component.html', - styleUrls: ['./file-picker.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => FilePickerComponent), - multi: true, - }, - ], + selector: 'app-file-picker', + standalone: true, + imports: [CommonModule, InputGroupModule, InputGroupAddonModule, InputTextModule, ButtonModule], + templateUrl: './file-picker.component.html', + styleUrls: ['./file-picker.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FilePickerComponent), + multi: true, + }, + ], }) export class FilePickerComponent implements ControlValueAccessor { - @Input() directory = false; - @Input() multiple = false; - @Input() placeholder = ''; - @Input() filters?: DialogFilter[]; + @Input() directory = false; + @Input() multiple = false; + @Input() placeholder = ''; + @Input() filters?: DialogFilter[]; - value = ''; + value = ''; - private onChange = (_: string) => {}; - private onTouched = () => {}; + private onChange = (_: string) => { + }; + private onTouched = () => { + }; - async triggerFileSelect() { - console.log('Button clicked'); + async triggerFileSelect() { + console.log('Button clicked'); - try { - const selected = await open({ - directory: this.directory, - multiple: this.multiple, - filters: this.filters, - }); + try { + const selected = await open({ + directory: this.directory, + multiple: this.multiple, + filters: this.filters, + }); - console.log(selected); + console.log(selected); - if (selected !== null) { - this.value = selected as string; - this.onChange(this.value); - } - } catch (err) { - console.error(err); + if (selected !== null) { + this.value = selected as string; + this.onChange(this.value); + } + } catch (err) { + console.error(err); + } } - } - onInput(event: Event) { - this.value = (event.target as HTMLInputElement).value; - this.onChange(this.value); - } + onInput(event: Event) { + this.value = (event.target as HTMLInputElement).value; + this.onChange(this.value); + } - writeValue(value: string): void { - this.value = value ?? ''; - } + writeValue(value: string): void { + this.value = value ?? ''; + } - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } - setDisabledState(disabled: boolean): void {} + setDisabledState(_disabled: boolean): void { + } } From d32e7e6c93ba8872476d54fb144e66ae3956f3c3 Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 14:41:19 +0200 Subject: [PATCH 6/7] Litning --- .../app/shared/components/file-picker/file-picker.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts index c39718f9..526e9c51 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts @@ -92,6 +92,8 @@ export class FilePickerComponent implements ControlValueAccessor { value = ''; private onChange = (_: string) => { + + void _; }; private onTouched = () => { }; @@ -135,5 +137,6 @@ export class FilePickerComponent implements ControlValueAccessor { } setDisabledState(_disabled: boolean): void { + void _disabled; } } From 1fd72335c478efebd1b6c8ea73363ae59fd7883b Mon Sep 17 00:00:00 2001 From: SmartMonkey Date: Fri, 3 Jul 2026 19:02:47 +0200 Subject: [PATCH 7/7] Formatting --- .../file-picker/file-picker.component.ts | 124 +++++++++--------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts index 526e9c51..e4e73771 100644 --- a/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts +++ b/phenoxtract-ui/src/app/shared/components/file-picker/file-picker.component.ts @@ -1,13 +1,13 @@ -import {Component, Input, forwardRef} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import { Component, Input, forwardRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import {InputGroupModule} from 'primeng/inputgroup'; -import {InputGroupAddonModule} from 'primeng/inputgroupaddon'; -import {InputTextModule} from 'primeng/inputtext'; -import {ButtonModule} from 'primeng/button'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; +import { ButtonModule } from 'primeng/button'; -import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; +import { open, DialogFilter } from '@tauri-apps/plugin-dialog'; /** * A reusable text input with an integrated native Tauri file/directory picker. @@ -70,73 +70,71 @@ import {open, DialogFilter} from '@tauri-apps/plugin-dialog'; * ``` */ @Component({ - selector: 'app-file-picker', - standalone: true, - imports: [CommonModule, InputGroupModule, InputGroupAddonModule, InputTextModule, ButtonModule], - templateUrl: './file-picker.component.html', - styleUrls: ['./file-picker.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => FilePickerComponent), - multi: true, - }, - ], + selector: 'app-file-picker', + standalone: true, + imports: [CommonModule, InputGroupModule, InputGroupAddonModule, InputTextModule, ButtonModule], + templateUrl: './file-picker.component.html', + styleUrls: ['./file-picker.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FilePickerComponent), + multi: true, + }, + ], }) export class FilePickerComponent implements ControlValueAccessor { - @Input() directory = false; - @Input() multiple = false; - @Input() placeholder = ''; - @Input() filters?: DialogFilter[]; + @Input() directory = false; + @Input() multiple = false; + @Input() placeholder = ''; + @Input() filters?: DialogFilter[]; - value = ''; + value = ''; - private onChange = (_: string) => { + private onChange = (_: string) => { + void _; + }; + private onTouched = () => {}; - void _; - }; - private onTouched = () => { - }; + async triggerFileSelect() { + console.log('Button clicked'); - async triggerFileSelect() { - console.log('Button clicked'); + try { + const selected = await open({ + directory: this.directory, + multiple: this.multiple, + filters: this.filters, + }); - try { - const selected = await open({ - directory: this.directory, - multiple: this.multiple, - filters: this.filters, - }); + console.log(selected); - console.log(selected); - - if (selected !== null) { - this.value = selected as string; - this.onChange(this.value); - } - } catch (err) { - console.error(err); - } - } - - onInput(event: Event) { - this.value = (event.target as HTMLInputElement).value; + if (selected !== null) { + this.value = selected as string; this.onChange(this.value); + } + } catch (err) { + console.error(err); } + } - writeValue(value: string): void { - this.value = value ?? ''; - } + onInput(event: Event) { + this.value = (event.target as HTMLInputElement).value; + this.onChange(this.value); + } - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } + writeValue(value: string): void { + this.value = value ?? ''; + } - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } - setDisabledState(_disabled: boolean): void { - void _disabled; - } + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(_disabled: boolean): void { + void _disabled; + } }
-

Welcome to Tauri + Angular!

+

Welcome to Tauri + Angular!

-
- - + -

Click on the logos to learn more about the frameworks

+ + + + + + +
+

Click on the logos to learn more about the frameworks

-
- - -
-

{{ greetingMessage }}

+
+ + +
+

{{ greetingMessage }}

- +