web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

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:
Mapps-lib/package.json | 4+++-
Aapps-lib/src/lib/component/button/button-arrow.svelte | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/button/button-layout-pair.svelte | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/button/button-layout.svelte | 39+++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/carousel/carousel-item.svelte | 6++++++
Aapps-lib/src/lib/component/carousel/carousel.svelte | 12++++++++++++
Aapps-lib/src/lib/component/entry/entry-line.svelte | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rapps-lib/src/lib/components/entry_wrap.svelte -> apps-lib/src/lib/component/entry/entry-wrap.svelte | 0
Aapps-lib/src/lib/component/float/float-page-button.svelte | 37+++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/float/float-tabs.svelte | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/glyph/glyph-button-simple.svelte | 40++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/glyph/glyph-button.svelte | 21+++++++++++++++++++++
Aapps-lib/src/lib/component/glyph/glyph-circle.svelte | 22++++++++++++++++++++++
Aapps-lib/src/lib/component/glyph/glyph-title-select-label.svelte | 32++++++++++++++++++++++++++++++++
Rapps-lib/src/lib/components/label_display.svelte -> apps-lib/src/lib/component/label/label-display.svelte | 0
Rapps-lib/src/lib/el/fill.svelte -> apps-lib/src/lib/component/lib/empty.svelte | 0
Aapps-lib/src/lib/component/lib/fade.svelte | 11+++++++++++
Aapps-lib/src/lib/component/lib/load-screen.svelte | 12++++++++++++
Rapps-lib/src/lib/components/logo_circle_sm.svelte -> apps-lib/src/lib/component/lib/logo-circle-sm.svelte | 0
Aapps-lib/src/lib/component/lib/logo-circle.svelte | 41+++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/lib/splash-screen.svelte | 12++++++++++++
Aapps-lib/src/lib/component/lib/view.svelte | 14++++++++++++++
Aapps-lib/src/lib/component/map/map-marker-dot.svelte | 15+++++++++++++++
Aapps-lib/src/lib/component/map/map-point-display.svelte | 28++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/map/map-point-select.svelte | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/map/map-popup-point-geolocation.svelte | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/nav/nav-option.svelte | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/nav/nav.svelte | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/page/page-header.svelte | 41+++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/page/page-toolbar.svelte | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-default-label.svelte | 30++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-end.svelte | 30++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-input.svelte | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-line.svelte | 40++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-offset.svelte | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-row-display-value.svelte | 41+++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-row-label.svelte | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-select.svelte | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-title.svelte | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis-touch.svelte | 34++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/component/trellis/trellis.svelte | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dapps-lib/src/lib/components/button_layout.svelte | 39---------------------------------------
Dapps-lib/src/lib/components/button_layout_pair.svelte | 55-------------------------------------------------------
Dapps-lib/src/lib/components/entry_line.svelte | 57---------------------------------------------------------
Dapps-lib/src/lib/components/layout_root.svelte | 6------
Dapps-lib/src/lib/components/layout_view.svelte | 62--------------------------------------------------------------
Dapps-lib/src/lib/components/layout_window.svelte | 13-------------
Dapps-lib/src/lib/components/logo_circle.svelte | 43-------------------------------------------
Dapps-lib/src/lib/components/overlay_loading.svelte | 14--------------
Dapps-lib/src/lib/components/overlay_splash.svelte | 14--------------
Dapps-lib/src/lib/el/blur.svelte | 13-------------
Dapps-lib/src/lib/el/css_static.svelte | 2--
Dapps-lib/src/lib/el/css_styles.svelte | 4----
Aapps-lib/src/lib/el/image-blob.svelte | 43+++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/el/image-path.svelte | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/el/input.svelte | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dapps-lib/src/lib/el/input_element.svelte | 103-------------------------------------------------------------------------------
Aapps-lib/src/lib/el/label-swap.svelte | 32++++++++++++++++++++++++++++++++
Rapps-lib/src/lib/el/load_symbol.svelte -> apps-lib/src/lib/el/load-symbol.svelte | 0
Aapps-lib/src/lib/el/select-menu.svelte | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/el/select.svelte | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/el/styles.svelte | 2++
Aapps-lib/src/lib/el/text-area.svelte | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/el/toast.svelte | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/feature/image-upload-add-photo.svelte | 36++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/feature/search-result-container.svelte | 16++++++++++++++++
Aapps-lib/src/lib/feature/search-result-display.svelte | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/global.d.ts | 1+
Mapps-lib/src/lib/index.ts | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Aapps-lib/src/lib/layout/layout-overlay-blur.svelte | 13+++++++++++++
Aapps-lib/src/lib/layout/layout-overlay-loading.svelte | 7+++++++
Aapps-lib/src/lib/layout/layout-overlay-splash.svelte | 7+++++++
Aapps-lib/src/lib/layout/layout-styles.svelte | 2++
Aapps-lib/src/lib/layout/layout-trellis.svelte | 11+++++++++++
Aapps-lib/src/lib/layout/layout-view.svelte | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/layout/layout-window.svelte | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/en/common.json | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/en/icu.json | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/en/measurement.json | 33+++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/en/model.json | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/en/trade.json | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/i18n.ts | 42++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/locale/locales.json | 4++++
Aapps-lib/src/lib/service/search/lib.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/service/search/types.ts | 7+++++++
Aapps-lib/src/lib/store/app.ts | 20++++++++++++++++++++
Aapps-lib/src/lib/store/client.ts | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/store/component.ts | 5+++++
Rapps-lib/src/lib/stores/ndk.ts -> apps-lib/src/lib/store/ndk.ts | 0
Dapps-lib/src/lib/stores/app.ts | 20--------------------
Dapps-lib/src/lib/stores/client.ts | 58----------------------------------------------------------
Mapps-lib/src/lib/types/app.ts | 21+++++++++++++++++++++
Aapps-lib/src/lib/types/component.ts | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/types/el.ts | 32+++++++++++++++++++++++++++++---
Aapps-lib/src/lib/types/feature.ts | 8++++++++
Mapps-lib/src/lib/types/interface.ts | 27+++++++++++++++++++++++++--
Aapps-lib/src/lib/types/model.ts | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/types/util.ts | 25+++++++++++++++++++++++++
Aapps-lib/src/lib/types/view.ts | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/app.ts | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/carousel.ts | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/casl.ts | 20++++++++++++++++++++
Aapps-lib/src/lib/util/component.ts | 11+++++++++++
Aapps-lib/src/lib/util/conf.ts | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/document.ts | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/error.ts | 37+++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/util/geolocation.ts | 48++++++++++++++++++++++++++++++++++++++++++++++++
Rapps-lib/src/lib/utils/i18n.ts -> apps-lib/src/lib/util/i18n.ts | 0
Aapps-lib/src/lib/util/kv.ts | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rapps-lib/src/lib/utils/styles.ts -> apps-lib/src/lib/util/styles.ts | 0
Aapps-lib/src/lib/util/view.ts | 8++++++++
Dapps-lib/src/lib/utils/app.ts | 91-------------------------------------------------------------------------------
Dapps-lib/src/lib/utils/carousel.ts | 87-------------------------------------------------------------------------------
Dapps-lib/src/lib/utils/document.ts | 49-------------------------------------------------
Dapps-lib/src/lib/utils/kv.ts | 19-------------------
Dapps-lib/src/lib/utils/routes.ts | 14--------------
Aapps-lib/src/lib/view/farm-land-add.svelte | 422+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/farm-land-view.svelte | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/farm-land.svelte | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/home.svelte | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/notifications.svelte | 31+++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/search.svelte | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/settings-nostr.svelte | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/settings-profile-edit.svelte | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/settings-profile.svelte | 244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/view/settings.svelte | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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>