feature/build (#1)
Co-authored-by: Tobias Klemp <tobias.klemp@v-office.com> Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
14
src/app.d.ts
vendored
14
src/app.d.ts
vendored
@@ -1,13 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
interface Locals {
|
||||
user: import('$lib/server/auth').SessionValidationResult['user']
|
||||
session: import('$lib/server/auth').SessionValidationResult['session']
|
||||
}
|
||||
} // interface Error {}
|
||||
// interface Locals {}
|
||||
namespace App {
|
||||
interface Locals {
|
||||
user: import('$lib/server/auth').SessionValidationResult['user']
|
||||
session: import('$lib/server/auth').SessionValidationResult['session']
|
||||
}
|
||||
} // interface Error {}
|
||||
// interface Locals {}
|
||||
} // interface PageData {}
|
||||
// interface PageState {}
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
||||
export { default as Badge } from './badge.svelte'
|
||||
export { badgeVariants, type BadgeVariant } from './badge.svelte'
|
||||
|
||||
@@ -1,25 +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";
|
||||
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,
|
||||
};
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -1,58 +1,60 @@
|
||||
import type { WithElementRef } from "$lib/utils.js";
|
||||
import type { WithElementRef } from '$lib/utils.js'
|
||||
import type {
|
||||
EmblaCarouselSvelteType,
|
||||
default as emblaCarouselSvelte,
|
||||
} from "embla-carousel-svelte";
|
||||
import { getContext, hasContext, setContext } from "svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
EmblaCarouselSvelteType,
|
||||
default as emblaCarouselSvelte,
|
||||
} from 'embla-carousel-svelte'
|
||||
import { getContext, hasContext, setContext } from 'svelte'
|
||||
import type { HTMLAttributes } from 'svelte/elements'
|
||||
|
||||
export type CarouselAPI =
|
||||
NonNullable<NonNullable<EmblaCarouselSvelteType["$$_attributes"]>["on:emblaInit"]> extends (
|
||||
evt: CustomEvent<infer CarouselAPI>
|
||||
) => void
|
||||
? CarouselAPI
|
||||
: never;
|
||||
NonNullable<
|
||||
NonNullable<EmblaCarouselSvelteType['$$_attributes']>['on:emblaInit']
|
||||
> extends (evt: CustomEvent<infer CarouselAPI>) => void
|
||||
? CarouselAPI
|
||||
: never
|
||||
|
||||
type EmblaCarouselConfig = NonNullable<Parameters<typeof emblaCarouselSvelte>[1]>;
|
||||
type EmblaCarouselConfig = NonNullable<
|
||||
Parameters<typeof emblaCarouselSvelte>[1]
|
||||
>
|
||||
|
||||
export type CarouselOptions = EmblaCarouselConfig["options"];
|
||||
export type CarouselPlugins = EmblaCarouselConfig["plugins"];
|
||||
export type CarouselOptions = EmblaCarouselConfig['options']
|
||||
export type CarouselPlugins = EmblaCarouselConfig['plugins']
|
||||
|
||||
////
|
||||
|
||||
export type CarouselProps = {
|
||||
opts?: CarouselOptions;
|
||||
plugins?: CarouselPlugins;
|
||||
setApi?: (api: CarouselAPI | undefined) => void;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
} & WithElementRef<HTMLAttributes<HTMLDivElement>>;
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugins
|
||||
setApi?: (api: CarouselAPI | undefined) => void
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
} & WithElementRef<HTMLAttributes<HTMLDivElement>>
|
||||
|
||||
const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT");
|
||||
const EMBLA_CAROUSEL_CONTEXT = Symbol('EMBLA_CAROUSEL_CONTEXT')
|
||||
|
||||
export type EmblaContext = {
|
||||
api: CarouselAPI | undefined;
|
||||
orientation: "horizontal" | "vertical";
|
||||
scrollNext: () => void;
|
||||
scrollPrev: () => void;
|
||||
canScrollNext: boolean;
|
||||
canScrollPrev: boolean;
|
||||
handleKeyDown: (e: KeyboardEvent) => void;
|
||||
options: CarouselOptions;
|
||||
plugins: CarouselPlugins;
|
||||
onInit: (e: CustomEvent<CarouselAPI>) => void;
|
||||
scrollTo: (index: number, jump?: boolean) => void;
|
||||
scrollSnaps: number[];
|
||||
selectedIndex: number;
|
||||
};
|
||||
api: CarouselAPI | undefined
|
||||
orientation: 'horizontal' | 'vertical'
|
||||
scrollNext: () => void
|
||||
scrollPrev: () => void
|
||||
canScrollNext: boolean
|
||||
canScrollPrev: boolean
|
||||
handleKeyDown: (e: KeyboardEvent) => void
|
||||
options: CarouselOptions
|
||||
plugins: CarouselPlugins
|
||||
onInit: (e: CustomEvent<CarouselAPI>) => void
|
||||
scrollTo: (index: number, jump?: boolean) => void
|
||||
scrollSnaps: number[]
|
||||
selectedIndex: number
|
||||
}
|
||||
|
||||
export function setEmblaContext(config: EmblaContext): EmblaContext {
|
||||
setContext(EMBLA_CAROUSEL_CONTEXT, config);
|
||||
return config;
|
||||
setContext(EMBLA_CAROUSEL_CONTEXT, config)
|
||||
return config
|
||||
}
|
||||
|
||||
export function getEmblaContext(name = "This component") {
|
||||
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
|
||||
throw new Error(`${name} must be used within a <Carousel.Root> component`);
|
||||
}
|
||||
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT);
|
||||
export function getEmblaContext(name = 'This component') {
|
||||
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
|
||||
throw new Error(`${name} must be used within a <Carousel.Root> component`)
|
||||
}
|
||||
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import Root from "./carousel.svelte";
|
||||
import Content from "./carousel-content.svelte";
|
||||
import Item from "./carousel-item.svelte";
|
||||
import Previous from "./carousel-previous.svelte";
|
||||
import Next from "./carousel-next.svelte";
|
||||
import Root from './carousel.svelte'
|
||||
import Content from './carousel-content.svelte'
|
||||
import Item from './carousel-item.svelte'
|
||||
import Previous from './carousel-previous.svelte'
|
||||
import Next from './carousel-next.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Previous,
|
||||
Next,
|
||||
//
|
||||
Root as Carousel,
|
||||
Content as CarouselContent,
|
||||
Item as CarouselItem,
|
||||
Previous as CarouselPrevious,
|
||||
Next as CarouselNext,
|
||||
};
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Previous,
|
||||
Next,
|
||||
//
|
||||
Root as Carousel,
|
||||
Content as CarouselContent,
|
||||
Item as CarouselItem,
|
||||
Previous as CarouselPrevious,
|
||||
Next as CarouselNext,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./input.svelte";
|
||||
import Root from './input.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./label.svelte";
|
||||
import Root from './label.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Root from "./radio-group.svelte";
|
||||
import Item from "./radio-group-item.svelte";
|
||||
import Root from './radio-group.svelte'
|
||||
import Item from './radio-group-item.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Item as RadioGroupItem,
|
||||
};
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Item as RadioGroupItem,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
import Root from './separator.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||
import Trigger from "./sheet-trigger.svelte";
|
||||
import Close from "./sheet-close.svelte";
|
||||
import Overlay from "./sheet-overlay.svelte";
|
||||
import Content from "./sheet-content.svelte";
|
||||
import Header from "./sheet-header.svelte";
|
||||
import Footer from "./sheet-footer.svelte";
|
||||
import Title from "./sheet-title.svelte";
|
||||
import Description from "./sheet-description.svelte";
|
||||
import { Dialog as SheetPrimitive } from 'bits-ui'
|
||||
import Trigger from './sheet-trigger.svelte'
|
||||
import Close from './sheet-close.svelte'
|
||||
import Overlay from './sheet-overlay.svelte'
|
||||
import Content from './sheet-content.svelte'
|
||||
import Header from './sheet-header.svelte'
|
||||
import Footer from './sheet-footer.svelte'
|
||||
import Title from './sheet-title.svelte'
|
||||
import Description from './sheet-description.svelte'
|
||||
|
||||
const Root = SheetPrimitive.Root;
|
||||
const Portal = SheetPrimitive.Portal;
|
||||
const Root = SheetPrimitive.Root
|
||||
const Portal = SheetPrimitive.Portal
|
||||
|
||||
export {
|
||||
Root,
|
||||
Close,
|
||||
Trigger,
|
||||
Portal,
|
||||
Overlay,
|
||||
Content,
|
||||
Header,
|
||||
Footer,
|
||||
Title,
|
||||
Description,
|
||||
//
|
||||
Root as Sheet,
|
||||
Close as SheetClose,
|
||||
Trigger as SheetTrigger,
|
||||
Portal as SheetPortal,
|
||||
Overlay as SheetOverlay,
|
||||
Content as SheetContent,
|
||||
Header as SheetHeader,
|
||||
Footer as SheetFooter,
|
||||
Title as SheetTitle,
|
||||
Description as SheetDescription,
|
||||
};
|
||||
Root,
|
||||
Close,
|
||||
Trigger,
|
||||
Portal,
|
||||
Overlay,
|
||||
Content,
|
||||
Header,
|
||||
Footer,
|
||||
Title,
|
||||
Description,
|
||||
//
|
||||
Root as Sheet,
|
||||
Close as SheetClose,
|
||||
Trigger as SheetTrigger,
|
||||
Portal as SheetPortal,
|
||||
Overlay as SheetOverlay,
|
||||
Content as SheetContent,
|
||||
Header as SheetHeader,
|
||||
Footer as SheetFooter,
|
||||
Title as SheetTitle,
|
||||
Description as SheetDescription,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
||||
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
export const SIDEBAR_WIDTH = "16rem";
|
||||
export const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||
export const SIDEBAR_WIDTH_ICON = "3rem";
|
||||
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||
export const SIDEBAR_COOKIE_NAME = 'sidebar:state'
|
||||
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
||||
export const SIDEBAR_WIDTH = '16rem'
|
||||
export const SIDEBAR_WIDTH_MOBILE = '18rem'
|
||||
export const SIDEBAR_WIDTH_ICON = '3rem'
|
||||
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
import { IsMobile } from "$lib/hooks/is-mobile.svelte.js";
|
||||
import { getContext, setContext } from "svelte";
|
||||
import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js";
|
||||
import { IsMobile } from '$lib/hooks/is-mobile.svelte.js'
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js'
|
||||
|
||||
type Getter<T> = () => T;
|
||||
type Getter<T> = () => T
|
||||
|
||||
export type SidebarStateProps = {
|
||||
/**
|
||||
* A getter function that returns the current open state of the sidebar.
|
||||
* We use a getter function here to support `bind:open` on the `Sidebar.Provider`
|
||||
* component.
|
||||
*/
|
||||
open: Getter<boolean>;
|
||||
/**
|
||||
* A getter function that returns the current open state of the sidebar.
|
||||
* We use a getter function here to support `bind:open` on the `Sidebar.Provider`
|
||||
* component.
|
||||
*/
|
||||
open: Getter<boolean>
|
||||
|
||||
/**
|
||||
* A function that sets the open state of the sidebar. To support `bind:open`, we need
|
||||
* a source of truth for changing the open state to ensure it will be synced throughout
|
||||
* the sub-components and any `bind:` references.
|
||||
*/
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
class SidebarState {
|
||||
readonly props: SidebarStateProps;
|
||||
open = $derived.by(() => this.props.open());
|
||||
openMobile = $state(false);
|
||||
setOpen: SidebarStateProps["setOpen"];
|
||||
#isMobile: IsMobile;
|
||||
state = $derived.by(() => (this.open ? "expanded" : "collapsed"));
|
||||
|
||||
constructor(props: SidebarStateProps) {
|
||||
this.setOpen = props.setOpen;
|
||||
this.#isMobile = new IsMobile();
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
// Convenience getter for checking if the sidebar is mobile
|
||||
// without this, we would need to use `sidebar.isMobile.current` everywhere
|
||||
get isMobile() {
|
||||
return this.#isMobile.current;
|
||||
}
|
||||
|
||||
// Event handler to apply to the `<svelte:window>`
|
||||
handleShortcutKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
this.toggle();
|
||||
}
|
||||
};
|
||||
|
||||
setOpenMobile = (value: boolean) => {
|
||||
this.openMobile = value;
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
return this.#isMobile.current
|
||||
? (this.openMobile = !this.openMobile)
|
||||
: this.setOpen(!this.open);
|
||||
};
|
||||
/**
|
||||
* A function that sets the open state of the sidebar. To support `bind:open`, we need
|
||||
* a source of truth for changing the open state to ensure it will be synced throughout
|
||||
* the sub-components and any `bind:` references.
|
||||
*/
|
||||
setOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
const SYMBOL_KEY = "scn-sidebar";
|
||||
class SidebarState {
|
||||
readonly props: SidebarStateProps
|
||||
open = $derived.by(() => this.props.open())
|
||||
openMobile = $state(false)
|
||||
setOpen: SidebarStateProps['setOpen']
|
||||
#isMobile: IsMobile
|
||||
state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'))
|
||||
|
||||
constructor(props: SidebarStateProps) {
|
||||
this.setOpen = props.setOpen
|
||||
this.#isMobile = new IsMobile()
|
||||
this.props = props
|
||||
}
|
||||
|
||||
// Convenience getter for checking if the sidebar is mobile
|
||||
// without this, we would need to use `sidebar.isMobile.current` everywhere
|
||||
get isMobile() {
|
||||
return this.#isMobile.current
|
||||
}
|
||||
|
||||
// Event handler to apply to the `<svelte:window>`
|
||||
handleShortcutKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
this.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
setOpenMobile = (value: boolean) => {
|
||||
this.openMobile = value
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
return this.#isMobile.current
|
||||
? (this.openMobile = !this.openMobile)
|
||||
: this.setOpen(!this.open)
|
||||
}
|
||||
}
|
||||
|
||||
const SYMBOL_KEY = 'scn-sidebar'
|
||||
|
||||
/**
|
||||
* Instantiates a new `SidebarState` instance and sets it in the context.
|
||||
@@ -68,7 +68,7 @@ const SYMBOL_KEY = "scn-sidebar";
|
||||
* @returns The `SidebarState` instance.
|
||||
*/
|
||||
export function setSidebar(props: SidebarStateProps): SidebarState {
|
||||
return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
|
||||
return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,5 +77,5 @@ export function setSidebar(props: SidebarStateProps): SidebarState {
|
||||
* @returns The `SidebarState` instance.
|
||||
*/
|
||||
export function useSidebar(): SidebarState {
|
||||
return getContext(Symbol.for(SYMBOL_KEY));
|
||||
return getContext(Symbol.for(SYMBOL_KEY))
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
import { useSidebar } from "./context.svelte.js";
|
||||
import Content from "./sidebar-content.svelte";
|
||||
import Footer from "./sidebar-footer.svelte";
|
||||
import GroupAction from "./sidebar-group-action.svelte";
|
||||
import GroupContent from "./sidebar-group-content.svelte";
|
||||
import GroupLabel from "./sidebar-group-label.svelte";
|
||||
import Group from "./sidebar-group.svelte";
|
||||
import Header from "./sidebar-header.svelte";
|
||||
import Input from "./sidebar-input.svelte";
|
||||
import Inset from "./sidebar-inset.svelte";
|
||||
import MenuAction from "./sidebar-menu-action.svelte";
|
||||
import MenuBadge from "./sidebar-menu-badge.svelte";
|
||||
import MenuButton from "./sidebar-menu-button.svelte";
|
||||
import MenuItem from "./sidebar-menu-item.svelte";
|
||||
import MenuSkeleton from "./sidebar-menu-skeleton.svelte";
|
||||
import MenuSubButton from "./sidebar-menu-sub-button.svelte";
|
||||
import MenuSubItem from "./sidebar-menu-sub-item.svelte";
|
||||
import MenuSub from "./sidebar-menu-sub.svelte";
|
||||
import Menu from "./sidebar-menu.svelte";
|
||||
import Provider from "./sidebar-provider.svelte";
|
||||
import Rail from "./sidebar-rail.svelte";
|
||||
import Separator from "./sidebar-separator.svelte";
|
||||
import Trigger from "./sidebar-trigger.svelte";
|
||||
import Root from "./sidebar.svelte";
|
||||
import { useSidebar } from './context.svelte.js'
|
||||
import Content from './sidebar-content.svelte'
|
||||
import Footer from './sidebar-footer.svelte'
|
||||
import GroupAction from './sidebar-group-action.svelte'
|
||||
import GroupContent from './sidebar-group-content.svelte'
|
||||
import GroupLabel from './sidebar-group-label.svelte'
|
||||
import Group from './sidebar-group.svelte'
|
||||
import Header from './sidebar-header.svelte'
|
||||
import Input from './sidebar-input.svelte'
|
||||
import Inset from './sidebar-inset.svelte'
|
||||
import MenuAction from './sidebar-menu-action.svelte'
|
||||
import MenuBadge from './sidebar-menu-badge.svelte'
|
||||
import MenuButton from './sidebar-menu-button.svelte'
|
||||
import MenuItem from './sidebar-menu-item.svelte'
|
||||
import MenuSkeleton from './sidebar-menu-skeleton.svelte'
|
||||
import MenuSubButton from './sidebar-menu-sub-button.svelte'
|
||||
import MenuSubItem from './sidebar-menu-sub-item.svelte'
|
||||
import MenuSub from './sidebar-menu-sub.svelte'
|
||||
import Menu from './sidebar-menu.svelte'
|
||||
import Provider from './sidebar-provider.svelte'
|
||||
import Rail from './sidebar-rail.svelte'
|
||||
import Separator from './sidebar-separator.svelte'
|
||||
import Trigger from './sidebar-trigger.svelte'
|
||||
import Root from './sidebar.svelte'
|
||||
|
||||
export {
|
||||
Content,
|
||||
Footer,
|
||||
Group,
|
||||
GroupAction,
|
||||
GroupContent,
|
||||
GroupLabel,
|
||||
Header,
|
||||
Input,
|
||||
Inset,
|
||||
Menu,
|
||||
MenuAction,
|
||||
MenuBadge,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuSkeleton,
|
||||
MenuSub,
|
||||
MenuSubButton,
|
||||
MenuSubItem,
|
||||
Provider,
|
||||
Rail,
|
||||
Root,
|
||||
Separator,
|
||||
//
|
||||
Root as Sidebar,
|
||||
Content as SidebarContent,
|
||||
Footer as SidebarFooter,
|
||||
Group as SidebarGroup,
|
||||
GroupAction as SidebarGroupAction,
|
||||
GroupContent as SidebarGroupContent,
|
||||
GroupLabel as SidebarGroupLabel,
|
||||
Header as SidebarHeader,
|
||||
Input as SidebarInput,
|
||||
Inset as SidebarInset,
|
||||
Menu as SidebarMenu,
|
||||
MenuAction as SidebarMenuAction,
|
||||
MenuBadge as SidebarMenuBadge,
|
||||
MenuButton as SidebarMenuButton,
|
||||
MenuItem as SidebarMenuItem,
|
||||
MenuSkeleton as SidebarMenuSkeleton,
|
||||
MenuSub as SidebarMenuSub,
|
||||
MenuSubButton as SidebarMenuSubButton,
|
||||
MenuSubItem as SidebarMenuSubItem,
|
||||
Provider as SidebarProvider,
|
||||
Rail as SidebarRail,
|
||||
Separator as SidebarSeparator,
|
||||
Trigger as SidebarTrigger,
|
||||
Trigger,
|
||||
useSidebar,
|
||||
};
|
||||
Content,
|
||||
Footer,
|
||||
Group,
|
||||
GroupAction,
|
||||
GroupContent,
|
||||
GroupLabel,
|
||||
Header,
|
||||
Input,
|
||||
Inset,
|
||||
Menu,
|
||||
MenuAction,
|
||||
MenuBadge,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuSkeleton,
|
||||
MenuSub,
|
||||
MenuSubButton,
|
||||
MenuSubItem,
|
||||
Provider,
|
||||
Rail,
|
||||
Root,
|
||||
Separator,
|
||||
//
|
||||
Root as Sidebar,
|
||||
Content as SidebarContent,
|
||||
Footer as SidebarFooter,
|
||||
Group as SidebarGroup,
|
||||
GroupAction as SidebarGroupAction,
|
||||
GroupContent as SidebarGroupContent,
|
||||
GroupLabel as SidebarGroupLabel,
|
||||
Header as SidebarHeader,
|
||||
Input as SidebarInput,
|
||||
Inset as SidebarInset,
|
||||
Menu as SidebarMenu,
|
||||
MenuAction as SidebarMenuAction,
|
||||
MenuBadge as SidebarMenuBadge,
|
||||
MenuButton as SidebarMenuButton,
|
||||
MenuItem as SidebarMenuItem,
|
||||
MenuSkeleton as SidebarMenuSkeleton,
|
||||
MenuSub as SidebarMenuSub,
|
||||
MenuSubButton as SidebarMenuSubButton,
|
||||
MenuSubItem as SidebarMenuSubItem,
|
||||
Provider as SidebarProvider,
|
||||
Rail as SidebarRail,
|
||||
Separator as SidebarSeparator,
|
||||
Trigger as SidebarTrigger,
|
||||
Trigger,
|
||||
useSidebar,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./skeleton.svelte";
|
||||
import Root from './skeleton.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Skeleton,
|
||||
};
|
||||
Root,
|
||||
//
|
||||
Root as Skeleton,
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import Root from "./tabs.svelte";
|
||||
import Content from "./tabs-content.svelte";
|
||||
import List from "./tabs-list.svelte";
|
||||
import Trigger from "./tabs-trigger.svelte";
|
||||
import Root from './tabs.svelte'
|
||||
import Content from './tabs-content.svelte'
|
||||
import List from './tabs-list.svelte'
|
||||
import Trigger from './tabs-trigger.svelte'
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
};
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { Tooltip as TooltipPrimitive } from "bits-ui";
|
||||
import Trigger from "./tooltip-trigger.svelte";
|
||||
import Content from "./tooltip-content.svelte";
|
||||
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;
|
||||
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,
|
||||
};
|
||||
Root,
|
||||
Trigger,
|
||||
Content,
|
||||
Provider,
|
||||
Portal,
|
||||
//
|
||||
Root as Tooltip,
|
||||
Content as TooltipContent,
|
||||
Trigger as TooltipTrigger,
|
||||
Provider as TooltipProvider,
|
||||
Portal as TooltipPortal,
|
||||
}
|
||||
|
||||
106
src/lib/server/LanguageService.ts
Normal file
106
src/lib/server/LanguageService.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { TolgeeStaticData } from '@tolgee/svelte'
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { loadAsync } from 'jszip'
|
||||
|
||||
class TranslationCacheItem {
|
||||
constructor(
|
||||
public translastions: TolgeeStaticData,
|
||||
public timestamp: number,
|
||||
) {}
|
||||
|
||||
get isExpired(): boolean {
|
||||
const now = Date.now()
|
||||
|
||||
const TTL = Number(env.TRANSLATION_CACHE_TTL)
|
||||
|
||||
if (Number.isNaN(TTL)) return false
|
||||
|
||||
return now - this.timestamp > TTL * 1000
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageService {
|
||||
protected cache: Record<string, TranslationCacheItem> = {}
|
||||
|
||||
async getTranslations(
|
||||
languageCodes: string[],
|
||||
): Promise<Record<string, TolgeeStaticData>> {
|
||||
const languageCodesToFetch = new Set<string>()
|
||||
|
||||
const result: Record<string, TolgeeStaticData> = {}
|
||||
|
||||
for (const lang of languageCodes) {
|
||||
const cacheItem = this.cache[lang]
|
||||
|
||||
if (!cacheItem || cacheItem.isExpired) {
|
||||
languageCodesToFetch.add(lang)
|
||||
continue
|
||||
}
|
||||
|
||||
Object.assign(result, { [lang]: cacheItem.translastions })
|
||||
}
|
||||
|
||||
if (languageCodesToFetch.size === 0) {
|
||||
return result
|
||||
}
|
||||
|
||||
const freshTranslations = await this.fetchTranslations(
|
||||
Array.from(languageCodesToFetch),
|
||||
)
|
||||
|
||||
for (const langKey of Object.keys(freshTranslations)) {
|
||||
if (langKey in this.cache) {
|
||||
delete this.cache[langKey]
|
||||
}
|
||||
this.cache[langKey] = new TranslationCacheItem(
|
||||
freshTranslations[langKey],
|
||||
Date.now(),
|
||||
)
|
||||
result[langKey] = Object.assign(freshTranslations[langKey])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
protected async fetchTranslations(languageCodes: string | string[]) {
|
||||
const url = new URL(`${env.TOLGEE_API_URL}/v2/projects/2/export`)
|
||||
|
||||
url.searchParams.append('format', 'JSON')
|
||||
url.searchParams.append('zip', 'true')
|
||||
url.searchParams.append('supportArrays', 'true')
|
||||
|
||||
for (const lang of languageCodes) {
|
||||
url.searchParams.append('languages', lang)
|
||||
}
|
||||
|
||||
if (!env.TOLGEE_API_KEY) {
|
||||
throw new Error('TOLGEE_API_KEY not set')
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-API-Key': env.TOLGEE_API_KEY,
|
||||
Accept: 'application/*',
|
||||
},
|
||||
})
|
||||
|
||||
const zippedBuffer = await response.arrayBuffer()
|
||||
|
||||
const zip = await loadAsync(zippedBuffer)
|
||||
|
||||
const translations: Record<string, TolgeeStaticData> = {}
|
||||
|
||||
for (const [filename, file] of Object.entries(zip.files)) {
|
||||
if (!file.dir && filename.endsWith('.json')) {
|
||||
const content = await file.async('string')
|
||||
const locale = filename.replace('.json', '')
|
||||
translations[locale] = JSON.parse(content)
|
||||
}
|
||||
}
|
||||
|
||||
return translations
|
||||
}
|
||||
}
|
||||
|
||||
export const languageService = new LanguageService()
|
||||
@@ -1,13 +1,17 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
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;
|
||||
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 };
|
||||
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
|
||||
}
|
||||
|
||||
7
src/routes/+layout.server.ts
Normal file
7
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { languageService } from '$lib/server/LanguageService'
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load: LayoutServerLoad = async () => {
|
||||
const translations = await languageService.getTranslations(['de'])
|
||||
return translations
|
||||
}
|
||||
@@ -6,8 +6,22 @@
|
||||
Tolgee,
|
||||
DevTools,
|
||||
FormatSimple,
|
||||
type TolgeeStaticData,
|
||||
} from '@tolgee/svelte'
|
||||
import { TopBar } from '$lib/components/ui/topBar'
|
||||
import { env } from '$env/dynamic/public'
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
let { children, data }: { data: TolgeeStaticData; children: Snippet } =
|
||||
$props()
|
||||
|
||||
function getLang(lang: string) {
|
||||
return async (): Promise<Record<string, any>> => {
|
||||
const res = await fetch(`/lang/${lang}`)
|
||||
const translations = await res.json()
|
||||
return translations
|
||||
}
|
||||
}
|
||||
|
||||
const tolgee = Tolgee()
|
||||
.use(DevTools())
|
||||
@@ -16,14 +30,14 @@
|
||||
language: 'de',
|
||||
|
||||
// for development
|
||||
apiUrl: import.meta.env.VITE_TOLGEE_API_URL,
|
||||
apiKey: import.meta.env.VITE_TOLGEE_API_KEY,
|
||||
apiUrl: env.PUBLIC_TOLGEE_API_URL,
|
||||
apiKey: env.PUBLIC_TOLGEE_API_KEY,
|
||||
|
||||
// for production
|
||||
staticData: {},
|
||||
staticData: {
|
||||
de: data['de'] ?? getLang('de'),
|
||||
},
|
||||
})
|
||||
|
||||
let { children } = $props()
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
10
src/routes/lang/[language]/+server.ts
Normal file
10
src/routes/lang/[language]/+server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { json } from '@sveltejs/kit'
|
||||
import { languageService } from '$lib/server/LanguageService.js'
|
||||
|
||||
export async function GET({ params }) {
|
||||
const { language } = params
|
||||
|
||||
const result = await languageService.getTranslations([language])
|
||||
|
||||
return json(result[language])
|
||||
}
|
||||
Reference in New Issue
Block a user