commit 89ffc1473203b4e86e29c1e28c58067b951aa59a
parent b8234b9494aec681663632b4bc3b67bf4f939662
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Mon, 13 Jan 2025 16:58:09 +0000
apps-lib: refactor app lib
Diffstat:
126 files changed, 6375 insertions(+), 802 deletions(-)
diff --git a/apps-lib/package.json b/apps-lib/package.json
@@ -19,7 +19,8 @@
"types": "./dist/index.d.ts",
"scripts": {
"dev": "svelte-package -w",
- "build": "npm run check && just build && svelte-package",
+ "prebuild": "just build",
+ "build": "npm run check && svelte-package",
"preview": "vite preview",
"package": "svelte-kit sync && svelte-package && publint",
"prepublishOnly": "npm run package",
@@ -47,6 +48,7 @@
"@nostr-dev-kit/ndk-cache-dexie": "^2.5.8",
"@nostr-dev-kit/ndk-svelte": "^2.3.2",
"@radroots/theme": "workspace:*",
+ "@radroots/svelte-maplibre": "workspace:*",
"@sveltekit-i18n/base": "^1.3.7",
"@sveltekit-i18n/parser-icu": "^1.0.8",
"luxon": "^3.5.0",
diff --git a/apps-lib/src/lib/component/button/button-arrow.svelte b/apps-lib/src/lib/component/button/button-arrow.svelte
@@ -0,0 +1,58 @@
+<script lang="ts">
+ import { Glyph, type CallbackPromise } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: {
+ label: string;
+ callback: CallbackPromise;
+ };
+
+ let visible_action = false;
+
+ onMount(async () => {
+ try {
+ document.addEventListener("click", () => toggle(false));
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ onDestroy(() => {
+ document.removeEventListener("click", () => toggle(false));
+ });
+
+ const toggle = (toggle_force?: boolean): void => {
+ visible_action =
+ typeof toggle_force === `boolean` ? toggle_force : !visible_action;
+ };
+</script>
+
+<div class={`relative inline-block`}>
+ <button
+ class={`flex flex-row w-auto h-10 gap-1 justify-center items-center el-re`}
+ on:click|stopPropagation={async () => {
+ toggle();
+ }}
+ >
+ <button
+ class={`${visible_action ? `fade-in` : `hidden`} absolute top-0 left-0 flex flex-row h-full justify-start items-center el-re`}
+ on:click|stopPropagation={async () => {
+ await basis.callback();
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph`,
+ dim: `sm`,
+ weight: `bold`,
+ key: `arrow-left`,
+ }}
+ />
+ </button>
+ <p
+ class={`font-circ font-[700] text-layer-0-glyph text-[1.6rem] tracking-tight ${visible_action ? `translate-x-8` : ``} duration-[350ms] el-re`}
+ >
+ {basis.label || ``}
+ </p>
+ </button>
+</div>
diff --git a/apps-lib/src/lib/component/button/button-layout-pair.svelte b/apps-lib/src/lib/component/button/button-layout-pair.svelte
@@ -0,0 +1,55 @@
+<script lang="ts">
+ import {
+ app_layout,
+ ButtonLayout,
+ Empty,
+ type CallbackPromise,
+ type IDisabledOpt,
+ } from "$lib";
+
+ export let basis: {
+ continue: IDisabledOpt & {
+ label: string;
+ callback: CallbackPromise;
+ };
+ back?: IDisabledOpt & {
+ visible: boolean;
+ label?: string;
+ callback: CallbackPromise;
+ };
+ };
+</script>
+
+<div class={`flex flex-col justify-center items-center`}>
+ <ButtonLayout
+ basis={{
+ disabled: basis.continue.disabled,
+ label: basis.continue.label,
+ callback: basis.continue.callback,
+ }}
+ ></ButtonLayout>
+ {#if basis.back}
+ <div class={`flex flex-col justify-center items-center el-re`}>
+ {#if basis.back.visible}
+ <button
+ class={`group flex flex-row h-12 w-${$app_layout} justify-center items-center fade-in`}
+ on:click|stopPropagation={async () => {
+ if (!basis.back?.disabled) await basis.back?.callback();
+ }}
+ >
+ <p
+ class={`font-sans font-[600] tracking-wide text-layer-1-glyph-shade ${basis.back?.disabled ? `` : `group-active:text-layer-1-glyph/40`} el-re`}
+ >
+ {basis.back.label || ``}
+ </p>
+ </button>
+ {:else}
+ <div
+ class={`flex flex-row h-4 w-full justify-start items-center`}
+ >
+ <Empty></Empty>
+ </div>
+ {/if}
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib/src/lib/component/button/button-layout.svelte b/apps-lib/src/lib/component/button/button-layout.svelte
@@ -0,0 +1,39 @@
+<script lang="ts">
+ import {
+ app_layout,
+ fmt_cl,
+ parse_layer,
+ type CallbackPromise,
+ type IClOpt,
+ type IDisabledOpt,
+ type ILyOpt,
+ } from "$lib";
+
+ export let basis: ILyOpt &
+ IClOpt &
+ IDisabledOpt & {
+ classes_inner?: string;
+ hide_active?: boolean;
+ label: string;
+ callback: CallbackPromise;
+ };
+
+ $: layer = parse_layer(basis.layer, 1);
+
+ $: classes_active = !basis.hide_active
+ ? `layer-1-active-surface layer-1-active-raise-less layer-1-active-ring-less`
+ : ``;
+</script>
+
+<button
+ class={`${fmt_cl(basis.classes)} group flex flex-row h-touch_guide w-${$app_layout} justify-center items-center bg-layer-${layer}-surface rounded-touch ${basis.disabled ? `opacity-60` : classes_active} el-re`}
+ on:click|stopPropagation={async () => {
+ if (!basis.disabled) await basis.callback();
+ }}
+>
+ <p
+ class={`${fmt_cl(basis.classes_inner)} font-sans font-[600] tracking-wide text-layer-${layer}-glyph-shade ${basis.disabled ? `` : `group-active:text-layer-${layer}-glyph/40 `} el-re`}
+ >
+ {basis.label || ``}
+ </p>
+</button>
diff --git a/apps-lib/src/lib/component/carousel/carousel-item.svelte b/apps-lib/src/lib/component/carousel/carousel-item.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+</script>
+
+<div class={`flex flex-col flex-shrink-0 w-[100vw] justify-start items-center`}>
+ <slot />
+</div>
diff --git a/apps-lib/src/lib/component/carousel/carousel.svelte b/apps-lib/src/lib/component/carousel/carousel.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+ import { casl_index } from "$lib";
+</script>
+
+<div class={`relative flex flex-col w-full`}>
+ <div
+ class={`flex transition-transform duration-500`}
+ style={`transform: translateX(-${Math.max($casl_index, 0) * 100}vw)`}
+ >
+ <slot />
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/entry/entry-line.svelte b/apps-lib/src/lib/component/entry/entry-line.svelte
@@ -0,0 +1,56 @@
+<script lang="ts">
+ import {
+ EntryWrap,
+ Glyph,
+ Input,
+ LoadSymbol,
+ parse_layer,
+ type IEntryLine,
+ type LoadingDimension,
+ } from "$lib";
+
+ export let basis: IEntryLine;
+
+ $: layer =
+ typeof basis.wrap?.layer === `boolean`
+ ? parse_layer(0)
+ : parse_layer(basis.wrap?.layer);
+ $: classes_layer =
+ typeof basis.wrap?.layer === `boolean`
+ ? ``
+ : `text-layer-${layer}-glyph`;
+ let loading_dim: LoadingDimension = `sm`;
+ $: loading_dim = basis.wrap?.style === `guide` ? `md` : `sm`;
+</script>
+
+<EntryWrap basis={basis?.wrap}>
+ <Input basis={basis.el}></Input>
+ {#if basis.loading}
+ <div
+ class={`z-5 absolute right-0 top-0 flex flex-row h-full pr-4 justify-end items-center fade-in el-re`}
+ >
+ <LoadSymbol
+ basis={{
+ dim: loading_dim,
+ }}
+ />
+ </div>
+ {:else if basis.notify_inline}
+ {#if `glyph` in basis.notify_inline}
+ <div
+ class={`z-5 absolute el-re right-0 top-0 flex flex-row h-full pr-3 justify-end items-center translate-x-[34px] fade-in`}
+ >
+ <Glyph
+ basis={typeof basis.notify_inline.glyph === `string`
+ ? {
+ key: basis.notify_inline.glyph,
+ dim: `xs+`,
+ weight: `bold`,
+ classes: `${classes_layer}`,
+ }
+ : basis.notify_inline.glyph}
+ ></Glyph>
+ </div>
+ {/if}
+ {/if}
+</EntryWrap>
diff --git a/apps-lib/src/lib/components/entry_wrap.svelte b/apps-lib/src/lib/component/entry/entry-wrap.svelte
diff --git a/apps-lib/src/lib/component/float/float-page-button.svelte b/apps-lib/src/lib/component/float/float-page-button.svelte
@@ -0,0 +1,37 @@
+<script lang="ts">
+ import {
+ Glyph,
+ LoadSymbol,
+ type GeometryScreenPositionHorizontal,
+ type ICb,
+ type IGlyphKey,
+ type ILoadingOpt,
+ } from "$lib";
+
+ export let basis: ICb &
+ ILoadingOpt &
+ IGlyphKey & {
+ posx: Omit<GeometryScreenPositionHorizontal, "center">;
+ };
+</script>
+
+<div class={`absolute top-16 ${basis.posx}-6 flex flex-row`}>
+ <button
+ class={`flex flex-row h-12 w-12 justify-center items-center bg-layer-1-surface rounded-full el-re`}
+ on:click={basis.callback}
+ >
+ {#if basis.loading}
+ <LoadSymbol />
+ {:else}
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph`,
+ dim: `sm+`,
+ weight: `bold`,
+ key: basis.glyph,
+ }}
+ ></Glyph>
+ {/if}
+ </button>
+</div>
+<div class="hidden left-6 right-6"></div>
diff --git a/apps-lib/src/lib/component/float/float-tabs.svelte b/apps-lib/src/lib/component/float/float-tabs.svelte
@@ -0,0 +1,101 @@
+<script lang="ts">
+ import { goto } from "$app/navigation";
+ import { page } from "$app/stores";
+ import { Empty, Glyph } from "$lib";
+</script>
+
+<div
+ class={`absolute bottom-0 left-0 h-24 flex flex-row w-full pt-2 justify-center items-start`}
+>
+ <div class={`flex flex-row justify-between gap-10 items-center`}>
+ <div
+ class={`grid grid-cols-4 flex flex-row h-[3.1rem] px-6 gap-6 justify-start items-center bg-layer-1-surface rounded-full backdrop-blur-lg`}
+ >
+ <button
+ class={`col-span-1 flex flex-row justify-center items-center`}
+ on:click={async () => {
+ await goto(`/`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[26px] text-layer-0-glyph/80 rotate-90`,
+ weight: $page.url.pathname !== `/` ? `bold` : `fill`,
+ key: `columns`,
+ }}
+ ></Glyph>
+ </button>
+ <button
+ class={`relative col-span-1 flex flex-row justify-center items-center`}
+ on:click={async () => {
+ await goto(`/search`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[24px] text-layer-0-glyph/80`,
+ weight: $page.url.pathname.startsWith(`/search`)
+ ? `fill`
+ : `bold`,
+ key: `magnifying-glass`,
+ }}
+ ></Glyph>
+ </button>
+ <button
+ class={`relative col-span-1 flex flex-row justify-center items-center`}
+ on:click={async () => {
+ goto(`/settings/profile`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[24px] text-layer-0-glyph/80`,
+ weight: $page.url.pathname.startsWith(
+ `/settings/profile`,
+ )
+ ? `fill`
+ : `bold`,
+ key: `user`,
+ }}
+ ></Glyph>
+ </button>
+ <button
+ class={`relative col-span-1 flex flex-row h-full justify-center items-center`}
+ on:click={async () => {
+ await goto(`/notifications`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[24px] text-layer-0-glyph/80`,
+ weight: $page.url.pathname.startsWith(`/notifications`)
+ ? `fill`
+ : `bold`,
+ key: `bell`,
+ }}
+ ></Glyph>
+ <div
+ class={`absolute top-2 -right-1 flex flex-row justify-start items-center`}
+ >
+ <div
+ class={`flex flex-row h-2 w-2 justify-start items-center bg-yellow-400 rounded-full`}
+ >
+ <Empty></Empty>
+ </div>
+ </div>
+ </button>
+ </div>
+ <button
+ class={`flex flex-row h-[3.1rem] w-[3.1rem] justify-center items-center bg-layer-1-surface rounded-full backdrop-blur-lg`}
+ on:click={async () => {}}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[22px] text-layer-0-glyph/80`,
+ weight: `bold`,
+ key: `plus`,
+ }}
+ ></Glyph>
+ </button>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/glyph/glyph-button-simple.svelte b/apps-lib/src/lib/component/glyph/glyph-button-simple.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ import {
+ fmt_cl,
+ Glyph,
+ type CallbackPromise,
+ type GlyphKey,
+ type IClOpt,
+ } from "$lib";
+
+ export let basis: IClOpt & {
+ kind?: `primary` | `neutral`;
+ label: string;
+ callback: CallbackPromise;
+ glyph?: GlyphKey;
+ };
+
+ $: classes_kind =
+ basis.kind === `neutral`
+ ? `text-layer-0-glyph-shade`
+ : `text-layer-0-glyph-hl`;
+</script>
+
+<button
+ class={`${fmt_cl(basis.classes)} group flex flex-row justify-center items-center`}
+ on:click={basis.callback}
+>
+ {#if basis.glyph}
+ <Glyph
+ basis={{
+ classes: `${classes_kind}`,
+ dim: `sm+`,
+ weight: `bold`,
+ key: basis.glyph,
+ }}
+ />
+ {/if}
+ <p class={`font-sans font-[600] text-guide ${classes_kind} opacity-active`}>
+ {basis.label}
+ </p>
+</button>
diff --git a/apps-lib/src/lib/component/glyph/glyph-button.svelte b/apps-lib/src/lib/component/glyph/glyph-button.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+ import { fmt_cl, glyph_style_map, type IGlyph } from "$lib";
+
+ export let basis: IGlyph;
+ $: basis = basis;
+
+ $: weight =
+ !basis?.weight || basis?.weight === `regular` ? `` : `-${basis.weight}`;
+ $: styles = basis?.dim
+ ? glyph_style_map.get(basis.dim)
+ : glyph_style_map.get(`sm`);
+</script>
+
+<button
+ class={`${fmt_cl(basis.classes)} flex flex-col justify-center items-center text-[${styles?.gl_1}px] el-re`}
+ on:click={async () => {
+ if (basis.callback) await basis.callback();
+ }}
+>
+ <i class={`ph${weight} ph-${basis.key}`}></i>
+</button>
diff --git a/apps-lib/src/lib/component/glyph/glyph-circle.svelte b/apps-lib/src/lib/component/glyph/glyph-circle.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+ import {
+ type IGlyphCircle,
+ GlyphButton,
+ fmt_cl,
+ glyph_style_map,
+ } from "$lib";
+
+ export let basis: IGlyphCircle;
+
+ $: styles = basis?.glyph?.dim
+ ? glyph_style_map.get(basis?.glyph?.dim)
+ : glyph_style_map.get(`sm`);
+</script>
+
+{#if styles?.dim_1}
+ <div
+ class={`${fmt_cl(basis?.classes_wrap)} flex flex-col h-[${styles?.dim_1}px] w-[${styles?.dim_1}px] justify-center items-center rounded-full el-re`}
+ >
+ <GlyphButton basis={basis?.glyph}></GlyphButton>
+ </div>
+{/if}
diff --git a/apps-lib/src/lib/component/glyph/glyph-title-select-label.svelte b/apps-lib/src/lib/component/glyph/glyph-title-select-label.svelte
@@ -0,0 +1,32 @@
+<script lang="ts">
+ import { ascii } from "$lib";
+
+ export let basis: {
+ label: string;
+ };
+</script>
+
+<div class={`flex flex-row justify-start items-center`}>
+ <p
+ class={`pr-[13px] font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`(${basis.label}`}
+ </p>
+ <div
+ class={`relative flex flex-row justify-start items-center -translate-x-[10px] -translate-y-[1px]`}
+ >
+ <p
+ class={`absolute font-sansd text-trellis_ti text-layer-0-glyph-label uppercase scale-y-[70%] scale-x-[80%] -translate-y-[1px]`}
+ >
+ {`${ascii.up}`}
+ </p>
+ <p
+ class={`absolute font-sansd text-trellis_ti text-layer-0-glyph-label uppercase scale-y-[70%] scale-x-[80%] translate-y-[2px]`}
+ >
+ {`${ascii.down}`}
+ </p>
+ </div>
+ <p class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}>
+ {`)`}
+ </p>
+</div>
diff --git a/apps-lib/src/lib/components/label_display.svelte b/apps-lib/src/lib/component/label/label-display.svelte
diff --git a/apps-lib/src/lib/el/fill.svelte b/apps-lib/src/lib/component/lib/empty.svelte
diff --git a/apps-lib/src/lib/component/lib/fade.svelte b/apps-lib/src/lib/component/lib/fade.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ import type { IBasisOpt } from "$lib/types/component";
+ import type { IClOpt } from "$lib/types/interface";
+ import { fmt_cl } from "$lib/util/app";
+
+ export let basis: IBasisOpt<IClOpt> = undefined;
+</script>
+
+<div class={`${fmt_cl(basis?.classes)} flex`}>
+ <slot />
+</div>
diff --git a/apps-lib/src/lib/component/lib/load-screen.svelte b/apps-lib/src/lib/component/lib/load-screen.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+ import { LoadSymbol } from "$lib";
+ import { fade } from "svelte/transition";
+</script>
+
+<div
+ in:fade={{ duration: 200 }}
+ out:fade={{ delay: 50, duration: 200 }}
+ class={`z-50 absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`}
+>
+ <LoadSymbol basis={{ dim: `lg` }} />
+</div>
diff --git a/apps-lib/src/lib/components/logo_circle_sm.svelte b/apps-lib/src/lib/component/lib/logo-circle-sm.svelte
diff --git a/apps-lib/src/lib/component/lib/logo-circle.svelte b/apps-lib/src/lib/component/lib/logo-circle.svelte
@@ -0,0 +1,41 @@
+<script lang="ts">
+ let array = [` • `, `radroots`, ` • `, `radroots`];
+</script>
+
+<div
+ class={`relative flex flex-col h-[196px] w-full justify-center items-center`}
+>
+ <div
+ class={`relative flex flex-row h-36 w-36 justify-center items-center bg-layer-2-surface rounded-full`}
+ >
+ <p
+ class={`font-sans font-[900] text-6xl text-layer-0-glyph -tracking-[0.4rem] -translate-x-[6px]`}
+ >
+ {"»`,"}
+ </p>
+ <p
+ class={`font-sans font-[900] text-6xl text-layer-0-glyph translate-x-[8px]`}
+ >
+ {"-"}
+ </p>
+ </div>
+ {#each array as char, index}
+ <div
+ class={`char font-sans text-layer-0-glyph/60 text-[0.8rem] text-center uppercase`}
+ style="--angle: {`${(1 / array.length) * index + 0.18}turn`}"
+ >
+ {char}
+ </div>
+ {/each}
+</div>
+
+<style>
+ .char {
+ width: 1em;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%) rotate(var(--angle, 0deg));
+ }
+</style>
diff --git a/apps-lib/src/lib/component/lib/splash-screen.svelte b/apps-lib/src/lib/component/lib/splash-screen.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+ import { LogoCircle } from "$lib";
+ import { fade } from "svelte/transition";
+</script>
+
+<div
+ in:fade={{ duration: 200 }}
+ out:fade={{ delay: 50, duration: 200 }}
+ class={`z-50 absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`}
+>
+ <LogoCircle />
+</div>
diff --git a/apps-lib/src/lib/component/lib/view.svelte b/apps-lib/src/lib/component/lib/view.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+ import { fade } from "svelte/transition";
+
+ export let key: string;
+</script>
+
+<div
+ in:fade={{ duration: 200 }}
+ out:fade={{ delay: 50, duration: 200 }}
+ data-view={key}
+ class={`hidden flex flex-col h-full w-full justify-start items-center`}
+>
+ <slot />
+</div>
diff --git a/apps-lib/src/lib/component/map/map-marker-dot.svelte b/apps-lib/src/lib/component/map/map-marker-dot.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+ import { Empty } from "$lib";
+</script>
+
+<div class="flex flex-row p-1">
+ <div
+ class={`z-20 flex flex-row h-map_circle w-map_circle justify-center items-center bg-white rounded-full shadow-lg`}
+ >
+ <div
+ class={`z-10 flex flex-row h-map_circle_inner w-map_circle_inner justify-center items-center bg-blue-400 rounded-full`}
+ >
+ <Empty></Empty>
+ </div>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/map/map-point-display.svelte b/apps-lib/src/lib/component/map/map-point-display.svelte
@@ -0,0 +1,28 @@
+<script lang="ts">
+ import {
+ app_thc,
+ cfg_map,
+ fmt_cl,
+ type GeolocationPoint,
+ type IClOpt,
+ MapMarkerDot,
+ } from "$lib";
+ import { MapLibre, Marker } from "@radroots/svelte-maplibre";
+
+ export let basis: IClOpt & {
+ point: GeolocationPoint;
+ zoom?: number;
+ };
+</script>
+
+<MapLibre
+ center={basis.point}
+ zoom={basis.zoom || 4}
+ class={`${fmt_cl(basis.classes)} relative aspect-1 w-full`}
+ style={cfg_map.styles.base[$app_thc]}
+ attributionControl={false}
+>
+ <Marker bind:lngLat={basis.point} draggable={false}>
+ <MapMarkerDot />
+ </Marker>
+</MapLibre>
diff --git a/apps-lib/src/lib/component/map/map-point-select.svelte b/apps-lib/src/lib/component/map/map-point-select.svelte
@@ -0,0 +1,70 @@
+<script lang="ts">
+ import {
+ app_thc,
+ cfg_map,
+ Empty,
+ handle_err,
+ MapPopupPointGeolocation,
+ type GeocoderReverseResult,
+ type GeolocationPoint,
+ type IClOpt,
+ type ILcGeocodeCallback,
+ } from "$lib";
+ import { MapLibre, Marker, Popup } from "@radroots/svelte-maplibre";
+
+ let map_center: GeolocationPoint = {
+ ...cfg_map.coords.default,
+ };
+ export let map_point: GeolocationPoint = {
+ ...cfg_map.coords.default,
+ };
+ export let map_point_geoc_r: GeocoderReverseResult | undefined = undefined;
+
+ export let basis: IClOpt & {
+ lc_geocode: ILcGeocodeCallback;
+ };
+
+ $: if (map_point && map_center.lat === 0 && map_center.lng === 0) {
+ map_center = map_point;
+ (async () => {
+ try {
+ map_point_geoc_r = await basis.lc_geocode(map_center);
+ } catch (e) {
+ await handle_err(e, `map-point-select-lc-geocode-init`);
+ }
+ })();
+ }
+</script>
+
+<MapLibre
+ center={map_center}
+ zoom={10}
+ class={`relative h-full w-full`}
+ style={cfg_map.styles.base[$app_thc]}
+ attributionControl={false}
+>
+ <Marker
+ bind:lngLat={map_point}
+ draggable
+ class={`flex flex-row h-map_circle w-map_circle justify-center items-center bg-white rounded-full shadow-lg`}
+ on:dragend={async () => {
+ if (!map_point) return;
+ const geoc_r = await basis.lc_geocode(map_point);
+ if (geoc_r) map_point_geoc_r = geoc_r;
+ }}
+ >
+ <div
+ class={`flex flex-row h-map_circle_inner w-map_circle_inner justify-center items-center bg-blue-400 rounded-full`}
+ >
+ <Empty />
+ </div>
+ <Popup open={true} offset={[0, -10]}>
+ <MapPopupPointGeolocation
+ basis={{
+ point: map_point,
+ geoc_r: map_point_geoc_r,
+ }}
+ />
+ </Popup>
+ </Marker>
+</MapLibre>
diff --git a/apps-lib/src/lib/component/map/map-popup-point-geolocation.svelte b/apps-lib/src/lib/component/map/map-popup-point-geolocation.svelte
@@ -0,0 +1,74 @@
+<script lang="ts">
+ import {
+ geol_lat_fmt,
+ geol_lng_fmt,
+ Glyph,
+ type GeocoderReverseResult,
+ type GeolocationPoint,
+ type IClOpt,
+ } from "$lib";
+ import Fade from "../lib/fade.svelte";
+
+ export let basis: IClOpt & {
+ point?: GeolocationPoint;
+ geoc_r?: GeocoderReverseResult;
+ };
+</script>
+
+<div
+ class={`flex flex-col w-fit px-5 py-[0.7rem] justify-start items-start bg-layer-1-surface rounded-2xl shadow-lg`}
+>
+ <div class={`flex flex-col w-full gap-1 justify-start items-start`}>
+ {#if basis.geoc_r}
+ <Fade
+ basis={{
+ classes: `flex-col w-full gap-1 justify-start items-start`,
+ }}
+ >
+ <div class={`flex flex-row gap-1 justify-start items-center`}>
+ <p
+ class={`font-sans font-[500] text-[0.9rem] text-layer-2-glyph`}
+ >
+ {basis.geoc_r.name}
+ </p>
+ <Glyph
+ basis={{
+ classes: `text-layer-2-glyph -translate-y-[2px]`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `map-pin-simple`,
+ }}
+ ></Glyph>
+ </div>
+ <div
+ class={`flex flex-row w-full gap-1 justify-start items-center`}
+ >
+ <p
+ class={`font-sans font-[500] text-[0.9rem] tracking-tight text-layer-2-glyph`}
+ >
+ {`${basis.geoc_r.admin1_name},`}
+ </p>
+ <p
+ class={`font-sans font-[500] text-[0.9rem] tracking-tight text-layer-2-glyph`}
+ >
+ {`${basis.geoc_r.country_name}`}
+ </p>
+ </div>
+ </Fade>
+ {/if}
+ {#if basis.point}
+ <div class={`flex flex-col w-full justify-start items-start`}>
+ <p
+ class={`font-sans font-[400] text-[0.9rem] text-layer-0-glyph`}
+ >
+ {`${geol_lat_fmt(basis.point.lat, `dms`)}`}
+ </p>
+ <p
+ class={`font-sans font-[400] text-[0.9rem] text-layer-0-glyph`}
+ >
+ {`${geol_lng_fmt(basis.point.lng, `dms`)}`}
+ </p>
+ </div>
+ {/if}
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/nav/nav-option.svelte b/apps-lib/src/lib/component/nav/nav-option.svelte
@@ -0,0 +1,82 @@
+<!-- svelte-ignore a11y-label-has-associated-control -->
+<script lang="ts">
+ import {
+ fmt_cl,
+ Glyph,
+ LoadSymbol,
+ parse_layer,
+ type INavBasisOption,
+ } from "$lib";
+
+ let el_swap: HTMLLabelElement | null = null;
+
+ export let basis: INavBasisOption;
+ $: basis = basis;
+ $: layer = parse_layer(1);
+ $: classes_disabled = basis.disabled ? `opacity-40` : ``;
+</script>
+
+{#if basis?.loading}
+ <div class={`flex flex-row pr-4 justify-center items-center`}>
+ <LoadSymbol />
+ </div>
+{:else}
+ <button
+ class={`${fmt_cl(basis?.classes)} group col-span-4 flex flex-row h-full justify-end items-center ${classes_disabled}`}
+ on:click={async () => {
+ if (!basis.disabled) await basis?.callback(el_swap);
+ }}
+ >
+ {#if `glyph` in basis && basis?.glyph}
+ <Glyph
+ basis={{
+ classes: `group-active:opacity-70 ${basis?.glyph?.classes}`,
+ ...basis?.glyph,
+ }}
+ ></Glyph>
+ {/if}
+ {#if `label` in basis && basis?.label}
+ <div
+ class={`flex flex-row justify-start items-center ${basis?.label && `glyph` in basis?.label && basis.label?.glyph ? `pr-2` : `pr-4`}`}
+ >
+ {#if `swap` in basis?.label}
+ <label
+ bind:this={el_swap}
+ class={`swap${basis?.label?.swap?.toggle ? ` swap-active` : ``}`}
+ >
+ <div class="swap-on">
+ <p
+ class={`${fmt_cl(basis?.label?.swap?.on.classes || `text-nav_prev text-layer-${layer}-glyph-hl group-active:opacity-70`)} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis?.label?.swap?.on.value}
+ </p>
+ </div>
+ <div class="swap-off">
+ <p
+ class={`${fmt_cl(basis?.label?.swap?.off.classes || `text-nav_prev text-layer-${layer}-glyph-hl group-active:opacity-70`)} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis?.label?.swap?.off.value}
+ </p>
+ </div>
+ </label>
+ {:else if `value` in basis?.label}
+ <p
+ class={`${fmt_cl(basis?.label.classes)} font-sans text-nav_prev text-layer-1-glyph-hl group-active:opacity-70 el-re`}
+ >
+ {basis?.label.value}
+ </p>
+ {/if}
+ {#if `glyph` in basis?.label}
+ <Glyph
+ basis={{
+ key: basis?.label?.glyph?.key,
+ classes: `text-layer-1-glyph-hl group-active:opacity-70 ${basis?.label?.glyph?.classes}`,
+ weight: `bold`,
+ dim: `md`,
+ }}
+ ></Glyph>
+ {/if}
+ </div>
+ {/if}
+ </button>
+{/if}
diff --git a/apps-lib/src/lib/component/nav/nav.svelte b/apps-lib/src/lib/component/nav/nav.svelte
@@ -0,0 +1,167 @@
+<script lang="ts">
+ import { goto } from "$app/navigation";
+ import {
+ app_layout,
+ ButtonArrow,
+ Empty,
+ encode_qp_route,
+ fmt_cl,
+ Glyph,
+ LoadSymbol,
+ nav_blur,
+ nav_prev,
+ nav_visible,
+ NavOption,
+ type INavBasis,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: INavBasis;
+ $: basis = basis;
+
+ let el: HTMLElement | null;
+ let el_inner: HTMLElement | null;
+
+ let nav_prev_label = ``;
+
+ $: classes_nav_blur = $nav_blur ? `bg-white/40 backdrop-blur-sm` : ``;
+
+ onMount(async () => {
+ try {
+ nav_visible.set(true);
+ if ($nav_prev.length)
+ nav_prev_label = $nav_prev[$nav_prev.length - 1].label || ``;
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ onDestroy(async () => {
+ try {
+ nav_visible.set(false);
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ const callback_prev = async (): Promise<void> => {
+ try {
+ if (basis.prev.prevent_route) {
+ await basis.prev.prevent_route.callback();
+ return;
+ } else if (basis.prev.callback) await basis.prev.callback();
+ let route_to =
+ typeof basis.prev.route === `string`
+ ? basis.prev.route
+ : encode_qp_route(basis.prev.route[0], basis.prev.route[1]);
+ if ($nav_prev.length) {
+ const nav_prev_li = $nav_prev[$nav_prev.length - 1];
+ $nav_prev = [...$nav_prev.slice(0, -1)];
+ if (nav_prev_li)
+ route_to = encode_qp_route(
+ nav_prev_li.route,
+ nav_prev_li.params,
+ );
+ }
+ await goto(route_to);
+ } catch (e) {
+ console.log(`(error) callback_prev `, e);
+ }
+ };
+</script>
+
+<div
+ bind:this={el}
+ class={`z-10 absolute top-0 left-0 flex flex-col w-full justify-start items-start h-nav_${$app_layout} ${classes_nav_blur} duration-[250ms] el-re`}
+>
+ <div
+ bind:this={el_inner}
+ class={`relative flex flex-col h-full w-full justify-end items-center`}
+ >
+ <div
+ class={`absolute bottom-[6px] left-0 grid grid-cols-12 flex flex-row h-8 w-full justify-start items-center`}
+ >
+ <div
+ class={`col-span-4 flex flex-row w-full justify-start items-center`}
+ >
+ {#if basis.prev.loading}
+ <div
+ class={`flex flex-row pl-4 justify-center items-center`}
+ >
+ <LoadSymbol />
+ </div>
+ {:else if basis.prev.kind === `arrow`}
+ <div
+ class={`flex flex-row w-full pl-8 justify-start items-center`}
+ >
+ <ButtonArrow
+ basis={{
+ label: nav_prev_label || basis.prev.label,
+ callback: async () => {
+ await callback_prev();
+ },
+ }}
+ />
+ </div>
+ {:else}
+ <button
+ class={`group flex flex-row h-full pl-2 justify-start items-center`}
+ on:click={async () => {
+ await callback_prev();
+ }}
+ >
+ <Glyph
+ basis={{
+ key: `caret-left`,
+ weight: `bold`,
+ dim: `md+`,
+ classes: `text-layer-1-glyph-hl group-active:opacity-70 transition-opacity`,
+ }}
+ ></Glyph>
+ {#if nav_prev_label || basis.prev.label}
+ <p
+ class={`font-sans text-nav_prev text-layer-1-glyph-hl group-active:opacity-60 transition-opacity`}
+ >
+ {nav_prev_label || basis.prev.label}
+ </p>
+ {:else}
+ <Empty></Empty>
+ {/if}
+ </button>
+ {/if}
+ </div>
+ <div
+ class={`col-span-4 flex flex-row h-full justify-center items-center`}
+ >
+ {#if basis.title}
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ if (basis.title.callback)
+ await basis.title.callback();
+ }}
+ >
+ {#if `value` in basis.title.label}
+ <p
+ class={`${fmt_cl(basis.title.label.classes)} font-sans text-nav_curr text-layer-1-glyph`}
+ >
+ {basis.title.label.value}
+ </p>
+ {/if}
+ </button>
+ {:else}
+ <Empty></Empty>
+ {/if}
+ </div>
+ <div
+ class={`col-span-4 flex flex-row h-full justify-end items-center`}
+ >
+ {#if basis.option}
+ <NavOption basis={basis.option} />
+ {:else}
+ <Empty></Empty>
+ {/if}
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/page/page-header.svelte b/apps-lib/src/lib/component/page/page-header.svelte
@@ -0,0 +1,41 @@
+<script lang="ts">
+ import {
+ app_layout,
+ callback_route,
+ Empty,
+ ph_blur,
+ type IPageHeader,
+ } from "$lib";
+ import { fade } from "svelte/transition";
+
+ export let basis: IPageHeader;
+</script>
+
+{#if $ph_blur}
+ <div
+ in:fade={{ duration: 50 }}
+ out:fade={{ delay: 50, duration: 200 }}
+ class={`z-20 fixed top-0 left-0 flex flex-row h-nav_${$app_layout} w-full justify-center items-center bg-layer-0-surface-blur/30 backdrop-blur-lg`}
+ >
+ <Empty></Empty>
+ </div>
+{/if}
+<div
+ class={`z-20 sticky top-0 flex flex-row w-full pb-4 px-6 justify-between items-center`}
+>
+ <div class={`flex flex-row justify-start items-center`}>
+ <button
+ class={`flex flex-row gap-1 justify-center items-center`}
+ on:click={async () => {
+ await callback_route(basis.callback_route);
+ }}
+ >
+ <p
+ class={`font-sansd font-[700] text-2xl text-layer-0-glyph capitalize`}
+ >
+ {basis.label || ``}
+ </p>
+ </button>
+ </div>
+ <slot name="header-option" />
+</div>
diff --git a/apps-lib/src/lib/component/page/page-toolbar.svelte b/apps-lib/src/lib/component/page/page-toolbar.svelte
@@ -0,0 +1,55 @@
+<script lang="ts">
+ import { goto } from "$app/navigation";
+ import {
+ Empty,
+ Glyph,
+ LogoCircleSm,
+ PageHeader,
+ type IBasisOpt,
+ type IPageToolbar,
+ } from "$lib";
+
+ export let basis: IBasisOpt<IPageToolbar> = undefined;
+</script>
+
+<div class={`flex flex-row h-12 w-full px-6 justify-between items-center`}>
+ <button
+ class={`flex flex-row gap-2 justify-start items-center`}
+ on:click={async () => {
+ if (basis?.callback) await basis.callback();
+ else await goto(`/`);
+ }}
+ >
+ <LogoCircleSm />
+ <p
+ class={`font-sansd italic font-[700] text-[1.7rem] text-layer-0-glyph lowercase`}
+ >
+ {`radroots`}
+ </p>
+ </button>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ await goto(`/settings`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph`,
+ dim: `lg`,
+ weight: `bold`,
+ key: `gear`,
+ }}
+ ></Glyph>
+ </button>
+</div>
+{#if basis?.header}
+ <div class={`flex flex-row h-5 w-full justify-center items-center`}>
+ <Empty></Empty>
+ </div>
+ <PageHeader basis={basis.header}>
+ <div slot="header-option">
+ <slot name="header-option" />
+ </div>
+ </PageHeader>
+{/if}
diff --git a/apps-lib/src/lib/component/trellis/trellis-default-label.svelte b/apps-lib/src/lib/component/trellis/trellis-default-label.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { fmt_cl, type ITrellisDefaultLabel } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let classes = ``;
+ export let layer: ThemeLayer;
+ export let labels: ITrellisDefaultLabel[];
+</script>
+
+<div class={`${fmt_cl(classes)} flex flex-row`}>
+ <p class={`font-sans text-trellis_ti text-layer-${layer}-glyph-shade`}>
+ {#each labels as label}
+ <span class={`${fmt_cl(label.classes)} font-sans text-trellis_ti`}>
+ {#if `callback` in label}
+ <button
+ class={``}
+ on:click|preventDefault={async () => {
+ if (`callback` in label && label.callback)
+ await label.callback();
+ }}
+ >
+ {label.label}
+ </button>
+ {:else}
+ {label.label}
+ {/if}
+ </span>
+ {/each}
+ </p>
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-end.svelte b/apps-lib/src/lib/component/trellis/trellis-end.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { Glyph, type ITrellisBasisTouchEnd } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisBasisTouchEnd;
+ export let layer: ThemeLayer;
+ export let hide_active: boolean;
+</script>
+
+<div
+ class={`absolute top-0 right-0 h-full w-max flex flex-row justify-center items-center`}
+>
+ <button
+ class={`flex pr-3`}
+ on:click|preventDefault={async (ev) => {
+ if (basis.callback) await basis.callback(ev);
+ }}
+ >
+ {#if basis.glyph}
+ <Glyph
+ basis={{
+ classes: `text-layer-${layer}-glyph-shade ${hide_active ? `` : `group-active:text-layer-${layer}-glyph_a`} translate-y-[1px] opacity-70`,
+ dim: `xs+`,
+ weight: `bold`,
+ ...basis.glyph,
+ }}
+ ></Glyph>
+ {/if}
+ </button>
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-input.svelte b/apps-lib/src/lib/component/trellis/trellis-input.svelte
@@ -0,0 +1,82 @@
+<script lang="ts">
+ import {
+ fmt_cl,
+ fmt_trellis,
+ Glyph,
+ Input,
+ LoadSymbol,
+ type ITrellisBasisInput,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisBasisInput;
+ export let layer: ThemeLayer;
+ export let hide_border_t: boolean;
+ export let hide_border_b: boolean;
+</script>
+
+<div class={`flex flex-row flex-grow h-full w-full`}>
+ <div
+ class={`${fmt_trellis(hide_border_b, hide_border_t)} flex flex-row h-line w-full justify-start items-center border-t-line border-layer-${layer}-surface-edge overflow-hidden`}
+ >
+ {#if basis.line_label && basis.line_label.value}
+ <div
+ class={`${fmt_cl(basis.line_label.classes)} flex flex-row h-full justify-start items-center overflow-x-hidden`}
+ >
+ <p class={`font-sans text-layer-${layer}-glyph_b`}>
+ {basis.line_label.value}
+ </p>
+ </div>
+ {/if}
+ <div
+ class={`relative flex flex-row flex-grow h-full pr-12 justify-start items-center`}
+ >
+ <Input
+ basis={{
+ ...basis.basis,
+ layer: layer,
+ }}
+ ></Input>
+ {#if basis.action}
+ {#if basis.action.visible}
+ <div
+ class={`absolute top-0 right-0 flex flex-row h-full w-12 pr-4 justify-end items-center fade-in`}
+ >
+ {#if basis.action.loading}
+ <div class={`flex flex-row fade-in`}>
+ <LoadSymbol
+ basis={{
+ dim: `glyph-send-button`,
+ blades: 8,
+ classes: `text-layer-${layer}-glyph el-re`,
+ }}
+ />
+ </div>
+ {:else}
+ <button
+ class={`group fade-in-long`}
+ on:click|preventDefault={async () => {
+ if (basis.action.callback)
+ await basis.action.callback();
+ }}
+ >
+ <Glyph
+ basis={basis.action.glyph
+ ? {
+ dim: `md-`,
+ ...basis.action.glyph,
+ }
+ : {
+ key: `plus`,
+ classes: `text-layer-${layer}-glyph`,
+ dim: `md-`,
+ }}
+ ></Glyph>
+ </button>
+ {/if}
+ </div>
+ {/if}
+ {/if}
+ </div>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-line.svelte b/apps-lib/src/lib/component/trellis/trellis-line.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ import { fmt_trellis, LoadSymbol, type CallbackPromiseGeneric } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let loading: boolean = false;
+ export let layer: ThemeLayer;
+ export let callback: CallbackPromiseGeneric<MouseEvent>;
+ export let hide_border_t: boolean;
+ export let hide_border_b: boolean;
+</script>
+
+<button
+ class={`flex flex-row flex-grow overflow-x-hidden`}
+ on:click={async (ev) => {
+ if (callback) await callback(ev);
+ }}
+>
+ <div
+ class={`${fmt_trellis(hide_border_b, hide_border_t)} flex flex-row h-full w-full justify-center items-center border-t-line border-layer-${layer}-surface-edge el-re`}
+ >
+ {#if loading}
+ <div
+ class={`flex flex-row h-full w-full justify-center items-center`}
+ >
+ <LoadSymbol basis={{ dim: `sm` }} />
+ </div>
+ {:else}
+ <div
+ class={`relative group flex flex-row h-line w-full pr-[2px] justify-between items-center el-re`}
+ >
+ <div
+ class={`flex flex-row h-full w-trellis_display justify-between items-center`}
+ >
+ <slot />
+ </div>
+ <slot name="end" />
+ </div>
+ {/if}
+ </div>
+</button>
diff --git a/apps-lib/src/lib/component/trellis/trellis-offset.svelte b/apps-lib/src/lib/component/trellis/trellis-offset.svelte
@@ -0,0 +1,70 @@
+<script lang="ts">
+ import {
+ Empty,
+ fmt_cl,
+ Glyph,
+ GlyphCircle,
+ LoadSymbol,
+ type ITrellisBasisOffset,
+ type ITrellisBasisOffsetMod,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisBasisOffset | undefined = undefined;
+ export let layer: ThemeLayer;
+
+ let mod: ITrellisBasisOffsetMod = `sm`;
+ $: mod = basis && basis.mod ? basis.mod : `sm`;
+</script>
+
+<div class={`flex flex-row h-full`}>
+ {#if mod === `sm`}
+ <div class={`${fmt_cl(``)} flex flex-row h-full w-[22px]`}>
+ <Empty></Empty>
+ </div>
+ {:else if mod === `glyph`}
+ <div class={`flex flex-row pr-[2px]`}>
+ <div class={`${fmt_cl(``)} flex flex-row h-full w-trellisOffset`}>
+ <Empty></Empty>
+ </div>
+ </div>
+ {:else if typeof mod === `object`}
+ <div
+ class={`flex flex-row h-full min-w-[20px] w-trellisOffset justify-center items-center pr-3`}
+ >
+ <button
+ class={`fade-in pl-2 translate-x-[3px] translate-y-[1px]`}
+ on:click|preventDefault={async (ev) => {
+ if (mod.loading) return;
+ else if (typeof basis !== `boolean` && basis?.callback)
+ await basis.callback(ev);
+ }}
+ >
+ {#if mod.loading}
+ <LoadSymbol basis={{ blades: 8, dim: `xs` }} />
+ {:else if `glyph` in mod}
+ <Glyph
+ basis={{
+ classes: mod.glyph.classes
+ ? mod.glyph.classes
+ : `text-layer-${layer}-glyph`,
+ ...mod.glyph,
+ }}
+ ></Glyph>
+ {:else if `glyph_circle` in mod}
+ <GlyphCircle
+ basis={{
+ classes_wrap: mod.glyph_circle?.classes_wrap,
+ glyph: {
+ classes: mod.glyph_circle?.glyph?.classes
+ ? mod.glyph_circle?.glyph?.classes
+ : `text-layer-${layer}-glyph`,
+ ...mod.glyph_circle?.glyph,
+ },
+ }}
+ ></GlyphCircle>
+ {/if}
+ </button>
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-row-display-value.svelte b/apps-lib/src/lib/component/trellis/trellis-row-display-value.svelte
@@ -0,0 +1,41 @@
+<script lang="ts">
+ import {
+ fmt_cl,
+ get_label_classes_kind,
+ Glyph,
+ type ITrellisKindDisplayValue,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisKindDisplayValue;
+ export let layer: ThemeLayer;
+ export let hide_active: boolean;
+</script>
+
+<button
+ class={`z-10 flex flex-grow justify-end`}
+ on:click|stopPropagation={async (ev) => {
+ if (basis.callback) await basis.callback(ev);
+ }}
+>
+ {#if `icon` in basis}
+ <Glyph
+ basis={{
+ classes:
+ basis.icon.classes ||
+ `${get_label_classes_kind(layer, `shade`, hide_active)}`,
+ key: basis.icon.key,
+ weight: `bold`,
+ dim: `sm`,
+ }}
+ ></Glyph>
+ {:else if basis.label}
+ {#if `value` in basis.label}
+ <p
+ class={`${fmt_cl(basis.label.classes)} font-sans text-line_d_e line-clamp-1 text-layer-0-glyph-label el-re`}
+ >
+ {basis.label.value}
+ </p>
+ {/if}
+ {/if}
+</button>
diff --git a/apps-lib/src/lib/component/trellis/trellis-row-label.svelte b/apps-lib/src/lib/component/trellis/trellis-row-label.svelte
@@ -0,0 +1,61 @@
+<script lang="ts">
+ import {
+ GlyphButton,
+ fmt_cl,
+ get_label_classes_kind,
+ type ILabelTupFields,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ILabelTupFields;
+ export let layer: ThemeLayer;
+ export let hide_active: boolean;
+</script>
+
+<div class={`flex flex-row h-full items-center justify-between`}>
+ {#if basis.left && basis.left.length}
+ <div class={`flex flex-row h-full items-center truncate`}>
+ {#each basis.left as title_l}
+ <div
+ class={`${fmt_cl(title_l.classes_wrap)} flex flex-row h-full items-center ${get_label_classes_kind(layer, undefined, hide_active)} ${title_l.hide_truncate ? `` : `truncate`}`}
+ >
+ {#if `glyph` in title_l}
+ <div
+ class={`flex flex-row justify-start items-center pr-2`}
+ >
+ <GlyphButton basis={{ ...title_l.glyph }}
+ ></GlyphButton>
+ </div>
+ {:else if `value` in title_l}
+ <p
+ class={`${fmt_cl(title_l.classes)} font-sans text-line_d ${title_l.hide_truncate ? `` : `truncate`} el-re`}
+ >
+ {title_l.value || ``}
+ </p>
+ {/if}
+ </div>
+ {/each}
+ </div>
+ {/if}
+ {#if basis.right && basis.right.length}
+ <div
+ class={`flex flex-row h-full w-content items-center justify-end pr-4`}
+ >
+ {#each basis.right.reverse() as title_r}
+ <div
+ class={`${fmt_cl(title_r.classes_wrap)} flex flex-row h-full gap-1 items-center ${title_r.hide_truncate ? `` : `truncate`}`}
+ >
+ {#if `glyph` in title_r}
+ <GlyphButton basis={{ ...title_r.glyph }}></GlyphButton>
+ {:else if `value` in title_r}
+ <p
+ class={`${fmt_cl(title_r.classes)} font-sans text-line_d text-layer-${layer}-glyph_d ${title_r.hide_truncate ? `` : `truncate`} el-re`}
+ >
+ {title_r.value || ``}
+ </p>
+ {/if}
+ </div>
+ {/each}
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-select.svelte b/apps-lib/src/lib/component/trellis/trellis-select.svelte
@@ -0,0 +1,57 @@
+<script lang="ts">
+ import {
+ LoadSymbol,
+ SelectMenu,
+ TrellisEnd,
+ TrellisLine,
+ TrellisRowDisplayValue,
+ TrellisRowLabel,
+ type ITrellisBasisSelect,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisBasisSelect;
+ export let layer: ThemeLayer;
+ export let hide_border_t: boolean;
+ export let hide_border_b: boolean;
+ export let hide_active: boolean;
+
+ $: value = basis.el.value;
+ $: loading = typeof basis?.loading === `boolean` ? basis.loading : false;
+</script>
+
+<TrellisLine
+ {layer}
+ {loading}
+ {hide_border_b}
+ {hide_border_t}
+ callback={basis.callback}
+>
+ <TrellisRowLabel basis={basis.label} {layer} {hide_active} />
+ {#if basis.display}
+ <div class={`flex flex-row pr-3 justify-center items-end`}>
+ <SelectMenu {value} basis={basis.el}>
+ <svelte:fragment slot="element">
+ {#if basis.display.loading}
+ <div
+ class={`flex flex-row h-full w-full justify-end items-center`}
+ >
+ <LoadSymbol basis={{ dim: `sm` }} />
+ </div>
+ {:else}
+ <TrellisRowDisplayValue
+ basis={{ ...basis.display }}
+ {layer}
+ {hide_active}
+ />
+ {/if}
+ </svelte:fragment>
+ </SelectMenu>
+ </div>
+ {/if}
+ <div slot="end">
+ {#if basis.end && !basis.display.loading}
+ <TrellisEnd basis={basis.end} {layer} {hide_active} />
+ {/if}
+ </div>
+</TrellisLine>
diff --git a/apps-lib/src/lib/component/trellis/trellis-title.svelte b/apps-lib/src/lib/component/trellis/trellis-title.svelte
@@ -0,0 +1,62 @@
+<script lang="ts">
+ import { Empty, fmt_cl, Glyph, type ITrellisTitle, LabelSwap } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisTitle;
+ export let layer: ThemeLayer = 0;
+
+ $: mod = basis && basis.mod ? basis.mod : `sm`;
+</script>
+
+<div
+ class={`${fmt_cl(basis.classes)} flex flex-row h-[24px] w-full pl-[2px] gap-1 items-center`}
+>
+ <button
+ class={`flex flex-row h-full w-max items-center gap-1 ${mod === `glyph` ? `pl-[36px]` : mod === `sm` ? `pl-[16px]` : ``}`}
+ on:click|preventDefault={async () => {
+ if (basis && basis.callback) await basis.callback();
+ }}
+ >
+ {#if basis.value === true}
+ <Empty></Empty>
+ {:else}
+ <p
+ class={`font-sans text-trellis_ti text-layer-${layer}-glyph-label uppercase`}
+ >
+ {basis.value || ``}
+ </p>
+ {/if}
+ </button>
+ {#if basis.link}
+ <button
+ class={`${fmt_cl(basis.link.classes)} group flex flex-row h-full w-max items-center`}
+ on:click|preventDefault={async () => {
+ if (basis.link && basis.link.callback)
+ await basis.link.callback();
+ }}
+ >
+ {#if basis.link.label}
+ {#if `swap` in basis.link.label}
+ <LabelSwap basis={basis.link.label} />
+ {:else if `value` in basis.link.label}
+ <p
+ class={`${fmt_cl(basis.link.label.classes)} font-sans text-trellis_ti uppercase fade-in`}
+ >
+ {basis.link.label.value || ``}
+ </p>
+ {/if}
+ {/if}
+ {#if basis.link.glyph}
+ <div class={`flex flex-row w-max`}>
+ <Glyph
+ basis={{
+ ...basis.link.glyph,
+ dim: `xs-`,
+ classes: `${fmt_cl(basis.link.glyph.classes)} fade-in`,
+ }}
+ ></Glyph>
+ </div>
+ {/if}
+ </button>
+ {/if}
+</div>
diff --git a/apps-lib/src/lib/component/trellis/trellis-touch.svelte b/apps-lib/src/lib/component/trellis/trellis-touch.svelte
@@ -0,0 +1,34 @@
+<script lang="ts">
+ import {
+ TrellisEnd,
+ TrellisLine,
+ TrellisRowDisplayValue,
+ TrellisRowLabel,
+ type ITrellisBasisTouch,
+ } from "$lib";
+ import type { ThemeLayer } from "@radroots/theme";
+
+ export let basis: ITrellisBasisTouch;
+ export let layer: ThemeLayer;
+ export let hide_border_t: boolean;
+ export let hide_border_b: boolean;
+ export let hide_active: boolean;
+</script>
+
+<TrellisLine {layer} {hide_border_b} {hide_border_t} callback={basis.callback}>
+ <TrellisRowLabel basis={basis.label} {layer} {hide_active} />
+ {#if basis.display}
+ <TrellisRowDisplayValue
+ basis={{
+ ...basis.display,
+ }}
+ {layer}
+ {hide_active}
+ />
+ {/if}
+ <svelte:fragment slot="end">
+ {#if basis.end}
+ <TrellisEnd basis={basis.end} {layer} {hide_active} />
+ {/if}
+ </svelte:fragment>
+</TrellisLine>
diff --git a/apps-lib/src/lib/component/trellis/trellis.svelte b/apps-lib/src/lib/component/trellis/trellis.svelte
@@ -0,0 +1,124 @@
+<script lang="ts">
+ import {
+ app_layout,
+ fmt_cl,
+ lls,
+ parse_layer,
+ TrellisDefaultLabel,
+ TrellisInput,
+ TrellisOffset,
+ TrellisSelect,
+ TrellisTitle,
+ TrellisTouch,
+ type ITrellis,
+ } from "$lib";
+
+ export let basis: { args: ITrellis };
+ $: ({ args } = basis);
+ $: hide_border_t =
+ typeof args.hide_border_top === `boolean` ? args.hide_border_top : true;
+ $: hide_border_b =
+ typeof args.hide_border_bottom === `boolean`
+ ? args.hide_border_bottom
+ : true;
+ $: hide_rounded =
+ typeof args.hide_rounded === `boolean` ? args.hide_rounded : false;
+ $: set_title_background =
+ typeof args.set_title_background === `boolean`
+ ? args.set_title_background
+ : false;
+ $: set_default_background =
+ typeof args.set_default_background === `boolean`
+ ? args.set_default_background
+ : false;
+</script>
+
+<div
+ id={basis.args.id || ``}
+ class={`${fmt_cl(args.classes)} flex flex-col`}
+ data-view={basis.args.view || ``}
+>
+ <div
+ class={`relative flex flex-col h-auto w-${$app_layout} gap-[3px] ${set_title_background ? `bg-layer-${args.layer}-surface` : ``}`}
+ >
+ {#if args.title && (!args.default_el || (args.default_el && args.default_el.show_title))}
+ <TrellisTitle
+ basis={args.title}
+ layer={parse_layer(args.layer - 1)}
+ />
+ {/if}
+ {#if args.default_el}
+ <div
+ class={`flex flex-col h-auto w-full justify-center items-center`}
+ >
+ {#if $$slots.default_el}
+ <slot name="default_el" />
+ {:else if args.default_el}
+ <TrellisDefaultLabel
+ layer={parse_layer(args.layer - 1)}
+ labels={args.default_el.labels
+ ? args.default_el.labels
+ : [
+ {
+ label: `${$lls(`common.no_items_to_display`)}.`,
+ },
+ ]}
+ />
+ {/if}
+ </div>
+ {:else if args.list}
+ <div class={`flex flex-col w-full justify-center items-center`}>
+ {#each args.list as basis}
+ <div
+ class={`${basis.hide_field ? "hidden" : ``} group flex flex-row h-full w-full justify-end items-center bg-layer-${args.layer}-surface ${basis.full_rounded ? `rounded-touch` : ``} ${hide_rounded ? `` : `first:rounded-t-2xl last:rounded-b-2xl`} ${!basis.hide_active ? `active:bg-layer-${args.layer}-surface_a` : ``} el-re`}
+ >
+ <div
+ class={`flex flex-row h-full w-full gap-1 items-center overflow-y-hidden`}
+ >
+ {#if !args.hide_offset}
+ <TrellisOffset
+ basis={basis.offset}
+ layer={args.layer}
+ />
+ {/if}
+ {#if $$slots.offset}
+ <slot name="offset" />
+ {/if}
+ {#if `touch` in basis && basis.touch}
+ <TrellisTouch
+ basis={basis.touch}
+ layer={args.layer}
+ {hide_border_b}
+ {hide_border_t}
+ hide_active={!!basis.hide_active}
+ />
+ {:else if `input` in basis && basis.input}
+ <TrellisInput
+ basis={basis.input}
+ layer={args.layer}
+ {hide_border_b}
+ {hide_border_t}
+ />
+ {:else if `select` in basis && basis.select}
+ <TrellisSelect
+ basis={basis.select}
+ layer={args.layer}
+ {hide_border_b}
+ {hide_border_t}
+ hide_active={!!basis.hide_active}
+ />
+ {/if}
+ </div>
+ </div>
+ {/each}
+ </div>
+ {/if}
+ </div>
+ {#if $$slots.append}
+ <div
+ class={`flex flex-col w-full ${set_default_background ? `bg-layer-${args.layer}-surface` : ``}`}
+ >
+ <slot name="append" />
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib/src/lib/components/button_layout.svelte b/apps-lib/src/lib/components/button_layout.svelte
@@ -1,39 +0,0 @@
-<script lang="ts">
- import {
- app_layout,
- fmt_cl,
- parse_layer,
- type CallbackPromise,
- type IClOpt,
- type IDisabledOpt,
- type ILyOpt,
- } from "$lib";
-
- export let basis: ILyOpt &
- IClOpt &
- IDisabledOpt & {
- classes_inner?: string;
- hide_active?: boolean;
- label: string;
- callback: CallbackPromise;
- };
-
- $: layer = parse_layer(basis.layer, 1);
-
- $: classes_active = !basis.hide_active
- ? `layer-1-active-surface layer-1-active-raise-less layer-1-active-ring-less`
- : ``;
-</script>
-
-<button
- class={`${fmt_cl(basis.classes)} group flex flex-row h-touch_guide w-${$app_layout} justify-center items-center bg-layer-${layer}-surface rounded-touch ${basis.disabled ? `opacity-60` : classes_active} el-re`}
- on:click|stopPropagation={async () => {
- if (!basis.disabled) await basis.callback();
- }}
->
- <p
- class={`${fmt_cl(basis.classes_inner)} font-sans font-[600] tracking-wide text-layer-${layer}-glyph-shade ${basis.disabled ? `` : `group-active:text-layer-${layer}-glyph/40 `} el-re`}
- >
- {basis.label}
- </p>
-</button>
diff --git a/apps-lib/src/lib/components/button_layout_pair.svelte b/apps-lib/src/lib/components/button_layout_pair.svelte
@@ -1,55 +0,0 @@
-<script lang="ts">
- import {
- app_layout,
- ButtonLayout,
- Fill,
- type CallbackPromise,
- type IDisabledOpt,
- } from "$lib";
-
- export let basis: {
- continue: IDisabledOpt & {
- label: string;
- callback: CallbackPromise;
- };
- back?: IDisabledOpt & {
- visible: boolean;
- label?: string;
- callback: CallbackPromise;
- };
- };
-</script>
-
-<div class={`flex flex-col justify-center items-center`}>
- <ButtonLayout
- basis={{
- disabled: basis.continue.disabled,
- label: basis.continue.label,
- callback: basis.continue.callback,
- }}
- />
- {#if basis.back}
- <div class={`flex flex-col justify-center items-center el-re`}>
- {#if basis.back.visible}
- <button
- class={`group flex flex-row h-12 w-${$app_layout} justify-center items-center fade-in`}
- on:click|stopPropagation={async () => {
- if (!basis.back?.disabled) await basis.back?.callback();
- }}
- >
- <p
- class={`font-sans font-[600] tracking-wide text-layer-1-glyph-shade ${basis.back?.disabled ? `` : `group-active:text-layer-1-glyph/40`} el-re`}
- >
- {basis.back.label}
- </p>
- </button>
- {:else}
- <div
- class={`flex flex-row h-4 w-full justify-start items-center`}
- >
- <Fill />
- </div>
- {/if}
- </div>
- {/if}
-</div>
diff --git a/apps-lib/src/lib/components/entry_line.svelte b/apps-lib/src/lib/components/entry_line.svelte
@@ -1,57 +0,0 @@
-<script lang="ts">
- import {
- EntryWrap,
- Glyph,
- InputElement,
- LoadSymbol,
- parse_layer,
- type IEntryLine,
- type LoadingDimension,
- } from "$lib";
-
- export let basis: IEntryLine;
- $: basis = basis;
-
- $: layer =
- typeof basis.wrap?.layer === `boolean`
- ? parse_layer(0)
- : parse_layer(basis.wrap?.layer);
- $: classes_layer =
- typeof basis.wrap?.layer === `boolean`
- ? ``
- : `text-layer-${layer}-glyph`;
- let loading_dim: LoadingDimension = `sm`;
- $: loading_dim = basis.wrap?.style === `guide` ? `md` : `sm`;
-</script>
-
-<EntryWrap basis={basis?.wrap}>
- <InputElement basis={basis.el} />
- {#if basis.loading}
- <div
- class={`z-5 absolute right-0 top-0 flex flex-row h-full pr-4 justify-end items-center fade-in el-re`}
- >
- <LoadSymbol
- basis={{
- dim: loading_dim,
- }}
- />
- </div>
- {:else if basis.notify_inline}
- {#if `glyph` in basis.notify_inline}
- <div
- class={`z-5 absolute el-re right-0 top-0 flex flex-row h-full pr-3 justify-end items-center translate-x-[34px] fade-in`}
- >
- <Glyph
- basis={typeof basis.notify_inline.glyph === `string`
- ? {
- key: basis.notify_inline.glyph,
- dim: `xs+`,
- weight: `bold`,
- classes: `${classes_layer}`,
- }
- : basis.notify_inline.glyph}
- />
- </div>
- {/if}
- {/if}
-</EntryWrap>
diff --git a/apps-lib/src/lib/components/layout_root.svelte b/apps-lib/src/lib/components/layout_root.svelte
@@ -1,6 +0,0 @@
-<script lang="ts">
- import { CssStatic, CssStyles } from "$lib";
-</script>
-
-<CssStatic />
-<CssStyles />
diff --git a/apps-lib/src/lib/components/layout_view.svelte b/apps-lib/src/lib/components/layout_view.svelte
@@ -1,62 +0,0 @@
-<script lang="ts">
- import {
- app_layout,
- fmt_cl,
- layout_view_cover,
- nav_blur,
- nav_visible,
- ph_blur,
- tabs_blur,
- tabs_visible,
- type AppLayoutKey,
- type IClOpt,
- } from "$lib";
- import { onDestroy, onMount } from "svelte";
-
- const styles: Record<AppLayoutKey, string> = {
- mobile_base: "pt-4",
- mobile_y: "pt-16 pb-10",
- };
-
- export let basis: (IClOpt & { fade?: boolean }) | undefined = undefined;
- let el: HTMLElement | null;
-
- onMount(async () => {
- try {
- el?.addEventListener("scroll", scrollChange);
- } catch (e) {
- } finally {
- }
- });
-
- onDestroy(async () => {
- try {
- el?.removeEventListener("scroll", scrollChange);
- } catch (e) {
- } finally {
- }
- });
-
- const scrollChange = (): void => {
- if (Math.max(el?.scrollTop || 0, 0) > 10) nav_blur.set(true);
- else nav_blur.set(false);
- if (Math.max(el?.scrollTop || 0, 0) > 10) tabs_blur.set(true);
- else tabs_blur.set(false);
- if (Math.max(el?.scrollTop || 0, 0) > 50) ph_blur.set(true);
- else ph_blur.set(false);
- };
-</script>
-
-<div
- bind:this={el}
- class={`${fmt_cl(basis?.classes)} absolute top-0 left-0 flex flex-col h-[100vh] w-full justify-start items-center overflow-y-scroll scroll-hide ${
- $layout_view_cover
- ? ``
- : $nav_visible
- ? `pt-h_nav_${$app_layout} ${styles[$app_layout]}`
- : `` //styles[$app_layout]
- } ${$layout_view_cover ? `` : $tabs_visible ? `pb-h_tabs_${$app_layout}` : ``} `}
- class:fade-in={basis?.fade}
->
- <slot />
-</div>
diff --git a/apps-lib/src/lib/components/layout_window.svelte b/apps-lib/src/lib/components/layout_window.svelte
@@ -1,13 +0,0 @@
-<script lang="ts">
- import { app_tilt } from "$lib";
-
- // <Blur />
-</script>
-
-<div
- class={`relative flex flex-col h-[100vh] w-full overflow-x-hidden overflow-y-hidden bg-layer-0-surface ${$app_tilt ? `scale-y-[96%] translate-y-4 rounded-t-[3rem]` : ``} delay-75 duration-200 el-re`}
->
- <div class={`flex flex-col h-full w-full`}>
- <slot />
- </div>
-</div>
diff --git a/apps-lib/src/lib/components/logo_circle.svelte b/apps-lib/src/lib/components/logo_circle.svelte
@@ -1,43 +0,0 @@
-<script lang="ts">
- let separator = ` • `;
- let array = [];
- $: array = [`radroots`, `radroots`].map((i) => [separator, ...i]).flat();
-</script>
-
-<div
- class={`relative flex flex-col h-[196px] w-full justify-center items-center`}
->
- <div
- class={`relative flex flex-row h-36 w-36 justify-center items-center bg-layer-2-surface rounded-full`}
- >
- <p
- class={`font-sans font-[900] text-6xl text-layer-0-glyph -tracking-[0.4rem] -translate-x-[6px]`}
- >
- {"»`,"}
- </p>
- <p
- class={`font-sans font-[900] text-6xl text-layer-0-glyph translate-x-[8px]`}
- >
- {"-"}
- </p>
- </div>
- {#each array as char, index}
- <div
- class={`char font-sans text-layer-0-glyph/60 text-[0.8rem] text-center uppercase`}
- style="--angle: {`${(1 / array.length) * index + 0.18}turn`}"
- >
- {char}
- </div>
- {/each}
-</div>
-
-<style>
- .char {
- width: 1em;
- height: 100%;
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%) rotate(var(--angle, 0deg));
- }
-</style>
diff --git a/apps-lib/src/lib/components/overlay_loading.svelte b/apps-lib/src/lib/components/overlay_loading.svelte
@@ -1,14 +0,0 @@
-<script lang="ts">
- import { app_loading, LoadSymbol } from "$lib";
- import { fade } from "svelte/transition";
-</script>
-
-{#if $app_loading}
- <div
- in:fade={{ duration: 200 }}
- out:fade={{ delay: 50, duration: 200 }}
- class={`z-50 absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`}
- >
- <LoadSymbol basis={{ dim: `lg` }} />
- </div>
-{/if}
diff --git a/apps-lib/src/lib/components/overlay_splash.svelte b/apps-lib/src/lib/components/overlay_splash.svelte
@@ -1,14 +0,0 @@
-<script lang="ts">
- import { app_splash, LogoCircle } from "$lib";
- import { fade } from "svelte/transition";
-</script>
-
-{#if $app_splash}
- <div
- in:fade={{ duration: 200 }}
- out:fade={{ delay: 50, duration: 200 }}
- class={`z-50 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`}
- >
- <LogoCircle />
- </div>
-{/if}
diff --git a/apps-lib/src/lib/el/blur.svelte b/apps-lib/src/lib/el/blur.svelte
@@ -1,13 +0,0 @@
-<script lang="ts">
- import { app_blur, Fill } from "$lib";
-</script>
-
-<div
- class={`z-10 absolute top-0 left-0 modal modal-bottom ${$app_blur ? `modal-open` : ``} h-[100vh] w-full m-0 p-0 backdrop-blur-sm overflow-y-hidden el-re`}
->
- <div
- class={`modal-box h-full w-full m-0 p-0 bg-transparent overflow-hidden el-re`}
- >
- <Fill />
- </div>
-</div>
diff --git a/apps-lib/src/lib/el/css_static.svelte b/apps-lib/src/lib/el/css_static.svelte
@@ -1 +0,0 @@
-<div class="hidden bg-layer-0-surface active:bg-layer-0-surface group-active:bg-layer-0-surface focus:bg-layer-0-surface group-focus:bg-layer-0-surface border-layer-0-surface active:border-layer-0-surface group-active:border-layer-0-surface focus:border-layer-0-surface group-focus:border-layer-0-surface bg-layer-0-surface_a active:bg-layer-0-surface_a group-active:bg-layer-0-surface_a focus:bg-layer-0-surface_a group-focus:bg-layer-0-surface_a border-layer-0-surface_a active:border-layer-0-surface_a group-active:border-layer-0-surface_a focus:border-layer-0-surface_a group-focus:border-layer-0-surface_a bg-layer-0-surface_w active:bg-layer-0-surface_w group-active:bg-layer-0-surface_w focus:bg-layer-0-surface_w group-focus:bg-layer-0-surface_w border-layer-0-surface_w active:border-layer-0-surface_w group-active:border-layer-0-surface_w focus:border-layer-0-surface_w group-focus:border-layer-0-surface_w bg-layer-0-surface-edge active:bg-layer-0-surface-edge group-active:bg-layer-0-surface-edge focus:bg-layer-0-surface-edge group-focus:bg-layer-0-surface-edge border-layer-0-surface-edge active:border-layer-0-surface-edge group-active:border-layer-0-surface-edge focus:border-layer-0-surface-edge group-focus:border-layer-0-surface-edge bg-layer-0-surface-blur active:bg-layer-0-surface-blur group-active:bg-layer-0-surface-blur focus:bg-layer-0-surface-blur group-focus:bg-layer-0-surface-blur border-layer-0-surface-blur active:border-layer-0-surface-blur group-active:border-layer-0-surface-blur focus:border-layer-0-surface-blur group-focus:border-layer-0-surface-blur bg-layer-1-surface active:bg-layer-1-surface group-active:bg-layer-1-surface focus:bg-layer-1-surface group-focus:bg-layer-1-surface border-layer-1-surface active:border-layer-1-surface group-active:border-layer-1-surface focus:border-layer-1-surface group-focus:border-layer-1-surface bg-layer-1-surface_a active:bg-layer-1-surface_a group-active:bg-layer-1-surface_a focus:bg-layer-1-surface_a group-focus:bg-layer-1-surface_a border-layer-1-surface_a active:border-layer-1-surface_a group-active:border-layer-1-surface_a focus:border-layer-1-surface_a group-focus:border-layer-1-surface_a bg-layer-1-surface-edge active:bg-layer-1-surface-edge group-active:bg-layer-1-surface-edge focus:bg-layer-1-surface-edge group-focus:bg-layer-1-surface-edge border-layer-1-surface-edge active:border-layer-1-surface-edge group-active:border-layer-1-surface-edge focus:border-layer-1-surface-edge group-focus:border-layer-1-surface-edge bg-layer-1-surface-err active:bg-layer-1-surface-err group-active:bg-layer-1-surface-err focus:bg-layer-1-surface-err group-focus:bg-layer-1-surface-err border-layer-1-surface-err active:border-layer-1-surface-err group-active:border-layer-1-surface-err focus:border-layer-1-surface-err group-focus:border-layer-1-surface-err bg-layer-1-surface-focus active:bg-layer-1-surface-focus group-active:bg-layer-1-surface-focus focus:bg-layer-1-surface-focus group-focus:bg-layer-1-surface-focus border-layer-1-surface-focus active:border-layer-1-surface-focus group-active:border-layer-1-surface-focus focus:border-layer-1-surface-focus group-focus:border-layer-1-surface-focus bg-layer-2-surface active:bg-layer-2-surface group-active:bg-layer-2-surface focus:bg-layer-2-surface group-focus:bg-layer-2-surface border-layer-2-surface active:border-layer-2-surface group-active:border-layer-2-surface focus:border-layer-2-surface group-focus:border-layer-2-surface bg-layer-2-surface_a active:bg-layer-2-surface_a group-active:bg-layer-2-surface_a focus:bg-layer-2-surface_a group-focus:bg-layer-2-surface_a border-layer-2-surface_a active:border-layer-2-surface_a group-active:border-layer-2-surface_a focus:border-layer-2-surface_a group-focus:border-layer-2-surface_a bg-layer-2-surface-edge active:bg-layer-2-surface-edge group-active:bg-layer-2-surface-edge focus:bg-layer-2-surface-edge group-focus:bg-layer-2-surface-edge border-layer-2-surface-edge active:border-layer-2-surface-edge group-active:border-layer-2-surface-edge focus:border-layer-2-surface-edge group-focus:border-layer-2-surface-edge text-layer-0-glyph active:text-layer-0-glyph group-active:text-layer-0-glyph focus:text-layer-0-glyph group-focus:text-layer-0-glyph text-layer-0-glyph_a active:text-layer-0-glyph_a group-active:text-layer-0-glyph_a focus:text-layer-0-glyph_a group-focus:text-layer-0-glyph_a text-layer-0-glyph_pl active:text-layer-0-glyph_pl group-active:text-layer-0-glyph_pl focus:text-layer-0-glyph_pl group-focus:text-layer-0-glyph_pl text-layer-0-glyph-hl active:text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl focus:text-layer-0-glyph-hl group-focus:text-layer-0-glyph-hl text-layer-0-glyph-hl_a active:text-layer-0-glyph-hl_a group-active:text-layer-0-glyph-hl_a focus:text-layer-0-glyph-hl_a group-focus:text-layer-0-glyph-hl_a text-layer-0-glyph-shade active:text-layer-0-glyph-shade group-active:text-layer-0-glyph-shade focus:text-layer-0-glyph-shade group-focus:text-layer-0-glyph-shade text-layer-0-glyph-label active:text-layer-0-glyph-label group-active:text-layer-0-glyph-label focus:text-layer-0-glyph-label group-focus:text-layer-0-glyph-label text-layer-1-glyph active:text-layer-1-glyph group-active:text-layer-1-glyph focus:text-layer-1-glyph group-focus:text-layer-1-glyph text-layer-1-glyph_a active:text-layer-1-glyph_a group-active:text-layer-1-glyph_a focus:text-layer-1-glyph_a group-focus:text-layer-1-glyph_a text-layer-1-glyph_d active:text-layer-1-glyph_d group-active:text-layer-1-glyph_d focus:text-layer-1-glyph_d group-focus:text-layer-1-glyph_d text-layer-1-glyph_pl active:text-layer-1-glyph_pl group-active:text-layer-1-glyph_pl focus:text-layer-1-glyph_pl group-focus:text-layer-1-glyph_pl text-layer-1-glyph-hl active:text-layer-1-glyph-hl group-active:text-layer-1-glyph-hl focus:text-layer-1-glyph-hl group-focus:text-layer-1-glyph-hl text-layer-1-glyph-hl_a active:text-layer-1-glyph-hl_a group-active:text-layer-1-glyph-hl_a focus:text-layer-1-glyph-hl_a group-focus:text-layer-1-glyph-hl_a text-layer-1-glyph-shade active:text-layer-1-glyph-shade group-active:text-layer-1-glyph-shade focus:text-layer-1-glyph-shade group-focus:text-layer-1-glyph-shade text-layer-1-glyph-label active:text-layer-1-glyph-label group-active:text-layer-1-glyph-label focus:text-layer-1-glyph-label group-focus:text-layer-1-glyph-label text-layer-2-glyph active:text-layer-2-glyph group-active:text-layer-2-glyph focus:text-layer-2-glyph group-focus:text-layer-2-glyph text-layer-2-glyph_a active:text-layer-2-glyph_a group-active:text-layer-2-glyph_a focus:text-layer-2-glyph_a group-focus:text-layer-2-glyph_a text-layer-2-glyph_d active:text-layer-2-glyph_d group-active:text-layer-2-glyph_d focus:text-layer-2-glyph_d group-focus:text-layer-2-glyph_d text-layer-2-glyph_pl active:text-layer-2-glyph_pl group-active:text-layer-2-glyph_pl focus:text-layer-2-glyph_pl group-focus:text-layer-2-glyph_pl text-layer-2-glyph-hl active:text-layer-2-glyph-hl group-active:text-layer-2-glyph-hl focus:text-layer-2-glyph-hl group-focus:text-layer-2-glyph-hl text-layer-2-glyph-hl_a active:text-layer-2-glyph-hl_a group-active:text-layer-2-glyph-hl_a focus:text-layer-2-glyph-hl_a group-focus:text-layer-2-glyph-hl_a text-layer-2-glyph-shade active:text-layer-2-glyph-shade group-active:text-layer-2-glyph-shade focus:text-layer-2-glyph-shade group-focus:text-layer-2-glyph-shade placeholder:text-layer-0-glyph_pl placeholder:text-layer-1-glyph_pl placeholder:text-layer-2-glyph_pl"></div>
-\ No newline at end of file
diff --git a/apps-lib/src/lib/el/css_styles.svelte b/apps-lib/src/lib/el/css_styles.svelte
@@ -1,3 +0,0 @@
-<div class="hidden h-[12px] w-[12px] h-[16px] w-[16px] h-[17px] w-[17px] h-[18px] w-[18px] h-[20px] w-[20px] h-[22px] w-[22px] h-[24px] w-[24px] h-[28px] w-[28px] h-[36px] w-[36px]"></div>
-<div class="hidden text-[12px] text-[15px] text-[16px] text-[18px] text-[19px] text-[20px] text-[21px] text-[23px] text-[24px] text-[26px] text-[27px] text-[28px] text-[30px] text-[36px] text-[40px]"></div>
-<div class="hidden h-tabs_mobile_base pt-h_tabs_mobile_base pb-h_tabs_mobile_base translate-y-h_tabs_mobile_base -translate-y-h_tabs_mobile_base h-tabs_mobile_y pt-h_tabs_mobile_y pb-h_tabs_mobile_y translate-y-h_tabs_mobile_y -translate-y-h_tabs_mobile_y h-nav_mobile_base pt-h_nav_mobile_base pb-h_nav_mobile_base translate-y-h_nav_mobile_base -translate-y-h_nav_mobile_base h-nav_mobile_y pt-h_nav_mobile_y pb-h_nav_mobile_y translate-y-h_nav_mobile_y -translate-y-h_nav_mobile_y h-view_mobile_base pt-h_view_mobile_base pb-h_view_mobile_base translate-y-h_view_mobile_base -translate-y-h_view_mobile_base h-view_mobile_y pt-h_view_mobile_y pb-h_view_mobile_y translate-y-h_view_mobile_y -translate-y-h_view_mobile_y h-view_offset_mobile_base pt-h_view_offset_mobile_base pb-h_view_offset_mobile_base translate-y-h_view_offset_mobile_base -translate-y-h_view_offset_mobile_base h-view_offset_mobile_y pt-h_view_offset_mobile_y pb-h_view_offset_mobile_y translate-y-h_view_offset_mobile_y -translate-y-h_view_offset_mobile_y h-trellis_centered_mobile_base pt-h_trellis_centered_mobile_base pb-h_trellis_centered_mobile_base translate-y-h_trellis_centered_mobile_base -translate-y-h_trellis_centered_mobile_base h-trellis_centered_mobile_y pt-h_trellis_centered_mobile_y pb-h_trellis_centered_mobile_y translate-y-h_trellis_centered_mobile_y -translate-y-h_trellis_centered_mobile_y w-mobile_base w-mobile_y top-dim_map_offset_top_mobile_base top-dim_map_offset_top_mobile_y"></div>
-\ No newline at end of file
diff --git a/apps-lib/src/lib/el/image-blob.svelte b/apps-lib/src/lib/el/image-blob.svelte
@@ -0,0 +1,43 @@
+<script lang="ts">
+ import type { IIdOpt } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: IIdOpt & {
+ data: Uint8Array | undefined;
+ alt?: string;
+ };
+
+ let img_url = ``;
+
+ onMount(async () => {
+ try {
+ if (!basis.data) return;
+ img_url = URL.createObjectURL(
+ new Blob([basis.data], {
+ type: "image/jpeg",
+ }),
+ );
+ } catch (e) {
+ console.log(`(error) image blob `, e);
+ }
+ });
+
+ onDestroy(async () => {
+ try {
+ URL.revokeObjectURL(img_url);
+ } catch (e) {}
+ });
+</script>
+
+{#if img_url}
+ <img id={basis.id || null} src={img_url} alt={basis.alt || null} />
+{/if}
+
+<style>
+ img {
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ display: block;
+ }
+</style>
diff --git a/apps-lib/src/lib/el/image-path.svelte b/apps-lib/src/lib/el/image-path.svelte
@@ -0,0 +1,48 @@
+<!-- svelte-ignore a11y-click-events-have-key-events -->
+<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
+<script lang="ts">
+ import { type ICbGOpt, type IClOpt, type IIdOpt, fmt_cl } from "$lib";
+ import { onMount } from "svelte";
+
+ export let basis: IClOpt &
+ ICbGOpt<
+ MouseEvent & {
+ currentTarget: EventTarget & HTMLImageElement;
+ }
+ > &
+ IIdOpt & {
+ path?: string;
+ alt?: string;
+ };
+
+ let img_src = ``;
+
+ onMount(async () => {
+ try {
+ if (basis.path) img_src = basis.path;
+ } catch (e) {
+ } finally {
+ }
+ });
+</script>
+
+{#if img_src}
+ <img
+ id={basis?.id || null}
+ class={`${fmt_cl(basis?.classes)}`}
+ src={img_src}
+ alt={basis?.alt || null}
+ on:click|stopPropagation={async (ev) => {
+ if (basis?.callback) await basis.callback(ev);
+ }}
+ />
+{/if}
+
+<style>
+ img {
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ display: block;
+ }
+</style>
diff --git a/apps-lib/src/lib/el/input.svelte b/apps-lib/src/lib/el/input.svelte
@@ -0,0 +1,104 @@
+<script lang="ts">
+ import { browser } from "$app/environment";
+ import {
+ type IInput,
+ fmt_cl,
+ kv_basis,
+ parse_layer,
+ value_constrain,
+ } from "$lib";
+ import { onMount } from "svelte";
+
+ let el: HTMLInputElement | null = null;
+
+ export let value: string = ``;
+ export let basis: IInput<string>;
+
+ onMount(async () => {
+ try {
+ await kv_init();
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ $: basis = basis;
+ $: id = basis?.id ? basis?.id : null;
+ $: layer =
+ typeof basis?.layer === `boolean`
+ ? parse_layer(0)
+ : parse_layer(basis?.layer);
+ $: classes_layer =
+ typeof basis?.layer === `boolean` || typeof basis?.layer === `undefined`
+ ? ``
+ : `bg-layer-${layer}-surface text-layer-${layer}-glyph placeholder:text-layer-${layer}-glyph_pl caret-layer-${layer}-glyph`;
+ $: if (basis?.id && basis?.sync && value) {
+ (async () => {
+ try {
+ browser && (await kv_basis.set(basis?.id, value));
+ } catch (e) {}
+ })();
+ }
+
+ const kv_init = async (): Promise<void> => {
+ try {
+ if (!basis?.id) return;
+ if (basis?.sync) {
+ const kv_val = browser && (await kv_basis.get(basis?.id));
+ if (kv_val && el) el.value = kv_val;
+ else browser && (await kv_basis.set(basis?.id, ``));
+ }
+ if (basis?.callback_mount) await basis?.callback_mount({ el });
+ } catch (e) {
+ console.log(`(error) kv_init `, e);
+ }
+ };
+
+ const handle_on_input = async (el: HTMLInputElement): Promise<void> => {
+ try {
+ let pass = true;
+ let val = el?.value;
+ if (basis?.field && el) {
+ val = value_constrain(basis?.field.charset, val);
+ el.value = val;
+ if (
+ !basis?.field.validate.test(val) &&
+ basis?.field.validate_keypress
+ ) {
+ //@todo set styles
+ }
+ pass = basis?.field.validate.test(val);
+ }
+ if (basis?.sync) browser && (await kv_basis.set(basis?.id, val));
+ if (basis?.callback) await basis?.callback({ value: val, pass });
+ } catch (e) {
+ console.log(`(error) handle_on_input `, e);
+ }
+ };
+</script>
+
+<input
+ bind:this={el}
+ bind:value
+ on:input={async ({ currentTarget: el }) => {
+ await handle_on_input(el);
+ }}
+ on:blur={async ({ currentTarget: el }) => {
+ if (basis.callback_blur) await basis.callback_blur({ el });
+ }}
+ on:focus={async ({ currentTarget: el }) => {
+ if (basis.callback_focus) await basis.callback_focus({ el });
+ }}
+ on:keydown={async (ev) => {
+ if (basis?.callback_keydown)
+ await basis?.callback_keydown({
+ key: ev.key,
+ key_s: ev.key === `Enter`,
+ el: ev.currentTarget,
+ });
+ }}
+ {id}
+ type="text"
+ class={`${fmt_cl(basis?.classes)} el-input ${classes_layer} el-re`}
+ placeholder={basis?.placeholder || ``}
+/>
diff --git a/apps-lib/src/lib/el/input_element.svelte b/apps-lib/src/lib/el/input_element.svelte
@@ -1,103 +0,0 @@
-<script lang="ts">
- import {
- type IInputElement,
- fmt_cl,
- kv,
- parse_layer,
- value_constrain,
- } from "$lib";
- import { onMount } from "svelte";
-
- let el: HTMLInputElement | null = null;
-
- export let value: string = ``;
- export let basis: IInputElement<string>;
-
- onMount(async () => {
- try {
- await kv_init();
- } catch (e) {
- } finally {
- }
- });
-
- $: basis = basis;
- $: id = basis?.id ? basis?.id : null;
- $: layer =
- typeof basis?.layer === `boolean`
- ? parse_layer(0)
- : parse_layer(basis?.layer);
- $: classes_layer =
- typeof basis?.layer === `boolean` || typeof basis?.layer === `undefined`
- ? ``
- : `bg-layer-${layer}-surface text-layer-${layer}-glyph placeholder:text-layer-${layer}-glyph_pl caret-layer-${layer}-glyph`;
- $: if (basis?.id && basis?.sync && value) {
- (async () => {
- try {
- await kv.set(basis?.id, value);
- } catch (e) {}
- })();
- }
-
- const kv_init = async (): Promise<void> => {
- try {
- if (!basis?.id) return;
- if (basis?.sync) {
- const kv_val = await kv.get(basis?.id);
- if (kv_val && el) el.value = kv_val;
- else await kv.set(basis?.id, ``);
- }
- if (basis?.callback_mount) await basis?.callback_mount({ el });
- } catch (e) {
- console.log(`(error) kv_init `, e);
- }
- };
-
- const handle_on_input = async (el: HTMLInputElement): Promise<void> => {
- try {
- let pass = true;
- let val = el?.value;
- if (basis?.field && el) {
- val = value_constrain(basis?.field.charset, val);
- el.value = val;
- if (
- !basis?.field.validate.test(val) &&
- basis?.field.validate_keypress
- ) {
- //@todo set styles
- }
- pass = basis?.field.validate.test(val);
- }
- if (basis?.sync) await kv.set(basis?.id, val);
- if (basis?.callback) await basis?.callback({ value: val, pass });
- } catch (e) {
- console.log(`(error) handle_on_input `, e);
- }
- };
-</script>
-
-<input
- bind:this={el}
- bind:value
- on:input={async ({ currentTarget: el }) => {
- await handle_on_input(el);
- }}
- on:blur={async ({ currentTarget: el }) => {
- if (basis.callback_blur) await basis.callback_blur({ el });
- }}
- on:focus={async ({ currentTarget: el }) => {
- if (basis.callback_focus) await basis.callback_focus({ el });
- }}
- on:keydown={async (ev) => {
- if (basis?.callback_keydown)
- await basis?.callback_keydown({
- key: ev.key,
- key_s: ev.key === `Enter`,
- el: ev.currentTarget,
- });
- }}
- {id}
- type="text"
- class={`${fmt_cl(basis?.classes)} el-input ${classes_layer} el-re`}
- placeholder={basis?.placeholder || ``}
-/>
diff --git a/apps-lib/src/lib/el/label-swap.svelte b/apps-lib/src/lib/el/label-swap.svelte
@@ -0,0 +1,32 @@
+<!-- svelte-ignore a11y-label-has-associated-control -->
+<script lang="ts">
+ import { fmt_cl, type ILabelSwap, type ILyOpt } from "$lib";
+
+ export let el_swap: HTMLLabelElement | null = null;
+ export let basis: ILabelSwap & ILyOpt;
+ $: basis = basis;
+
+ $: layer = basis?.layer ? basis.layer : 1;
+</script>
+
+<div class={`flex flex-row justify-start items-center`}>
+ <label
+ bind:this={el_swap}
+ class={`swap${basis.swap.toggle ? ` swap-active` : ``}`}
+ >
+ <div class="swap-on">
+ <p
+ class={`${fmt_cl(basis.swap.on.classes || `text-nav_prev text-layer-${layer}-glyph-hl group-active:opacity-60`)} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis.swap.on.value}
+ </p>
+ </div>
+ <div class="swap-off">
+ <p
+ class={`${fmt_cl(basis.swap.off.classes || `text-nav_prev text-layer-${layer}-glyph-hl group-active:opacity-60`)} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis.swap.off.value}
+ </p>
+ </div>
+ </label>
+</div>
diff --git a/apps-lib/src/lib/el/load_symbol.svelte b/apps-lib/src/lib/el/load-symbol.svelte
diff --git a/apps-lib/src/lib/el/select-menu.svelte b/apps-lib/src/lib/el/select-menu.svelte
@@ -0,0 +1,64 @@
+<script lang="ts">
+ import { fmt_cl, parse_layer, type ISelect } from "$lib";
+
+ export let value: string;
+ export let basis: ISelect;
+ $: basis = basis;
+
+ let el_wrap: HTMLDivElement | null = null;
+ let el_select: HTMLSelectElement | null = null;
+ $: layer =
+ typeof basis?.layer === `boolean`
+ ? parse_layer(0)
+ : parse_layer(basis.layer);
+ $: classes_layer =
+ typeof basis?.layer === `boolean` ? `` : `text-layer-${layer}-glyph`;
+</script>
+
+<div
+ class={`relative flex flex-row h-max w-auto justify-center items-center`}
+ bind:this={el_wrap}
+>
+ <div
+ class={`${fmt_cl(basis.classes)} z-20 absolute top-0 left-0 flex flex-row h-full w-full justify-end items-center ${classes_layer}`}
+ >
+ <select
+ class={`select select-ghost h-full w-full bg-transparent focus:border-0 focus:outline-0 text-transparent focus:text-transparent`}
+ bind:this={el_select}
+ bind:value
+ on:change={async (e) => {
+ const opt = basis.options
+ .map((i) => i.entries)
+ .reduce((_, j) => j, [])
+ .find((k) => k.value === e.currentTarget?.value);
+ if (basis.callback && opt) await basis.callback(opt);
+ if (el_select) el_select.value = value;
+ }}
+ >
+ {#each basis.options as opt_g}
+ {#if opt_g.group}
+ <optgroup>
+ {#each opt_g.entries as opt}
+ <option
+ label={opt_g.group === true
+ ? `-`.repeat(21)
+ : opt_g.group || ``}
+ >
+ {opt.label}
+ </option>
+ {/each}
+ </optgroup>
+ {:else}
+ {#each opt_g.entries as opt}
+ <option value={opt.value} disabled={!!opt.disabled}>
+ {opt.label}
+ </option>
+ {/each}
+ {/if}
+ {/each}
+ </select>
+ </div>
+ <div class={`z-10 flex flex-row h-full w-full`}>
+ <slot name="element" />
+ </div>
+</div>
diff --git a/apps-lib/src/lib/el/select.svelte b/apps-lib/src/lib/el/select.svelte
@@ -0,0 +1,111 @@
+<script lang="ts">
+ import { browser } from "$app/environment";
+ import { fmt_cl, Glyph, kv_basis, parse_layer, type ISelect } from "$lib";
+ import { onMount } from "svelte";
+
+ let el: HTMLSelectElement | null = null;
+
+ export let value: string;
+ export let basis: ISelect;
+
+ $: basis = basis;
+ $: id = basis?.id ? basis?.id : null;
+ $: layer =
+ typeof basis?.layer === `boolean`
+ ? parse_layer(0)
+ : parse_layer(basis.layer);
+ $: classes_layer =
+ typeof basis?.layer === `boolean`
+ ? ``
+ : !value
+ ? `text-layer-${layer}-glyph_pl`
+ : `text-layer-${layer}-glyph_d`;
+ onMount(async () => {
+ try {
+ if (basis.id && basis.sync_init) {
+ const sync_val = browser && (await kv_basis.get(basis.id));
+ browser && (await kv_basis.set(basis.id, sync_val || ``));
+ }
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ $: if (basis?.id && basis?.sync) {
+ (async () => {
+ try {
+ browser && (await kv_basis.set(basis?.id, value));
+ } catch (e) {}
+ })();
+ }
+
+ const handle_on_change = async (el: HTMLSelectElement): Promise<void> => {
+ try {
+ const opt = basis.options
+ .map((i) => i.entries)
+ .reduce((_, j) => j, [])
+ .find((k) => k.value === el?.value);
+ if (el) el.value = value;
+ if (basis?.sync) browser && (await kv_basis.set(basis?.id, value));
+ if (basis.callback && opt) await basis.callback(opt);
+ } catch (e) {
+ console.log(`(error) handle_on_change `, e);
+ }
+ };
+</script>
+
+{#if basis?.show_arrows === "l"}
+ <div class={`flex flex-row justify-center items-center`}>
+ <Glyph
+ basis={{
+ key: `caret-up-down`,
+ dim: `xs`,
+ weight: `bold`,
+ classes: `text-layer-${layer}-glyph translate-y-[1px]`,
+ }}
+ ></Glyph>
+ </div>
+{/if}
+<select
+ bind:this={el}
+ bind:value
+ on:change={async ({ currentTarget: el }) => {
+ handle_on_change(el);
+ }}
+ {id}
+ class={`${fmt_cl(basis.classes)} z-10 el-select ${classes_layer}`}
+>
+ {#each basis.options as opt_g}
+ {#if opt_g.group}
+ <optgroup>
+ {#each opt_g.entries as opt}
+ <option
+ label={opt_g.group === true
+ ? `-`.repeat(21)
+ : opt_g.group || ``}
+ >
+ {opt.label}
+ </option>
+ {/each}
+ </optgroup>
+ {:else}
+ {#each opt_g.entries as opt}
+ <option value={opt.value} disabled={!!opt.disabled}>
+ {opt.label}
+ </option>
+ {/each}
+ {/if}
+ {/each}
+</select>
+{#if basis?.show_arrows === "r"}
+ <div class={`flex flex-row justify-center items-center`}>
+ <Glyph
+ basis={{
+ key: `caret-up-down`,
+ dim: `xs`,
+ weight: `bold`,
+ classes: `text-layer-${layer}-glyph`,
+ }}
+ ></Glyph>
+ </div>
+{/if}
diff --git a/apps-lib/src/lib/el/styles.svelte b/apps-lib/src/lib/el/styles.svelte
@@ -0,0 +1 @@
+<div class="hidden -bottom-lo_view_mobile_base -bottom-lo_view_mobile_y -bottom-nav_mobile_base -bottom-nav_mobile_y -bottom-tabs_mobile_base -bottom-tabs_mobile_y -bottom-trellis_centered_mobile_base -bottom-trellis_centered_mobile_y -bottom-view_mobile_base -bottom-view_mobile_y -bottom-view_offset_mobile_base -bottom-view_offset_mobile_y -top-lo_view_mobile_base -top-lo_view_mobile_y -top-nav_mobile_base -top-nav_mobile_y -top-tabs_mobile_base -top-tabs_mobile_y -top-trellis_centered_mobile_base -top-trellis_centered_mobile_y -top-view_mobile_base -top-view_mobile_y -top-view_offset_mobile_base -top-view_offset_mobile_y -translate-y-h_lo_view_mobile_base -translate-y-h_lo_view_mobile_y -translate-y-h_nav_mobile_base -translate-y-h_nav_mobile_y -translate-y-h_tabs_mobile_base -translate-y-h_tabs_mobile_y -translate-y-h_trellis_centered_mobile_base -translate-y-h_trellis_centered_mobile_y -translate-y-h_view_mobile_base -translate-y-h_view_mobile_y -translate-y-h_view_offset_mobile_base -translate-y-h_view_offset_mobile_y active:bg-layer_0_glyph active:bg-layer_0_glyph_a active:bg-layer_0_glyph_hl active:bg-layer_0_glyph_hl_a active:bg-layer_0_glyph_label active:bg-layer_0_glyph_pl active:bg-layer_0_glyph_shade active:bg-layer_0_surface active:bg-layer_0_surface_a active:bg-layer_0_surface_blur active:bg-layer_0_surface_edge active:bg-layer_0_surface_w active:bg-layer_1_glyph active:bg-layer_1_glyph_a active:bg-layer_1_glyph_d active:bg-layer_1_glyph_hl active:bg-layer_1_glyph_hl_a active:bg-layer_1_glyph_label active:bg-layer_1_glyph_pl active:bg-layer_1_glyph_shade active:bg-layer_1_surface active:bg-layer_1_surface_a active:bg-layer_1_surface_edge active:bg-layer_1_surface_err active:bg-layer_1_surface_focus active:bg-layer_2_glyph active:bg-layer_2_glyph_a active:bg-layer_2_glyph_d active:bg-layer_2_glyph_hl active:bg-layer_2_glyph_hl_a active:bg-layer_2_glyph_pl active:bg-layer_2_glyph_shade active:bg-layer_2_surface active:bg-layer_2_surface_a active:bg-layer_2_surface_edge active:border-layer_0_glyph active:border-layer_0_glyph_a active:border-layer_0_glyph_hl active:border-layer_0_glyph_hl_a active:border-layer_0_glyph_label active:border-layer_0_glyph_pl active:border-layer_0_glyph_shade active:border-layer_0_surface active:border-layer_0_surface_a active:border-layer_0_surface_blur active:border-layer_0_surface_edge active:border-layer_0_surface_w active:border-layer_1_glyph active:border-layer_1_glyph_a active:border-layer_1_glyph_d active:border-layer_1_glyph_hl active:border-layer_1_glyph_hl_a active:border-layer_1_glyph_label active:border-layer_1_glyph_pl active:border-layer_1_glyph_shade active:border-layer_1_surface active:border-layer_1_surface_a active:border-layer_1_surface_edge active:border-layer_1_surface_err active:border-layer_1_surface_focus active:border-layer_2_glyph active:border-layer_2_glyph_a active:border-layer_2_glyph_d active:border-layer_2_glyph_hl active:border-layer_2_glyph_hl_a active:border-layer_2_glyph_pl active:border-layer_2_glyph_shade active:border-layer_2_surface active:border-layer_2_surface_a active:border-layer_2_surface_edge active:text-layer_0_glyph active:text-layer_0_glyph_a active:text-layer_0_glyph_hl active:text-layer_0_glyph_hl_a active:text-layer_0_glyph_label active:text-layer_0_glyph_pl active:text-layer_0_glyph_shade active:text-layer_0_surface active:text-layer_0_surface_a active:text-layer_0_surface_blur active:text-layer_0_surface_edge active:text-layer_0_surface_w active:text-layer_1_glyph active:text-layer_1_glyph_a active:text-layer_1_glyph_d active:text-layer_1_glyph_hl active:text-layer_1_glyph_hl_a active:text-layer_1_glyph_label active:text-layer_1_glyph_pl active:text-layer_1_glyph_shade active:text-layer_1_surface active:text-layer_1_surface_a active:text-layer_1_surface_edge active:text-layer_1_surface_err active:text-layer_1_surface_focus active:text-layer_2_glyph active:text-layer_2_glyph_a active:text-layer_2_glyph_d active:text-layer_2_glyph_hl active:text-layer_2_glyph_hl_a active:text-layer_2_glyph_pl active:text-layer_2_glyph_shade active:text-layer_2_surface active:text-layer_2_surface_a active:text-layer_2_surface_edge bg-layer_0_glyph bg-layer_0_glyph_a bg-layer_0_glyph_hl bg-layer_0_glyph_hl_a bg-layer_0_glyph_label bg-layer_0_glyph_pl bg-layer_0_glyph_shade bg-layer_0_surface bg-layer_0_surface_a bg-layer_0_surface_blur bg-layer_0_surface_edge bg-layer_0_surface_w bg-layer_1_glyph bg-layer_1_glyph_a bg-layer_1_glyph_d bg-layer_1_glyph_hl bg-layer_1_glyph_hl_a bg-layer_1_glyph_label bg-layer_1_glyph_pl bg-layer_1_glyph_shade bg-layer_1_surface bg-layer_1_surface_a bg-layer_1_surface_edge bg-layer_1_surface_err bg-layer_1_surface_focus bg-layer_2_glyph bg-layer_2_glyph_a bg-layer_2_glyph_d bg-layer_2_glyph_hl bg-layer_2_glyph_hl_a bg-layer_2_glyph_pl bg-layer_2_glyph_shade bg-layer_2_surface bg-layer_2_surface_a bg-layer_2_surface_edge border-layer_0_glyph border-layer_0_glyph_a border-layer_0_glyph_hl border-layer_0_glyph_hl_a border-layer_0_glyph_label border-layer_0_glyph_pl border-layer_0_glyph_shade border-layer_0_surface border-layer_0_surface_a border-layer_0_surface_blur border-layer_0_surface_edge border-layer_0_surface_w border-layer_1_glyph border-layer_1_glyph_a border-layer_1_glyph_d border-layer_1_glyph_hl border-layer_1_glyph_hl_a border-layer_1_glyph_label border-layer_1_glyph_pl border-layer_1_glyph_shade border-layer_1_surface border-layer_1_surface_a border-layer_1_surface_edge border-layer_1_surface_err border-layer_1_surface_focus border-layer_2_glyph border-layer_2_glyph_a border-layer_2_glyph_d border-layer_2_glyph_hl border-layer_2_glyph_hl_a border-layer_2_glyph_pl border-layer_2_glyph_shade border-layer_2_surface border-layer_2_surface_a border-layer_2_surface_edge bottom-lo_view_mobile_base bottom-lo_view_mobile_y bottom-nav_mobile_base bottom-nav_mobile_y bottom-tabs_mobile_base bottom-tabs_mobile_y bottom-trellis_centered_mobile_base bottom-trellis_centered_mobile_y bottom-view_mobile_base bottom-view_mobile_y bottom-view_offset_mobile_base bottom-view_offset_mobile_y focus:bg-layer_0_glyph focus:bg-layer_0_glyph_a focus:bg-layer_0_glyph_hl focus:bg-layer_0_glyph_hl_a focus:bg-layer_0_glyph_label focus:bg-layer_0_glyph_pl focus:bg-layer_0_glyph_shade focus:bg-layer_0_surface focus:bg-layer_0_surface_a focus:bg-layer_0_surface_blur focus:bg-layer_0_surface_edge focus:bg-layer_0_surface_w focus:bg-layer_1_glyph focus:bg-layer_1_glyph_a focus:bg-layer_1_glyph_d focus:bg-layer_1_glyph_hl focus:bg-layer_1_glyph_hl_a focus:bg-layer_1_glyph_label focus:bg-layer_1_glyph_pl focus:bg-layer_1_glyph_shade focus:bg-layer_1_surface focus:bg-layer_1_surface_a focus:bg-layer_1_surface_edge focus:bg-layer_1_surface_err focus:bg-layer_1_surface_focus focus:bg-layer_2_glyph focus:bg-layer_2_glyph_a focus:bg-layer_2_glyph_d focus:bg-layer_2_glyph_hl focus:bg-layer_2_glyph_hl_a focus:bg-layer_2_glyph_pl focus:bg-layer_2_glyph_shade focus:bg-layer_2_surface focus:bg-layer_2_surface_a focus:bg-layer_2_surface_edge focus:border-layer_0_glyph focus:border-layer_0_glyph_a focus:border-layer_0_glyph_hl focus:border-layer_0_glyph_hl_a focus:border-layer_0_glyph_label focus:border-layer_0_glyph_pl focus:border-layer_0_glyph_shade focus:border-layer_0_surface focus:border-layer_0_surface_a focus:border-layer_0_surface_blur focus:border-layer_0_surface_edge focus:border-layer_0_surface_w focus:border-layer_1_glyph focus:border-layer_1_glyph_a focus:border-layer_1_glyph_d focus:border-layer_1_glyph_hl focus:border-layer_1_glyph_hl_a focus:border-layer_1_glyph_label focus:border-layer_1_glyph_pl focus:border-layer_1_glyph_shade focus:border-layer_1_surface focus:border-layer_1_surface_a focus:border-layer_1_surface_edge focus:border-layer_1_surface_err focus:border-layer_1_surface_focus focus:border-layer_2_glyph focus:border-layer_2_glyph_a focus:border-layer_2_glyph_d focus:border-layer_2_glyph_hl focus:border-layer_2_glyph_hl_a focus:border-layer_2_glyph_pl focus:border-layer_2_glyph_shade focus:border-layer_2_surface focus:border-layer_2_surface_a focus:border-layer_2_surface_edge focus:text-layer_0_glyph focus:text-layer_0_glyph_a focus:text-layer_0_glyph_hl focus:text-layer_0_glyph_hl_a focus:text-layer_0_glyph_label focus:text-layer_0_glyph_pl focus:text-layer_0_glyph_shade focus:text-layer_0_surface focus:text-layer_0_surface_a focus:text-layer_0_surface_blur focus:text-layer_0_surface_edge focus:text-layer_0_surface_w focus:text-layer_1_glyph focus:text-layer_1_glyph_a focus:text-layer_1_glyph_d focus:text-layer_1_glyph_hl focus:text-layer_1_glyph_hl_a focus:text-layer_1_glyph_label focus:text-layer_1_glyph_pl focus:text-layer_1_glyph_shade focus:text-layer_1_surface focus:text-layer_1_surface_a focus:text-layer_1_surface_edge focus:text-layer_1_surface_err focus:text-layer_1_surface_focus focus:text-layer_2_glyph focus:text-layer_2_glyph_a focus:text-layer_2_glyph_d focus:text-layer_2_glyph_hl focus:text-layer_2_glyph_hl_a focus:text-layer_2_glyph_pl focus:text-layer_2_glyph_shade focus:text-layer_2_surface focus:text-layer_2_surface_a focus:text-layer_2_surface_edge group-active:bg-layer_0_glyph group-active:bg-layer_0_glyph_a group-active:bg-layer_0_glyph_hl group-active:bg-layer_0_glyph_hl_a group-active:bg-layer_0_glyph_label group-active:bg-layer_0_glyph_pl group-active:bg-layer_0_glyph_shade group-active:bg-layer_0_surface group-active:bg-layer_0_surface_a group-active:bg-layer_0_surface_blur group-active:bg-layer_0_surface_edge group-active:bg-layer_0_surface_w group-active:bg-layer_1_glyph group-active:bg-layer_1_glyph_a group-active:bg-layer_1_glyph_d group-active:bg-layer_1_glyph_hl group-active:bg-layer_1_glyph_hl_a group-active:bg-layer_1_glyph_label group-active:bg-layer_1_glyph_pl group-active:bg-layer_1_glyph_shade group-active:bg-layer_1_surface group-active:bg-layer_1_surface_a group-active:bg-layer_1_surface_edge group-active:bg-layer_1_surface_err group-active:bg-layer_1_surface_focus group-active:bg-layer_2_glyph group-active:bg-layer_2_glyph_a group-active:bg-layer_2_glyph_d group-active:bg-layer_2_glyph_hl group-active:bg-layer_2_glyph_hl_a group-active:bg-layer_2_glyph_pl group-active:bg-layer_2_glyph_shade group-active:bg-layer_2_surface group-active:bg-layer_2_surface_a group-active:bg-layer_2_surface_edge group-active:border-layer_0_glyph group-active:border-layer_0_glyph_a group-active:border-layer_0_glyph_hl group-active:border-layer_0_glyph_hl_a group-active:border-layer_0_glyph_label group-active:border-layer_0_glyph_pl group-active:border-layer_0_glyph_shade group-active:border-layer_0_surface group-active:border-layer_0_surface_a group-active:border-layer_0_surface_blur group-active:border-layer_0_surface_edge group-active:border-layer_0_surface_w group-active:border-layer_1_glyph group-active:border-layer_1_glyph_a group-active:border-layer_1_glyph_d group-active:border-layer_1_glyph_hl group-active:border-layer_1_glyph_hl_a group-active:border-layer_1_glyph_label group-active:border-layer_1_glyph_pl group-active:border-layer_1_glyph_shade group-active:border-layer_1_surface group-active:border-layer_1_surface_a group-active:border-layer_1_surface_edge group-active:border-layer_1_surface_err group-active:border-layer_1_surface_focus group-active:border-layer_2_glyph group-active:border-layer_2_glyph_a group-active:border-layer_2_glyph_d group-active:border-layer_2_glyph_hl group-active:border-layer_2_glyph_hl_a group-active:border-layer_2_glyph_pl group-active:border-layer_2_glyph_shade group-active:border-layer_2_surface group-active:border-layer_2_surface_a group-active:border-layer_2_surface_edge group-active:text-layer_0_glyph group-active:text-layer_0_glyph_a group-active:text-layer_0_glyph_hl group-active:text-layer_0_glyph_hl_a group-active:text-layer_0_glyph_label group-active:text-layer_0_glyph_pl group-active:text-layer_0_glyph_shade group-active:text-layer_0_surface group-active:text-layer_0_surface_a group-active:text-layer_0_surface_blur group-active:text-layer_0_surface_edge group-active:text-layer_0_surface_w group-active:text-layer_1_glyph group-active:text-layer_1_glyph_a group-active:text-layer_1_glyph_d group-active:text-layer_1_glyph_hl group-active:text-layer_1_glyph_hl_a group-active:text-layer_1_glyph_label group-active:text-layer_1_glyph_pl group-active:text-layer_1_glyph_shade group-active:text-layer_1_surface group-active:text-layer_1_surface_a group-active:text-layer_1_surface_edge group-active:text-layer_1_surface_err group-active:text-layer_1_surface_focus group-active:text-layer_2_glyph group-active:text-layer_2_glyph_a group-active:text-layer_2_glyph_d group-active:text-layer_2_glyph_hl group-active:text-layer_2_glyph_hl_a group-active:text-layer_2_glyph_pl group-active:text-layer_2_glyph_shade group-active:text-layer_2_surface group-active:text-layer_2_surface_a group-active:text-layer_2_surface_edge group-focus:bg-layer_0_glyph group-focus:bg-layer_0_glyph_a group-focus:bg-layer_0_glyph_hl group-focus:bg-layer_0_glyph_hl_a group-focus:bg-layer_0_glyph_label group-focus:bg-layer_0_glyph_pl group-focus:bg-layer_0_glyph_shade group-focus:bg-layer_0_surface group-focus:bg-layer_0_surface_a group-focus:bg-layer_0_surface_blur group-focus:bg-layer_0_surface_edge group-focus:bg-layer_0_surface_w group-focus:bg-layer_1_glyph group-focus:bg-layer_1_glyph_a group-focus:bg-layer_1_glyph_d group-focus:bg-layer_1_glyph_hl group-focus:bg-layer_1_glyph_hl_a group-focus:bg-layer_1_glyph_label group-focus:bg-layer_1_glyph_pl group-focus:bg-layer_1_glyph_shade group-focus:bg-layer_1_surface group-focus:bg-layer_1_surface_a group-focus:bg-layer_1_surface_edge group-focus:bg-layer_1_surface_err group-focus:bg-layer_1_surface_focus group-focus:bg-layer_2_glyph group-focus:bg-layer_2_glyph_a group-focus:bg-layer_2_glyph_d group-focus:bg-layer_2_glyph_hl group-focus:bg-layer_2_glyph_hl_a group-focus:bg-layer_2_glyph_pl group-focus:bg-layer_2_glyph_shade group-focus:bg-layer_2_surface group-focus:bg-layer_2_surface_a group-focus:bg-layer_2_surface_edge group-focus:border-layer_0_glyph group-focus:border-layer_0_glyph_a group-focus:border-layer_0_glyph_hl group-focus:border-layer_0_glyph_hl_a group-focus:border-layer_0_glyph_label group-focus:border-layer_0_glyph_pl group-focus:border-layer_0_glyph_shade group-focus:border-layer_0_surface group-focus:border-layer_0_surface_a group-focus:border-layer_0_surface_blur group-focus:border-layer_0_surface_edge group-focus:border-layer_0_surface_w group-focus:border-layer_1_glyph group-focus:border-layer_1_glyph_a group-focus:border-layer_1_glyph_d group-focus:border-layer_1_glyph_hl group-focus:border-layer_1_glyph_hl_a group-focus:border-layer_1_glyph_label group-focus:border-layer_1_glyph_pl group-focus:border-layer_1_glyph_shade group-focus:border-layer_1_surface group-focus:border-layer_1_surface_a group-focus:border-layer_1_surface_edge group-focus:border-layer_1_surface_err group-focus:border-layer_1_surface_focus group-focus:border-layer_2_glyph group-focus:border-layer_2_glyph_a group-focus:border-layer_2_glyph_d group-focus:border-layer_2_glyph_hl group-focus:border-layer_2_glyph_hl_a group-focus:border-layer_2_glyph_pl group-focus:border-layer_2_glyph_shade group-focus:border-layer_2_surface group-focus:border-layer_2_surface_a group-focus:border-layer_2_surface_edge group-focus:text-layer_0_glyph group-focus:text-layer_0_glyph_a group-focus:text-layer_0_glyph_hl group-focus:text-layer_0_glyph_hl_a group-focus:text-layer_0_glyph_label group-focus:text-layer_0_glyph_pl group-focus:text-layer_0_glyph_shade group-focus:text-layer_0_surface group-focus:text-layer_0_surface_a group-focus:text-layer_0_surface_blur group-focus:text-layer_0_surface_edge group-focus:text-layer_0_surface_w group-focus:text-layer_1_glyph group-focus:text-layer_1_glyph_a group-focus:text-layer_1_glyph_d group-focus:text-layer_1_glyph_hl group-focus:text-layer_1_glyph_hl_a group-focus:text-layer_1_glyph_label group-focus:text-layer_1_glyph_pl group-focus:text-layer_1_glyph_shade group-focus:text-layer_1_surface group-focus:text-layer_1_surface_a group-focus:text-layer_1_surface_edge group-focus:text-layer_1_surface_err group-focus:text-layer_1_surface_focus group-focus:text-layer_2_glyph group-focus:text-layer_2_glyph_a group-focus:text-layer_2_glyph_d group-focus:text-layer_2_glyph_hl group-focus:text-layer_2_glyph_hl_a group-focus:text-layer_2_glyph_pl group-focus:text-layer_2_glyph_shade group-focus:text-layer_2_surface group-focus:text-layer_2_surface_a group-focus:text-layer_2_surface_edge h-[12px] h-[16px] h-[17px] h-[18px] h-[20px] h-[22px] h-[24px] h-[28px] h-[36px] h-entry_bold h-entry_guide h-entry_line h-envelope_button h-envelope_top h-line h-line_label h-lo_view_mobile_base h-lo_view_mobile_y h-nav_mobile_base h-nav_mobile_y h-tabs_mobile_base h-tabs_mobile_y h-toast_min h-touch_bold h-touch_guide h-trellis_centered_mobile_base h-trellis_centered_mobile_y h-view_mobile_base h-view_mobile_y h-view_offset_mobile_base h-view_offset_mobile_y pb-h_lo_view_mobile_base pb-h_lo_view_mobile_y pb-h_nav_mobile_base pb-h_nav_mobile_y pb-h_tabs_mobile_base pb-h_tabs_mobile_y pb-h_trellis_centered_mobile_base pb-h_trellis_centered_mobile_y pb-h_view_mobile_base pb-h_view_mobile_y pb-h_view_offset_mobile_base pb-h_view_offset_mobile_y pt-h_lo_view_mobile_base pt-h_lo_view_mobile_y pt-h_nav_mobile_base pt-h_nav_mobile_y pt-h_tabs_mobile_base pt-h_tabs_mobile_y pt-h_trellis_centered_mobile_base pt-h_trellis_centered_mobile_y pt-h_view_mobile_base pt-h_view_mobile_y pt-h_view_offset_mobile_base pt-h_view_offset_mobile_y text-[12px] text-[15px] text-[16px] text-[18px] text-[19px] text-[20px] text-[21px] text-[23px] text-[24px] text-[26px] text-[27px] text-[28px] text-[30px] text-[36px] text-[40px] text-layer_0_glyph text-layer_0_glyph_a text-layer_0_glyph_hl text-layer_0_glyph_hl_a text-layer_0_glyph_label text-layer_0_glyph_pl text-layer_0_glyph_shade text-layer_0_surface text-layer_0_surface_a text-layer_0_surface_blur text-layer_0_surface_edge text-layer_0_surface_w text-layer_1_glyph text-layer_1_glyph_a text-layer_1_glyph_d text-layer_1_glyph_hl text-layer_1_glyph_hl_a text-layer_1_glyph_label text-layer_1_glyph_pl text-layer_1_glyph_shade text-layer_1_surface text-layer_1_surface_a text-layer_1_surface_edge text-layer_1_surface_err text-layer_1_surface_focus text-layer_2_glyph text-layer_2_glyph_a text-layer_2_glyph_d text-layer_2_glyph_hl text-layer_2_glyph_hl_a text-layer_2_glyph_pl text-layer_2_glyph_shade text-layer_2_surface text-layer_2_surface_a text-layer_2_surface_edge top-lo_view_mobile_base top-lo_view_mobile_y top-nav_mobile_base top-nav_mobile_y top-tabs_mobile_base top-tabs_mobile_y top-trellis_centered_mobile_base top-trellis_centered_mobile_y top-view_mobile_base top-view_mobile_y top-view_offset_mobile_base top-view_offset_mobile_y translate-y-h_lo_view_mobile_base translate-y-h_lo_view_mobile_y translate-y-h_nav_mobile_base translate-y-h_nav_mobile_y translate-y-h_tabs_mobile_base translate-y-h_tabs_mobile_y translate-y-h_trellis_centered_mobile_base translate-y-h_trellis_centered_mobile_y translate-y-h_view_mobile_base translate-y-h_view_mobile_y translate-y-h_view_offset_mobile_base translate-y-h_view_offset_mobile_y w-[12px] w-[16px] w-[17px] w-[18px] w-[20px] w-[22px] w-[24px] w-[28px] w-[36px] w-mobile_base w-mobile_y"></div>
+\ No newline at end of file
diff --git a/apps-lib/src/lib/el/text-area.svelte b/apps-lib/src/lib/el/text-area.svelte
@@ -0,0 +1,98 @@
+<script lang="ts">
+ import { browser } from "$app/environment";
+ import {
+ type ITextAreaElement,
+ fmt_cl,
+ fmt_textarea_value,
+ kv_basis,
+ parse_layer,
+ value_constrain_textarea,
+ } from "$lib";
+ import { onMount } from "svelte";
+
+ let el: HTMLTextAreaElement | null = null;
+
+ export let value: string = ``;
+ export let basis: ITextAreaElement;
+ $: basis = basis;
+
+ $: id = basis.id ? basis.id : null;
+ $: layer =
+ typeof basis.layer === `boolean` ? 0 : parse_layer(basis.layer, 1); //@todo
+
+ onMount(async () => {
+ try {
+ await kv_init();
+ } catch (e) {}
+ });
+
+ $: if (basis?.id && basis?.sync && value) {
+ (async () => {
+ try {
+ browser && (await kv_basis.set(basis?.id, value));
+ } catch (e) {}
+ })();
+ }
+
+ const kv_init = async (): Promise<void> => {
+ try {
+ if (!basis?.id) return;
+ if (basis?.sync) {
+ const kv_val = browser && (await kv_basis.get(basis?.id));
+ if (kv_val && el) el.value = fmt_textarea_value(kv_val);
+ else browser && (await kv_basis.set(basis?.id, ``));
+ }
+ if (basis?.on_mount) await basis?.on_mount(el);
+ } catch (e) {
+ console.log(`(error) kv_init `, e);
+ }
+ };
+
+ const handle_on_input = async (el: HTMLTextAreaElement): Promise<void> => {
+ try {
+ let pass = true;
+ let val = el?.value;
+ if (basis?.field && el) {
+ val = value_constrain_textarea(basis?.field.charset, val);
+ el.value = fmt_textarea_value(val);
+ if (
+ !basis?.field.validate.test(val) &&
+ basis?.field.validate_keypress
+ ) {
+ //@todo set styles
+ }
+ pass = basis?.field.validate.test(val);
+ }
+ if (basis?.sync) browser && (await kv_basis.set(basis?.id, val));
+ if (basis?.callback) await basis?.callback({ value: val, pass });
+ } catch (e) {
+ console.log(`(error) handle_on_input `, e);
+ }
+ };
+</script>
+
+<textarea
+ bind:this={el}
+ bind:value
+ on:input={async ({ currentTarget: el }) => {
+ await handle_on_input(el);
+ }}
+ on:blur={async ({ currentTarget: el }) => {
+ if (basis.callback_blur) await basis.callback_blur({ el });
+ }}
+ on:focus={async ({ currentTarget: el }) => {
+ if (basis.callback_focus) await basis.callback_focus({ el });
+ }}
+ on:keydown={async (ev) => {
+ if (basis.callback_keydown)
+ await basis.callback_keydown({
+ key: ev.key,
+ key_s: ev.key === `Enter`,
+ el: ev.currentTarget,
+ });
+ }}
+ {id}
+ contenteditable="true"
+ class={`${fmt_cl(basis.classes)} el-textarea w-full bg-layer-${layer}-surface text-layer-${layer}-glyph placeholder:text-layer-${layer}-glyph_pl caret-layer-${layer}-glyph`}
+ placeholder={basis.placeholder || ``}
+></textarea>
diff --git a/apps-lib/src/lib/el/toast.svelte b/apps-lib/src/lib/el/toast.svelte
@@ -0,0 +1,55 @@
+<script lang="ts">
+ import {
+ app_layout,
+ fmt_cl,
+ get_layout,
+ Glyph,
+ parse_layer,
+ toast_layout_map,
+ toast_style_map,
+ type IToast,
+ type IToastKind,
+ } from "$lib";
+
+ export let basis: IToast;
+ $: basis = basis;
+
+ let styles: IToastKind[] = basis.styles || [`simple`];
+ $: styles = styles;
+ $: layer = basis.layer ? parse_layer(basis.layer) : 1;
+ $: layout = get_layout($app_layout);
+</script>
+
+<div
+ class={`${fmt_cl(toast_layout_map.get(layout))} z-[1000] h-[100vh] toast w-full ${basis.position || `top-center`} `}
+>
+ <div class={`flex flex-row w-full h-max justify-center pb-2`}>
+ <div
+ class={`${fmt_cl(basis.classes)} relative grid grid-cols-12 h-max items-center justify-center ${styles.includes(`simple`) ? `bg-layer-${layer}-surface` : ``} ${fmt_cl(styles.map((style) => fmt_cl(toast_style_map.get(style)?.outer)).join(` `))}`}
+ >
+ <div
+ class={`absolute top-0 left-4 flex flex-row h-full items-center text-layer-${layer}-glyph`}
+ >
+ <Glyph
+ basis={{
+ key: `info`,
+ weight: `regular`,
+ dim: `md`,
+ ...basis.glyph,
+ }}
+ ></Glyph>
+ </div>
+ <div
+ class={`col-span-12 flex flex-row pl-1 ${fmt_cl(styles.map((style) => fmt_cl(toast_style_map.get(style)?.inner)).join(` `))}`}
+ >
+ {#if `value` in basis.label}
+ <p
+ class={`font-sans font-[500] truncate text-layer-${layer}-glyph -translate-y-[1px]`}
+ >
+ {basis.label.value}
+ </p>
+ {/if}
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/feature/image-upload-add-photo.svelte b/apps-lib/src/lib/feature/image-upload-add-photo.svelte
@@ -0,0 +1,36 @@
+<script lang="ts">
+ import { Glyph, lls, type CallbackPromiseReturn } from "$lib";
+
+ export let bv_photo_path: string;
+
+ export let basis: {
+ lc_handle_photo_add: CallbackPromiseReturn<string | undefined>;
+ };
+</script>
+
+<div class={`relative flex flex-row w-full justify-center items-center`}>
+ <button
+ class={`flex flex-row h-[5rem] w-[5rem] justify-center items-center bg-layer-1-surface/60 rounded-full`}
+ on:click={async () => {
+ const photo_path_add = await basis.lc_handle_photo_add();
+ if (photo_path_add) bv_photo_path = photo_path_add;
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `text-[40px] text-layer-2-glyph`,
+ dim: `sm`,
+ key: `camera`,
+ }}
+ ></Glyph>
+ <div
+ class={`absolute -bottom-[1.8rem] flex flex-row justify-start items-center`}
+ >
+ <p
+ class={`font-arch font-[600] text-sm text-layer-0-glyph capitalize`}
+ >
+ {`${$lls(`icu.add_*`, { value: `${$lls(`common.photo`)}` })}`}
+ </p>
+ </div>
+ </button>
+</div>
diff --git a/apps-lib/src/lib/feature/search-result-container.svelte b/apps-lib/src/lib/feature/search-result-container.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+ import type { CallbackPromise } from "$lib";
+
+ export let basis: {
+ callback: CallbackPromise;
+ };
+</script>
+
+<button
+ on:click={async () => {
+ await basis.callback();
+ }}
+ class={`group flex flex-row h-[4rem] w-full px-4 justify-between items-center bg-layer-1-surface round-24 layer-1-active-surface el-re`}
+>
+ <slot />
+</button>
diff --git a/apps-lib/src/lib/feature/search-result-display.svelte b/apps-lib/src/lib/feature/search-result-display.svelte
@@ -0,0 +1,216 @@
+<script lang="ts">
+ import {
+ ascii,
+ Glyph,
+ lls,
+ SearchResultContainer,
+ type ISearchResultDisplayCallbacks,
+ type SearchServiceResult,
+ } from "$lib";
+
+ export let basis: ISearchResultDisplayCallbacks & {
+ result: SearchServiceResult;
+ };
+</script>
+
+{#if `location_gcs` in basis.result && basis.result.location_gcs.id}
+ <SearchResultContainer
+ basis={{
+ callback: async () => {
+ await basis.lc_handle_result_location_gcs(
+ basis.result.location_gcs,
+ );
+ },
+ }}
+ >
+ <div class={`flex flex-row gap-4 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-stone-500 round-24`}
+ >
+ <Glyph
+ basis={{
+ classes: `text-white`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `compass`,
+ }}
+ ></Glyph>
+ </div>
+ <div class={`flex flex-row gap-1 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {`${$lls(`common.location`)}`}
+ </p>
+ </div>
+ <p
+ class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`}
+ >
+ {ascii.bullet}
+ </p>
+
+ {#if basis.result.location_gcs.kind === `farm_land`}
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-lime-500 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {`${$lls(`common.farm_land`)}`}
+ </p>
+ </div>
+ {/if}
+ </div>
+ </div>
+ <div
+ class={`flex flex-row flex-grow pl-4 pr-2 justify-end items-center overflow-hidden`}
+ >
+ {#if basis.result.location_gcs.label}
+ <p
+ class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph`}
+ >
+ {`${basis.result.location_gcs.label}`}
+ </p>
+ {:else}
+ <p
+ class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph truncate`}
+ >
+ {`${basis.result.location_gcs.gc_name}, ${basis.result.location_gcs.gc_admin1_id}`}
+ </p>
+ {/if}
+ </div>
+ </SearchResultContainer>
+{:else if `nostr_profile` in basis && basis.result.nostr_profile.id}
+ <SearchResultContainer
+ basis={{
+ callback: async () => {
+ await basis.lc_handle_result_nostr_profile(
+ basis.result.nostr_profile,
+ );
+ },
+ }}
+ >
+ <div class={`flex flex-row gap-4 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-blue-400 round-24`}
+ >
+ <Glyph
+ basis={{
+ classes: `text-white`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `user`,
+ }}
+ ></Glyph>
+ </div>
+ <div class={`flex flex-row gap-1 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {`${$lls(`common.profile`)}`}
+ </p>
+ </div>
+ <p
+ class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`}
+ >
+ {ascii.bullet}
+ </p>
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-purple-400 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {#if basis.result.result_k === `name`}
+ {`${$lls(`common.name`)}`}
+ {:else}
+ {`@todo`}
+ {/if}
+ </p>
+ </div>
+ </div>
+ </div>
+ <div
+ class={`flex flex-row flex-grow pl-4 pr-2 justify-end items-center overflow-hidden`}
+ >
+ {#if basis.result.nostr_profile.name}
+ <p
+ class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph tracking-wide truncate`}
+ >
+ {#if basis.result.result_k === `name`}
+ {`${basis.result.nostr_profile.name}`}
+ {:else if basis.result.result_v}
+ {`${basis.result.result_v}`}
+ {:else}
+ {`@todo`}
+ {/if}
+ </p>
+ {/if}
+ </div>
+ </SearchResultContainer>
+{:else if `nostr_relay` in basis && basis.result.nostr_relay.id}
+ <SearchResultContainer
+ basis={{
+ callback: async () => {
+ await basis.lc_handle_result_nostr_relay(
+ basis.result.nostr_relay,
+ );
+ },
+ }}
+ >
+ <div class={`flex flex-row gap-4 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-blue-400 round-24`}
+ >
+ <Glyph
+ basis={{
+ classes: `text-white`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `user`,
+ }}
+ ></Glyph>
+ </div>
+ <div class={`flex flex-row gap-1 justify-start items-center`}>
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {`${$lls(`common.relay`)}`}
+ </p>
+ </div>
+ <p
+ class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`}
+ >
+ {ascii.bullet}
+ </p>
+ <div
+ class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-yellow-400 rounded-md`}
+ >
+ <p
+ class={`font-sans font-[900] text-[0.7rem] text-white uppercase`}
+ >
+ {`${$lls(`common.url`)}`}
+ </p>
+ </div>
+ </div>
+ </div>
+ <div
+ class={`flex flex-row flex-grow pr-2 justify-end items-center overflow-hidden`}
+ >
+ <p
+ class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph tracking-wide truncate`}
+ >
+ {`${basis.result.nostr_relay.url}`}
+ </p>
+ </div>
+ </SearchResultContainer>
+{/if}
diff --git a/apps-lib/src/lib/global.d.ts b/apps-lib/src/lib/global.d.ts
@@ -1,3 +1,4 @@
+declare module "$app/environment";
declare module "$app/navigation";
declare module "$app/stores";
diff --git a/apps-lib/src/lib/index.ts b/apps-lib/src/lib/index.ts
@@ -1,33 +1,98 @@
-export { default as ButtonLayout } from "./components/button_layout.svelte";
-export { default as ButtonLayoutPair } from "./components/button_layout_pair.svelte";
-export { default as EntryLine } from "./components/entry_line.svelte";
-export { default as EntryWrap } from "./components/entry_wrap.svelte";
-export { default as LabelDisplay } from "./components/label_display.svelte";
-export { default as LayoutRoot } from "./components/layout_root.svelte";
-export { default as LayoutView } from "./components/layout_view.svelte";
-export { default as LayoutWindow } from "./components/layout_window.svelte";
-export { default as LogoCircle } from "./components/logo_circle.svelte";
-export { default as LogoCircleSm } from "./components/logo_circle_sm.svelte";
-export { default as OverlayLoading } from "./components/overlay_loading.svelte";
-export { default as OverlaySplash } from "./components/overlay_splash.svelte";
-export { default as Blur } from "./el/blur.svelte";
-export { default as CssStatic } from "./el/css_static.svelte";
-export { default as CssStyles } from "./el/css_styles.svelte";
-export { default as Fill } from "./el/fill.svelte";
-export { default as Glyph } from "./el/glyph.svelte";
-export { default as InputElement } from "./el/input_element.svelte";
-export { default as LoadSymbol } from "./el/load_symbol.svelte";
-export * from "./stores/app";
-export * from "./stores/client";
-export * from "./stores/ndk";
-export * from "./types/app";
-export * from "./types/el";
-export * from "./types/interface";
-export * from "./utils/app";
-export * from "./utils/carousel";
-export * from "./utils/document";
-export * from "./utils/i18n";
-export * from "./utils/kv";
-export * from "./utils/routes";
-export * from "./utils/styles";
-
+export { default as ButtonArrow } from "./component/button/button-arrow.svelte"
+export { default as ButtonLayoutPair } from "./component/button/button-layout-pair.svelte"
+export { default as ButtonLayout } from "./component/button/button-layout.svelte"
+export { default as CarouselItem } from "./component/carousel/carousel-item.svelte"
+export { default as Carousel } from "./component/carousel/carousel.svelte"
+export { default as EntryLine } from "./component/entry/entry-line.svelte"
+export { default as EntryWrap } from "./component/entry/entry-wrap.svelte"
+export { default as FloatPageButton } from "./component/float/float-page-button.svelte"
+export { default as FloatTabs } from "./component/float/float-tabs.svelte"
+export { default as GlyphButtonSimple } from "./component/glyph/glyph-button-simple.svelte"
+export { default as GlyphButton } from "./component/glyph/glyph-button.svelte"
+export { default as GlyphCircle } from "./component/glyph/glyph-circle.svelte"
+export { default as GlyphTitleSelectLabel } from "./component/glyph/glyph-title-select-label.svelte"
+export { default as LabelDisplay } from "./component/label/label-display.svelte"
+export { default as Empty } from "./component/lib/empty.svelte"
+export { default as Fade } from "./component/lib/fade.svelte"
+export { default as LoadScreen } from "./component/lib/load-screen.svelte"
+export { default as LogoCircleSm } from "./component/lib/logo-circle-sm.svelte"
+export { default as LogoCircle } from "./component/lib/logo-circle.svelte"
+export { default as SplashScreen } from "./component/lib/splash-screen.svelte"
+export { default as View } from "./component/lib/view.svelte"
+export { default as MapMarkerDot } from "./component/map/map-marker-dot.svelte"
+export { default as MapPointDisplay } from "./component/map/map-point-display.svelte"
+export { default as MapPointSelect } from "./component/map/map-point-select.svelte"
+export { default as MapPopupPointGeolocation } from "./component/map/map-popup-point-geolocation.svelte"
+export { default as NavOption } from "./component/nav/nav-option.svelte"
+export { default as Nav } from "./component/nav/nav.svelte"
+export { default as PageHeader } from "./component/page/page-header.svelte"
+export { default as PageToolbar } from "./component/page/page-toolbar.svelte"
+export { default as TrellisDefaultLabel } from "./component/trellis/trellis-default-label.svelte"
+export { default as TrellisEnd } from "./component/trellis/trellis-end.svelte"
+export { default as TrellisInput } from "./component/trellis/trellis-input.svelte"
+export { default as TrellisLine } from "./component/trellis/trellis-line.svelte"
+export { default as TrellisOffset } from "./component/trellis/trellis-offset.svelte"
+export { default as TrellisRowDisplayValue } from "./component/trellis/trellis-row-display-value.svelte"
+export { default as TrellisRowLabel } from "./component/trellis/trellis-row-label.svelte"
+export { default as TrellisSelect } from "./component/trellis/trellis-select.svelte"
+export { default as TrellisTitle } from "./component/trellis/trellis-title.svelte"
+export { default as TrellisTouch } from "./component/trellis/trellis-touch.svelte"
+export { default as Trellis } from "./component/trellis/trellis.svelte"
+export { default as Glyph } from "./el/glyph.svelte"
+export { default as ImageBlob } from "./el/image-blob.svelte"
+export { default as ImagePath } from "./el/image-path.svelte"
+export { default as Input } from "./el/input.svelte"
+export { default as LabelSwap } from "./el/label-swap.svelte"
+export { default as LoadSymbol } from "./el/load-symbol.svelte"
+export { default as SelectMenu } from "./el/select-menu.svelte"
+export { default as Select } from "./el/select.svelte"
+export { default as Styles } from "./el/styles.svelte"
+export { default as TextArea } from "./el/text-area.svelte"
+export { default as Toast } from "./el/toast.svelte"
+export { default as ImageUploadAddPhoto } from "./feature/image-upload-add-photo.svelte"
+export { default as SearchResultContainer } from "./feature/search-result-container.svelte"
+export { default as SearchResultDisplay } from "./feature/search-result-display.svelte"
+export { default as LayoutOverlayBlur } from "./layout/layout-overlay-blur.svelte"
+export { default as LayoutOverlayLoading } from "./layout/layout-overlay-loading.svelte"
+export { default as LayoutOverlaySplash } from "./layout/layout-overlay-splash.svelte"
+export { default as LayoutStyles } from "./layout/layout-styles.svelte"
+export { default as LayoutTrellis } from "./layout/layout-trellis.svelte"
+export { default as LayoutView } from "./layout/layout-view.svelte"
+export { default as LayoutWindow } from "./layout/layout-window.svelte"
+export * from "./locale/i18n"
+export * from "./service/search/lib"
+export * from "./service/search/types"
+export * from "./store/app"
+export * from "./store/client"
+export * from "./store/component"
+export * from "./store/ndk"
+export * from "./types/app"
+export * from "./types/component"
+export * from "./types/el"
+export * from "./types/feature"
+export * from "./types/interface"
+export * from "./types/model"
+export * from "./types/util"
+export * from "./types/view"
+export * from "./util/app"
+export * from "./util/carousel"
+export * from "./util/casl"
+export * from "./util/component"
+export * from "./util/conf"
+export * from "./util/document"
+export * from "./util/error"
+export * from "./util/geolocation"
+export * from "./util/i18n"
+export * from "./util/kv"
+export * from "./util/styles"
+export * from "./util/view"
+export { default as FarmLandAdd } from "./view/farm-land-add.svelte"
+export { default as FarmLandView } from "./view/farm-land-view.svelte"
+export { default as FarmLand } from "./view/farm-land.svelte"
+export { default as Home } from "./view/home.svelte"
+export { default as Notifications } from "./view/notifications.svelte"
+export { default as Search } from "./view/search.svelte"
+export { default as SettingsNostr } from "./view/settings-nostr.svelte"
+export { default as SettingsProfileEdit } from "./view/settings-profile-edit.svelte"
+export { default as SettingsProfile } from "./view/settings-profile.svelte"
+export { default as Settings } from "./view/settings.svelte"
diff --git a/apps-lib/src/lib/layout/layout-overlay-blur.svelte b/apps-lib/src/lib/layout/layout-overlay-blur.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+ import { app_blur, Empty } from "$lib";
+</script>
+
+<div
+ class={`z-10 absolute top-0 left-0 modal modal-bottom ${$app_blur ? `modal-open` : ``} h-[100vh] w-full m-0 p-0 backdrop-blur-sm overflow-y-hidden el-re`}
+>
+ <div
+ class={`modal-box h-full w-full m-0 p-0 bg-transparent overflow-hidden el-re`}
+ >
+ <Empty></Empty>
+ </div>
+</div>
diff --git a/apps-lib/src/lib/layout/layout-overlay-loading.svelte b/apps-lib/src/lib/layout/layout-overlay-loading.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+ import { app_loading, LoadScreen } from "$lib";
+</script>
+
+{#if $app_loading}
+ <LoadScreen />
+{/if}
diff --git a/apps-lib/src/lib/layout/layout-overlay-splash.svelte b/apps-lib/src/lib/layout/layout-overlay-splash.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+ import { app_splash, SplashScreen } from "$lib";
+</script>
+
+{#if $app_splash}
+ <SplashScreen />
+{/if}
diff --git a/apps-lib/src/lib/layout/layout-styles.svelte b/apps-lib/src/lib/layout/layout-styles.svelte
@@ -0,0 +1 @@
+<div class="hidden -bottom-lo_view_mobile_base -bottom-lo_view_mobile_y -bottom-nav_mobile_base -bottom-nav_mobile_y -bottom-tabs_mobile_base -bottom-tabs_mobile_y -bottom-trellis_centered_mobile_base -bottom-trellis_centered_mobile_y -bottom-view_mobile_base -bottom-view_mobile_y -bottom-view_offset_mobile_base -bottom-view_offset_mobile_y -top-lo_view_mobile_base -top-lo_view_mobile_y -top-nav_mobile_base -top-nav_mobile_y -top-tabs_mobile_base -top-tabs_mobile_y -top-trellis_centered_mobile_base -top-trellis_centered_mobile_y -top-view_mobile_base -top-view_mobile_y -top-view_offset_mobile_base -top-view_offset_mobile_y -translate-y-h_lo_view_mobile_base -translate-y-h_lo_view_mobile_y -translate-y-h_nav_mobile_base -translate-y-h_nav_mobile_y -translate-y-h_tabs_mobile_base -translate-y-h_tabs_mobile_y -translate-y-h_trellis_centered_mobile_base -translate-y-h_trellis_centered_mobile_y -translate-y-h_view_mobile_base -translate-y-h_view_mobile_y -translate-y-h_view_offset_mobile_base -translate-y-h_view_offset_mobile_y active:bg-layer_0_glyph active:bg-layer_0_glyph_a active:bg-layer_0_glyph_hl active:bg-layer_0_glyph_hl_a active:bg-layer_0_glyph_label active:bg-layer_0_glyph_pl active:bg-layer_0_glyph_shade active:bg-layer_0_surface active:bg-layer_0_surface_a active:bg-layer_0_surface_blur active:bg-layer_0_surface_edge active:bg-layer_0_surface_w active:bg-layer_1_glyph active:bg-layer_1_glyph_a active:bg-layer_1_glyph_d active:bg-layer_1_glyph_hl active:bg-layer_1_glyph_hl_a active:bg-layer_1_glyph_label active:bg-layer_1_glyph_pl active:bg-layer_1_glyph_shade active:bg-layer_1_surface active:bg-layer_1_surface_a active:bg-layer_1_surface_edge active:bg-layer_1_surface_err active:bg-layer_1_surface_focus active:bg-layer_2_glyph active:bg-layer_2_glyph_a active:bg-layer_2_glyph_d active:bg-layer_2_glyph_hl active:bg-layer_2_glyph_hl_a active:bg-layer_2_glyph_pl active:bg-layer_2_glyph_shade active:bg-layer_2_surface active:bg-layer_2_surface_a active:bg-layer_2_surface_edge active:border-layer_0_glyph active:border-layer_0_glyph_a active:border-layer_0_glyph_hl active:border-layer_0_glyph_hl_a active:border-layer_0_glyph_label active:border-layer_0_glyph_pl active:border-layer_0_glyph_shade active:border-layer_0_surface active:border-layer_0_surface_a active:border-layer_0_surface_blur active:border-layer_0_surface_edge active:border-layer_0_surface_w active:border-layer_1_glyph active:border-layer_1_glyph_a active:border-layer_1_glyph_d active:border-layer_1_glyph_hl active:border-layer_1_glyph_hl_a active:border-layer_1_glyph_label active:border-layer_1_glyph_pl active:border-layer_1_glyph_shade active:border-layer_1_surface active:border-layer_1_surface_a active:border-layer_1_surface_edge active:border-layer_1_surface_err active:border-layer_1_surface_focus active:border-layer_2_glyph active:border-layer_2_glyph_a active:border-layer_2_glyph_d active:border-layer_2_glyph_hl active:border-layer_2_glyph_hl_a active:border-layer_2_glyph_pl active:border-layer_2_glyph_shade active:border-layer_2_surface active:border-layer_2_surface_a active:border-layer_2_surface_edge active:text-layer_0_glyph active:text-layer_0_glyph_a active:text-layer_0_glyph_hl active:text-layer_0_glyph_hl_a active:text-layer_0_glyph_label active:text-layer_0_glyph_pl active:text-layer_0_glyph_shade active:text-layer_0_surface active:text-layer_0_surface_a active:text-layer_0_surface_blur active:text-layer_0_surface_edge active:text-layer_0_surface_w active:text-layer_1_glyph active:text-layer_1_glyph_a active:text-layer_1_glyph_d active:text-layer_1_glyph_hl active:text-layer_1_glyph_hl_a active:text-layer_1_glyph_label active:text-layer_1_glyph_pl active:text-layer_1_glyph_shade active:text-layer_1_surface active:text-layer_1_surface_a active:text-layer_1_surface_edge active:text-layer_1_surface_err active:text-layer_1_surface_focus active:text-layer_2_glyph active:text-layer_2_glyph_a active:text-layer_2_glyph_d active:text-layer_2_glyph_hl active:text-layer_2_glyph_hl_a active:text-layer_2_glyph_pl active:text-layer_2_glyph_shade active:text-layer_2_surface active:text-layer_2_surface_a active:text-layer_2_surface_edge bg-layer_0_glyph bg-layer_0_glyph_a bg-layer_0_glyph_hl bg-layer_0_glyph_hl_a bg-layer_0_glyph_label bg-layer_0_glyph_pl bg-layer_0_glyph_shade bg-layer_0_surface bg-layer_0_surface_a bg-layer_0_surface_blur bg-layer_0_surface_edge bg-layer_0_surface_w bg-layer_1_glyph bg-layer_1_glyph_a bg-layer_1_glyph_d bg-layer_1_glyph_hl bg-layer_1_glyph_hl_a bg-layer_1_glyph_label bg-layer_1_glyph_pl bg-layer_1_glyph_shade bg-layer_1_surface bg-layer_1_surface_a bg-layer_1_surface_edge bg-layer_1_surface_err bg-layer_1_surface_focus bg-layer_2_glyph bg-layer_2_glyph_a bg-layer_2_glyph_d bg-layer_2_glyph_hl bg-layer_2_glyph_hl_a bg-layer_2_glyph_pl bg-layer_2_glyph_shade bg-layer_2_surface bg-layer_2_surface_a bg-layer_2_surface_edge border-layer_0_glyph border-layer_0_glyph_a border-layer_0_glyph_hl border-layer_0_glyph_hl_a border-layer_0_glyph_label border-layer_0_glyph_pl border-layer_0_glyph_shade border-layer_0_surface border-layer_0_surface_a border-layer_0_surface_blur border-layer_0_surface_edge border-layer_0_surface_w border-layer_1_glyph border-layer_1_glyph_a border-layer_1_glyph_d border-layer_1_glyph_hl border-layer_1_glyph_hl_a border-layer_1_glyph_label border-layer_1_glyph_pl border-layer_1_glyph_shade border-layer_1_surface border-layer_1_surface_a border-layer_1_surface_edge border-layer_1_surface_err border-layer_1_surface_focus border-layer_2_glyph border-layer_2_glyph_a border-layer_2_glyph_d border-layer_2_glyph_hl border-layer_2_glyph_hl_a border-layer_2_glyph_pl border-layer_2_glyph_shade border-layer_2_surface border-layer_2_surface_a border-layer_2_surface_edge bottom-lo_view_mobile_base bottom-lo_view_mobile_y bottom-nav_mobile_base bottom-nav_mobile_y bottom-tabs_mobile_base bottom-tabs_mobile_y bottom-trellis_centered_mobile_base bottom-trellis_centered_mobile_y bottom-view_mobile_base bottom-view_mobile_y bottom-view_offset_mobile_base bottom-view_offset_mobile_y focus:bg-layer_0_glyph focus:bg-layer_0_glyph_a focus:bg-layer_0_glyph_hl focus:bg-layer_0_glyph_hl_a focus:bg-layer_0_glyph_label focus:bg-layer_0_glyph_pl focus:bg-layer_0_glyph_shade focus:bg-layer_0_surface focus:bg-layer_0_surface_a focus:bg-layer_0_surface_blur focus:bg-layer_0_surface_edge focus:bg-layer_0_surface_w focus:bg-layer_1_glyph focus:bg-layer_1_glyph_a focus:bg-layer_1_glyph_d focus:bg-layer_1_glyph_hl focus:bg-layer_1_glyph_hl_a focus:bg-layer_1_glyph_label focus:bg-layer_1_glyph_pl focus:bg-layer_1_glyph_shade focus:bg-layer_1_surface focus:bg-layer_1_surface_a focus:bg-layer_1_surface_edge focus:bg-layer_1_surface_err focus:bg-layer_1_surface_focus focus:bg-layer_2_glyph focus:bg-layer_2_glyph_a focus:bg-layer_2_glyph_d focus:bg-layer_2_glyph_hl focus:bg-layer_2_glyph_hl_a focus:bg-layer_2_glyph_pl focus:bg-layer_2_glyph_shade focus:bg-layer_2_surface focus:bg-layer_2_surface_a focus:bg-layer_2_surface_edge focus:border-layer_0_glyph focus:border-layer_0_glyph_a focus:border-layer_0_glyph_hl focus:border-layer_0_glyph_hl_a focus:border-layer_0_glyph_label focus:border-layer_0_glyph_pl focus:border-layer_0_glyph_shade focus:border-layer_0_surface focus:border-layer_0_surface_a focus:border-layer_0_surface_blur focus:border-layer_0_surface_edge focus:border-layer_0_surface_w focus:border-layer_1_glyph focus:border-layer_1_glyph_a focus:border-layer_1_glyph_d focus:border-layer_1_glyph_hl focus:border-layer_1_glyph_hl_a focus:border-layer_1_glyph_label focus:border-layer_1_glyph_pl focus:border-layer_1_glyph_shade focus:border-layer_1_surface focus:border-layer_1_surface_a focus:border-layer_1_surface_edge focus:border-layer_1_surface_err focus:border-layer_1_surface_focus focus:border-layer_2_glyph focus:border-layer_2_glyph_a focus:border-layer_2_glyph_d focus:border-layer_2_glyph_hl focus:border-layer_2_glyph_hl_a focus:border-layer_2_glyph_pl focus:border-layer_2_glyph_shade focus:border-layer_2_surface focus:border-layer_2_surface_a focus:border-layer_2_surface_edge focus:text-layer_0_glyph focus:text-layer_0_glyph_a focus:text-layer_0_glyph_hl focus:text-layer_0_glyph_hl_a focus:text-layer_0_glyph_label focus:text-layer_0_glyph_pl focus:text-layer_0_glyph_shade focus:text-layer_0_surface focus:text-layer_0_surface_a focus:text-layer_0_surface_blur focus:text-layer_0_surface_edge focus:text-layer_0_surface_w focus:text-layer_1_glyph focus:text-layer_1_glyph_a focus:text-layer_1_glyph_d focus:text-layer_1_glyph_hl focus:text-layer_1_glyph_hl_a focus:text-layer_1_glyph_label focus:text-layer_1_glyph_pl focus:text-layer_1_glyph_shade focus:text-layer_1_surface focus:text-layer_1_surface_a focus:text-layer_1_surface_edge focus:text-layer_1_surface_err focus:text-layer_1_surface_focus focus:text-layer_2_glyph focus:text-layer_2_glyph_a focus:text-layer_2_glyph_d focus:text-layer_2_glyph_hl focus:text-layer_2_glyph_hl_a focus:text-layer_2_glyph_pl focus:text-layer_2_glyph_shade focus:text-layer_2_surface focus:text-layer_2_surface_a focus:text-layer_2_surface_edge group-active:bg-layer_0_glyph group-active:bg-layer_0_glyph_a group-active:bg-layer_0_glyph_hl group-active:bg-layer_0_glyph_hl_a group-active:bg-layer_0_glyph_label group-active:bg-layer_0_glyph_pl group-active:bg-layer_0_glyph_shade group-active:bg-layer_0_surface group-active:bg-layer_0_surface_a group-active:bg-layer_0_surface_blur group-active:bg-layer_0_surface_edge group-active:bg-layer_0_surface_w group-active:bg-layer_1_glyph group-active:bg-layer_1_glyph_a group-active:bg-layer_1_glyph_d group-active:bg-layer_1_glyph_hl group-active:bg-layer_1_glyph_hl_a group-active:bg-layer_1_glyph_label group-active:bg-layer_1_glyph_pl group-active:bg-layer_1_glyph_shade group-active:bg-layer_1_surface group-active:bg-layer_1_surface_a group-active:bg-layer_1_surface_edge group-active:bg-layer_1_surface_err group-active:bg-layer_1_surface_focus group-active:bg-layer_2_glyph group-active:bg-layer_2_glyph_a group-active:bg-layer_2_glyph_d group-active:bg-layer_2_glyph_hl group-active:bg-layer_2_glyph_hl_a group-active:bg-layer_2_glyph_pl group-active:bg-layer_2_glyph_shade group-active:bg-layer_2_surface group-active:bg-layer_2_surface_a group-active:bg-layer_2_surface_edge group-active:border-layer_0_glyph group-active:border-layer_0_glyph_a group-active:border-layer_0_glyph_hl group-active:border-layer_0_glyph_hl_a group-active:border-layer_0_glyph_label group-active:border-layer_0_glyph_pl group-active:border-layer_0_glyph_shade group-active:border-layer_0_surface group-active:border-layer_0_surface_a group-active:border-layer_0_surface_blur group-active:border-layer_0_surface_edge group-active:border-layer_0_surface_w group-active:border-layer_1_glyph group-active:border-layer_1_glyph_a group-active:border-layer_1_glyph_d group-active:border-layer_1_glyph_hl group-active:border-layer_1_glyph_hl_a group-active:border-layer_1_glyph_label group-active:border-layer_1_glyph_pl group-active:border-layer_1_glyph_shade group-active:border-layer_1_surface group-active:border-layer_1_surface_a group-active:border-layer_1_surface_edge group-active:border-layer_1_surface_err group-active:border-layer_1_surface_focus group-active:border-layer_2_glyph group-active:border-layer_2_glyph_a group-active:border-layer_2_glyph_d group-active:border-layer_2_glyph_hl group-active:border-layer_2_glyph_hl_a group-active:border-layer_2_glyph_pl group-active:border-layer_2_glyph_shade group-active:border-layer_2_surface group-active:border-layer_2_surface_a group-active:border-layer_2_surface_edge group-active:text-layer_0_glyph group-active:text-layer_0_glyph_a group-active:text-layer_0_glyph_hl group-active:text-layer_0_glyph_hl_a group-active:text-layer_0_glyph_label group-active:text-layer_0_glyph_pl group-active:text-layer_0_glyph_shade group-active:text-layer_0_surface group-active:text-layer_0_surface_a group-active:text-layer_0_surface_blur group-active:text-layer_0_surface_edge group-active:text-layer_0_surface_w group-active:text-layer_1_glyph group-active:text-layer_1_glyph_a group-active:text-layer_1_glyph_d group-active:text-layer_1_glyph_hl group-active:text-layer_1_glyph_hl_a group-active:text-layer_1_glyph_label group-active:text-layer_1_glyph_pl group-active:text-layer_1_glyph_shade group-active:text-layer_1_surface group-active:text-layer_1_surface_a group-active:text-layer_1_surface_edge group-active:text-layer_1_surface_err group-active:text-layer_1_surface_focus group-active:text-layer_2_glyph group-active:text-layer_2_glyph_a group-active:text-layer_2_glyph_d group-active:text-layer_2_glyph_hl group-active:text-layer_2_glyph_hl_a group-active:text-layer_2_glyph_pl group-active:text-layer_2_glyph_shade group-active:text-layer_2_surface group-active:text-layer_2_surface_a group-active:text-layer_2_surface_edge group-focus:bg-layer_0_glyph group-focus:bg-layer_0_glyph_a group-focus:bg-layer_0_glyph_hl group-focus:bg-layer_0_glyph_hl_a group-focus:bg-layer_0_glyph_label group-focus:bg-layer_0_glyph_pl group-focus:bg-layer_0_glyph_shade group-focus:bg-layer_0_surface group-focus:bg-layer_0_surface_a group-focus:bg-layer_0_surface_blur group-focus:bg-layer_0_surface_edge group-focus:bg-layer_0_surface_w group-focus:bg-layer_1_glyph group-focus:bg-layer_1_glyph_a group-focus:bg-layer_1_glyph_d group-focus:bg-layer_1_glyph_hl group-focus:bg-layer_1_glyph_hl_a group-focus:bg-layer_1_glyph_label group-focus:bg-layer_1_glyph_pl group-focus:bg-layer_1_glyph_shade group-focus:bg-layer_1_surface group-focus:bg-layer_1_surface_a group-focus:bg-layer_1_surface_edge group-focus:bg-layer_1_surface_err group-focus:bg-layer_1_surface_focus group-focus:bg-layer_2_glyph group-focus:bg-layer_2_glyph_a group-focus:bg-layer_2_glyph_d group-focus:bg-layer_2_glyph_hl group-focus:bg-layer_2_glyph_hl_a group-focus:bg-layer_2_glyph_pl group-focus:bg-layer_2_glyph_shade group-focus:bg-layer_2_surface group-focus:bg-layer_2_surface_a group-focus:bg-layer_2_surface_edge group-focus:border-layer_0_glyph group-focus:border-layer_0_glyph_a group-focus:border-layer_0_glyph_hl group-focus:border-layer_0_glyph_hl_a group-focus:border-layer_0_glyph_label group-focus:border-layer_0_glyph_pl group-focus:border-layer_0_glyph_shade group-focus:border-layer_0_surface group-focus:border-layer_0_surface_a group-focus:border-layer_0_surface_blur group-focus:border-layer_0_surface_edge group-focus:border-layer_0_surface_w group-focus:border-layer_1_glyph group-focus:border-layer_1_glyph_a group-focus:border-layer_1_glyph_d group-focus:border-layer_1_glyph_hl group-focus:border-layer_1_glyph_hl_a group-focus:border-layer_1_glyph_label group-focus:border-layer_1_glyph_pl group-focus:border-layer_1_glyph_shade group-focus:border-layer_1_surface group-focus:border-layer_1_surface_a group-focus:border-layer_1_surface_edge group-focus:border-layer_1_surface_err group-focus:border-layer_1_surface_focus group-focus:border-layer_2_glyph group-focus:border-layer_2_glyph_a group-focus:border-layer_2_glyph_d group-focus:border-layer_2_glyph_hl group-focus:border-layer_2_glyph_hl_a group-focus:border-layer_2_glyph_pl group-focus:border-layer_2_glyph_shade group-focus:border-layer_2_surface group-focus:border-layer_2_surface_a group-focus:border-layer_2_surface_edge group-focus:text-layer_0_glyph group-focus:text-layer_0_glyph_a group-focus:text-layer_0_glyph_hl group-focus:text-layer_0_glyph_hl_a group-focus:text-layer_0_glyph_label group-focus:text-layer_0_glyph_pl group-focus:text-layer_0_glyph_shade group-focus:text-layer_0_surface group-focus:text-layer_0_surface_a group-focus:text-layer_0_surface_blur group-focus:text-layer_0_surface_edge group-focus:text-layer_0_surface_w group-focus:text-layer_1_glyph group-focus:text-layer_1_glyph_a group-focus:text-layer_1_glyph_d group-focus:text-layer_1_glyph_hl group-focus:text-layer_1_glyph_hl_a group-focus:text-layer_1_glyph_label group-focus:text-layer_1_glyph_pl group-focus:text-layer_1_glyph_shade group-focus:text-layer_1_surface group-focus:text-layer_1_surface_a group-focus:text-layer_1_surface_edge group-focus:text-layer_1_surface_err group-focus:text-layer_1_surface_focus group-focus:text-layer_2_glyph group-focus:text-layer_2_glyph_a group-focus:text-layer_2_glyph_d group-focus:text-layer_2_glyph_hl group-focus:text-layer_2_glyph_hl_a group-focus:text-layer_2_glyph_pl group-focus:text-layer_2_glyph_shade group-focus:text-layer_2_surface group-focus:text-layer_2_surface_a group-focus:text-layer_2_surface_edge h-[12px] h-[16px] h-[17px] h-[18px] h-[20px] h-[22px] h-[24px] h-[28px] h-[36px] h-entry_bold h-entry_guide h-entry_line h-envelope_button h-envelope_top h-line h-line_label h-lo_view_mobile_base h-lo_view_mobile_y h-nav_mobile_base h-nav_mobile_y h-tabs_mobile_base h-tabs_mobile_y h-toast_min h-touch_bold h-touch_guide h-trellis_centered_mobile_base h-trellis_centered_mobile_y h-view_mobile_base h-view_mobile_y h-view_offset_mobile_base h-view_offset_mobile_y pb-h_lo_view_mobile_base pb-h_lo_view_mobile_y pb-h_nav_mobile_base pb-h_nav_mobile_y pb-h_tabs_mobile_base pb-h_tabs_mobile_y pb-h_trellis_centered_mobile_base pb-h_trellis_centered_mobile_y pb-h_view_mobile_base pb-h_view_mobile_y pb-h_view_offset_mobile_base pb-h_view_offset_mobile_y pt-h_lo_view_mobile_base pt-h_lo_view_mobile_y pt-h_nav_mobile_base pt-h_nav_mobile_y pt-h_tabs_mobile_base pt-h_tabs_mobile_y pt-h_trellis_centered_mobile_base pt-h_trellis_centered_mobile_y pt-h_view_mobile_base pt-h_view_mobile_y pt-h_view_offset_mobile_base pt-h_view_offset_mobile_y text-[12px] text-[15px] text-[16px] text-[18px] text-[19px] text-[20px] text-[21px] text-[23px] text-[24px] text-[26px] text-[27px] text-[28px] text-[30px] text-[36px] text-[40px] text-layer_0_glyph text-layer_0_glyph_a text-layer_0_glyph_hl text-layer_0_glyph_hl_a text-layer_0_glyph_label text-layer_0_glyph_pl text-layer_0_glyph_shade text-layer_0_surface text-layer_0_surface_a text-layer_0_surface_blur text-layer_0_surface_edge text-layer_0_surface_w text-layer_1_glyph text-layer_1_glyph_a text-layer_1_glyph_d text-layer_1_glyph_hl text-layer_1_glyph_hl_a text-layer_1_glyph_label text-layer_1_glyph_pl text-layer_1_glyph_shade text-layer_1_surface text-layer_1_surface_a text-layer_1_surface_edge text-layer_1_surface_err text-layer_1_surface_focus text-layer_2_glyph text-layer_2_glyph_a text-layer_2_glyph_d text-layer_2_glyph_hl text-layer_2_glyph_hl_a text-layer_2_glyph_pl text-layer_2_glyph_shade text-layer_2_surface text-layer_2_surface_a text-layer_2_surface_edge top-lo_view_mobile_base top-lo_view_mobile_y top-nav_mobile_base top-nav_mobile_y top-tabs_mobile_base top-tabs_mobile_y top-trellis_centered_mobile_base top-trellis_centered_mobile_y top-view_mobile_base top-view_mobile_y top-view_offset_mobile_base top-view_offset_mobile_y translate-y-h_lo_view_mobile_base translate-y-h_lo_view_mobile_y translate-y-h_nav_mobile_base translate-y-h_nav_mobile_y translate-y-h_tabs_mobile_base translate-y-h_tabs_mobile_y translate-y-h_trellis_centered_mobile_base translate-y-h_trellis_centered_mobile_y translate-y-h_view_mobile_base translate-y-h_view_mobile_y translate-y-h_view_offset_mobile_base translate-y-h_view_offset_mobile_y w-[12px] w-[16px] w-[17px] w-[18px] w-[20px] w-[22px] w-[24px] w-[28px] w-[36px] w-mobile_base w-mobile_y"></div>
+\ No newline at end of file
diff --git a/apps-lib/src/lib/layout/layout-trellis.svelte b/apps-lib/src/lib/layout/layout-trellis.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ import { fmt_cl, type IBasisOpt, type IClOpt } from "$lib";
+
+ export let basis: IBasisOpt<IClOpt> = undefined;
+</script>
+
+<div
+ class={`${fmt_cl(basis?.classes)} flex flex-col pb-12 gap-4 justify-center items-center scroll-hide`}
+>
+ <slot />
+</div>
diff --git a/apps-lib/src/lib/layout/layout-view.svelte b/apps-lib/src/lib/layout/layout-view.svelte
@@ -0,0 +1,55 @@
+<script lang="ts">
+ import {
+ app_layout,
+ fmt_cl,
+ layout_view_cover,
+ nav_blur,
+ ph_blur,
+ tabs_blur,
+ type IBasisOpt,
+ type IClOpt,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: IBasisOpt<IClOpt & { fade?: boolean }> = undefined;
+ let el: HTMLElement | null;
+
+ /*$: classes_resp = $layout_view_cover
+ ? ``
+ : $nav_visible
+ ? `pt-h_nav_${$app_layout}`
+ : `pt-h_lo_view_${$app_layout}`;
+ */
+ onMount(async () => {
+ try {
+ el?.addEventListener("scroll", scrollChange);
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ onDestroy(async () => {
+ try {
+ el?.removeEventListener("scroll", scrollChange);
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ const scrollChange = (): void => {
+ if (Math.max(el?.scrollTop || 0, 0) > 10) nav_blur.set(true);
+ else nav_blur.set(false);
+ if (Math.max(el?.scrollTop || 0, 0) > 10) tabs_blur.set(true);
+ else tabs_blur.set(false);
+ if (Math.max(el?.scrollTop || 0, 0) > 30) ph_blur.set(true);
+ else ph_blur.set(false);
+ };
+</script>
+
+<div
+ bind:this={el}
+ class={`${fmt_cl(basis?.classes)} absolute top-0 left-0 flex flex-col h-[100vh] w-full justify-start items-center overflow-y-scroll scroll-hide ${$layout_view_cover ? `` : `pt-h_lo_view_${$app_layout}`}`}
+ class:fade-in={basis?.fade}
+>
+ <slot />
+</div>
diff --git a/apps-lib/src/lib/layout/layout-window.svelte b/apps-lib/src/lib/layout/layout-window.svelte
@@ -0,0 +1,52 @@
+<script>
+ import {
+ app_layout,
+ app_tilt,
+ app_toast,
+ app_win,
+ cfg_app,
+ envelope_tilt,
+ envelope_visible,
+ LayoutOverlayBlur,
+ LayoutOverlayLoading,
+ LayoutOverlaySplash,
+ LayoutStyles,
+ Toast,
+ } from "$lib";
+ import { onMount } from "svelte";
+
+ onMount(async () => {
+ try {
+ app_win.set({ h: window.innerHeight, w: window.innerWidth });
+ app_toast.set(false);
+ } catch (e) {
+ console.log(`(layout mount) `, e);
+ } finally {
+ }
+ });
+
+ app_win.subscribe((_app_win) => {
+ if (_app_win.h > cfg_app.layout.mobile_y.h) app_layout.set(`mobile_y`);
+ });
+
+ envelope_visible.subscribe(async (_envelope_visible) => {
+ if (_envelope_visible && $envelope_tilt) app_tilt.set(true);
+ else app_tilt.set(false);
+ });
+</script>
+
+<div
+ class={`relative flex flex-col h-[100vh] w-full overflow-x-hidden overflow-y-hidden bg-layer-0-surface ${$app_tilt ? `scale-y-[96%] translate-y-4 rounded-t-[3rem]` : ``} delay-75 duration-200 el-re`}
+>
+ <div class={`flex flex-col h-full w-full`}>
+ <slot />
+ </div>
+ <slot name="overlay" />
+ <LayoutOverlayBlur />
+</div>
+<LayoutOverlayLoading />
+<LayoutOverlaySplash />
+<LayoutStyles />
+{#if $app_toast}
+ <Toast basis={$app_toast} />
+{/if}
diff --git a/apps-lib/src/lib/locale/en/common.json b/apps-lib/src/lib/locale/en/common.json
@@ -0,0 +1,168 @@
+{
+ "loading": "Loading",
+ "disable_uploads": "Disable uploads",
+ "this_action_is_irreversible": "This action is irreversible",
+ "notifications": "Notifications",
+ "accept": "Accept",
+ "activation": "Activation",
+ "active": "Active",
+ "add": "Add",
+ "add_current_location": "Add current location",
+ "add_map_location": "Add map location",
+ "add_new": "Add new",
+ "add_new_location": "Add new location",
+ "agree": "Agree",
+ "all_accounts": "All accounts",
+ "area": "Area",
+ "at": "At",
+ "authenticated": "Authenticated",
+ "available_balance": "Available balance",
+ "back": "Back",
+ "bag": "Bag",
+ "bags": "Bags",
+ "bank_account": "Bank account",
+ "banner_photo": "Banner photo",
+ "bio": "Bio",
+ "business_name": "Business name",
+ "cancel": "Cancel",
+ "choose_a_profile_name": "Choose a profile name",
+ "climate": "Climate",
+ "close": "Close",
+ "color_mode": "Color mode",
+ "complete": "Complete",
+ "configure": "Configure",
+ "configure_your_device": "Configure your device",
+ "connect": "Connect",
+ "connected": "Connected",
+ "connection": "Connection",
+ "continue": "Continue",
+ "coordinates": "Coordinates",
+ "current_location": "Current location",
+ "dark": "Dark",
+ "date_created": "Date created",
+ "date_modified": "Date modified",
+ "delete": "Delete",
+ "description": "Description",
+ "details": "Details",
+ "device": "Device",
+ "disagree": "Disagree",
+ "disconnect": "Disconnect",
+ "do_you_want_to_continue_q": "Do you want to continue?",
+ "done": "Done",
+ "edit": "Edit",
+ "elevation": "Elevation",
+ "end_date": "End date",
+ "endpoint": "Endpoint",
+ "estate": "Estate",
+ "failure_to_process_the_request": "Failure to process the request",
+ "farm": "Farm",
+ "farm_land": "Farm land",
+ "file_name": "File name",
+ "file_size": "File size",
+ "filters": "Filters",
+ "from": "From",
+ "general": "General",
+ "hex": "Hex",
+ "highest_price": "Highest price",
+ "home": "Home",
+ "inbox": "Inbox",
+ "inflows": "Inflows",
+ "items": "Items",
+ "key": "Key",
+ "keypair": "Keypair",
+ "keys": "Keys",
+ "land_area": "Land area",
+ "land_plot": "Land plot",
+ "latitude": "Latitude",
+ "light": "Light",
+ "list": "List",
+ "listing": "Listing",
+ "location": "Location",
+ "locations": "Locations",
+ "longitude": "Longitude",
+ "lot": "Lot",
+ "lot_name": "Lot name",
+ "lowest_price": "Lowest price",
+ "make_primary": "Make primary",
+ "map": "Map",
+ "market": "Market",
+ "message": "Message",
+ "messages": "Messages",
+ "month": "Month",
+ "month_to_date": "Month to date",
+ "name": "Name",
+ "name_of_farm_or_estate": "Name of farm or estate",
+ "new": "New",
+ "no": "No",
+ "no_items_to_display": "No items to display",
+ "no_locations_saved": "No locations saved",
+ "nostr": "Nostr",
+ "not_connected": "Not connected",
+ "npub": "Npub",
+ "nsec": "Nsec",
+ "optional": "Optional",
+ "options": "Options",
+ "options_list": "Options list",
+ "order": "Order",
+ "origin": "Origin",
+ "other": "Other",
+ "outflows": "Outflows",
+ "overview": "Overview",
+ "page": "Page",
+ "per": "Per",
+ "personal": "Personal",
+ "photo": "Photo",
+ "photo_hosting": "Photo hosting",
+ "photos": "Photos",
+ "post": "Post",
+ "preview": "Preview",
+ "price": "Price",
+ "process": "Process",
+ "product": "Product",
+ "product_location": "Product location",
+ "product_name": "Product name",
+ "products": "Products",
+ "profile": "Profile",
+ "profile_name": "Profile name",
+ "profile_photo": "Profile photo",
+ "profiles": "Profiles",
+ "public_key": "Public key",
+ "publish": "Publish",
+ "quantity": "Quantity",
+ "quit": "Quit",
+ "reading": "Reading",
+ "relay": "Relay",
+ "relays": "Relays",
+ "reset": "Reset",
+ "return": "Return",
+ "review": "Review",
+ "search": "Search",
+ "secret_key": "Secret key",
+ "settings": "Settings",
+ "setup": "Setup",
+ "setup_for_farmer": "Setup for Farmer",
+ "skip": "Skip",
+ "socials": "Socials",
+ "soil": "Soil",
+ "start_date": "Start date",
+ "status": "Status",
+ "subject": "Subject",
+ "submit": "Submit",
+ "summary": "Summary",
+ "terms_of_use_agreement": "Terms of Use agreement",
+ "title": "Title",
+ "to": "To",
+ "unlock": "Unlock",
+ "update": "Update",
+ "upload_url": "Upload url",
+ "url": "url",
+ "username": "Username",
+ "using_public_key": "Using public key",
+ "value": "Value",
+ "view": "View",
+ "wallet": "Wallet",
+ "website": "Website",
+ "year": "Year",
+ "yes": "Yes",
+ "your_name": "Your name"
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/locale/en/icu.json b/apps-lib/src/lib/locale/en/icu.json
@@ -0,0 +1,84 @@
+{
+ "*_as": "{value} as",
+ "*_available": "{value} Available",
+ "*_copied": "{value} copied",
+ "*_description": "{value} description",
+ "*_details": "{value} details",
+ "*_failure": "{value} failure",
+ "*_list": "{value} list",
+ "*_month": "{value} month",
+ "*_name": "{value} name",
+ "*_order": "{value} order",
+ "*_price": "{value} price",
+ "*_quantity": "{value} quantity",
+ "*_sold": "{value} sold",
+ "*_summary": "{value} summary",
+ "*_the": "{value} the",
+ "*_title": "{value} title",
+ "*_total": "{value} total",
+ "*_your_device": "{value} your device",
+ "a_*_is_required": "A {value} is required",
+ "add_*": "Add {value}",
+ "add_a_*": "Add a {value}",
+ "add_existing_*": "Add existing {value}",
+ "as_*": "As {value}",
+ "choose_*": "Choose {value}",
+ "choose_a_*": "Choose a {}",
+ "choose_on_*": "Choose on {value}",
+ "click_to_*": "Click to {value}",
+ "click_to_add_a_*": "Click to add a {value}",
+ "configure_*": "Configure {value}",
+ "connect_*": "Connect {value}",
+ "connected_*": "Connected {value}",
+ "create_new_*": "Create new {value}",
+ "default_*": "Default {value}",
+ "delete_*": "Delete {value}",
+ "disconnect_*": "Disconnect {value}",
+ "edit_*": "Edit {value}",
+ "enter_*": "Enter {value}",
+ "enter_*_per_order": "Enter {value} per order",
+ "enter_a_*": "Enter a {value}",
+ "enter_new_*": "Enter new {value}",
+ "enter_the_*": "Enter the {value}",
+ "error_loading_*": "Error loading {value}",
+ "failure_*": "Failure {value}",
+ "failure_saving_*_to_the_database": "Failure saving {value} to the database",
+ "go_*": "Go {value}",
+ "invalid_*": "Invalid {value}",
+ "invalid_*_entry": "Invalid {value} entry",
+ "last_*": "Last {value}",
+ "listing_*": "Listing {value}",
+ "month_of_*": "Month of {value}",
+ "name_your_*": "Name your {value}",
+ "new_*": "New {value}",
+ "no_*": "No {value}",
+ "no_*_published": "No {value} published",
+ "no_*_selected": "No {value} selected",
+ "no_*_to_display": "No {value} to display",
+ "nostr_*": "Nostr {value}",
+ "post_*": "Post {value}",
+ "primary_*": "Primary {value}",
+ "reading_*": "Reading {value}",
+ "set_as_*": "Set as {value}",
+ "show_*": "Show {value}",
+ "the_*": "The {value}",
+ "the_*_is_available": "The {value} is available",
+ "the_*_is_incomplete": "The {value} is incomplete",
+ "the_*_is_missing": "The {value} is missing",
+ "the_*_is_registered": "The {value} is registered",
+ "the_*_is_required": "The {value} is missing",
+ "the_current_entry_*_will_be_deleted": "The current entry \"{value}\" will be deleted",
+ "the_listing_will_be_created_without_a_*": "The listing will be created without a {value}",
+ "there_was_a_failure_while_*": "There was a failure while {value}",
+ "this_*": "This {value}",
+ "toggle_*": "Toggle {value}",
+ "total_*": "Total {value}",
+ "unable_to_save_*": "Unable to save {value}",
+ "unconnected_*": "Unconnected {value}",
+ "uploading_*_photos": "Uploading {value} photos",
+ "use_existing_*": "Use existing {value}",
+ "valid_*": "Valid {value}",
+ "view_*": "View {value}",
+ "view_the_*": "View the {value}",
+ "your_*": "Your {value}"
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/locale/en/measurement.json b/apps-lib/src/lib/locale/en/measurement.json
@@ -0,0 +1,32 @@
+{
+ "area": {
+ "ac": "Acre",
+ "ac_ab": "Ac.",
+ "ha": "Hectare",
+ "ha_ab": "Ha.",
+ "m2": "Square metre",
+ "m2_ab": "Sq. M."
+ },
+ "length": {
+ "ft": "Foot",
+ "ft_ab": "Ft.",
+ "m": "Metre",
+ "m_ab": "M."
+ },
+ "mass": {
+ "unit": {
+ "g": "Gram",
+ "g_ab": "g.",
+ "g_pl": "Grams",
+ "hg": "100 Gram",
+ "hg_ab": "100g.",
+ "hg_pl": "100 Grams",
+ "kg": "Kilogram",
+ "kg_ab": "kg.",
+ "kg_pl": "Kilograms",
+ "lb": "Pound",
+ "lb_ab": "lb.",
+ "lb_pl": "Pounds"
+ }
+ }
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/locale/en/model.json b/apps-lib/src/lib/locale/en/model.json
@@ -0,0 +1,291 @@
+{
+ "location_gcs": {
+ "fields": {
+ "area": {
+ "label": "Location Area"
+ },
+ "climate": {
+ "label": "Location Climate"
+ },
+ "elevation": {
+ "label": "Location Elevation"
+ },
+ "gc_admin1_id": {
+ "label": "Location Gc Admin1 Id"
+ },
+ "gc_admin1_name": {
+ "label": "Location Gc Admin1 Name"
+ },
+ "gc_country_id": {
+ "label": "Location Gc Country Id"
+ },
+ "gc_country_name": {
+ "label": "Location Gc Country Name"
+ },
+ "gc_id": {
+ "label": "Location Gc Id"
+ },
+ "gc_name": {
+ "label": "Location Gc Name"
+ },
+ "geohash": {
+ "label": "Location Geohash"
+ },
+ "kind": {
+ "label": "Location Kind"
+ },
+ "label": {
+ "label": "Location Label"
+ },
+ "lat": {
+ "label": "Location Latitude"
+ },
+ "lng": {
+ "label": "Location Latitude"
+ },
+ "soil": {
+ "label": "Location Soil"
+ }
+ },
+ "schema": {
+ "geohash.length": "The location geohash must be 9 characters",
+ "geohash.required": "The location geohash is required",
+ "kind.required": "The location kind is required",
+ "lat.max": "The location latitude must be less than 90",
+ "lat.min": "The location latitude must be greater than -90",
+ "lat.required": "The location latitude is required",
+ "lng.max": "The location latitude must be less than 180",
+ "lng.min": "The location latitude must be greater than -180",
+ "lng.required": "The location latitude is required"
+ }
+ },
+ "log_error": {
+ "fields": {
+ "app_system": {
+ "label": "Log App System"
+ },
+ "app_version": {
+ "label": "Log App Version"
+ },
+ "cause": {
+ "label": "Log Cause"
+ },
+ "data": {
+ "label": "Log Data"
+ },
+ "error": {
+ "label": "Log Error"
+ },
+ "message": {
+ "label": "Log Message"
+ },
+ "nostr_pubkey": {
+ "label": "Log Nostr Pubkey"
+ },
+ "stack_trace": {
+ "label": "Log Stack Trace"
+ }
+ },
+ "schema": {
+ "app_system.required": "The log app system is required",
+ "app_version.required": "The log app version is required",
+ "error.required": "The log error is required",
+ "message.required": "The log message is required",
+ "nostr_pubkey.required": "The log nostr pubkey is required"
+ }
+ },
+ "media_upload": {
+ "fields": {
+ "description": {
+ "label": "Description"
+ },
+ "file_path": {
+ "label": "File Path"
+ },
+ "label": {
+ "label": "Label"
+ },
+ "mime_type": {
+ "label": "Mime Type"
+ },
+ "res_base": {
+ "label": "Resource Endpoint"
+ },
+ "res_path": {
+ "label": "Resource Path"
+ }
+ },
+ "schema": {
+ "file_path.required": "The media file path is required",
+ "mime_type.required": "The media mime type is required",
+ "res_base.regex": "The media resource endpoint requires an http protocol",
+ "res_base.required": "The media resource endpoint is required",
+ "res_base.url": "The media resource endpoint is incorrectly formatted",
+ "res_path.required": "The media resource path is required"
+ }
+ },
+ "nostr_profile": {
+ "fields": {
+ "about": {
+ "label": "Profile About"
+ },
+ "banner": {
+ "label": "Profile Banner"
+ },
+ "display_name": {
+ "label": "Profile Display Name"
+ },
+ "lud06": {
+ "label": "Profile Lud-06"
+ },
+ "lud16": {
+ "label": "Profile Lud-16"
+ },
+ "name": {
+ "label": "Profile Name"
+ },
+ "nip05": {
+ "label": "Profile Nip-05"
+ },
+ "picture": {
+ "label": "Profile Picture"
+ },
+ "public_key": {
+ "label": "Profile Public Key"
+ },
+ "website": {
+ "label": "Profile Website"
+ }
+ },
+ "schema": {
+ "banner.url": "The profile banner url is incomplete",
+ "nip05.email": "The profile nip-05 is incorrectly formatted",
+ "picture.url": "The profile picture url is incomplete",
+ "public_key.length": "The profile public key must be 64 characters",
+ "public_key.required": "The profile public key is required",
+ "website.url": "The profile website url is incomplete"
+ }
+ },
+ "nostr_relay": {
+ "fields": {
+ "contact": {
+ "label": "Administrator Contact"
+ },
+ "data": {
+ "label": "Additional Information"
+ },
+ "description": {
+ "label": "Relay Description"
+ },
+ "name": {
+ "label": "Relay Name"
+ },
+ "pubkey": {
+ "label": "Administrator"
+ },
+ "relay_id": {
+ "label": "Relay Id"
+ },
+ "software": {
+ "label": "Software"
+ },
+ "supported_nips": {
+ "label": "Supported Nips"
+ },
+ "url": {
+ "label": "Relay Endpoint"
+ },
+ "version": {
+ "label": "Software Version"
+ }
+ },
+ "schema": {
+ "url.regex": "The relay relay endpoint requires a websocket protocol",
+ "url.required": "The relay relay endpoint is required",
+ "url.url": "The relay relay endpoint is incorrectly formatted"
+ }
+ },
+ "trade_product": {
+ "fields": {
+ "category": {
+ "label": "Product Category"
+ },
+ "key": {
+ "label": "Product Kind"
+ },
+ "lot": {
+ "label": "Product Lot"
+ },
+ "notes": {
+ "label": "Notes"
+ },
+ "price_amt": {
+ "label": "Price Amount"
+ },
+ "price_currency": {
+ "label": "Price Currency"
+ },
+ "price_qty_amt": {
+ "label": "Price Quantity"
+ },
+ "price_qty_unit": {
+ "label": "Price Quantity Unit"
+ },
+ "process": {
+ "label": "Processing Method"
+ },
+ "profile": {
+ "label": "Flavor Profile"
+ },
+ "qty_amt": {
+ "label": "Quantity Amount"
+ },
+ "qty_avail": {
+ "label": "Quantity Available"
+ },
+ "qty_label": {
+ "label": "Quantity Name"
+ },
+ "qty_unit": {
+ "label": "Quantity Unit"
+ },
+ "summary": {
+ "label": "Product Description"
+ },
+ "title": {
+ "label": "Product Title"
+ },
+ "year": {
+ "label": "Production Year"
+ }
+ },
+ "schema": {
+ "category.required": "The product category is required",
+ "key.required": "The product kind is required",
+ "lot.max": "The product lot must be less than 120 characters",
+ "lot.min": "The product lot must be more than 1 character",
+ "lot.required": "The product lot is required",
+ "price_amt.positive": "The product price amount must be positive",
+ "price_amt.required": "The product price amount is required",
+ "price_currency.length": "The product price currency must be 3 characters",
+ "price_currency.required": "The product price currency is required",
+ "price_qty_amt.int": "The product price quantity must be an integer",
+ "price_qty_amt.positive": "The product price quantity must be positive",
+ "price_qty_amt.required": "The product price quantity is required",
+ "price_qty_unit.required": "The product price quantity unit is required",
+ "process.required": "The product processing method is required",
+ "profile.required": "The product flavor profile is required",
+ "qty_amt.int": "The product quantity amount must be an integer",
+ "qty_amt.positive": "The product quantity amount must be positive",
+ "qty_amt.required": "The product quantity amount is required",
+ "qty_avail.int": "The product quantity available must be an integer",
+ "qty_avail.positive": "The product quantity available must be positive",
+ "qty_unit.required": "The product quantity unit is required",
+ "summary.required": "The product description is required",
+ "title.required": "The product title is required",
+ "year.int": "The product production year must be an integer",
+ "year.positive": "The product production year must be positive",
+ "year.required": "The product production year is required"
+ }
+ }
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/locale/en/trade.json b/apps-lib/src/lib/locale/en/trade.json
@@ -0,0 +1,111 @@
+{
+ "glossary": {
+ "lot": "Lot",
+ "process": "Process",
+ "profile": "Profile"
+ },
+ "product": {
+ "fields": {
+ "price_amt": {
+ "err_invalid": "Enter the price"
+ }
+ },
+ "key": {
+ "cacao": {
+ "name": "Cacao",
+ "process": {
+ "chocolate": "Chocolate",
+ "cocoa_butter": "Cocoa Butter",
+ "cocoa_powder": "Cocoa Powder",
+ "dried": "Dried",
+ "fermented": "Fermented",
+ "raw": "Raw",
+ "roasted": "Roasted"
+ }
+ },
+ "coffee": {
+ "name": "Coffee",
+ "process": {
+ "carbonic_maceration": "Carbonic Maceration",
+ "dry": "Dry",
+ "honey": "Honey",
+ "natural": "Natural",
+ "pulped_natural": "Pulped Natural",
+ "semi_washed": "Semi-Washed",
+ "washed": "Washed",
+ "wet_hulled": "Wet-Hulled"
+ },
+ "profile": {
+ "apricot": "Apricot",
+ "banana": "Banana",
+ "bergamot": "Bergamot",
+ "berry": "Berry",
+ "black cherry": "Black Cherry",
+ "black currant": "Black Currant",
+ "black tea": "Black Tea",
+ "blueberry": "Blueberry",
+ "brown sugar": "Brown Sugar",
+ "caramel": "Caramel",
+ "cherry": "Cherry",
+ "chocolate": "Chocolate",
+ "cola": "Cola",
+ "cranberry": "Cranberry",
+ "ctrius": "Ctrius",
+ "dark chocolate": "Dark Chocolate",
+ "dried dates": "Dried Dates",
+ "dried fig": "Dried Fig",
+ "golden raisin": "Golden Raisin",
+ "grape": "Grape",
+ "grapefruit": "Grapefruit",
+ "green apple": "Green Apple",
+ "green grape": "Green Grape",
+ "green tea": "Green Tea",
+ "jasmine honeysuckle": "Jasmine Honeysuckle",
+ "lemon": "Lemon",
+ "licorice/anise": "Licorice/Anise",
+ "lychee": "Lychee",
+ "magnolia": "Magnolia",
+ "mandarin": "Mandarin",
+ "mango": "Mango",
+ "maple syrup": "Maple Syrup",
+ "marshmallow": "Marshmallow",
+ "milk chocolate": "Milk Chocolate",
+ "natural": "Natural",
+ "nectarine": "Nectarine",
+ "nougat": "Nougat",
+ "nut": "Nut",
+ "orange": "Orange",
+ "orange blossom": "Orange Blossom",
+ "peach": "Peach",
+ "pineapple": "Pineapple",
+ "plum": "Plum",
+ "prune": "Prune",
+ "raisin": "Raisin",
+ "raspberry": "Raspberry",
+ "red apple": "Red Apple",
+ "red currant": "Red Currant",
+ "red grape": "Red Grape",
+ "roses": "Roses",
+ "star fruit": "Star Fruit",
+ "stronefruit": "Stronefruit",
+ "sugar cane": "Sugar Cane",
+ "sweet bread pastry": "Sweet Bread Pastry",
+ "tamarind": "Tamarind",
+ "tropical fruit": "Tropical Fruit",
+ "vanilla": "Vanilla",
+ "white grape": "White Grape"
+ }
+ },
+ "maca": {
+ "name": "Maca",
+ "process": {
+ "capsules": "Capsules",
+ "gelatinized": "Gelatinized",
+ "powdered": "Powdered",
+ "raw": "Raw",
+ "roasted": "Roasted"
+ }
+ }
+ }
+ }
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/locale/i18n.ts b/apps-lib/src/lib/locale/i18n.ts
@@ -0,0 +1,42 @@
+import i18n from '@sveltekit-i18n/base';
+import type { Config } from '@sveltekit-i18n/parser-icu';
+import parser from '@sveltekit-i18n/parser-icu';
+import locales_keys from './locales.json';
+
+type LanguageConfig = {
+ default?: string;
+ value?: string;
+};
+
+export type Locale = keyof typeof locales_keys;
+
+const libtranslations: Record<Locale, any> = {
+ en: { locales_keys },
+};
+
+const config: Config<LanguageConfig> = {
+ initLocale: `en` satisfies Locale,
+ translations: libtranslations,
+ parser: parser(),
+ loaders: [
+ ...Object.keys(libtranslations).map((locale) => [`common`, `icu`, `measurement`, `model`, `trade`].map(key => ({
+ locale,
+ key,
+ loader: async () => (await import(`./${locale}/${key}.json`)).default
+ }))),
+ ].flat()
+};
+
+const {
+ t: lls,
+ loading: libtranslations_loading,
+ locales: liblocales,
+ locale: liblocale,
+ loadTranslations: libload_translations
+} = new i18n(config);;
+
+libtranslations_loading.subscribe(async (_loading) => {
+ if (_loading) await libtranslations_loading.toPromise();
+});
+export { libload_translations, liblocale, liblocales, libtranslations, libtranslations_loading, lls };
+
diff --git a/apps-lib/src/lib/locale/locales.json b/apps-lib/src/lib/locale/locales.json
@@ -0,0 +1,3 @@
+{
+ "en": "English"
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/service/search/lib.ts b/apps-lib/src/lib/service/search/lib.ts
@@ -0,0 +1,67 @@
+import { type ISearchService, type SearchServiceFlattenedData, type SearchServiceResult } from "$lib";
+
+export class SearchService implements ISearchService {
+ private _flattened_data: SearchServiceFlattenedData[] = [];
+ private _index_map: Map<string, Record<string, any>[]> = new Map();
+
+ constructor(data: Record<string, any[]>) {
+ this.flatten_data(data);
+ this.index_data();
+ }
+
+ private get flattened_data() {
+ return this._flattened_data;
+ }
+
+ private flatten_data(data: Record<string, any[]>) {
+ Object.keys(data).forEach((list_group) => {
+ const list = data[list_group];
+ list.forEach((item) => {
+ const flattened_item: SearchServiceFlattenedData = { ...item, group: list_group };
+ this._flattened_data.push(flattened_item);
+ });
+ });
+ }
+
+ private index_data(): void {
+ this.flattened_data.forEach((item) => {
+ Object.keys(item).forEach((key) => {
+ const key_lower = key.toLowerCase();
+ const value = item[key];
+ if (value != null) {
+ if (!this._index_map.has(key_lower)) this._index_map.set(key_lower, []);
+ this._index_map.get(key_lower)?.push(item);
+ }
+ });
+ });
+ }
+
+ public search(query: string): SearchServiceResult[] {
+ if (!query) return [];
+ const search_query = query.toLowerCase().trim();
+ let results: SearchServiceResult[] = [];
+ const results_seen = new Set<string>();
+ this._index_map.forEach((items) => {
+ items.forEach((item) => {
+ for (const [key, value] of Object.entries(item)) {
+ if (key === `id` || key === `created_at` || key === `updated_at` || key === `public_key` || key === `group`) continue;
+ if (value && value.toString().replace(/[()_-]/gi, ` `).toLowerCase().includes(search_query)) {
+ const { group, ...rest } = item;
+ if (!(`id` in item)) continue;
+ const result_key = item.id;
+ if (results_seen.has(result_key)) continue;
+ results_seen.add(result_key);
+ const reshaped_result: SearchServiceResult = {
+ id: item.id,
+ result_k: key,
+ result_v: value,
+ [group]: { ...rest },
+ };
+ results.push(reshaped_result);
+ }
+ };
+ });
+ });
+ return results;
+ }
+}
diff --git a/apps-lib/src/lib/service/search/types.ts b/apps-lib/src/lib/service/search/types.ts
@@ -0,0 +1,6 @@
+export type SearchServiceResult = Record<string, any> & { id: string, result_k: string; result_v: string; };
+export type SearchServiceFlattenedData = Record<string, any> & { list: string; };
+
+export type ISearchService = {
+ search(input: string): SearchServiceResult[]
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/store/app.ts b/apps-lib/src/lib/store/app.ts
@@ -0,0 +1,20 @@
+import type { AppConfigType, AppLayoutKey, IToast } from "$lib";
+import type { ColorMode, ThemeKey } from "@radroots/theme";
+import { writable } from "svelte/store";
+
+export const app_layout = writable<AppLayoutKey>(`mobile_base`);
+export const app_cfg_type = writable<AppConfigType>(`personal`);
+export const app_init = writable<boolean>(false);
+export const app_tilt = writable<boolean>(false);
+export const app_loading = writable<boolean>(false);
+export const app_splash = writable<boolean>(true);
+export const app_win = writable<{ h: number, w: number }>({ h: 0, w: 0 });
+export const app_notify = writable<string>(``);
+export const app_toast = writable<IToast | false>(false);
+export const app_blur = writable<boolean>(false);
+export const app_db = writable<boolean>(false);
+export const app_geoc = writable<boolean>(false);
+export const app_thc = writable<ColorMode>(`light`);
+export const app_th = writable<ThemeKey>(`os`);
+export const key_nostr = writable<string>(``);
+export const app_nostr_profiles = writable<string[]>([]);
diff --git a/apps-lib/src/lib/store/client.ts b/apps-lib/src/lib/store/client.ts
@@ -0,0 +1,50 @@
+import { type NavigationPreviousParam } from "$lib";
+import { writable } from "svelte/store";
+import { queryParam, queryParameters } from "sveltekit-search-params";
+
+export const qp = queryParameters();
+export const qp_nostr_pk = queryParam<string>("nostr_pk");
+export const qp_rkey = queryParam<string>("rkey");
+export const qp_id = queryParam<string>("id");
+export const qp_lat = queryParam<string>("lat");
+export const qp_lng = queryParam<string>("lng");
+
+export const app_pwa_polyfills = writable<boolean>(false);
+
+export const nav_visible = writable<boolean>(false);
+export const nav_blur = writable<boolean>(false);
+export const nav_prev = writable<NavigationPreviousParam<string>[]>([]);
+
+export const layout_view_cover = writable<boolean>(false);
+
+export const tabs_visible = writable<boolean>(false);
+export const tabs_blur = writable<boolean>(false);
+export const tabs_active = writable<number>(0);
+
+export const ph_blur = writable<boolean>(false);
+
+export const carousel_active = writable<boolean>(false);
+export const carousel_index = writable<number>(0);
+export const carousel_index_max = writable<number>(0);
+const fn_carousel_num = (num_i: number, num_min: number) => {
+ const store = writable<number>(num_i);
+ return {
+ subscribe: store.subscribe,
+ set: (num: number) => {
+ store.set(Math.max(num, num_min));
+ },
+ update: (updater: (num: number) => number) => {
+ store.update((num) => Math.max(updater(num), num_min));
+ }
+ };
+}
+export const carousel_num = fn_carousel_num(1, 1);
+export const envelope_visible = writable<boolean>(false);
+export const envelope_tilt = writable<boolean>(true);
+
+export const nostr_ndk_configured = writable<boolean>(false);
+export const nostr_relays_poll_documents = writable<boolean>(false);
+export const nostr_relays_poll_documents_count = writable<number>(0);
+export const nostr_relays_connected = writable<string[]>([]);
+export const nostr_sync_prevent = writable<boolean>(false);
+
diff --git a/apps-lib/src/lib/store/component.ts b/apps-lib/src/lib/store/component.ts
@@ -0,0 +1,4 @@
+import { writable } from "svelte/store";
+
+export const casl_index = writable<number>(0);
+export const casl_index_max = writable<number>(0);
+\ No newline at end of file
diff --git a/apps-lib/src/lib/stores/ndk.ts b/apps-lib/src/lib/store/ndk.ts
diff --git a/apps-lib/src/lib/stores/app.ts b/apps-lib/src/lib/stores/app.ts
@@ -1,20 +0,0 @@
-import type { AppConfigType, AppLayoutKey, IToast } from "$lib";
-import type { ColorMode, ThemeKey } from "@radroots/theme";
-import { writable } from "svelte/store";
-
-export const app_layout = writable<AppLayoutKey>(`mobile_base`);
-export const app_cfg_type = writable<AppConfigType>(`personal`);
-export const app_init = writable<boolean>(false);
-export const app_tilt = writable<boolean>(false);
-export const app_loading = writable<boolean>(false);
-export const app_splash = writable<boolean>(true);
-export const app_win = writable<{ h: number, w: number }>({ h: 0, w: 0 });
-export const app_notify = writable<string>(``);
-export const app_toast = writable<IToast | false>(false);
-export const app_blur = writable<boolean>(false);
-export const app_db = writable<boolean>(false);
-export const app_geoc = writable<boolean>(false);
-export const app_thc = writable<ColorMode>(`light`);
-export const app_th = writable<ThemeKey>(`os`);
-export const app_nostrkey = writable<string>(``);
-export const app_nostr_profiles = writable<string[]>([]);
diff --git a/apps-lib/src/lib/stores/client.ts b/apps-lib/src/lib/stores/client.ts
@@ -1,58 +0,0 @@
-import { type NavigationPreviousParam } from "$lib";
-import { writable } from "svelte/store";
-import { queryParam, queryParameters } from "sveltekit-search-params";
-
-//@ts-ignore
-const kv_name = import.meta.env.VITE_PUBLIC_KV_NAME;
-if (!kv_name) throw new Error('Error: VITE_PUBLIC_KV_NAME is required');
-
-export const qp = queryParameters();
-export const qp_nostr_pk = queryParam<string>("nostr_pk");
-export const qp_rkey = queryParam<string>("rkey");
-export const qp_id = queryParam<string>("id");
-export const qp_lat = queryParam<string>("lat");
-export const qp_lng = queryParam<string>("lng");
-
-export let kv: Keyva;
-if (typeof window !== 'undefined') kv = new Keyva({ name: kv_name });
-
-
-export const app_pwa_polyfills = writable<boolean>(false);
-
-export const nav_visible = writable<boolean>(false);
-export const nav_blur = writable<boolean>(false);
-export const nav_prev = writable<NavigationPreviousParam<string>[]>([]);
-
-export const layout_view_cover = writable<boolean>(false);
-
-export const tabs_visible = writable<boolean>(false);
-export const tabs_blur = writable<boolean>(false);
-export const tabs_active = writable<number>(0);
-
-export const ph_blur = writable<boolean>(false);
-
-export const carousel_active = writable<boolean>(false);
-export const carousel_index = writable<number>(0);
-export const carousel_index_max = writable<number>(0);
-const fn_carousel_num = (num_i: number, num_min: number) => {
- const store = writable<number>(num_i);
- return {
- subscribe: store.subscribe,
- set: (num: number) => {
- store.set(Math.max(num, num_min));
- },
- update: (updater: (num: number) => number) => {
- store.update((num) => Math.max(updater(num), num_min));
- }
- };
-}
-export const carousel_num = fn_carousel_num(1, 1);
-export const envelope_visible = writable<boolean>(false);
-export const envelope_tilt = writable<boolean>(true);
-
-export const nostr_ndk_configured = writable<boolean>(false);
-export const nostr_relays_poll_documents = writable<boolean>(false);
-export const nostr_relays_poll_documents_count = writable<number>(0);
-export const nostr_relays_connected = writable<string[]>([]);
-export const nostr_sync_prevent = writable<boolean>(false);
-
diff --git a/apps-lib/src/lib/types/app.ts b/apps-lib/src/lib/types/app.ts
@@ -1,3 +1,20 @@
+//%types%
+export type NavigationRoute = string;
+//%types%
+
+export type INavigationRoutePreventRouteNav = {
+ prevent_route?: {
+ callback: CallbackPromise;
+ };
+}
+
+export type INavigationRoutePreventRoute = {
+ prevent_route: CallbackPromise;
+}
+export type INavigationRoute = {
+ route: NavigationRoute | [NavigationRoute, NavigationParamTuple[]];
+};
+
export type GlyphKey = |
`images-square` |
`bell` |
@@ -118,6 +135,7 @@ export type GlyphKeyCurrency = `dollar` | `eur`;
export type AppConfigType = `farmer` | `personal`
export type AppLayoutKey = 'mobile_base' | 'mobile_y';
+export type CallbackPromiseFull<Ti, Tr> = (value: Ti) => Promise<Tr>;
export type CallbackPromiseGeneric<T> = (value: T) => Promise<void>;
export type CallbackPromiseReturn<T> = () => Promise<T>;
export type CallbackPromise = () => Promise<void>;
@@ -161,3 +179,5 @@ export type NavigationRouteParamLng = `lng`;
export type NavigationRouteParamKey = NavigationRouteParamNostrPublicKey | NavigationRouteParamId | NavigationRouteParamRecordKey | NavigationRouteParamLat | NavigationRouteParamLng;
export type NavigationParamTuple = [NavigationRouteParamKey, string];
export type NavigationPreviousParam<T extends string> = { route: T, label?: string; params?: NavigationParamTuple[] }
+
+export type CallbackRoute = CallbackPromise | INavigationRoute;
+\ No newline at end of file
diff --git a/apps-lib/src/lib/types/component.ts b/apps-lib/src/lib/types/component.ts
@@ -0,0 +1,156 @@
+import type { CallbackPromise, CallbackRoute, GlyphKey, ICbG, ICbGOpt, ICbOpt, IClOpt, IDisabledOpt, IGl, IGlOpt, IGlyph, IInput, ILabel, ILabelOpt, ILabelTup, ILoadingOpt, ILy, INavigationRoute, INavigationRoutePreventRouteNav, ISelect } from "$lib";
+
+export type IBasisOpt<T extends object> = T | undefined;
+
+export type IPageToolbar = ICbOpt & {
+ header?: IPageHeader;
+};
+
+export type IPageHeader = {
+ label: string;
+ callback_route?: CallbackRoute;
+};
+
+export type IGlyphCircle = {
+ classes_wrap: string;
+ glyph: IGlyph
+};
+
+export type ITrellis = ILy &
+ IClOpt &
+ ITrellisStyles & {
+ id?: string;
+ view?: string;
+ title?: ITrellisTitle;
+ description?: ITrellisDescription;
+ default_el?: ITrellisDefault;
+ list?: (ITrellisKind | undefined)[];
+ hide_offset?: true;
+ };
+
+export type ITrellisStyles = {
+ hide_rounded?: boolean;
+ hide_border_top?: boolean;
+ hide_border_bottom?: boolean;
+ set_title_background?: boolean;
+ set_default_background?: boolean;
+};
+
+export type ITrellisTitle = ICbOpt &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod,
+ value: string | true;
+ link?: ICbOpt &
+ IClOpt &
+ IGlOpt & ILabelOpt;
+ };
+
+export type ITrellisDescription = string | true;
+
+export type ITrellisBasisOffsetModKey = 'sm' | 'glyph';
+export type ITrellisBasisOffsetMod = ITrellisBasisOffsetModKey | (({ glyph: IGlyph } | { glyph_circle: IGlyphCircle }) & {
+ loading?: boolean;
+});
+
+export type ITrellisDefault = {
+ labels?: ITrellisDefaultLabel[];
+ show_title?: boolean;
+};
+
+export type ITrellisDefaultLabel = ICbOpt & {
+ label: string;
+ classes?: string;
+};
+
+export type ITrellisKind = (
+ | ITrellisKindTouch
+ | ITrellisKindInput
+ | ITrellisKindSelect
+);
+
+export type ITrellisBasis = {
+ loading?: boolean;
+ hide_active?: boolean;
+ hide_field?: boolean;
+ offset?: ITrellisBasisOffset;
+ full_rounded?: boolean;
+};
+
+export type ITrellisBasisOffset = ICbGOpt<MouseEvent> &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod;
+ classes?: string;
+ hide_space?: boolean;
+ hide_offset?: boolean;
+ };
+
+export type ITrellisKindDisplay = {
+ display?: ITrellisKindDisplayValue;
+}
+export type ITrellisKindDisplayValue = ICbGOpt<MouseEvent> & ILoadingOpt &
+ (ITrellisKindDisplayValueIcon | ILabel);
+
+
+export type ITrellisKindDisplayValueIcon = {
+ icon: {
+ classes?: string;
+ key: GlyphKey;
+ };
+};
+export type ITrellisKindTouch = ITrellisBasis & {
+ touch: ITrellisBasisTouch;
+};
+
+export type ITrellisBasisTouch = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & {
+ end?: ITrellisBasisTouchEnd;
+ };
+
+export type ITrellisKindInput = ITrellisBasis & {
+ input: ITrellisBasisInput;
+};
+
+export type ITrellisBasisInput = {
+ basis: IInput<string>;
+ line_label?: {
+ classes?: string;
+ value: string;
+ };
+ action?: {
+ visible: boolean;
+ loading?: boolean;
+ callback?: CallbackPromise;
+ glyph?: IGlyph
+ };
+};
+
+export type ITrellisKindSelect = ITrellisBasis & {
+ select: ITrellisBasisSelect;
+};
+
+export type ITrellisBasisSelect = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & ILoadingOpt & {
+ end?: ITrellisBasisTouchEnd;
+ el: ISelect & { value: string; };
+ };
+
+export type ITrellisBasisTouchEnd = ICbGOpt<MouseEvent> & IGl;
+
+export type INavBasisPrev = IClOpt & ICbG<
+ HTMLLabelElement | null
+> & IGlOpt & ILabelOpt & IDisabledOpt & {
+ loading?: boolean;
+};
+export type INavBasisOption = IClOpt & ICbG<
+ HTMLLabelElement | null
+> & IGlOpt & ILabelOpt & IDisabledOpt & {
+ loading?: boolean;
+};
+export type INavBasis = {
+ prev: ICbOpt & ILoadingOpt & INavigationRoute & INavigationRoutePreventRouteNav & {
+ label?: string;
+ kind?: 'arrow'
+ };
+ title?: ICbOpt & ILabel;
+ option?: INavBasisOption;
+};
diff --git a/apps-lib/src/lib/types/el.ts b/apps-lib/src/lib/types/el.ts
@@ -1,4 +1,4 @@
-import type { ElementCallbackMount, ElementCallbackValue, ElementCallbackValueBlur, ElementCallbackValueKeydown, FormField, GeometryGlyphDimension, GeometryScreenPosition, GlyphKey, GlyphWeight, ICbOpt, IClOpt, IGlOpt, IIdGOpt, IIdOpt, ILabel, ILyOpt } from "$lib";
+import type { CallbackPromiseGeneric, ElementCallbackMount, ElementCallbackValue, ElementCallbackValueBlur, ElementCallbackValueKeydown, FormField, GeometryGlyphDimension, GeometryScreenPosition, GlyphKey, GlyphWeight, ICbGOpt, ICbOpt, IClOpt, IDisabledOpt, IGlOpt, IId, IIdGOpt, IIdOpt, ILabel, ILyOpt } from "$lib";
export type IToastKind = `simple`;
@@ -14,8 +14,7 @@ export type IGlyph = ICbOpt & IIdOpt & ILyOpt & IClOpt & {
dim?: GeometryGlyphDimension;
};
-
-export type IInputElement<T extends string> = IIdGOpt<T> & IClOpt & ILyOpt & {
+export type IInput<T extends string> = IIdGOpt<T> & IClOpt & ILyOpt & {
placeholder?: string;
label?: string;
hidden?: boolean;
@@ -27,4 +26,31 @@ export type IInputElement<T extends string> = IIdGOpt<T> & IClOpt & ILyOpt & {
callback_blur?: ElementCallbackValueBlur<HTMLInputElement>;
callback_focus?: ElementCallbackValueBlur<HTMLInputElement>;
callback_mount?: ElementCallbackMount<HTMLInputElement>;
+};
+
+export type ISelectOption<T extends string> = IDisabledOpt & {
+ value: T;
+ label: string;
+};
+
+export type ISelect = IIdOpt & IClOpt & ILyOpt &
+ ICbGOpt<ISelectOption<string>> & {
+ sync?: boolean;
+ sync_init?: boolean;
+ options: { group?: string | true; entries: ISelectOption<string>[] }[];
+ show_arrows?: 'l' | 'r';
+ };
+
+export type ITextAreaElement = IId & IClOpt & ILyOpt & {
+ placeholder?: string;
+ label?: string;
+ hidden?: boolean;
+ validate?: RegExp;
+ sync?: true;
+ field?: FormField;
+ callback?: CallbackPromiseGeneric<{ value: string; pass: boolean; }>;
+ callback_keydown?: CallbackPromiseGeneric<{ key: string; key_s: boolean; el: HTMLTextAreaElement }>;
+ callback_blur?: CallbackPromiseGeneric<{ el: HTMLTextAreaElement }>;
+ callback_focus?: CallbackPromiseGeneric<{ el: HTMLTextAreaElement }>;
+ on_mount?: CallbackPromiseGeneric<HTMLTextAreaElement>;
};
\ No newline at end of file
diff --git a/apps-lib/src/lib/types/feature.ts b/apps-lib/src/lib/types/feature.ts
@@ -0,0 +1,7 @@
+import { type CallbackPromiseGeneric, type ModelLocationGcs, type ModelNostrProfile, type ModelNostrRelay } from "$lib";
+
+export type ISearchResultDisplayCallbacks = {
+ lc_handle_result_location_gcs: CallbackPromiseGeneric<ModelLocationGcs>;
+ lc_handle_result_nostr_profile: CallbackPromiseGeneric<ModelNostrProfile>;
+ lc_handle_result_nostr_relay: CallbackPromiseGeneric<ModelNostrRelay>;
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/types/interface.ts b/apps-lib/src/lib/types/interface.ts
@@ -1,4 +1,4 @@
-import type { CallbackPromise, EntryStyle, GlyphKey, IGlyph, IInputElement, LayerGlyphBasisKind, LoadingBlades, LoadingDimension } from "$lib";
+import type { CallbackPromise, CallbackPromiseGeneric, EntryStyle, GlyphKey, IGlyph, IInput, LayerGlyphBasisKind, LoadingBlades, LoadingDimension } from "$lib";
import type { ThemeLayer } from "@radroots/theme";
import type { TransitionConfig } from "svelte/transition";
@@ -16,6 +16,12 @@ export type ICb = {
export type ICbOpt = Partial<ICb>;
+export type ICbG<T> = {
+ callback: CallbackPromiseGeneric<T> | never;
+};
+
+export type ICbGOpt<T> = Partial<ICbG<T>>;
+
export type ICl = {
classes: string | never;
};
@@ -35,6 +41,10 @@ export type IGl = {
export type IGlOpt = Partial<IGl>;
+export type IGlyphKey = {
+ glyph: GlyphKey
+};
+
export type ILy = {
layer: ThemeLayer | never;
};
@@ -55,6 +65,17 @@ export type ILabelSwap = {
swap: ILableFieldsSwap;
}
+export type ILabelTupFields = {
+ left?: ILableFields[];
+ right?: ILableFields[];
+};
+
+export type ILabelTup = {
+ label: ILabelTupFields;
+};
+
+export type LabelFieldKind = `link` | `on` | `shade`;
+
export type ILableFields = & {
classes_wrap?: string
classes?: string;
@@ -72,6 +93,8 @@ export type ILabel = {
label: ILableFields;
};
+export type ILabelOpt = Partial<ILabel>;
+
export type ILoadSymbol = IClOpt & {
color?: 'white';
blades?: LoadingBlades;
@@ -126,7 +149,7 @@ export type IEntryWrap = IClOpt & IIdOpt & ILyOpt & {
export type IEntryLine = ILoadingOpt & {
wrap?: IEntryWrap;
- el: IInputElement<string>;
+ el: IInput<string>;
notify_inline?: {
glyph: GlyphKey | IGlyph;
};
diff --git a/apps-lib/src/lib/types/model.ts b/apps-lib/src/lib/types/model.ts
@@ -0,0 +1,131 @@
+export type ModelTableBasis = {
+ id: string;
+ created_at: string;
+ updated_at: string;
+};
+
+export type ModelLocationGcs = ModelTableBasis & ModelLocationGcsFields;
+
+export type ModelLocationGcsFields = {
+ lat: number;
+ lng: number;
+ geohash: string;
+ kind: string;
+ label?: string;
+ area?: number;
+ elevation?: number;
+ soil?: string;
+ climate?: string;
+ gc_id?: string;
+ gc_name?: string;
+ gc_admin1_id?: string;
+ gc_admin1_name?: string;
+ gc_country_id?: string;
+ gc_country_name?: string;
+};
+export type ModelLocationGcsFieldsKey = "lat" | "lng" | "geohash" | "kind" | "label" | "area" | "elevation" | "soil" | "climate" | "gc_id" | "gc_name" | "gc_admin1_id" | "gc_admin1_name" | "gc_country_id" | "gc_country_name";
+
+export type ModelLocationGcsFormFields = Record<ModelLocationGcsFieldsKey, string>;
+
+export type ModelLocationGcsEditFields = Partial<ModelLocationGcsFormFields>;
+
+export type ModelTradeProduct = ModelTableBasis & ModelTradeProductFields;
+
+export type ModelTradeProductFields = {
+ key: string;
+ category: string;
+ title: string;
+ summary: string;
+ process: string;
+ lot: string;
+ profile: string;
+ year: number;
+ qty_amt: number;
+ qty_unit: string;
+ qty_label?: string;
+ qty_avail?: number;
+ price_amt: number;
+ price_currency: string;
+ price_qty_amt: number;
+ price_qty_unit: string;
+ notes?: string;
+};
+export type ModelTradeProductFieldsKey = "key" | "category" | "title" | "summary" | "process" | "lot" | "profile" | "year" | "qty_amt" | "qty_unit" | "qty_label" | "qty_avail" | "price_amt" | "price_currency" | "price_qty_amt" | "price_qty_unit" | "notes";
+
+export type ModelTradeProductFormFields = Record<ModelTradeProductFieldsKey, string>;
+
+export type ModelTradeProductEditFields = Partial<ModelTradeProductFormFields>;
+
+export type ModelNostrProfile = ModelTableBasis & ModelNostrProfileFields;
+
+export type ModelNostrProfileFields = {
+ public_key: string;
+ name?: string;
+ display_name?: string;
+ about?: string;
+ website?: string;
+ picture?: string;
+ banner?: string;
+ nip05?: string;
+ lud06?: string;
+ lud16?: string;
+};
+export type ModelNostrProfileFieldsKey = "public_key" | "name" | "display_name" | "about" | "website" | "picture" | "banner" | "nip05" | "lud06" | "lud16";
+
+export type ModelNostrProfileFormFields = Record<ModelNostrProfileFieldsKey, string>;
+
+export type ModelNostrProfileEditFields = Partial<ModelNostrProfileFormFields>;
+
+export type ModelNostrRelay = ModelTableBasis & ModelNostrRelayFields;
+
+export type ModelNostrRelayFields = {
+ url: string;
+ relay_id?: string;
+ name?: string;
+ description?: string;
+ pubkey?: string;
+ contact?: string;
+ supported_nips?: string;
+ software?: string;
+ version?: string;
+ data?: string;
+};
+export type ModelNostrRelayFieldsKey = "url" | "relay_id" | "name" | "description" | "pubkey" | "contact" | "supported_nips" | "software" | "version" | "data";
+
+export type ModelNostrRelayFormFields = Record<ModelNostrRelayFieldsKey, string>;
+
+export type ModelNostrRelayEditFields = Partial<ModelNostrRelayFormFields>;
+
+export type ModelMediaImage = ModelTableBasis & ModelMediaImageFields;
+
+export type ModelMediaImageFields = {
+ file_path: string;
+ mime_type: string;
+ res_base: string;
+ res_path: string;
+ label?: string;
+ description?: string;
+};
+export type ModelMediaImageFieldsKey = "file_path" | "mime_type" | "res_base" | "res_path" | "label" | "description";
+
+export type ModelMediaImageFormFields = Record<ModelMediaImageFieldsKey, string>;
+
+export type ModelMediaImageEditFields = Partial<ModelMediaImageFormFields>;
+
+export type ModelLogError = ModelTableBasis & ModelLogErrorFields;
+
+export type ModelLogErrorFields = {
+ error: string;
+ message: string;
+ stack_trace?: string;
+ cause?: string;
+ app_system: string;
+ app_version: string;
+ nostr_pubkey: string;
+ data?: string;
+};
+export type ModelLogErrorFieldsKey = "error" | "message" | "stack_trace" | "cause" | "app_system" | "app_version" | "nostr_pubkey" | "data";
+
+export type ModelLogErrorFormFields = Record<ModelLogErrorFieldsKey, string>;
+
+export type ModelLogErrorEditFields = Partial<ModelLogErrorFormFields>;
+\ No newline at end of file
diff --git a/apps-lib/src/lib/types/util.ts b/apps-lib/src/lib/types/util.ts
@@ -0,0 +1,24 @@
+import { type CallbackPromiseFull } from "$lib";
+
+export type GeolocationLatitudeFmtOption = 'dms' | 'd' | 'dm';
+
+export type GeolocationPoint = {
+ lat: number;
+ lng: number;
+};
+
+export type GeocoderReverseResult = {
+ id: number;
+ name: string;
+ admin1_id: string | number;
+ admin1_name: string;
+ country_id: string;
+ country_name: string;
+ latitude: number;
+ longitude: number;
+};
+
+export type ILcGeocodeCallback = CallbackPromiseFull<
+ GeolocationPoint,
+ GeocoderReverseResult | undefined
+>
+\ No newline at end of file
diff --git a/apps-lib/src/lib/types/view.ts b/apps-lib/src/lib/types/view.ts
@@ -0,0 +1,48 @@
+import { type CallbackPromise, type CallbackPromiseFull, type CallbackPromiseReturn, type IBasisOpt } from "..";
+
+export type ViewBasis<T extends object> = {
+ kv_init_prevent?: boolean;
+ lc_on_mount?: CallbackPromise;
+ lc_on_destroy?: CallbackPromise;
+ lc_gui_alert?: CallbackPromiseFull<string, boolean>;
+ lc_gui_confirm?: CallbackPromiseFull<string, boolean>;
+} & T;
+
+export type ViewBasisLoadData<TView extends object, TLoadData extends object> = ViewBasis<TView> & {
+ lc_load_data: CallbackPromiseReturn<TLoadData>;
+ lc_handle_back?: CallbackPromise;
+};
+
+export type IFarmLoadData<
+ TLocationGcs extends object,
+> = IBasisOpt<{
+ location_gcs: TLocationGcs[];
+}>;
+
+export type IFarmViewLoadData<
+ TLocationGcs extends object,
+> = IBasisOpt<{
+ location_gcs: TLocationGcs;
+}>;
+
+export type ISearchLoadData<
+ TLocationGcs extends object,
+ TNostrProfile extends object,
+ TNostrRelay extends object,
+ TTradeProduct extends object,
+> = IBasisOpt<{
+ location_gcs: TLocationGcs[];
+ nostr_profile: TNostrProfile[];
+ nostr_relay: TNostrRelay[];
+ trade_product: TTradeProduct[];
+}>;
+
+export type ISettingsNostrProfileLoadData<TNostrProfile extends object> = IBasisOpt<{
+ nostr_profile: TNostrProfile;
+}>;
+
+export type ISettingsNostrProfileEditLoadData<TNostrProfile extends object, TNostrProfileFieldKey extends string> = IBasisOpt<{
+ nostr_profile: TNostrProfile;
+ field_key: TNostrProfileFieldKey;
+ field_val?: string;
+}>;
+\ No newline at end of file
diff --git a/apps-lib/src/lib/util/app.ts b/apps-lib/src/lib/util/app.ts
@@ -0,0 +1,143 @@
+import { goto } from "$app/navigation";
+import { nav_prev, type AppLayoutKey, type CallbackPromise, type CallbackRoute, type INavigationRoute, type INavigationRoutePreventRoute, type NavigationParamTuple } from "$lib";
+import type { ColorMode, ThemeKey, ThemeLayer } from "@radroots/theme";
+import { get } from "svelte/store";
+
+export const get_store = get;
+
+export const sleep = async (ms: number): Promise<void> => {
+ await new Promise((resolve) => setTimeout(resolve, ms));
+};
+
+export const theme_set = (theme_key: ThemeKey, color_mode: ColorMode): void => {
+ const data_theme = `${theme_key}_${color_mode}`;
+ document.documentElement.setAttribute("data-theme", data_theme);
+};
+
+export const fmt_id = (id?: string): string => {
+ const pref = location.pathname.slice(1, -1).replaceAll(`-`, `_`).replaceAll(`/`, `-`).replaceAll(`--`, `-`);
+ return `*${pref}${id ? `-${id}` : ``}`
+};
+
+export const fmt_cl = (classes?: string): string => {
+ return classes ? classes : ``;
+};
+
+export const parse_layer = (layer?: number, layer_default?: ThemeLayer): ThemeLayer => {
+ switch (layer) {
+ case 0:
+ case 1:
+ case 2:
+ return layer;
+ default:
+ return layer_default ? layer_default : 0;
+ };
+};
+
+export const encode_qp = (params_list?: NavigationParamTuple[]): string => {
+ const params = (params_list || []).filter(i => i[0] && i[1])
+ if (!params.length) return ``;
+ return params.map(([k, v], index) => `${index === 0 ? `?` : ``}&${k.trim()}=${encodeURI(v.trim())}`).join(``).trim();
+};
+
+export const encode_qp_route = <T extends string>(route: T, params_list?: NavigationParamTuple[]): string => {
+ return `${route}/${encode_qp(params_list)}`.replaceAll(`//`, `/`)
+};
+
+export const exe_iter = async (callback: CallbackPromise, num: number = 1, delay: number = 400): Promise<void> => {
+ try {
+ const iter_fn = (count: number) => {
+ if (count > 0) {
+ callback();
+ if (count > 1) {
+ setTimeout(() => {
+ iter_fn(count - 1);
+ }, delay);
+ }
+ }
+ };
+ iter_fn(num);
+ } catch (e) {
+ console.log(`(error) exe_iter `, e);
+ }
+};
+
+export const value_constrain = (regex_charset: RegExp, value: string): string => {
+ return value
+ .split(``)
+ .filter((char) => regex_charset.test(char))
+ .join(``);
+};
+
+export const value_constrain_textarea = (regex_charset: RegExp, value: string): string => {
+ return value
+ .replace(/\u00A0/g, ` `)
+ .split(/[\n]/)
+ .map(line => line
+ .split(``)
+ .filter((char) => regex_charset.test(char))
+ .join(``)
+ )
+ .join("\n");
+};
+
+export const fmt_textarea_value = (value: string): string => {
+ return value.replace(/ /g, `\u00A0`);
+};
+
+export const route_prev = async (opts: (INavigationRoute | INavigationRoutePreventRoute)): Promise<void> => {
+ let route_to = `/`
+ if (`prevent_route` in opts && opts.prevent_route) return void await opts.prevent_route();
+ else if (`route` in opts) {
+ const $nav_prev = get_store(nav_prev);
+ console.log(JSON.stringify($nav_prev, null, 4), `$nav_prev`)
+ let route_to =
+ typeof opts.route === `string`
+ ? opts.route
+ : encode_qp_route(opts.route[0], opts.route[1]);
+ if ($nav_prev.length) {
+ const nav_prev_li = $nav_prev[$nav_prev.length - 1];
+ console.log(JSON.stringify(nav_prev_li, null, 4), `nav_prev_li`)
+ nav_prev.set([...$nav_prev.slice(0, -1)]);
+ if (nav_prev_li)
+ route_to = encode_qp_route(
+ nav_prev_li.route,
+ nav_prev_li.params,
+ );
+ console.log(JSON.stringify($nav_prev, null, 4), `$nav_prev`)
+ }
+ }
+ console.log(JSON.stringify(route_to, null, 4), `route_to`)
+ await goto(route_to);
+};
+
+export const callback_route = async (callback_route: CallbackRoute): Promise<void> => {
+ if (`route` in callback_route) {
+ if (typeof callback_route.route === `string`) return void await goto(callback_route.route);
+ else return void await goto(
+ encode_qp_route(
+ callback_route.route[0],
+ callback_route.route[1],
+ ),
+ );
+ }
+ return void await callback_route();
+};
+
+export const get_layout = (val: string | false): AppLayoutKey => {
+ switch (val) {
+ case `mobile_base`:
+ case `mobile_y`:
+ return val;
+ default:
+ return `mobile_base`;
+ };
+};
+
+export const debounce_input = (func: Function, delay: number) => {
+ let timer: ReturnType<typeof setTimeout>;
+ return function (this: any, ...args: any) {
+ clearTimeout(timer);
+ timer = setTimeout(() => func.apply(this, args), delay);
+ };
+};
diff --git a/apps-lib/src/lib/util/carousel.ts b/apps-lib/src/lib/util/carousel.ts
@@ -0,0 +1,86 @@
+import {
+ carousel_active,
+ carousel_index,
+ carousel_index_max,
+ carousel_num,
+ exe_iter,
+ get_store
+} from "$lib";
+const CAROUSEL_DELAY_MS = 150;
+
+const get_slide_container = <T extends string>(
+ view: T,
+): Element | undefined => {
+ const el = document.querySelector(
+ `[data-carousel-container="${view}"]`,
+ );
+ return el ? el : undefined;
+};
+
+const get_slide_item = <T extends string>(view: T): Element | undefined => {
+ const el = document.querySelector(`[data-carousel-item="${view}"]`);
+ return el ? el : undefined;
+};
+
+const carousel_dec_handler = async <T extends string>(
+ view: T,
+): Promise<void> => {
+ const $carousel_active = get_store(carousel_active);
+ if ($carousel_active) return;
+ carousel_active.set(true);
+ const slide_item = get_slide_item<T>(view);
+ const slide_container = get_slide_container<T>(view);
+ if (slide_container && slide_item) {
+ const slide_w = slide_item?.clientWidth || 0;
+ slide_container.scrollLeft -= slide_w;
+ const $carousel_index = get_store(carousel_index);
+ carousel_index.set(Math.max($carousel_index - 1, 0));
+ }
+ carousel_active.set(false);
+};
+
+const carousel_inc_handler = async <T extends string>(
+ view: T,
+): Promise<void> => {
+ const $carousel_active = get_store(carousel_active);
+ if ($carousel_active) return;
+ carousel_active.set(true);
+ const slide_item = get_slide_item<T>(view);
+ const slide_container = get_slide_container<T>(view);
+ if (slide_container && slide_item) {
+ const slide_w = slide_item?.clientWidth || 0;
+ slide_container.scrollLeft += slide_w;
+ const $carousel_index = get_store(carousel_index);
+ const $carousel_index_max = get_store(carousel_index_max);
+ carousel_index.set(
+ Math.min($carousel_index + 1, $carousel_index_max),
+ );
+ }
+ carousel_active.set(false);
+};
+
+export const carousel_inc = async <T extends string>(
+ view: T,
+ duration: number = CAROUSEL_DELAY_MS
+): Promise<void> => {
+ const $carousel_num = get_store(carousel_num);
+ carousel_num.set(1);
+ await exe_iter(async () => carousel_inc_handler(view), $carousel_num, duration);
+};
+
+
+export const carousel_dec = async <T extends string>(
+ view: T,
+ duration: number = CAROUSEL_DELAY_MS
+): Promise<void> => {
+ const $carousel_num = get_store(carousel_num);
+ carousel_num.set(1);
+ await exe_iter(async () => carousel_dec_handler(view), $carousel_num, duration);
+};
+
+export const carousel_init = async <T extends string>(view: T, num_max: number): Promise<void> => {
+ await carousel_dec(view);
+ carousel_index.set(0);
+ carousel_index_max.set(num_max);
+ carousel_num.set(1);
+};
diff --git a/apps-lib/src/lib/util/casl.ts b/apps-lib/src/lib/util/casl.ts
@@ -0,0 +1,19 @@
+import { casl_index, casl_index_max } from "$lib/store/component";
+import { get_store } from "./app";
+
+export const casl_inc = async (opts?: 'reflow'): Promise<void> => {
+ const $casl_index = get_store(casl_index);
+ const $casl_index_max = get_store(casl_index_max);
+ casl_index.set(($casl_index + 1) % $casl_index_max);
+};
+
+export const casl_dec = async (opts?: 'reflow'): Promise<void> => {
+ const $casl_index = get_store(casl_index);
+ const $casl_index_max = get_store(casl_index_max);
+ casl_index.set(($casl_index - 1 + $casl_index_max) % $casl_index_max);
+};
+
+export const casl_init = (index_curr: number, index_max: number): void => {
+ casl_index.set(index_curr);
+ casl_index_max.set(index_max);
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/util/component.ts b/apps-lib/src/lib/util/component.ts
@@ -0,0 +1,10 @@
+import { type LabelFieldKind } from "$lib";
+import type { ThemeLayer } from "@radroots/theme";
+
+export const fmt_trellis = (hide_border_t: boolean, hide_border_b: boolean): string => {
+ return `${hide_border_t ? `group-first:border-t-0` : `group-first:border-t-line`} ${hide_border_b ? `group-last:border-b-0` : `group-last:border-b-line`}`;
+};
+
+export const get_label_classes_kind = (layer: ThemeLayer, label_kind: LabelFieldKind | undefined, hide_active: boolean): string => {
+ return `text-layer-${layer}-glyph${label_kind ? `-${label_kind}` : ``} ${hide_active ? `` : `group-active:text-layer-${layer}-glyph${label_kind ? `-${label_kind}_a` : `_a`}`}`
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/util/conf.ts b/apps-lib/src/lib/util/conf.ts
@@ -0,0 +1,51 @@
+import { type AppLayoutKey } from "$lib";
+
+export const ascii = {
+ bullet: '•',
+ dash: `—`,
+ up: `↑`,
+ down: `↓`
+}
+
+type ConfigWindow = {
+ layout: Record<AppLayoutKey, {
+ h: number;
+ }>;
+ debounce: {
+ search: number;
+ }
+};
+
+export const cfg_app: ConfigWindow = {
+ layout: {
+ mobile_base: {
+ h: 600
+ },
+ mobile_y: {
+ h: 750
+ }
+ },
+ debounce: {
+ search: 200
+ },
+};
+
+export const cfg_map = {
+ styles: {
+ base: {
+ light: `https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json`,
+ dark: `https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json`
+ }
+ },
+ popup: {
+ dot: {
+ offset: [0, -10] as [number, number]
+ }
+ },
+ coords: {
+ default: {
+ lat: 0,
+ lng: 0,
+ }
+ }
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/util/document.ts b/apps-lib/src/lib/util/document.ts
@@ -0,0 +1,52 @@
+import { browser } from "$app/environment";
+
+export const el_id = (id: string): HTMLElement | undefined => {
+ const el = document.getElementById(id);
+ return el ? el : undefined;
+};
+
+export const el_toggle = (id: string, toggle_class: string): void => {
+ const el = document.getElementById(id);
+ if (el) el.classList.toggle(toggle_class);
+};
+
+export const els_id_pref = (id_pref: string): Element[] | undefined => {
+ const els = document.querySelectorAll(`[id^="${id_pref}"]`);
+ if (els && els.length) return Array.from(els);
+ return undefined;
+};
+
+export const els_id_pref_index = (id_pref: string, num_index: number, orientation: `greater` | `lesser` | `not` = `greater`, inclusive: boolean = true): Element[] | undefined => {
+ const els = document.querySelectorAll(`[id^="${`${id_pref}-`.replaceAll(`--`, `-`)}"]`);
+ if (els && els.length) return Array.from(els).filter(el => {
+ const match = el.id.match(/(?<=^|\-)[0-9]\d*(?=\-)/)
+ if (match) {
+ const num = parseInt(match[0], 10);
+ switch (orientation) {
+ case `greater`: {
+ if (inclusive) return num >= num_index;
+ else return num > num_index;
+ }
+ case `lesser`: {
+ if (inclusive) return num <= num_index;
+ else return num < num_index;
+ }
+ case `not`: {
+ return num !== num_index;
+ }
+ }
+ }
+ return false;
+ });
+ return undefined;
+};
+
+export const view_effect = <T extends string>(view: T): void => {
+ console.log(`view_effect `, view)
+ if (!browser) return;
+ for (const el of document.querySelectorAll(`[data-view]`)) {
+ if (el.getAttribute(`data-view`) !== view) el.classList.add(`hidden`)
+ else el.classList.remove(`hidden`)
+ }
+};
+
diff --git a/apps-lib/src/lib/util/error.ts b/apps-lib/src/lib/util/error.ts
@@ -0,0 +1,36 @@
+import { page } from "$app/stores";
+import { get_store } from "$lib";
+
+export type IErrorCatchCallback = {
+ name: string;
+ message: string;
+ stack: string;
+ url: string;
+ func: string;
+};
+
+export const catch_err = async (e: unknown, func: string, callback: (opts: IErrorCatchCallback) => Promise<void>): Promise<void> => {
+ const $page = get_store(page) as any;
+ let name = ``;
+ let message = ``;
+ let stack = ``;
+ let url = ``;
+ if (e instanceof Error) {
+ name = e.name;
+ message = e.message;
+ stack = e.stack;
+ url = $page.url.pathname;
+ }
+ await callback({ name, message, stack, url, func });
+};
+
+export const handle_err = async (e: unknown, fcall: string): Promise<void> => {
+ try {
+ return void await catch_err(e, fcall, async (opts) => {
+ console.log(`handle_err e `, e)
+ console.log(JSON.stringify(opts, null, 4), `handle_err opts`)
+ });
+ } catch (e) {
+ console.log(`(handle_err) `, e)
+ }
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/util/geolocation.ts b/apps-lib/src/lib/util/geolocation.ts
@@ -0,0 +1,47 @@
+import { get_store, liblocale, type GeolocationLatitudeFmtOption } from "$lib";
+
+export const geol_lat_fmt = (lat: number, fmt_opt: GeolocationLatitudeFmtOption, precision: number = 5): string => {
+ const $locale = get_store(liblocale)
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat($locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat($locale, options);
+ const fmt_sec = new Intl.NumberFormat($locale, options);
+ if (fmt_opt === 'dms') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = Math.floor((Math.abs(lat) - deg) * 60);
+ const sec = ((Math.abs(lat) - deg - min / 60) * 3600);
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${fmt_sec.format(sec)}" ${lat >= 0 ? 'N' : 'S'}`;
+ } else if (fmt_opt === 'dm') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = (Math.abs(lat) - deg) * 60;
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${lat >= 0 ? 'N' : 'S'}`;
+ } else {
+ return `${lat.toLocaleString($locale, { maximumFractionDigits: precision })}° ${lat >= 0 ? 'N' : 'S'}`;
+ }
+};
+
+export const geol_lng_fmt = (lng: number, fmt_opt: GeolocationLatitudeFmtOption, precision: number = 5): string => {
+ const $locale = get_store(liblocale)
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat($locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat($locale, options);
+ const fmt_sec = new Intl.NumberFormat($locale, options);
+ if (fmt_opt === 'dms') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = Math.floor((Math.abs(lng) - degrees) * 60);
+ const seconds = ((Math.abs(lng) - degrees - minutes / 60) * 3600);
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${fmt_sec.format(seconds)}" ${lng >= 0 ? 'E' : 'W'}`;
+ } else if (fmt_opt === 'dm') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = (Math.abs(lng) - degrees) * 60;
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${lng >= 0 ? 'E' : 'W'}`;
+ } else {
+ return `${lng.toLocaleString($locale, { maximumFractionDigits: precision })}° ${lng >= 0 ? 'E' : 'W'}`;
+ }
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/utils/i18n.ts b/apps-lib/src/lib/util/i18n.ts
diff --git a/apps-lib/src/lib/util/kv.ts b/apps-lib/src/lib/util/kv.ts
@@ -0,0 +1,54 @@
+import { browser } from "$app/environment";
+import { fmt_id } from "$lib";
+
+//@ts-ignore
+const kv_name = import.meta.env.VITE_PUBLIC_KV_NAME;
+if (!kv_name) throw new Error('Error: VITE_PUBLIC_KV_NAME is required');
+
+export let kv_basis: Keyva;
+if (browser) kv_basis = new Keyva({ name: kv_name });
+
+export const kv_init = async (): Promise<void> => {
+ if (!browser) return;
+ const range = Keyva.prefix(`*`);
+ const kv_list = await kv_basis.each({ range }, `keys`);
+ await Promise.all(kv_list.map((i) => kv_basis.delete(i)));
+};
+
+export const kv_init_page = async (): Promise<void> => {
+ if (!browser) return;
+ const kv_pref = fmt_id();
+ const range = Keyva.prefix(kv_pref);
+ const kv_list = await kv_basis.each({ range }, `keys`);
+ await Promise.all(kv_list.map((i) => kv_basis.delete(i)));
+};
+
+export const kv_sync = async (list: [string, string][]): Promise<void> => {
+ if (!browser) return;
+ for (const [key, val] of list) await kv_basis.set(key, val);
+};
+
+export class KvLib<T extends string> {
+ private _kv: Keyva;
+
+ constructor(kv: Keyva) {
+ this._kv = kv;
+ }
+ public init = async () => {
+ await kv_init_page();
+ }
+
+ public save = async (key: T, value: string) => {
+ await this._kv.set(fmt_id(key), value);
+ }
+
+ public read = async (key: T): Promise<string | undefined> => {
+ const result = await this._kv.get<string>(fmt_id(key));
+ if (result) return result;
+ return undefined;
+ }
+
+ public del = async (key: T) => {
+ await this._kv.delete(fmt_id(key));
+ }
+}
+\ No newline at end of file
diff --git a/apps-lib/src/lib/utils/styles.ts b/apps-lib/src/lib/util/styles.ts
diff --git a/apps-lib/src/lib/util/view.ts b/apps-lib/src/lib/util/view.ts
@@ -0,0 +1,7 @@
+import { casl_init, view_effect } from "$lib";
+
+export const handle_view = <T extends string>(view: T, casl_rec: Record<T, { max_index: number }>): T => {
+ casl_init(0, casl_rec[view].max_index);
+ view_effect<T>(view);
+ return view;
+};
+\ No newline at end of file
diff --git a/apps-lib/src/lib/utils/app.ts b/apps-lib/src/lib/utils/app.ts
@@ -1,91 +0,0 @@
-import { page } from "$app/stores";
-import type { CallbackPromise, NavigationParamTuple } from "$lib";
-import type { ColorMode, ThemeKey, ThemeLayer } from "@radroots/theme";
-import { get } from "svelte/store";
-
-export const get_store = get;
-
-export const sleep = async (ms: number): Promise<void> => {
- await new Promise((resolve) => setTimeout(resolve, ms));
-};
-
-export const theme_set = (theme_key: ThemeKey, color_mode: ColorMode): void => {
- const data_theme = `${theme_key}_${color_mode}`;
- document.documentElement.setAttribute("data-theme", data_theme);
-};
-
-export const fmt_id = (id?: string): string => {
- const pref = location.pathname.slice(1, -1).replaceAll(`-`, `_`).replaceAll(`/`, `-`).replaceAll(`--`, `-`);
- return `*${pref}${id ? `-${id}` : ``}`
-};
-
-export const fmt_cl = (classes?: string): string => {
- return classes ? classes : ``;
-};
-
-export const parse_layer = (layer?: number, layer_default?: ThemeLayer): ThemeLayer => {
- switch (layer) {
- case 0:
- case 1:
- case 2:
- return layer;
- default:
- return layer_default ? layer_default : 0;
- };
-};
-
-export const encode_qp = (params_list?: NavigationParamTuple[]): string => {
- const params = (params_list || []).filter(i => i[0] && i[1])
- if (!params.length) return ``;
- return params.map(([k, v], index) => `${index === 0 ? `?` : ``}&${k.trim()}=${encodeURI(v.trim())}`).join(``).trim();
-};
-
-export const encode_qp_route = <T extends string>(route: T, params_list?: NavigationParamTuple[]): string => {
- return `${route}/${encode_qp(params_list)}`.replaceAll(`//`, `/`)
-};
-
-export const catch_err = async (e: unknown, func: string, callback: (opts: {
- name: string;
- message: string;
- stack: string;
- url: string;
- func: string;
-}) => Promise<void>): Promise<void> => {
- const $page = get_store(page) as any;
- let name = ``;
- let message = ``;
- let stack = ``;
- let url = ``;
- if (e instanceof Error) {
- name = e.name;
- message = e.message;
- stack = e.stack;
- url = $page.url.pathname;
- }
- await callback({ name, message, stack, url, func });
-};
-
-export const exe_iter = async (callback: CallbackPromise, num: number = 1, delay: number = 400): Promise<void> => {
- try {
- const iter_fn = (count: number) => {
- if (count > 0) {
- callback();
- if (count > 1) {
- setTimeout(() => {
- iter_fn(count - 1);
- }, delay);
- }
- }
- };
- iter_fn(num);
- } catch (e) {
- console.log(`(error) exe_iter `, e);
- }
-};
-
-export const value_constrain = (regex_charset: RegExp, value: string): string => {
- return value
- .split(``)
- .filter((char) => regex_charset.test(char))
- .join(``);
-};
diff --git a/apps-lib/src/lib/utils/carousel.ts b/apps-lib/src/lib/utils/carousel.ts
@@ -1,87 +0,0 @@
-import {
- carousel_active,
- carousel_index,
- carousel_index_max,
- carousel_num,
- exe_iter,
- get_store
-} from "$lib";
-const CAROUSEL_DELAY_MS = 150;
-
-const get_slide_container = <T extends string>(
- view: T,
-): Element | undefined => {
- const el = document.querySelector(
- `[data-carousel-container="${view}"]`,
- );
- return el ? el : undefined;
-};
-
-const get_slide_item = <T extends string>(view: T): Element | undefined => {
- const el = document.querySelector(`[data-carousel-item="${view}"]`);
- return el ? el : undefined;
-};
-
-const carousel_dec_handler = async <T extends string>(
- view: T,
-): Promise<void> => {
- const $carousel_active = get_store(carousel_active);
- if ($carousel_active) return;
- carousel_active.set(true);
- const slide_item = get_slide_item<T>(view);
- const slide_container = get_slide_container<T>(view);
- if (slide_container && slide_item) {
- const slide_w = slide_item?.clientWidth || 0;
- slide_container.scrollLeft -= slide_w;
- const $carousel_index = get_store(carousel_index);
- carousel_index.set(Math.max($carousel_index - 1, 0));
- }
- carousel_active.set(false);
-};
-
-const carousel_inc_handler = async <T extends string>(
- view: T,
-): Promise<void> => {
- const $carousel_active = get_store(carousel_active);
- if ($carousel_active) return;
- carousel_active.set(true);
- const slide_item = get_slide_item<T>(view);
- const slide_container = get_slide_container<T>(view);
- if (slide_container && slide_item) {
- const slide_w = slide_item?.clientWidth || 0;
- slide_container.scrollLeft += slide_w;
- const $carousel_index = get_store(carousel_index);
- const $carousel_index_max = get_store(carousel_index_max);
- carousel_index.set(
- Math.min($carousel_index + 1, $carousel_index_max),
- );
- }
- carousel_active.set(false);
-};
-
-export const carousel_inc = async <T extends string>(
- view: T,
- duration: number = CAROUSEL_DELAY_MS
-): Promise<void> => {
- const $carousel_num = get_store(carousel_num);
- carousel_num.set(1);
- await exe_iter(async () => carousel_inc_handler(view), $carousel_num, duration);
-};
-
-
-export const carousel_dec = async <T extends string>(
- view: T,
- duration: number = CAROUSEL_DELAY_MS
-): Promise<void> => {
- const $carousel_num = get_store(carousel_num);
- carousel_num.set(1);
- await exe_iter(async () => carousel_dec_handler(view), $carousel_num, duration);
-};
-
-export const carousel_init = async <T extends string>(view: T, num_max: number,
-): Promise<void> => {
- await carousel_dec(view);
- carousel_index.set(0);
- carousel_index_max.set(num_max);
- carousel_num.set(1);
-};
diff --git a/apps-lib/src/lib/utils/document.ts b/apps-lib/src/lib/utils/document.ts
@@ -1,49 +0,0 @@
-export const el_id = (id: string): HTMLElement | undefined => {
- const el = document.getElementById(id);
- return el ? el : undefined;
-};
-
-export const el_toggle = (id: string, toggle_class: string): void => {
- const el = document.getElementById(id);
- if (el) el.classList.toggle(toggle_class);
-};
-
-export const els_id_pref = (id_pref: string): Element[] | undefined => {
- const els = document.querySelectorAll(`[id^="${id_pref}"]`);
- if (els && els.length) return Array.from(els);
- return undefined;
-};
-
-export const els_id_pref_index = (id_pref: string, num_index: number, orientation: `greater` | `lesser` | `not` = `greater`, inclusive: boolean = true): Element[] | undefined => {
- const els = document.querySelectorAll(`[id^="${`${id_pref}-`.replaceAll(`--`, `-`)}"]`);
- if (els && els.length) return Array.from(els).filter(el => {
- const match = el.id.match(/(?<=^|\-)[0-9]\d*(?=\-)/)
- if (match) {
- const num = parseInt(match[0], 10);
- switch (orientation) {
- case `greater`: {
- if (inclusive) return num >= num_index;
- else return num > num_index;
- }
- case `lesser`: {
- if (inclusive) return num <= num_index;
- else return num < num_index;
- }
- case `not`: {
- return num !== num_index;
- }
- }
- }
- return false;
- });
- return undefined;
-};
-
-export const view_effect = <T extends string>(view: T): void => {
- for (const el of document.querySelectorAll(`[data-view]`))
- el.classList.toggle(
- `hidden`,
- el.getAttribute(`data-view`) !== view,
- );
-};
-
diff --git a/apps-lib/src/lib/utils/kv.ts b/apps-lib/src/lib/utils/kv.ts
@@ -1,19 +0,0 @@
-import { fmt_id, kv } from "$lib";
-
-export const kv_init = async (): Promise<void> => {
- const range = Keyva.prefix(`*`);
- const kv_list = await kv.each({ range }, `keys`);
- await Promise.all(kv_list.map((i) => kv.delete(i)));
-};
-
-export const kv_init_page = async (): Promise<void> => {
- const kv_pref = fmt_id();
- const range = Keyva.prefix(kv_pref);
- const kv_list = await kv.each({ range }, `keys`);
- await Promise.all(kv_list.map((i) => kv.delete(i)));
-};
-
-export const kv_sync = async (list: [string, string][]): Promise<void> => {
- for (const [key, val] of list) await kv.set(key, val);
-};
-
diff --git a/apps-lib/src/lib/utils/routes.ts b/apps-lib/src/lib/utils/routes.ts
@@ -1,13 +0,0 @@
-export type NavigationRoute =
- | "/"
- | "/cfg";
-
-export function parse_route(route: string): NavigationRoute {
- switch (route) {
- case "/":
- case "/cfg":
- return route;
- default:
- return "/";
- };
-};
-\ No newline at end of file
diff --git a/apps-lib/src/lib/view/farm-land-add.svelte b/apps-lib/src/lib/view/farm-land-add.svelte
@@ -0,0 +1,422 @@
+<script lang="ts">
+ import {
+ Carousel,
+ casl_dec,
+ casl_inc,
+ casl_index,
+ FloatTabs,
+ fmt_id,
+ geol_lat_fmt,
+ geol_lng_fmt,
+ GlyphButtonSimple,
+ GlyphTitleSelectLabel,
+ handle_err,
+ handle_view,
+ Input,
+ kv_init_page,
+ LayoutView,
+ lls,
+ MapPointSelect,
+ PageToolbar,
+ SelectMenu,
+ type CallbackPromiseReturn,
+ type GeocoderReverseResult,
+ type GeolocationPoint,
+ type ILcGeocodeCallback,
+ type ViewBasis,
+ } from "$lib";
+ import CarouselItem from "$lib/component/carousel/carousel-item.svelte";
+ import View from "$lib/component/lib/view.svelte";
+ import { onDestroy, onMount } from "svelte";
+
+ const casl_rec: Record<View, { max_index: number }> = {
+ map_init: {
+ max_index: 2,
+ },
+ loc_form: {
+ max_index: 1,
+ },
+ };
+
+ type View = `map_init` | `loc_form`;
+ let view: View = `map_init`;
+
+ export let bv_map_geol_p: GeolocationPoint | undefined = undefined;
+ export let bv_map_geoc_r: GeocoderReverseResult | undefined = undefined;
+
+ let lgcs_label = ``;
+ let lgcs_area = ``;
+ let lgcs_area_unit = `ha`;
+ let lgcs_elevation = ``;
+ let lgcs_elevation_unit = `m`;
+ let lgcs_climate = ``;
+
+ export let basis: ViewBasis<{
+ lc_geocode: ILcGeocodeCallback;
+ lc_submit: CallbackPromiseReturn<{ id: string } | void>;
+ }>;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ handle_view(view, casl_rec);
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+
+ const handle_dec = async (): Promise<void> => {
+ await casl_dec();
+ };
+
+ const handle_inc = async (): Promise<void> => {
+ await casl_inc();
+ };
+
+ const submit = async (): Promise<void> => {
+ try {
+ const result = await basis.lc_submit();
+ if (result) view = handle_view(`loc_form`, casl_rec);
+ } catch (e) {
+ await handle_err(e, `submit`);
+ }
+ };
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`icu.add_*`, { value: `${$lls(`common.farm_land`)}` })}`,
+ callback_route: {
+ route: `/farm/land`,
+ },
+ },
+ }}
+ >
+ <div
+ slot="header-option"
+ class={`flex flex-row gap-4 justify-start items-center`}
+ >
+ <div
+ class={`${$casl_index > 0 ? `fade-in` : `hidden`} flex flex-row justify-start items-center`}
+ >
+ <GlyphButtonSimple
+ basis={{
+ classes: `gap-1`,
+ kind: `neutral`,
+ label: `${$lls(`common.map`)}`,
+ glyph: `arrow-left`,
+ callback: handle_dec,
+ }}
+ />
+ </div>
+ <GlyphButtonSimple
+ basis={{
+ label: `${$lls(`common.add`)}`,
+ callback: async () => {
+ if (view === `map_init` && $casl_index === 0) {
+ if (!(bv_map_geol_p && bv_map_geoc_r))
+ return void (await basis.lc_gui_alert(
+ `Please select a location.`, //@todo
+ ));
+ return void (await handle_inc());
+ } else if (view === `map_init` && $casl_index === 1) {
+ if (bv_map_geol_p && bv_map_geoc_r)
+ return void (await submit());
+ }
+ },
+ }}
+ />
+ </div>
+ </PageToolbar>
+ <View key={`map_init`}>
+ <Carousel>
+ <CarouselItem>
+ <div
+ class={`flex flex-col w-full px-4 justify-center items-center`}
+ >
+ <div
+ class={`relative flex flex-col w-full justify-start items-center rounded-[20px] overflow-hidden border-x-[3px] border-y-[5px] border-white/30 shadow-sm`}
+ >
+ <div
+ class={`flex flex-row h-[32rem] w-full justify-center items-center bg-base-100/60 overflow-hidden rounded-2xl`}
+ >
+ <MapPointSelect
+ bind:map_point={bv_map_geol_p}
+ bind:map_point_geoc_r={bv_map_geoc_r}
+ basis={{
+ lc_geocode: basis.lc_geocode,
+ }}
+ />
+ </div>
+ </div>
+ </div>
+ </CarouselItem>
+ <CarouselItem>
+ <div
+ class={`flex flex-col w-full pt-4 px-4 gap-4 justify-center items-center`}
+ >
+ <div
+ class={`flex flex-col w-full px-2 gap-4 justify-start items-center`}
+ >
+ {#if bv_map_geoc_r && bv_map_geol_p}
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-row w-full justify-start items-center`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.location`)}`}
+ </p>
+ </div>
+ <div
+ class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <p
+ class={`font-sans font-[400] text-[1.1rem] text-layer-0-glyph`}
+ >
+ {`${bv_map_geoc_r.name}, ${bv_map_geoc_r.admin1_id}, ${bv_map_geoc_r.country_name}`}
+ </p>
+ </div>
+ </div>
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.coordinates`)}`}
+ </p>
+ <div
+ class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <p
+ class={`font-sans font-[400] text-[1.1rem] text-layer-0-glyph`}
+ >
+ {`${geol_lat_fmt(
+ bv_map_geol_p.lat,
+ `d`,
+ 4,
+ )}, ${geol_lng_fmt(
+ bv_map_geol_p.lng,
+ `d`,
+ 4,
+ )}`}
+ </p>
+ </div>
+ </div>
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-row w-full justify-start items-center`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.farm`)}/${`${$lls(`common.estate`)}`}`}
+ </p>
+ </div>
+ <div
+ class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <Input
+ bind:value={lgcs_label}
+ basis={{
+ id: fmt_id(`label`),
+ sync: true,
+ layer: 0,
+ classes: `h-10 placeholder:text-[1.1rem]`,
+ placeholder: `${$lls(`common.name_of_farm_or_estate`)}`,
+ /*field: {
+ charset:
+ location_gcs_form_fields
+ .label.charset,
+ validate:
+ location_gcs_form_fields
+ .label.validation,
+ validate_keypress: true,
+ },*/
+ }}
+ />
+ </div>
+ </div>
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-row w-full gap-1 justify-start items-center`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.area`)}`}
+ </p>
+ <SelectMenu
+ bind:value={lgcs_area_unit}
+ basis={{
+ layer: 0,
+ options: [
+ {
+ entries: [
+ {
+ label: `${$lls(`measurement.area.ac`)}`,
+ value: `ac`,
+ },
+ {
+ label: `${$lls(`measurement.area.ha`)}`,
+ value: `ha`,
+ },
+ {
+ label: `${$lls(`measurement.area.m2`)}`,
+ value: `m2`,
+ },
+ ],
+ },
+ ],
+ }}
+ >
+ <svelte:fragment slot="element">
+ <GlyphTitleSelectLabel
+ basis={{
+ label: `${$lls(`measurement.area.${lgcs_area_unit}_ab`)}`,
+ }}
+ />
+ </svelte:fragment>
+ </SelectMenu>
+ </div>
+ <div
+ class={`relative flex flex-row h-12 w-full justify-between items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <Input
+ bind:value={lgcs_area}
+ basis={{
+ id: fmt_id(`area`),
+ sync: true,
+ layer: 0,
+ classes: `h-10 placeholder:text-[1.1rem]`,
+ placeholder: `${$lls(`common.land_area`)}`,
+ /*field: {
+ charset: regex.num,
+ validate: regex.num,
+ validate_keypress: true,
+ },*/
+ }}
+ />
+ </div>
+ </div>
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-row w-full gap-1 justify-start items-center`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.elevation`)}`}
+ </p>
+ <SelectMenu
+ bind:value={lgcs_elevation_unit}
+ basis={{
+ layer: 0,
+ options: [
+ {
+ entries: [
+ {
+ label: `${$lls(`measurement.length.m`)}`,
+ value: `m`,
+ },
+ {
+ label: `${$lls(`measurement.length.ft`)}`,
+ value: `ft`,
+ },
+ ],
+ },
+ ],
+ }}
+ >
+ <svelte:fragment slot="element">
+ <GlyphTitleSelectLabel
+ basis={{
+ label: `${$lls(`measurement.length.${lgcs_elevation_unit}_ab`)}`,
+ }}
+ />
+ </svelte:fragment>
+ </SelectMenu>
+ </div>
+ <div
+ class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <Input
+ bind:value={lgcs_elevation}
+ basis={{
+ id: fmt_id(`elevation`),
+ sync: true,
+ layer: 0,
+ classes: `h-10 placeholder:text-[1.1rem]`,
+ placeholder: `${$lls(`common.elevation`)}`,
+ /*field: {
+ charset: regex.num,
+ validate: regex.num,
+ validate_keypress: true,
+ },*/
+ }}
+ />
+ </div>
+ </div>
+ <div
+ class={`flex flex-col w-full gap-1 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-row w-full justify-start items-center`}
+ >
+ <p
+ class={`font-sansd text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {`${$lls(`common.climate`)}`}
+ </p>
+ </div>
+ <div
+ class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`}
+ >
+ <Input
+ bind:value={lgcs_climate}
+ basis={{
+ id: fmt_id(`climate`),
+ sync: true,
+ layer: 0,
+ classes: `h-10 placeholder:text-[1.1rem]`,
+ placeholder: `${$lls(`common.climate`)}`,
+ /*field: {
+ charset: regex.description,
+ validate: regex.description_ch,
+ validate_keypress: true,
+ },*/
+ }}
+ />
+ </div>
+ </div>
+ {/if}
+ </div>
+ </div>
+ </CarouselItem>
+ </Carousel>
+ </View>
+ <View key={`loc_form`}>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ view = handle_view(`map_init`, casl_rec);
+ }}
+ >
+ <p class={`font-sans font-[400] text-layer-0-glyph`}>{`two`}</p>
+ </button>
+ </View>
+</LayoutView>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/farm-land-view.svelte b/apps-lib/src/lib/view/farm-land-view.svelte
@@ -0,0 +1,83 @@
+<script lang="ts" generics="TLocationGcs extends ModelLocationGcs">
+ import {
+ app_notify,
+ GlyphButtonSimple,
+ kv_init_page,
+ LayoutView,
+ lls,
+ MapPointDisplay,
+ PageToolbar,
+ qp_id,
+ type IFarmViewLoadData,
+ type ModelLocationGcs,
+ type ViewBasisLoadData,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+ type LoadData = IFarmViewLoadData<TLocationGcs>;
+
+ export let basis: ViewBasisLoadData<{}, LoadData>;
+ let load_data: LoadData = undefined;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ if (!$qp_id)
+ return void app_notify.set(
+ `${$lls(`error.page.load.query_param`)}`,
+ );
+ load_data = await basis.lc_load_data();
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`icu.add_*`, { value: `${$lls(`common.farm_land`)}` })}`,
+ callback_route: {
+ route: `/farm/land`,
+ },
+ },
+ }}
+ >
+ <div
+ slot="header-option"
+ class={`flex flex-row gap-4 justify-start items-center`}
+ >
+ <GlyphButtonSimple
+ basis={{
+ label: `${$lls(`common.edit`)}`,
+ callback: async () => {
+ alert(`@todo!`);
+ },
+ }}
+ />
+ </div>
+ </PageToolbar>
+ {#if load_data?.location_gcs}
+ <div class={`flex flex-col w-full px-4 justify-center items-center`}>
+ <div
+ class={`flex flex-row h-[20rem] w-full justify-center items-center bg-layer-2-surface round-44 overflow-hidden`}
+ >
+ <MapPointDisplay
+ basis={{
+ zoom: 12,
+ point: {
+ lat: load_data.location_gcs.lat,
+ lng: load_data.location_gcs.lng,
+ },
+ }}
+ />
+ </div>
+ <div class={`flex flex-col w-full justify-center items-center`}>
+ <div class={`flex flex-row w-full justify-start items-center`}>
+ <p class={`font-sans font-[400] text-layer-0-glyph`}>hi</p>
+ </div>
+ </div>
+ </div>
+ {/if}
+</LayoutView>
diff --git a/apps-lib/src/lib/view/farm-land.svelte b/apps-lib/src/lib/view/farm-land.svelte
@@ -0,0 +1,142 @@
+<script lang="ts" generics="TLocationGcs extends ModelLocationGcs">
+ import {
+ Fade,
+ FloatTabs,
+ geol_lat_fmt,
+ geol_lng_fmt,
+ GlyphButtonSimple,
+ kv_init_page,
+ LayoutView,
+ lls,
+ MapPointDisplay,
+ PageToolbar,
+ type CallbackPromise,
+ type IFarmLoadData,
+ type ModelLocationGcs,
+ type ViewBasisLoadData,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+ type LoadData = IFarmLoadData<TLocationGcs>;
+
+ export let basis: ViewBasisLoadData<
+ {
+ lc_handle_farm_land_add: CallbackPromise;
+ lc_handle_farm_land_view: CallbackPromise;
+ },
+ LoadData
+ >;
+ let load_data: LoadData = undefined;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ load_data = await basis.lc_load_data();
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: { label: `${$lls(`common.farm_land`)}` },
+ }}
+ >
+ <div
+ slot="header-option"
+ class={`flex flex-row justify-start items-center`}
+ >
+ <Fade>
+ <GlyphButtonSimple
+ basis={{
+ label: `${$lls(`common.add`)}`,
+ callback: basis.lc_handle_farm_land_add,
+ }}
+ ></GlyphButtonSimple>
+ </Fade>
+ </div>
+ </PageToolbar>
+ <div
+ class={`flex flex-col w-full pt-2 px-4 gap-5 justify-start items-center`}
+ >
+ {#if load_data && load_data.location_gcs.length}
+ {#each load_data.location_gcs.filter((i) => i.kind === `farm_land`) as li}
+ <button
+ class={`group flex flex-row h-[5rem] w-full px-8 gap-8 justify-start items-center bg-layer-1-surface layer-1-active-surface round-36 el-re`}
+ on:click={basis.lc_handle_farm_land_view}
+ >
+ <div
+ class={`flex flex-col h-[4rem] w-[4rem] justify-start items-center bg-layer-2-surface round-24`}
+ >
+ <MapPointDisplay
+ basis={{
+ point: {
+ lat: li.lat,
+ lng: li.lng,
+ },
+ }}
+ />
+ </div>
+ <div
+ class={`flex flex-col flex-grow h-[3.25rem] justify-between items-start`}
+ >
+ <div
+ class={`flex flex-row w-full justify-start items-center`}
+ >
+ <p
+ class={`font-sans font-[500] text-layer-0-glyph`}
+ >
+ {`${
+ li.label ||
+ `${geol_lat_fmt(
+ li.lat,
+ `d`,
+ 4,
+ )}, ${geol_lng_fmt(li.lng, `d`, 4)}`
+ }`}
+ </p>
+ </div>
+ <div
+ class={`flex flex-row w-full gap-2 justify-start items-center`}
+ >
+ {#if li.kind === `farm_land`}
+ <div
+ class={`flex flex-row h-5 px-2 justify-center items-center bg-layer-2-surface rounded-md`}
+ >
+ <p
+ class={`font-sans font-[700] text-[0.8rem] text-white`}
+ >
+ {`${$lls(`common.farm`)}`}
+ </p>
+ </div>
+ {/if}
+ <p
+ class={`font-sansd font-[500] text-layer-0-glyph`}
+ >
+ {`${li.gc_name}, ${li.gc_admin1_id}, ${li.gc_country_id}`}
+ </p>
+ </div>
+ </div>
+ </button>
+ {/each}
+ {:else}
+ <div class={`flex flex-col w-full justify-center items-center`}>
+ <div class={`flex flex-row w-full justify-center items-center`}>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={basis.lc_handle_farm_land_add}
+ >
+ <p
+ class={`font-sans font-[400] text-lg text-layer-0-glyph`}
+ >
+ {`- Add land -`}
+ </p>
+ </button>
+ </div>
+ </div>
+ {/if}
+ </div>
+</LayoutView>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/home.svelte b/apps-lib/src/lib/view/home.svelte
@@ -0,0 +1,50 @@
+<script lang="ts">
+ import {
+ FloatTabs,
+ kv_init_page,
+ LayoutView,
+ lls,
+ PageToolbar,
+ type CallbackPromise,
+ type ViewBasis,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: ViewBasis<{
+ lc_handle_farm: CallbackPromise;
+ }>;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`common.general`)}`,
+ },
+ }}
+ />
+ <div class={`flex flex-col w-full px-4 gap-4 justify-center items-center`}>
+ <div class={`flex flex-col w-full gap-5 justify-center items-center`}>
+ <button
+ class={`group flex flex-row h-[3.5rem] w-full justify-center items-center rounded-touch bg-layer-1-surface layer-1-active-surface layer-1-active-ring`}
+ on:click={basis.lc_handle_farm}
+ >
+ <p
+ class={`font-sans font-[700] text-xl text-layer-0-glyph capitalize tracking-wider opacity-active`}
+ >
+ {`${$lls(`common.farm_land`)}`}
+ </p>
+ </button>
+ </div>
+ </div>
+</LayoutView>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/notifications.svelte b/apps-lib/src/lib/view/notifications.svelte
@@ -0,0 +1,31 @@
+<script lang="ts">
+ import { FloatTabs, LayoutView, lls, PageToolbar } from "$lib";
+
+ let notifications: any[] = [];
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`common.notifications`)} (${notifications.length})`,
+ },
+ }}
+ />
+ <div class={`flex flex-col w-full px-4 gap-6 justify-center items-center`}>
+ {#if notifications.length}
+ {#each notifications as li}
+ <div class={`flex flex-row w-full justify-center items-center`}>
+ {li}
+ </div>
+ {/each}
+ {:else}
+ <div class={`flex flex-row w-full justify-center items-center`}>
+ <p class={`font-sans font-[500] text-layer-0-glyph capitalize`}>
+ {`${$lls(`icu.no_*`, { value: `${$lls(`common.notifications`)}` })}`}
+ </p>
+ </div>
+ {/if}
+ </div>
+</LayoutView>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/search.svelte b/apps-lib/src/lib/view/search.svelte
@@ -0,0 +1,110 @@
+<script
+ lang="ts"
+ generics="TLocationGcs extends ModelLocationGcs, TNostrProfile extends ModelNostrProfile, TNostrRelay extends ModelNostrRelay, TTradeProduct extends ModelTradeProduct"
+>
+ import {
+ cfg_app,
+ debounce_input,
+ FloatTabs,
+ fmt_id,
+ Glyph,
+ Input,
+ kv_init_page,
+ LayoutView,
+ lls,
+ PageToolbar,
+ SearchResultDisplay,
+ SearchService,
+ type ISearchLoadData,
+ type ISearchResultDisplayCallbacks,
+ type ModelLocationGcs,
+ type ModelNostrProfile,
+ type ModelNostrRelay,
+ type ModelTradeProduct,
+ type SearchServiceResult,
+ type ViewBasisLoadData,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ type LoadData = ISearchLoadData<
+ TLocationGcs,
+ TNostrProfile,
+ TNostrRelay,
+ TTradeProduct
+ >;
+
+ export let basis: ViewBasisLoadData<
+ ISearchResultDisplayCallbacks & {},
+ LoadData
+ >;
+ let load_data: LoadData = undefined;
+
+ let client_search: SearchService | undefined = undefined;
+ let search_val = ``;
+ let search_results: SearchServiceResult[] = [];
+
+ onMount(async () => {
+ search_val = ``;
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ load_data = await basis.lc_load_data();
+ if (load_data) client_search = new SearchService(load_data);
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+
+ const handle_search_input = debounce_input((val: string) => {
+ if (client_search) search_results = client_search.search(val);
+ }, cfg_app.debounce.search);
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: { label: `${$lls(`common.search`)}` },
+ callback: basis.lc_handle_back,
+ }}
+ />
+ <div class={`flex flex-col w-full px-4 gap-6 justify-center items-center`}>
+ <div
+ class={`relative flex flex-row w-full justify-center items-center bg-layer-1-surface rounded-2xl`}
+ >
+ <Glyph
+ basis={{
+ classes: `absolute left-4 text-layer-0-glyph-shade`,
+ dim: `sm`,
+ weight: `bold`,
+ key: `magnifying-glass`,
+ }}
+ ></Glyph>
+ <Input
+ bind:value={search_val}
+ basis={{
+ id: fmt_id(`search`),
+ sync: true,
+ classes: `pl-12 text-layer-0-glyph`,
+ placeholder: `Enter search query`,
+ callback: async ({ value }) => handle_search_input(value),
+ }}
+ ></Input>
+ </div>
+ <div class={`flex flex-col w-full gap-4 justify-center items-center`}>
+ {#each search_results as li (li.id)}
+ <SearchResultDisplay
+ basis={{
+ result: li,
+ lc_handle_result_location_gcs:
+ basis.lc_handle_result_location_gcs,
+ lc_handle_result_nostr_profile:
+ basis.lc_handle_result_nostr_profile,
+ lc_handle_result_nostr_relay:
+ basis.lc_handle_result_nostr_relay,
+ }}
+ />
+ {/each}
+ </div>
+ </div>
+</LayoutView>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/settings-nostr.svelte b/apps-lib/src/lib/view/settings-nostr.svelte
@@ -0,0 +1,134 @@
+<script lang="ts">
+ import {
+ LayoutTrellis,
+ LayoutView,
+ PageToolbar,
+ Trellis,
+ ascii,
+ handle_err,
+ lls,
+ type ISelectOption,
+ } from "$lib";
+ import { onMount } from "svelte";
+
+ const page_param: {
+ select_options: ISelectOption<string>[];
+ } = {
+ select_options: [
+ {
+ value: ascii.bullet,
+ label: `${$lls(`icu.choose_*`, { value: `${$lls(`common.photo_hosting`)}`.toLowerCase() })}`,
+ disabled: true,
+ },
+ {
+ value: `^radroots`,
+ label: `https://radroots.market`,
+ },
+ {
+ value: `*add`,
+ label: `${$lls(`icu.add_*`, { value: `${$lls(`common.upload_url`)}`.toLowerCase() })}`,
+ },
+ {
+ value: `*disable`,
+ label: `${$lls(`common.disable_uploads`)}`,
+ },
+ ],
+ };
+
+ let nostr_photohosting_sel_val = ``;
+ let nostr_photohosting_sel_label = ``;
+
+ onMount(async () => {
+ try {
+ await init_page();
+ } catch (e) {
+ } finally {
+ }
+ });
+
+ const init_page = async (): Promise<void> => {
+ try {
+ nostr_photohosting_sel_val = `^radroots`; //@todo
+ } catch (e) {
+ await handle_err(e, `init_page`);
+ }
+ };
+
+ $: nostr_photohosting_sel_label = nostr_photohosting_sel_val
+ ? page_param.select_options.filter(
+ (i) => i.value === nostr_photohosting_sel_val,
+ )?.[0].label
+ : ``;
+
+ const handle_select_option = async (
+ option_value: string,
+ ): Promise<void> => {
+ try {
+ if (!option_value.startsWith(`*`)) {
+ nostr_photohosting_sel_val = option_value;
+ return;
+ }
+ nostr_photohosting_sel_val = ``;
+ alert(`@todo!`);
+ nostr_photohosting_sel_val = `^radroots`;
+ } catch (e) {
+ await handle_err(e, `handle_select_option`);
+ }
+ };
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`common.nostr`)}`,
+ },
+ }}
+ />
+ <LayoutTrellis>
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ list: [
+ {
+ hide_active: true,
+ select: {
+ label: {
+ left: [
+ {
+ value: `Photo Hosting`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ display: {
+ loading: !nostr_photohosting_sel_val,
+ label: {
+ value: nostr_photohosting_sel_label,
+ },
+ },
+ el: {
+ value: nostr_photohosting_sel_val,
+ options: [
+ {
+ entries: page_param.select_options,
+ },
+ ],
+ callback: async ({ value }) => {
+ await handle_select_option(value);
+ },
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ </LayoutTrellis>
+</LayoutView>
diff --git a/apps-lib/src/lib/view/settings-profile-edit.svelte b/apps-lib/src/lib/view/settings-profile-edit.svelte
@@ -0,0 +1,146 @@
+<script
+ lang="ts"
+ generics="TModelNostrProfile extends ModelNostrProfile, TModelNostrProfileFieldsKey extends ModelNostrProfileFieldsKey"
+>
+ import {
+ app_notify,
+ fmt_id,
+ Input,
+ kv_init_page,
+ LayoutView,
+ lls,
+ Nav,
+ qp_nostr_pk,
+ qp_rkey,
+ TextArea,
+ type CallbackPromise,
+ type ISettingsNostrProfileEditLoadData,
+ type ModelNostrProfile,
+ type ModelNostrProfileFieldsKey,
+ type ViewBasisLoadData,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ type LoadData = ISettingsNostrProfileEditLoadData<
+ TModelNostrProfile,
+ TModelNostrProfileFieldsKey
+ >;
+
+ export let basis: ViewBasisLoadData<
+ {
+ lc_submit: CallbackPromise;
+ },
+ LoadData
+ >;
+ let load_data: LoadData = undefined;
+
+ let val_input_init = ``;
+ let val_input = ``;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ if (!$qp_rkey || !$qp_nostr_pk)
+ return void app_notify.set(
+ `${$lls(`error.page.load.query_param`)}`,
+ );
+ load_data = await basis.lc_load_data();
+ if (load_data?.field_val) {
+ val_input = load_data.field_val;
+ val_input_init = load_data.field_val;
+ }
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+
+ $: translated_field_key = load_data?.field_key
+ ? `${$lls(`models.nostr_profile.fields.${load_data.field_key}.label`)}`
+ : ``;
+ $: val_input_delta = val_input_init !== val_input;
+</script>
+
+<LayoutView>
+ {#if load_data}
+ <div
+ class={`flex flex-col w-full pt-4 px-4 gap-1 justify-start items-center fade-in`}
+ >
+ <div class={`flex flex-row w-full pl-2 justify-start items-center`}>
+ <p
+ class={`font-sans text-trellis_ti text-layer-0-glyph-label uppercase`}
+ >
+ {translated_field_key.replace(/profile /i, ``)}
+ </p>
+ </div>
+ {#if load_data.field_key === `about`}
+ <TextArea
+ bind:value={val_input}
+ basis={{
+ id: fmt_id(load_data.field_key),
+ classes: `min-h-[12rem] pl-4`,
+ sync: true,
+ layer: 1,
+ placeholder: `${$lls(`icu.enter_*`, { value: `${translated_field_key}`.toLowerCase() })}`,
+ /*field: {
+ charset:
+ nostr_profile_form_fields[load_data.field_key].charset,
+ validate:
+ nostr_profile_form_fields[load_data.field_key]
+ .validation,
+ validate_keypress: true,
+ },*/
+ callback_keydown: async ({ key_s }) => {
+ if (key_s && val_input_delta)
+ await basis.lc_submit();
+ },
+ }}
+ ></TextArea>
+ {:else}
+ <Input
+ bind:value={val_input}
+ basis={{
+ id: fmt_id(load_data.field_key),
+ classes: `rounded-touch pl-4`,
+ sync: true,
+ layer: 1,
+ placeholder: `${$lls(`icu.enter_*`, { value: `${translated_field_key}`.toLowerCase() })}`,
+ /*field: {
+ charset:
+ nostr_profile_form_fields[load_data.field_key].charset,
+ validate:
+ nostr_profile_form_fields[load_data.field_key]
+ .validation,
+ validate_keypress: true,
+ },*/
+ callback_keydown: async ({ key_s }) => {
+ if (key_s && val_input_delta)
+ await basis.lc_submit();
+ },
+ }}
+ ></Input>
+ {/if}
+ </div>
+ {/if}
+</LayoutView>
+<Nav
+ basis={{
+ prev: {
+ label: `${$lls(`common.profile`)}`,
+ route: `/settings/profile`,
+ prevent_route: val_input_delta
+ ? {
+ callback: async () => {
+ if (val_input_delta) await basis.lc_submit();
+ },
+ }
+ : undefined,
+ },
+ title: {
+ label: {
+ classes: `capitalize`,
+ value: `${$lls(`icu.edit_*`, { value: `${$lls(`common.profile`)}` })}`,
+ },
+ },
+ }}
+/>
diff --git a/apps-lib/src/lib/view/settings-profile.svelte b/apps-lib/src/lib/view/settings-profile.svelte
@@ -0,0 +1,244 @@
+<script lang="ts" generics="TModelNostrProfile extends ModelNostrProfile">
+ import {
+ ascii,
+ FloatPageButton,
+ FloatTabs,
+ Glyph,
+ ImageBlob,
+ ImagePath,
+ ImageUploadAddPhoto,
+ kv_init_page,
+ lls,
+ type CallbackPromise,
+ type CallbackPromiseFull,
+ type CallbackPromiseReturn,
+ type ISettingsNostrProfileLoadData,
+ type ModelNostrProfile,
+ type ViewBasisLoadData,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let bv_photo_path_opt = ``;
+
+ type LoadData = ISettingsNostrProfileLoadData<TModelNostrProfile>;
+
+ export let basis: ViewBasisLoadData<
+ {
+ lc_handle_photo_add: CallbackPromiseReturn<string | undefined>;
+ lc_handle_photo_options: CallbackPromise;
+ lc_fs_read_bin: CallbackPromiseFull<string, Uint8Array | undefined>;
+ lc_handle_edit_profile_name: CallbackPromise;
+ lc_handle_edit_profile_name_confirm: CallbackPromiseReturn<boolean>;
+ lc_handle_edit_profile_display_name: CallbackPromise;
+ lc_handle_edit_profile_about: CallbackPromise;
+ },
+ LoadData
+ >;
+ let load_data: LoadData = undefined;
+
+ let loading_photo_upload = false;
+ type ViewDisplay = `photos` | `following` | `followers`;
+ let view_display: ViewDisplay = `photos`;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ load_data = await basis.lc_load_data();
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+
+ $: photo_overlay_visible =
+ load_data?.nostr_profile?.picture || bv_photo_path_opt;
+ $: classes_photo_overlay_glyph = photo_overlay_visible
+ ? `text-white`
+ : `text-layer-0-glyph`;
+ $: classes_photo_overlay_glyph_opt = photo_overlay_visible
+ ? `text-gray-300`
+ : `text-layer-0-glyph`;
+ $: classes_photo_overlay_glyph_opt_selected = photo_overlay_visible
+ ? `text-white`
+ : `text-layer-1-glyph`;
+</script>
+
+<div
+ class={`relative flex flex-col min-h-[525px] h-[525px] w-full justify-center items-center bg-layer-2-surface fade-in`}
+>
+ <FloatPageButton
+ basis={{
+ posx: `left`,
+ glyph: `arrow-left`,
+ loading: loading_photo_upload,
+ callback: basis.lc_handle_back,
+ }}
+ />
+ <FloatPageButton
+ basis={{
+ posx: `right`,
+ glyph: `images-square`,
+ loading: loading_photo_upload,
+ callback: basis.lc_handle_photo_options,
+ }}
+ />
+ {#if load_data?.nostr_profile?.picture}
+ <ImagePath
+ basis={{
+ path: load_data.nostr_profile.picture,
+ }}
+ />
+ {:else if bv_photo_path_opt}
+ {#await basis.lc_fs_read_bin(bv_photo_path_opt) then file_data}
+ <ImageBlob
+ basis={{
+ data: file_data,
+ }}
+ />
+ {/await}
+ {:else}
+ <div class={`flex flex-row justify-start items-center -translate-y-8`}>
+ <ImageUploadAddPhoto
+ bind:bv_photo_path={bv_photo_path_opt}
+ basis={{
+ lc_handle_photo_add: basis.lc_handle_photo_add,
+ }}
+ />
+ </div>
+ {/if}
+ <div
+ class={`absolute bottom-0 left-0 flex flex-col h-[calc(100%-100%/1.618)] w-full px-6 gap-2 justify-end items-center`}
+ >
+ <div
+ class={`flex flex-col w-full gap-[2px] justify-center items-center`}
+ >
+ <div class={`flex flex-row h-10 w-full justify-start items-center`}>
+ <button
+ class={`group flex flex-row justify-center items-center`}
+ on:click={basis.lc_handle_edit_profile_display_name}
+ >
+ <p
+ class={`font-sansd font-[600] text-[2rem] ${classes_photo_overlay_glyph} ${load_data?.nostr_profile.display_name ? `` : `capitalize opacity-active`} el-re`}
+ >
+ {load_data?.nostr_profile.display_name
+ ? load_data.nostr_profile.display_name
+ : `+ ${`${$lls(`icu.add_*`, { value: `${$lls(`common.profile_name`)}` })}`}`}
+ </p>
+ </button>
+ </div>
+ <div
+ class={`flex flex-row w-full gap-[6px] justify-start items-center`}
+ >
+ <button
+ class={`group flex flex-row justify-center items-center`}
+ on:click={async () => {
+ if (load_data?.nostr_profile.name) {
+ const confirm =
+ basis.lc_handle_edit_profile_name_confirm();
+ if (!confirm) return;
+ }
+ await basis.lc_handle_edit_profile_name();
+ }}
+ >
+ <p
+ class={`font-sansd font-[600] text-[1.1rem] ${classes_photo_overlay_glyph} ${load_data?.nostr_profile.name ? `` : `capitalize opacity-active`} el-re`}
+ >
+ {load_data?.nostr_profile.name
+ ? `@${load_data.nostr_profile.name}`
+ : `+ ${`${$lls(`icu.add_*`, { value: `${$lls(`common.username`)}` })}`}`}
+ </p>
+ </button>
+ <p
+ class={`font-sans font-[400] ${classes_photo_overlay_glyph}`}
+ >
+ {ascii.bullet}
+ </p>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ alert(`@todo!`);
+ }}
+ >
+ <Glyph
+ basis={{
+ classes: `${classes_photo_overlay_glyph}`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `link-simple`,
+ }}
+ />
+ </button>
+ </div>
+ <div class={`flex flex-row w-full justify-start items-center`}>
+ <button
+ class={`group flex flex-row justify-center items-center`}
+ on:click={basis.lc_handle_edit_profile_about}
+ >
+ <p
+ class={`font-sansd font-[400] text-[1.1rem] ${classes_photo_overlay_glyph} ${load_data?.nostr_profile.about ? `` : `capitalize opacity-active`}`}
+ >
+ {load_data?.nostr_profile.about
+ ? `@${load_data.nostr_profile.about}`
+ : `+ ${`${$lls(`icu.add_*`, { value: `${$lls(`common.bio`)}` })}`}`}
+ </p>
+ </button>
+ </div>
+ </div>
+ <div
+ class={`flex flex-row w-full pt-2 pb-6 gap-2 justify-start items-center`}
+ >
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ view_display = `photos`;
+ }}
+ >
+ <p
+ class={`font-sans text-[1.1rem] font-[600] capitalize ${view_display === `photos` ? classes_photo_overlay_glyph_opt_selected : classes_photo_overlay_glyph_opt} el-re`}
+ >
+ {`photos`}
+ </p>
+ </button>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ view_display = `following`;
+ }}
+ >
+ <p
+ class={`font-sans text-[1.1rem] font-[600] capitalize ${view_display === `following` ? classes_photo_overlay_glyph_opt_selected : classes_photo_overlay_glyph_opt} el-re`}
+ >
+ {`following`}
+ </p>
+ </button>
+ <button
+ class={`flex flex-row justify-center items-center`}
+ on:click={async () => {
+ view_display = `followers`;
+ }}
+ >
+ <p
+ class={`font-sans text-[1.1rem] font-[600] capitalize ${view_display === `followers` ? classes_photo_overlay_glyph_opt_selected : classes_photo_overlay_glyph_opt} el-re`}
+ >
+ {`followers`}
+ </p>
+ </button>
+ </div>
+ </div>
+</div>
+<div class={`flex flex-col w-full justify-start items-center`}>
+ {#if view_display === `photos`}
+ <p class={`font-sans font-[400] text-layer-0-glyph`}>
+ {view_display}
+ </p>
+ {:else if view_display === `following`}
+ <p class={`font-sans font-[400] text-layer-0-glyph`}>
+ {view_display}
+ </p>
+ {:else if view_display === `followers`}
+ <p class={`font-sans font-[400] text-layer-0-glyph`}>
+ {view_display}
+ </p>
+ {/if}
+</div>
+<FloatTabs />
diff --git a/apps-lib/src/lib/view/settings.svelte b/apps-lib/src/lib/view/settings.svelte
@@ -0,0 +1,364 @@
+<script lang="ts">
+ import {
+ app_thc,
+ ascii,
+ kv_init_page,
+ LayoutTrellis,
+ LayoutView,
+ lls,
+ PageToolbar,
+ Trellis,
+ type CallbackPromise,
+ type CallbackPromiseGeneric,
+ type ISelectOption,
+ type ViewBasis,
+ } from "$lib";
+ import { onDestroy, onMount } from "svelte";
+
+ export let basis: ViewBasis<{
+ lc_color_mode: CallbackPromiseGeneric<ISelectOption<string>>;
+ lc_settings_nostr: CallbackPromise;
+ }>;
+
+ onMount(async () => {
+ if (!basis.kv_init_prevent) await kv_init_page();
+ if (basis.lc_on_mount) await basis.lc_on_mount();
+ });
+
+ onDestroy(async () => {
+ if (basis.lc_on_destroy) await basis.lc_on_destroy();
+ });
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$lls(`common.settings`)}`,
+ },
+ }}
+ />
+ <LayoutTrellis>
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Appearance`,
+ },
+ list: [
+ {
+ hide_active: true,
+ select: {
+ label: {
+ left: [
+ {
+ value: `${$lls(`common.color_mode`)}`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ display: {
+ label: {
+ value: `${$app_thc}`,
+ classes: `capitalize`,
+ },
+ },
+ el: {
+ value: $app_thc,
+ options: [
+ {
+ entries: [
+ {
+ value: ascii.bullet,
+ label: `${$lls(`icu.choose_*`, { value: `${$lls(`common.color_mode`)}`.toLowerCase() })}`,
+ disabled: true,
+ },
+ {
+ value: `light`,
+ label: `${$lls(`common.light`)}`,
+ },
+ {
+ value: `dark`,
+ label: `${$lls(`common.dark`)}`,
+ },
+ ],
+ },
+ ],
+ callback: basis.lc_color_mode,
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Nostr Keys`,
+ },
+ list: [
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Nostr Key (public)`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ callback: async () => {
+ alert(`@todo!`);
+ /*const public_key = await keystore.get(
+ ks.keys.nostr_publickey,
+ );
+ if (`err` in public_key) return;
+ await dialog.alert(
+ `Hi! This is your nostr public key ${public_key.result}`,
+ );*/
+ },
+ },
+ },
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Nostr Key (secret)`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ callback: async () => {
+ alert(`@todo!`);
+ /*const public_key = await keystore.get(
+ ks.keys.nostr_publickey,
+ );
+ if (`err` in public_key) return;
+ const secret_key = await keystore.get(
+ ks.keys.nostr_secretkey(
+ public_key.result,
+ ),
+ );
+ if (`err` in secret_key) return;
+ await dialog.alert(
+ `Hi! This is your nostr secret key ${secret_key.result}`,
+ );*/
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Configuration`,
+ },
+ list: [
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Nostr Settings`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ callback: basis.lc_settings_nostr,
+ },
+ },
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Reset Device`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ callback: async () => {
+ alert(`@todo!`);
+ /*const confirm = await dialog.confirm(
+ `Hi! This will delete your saved keys.`,
+ );
+ if (confirm === true) await reset_device();*/
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Location`,
+ },
+ list: [
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Geolocation Current`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ callback: async () => {
+ alert(`@todo!`);
+ /*const pos = await geol.current();
+ await dialog.alert(JSON.stringify(pos));*/
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Web`,
+ },
+ list: [
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Radroots.Org (Open Homepage)`,
+ },
+ ],
+ },
+ callback: async () => {
+ //const url = `https://radroots.org`;
+ //await browser.open(url);
+ },
+ },
+ },
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Radroots.Org (Share Homepage)`,
+ },
+ ],
+ },
+ callback: async () => {
+ //await share.open({
+ // title: `Radroots Home Page`,
+ // text: `Find farmers around the world.`,
+ // url: `https://radroots.org`,
+ // dialog_title: `This is the dialog title`,
+ //});
+ },
+ },
+ },
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Primal.Net (Open Profile)`,
+ },
+ ],
+ },
+ callback: async () => {
+ //const public_key = await keystore.get(
+ // ks.keys.nostr_publickey,
+ //);
+ //const npub = nostr.lib.npub(public_key);
+ //const url = `https://primal.net/p/${npub}`;
+ //await browser.open(url);
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ <Trellis
+ basis={{
+ args: {
+ layer: 1,
+ title: {
+ value: `Device`,
+ },
+ list: [
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Device (Info)`,
+ },
+ ],
+ },
+ callback: async () => {
+ //const data = await device.info();
+ //await dialog.alert(JSON.stringify(data));
+ },
+ },
+ },
+ {
+ touch: {
+ label: {
+ left: [
+ {
+ value: `Device (Battery)`,
+ },
+ ],
+ },
+ callback: async () => {
+ //const data = await device.battery();
+ //await dialog.alert(JSON.stringify(data));
+ },
+ },
+ },
+ ],
+ },
+ }}
+ />
+ </LayoutTrellis>
+</LayoutView>