Initial commit
This commit is contained in:
commit
d9cff2e70c
72 changed files with 2878 additions and 0 deletions
26
packages/client/.gitignore
vendored
Normal file
26
packages/client/.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
/node_modules
|
||||
|
||||
# Idea
|
||||
/.idea
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
59
packages/client/Containerfile
Normal file
59
packages/client/Containerfile
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Stage 1: Builder
|
||||
# This stage installs all dependencies and builds all packages.
|
||||
FROM oven/bun:1 AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency manifests
|
||||
COPY package.json bun.lock ./
|
||||
COPY tsconfig.base.json ./
|
||||
|
||||
# Copy package-specific manifests to leverage Docker cache
|
||||
COPY packages/shared/package.json packages/shared/tsconfig.json ./packages/shared/
|
||||
COPY packages/client/package.json packages/client/tsconfig.json ./packages/client/
|
||||
|
||||
# Install ALL workspace dependencies
|
||||
RUN bun install
|
||||
|
||||
# Copy source code
|
||||
COPY packages/shared ./packages/shared/
|
||||
COPY packages/client ./packages/client/
|
||||
|
||||
# Build the client and its dependencies (i.e., 'shared')
|
||||
RUN bun run --filter=@hnu.de/hl7v2-shared build
|
||||
RUN bun run --filter=@hnu.de/hl7v2-client build
|
||||
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Stage 2: Production
|
||||
FROM oven/bun:1-slim
|
||||
WORKDIR /app
|
||||
|
||||
# Create the full directory structure first
|
||||
RUN mkdir -p packages/shared packages/client
|
||||
|
||||
# Copy workspace configuration
|
||||
COPY --from=builder /app/package.json /app/bun.lock ./
|
||||
|
||||
# Set up shared package with its dist directory
|
||||
COPY --from=builder /app/packages/shared/package.json ./packages/shared/
|
||||
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
|
||||
|
||||
# Set up client package with its dist directory
|
||||
COPY --from=builder /app/packages/client/package.json ./packages/client/
|
||||
COPY --from=builder /app/packages/client/build ./packages/client/build
|
||||
|
||||
# Install ONLY production dependencies
|
||||
ENV NODE_ENV=production
|
||||
RUN bun install \
|
||||
--frozen-lockfile \
|
||||
--production
|
||||
|
||||
# Set environment variables from config.ts
|
||||
ENV PUBLIC_SERVER=localhost:8080
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Run the client from its package directory
|
||||
WORKDIR /app/packages/client
|
||||
CMD ["bun", "-r", "dotenv/config", "build/index.js"]
|
16
packages/client/components.json
Normal file
16
packages/client/components.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"tailwind": {
|
||||
"css": "src/app.css",
|
||||
"baseColor": "neutral"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry"
|
||||
}
|
41
packages/client/package.json
Normal file
41
packages/client/package.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "@hnu.de/hl7v2-client",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hnu.de/hl7v2-shared": "workspace:*",
|
||||
"dotenv": "^17.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/adapter-node": "^5.2.13",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"bits-ui": "^2.8.6",
|
||||
"clsx": "^2.1.1",
|
||||
"formsnap": "^2.0.1",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"sveltekit-superforms": "^2.26.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.5",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^7.0.4",
|
||||
"ws": "^8.18.3"
|
||||
}
|
||||
}
|
121
packages/client/src/app.css
Normal file
121
packages/client/src/app.css
Normal file
|
@ -0,0 +1,121 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
13
packages/client/src/app.d.ts
vendored
Normal file
13
packages/client/src/app.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
import type { WebSocketServer } from 'ws';
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
12
packages/client/src/app.html
Normal file
12
packages/client/src/app.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
40
packages/client/src/lib/components/theme-selector.svelte
Normal file
40
packages/client/src/lib/components/theme-selector.svelte
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { userPrefersMode, resetMode, setMode } from "mode-watcher"
|
||||
import { buttonVariants } from "$lib/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from "$lib/components/ui/dropdown-menu"
|
||||
import { MoonIcon, SunIcon, SunMoonIcon } from "@lucide/svelte"
|
||||
|
||||
</script>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class={buttonVariants({ variant: "ghost", size: "icon" })}>
|
||||
{#if userPrefersMode.current === "light"}
|
||||
<SunIcon class="size-[1.2rem]"/>
|
||||
{:else if userPrefersMode.current === "dark"}
|
||||
<MoonIcon class="size-[1.2rem]"/>
|
||||
{:else}
|
||||
<SunMoonIcon class="size-[1.2rem]"/>
|
||||
{/if}
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onclick={() => setMode("light")}>
|
||||
<SunIcon/>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onclick={() => setMode("dark")}>
|
||||
<MoonIcon/>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onclick={() => resetMode()}>
|
||||
<SunMoonIcon/>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
80
packages/client/src/lib/components/ui/button/button.svelte
Normal file
80
packages/client/src/lib/components/ui/button/button.svelte
Normal file
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
||||
outline:
|
||||
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
||||
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
class: className,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = 'button',
|
||||
disabled,
|
||||
children,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
12
packages/client/src/lib/components/ui/button/index.ts
Normal file
12
packages/client/src/lib/components/ui/button/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Root, { type ButtonProps, type ButtonSize, type ButtonVariant, buttonVariants } from './button.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</p>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("[.border-t]:pt-6 flex items-center px-6", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
20
packages/client/src/lib/components/ui/card/card-title.svelte
Normal file
20
packages/client/src/lib/components/ui/card/card-title.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("font-semibold leading-none", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
23
packages/client/src/lib/components/ui/card/card.svelte
Normal file
23
packages/client/src/lib/components/ui/card/card.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
25
packages/client/src/lib/components/ui/card/index.ts
Normal file
25
packages/client/src/lib/components/ui/card/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Root from './card.svelte';
|
||||
import Content from './card-content.svelte';
|
||||
import Description from './card-description.svelte';
|
||||
import Footer from './card-footer.svelte';
|
||||
import Header from './card-header.svelte';
|
||||
import Title from './card-title.svelte';
|
||||
import Action from './card-action.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn("size-4", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-content"
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-dropdown-menu-content-available-height) origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-group-heading"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.RadioGroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubContentProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
class={cn(
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground outline-hidden [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
49
packages/client/src/lib/components/ui/dropdown-menu/index.ts
Normal file
49
packages/client/src/lib/components/ui/dropdown-menu/index.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
import Group from "./dropdown-menu-group.svelte";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import RadioGroup from "./dropdown-menu-radio-group.svelte";
|
||||
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||
import Separator from "./dropdown-menu-separator.svelte";
|
||||
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||
import Trigger from "./dropdown-menu-trigger.svelte";
|
||||
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||
import GroupHeading from "./dropdown-menu-group-heading.svelte";
|
||||
const Sub = DropdownMenuPrimitive.Sub;
|
||||
const Root = DropdownMenuPrimitive.Root;
|
||||
|
||||
export {
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Root as DropdownMenu,
|
||||
CheckboxItem as DropdownMenuCheckboxItem,
|
||||
Content as DropdownMenuContent,
|
||||
Group as DropdownMenuGroup,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
Sub as DropdownMenuSub,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
GroupHeading as DropdownMenuGroupHeading,
|
||||
Group,
|
||||
GroupHeading,
|
||||
Item,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Root,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Sub,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
Trigger,
|
||||
};
|
7
packages/client/src/lib/components/ui/input/index.ts
Normal file
7
packages/client/src/lib/components/ui/input/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Root from './input.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
51
packages/client/src/lib/components/ui/input/input.svelte
Normal file
51
packages/client/src/lib/components/ui/input/input.svelte
Normal file
|
@ -0,0 +1,51 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, 'type'> &
|
||||
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
...restProps
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot="input"
|
||||
class={cn(
|
||||
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot="input"
|
||||
class={cn(
|
||||
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
|
@ -0,0 +1,28 @@
|
|||
import Root from './navigation-menu.svelte';
|
||||
import Content from './navigation-menu-content.svelte';
|
||||
import Indicator from './navigation-menu-indicator.svelte';
|
||||
import Item from './navigation-menu-item.svelte';
|
||||
import Link from './navigation-menu-link.svelte';
|
||||
import List from './navigation-menu-list.svelte';
|
||||
import Trigger from './navigation-menu-trigger.svelte';
|
||||
import Viewport from './navigation-menu-viewport.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Indicator,
|
||||
Item,
|
||||
Link,
|
||||
List,
|
||||
Trigger,
|
||||
Viewport,
|
||||
//
|
||||
Root as NavigationMenuRoot,
|
||||
Content as NavigationMenuContent,
|
||||
Indicator as NavigationMenuIndicator,
|
||||
Item as NavigationMenuItem,
|
||||
Link as NavigationMenuLink,
|
||||
List as NavigationMenuList,
|
||||
Trigger as NavigationMenuTrigger,
|
||||
Viewport as NavigationMenuViewport,
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="navigation-menu-content"
|
||||
class={cn(
|
||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto",
|
||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.IndicatorProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
bind:ref
|
||||
data-slot="navigation-menu-indicator"
|
||||
class={cn(
|
||||
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<div class="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md"></div>
|
||||
</NavigationMenuPrimitive.Indicator>
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="navigation-menu-item"
|
||||
class={cn("relative", className)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.LinkProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Link
|
||||
bind:ref
|
||||
data-slot="navigation-menu-link"
|
||||
class={cn(
|
||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm outline-none transition-all focus-visible:outline-1 focus-visible:ring-[3px] [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.List
|
||||
bind:ref
|
||||
data-slot="navigation-menu-list"
|
||||
class={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts" module>
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
export const navigationMenuTriggerStyle = tv({
|
||||
base: 'bg-background hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 group inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50',
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="navigation-menu-trigger"
|
||||
class={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
<ChevronDownIcon
|
||||
class="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.ViewportProps = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn("absolute left-0 top-full isolate z-50 flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
bind:ref
|
||||
data-slot="navigation-menu-viewport"
|
||||
class={cn(
|
||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--bits-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--bits-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
<script lang="ts">
|
||||
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import NavigationMenuViewport from './navigation-menu-viewport.svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
viewport = true,
|
||||
children,
|
||||
...restProps
|
||||
}: NavigationMenuPrimitive.RootProps & {
|
||||
viewport?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<NavigationMenuPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="navigation-menu"
|
||||
data-viewport={viewport}
|
||||
class={cn(
|
||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
{#if viewport}
|
||||
<NavigationMenuViewport/>
|
||||
{/if}
|
||||
</NavigationMenuPrimitive.Root>
|
21
packages/client/src/lib/components/ui/tooltip/index.ts
Normal file
21
packages/client/src/lib/components/ui/tooltip/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import Trigger from "./tooltip-trigger.svelte";
|
||||
import Content from "./tooltip-content.svelte";
|
||||
|
||||
const Root = TooltipPrimitive.Root;
|
||||
const Provider = TooltipPrimitive.Provider;
|
||||
const Portal = TooltipPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Trigger,
|
||||
Content,
|
||||
Provider,
|
||||
Portal,
|
||||
//
|
||||
Root as Tooltip,
|
||||
Content as TooltipContent,
|
||||
Trigger as TooltipTrigger,
|
||||
Provider as TooltipProvider,
|
||||
Portal as TooltipPortal,
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 0,
|
||||
side = "top",
|
||||
children,
|
||||
arrowClasses,
|
||||
...restProps
|
||||
}: TooltipPrimitive.ContentProps & {
|
||||
arrowClasses?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="tooltip-content"
|
||||
{sideOffset}
|
||||
{side}
|
||||
class={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-tooltip-content-transform-origin) z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<TooltipPrimitive.Arrow>
|
||||
{#snippet child({ props })}
|
||||
<div
|
||||
class={cn(
|
||||
"bg-primary z-50 size-2.5 rotate-45 rounded-[2px]",
|
||||
"data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]",
|
||||
"data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%_+_1px)]",
|
||||
"data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2",
|
||||
"data-[side=left]:-translate-y-[calc(50%_-_3px)]",
|
||||
arrowClasses
|
||||
)}
|
||||
{...props}
|
||||
></div>
|
||||
{/snippet}
|
||||
</TooltipPrimitive.Arrow>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
|
13
packages/client/src/lib/utils.ts
Normal file
13
packages/client/src/lib/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
26
packages/client/src/routes/+layout.svelte
Normal file
26
packages/client/src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
|
||||
import '../app.css';
|
||||
import ThemeSelector from '$lib/components/theme-selector.svelte';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
</script>
|
||||
|
||||
<ModeWatcher/>
|
||||
|
||||
<!-- navigation bar -->
|
||||
<header class="sticky top-0 right-0 left-0 flex justify-center z-50 backdrop-blur py-4 px-4 lg:px-8">
|
||||
<div class="flex items-center justify-between w-full max-w-7xl">
|
||||
<a href="/" class="cursor-pointer">
|
||||
<img class="w-24 hidden dark:block" src="logo_white.svg" alt="HNU Logo"/>
|
||||
<img class="w-24 block dark:hidden" src="logo_black.svg" alt="HNU Logo"/>
|
||||
</a>
|
||||
<div class="flex space-x-2">
|
||||
<ThemeSelector/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{@render children()}
|
298
packages/client/src/routes/+page.svelte
Normal file
298
packages/client/src/routes/+page.svelte
Normal file
|
@ -0,0 +1,298 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
CopyIcon,
|
||||
Loader2Icon,
|
||||
MessageSquareIcon,
|
||||
SendIcon,
|
||||
UnplugIcon,
|
||||
UserIcon,
|
||||
} from '@lucide/svelte';
|
||||
import { ConnectionState, type Message, MessageType, type ReceiveHl7v2Message } from '@hnu.de/hl7v2-shared';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/dropdown-menu';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '$lib/components/ui/tooltip';
|
||||
|
||||
// connection state
|
||||
let ws = $state<WebSocket | undefined>(undefined);
|
||||
let userId = $state<string | undefined>(undefined);
|
||||
let connectionState = $state<ConnectionState>(ConnectionState.disconnected);
|
||||
|
||||
// client state
|
||||
let composedMessage = $state('');
|
||||
let sentMessages = $state<ReceiveHl7v2Message[]>([]); // storing sent messages with client-timestamp for now
|
||||
let receivedMessages = $state<ReceiveHl7v2Message[]>([]);
|
||||
let isSending = $state(false);
|
||||
let copySuccess = $state(false);
|
||||
let deliveryError = $state('');
|
||||
|
||||
// segment presets
|
||||
const segmentTemplates = {
|
||||
MSH: {
|
||||
name: 'Message Header',
|
||||
template: () => `MSH|^~\\&|SENDER_APP|SENDER_FACILITY|RECIPIENT_USER_ID|RECEIVER_FACILITY|${new Date().toISOString().replace(/[-:.]/g, '').slice(0, 14)}||ADT^A01|${Date.now()}|P|2.3`,
|
||||
},
|
||||
PID: {
|
||||
name: 'Patient Identification',
|
||||
template: () => `PID|||PATIENT_MRN||Doe^John^J||19900101|M`,
|
||||
},
|
||||
PV1: {
|
||||
name: 'Patient Visit',
|
||||
template: () => `PV1||I|ER^101^A|||1234^Welby^Marcus`,
|
||||
},
|
||||
AL1: {
|
||||
name: 'Patient Allergy Information',
|
||||
template: () => 'AL1|1|DA|12345^Penicillin|SV|Hives',
|
||||
},
|
||||
ITM: {
|
||||
name: 'Material Item',
|
||||
template: () => 'ITM|1|ITEM-789|Gauze Pads|Sterile Gauze Pads 4x4',
|
||||
},
|
||||
IVC: {
|
||||
name: 'Invoice',
|
||||
template: () => 'IVC|INV-987|ACCT-654|500.00|20230201',
|
||||
},
|
||||
};
|
||||
const segmentTypes = Object.keys(segmentTemplates) as Array<keyof typeof segmentTemplates>;
|
||||
|
||||
function connectToServer() {
|
||||
|
||||
console.log('Connecting to server...');
|
||||
connectionState = ConnectionState.connecting;
|
||||
|
||||
const socket = new WebSocket(`wss://${env.PUBLIC_SERVER}`);
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log('WebSocket connection established.');
|
||||
ws = socket;
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
|
||||
const message = JSON.parse(event.data) as Message;
|
||||
console.log('Message received from server:', message);
|
||||
|
||||
switch (message.type) {
|
||||
|
||||
// initial message from server assigning ID
|
||||
case MessageType.assign_id:
|
||||
userId = message.payload.userId;
|
||||
composedMessage = segmentTemplates.MSH.template();
|
||||
connectionState = ConnectionState.connected;
|
||||
break;
|
||||
|
||||
// message from another client
|
||||
case MessageType.receive_hl7v2:
|
||||
receivedMessages = [message, ...receivedMessages];
|
||||
break;
|
||||
|
||||
// message from server due to delivery error
|
||||
case MessageType.delivery_error:
|
||||
deliveryError = message.payload.error;
|
||||
setTimeout(() => deliveryError = '', 5000); // Clear error after 5 seconds
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log('WebSocket connection closed.');
|
||||
connectionState = ConnectionState.disconnected;
|
||||
ws = undefined;
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
connectionState = ConnectionState.disconnected;
|
||||
ws = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
// clean up on close
|
||||
$effect(() => {
|
||||
return () => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
connectionState = ConnectionState.disconnected;
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function addSegment(type: keyof typeof segmentTemplates) {
|
||||
const template = segmentTemplates[type].template();
|
||||
composedMessage += `\r\n${template}`;
|
||||
}
|
||||
|
||||
function handleSendMessage(message: string) {
|
||||
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN || isSending || !userId) {
|
||||
console.log('Socket not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
isSending = true;
|
||||
deliveryError = '';
|
||||
|
||||
const messageToSend = {
|
||||
type: 'send_hl7v2',
|
||||
payload: { message },
|
||||
} as Message;
|
||||
ws.send(JSON.stringify(messageToSend));
|
||||
|
||||
sentMessages = [{
|
||||
type: MessageType.receive_hl7v2,
|
||||
payload: { message, timestamp: new Date().toISOString() },
|
||||
} as ReceiveHl7v2Message, ...sentMessages];
|
||||
composedMessage = segmentTemplates.MSH.template();
|
||||
isSending = false;
|
||||
}
|
||||
|
||||
function copyUserId() {
|
||||
if (userId) {
|
||||
navigator.clipboard.writeText(userId).then(() => {
|
||||
copySuccess = true;
|
||||
setTimeout(() => copySuccess = false, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{#snippet message(msg: ReceiveHl7v2Message)}
|
||||
<div class="bg-foreground/10 p-3 rounded-md space-y-1">
|
||||
<p class="text-xs">{new Date(msg.payload.timestamp).toLocaleString()}</p>
|
||||
<pre class="text-sm whitespace-pre-wrap break-all">{msg.payload.message}</pre>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
|
||||
<div class="p-4 lg:p-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<main class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
<!-- Left Column: User Info & Logs -->
|
||||
<div class="space-y-6">
|
||||
<Card>
|
||||
<CardHeader class="flex flex-row items-center">
|
||||
<UserIcon class="mr-2"/>
|
||||
Your Info
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if connectionState === ConnectionState.connected && userId}
|
||||
<Label class="">Station ID</Label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Input disabled bind:value={userId}/>
|
||||
<Button variant="outline" size="icon" onclick={copyUserId}>
|
||||
{#if copySuccess}
|
||||
<CheckIcon class={'text-green-400'}/>
|
||||
{:else}
|
||||
<CopyIcon/>
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if connectionState === ConnectionState.disconnected}
|
||||
<div class="flex items-center space-x-2">
|
||||
<UnplugIcon/>
|
||||
<span>Disconnected</span>
|
||||
</div>
|
||||
{:else if connectionState === ConnectionState.connecting}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Loader2Icon class="animate-spin"/>
|
||||
<span>Connecting...</span>
|
||||
</div>
|
||||
{/if}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="flex items-center space-x-2">
|
||||
<ChevronRightIcon class="text-green-400"/>
|
||||
<span>Received Messages</span>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
{#if receivedMessages.length === 0}
|
||||
<p class="text-foreground/70 italic">Waiting for incoming messages...</p>
|
||||
{/if}
|
||||
{#each receivedMessages as msg (msg.payload.timestamp)}
|
||||
{@render message(msg)}
|
||||
{/each}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Composer and Sent Log -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<Card>
|
||||
<CardHeader class="flex items-center space-x-2">
|
||||
<MessageSquareIcon/>
|
||||
<span>HL7v2 Message Editor</span>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if connectionState === ConnectionState.connected && userId}
|
||||
<div>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{#each segmentTypes as type (type)}
|
||||
{#if type !== 'MSH'}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button onclick={() => addSegment(type)}>
|
||||
+ {type}
|
||||
</Button>
|
||||
</TooltipTrigger
|
||||
>
|
||||
<TooltipContent>
|
||||
{segmentTemplates[type].name}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<textarea bind:value={composedMessage}
|
||||
class="w-full h-64 bg-black font-mono text-sm text-green-400 p-4 rounded-md"></textarea>
|
||||
<Button onclick={() => handleSendMessage(composedMessage)}
|
||||
disabled={isSending || !composedMessage} class="w-full space-x-2">
|
||||
<SendIcon/>
|
||||
<span>Send Message</span>
|
||||
</Button>
|
||||
{#if deliveryError}
|
||||
<p class="text-red-600 dark:text-red-400 text-center mt-2">{deliveryError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if connectionState === ConnectionState.disconnected}
|
||||
<div class="flex flex-col items-center justify-center min-h-96 text-foreground/70 space-y-4">
|
||||
<h3 class="text-xl font-bold">Connect as a client</h3>
|
||||
<Button onclick={connectToServer}>Connect</Button>
|
||||
</div>
|
||||
{:else if connectionState === ConnectionState.connecting}
|
||||
<div class="flex flex-col items-center justify-center min-h-96 text-foreground/70">
|
||||
<Loader2Icon class="animate-spin mb-4" size={48}/>
|
||||
<h3 class="text-xl font-bold">Connecting...</h3>
|
||||
</div>
|
||||
{/if}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="flex items-center space-x-2">
|
||||
<SendIcon class="text-blue-400"/>
|
||||
<span>Sent Messages Log</span>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
{#if sentMessages.length === 0}
|
||||
<p class="text-foreground/70 italic">Your sent messages will appear here.</p>
|
||||
{/if}
|
||||
{#each sentMessages as msg (msg.payload.timestamp)}
|
||||
{@render message(msg)}
|
||||
{/each}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
7
packages/client/static/favicon.svg
Normal file
7
packages/client/static/favicon.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128">
|
||||
<title>svelte-logo</title>
|
||||
<path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116"
|
||||
style="fill:#ff3e00"/>
|
||||
<path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328"
|
||||
style="fill:#fff"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
80
packages/client/static/logo_black.svg
Normal file
80
packages/client/static/logo_black.svg
Normal file
|
@ -0,0 +1,80 @@
|
|||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 188.3 100" style="enable-background:new 0 0 188.3 100;" xml:space="preserve">
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
<slices>
|
||||
</slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="100" width="188.3" x="-68.6" y="1.5">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<g>
|
||||
<path d="M7,76.3H1.3v-4.1H0v9.7h1.3v-4.4H7v4.4h1.3v-9.7H7V76.3z M13.1,75c-1.9,0-3.5,1.4-3.5,3.5s1.4,3.5,3.5,3.5s3.5-1.4,3.5-3.5
|
||||
S15.2,75,13.1,75z M13.1,81c-1.4,0-2.1-1.1-2.1-2.5c0-1.6,0.8-2.5,2.1-2.5c1.4,0,2.1,1.1,2.1,2.5C15.3,80.1,14.6,81,13.1,81z
|
||||
M21.2,75.9c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3
|
||||
l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1c-1.3,0-2.1-0.9-2.1-2.5C19,76.9,19.9,75.9,21.2,75.9z M29,75.9c1.1,0,1.6,0.6,1.6,1.9V82h1.3
|
||||
v-4.1c0-1.6-0.6-2.7-2.5-2.7c-0.9,0-1.9,0.6-2.2,1.4v-4.1h-1.3v9.7h1.3v-3.8C27.1,76.7,27.8,75.9,29,75.9z M36.2,81.2
|
||||
c-1.1,0-1.9-0.5-2.1-1.6h-1.1c0.2,1.6,1.4,2.4,3,2.4s2.7-0.8,2.7-2.1c0-1.3-0.8-1.7-2.2-1.9l-1.3-0.2c-0.6-0.2-1.1-0.5-1.1-0.9
|
||||
c0-0.6,0.5-0.9,1.4-0.9c1.1,0,1.7,0.5,1.9,1.6h1.1c-0.2-1.6-1.1-2.4-3-2.4c-1.6,0-2.5,0.8-2.5,2.1c0,1.1,0.8,1.7,2.1,1.9h1.3
|
||||
c0.8,0.2,1.3,0.3,1.3,0.9C37.7,80.9,37,81.2,36.2,81.2z M43.4,75.9c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3
|
||||
c-2.1,0-3.5,1.4-3.5,3.5c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1c-1.3,0-2.1-0.9-2.1-2.5
|
||||
C41.3,76.9,42.1,75.9,43.4,75.9z M51.1,75.9c1.1,0,1.6,0.6,1.6,1.9V82H54v-4.1c0-1.6-0.6-2.7-2.5-2.7c-0.9,0-1.9,0.6-2.2,1.4v-4.1
|
||||
h-1.3v9.7h1.3v-3.8C49.4,76.7,50.2,75.9,51.1,75.9z M60.4,79c0,1.3-0.8,2.1-1.7,2.1c-1.1,0-1.6-0.8-1.6-1.9V75h-1.3v4.1
|
||||
c0,1.6,0.6,2.7,2.4,2.7c0.9,0,1.9-0.6,2.2-1.4v1.1h1.3v-6.6h-1.3C60.4,74.8,60.4,79,60.4,79z M63.6,72.2h1.3v9.7h-1.3V72.2z
|
||||
M69.8,82.1c1.7,0,2.8-0.9,3.2-2.4l-1.1-0.2c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.1-0.9-2.2-2.2h5.4v-0.5c0-1.9-1.1-3.3-3.2-3.3
|
||||
c-1.9,0-3.3,1.6-3.3,3.6C66.3,80.7,67.7,82.1,69.8,82.1z M69.6,75.9c1.3,0,1.9,0.9,1.9,2.1h-4C67.7,76.9,68.5,75.9,69.6,75.9z
|
||||
M85.3,81.8v-9.7H84v7.4l-5.2-7.4h-1.4v9.7h1.3v-7.8l5.5,7.8H85.3z M93.5,79.6l-1.1-0.2c-0.3,1.1-0.9,1.6-2.1,1.6
|
||||
c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5c0-1.9-1.1-3.3-3.2-3.3c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5C91.9,82.1,93,81.2,93.5,79.6z
|
||||
M90.2,75.9c1.3,0,1.9,0.9,1.9,2.1h-4C88.3,76.9,89.1,75.9,90.2,75.9z M94.9,75.2v4.1c0,1.6,0.6,2.7,2.4,2.7c0.9,0,1.9-0.6,2.2-1.4
|
||||
v1.1h1.3V75h-1.3v3.8c0,1.3-0.8,2.1-1.7,2.1c-1.1,0-1.6-0.8-1.6-1.9v-4.1h-1.3V75.2z M102.7,77.4h3.5v1.1h-3.5V77.4z M115.8,78.2
|
||||
v-6h-1.3v6c0,1.9-0.9,2.7-2.7,2.7c-1.7,0-2.5-0.9-2.5-2.7v-6h-1.3v6c0,2.8,1.7,4,4,4S115.8,81,115.8,78.2z M117.7,72.2h1.3v9.7
|
||||
h-1.3V72.2z M128.8,75c-1.1,0-1.9,0.6-2.2,1.4c-0.3-0.9-0.9-1.4-2.2-1.4c-1.1,0-1.7,0.6-2.1,1.4v-1.3H121v6.6h1.3V78
|
||||
c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.4,0.6,1.4,1.9V82h1.1v-3.8c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.4,0.6,1.4,1.9v4.1h1.3V78
|
||||
C131.2,76.3,130.5,75,128.8,75z M6.5,93.7c0,1.9-0.9,2.7-2.7,2.7s-2.5-0.9-2.5-2.7v-6H0v6c0,2.8,1.7,4,4,4s4-1.1,4-4v-6H6.5V93.7z
|
||||
M13.1,90.5c-0.9,0-1.9,0.6-2.2,1.4v-1.3H9.7v6.6h1.3v-3.8c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.6,0.6,1.6,1.9v4.1h1.3v-4.1
|
||||
C15.7,91.6,14.9,90.5,13.1,90.5z M17.4,90.7h1.3v6.6h-1.3V90.7z M17.4,87.7h1.3v1.4h-1.3V87.7z M23.4,95.9h-0.2l-2.1-5.2h-1.3
|
||||
l2.7,6.6h1.3l2.7-6.6h-1.1L23.4,95.9z M30.7,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.3-0.3
|
||||
c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5C33.9,91.9,32.8,90.5,30.7,90.5z M28.5,93.5c0.2-1.1,0.9-2.1,2.1-2.1
|
||||
c1.3,0,1.9,0.9,1.9,2.1H28.5z M36.6,92.1v-1.4h-1.3v6.6h1.3v-3.5c0-1.3,0.8-2.2,1.7-2.2c0.5,0,0.6,0,1.1,0.3l0.2-1.1
|
||||
c-0.2-0.2-0.5-0.3-0.9-0.3C37.5,90.5,36.9,91.3,36.6,92.1z M43.7,93.5l-1.3-0.2c-0.6-0.2-1.1-0.5-1.1-0.9c0-0.6,0.5-0.9,1.4-0.9
|
||||
c1.1,0,1.7,0.5,1.9,1.6h1.1c-0.2-1.6-1.1-2.4-3-2.4c-1.6,0-2.5,0.8-2.5,2.1c0,1.1,0.8,1.7,2.1,1.9l1.1,0.2c0.8,0.2,1.3,0.3,1.3,0.9
|
||||
c0,0.6-0.6,0.9-1.6,0.9c-1.1,0-1.9-0.5-2.1-1.6h-1.1c0.2,1.6,1.4,2.4,3,2.4c1.6,0,2.7-0.8,2.7-2.1C45.9,94.3,45.1,93.8,43.7,93.5z
|
||||
M47.3,87.7h1.3v1.4h-1.3V87.7z M47.3,90.7h1.3v6.6h-1.3V90.7z M52.1,95.7v-4.1H54v-0.9h-1.9v-1.9h-0.8l-0.2,0.9
|
||||
c-0.2,0.6-0.3,0.9-0.9,1.1h-0.5v0.6h0.9v4.3c0,1.3,0.6,1.7,1.6,1.7c0.8,0,1.3-0.2,1.7-0.6V96c-0.3,0.2-0.8,0.5-1.3,0.5
|
||||
S52.1,96.2,52.1,95.7z M58.1,95.9l-2.2-5.2h-1.3l2.8,6.6l-1.1,2.7h1.3l3.8-9.3h-1.3L58.1,95.9z M68.5,90.5c-1.9,0-3.5,1.4-3.5,3.5
|
||||
s1.4,3.5,3.5,3.5S72,96,72,94S70.4,90.5,68.5,90.5z M68.5,96.5c-1.4,0-2.1-1.1-2.1-2.5c0-1.6,0.8-2.5,2.1-2.5
|
||||
c1.4,0,2.1,1.1,2.1,2.5C70.6,95.6,69.8,96.5,68.5,96.5z M75.9,87.5c-1.4,0-2.2,0.9-2.2,2.4v0.8h-1.1v0.9h1.1v5.9H75v-5.9h1.9v-0.9
|
||||
H75v-0.9c0-0.8,0.3-1.3,1.1-1.3c0.6,0,0.9,0.5,1.1,0.9l0.9-0.3C77.7,88,77.1,87.5,75.9,87.5z M84.2,87.7l-4,9.7h1.3l1.1-2.8H87
|
||||
l1.1,2.8h1.4l-4-9.7H84.2z M83.1,93.5l1.7-4.3l1.7,4.3C86.6,93.5,83.1,93.5,83.1,93.5z M94.5,90.5c-1.3,0-2.1,0.8-2.4,1.6v-1.4
|
||||
h-1.3v9.3h1.3v-4c0.3,0.8,1.1,1.6,2.4,1.6c2.1,0,3-1.6,3-3.5C97.6,92.1,96.5,90.5,94.5,90.5z M94.3,96.5c-1.3,0-2.2-0.8-2.2-2.4
|
||||
v-0.3c0-1.4,0.9-2.4,2.2-2.4c1.3,0,2.1,0.9,2.1,2.5C96.2,95.6,95.6,96.5,94.3,96.5z M102.7,90.5c-1.3,0-2.1,0.8-2.4,1.6v-1.4h-1.3
|
||||
v9.3h1.3v-4c0.3,0.8,1.1,1.6,2.4,1.6c2.1,0,3-1.6,3-3.5C105.9,92.1,104.7,90.5,102.7,90.5z M102.5,96.5c-1.3,0-2.2-0.8-2.2-2.4
|
||||
v-0.3c0-1.4,0.9-2.4,2.2-2.4c1.3,0,2.1,0.9,2.1,2.5C104.6,95.6,103.8,96.5,102.5,96.5z M107.3,87.7h1.3v9.7h-1.3V87.7z M110.4,87.7
|
||||
h1.3v1.4h-1.3V87.7z M110.4,90.7h1.3v6.6h-1.3V90.7z M116.6,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4
|
||||
l-1.1-0.3c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.1-0.9-2.2-2.2h5.4v-0.5C119.8,91.9,118.8,90.5,116.6,90.5z M114.6,93.5
|
||||
c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1H114.6z M126.4,92.1c-0.3-0.8-1.1-1.6-2.4-1.6c-2.1,0-3,1.6-3,3.5
|
||||
c0,2.1,1.1,3.5,3,3.5c1.3,0,2.1-0.8,2.4-1.6v1.4h1.3v-9.7h-1.3C126.4,87.7,126.4,92.1,126.4,92.1z M126.4,94.1
|
||||
c0,1.4-0.9,2.4-2.1,2.4c-1.3,0-2.1-0.9-2.1-2.5s0.8-2.5,2.1-2.5c1.3,0,2.1,0.8,2.1,2.4C126.4,93.8,126.4,94.1,126.4,94.1z
|
||||
M137,92.1l-1.7-0.3c-1.1-0.2-1.7-0.5-1.7-1.4c0-1.1,0.8-1.7,2.2-1.7c1.6,0,2.5,0.9,2.7,2.5h1.3c0-2.2-1.4-3.5-3.8-3.5
|
||||
c-2.1,0-3.5,1.1-3.5,2.8c0,1.6,0.9,2.2,2.4,2.5l1.9,0.3c1.3,0.3,1.9,0.6,1.9,1.6c0,1.1-0.9,1.7-2.4,1.7c-1.6,0-2.8-0.9-2.8-2.7
|
||||
h-1.3c0,2.4,1.7,3.6,4.1,3.6c2.1,0,3.6-1.1,3.6-2.8C139.7,93.2,138.8,92.4,137,92.1z M144.3,91.5c1.3,0,1.9,0.8,2.1,2.1h1.1
|
||||
c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3l-1.1-0.2c0,1.4-0.8,2.2-2.1,2.2
|
||||
c-1.3,0-2.1-0.9-2.1-2.5S143,91.5,144.3,91.5z M148.9,87.7h1.3v1.4h-1.3V87.7z M149.1,90.7h1.3v6.6h-1.3V90.7z M155.1,90.5
|
||||
c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.1-0.3c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.2-0.9-2.2-2.2h5.4
|
||||
v-0.5C158.4,91.9,157.3,90.5,155.1,90.5z M153,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1H153z M163.3,90.5
|
||||
c-0.9,0-1.9,0.6-2.2,1.4v-1.3h-1.3v6.6h1.3v-3.8c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.6,0.6,1.6,1.9v4.1h1.3v-4.1
|
||||
C165.8,91.6,165,90.5,163.3,90.5z M170.6,91.5c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5
|
||||
c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1s-2.1-0.9-2.1-2.5C168.5,92.4,169.3,91.5,170.6,91.5z
|
||||
M178.3,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.3-0.3c-0.3,1.1-0.9,1.6-2.1,1.6
|
||||
c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5C181.5,91.9,180.4,90.5,178.3,90.5z M176.1,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1
|
||||
H176.1z M186.2,93.5l-1.3-0.2c-0.6-0.2-1.1-0.5-1.1-0.9c0-0.6,0.5-0.9,1.4-0.9c1.1,0,1.7,0.5,1.9,1.6h1.1c-0.2-1.6-1.1-2.4-3-2.4
|
||||
c-1.6,0-2.5,0.8-2.5,2.1c0,1.1,0.8,1.7,2.1,1.9l1.1,0.2c0.8,0.2,1.3,0.3,1.3,0.9c0,0.6-0.6,0.9-1.6,0.9c-1.1,0-1.9-0.5-2.1-1.6
|
||||
h-1.1c0.2,1.6,1.4,2.4,3,2.4c1.6,0,2.7-0.8,2.7-2.1C188.4,94.3,187.5,93.8,186.2,93.5z M6.2,0h11.7v19.8H6.2V0z M17.7,37.7h10.1
|
||||
v-4.7H6.2V57H0v3.6h17.7V37.7z M55.2,0H43.7v57h-6.2v3.6h17.7V0z M149.2,44.3c1.7,0,3.3-0.2,4.6-0.6c-2.1-1.9-3-5.1-3-10V0h-11.6
|
||||
v30.2C139.2,39.4,141.6,44.3,149.2,44.3z">
|
||||
</path>
|
||||
<path d="M144,57.8c4.4,2.5,10,4,16.6,4c17.6,0,27.7-9.3,27.7-25.5V0h-11.6v32.9c0,17.7-12,25.2-27.5,25.2
|
||||
C147.3,58.1,145.6,57.9,144,57.8z M95.7,15.7L84.7,0H73.3l22.5,31.8V15.7z M122.3,60.6V0h-11.7v57h-5.5l2.7,3.6H122.3z M84.8,37.5
|
||||
L72.3,20.6V57h-4.6v3.6h17.1V37.5z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.2 KiB |
83
packages/client/static/logo_white.svg
Normal file
83
packages/client/static/logo_white.svg
Normal file
|
@ -0,0 +1,83 @@
|
|||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 188.3 100" style="enable-background:new 0 0 188.3 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
<slices>
|
||||
</slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="100" width="188.3" x="-68.6" y="1.5">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<g>
|
||||
<path class="st0" d="M7,76.3H1.3v-4.1H0v9.7h1.3v-4.4H7v4.4h1.3v-9.7H7V76.3z M13.1,75c-1.9,0-3.5,1.4-3.5,3.5s1.4,3.5,3.5,3.5
|
||||
s3.5-1.4,3.5-3.5S15.2,75,13.1,75z M13.1,81c-1.4,0-2.1-1.1-2.1-2.5c0-1.6,0.8-2.5,2.1-2.5c1.4,0,2.1,1.1,2.1,2.5
|
||||
C15.3,80.1,14.6,81,13.1,81z M21.2,75.9c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5
|
||||
c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1c-1.3,0-2.1-0.9-2.1-2.5C19,76.9,19.9,75.9,21.2,75.9z
|
||||
M29,75.9c1.1,0,1.6,0.6,1.6,1.9V82h1.3v-4.1c0-1.6-0.6-2.7-2.5-2.7c-0.9,0-1.9,0.6-2.2,1.4v-4.1h-1.3v9.7h1.3v-3.8
|
||||
C27.1,76.7,27.8,75.9,29,75.9z M36.2,81.2c-1.1,0-1.9-0.5-2.1-1.6h-1.1c0.2,1.6,1.4,2.4,3,2.4s2.7-0.8,2.7-2.1
|
||||
c0-1.3-0.8-1.7-2.2-1.9l-1.3-0.2c-0.6-0.2-1.1-0.5-1.1-0.9c0-0.6,0.5-0.9,1.4-0.9c1.1,0,1.7,0.5,1.9,1.6h1.1
|
||||
c-0.2-1.6-1.1-2.4-3-2.4c-1.6,0-2.5,0.8-2.5,2.1c0,1.1,0.8,1.7,2.1,1.9h1.3c0.8,0.2,1.3,0.3,1.3,0.9C37.7,80.9,37,81.2,36.2,81.2z
|
||||
M43.4,75.9c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3
|
||||
l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1c-1.3,0-2.1-0.9-2.1-2.5C41.3,76.9,42.1,75.9,43.4,75.9z M51.1,75.9c1.1,0,1.6,0.6,1.6,1.9V82
|
||||
H54v-4.1c0-1.6-0.6-2.7-2.5-2.7c-0.9,0-1.9,0.6-2.2,1.4v-4.1h-1.3v9.7h1.3v-3.8C49.4,76.7,50.2,75.9,51.1,75.9z M60.4,79
|
||||
c0,1.3-0.8,2.1-1.7,2.1c-1.1,0-1.6-0.8-1.6-1.9V75h-1.3v4.1c0,1.6,0.6,2.7,2.4,2.7c0.9,0,1.9-0.6,2.2-1.4v1.1h1.3v-6.6h-1.3
|
||||
C60.4,74.8,60.4,79,60.4,79z M63.6,72.2h1.3v9.7h-1.3V72.2z M69.8,82.1c1.7,0,2.8-0.9,3.2-2.4l-1.1-0.2c-0.3,1.1-0.9,1.6-2.1,1.6
|
||||
c-1.3,0-2.1-0.9-2.2-2.2h5.4v-0.5c0-1.9-1.1-3.3-3.2-3.3c-1.9,0-3.3,1.6-3.3,3.6C66.3,80.7,67.7,82.1,69.8,82.1z M69.6,75.9
|
||||
c1.3,0,1.9,0.9,1.9,2.1h-4C67.7,76.9,68.5,75.9,69.6,75.9z M85.3,81.8v-9.7H84v7.4l-5.2-7.4h-1.4v9.7h1.3v-7.8l5.5,7.8H85.3z
|
||||
M93.5,79.6l-1.1-0.2c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5c0-1.9-1.1-3.3-3.2-3.3c-1.9,0-3.3,1.6-3.3,3.6
|
||||
s1.4,3.5,3.5,3.5C91.9,82.1,93,81.2,93.5,79.6z M90.2,75.9c1.3,0,1.9,0.9,1.9,2.1h-4C88.3,76.9,89.1,75.9,90.2,75.9z M94.9,75.2
|
||||
v4.1c0,1.6,0.6,2.7,2.4,2.7c0.9,0,1.9-0.6,2.2-1.4v1.1h1.3V75h-1.3v3.8c0,1.3-0.8,2.1-1.7,2.1c-1.1,0-1.6-0.8-1.6-1.9v-4.1h-1.3
|
||||
V75.2z M102.7,77.4h3.5v1.1h-3.5V77.4z M115.8,78.2v-6h-1.3v6c0,1.9-0.9,2.7-2.7,2.7c-1.7,0-2.5-0.9-2.5-2.7v-6h-1.3v6
|
||||
c0,2.8,1.7,4,4,4S115.8,81,115.8,78.2z M117.7,72.2h1.3v9.7h-1.3V72.2z M128.8,75c-1.1,0-1.9,0.6-2.2,1.4c-0.3-0.9-0.9-1.4-2.2-1.4
|
||||
c-1.1,0-1.7,0.6-2.1,1.4v-1.3H121v6.6h1.3V78c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.4,0.6,1.4,1.9V82h1.1v-3.8c0-1.3,0.8-2.1,1.7-2.1
|
||||
c1.1,0,1.4,0.6,1.4,1.9v4.1h1.3V78C131.2,76.3,130.5,75,128.8,75z M6.5,93.7c0,1.9-0.9,2.7-2.7,2.7s-2.5-0.9-2.5-2.7v-6H0v6
|
||||
c0,2.8,1.7,4,4,4s4-1.1,4-4v-6H6.5V93.7z M13.1,90.5c-0.9,0-1.9,0.6-2.2,1.4v-1.3H9.7v6.6h1.3v-3.8c0-1.3,0.8-2.1,1.7-2.1
|
||||
c1.1,0,1.6,0.6,1.6,1.9v4.1h1.3v-4.1C15.7,91.6,14.9,90.5,13.1,90.5z M17.4,90.7h1.3v6.6h-1.3V90.7z M17.4,87.7h1.3v1.4h-1.3V87.7z
|
||||
M23.4,95.9h-0.2l-2.1-5.2h-1.3l2.7,6.6h1.3l2.7-6.6h-1.1L23.4,95.9z M30.7,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5
|
||||
c1.7,0,2.8-0.9,3.2-2.4l-1.3-0.3c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5C33.9,91.9,32.8,90.5,30.7,90.5z
|
||||
M28.5,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1H28.5z M36.6,92.1v-1.4h-1.3v6.6h1.3v-3.5c0-1.3,0.8-2.2,1.7-2.2
|
||||
c0.5,0,0.6,0,1.1,0.3l0.2-1.1c-0.2-0.2-0.5-0.3-0.9-0.3C37.5,90.5,36.9,91.3,36.6,92.1z M43.7,93.5l-1.3-0.2
|
||||
c-0.6-0.2-1.1-0.5-1.1-0.9c0-0.6,0.5-0.9,1.4-0.9c1.1,0,1.7,0.5,1.9,1.6h1.1c-0.2-1.6-1.1-2.4-3-2.4c-1.6,0-2.5,0.8-2.5,2.1
|
||||
c0,1.1,0.8,1.7,2.1,1.9l1.1,0.2c0.8,0.2,1.3,0.3,1.3,0.9c0,0.6-0.6,0.9-1.6,0.9c-1.1,0-1.9-0.5-2.1-1.6h-1.1c0.2,1.6,1.4,2.4,3,2.4
|
||||
c1.6,0,2.7-0.8,2.7-2.1C45.9,94.3,45.1,93.8,43.7,93.5z M47.3,87.7h1.3v1.4h-1.3V87.7z M47.3,90.7h1.3v6.6h-1.3V90.7z M52.1,95.7
|
||||
v-4.1H54v-0.9h-1.9v-1.9h-0.8l-0.2,0.9c-0.2,0.6-0.3,0.9-0.9,1.1h-0.5v0.6h0.9v4.3c0,1.3,0.6,1.7,1.6,1.7c0.8,0,1.3-0.2,1.7-0.6V96
|
||||
c-0.3,0.2-0.8,0.5-1.3,0.5S52.1,96.2,52.1,95.7z M58.1,95.9l-2.2-5.2h-1.3l2.8,6.6l-1.1,2.7h1.3l3.8-9.3h-1.3L58.1,95.9z
|
||||
M68.5,90.5c-1.9,0-3.5,1.4-3.5,3.5s1.4,3.5,3.5,3.5S72,96,72,94S70.4,90.5,68.5,90.5z M68.5,96.5c-1.4,0-2.1-1.1-2.1-2.5
|
||||
c0-1.6,0.8-2.5,2.1-2.5c1.4,0,2.1,1.1,2.1,2.5C70.6,95.6,69.8,96.5,68.5,96.5z M75.9,87.5c-1.4,0-2.2,0.9-2.2,2.4v0.8h-1.1v0.9h1.1
|
||||
v5.9H75v-5.9h1.9v-0.9H75v-0.9c0-0.8,0.3-1.3,1.1-1.3c0.6,0,0.9,0.5,1.1,0.9l0.9-0.3C77.7,88,77.1,87.5,75.9,87.5z M84.2,87.7
|
||||
l-4,9.7h1.3l1.1-2.8H87l1.1,2.8h1.4l-4-9.7H84.2z M83.1,93.5l1.7-4.3l1.7,4.3C86.6,93.5,83.1,93.5,83.1,93.5z M94.5,90.5
|
||||
c-1.3,0-2.1,0.8-2.4,1.6v-1.4h-1.3v9.3h1.3v-4c0.3,0.8,1.1,1.6,2.4,1.6c2.1,0,3-1.6,3-3.5C97.6,92.1,96.5,90.5,94.5,90.5z
|
||||
M94.3,96.5c-1.3,0-2.2-0.8-2.2-2.4v-0.3c0-1.4,0.9-2.4,2.2-2.4c1.3,0,2.1,0.9,2.1,2.5C96.2,95.6,95.6,96.5,94.3,96.5z M102.7,90.5
|
||||
c-1.3,0-2.1,0.8-2.4,1.6v-1.4h-1.3v9.3h1.3v-4c0.3,0.8,1.1,1.6,2.4,1.6c2.1,0,3-1.6,3-3.5C105.9,92.1,104.7,90.5,102.7,90.5z
|
||||
M102.5,96.5c-1.3,0-2.2-0.8-2.2-2.4v-0.3c0-1.4,0.9-2.4,2.2-2.4c1.3,0,2.1,0.9,2.1,2.5C104.6,95.6,103.8,96.5,102.5,96.5z
|
||||
M107.3,87.7h1.3v9.7h-1.3V87.7z M110.4,87.7h1.3v1.4h-1.3V87.7z M110.4,90.7h1.3v6.6h-1.3V90.7z M116.6,90.5
|
||||
c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.1-0.3c-0.3,1.1-0.9,1.6-2.1,1.6c-1.3,0-2.1-0.9-2.2-2.2h5.4
|
||||
v-0.5C119.8,91.9,118.8,90.5,116.6,90.5z M114.6,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1H114.6z M126.4,92.1
|
||||
c-0.3-0.8-1.1-1.6-2.4-1.6c-2.1,0-3,1.6-3,3.5c0,2.1,1.1,3.5,3,3.5c1.3,0,2.1-0.8,2.4-1.6v1.4h1.3v-9.7h-1.3
|
||||
C126.4,87.7,126.4,92.1,126.4,92.1z M126.4,94.1c0,1.4-0.9,2.4-2.1,2.4c-1.3,0-2.1-0.9-2.1-2.5s0.8-2.5,2.1-2.5
|
||||
c1.3,0,2.1,0.8,2.1,2.4C126.4,93.8,126.4,94.1,126.4,94.1z M137,92.1l-1.7-0.3c-1.1-0.2-1.7-0.5-1.7-1.4c0-1.1,0.8-1.7,2.2-1.7
|
||||
c1.6,0,2.5,0.9,2.7,2.5h1.3c0-2.2-1.4-3.5-3.8-3.5c-2.1,0-3.5,1.1-3.5,2.8c0,1.6,0.9,2.2,2.4,2.5l1.9,0.3c1.3,0.3,1.9,0.6,1.9,1.6
|
||||
c0,1.1-0.9,1.7-2.4,1.7c-1.6,0-2.8-0.9-2.8-2.7h-1.3c0,2.4,1.7,3.6,4.1,3.6c2.1,0,3.6-1.1,3.6-2.8C139.7,93.2,138.8,92.4,137,92.1z
|
||||
M144.3,91.5c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3
|
||||
l-1.1-0.2c0,1.4-0.8,2.2-2.1,2.2c-1.3,0-2.1-0.9-2.1-2.5S143,91.5,144.3,91.5z M148.9,87.7h1.3v1.4h-1.3V87.7z M149.1,90.7h1.3v6.6
|
||||
h-1.3V90.7z M155.1,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.1-0.3c-0.3,1.1-0.9,1.6-2.1,1.6
|
||||
c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5C158.4,91.9,157.3,90.5,155.1,90.5z M153,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1H153
|
||||
z M163.3,90.5c-0.9,0-1.9,0.6-2.2,1.4v-1.3h-1.3v6.6h1.3v-3.8c0-1.3,0.8-2.1,1.7-2.1c1.1,0,1.6,0.6,1.6,1.9v4.1h1.3v-4.1
|
||||
C165.8,91.6,165,90.5,163.3,90.5z M170.6,91.5c1.3,0,1.9,0.8,2.1,2.1h1.1c-0.2-1.6-1.3-3-3.2-3c-2.1,0-3.5,1.4-3.5,3.5
|
||||
c0,2.2,1.4,3.5,3.5,3.5c1.9,0,3-1.3,3.2-3l-1.1-0.2c-0.2,1.1-0.8,2.1-2.1,2.1s-2.1-0.9-2.1-2.5C168.5,92.4,169.3,91.5,170.6,91.5z
|
||||
M178.3,90.5c-1.9,0-3.3,1.6-3.3,3.6s1.4,3.5,3.5,3.5c1.7,0,2.8-0.9,3.2-2.4l-1.3-0.3c-0.3,1.1-0.9,1.6-2.1,1.6
|
||||
c-1.3,0-2.2-0.9-2.2-2.2h5.4v-0.5C181.5,91.9,180.4,90.5,178.3,90.5z M176.1,93.5c0.2-1.1,0.9-2.1,2.1-2.1c1.3,0,1.9,0.9,1.9,2.1
|
||||
H176.1z M186.2,93.5l-1.3-0.2c-0.6-0.2-1.1-0.5-1.1-0.9c0-0.6,0.5-0.9,1.4-0.9c1.1,0,1.7,0.5,1.9,1.6h1.1c-0.2-1.6-1.1-2.4-3-2.4
|
||||
c-1.6,0-2.5,0.8-2.5,2.1c0,1.1,0.8,1.7,2.1,1.9l1.1,0.2c0.8,0.2,1.3,0.3,1.3,0.9c0,0.6-0.6,0.9-1.6,0.9c-1.1,0-1.9-0.5-2.1-1.6
|
||||
h-1.1c0.2,1.6,1.4,2.4,3,2.4c1.6,0,2.7-0.8,2.7-2.1C188.4,94.3,187.5,93.8,186.2,93.5z M6.2,0h11.7v19.8H6.2V0z M17.7,37.7h10.1
|
||||
v-4.7H6.2V57H0v3.6h17.7V37.7z M55.2,0H43.7v57h-6.2v3.6h17.7V0z M149.2,44.3c1.7,0,3.3-0.2,4.6-0.6c-2.1-1.9-3-5.1-3-10V0h-11.6
|
||||
v30.2C139.2,39.4,141.6,44.3,149.2,44.3z">
|
||||
</path>
|
||||
<path class="st0" d="M144,57.8c4.4,2.5,10,4,16.6,4c17.6,0,27.7-9.3,27.7-25.5V0h-11.6v32.9c0,17.7-12,25.2-27.5,25.2
|
||||
C147.3,58.1,145.6,57.9,144,57.8z M95.7,15.7L84.7,0H73.3l22.5,31.8V15.7z M122.3,60.6V0h-11.7v57h-5.5l2.7,3.6H122.3z M84.8,37.5
|
||||
L72.3,20.6V57h-4.6v3.6h17.1V37.5z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.3 KiB |
12
packages/client/svelte.config.js
Normal file
12
packages/client/svelte.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import adapter from '@sveltejs/adapter-node';
|
||||
import {vitePreprocess} from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
14
packages/client/tsconfig.json
Normal file
14
packages/client/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
},
|
||||
}
|
10
packages/client/vite.config.ts
Normal file
10
packages/client/vite.config.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
tailwindcss()
|
||||
],
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue