web_lib

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

commit af35fb75a82f87f59f29f60d82e526e257696147
parent c204a863c6e9486a96fc2fe8016c26816689ef6d
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Thu, 26 Sep 2024 17:15:54 +0000

apps-lib: add nav previous store, add query params stores, add routes utils, edit query params utils, add input element ui, add select element ui, add trellis input component, edit components, locales, stores, types, utils

Diffstat:
Mapps-lib/src/lib/components/input_form.svelte | 4+++-
Mapps-lib/src/lib/components/layout_trellis.svelte | 11++++++++++-
Mapps-lib/src/lib/components/layout_view.svelte | 20++++++++++----------
Mapps-lib/src/lib/components/nav.svelte | 38++++++++++++++++++++++++++++++--------
Mapps-lib/src/lib/components/tabs.svelte | 4++--
Mapps-lib/src/lib/components/trellis.svelte | 21+++++++++++++++++----
Aapps-lib/src/lib/components/trellis_input.svelte | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/components/trellis_row_label.svelte | 6+++---
Mapps-lib/src/lib/components/trellis_touch.svelte | 2+-
Mapps-lib/src/lib/index.ts | 4++++
Mapps-lib/src/lib/locales/en/common.json | 9+++++++++
Mapps-lib/src/lib/locales/en/icu.json | 13++++++++++++-
Aapps-lib/src/lib/locales/en/model_fields.json | 31+++++++++++++++++++++++++++++++
Mapps-lib/src/lib/locales/i18n.ts | 2+-
Mapps-lib/src/lib/stores/client.ts | 20+++++++++++++-------
Mapps-lib/src/lib/types/client.ts | 15+++++++--------
Mapps-lib/src/lib/types/components.ts | 11+++++------
Mapps-lib/src/lib/types/trellis.ts | 25+++++++++++++++++++++++--
Mapps-lib/src/lib/types/ui.ts | 39+++++++++++++++++++++++++++++++++++++--
Aapps-lib/src/lib/ui/input_element.svelte | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/ui/loading.svelte | 5+++--
Aapps-lib/src/lib/ui/select_element.svelte | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/utils/client.ts | 42++++++++++++++++++++++++++++++------------
Aapps-lib/src/lib/utils/routes.ts | 44++++++++++++++++++++++++++++++++++++++++++++
24 files changed, 514 insertions(+), 71 deletions(-)

diff --git a/apps-lib/src/lib/components/input_form.svelte b/apps-lib/src/lib/components/input_form.svelte @@ -15,11 +15,13 @@ ? `bg-transparent` : `bg-layer-${layer}-surface`; $: classes_wrap = `px-4`; + $: sync_init = + typeof basis.sync_init === `boolean` ? basis.sync_init : true; onMount(async () => { try { if (basis.id) { - if (basis.init) await kv.set(basis.id, ``); + if (sync_init) await kv.set(basis.id, ``); else if (basis.sync) { const kv_val = await kv.get(basis.id); if (kv_val) el.value = kv_val; diff --git a/apps-lib/src/lib/components/layout_trellis.svelte b/apps-lib/src/lib/components/layout_trellis.svelte @@ -1,3 +1,12 @@ -<div class={`flex flex-col w-full pt-4 px-6 pb-12 gap-4`}> +<script lang="ts"> + import type { IClOpt } from "$lib/types/client"; + import { fmt_cl } from "$lib/utils/client"; + + export let basis: IClOpt | undefined = undefined; +</script> + +<div + class={`${fmt_cl(basis?.classes)} flex flex-col w-full pt-4 px-6 pb-12 gap-4`} +> <slot /> </div> diff --git a/apps-lib/src/lib/components/layout_view.svelte b/apps-lib/src/lib/components/layout_view.svelte @@ -3,11 +3,11 @@ type AppLayoutKey, type IClOpt, app_layout, - app_nav_blur, - app_nav_visible, - app_tabs_blur, - app_tabs_visible, fmt_cl, + nav_blur, + nav_visible, + tabs_blur, + tabs_visible, } from "$lib"; import { onDestroy, onMount } from "svelte"; @@ -39,18 +39,18 @@ } }); - $: classes_nav = $app_nav_visible + $: classes_nav = $nav_visible ? `pt-h_nav_${$app_layout}` : styles[$app_layout]; - $: classes_tabs = $app_tabs_visible ? `pb-h_tabs_${$app_layout}` : ``; + $: classes_tabs = $tabs_visible ? `pb-h_tabs_${$app_layout}` : ``; $: classes_fade = basis?.fade ? `fade-in` : ``; const scrollChange = (): void => { - if (Math.max(el?.scrollTop || 0, 0) > 10) app_nav_blur.set(true); - else app_nav_blur.set(false); + 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) app_tabs_blur.set(true); - else app_tabs_blur.set(false); + if (Math.max(el?.scrollTop || 0, 0) > 10) tabs_blur.set(true); + else tabs_blur.set(false); }; </script> diff --git a/apps-lib/src/lib/components/nav.svelte b/apps-lib/src/lib/components/nav.svelte @@ -2,11 +2,13 @@ import { goto } from "$app/navigation"; import { app_layout, - app_nav_blur, - app_nav_visible, + encode_qp_route, Fill, fmt_cl, Glyph, + nav_blur, + nav_prev, + nav_visible, type INavBasis, } from "$lib"; import Loading from "$lib/ui/loading.svelte"; @@ -20,7 +22,7 @@ onMount(async () => { try { - app_nav_visible.set(true); + nav_visible.set(true); } catch (e) { } finally { } @@ -28,7 +30,7 @@ onDestroy(async () => { try { - app_nav_visible.set(false); + nav_visible.set(false); } catch (e) { } finally { } @@ -37,7 +39,7 @@ <div bind:this={el} - class={`z-10 absolute top-0 left-0 flex flex-col w-full justify-start items-start transition-all duration-[250ms] h-nav_${$app_layout} ${$app_nav_blur ? `bg-layer-0-surface-blur/30 backdrop-blur-md` : ``}`} + class={`z-10 absolute top-0 left-0 flex flex-col w-full justify-start items-start transition-all duration-[250ms] h-nav_${$app_layout} ${$nav_blur ? `bg-layer-0-surface-blur/30 backdrop-blur-md` : ``}`} > <div bind:this={el_inner} @@ -50,7 +52,27 @@ class={`group col-span-4 flex flex-row h-full pl-2 justify-start items-center`} on:click={async () => { if (basis.prev.callback) await basis.prev.callback(); - await goto(basis.prev.route); + console.log(`basis.prev.route `, basis.prev.route); + let route_to = + typeof basis.prev.route === `string` + ? basis.prev.route + : encode_qp_route( + basis.prev.route[0], + basis.prev.route[1], + ); + console.log(`route_to `, route_to); + if ($nav_prev.length) { + const nav_prev_param = $nav_prev.pop(); + console.log(`nav_prev_param `, nav_prev_param); + if (nav_prev_param) + route_to = encode_qp_route( + nav_prev_param.route, + nav_prev_param.params, + ); + } + + console.log(`route_to `, route_to); + await goto(route_to); }} > <Glyph @@ -83,9 +105,9 @@ }} > <p - class={`font-sans text-navCurrent text-layer-1-glyph`} + class={`${fmt_cl(basis.title.label.classes)} font-sans text-navCurrent text-layer-1-glyph`} > - {basis.title.label} + {basis.title.label.value} </p> </button> {:else} diff --git a/apps-lib/src/lib/components/tabs.svelte b/apps-lib/src/lib/components/tabs.svelte @@ -4,15 +4,15 @@ type ITabsBasis, app_layout, app_tab_active, - app_tabs_blur, fmt_cl, sleep, + tabs_blur, } from "$lib"; export let basis: ITabsBasis; $: basis = basis; - $: classes_blur = $app_tabs_blur ? `bg-layer-1-surface/30` : ``; + $: classes_blur = $tabs_blur ? `bg-layer-1-surface/30` : ``; let tab_focus: number | null = null; diff --git a/apps-lib/src/lib/components/trellis.svelte b/apps-lib/src/lib/components/trellis.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { fmt_cl, parse_layer, t, type ITrellis } from ".."; import TrellisDefaultLabel from "./trellis_default_label.svelte"; + import TrellisInput from "./trellis_input.svelte"; import TrellisOffset from "./trellis_offset.svelte"; import TrellisTitle from "./trellis_title.svelte"; import TrellisTouch from "./trellis_touch.svelte"; @@ -69,10 +70,15 @@ <div class={`flex flex-row h-full w-full gap-1 items-center overflow-y-hidden`} > - <TrellisOffset - basis={basis.offset} - layer={args.layer} - /> + {#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} @@ -81,6 +87,13 @@ {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} + /> {/if} </div> </div> diff --git a/apps-lib/src/lib/components/trellis_input.svelte b/apps-lib/src/lib/components/trellis_input.svelte @@ -0,0 +1,78 @@ +<script lang="ts"> + import { fmt_cl, fmt_trellis, type ITrellisBasisInput } from "$lib"; + import Glyph from "$lib/ui/glyph.svelte"; + import InputElement from "$lib/ui/input_element.svelte"; + import Loading from "$lib/ui/loading.svelte"; + 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`} + > + <InputElement + basis={{ + ...basis.basis, + layer: layer, + }} + /> + {#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`}> + <Loading + basis={{ + dim: `glyph-send-button`, + blades: 6, + classes: `text-layer-${layer}-glyph transition-all`, + }} + /> + </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-`, + }} + /> + </button> + {/if} + </div> + {/if} + {/if} + </div> + </div> +</div> diff --git a/apps-lib/src/lib/components/trellis_row_label.svelte b/apps-lib/src/lib/components/trellis_row_label.svelte @@ -13,7 +13,7 @@ <div class={`flex flex-row h-full w-content items-center`}> {#each basis.left as title_l} <div - class={`flex flex-row h-full max-w-fit items-center ${title_l.hide_truncate ? `` : `truncate`}`} + class={`${fmt_cl(title_l.classes_wrap)} flex flex-row h-full max-w-fit items-center ${title_l.hide_truncate ? `` : `truncate`}`} > {#if title_l.glyph} <div @@ -37,13 +37,13 @@ > {#each basis.right.reverse() as title_r} <div - class={`${fmt_cl(title_r.classes)} flex flex-row h-full max-w-fit gap-1 items-center ${title_r.hide_truncate ? `` : `truncate`}`} + class={`${fmt_cl(title_r.classes_wrap)} flex flex-row h-full max-w-trellis_value gap-1 items-center ${title_r.hide_truncate ? `` : `truncate`}`} > {#if title_r.glyph} <Glyph basis={{ ...title_r.glyph }} /> {/if} <p - class={`${get_label_classes(layer, title_r.kind, hide_active)} ${title_r.hide_truncate ? `` : `truncate`} truncate font-sans text-trellisLine transition-all`} + class={`${fmt_cl(title_r.classes)} ${get_label_classes(layer, title_r.kind, hide_active)} ${title_r.hide_truncate ? `` : `truncate`} font-sans text-trellisLine transition-all`} > {title_r.value || ``} </p> diff --git a/apps-lib/src/lib/components/trellis_touch.svelte b/apps-lib/src/lib/components/trellis_touch.svelte @@ -24,7 +24,7 @@ }} > <div - class={`flex flex-row h-full w-full justify-between items-center`} + class={`flex flex-row h-full w-full justify-between items-center ${basis.end ? `pr-2` : ``}`} > <TrellisRowLabel basis={basis.label} {layer} {hide_active} /> {#if basis.display} diff --git a/apps-lib/src/lib/index.ts b/apps-lib/src/lib/index.ts @@ -17,6 +17,7 @@ export { default as Tabs } from "./components/tabs.svelte" export { default as Trellis } from "./components/trellis.svelte" export { default as TrellisDefaultLabel } from "./components/trellis_default_label.svelte" export { default as TrellisEnd } from "./components/trellis_end.svelte" +export { default as TrellisInput } from "./components/trellis_input.svelte" export { default as TrellisOffset } from "./components/trellis_offset.svelte" export { default as TrellisRowDisplayValue } from "./components/trellis_row_display_value.svelte" export { default as TrellisRowLabel } from "./components/trellis_row_label.svelte" @@ -32,9 +33,12 @@ export { default as CssStatic } from "./ui/css_static.svelte" export { default as Divider } from "./ui/divider.svelte" export { default as Fill } from "./ui/fill.svelte" export { default as Glyph } from "./ui/glyph.svelte" +export { default as InputElement } from "./ui/input_element.svelte" export { default as Loading } from "./ui/loading.svelte" +export { default as SelectElement } from "./ui/select_element.svelte" export * from "./utils/client" export * from "./utils/dom" export * from "./utils/geo" export * from "./utils/ndk" +export * from "./utils/routes" export * from "./utils/time" diff --git a/apps-lib/src/lib/locales/en/common.json b/apps-lib/src/lib/locales/en/common.json @@ -1,4 +1,13 @@ { + "settings": "Settings", + "products": "Products", + "locations": "Locations", + "profiles": "Profiles", + "profile": "Profile", + "profile_name": "Profile name", + "details": "Details", + "key": "Key", + "active": "Active", "delete": "Delete", "messages": "Messages", "message": "Message", diff --git a/apps-lib/src/lib/locales/en/icu.json b/apps-lib/src/lib/locales/en/icu.json @@ -1,3 +1,14 @@ { - "*_available": "{value} Available" + "invalid_entry": "Invalid {value} entry", + "enter_new": "Enter new {value}", + "enter": "Enter {value}", + "post": "Post {value}", + "no": "No {value}", + "delete": "Delete {value}", + "set_as": "Set as {value}", + "add": "Add {value}", + "add_a": "Add a {value}", + "edit": "Edit {value}", + "view": "View {value}", + "available": "{value} Available" } \ No newline at end of file diff --git a/apps-lib/src/lib/locales/en/model_fields.json b/apps-lib/src/lib/locales/en/model_fields.json @@ -0,0 +1,30 @@ +{ + "lat": "Latitude", + "lng": "Longitude", + "geohash": "Geohash", + "label": "Label", + "key": "Key", + "process": "Process", + "lot": "Lot", + "profile": "Profile", + "year": "Year", + "qty_amt": "Quantity Amount", + "qty_unit": "Quantity Unit", + "qty_label": "Quantity Label", + "qty_avail": "Quantity Available", + "price_amt": "Price", + "price_currency": "Currency", + "price_qty_amt": "Price", + "price_qty_unit": "Unit", + "notes": "Notes", + "public_key": "Public Key", + "name": "Profile Name", + "display_name": "Display Name", + "about": "About", + "website": "Website", + "picture": "Picture", + "banner": "Banner", + "nip05": "NIP 05", + "lud06": "LUD 06", + "lud16": "LUD 16" +} +\ No newline at end of file diff --git a/apps-lib/src/lib/locales/i18n.ts b/apps-lib/src/lib/locales/i18n.ts @@ -11,7 +11,7 @@ type LanguageConfig = { value?: string; }; -const locales_files = [`app`, `common`, `currency`, `icu`, `measurement`, `trade`] as const; +const locales_files = [`app`, `common`, `currency`, `icu`, `measurement`, `model_fields`, `trade`] as const; const translations_keys: Record<Locale, any> = { en: { locales_keys }, }; diff --git a/apps-lib/src/lib/stores/client.ts b/apps-lib/src/lib/stores/client.ts @@ -1,12 +1,14 @@ -import { type AppLayoutKey } from "$lib"; -import { queryParameters } from "sveltekit-search-params"; +import { type AppLayoutKey, 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 app_qp = queryParameters(); +export const qp = queryParameters(); +export const qp_nostr_pk = queryParam("nostr_pk"); +export const qp_rkey = queryParam("rkey"); export let kv: Keyva; if (typeof window !== 'undefined') kv = new Keyva({ name: kv_name }); @@ -15,10 +17,14 @@ export const app_layout = writable<AppLayoutKey>(`base`); export const app_config = writable<boolean>(false); export const app_render = writable<boolean>(false); export const app_win = writable<[number, number]>([0, 0]); +export const app_notify = writable<string>(``); -export const app_nav_visible = writable<boolean>(false); -export const app_nav_blur = writable<boolean>(false); +export const nav_visible = writable<boolean>(false); +export const nav_blur = writable<boolean>(false); +export const nav_prev = writable<NavigationPreviousParam[]>([]); -export const app_tabs_visible = writable<boolean>(false); -export const app_tabs_blur = writable<boolean>(false); +export const tabs_visible = writable<boolean>(false); +export const tabs_blur = writable<boolean>(false); export const app_tab_active = writable<number>(0); + + diff --git a/apps-lib/src/lib/types/client.ts b/apps-lib/src/lib/types/client.ts @@ -1,15 +1,15 @@ +import { type IGlyph, type NavigationRoute } from "$lib"; import type { ThemeLayer } from "@radroots/theme"; -import type { IGlyph } from "./ui"; export type AppLayoutKey = 'lg' | 'base'; -type NavigationRouteBasis = string; export type AnchorRoute = `/${string}`; -export type NavigationRouteParamPublicKey = `pk`; +export type NavigationRouteParamNostrPublicKey = `nostr_pk`; +export type NavigationRouteParamRecordKey = `rkey`; export type NavigationRouteParamId = `id`; export type NavigationRouteParamCmd = `cmd`; -export type NavigationRouteParamKey = NavigationRouteParamPublicKey | NavigationRouteParamId | NavigationRouteParamCmd; +export type NavigationRouteParamKey = NavigationRouteParamNostrPublicKey | NavigationRouteParamId | NavigationRouteParamCmd | NavigationRouteParamRecordKey; export type NavigationParamTuple = [NavigationRouteParamKey, string]; -export type NavigationPreviousParam = { route: NavigationRouteBasis, label?: string; params?: NavigationParamTuple[] } +export type NavigationPreviousParam = { route: NavigationRoute, label?: string; params?: NavigationParamTuple[] } export type GeometryScreenPositionHorizontal = `left` | `center` | `right`; export type GeometryScreenPositionVertical = `top` | `center` | `bottom`; @@ -33,8 +33,6 @@ export type CallbackPromiseGeneric<T> = (value: T) => Promise<void>; //export type CallbackPromiseReturn<T> = () => Promise<T>; export type CallbackPromise = () => Promise<void>; -export type NavigationRoute = string; //@todo - export type IRoute = { route: NavigationRoute; }; @@ -130,7 +128,8 @@ export type ILabelTupFields = { export type ILableFields = IGlOpt & { value: string; swap?: string; - classes?: string + classes_wrap?: string + classes?: string; kind?: LabelFieldKind hide_truncate?: boolean; hide_active?: boolean; diff --git a/apps-lib/src/lib/types/components.ts b/apps-lib/src/lib/types/components.ts @@ -1,4 +1,5 @@ -import type { CallbackPromise, CallbackPromiseGeneric, ICb, ICbGOpt, ICbOpt, IClOpt, IGl, IGlOpt, ILabelFieldsOpt, ILabelOpt, ILabelOptFieldsOpt, ILyOpt, ILyOptTs } from "./client"; +import type { NavigationRoute } from "$lib/utils/routes"; +import type { CallbackPromise, CallbackPromiseGeneric, ICb, ICbGOpt, ICbOpt, IClOpt, IGl, IGlOpt, ILabel, ILabelFieldsOpt, ILabelOpt, ILabelOptFieldsOpt, ILyOpt, ILyOptTs, NavigationParamTuple } from "./client"; import type { GlyphKey, GlyphWeight, IGlyph } from "./ui"; export type ITabsBasisList = { @@ -31,7 +32,7 @@ export type IInputFormBasis = IClOpt & ILyOptTs & ICbGOpt<{ val: string; pass: b hidden?: boolean; validate?: RegExp; sync?: boolean; - init?: boolean; + sync_init?: boolean; field?: IFormField; notify_inline?: { glyph: GlyphKey | IGlyph; @@ -82,11 +83,9 @@ export type IEnvelopeTitledBasis = { export type INavBasis = { prev: ICbOpt & { label?: string; - route: string; - }; - title?: ICbOpt & { - label: string; + route: NavigationRoute | [NavigationRoute, NavigationParamTuple[]]; }; + title?: ICbOpt & ILabel; option?: ICb & IGlOpt & ILabelOpt & { loading?: boolean; }; diff --git a/apps-lib/src/lib/types/trellis.ts b/apps-lib/src/lib/types/trellis.ts @@ -1,5 +1,5 @@ -import type { ICbGOpt, ICbOpt, ICbROpt, IClOpt, IGlOpt, ILabel, ILabelOpt, ILabelTup, ILy } from "./client"; -import type { GlyphKey, IGlyph } from "./ui"; +import type { CallbackPromise, ICbGOpt, ICbOpt, ICbROpt, IClOpt, IGlOpt, ILabel, ILabelOpt, ILabelTup, ILy } from "./client"; +import type { GlyphKey, IGlyph, IInputElement } from "./ui"; export type ITrellis = ILy & IClOpt & @@ -10,6 +10,7 @@ export type ITrellis = ILy & description?: ITrellisDescription; default_el?: ITrellisDefault; list?: (ITrellisKind | undefined)[]; + hide_offset?: true; }; export type ITrellisTitle = ICbOpt & @@ -46,6 +47,7 @@ export type ITrellisBasisOffsetMod = ITrellisBasisOffsetModKey | IGlyph; export type ITrellisKind = ( | ITrellisKindTouch + | ITrellisKindInput ); export type ITrellisBasis = { @@ -88,3 +90,21 @@ export type ITrellisBasisTouch = ICbGOpt<MouseEvent> & end?: ITrellisBasisTouchEnd; display?: ITrellisKindDisplayValue; }; + +export type ITrellisKindInput = ITrellisBasis & { + input: ITrellisBasisInput; +}; + +export type ITrellisBasisInput = { + basis: IInputElement; + line_label?: { + classes?: string; + value: string; + }; + action?: { + visible: boolean; + loading?: boolean; + callback?: CallbackPromise; + glyph?: IGlyph + }; +}; +\ No newline at end of file diff --git a/apps-lib/src/lib/types/ui.ts b/apps-lib/src/lib/types/ui.ts @@ -1,9 +1,10 @@ +import type { CallbackPromiseGeneric, GeometryCardinalDirection, GeometryGlyphDimension, ICbGOpt, ICbOpt, IClOpt, IFormField, ILy, ILyOptTs } from "$lib"; import type { ThemeLayer } from "@radroots/theme"; -import type { GeometryCardinalDirection, GeometryGlyphDimension, ICbOpt } from "./client"; export type GlyphKeyCurrency = `dollar` | `eur`; export type GlyphKey = | + `address-book-tabs` | `paper-plane-tilt` | `note-pencil` | `share-fat` | @@ -94,9 +95,42 @@ export type IGlyph = ICbOpt & { export type ILoadingBlades = 6 | 12; +export type ILoadingDimension = GeometryGlyphDimension | `glyph-send-button`; + export type ILoading = { classes?: string; color?: 'white'; blades?: ILoadingBlades; - dim?: GeometryGlyphDimension; + dim?: ILoadingDimension; +}; + +export type ISelectOption<T extends string> = { + value: T; + label: string; + disabled?: boolean; }; + +export type ISelectElement = ILy & + ICbGOpt<ISelectOption<string>> & { + id?: string; + classes?: string; + mask?: boolean; + options: { group?: string | true; entries: ISelectOption<string>[] }[]; + }; + +export type IInputElement = IClOpt & ILyOptTs & { + id: string; + placeholder?: string; + label?: string; + hidden?: boolean; + validate?: RegExp; + sync?: true; + sync_init?: true | string; + field?: IFormField; + /*notify_inline?: { + glyph: GlyphKey | IGlyph; + };*/ + callback?: CallbackPromiseGeneric<{ val: string; pass: boolean; }>; + callback_keydown?: CallbackPromiseGeneric<{ key: string; }>; + on_mount?: CallbackPromiseGeneric<HTMLElement>; +}; +\ No newline at end of file diff --git a/apps-lib/src/lib/ui/input_element.svelte b/apps-lib/src/lib/ui/input_element.svelte @@ -0,0 +1,81 @@ +<script lang="ts"> + import { type IInputElement, fmt_cl, kv, parse_layer } from "$lib"; + import { onMount } from "svelte"; + + let el: HTMLInputElement | null = null; + + export let basis: IInputElement; + $: basis = basis; + + $: id = basis.id ? basis.id : null; + $: layer = + typeof basis.layer === `boolean` ? 0 : parse_layer(basis.layer, 1); //@todo + + onMount(async () => { + try { + if (basis.id) { + if (basis.sync_init) + await kv.set( + basis.id, + typeof basis.sync_init === `string` + ? basis.sync_init + : ``, + ); + else if (basis.sync) { + const kv_val = await kv.get(basis.id); + if (kv_val) el.value = kv_val; + await kv.set(basis.id, kv_val || ``); + } + } + if (basis.on_mount) await basis.on_mount(el); + } catch (e) {} + }); +</script> + +<input + bind:this={el} + {id} + type="text" + class={`${fmt_cl(basis.classes)} form-input h-full text-layer-${layer}-glyph placeholder:text-layer-${layer}-glyph_pl caret-layer-${layer}-glyph`} + placeholder={basis.placeholder || ""} + on:input={async ({ currentTarget: el }) => { + let pass = true; + let val = el.value; + if (basis.field) { + val = el.value + .split("") + .filter((char) => basis.field.charset.test(char)) + .join(""); + if ( + !basis.field.validate.test(val) && + basis.field.validate_keypress + ) { + pass = false; + } + } + el.value = val; + if (basis.sync) await kv.set(basis.id, val); + if (basis.callback) await basis.callback({ val, pass }); + }} + on:keydown={async (ev) => { + if (basis.callback_keydown) + await basis.callback_keydown({ key: ev.key }); + }} +/> + +<!-- + <input + class={`${fmt_cl(basis.classes)} input flex flex-row p-[0px] border-[0px] focus:border-0 outline-[0px] focus:outline-0 text-layer-${layer}-glyph placeholder:text-layer-${layer}-glyph_pl placeholder:font-[300] caret-layer-${layer}-glyph bg-transparent`} + placeholder={basis.placeholder || ``} + value={basis.value} + bind:this={el} + on:input={async (ev) => { + if (basis.callback_value) + await basis.callback_value([ev.currentTarget.value, el]); + }} + on:keydown={async (ev) => { + if (basis.callback_keydown) + await basis.callback_keydown([ev.key, ev.currentTarget.value]); + }} +/> +--> diff --git a/apps-lib/src/lib/ui/loading.svelte b/apps-lib/src/lib/ui/loading.svelte @@ -1,12 +1,13 @@ <script lang="ts"> - import { fmt_cl, type GeometryGlyphDimension, type ILoading } from "$lib"; + import { fmt_cl, type ILoading, type ILoadingDimension } from "$lib"; - export const lm: Map<GeometryGlyphDimension, [string, string]> = new Map([ + export const lm: Map<ILoadingDimension, [string, string]> = new Map([ [`xl`, [`h-[36px] w-[36px]`, `text-[22px]`]], [`lg`, [`h-[32px] w-[32px]`, `text-[18px]`]], [`md`, [`h-[32px] w-[32px]`, `text-[18px]`]], [`sm`, [`h-[32px] w-[32px]`, `text-[18px]`]], [`xs`, [`h-[32px] w-[32px]`, `text-[18px]`]], + [`glyph-send-button`, [`h-[20px] w-[20px]`, `text-[18px]`]], ]); export let basis: ILoading | undefined = undefined; diff --git a/apps-lib/src/lib/ui/select_element.svelte b/apps-lib/src/lib/ui/select_element.svelte @@ -0,0 +1,60 @@ +<script lang="ts"> + import type { ISelectElement } from "$lib"; + + export let basis: { args: ISelectElement }; + $: ({ args } = basis); + + const value = `~`; + + let el_wrap: HTMLDivElement | null = null; + let el_select: HTMLSelectElement | null = null; + let layer = args?.layer || 0; +</script> + +<div + class={`relative flex flex-row h-max w-auto justify-center items-center`} + bind:this={el_wrap} +> + <div + class={`z-50 absolute top-0 left-0 flex flex-row h-full w-full justify-end items-center text-layer-${layer}-glyph`} + > + <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} + {value} + on:change={async (e) => { + const opt = args.options + .map((i) => i.entries) + .reduce((_, j) => j, []) + .find((k) => k.value === e.currentTarget?.value); + if (args.callback && opt) await args.callback(opt); + if (el_select) el_select.value = value; + }} + > + {#each args.options as optg} + {#if optg.group} + <optgroup> + {#each optg.entries as opt} + <option + label={optg.group === true + ? `-`.repeat(21) + : optg.group || ``} + > + {opt.label} + </option> + {/each} + </optgroup> + {:else} + {#each optg.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/utils/client.ts b/apps-lib/src/lib/utils/client.ts @@ -1,3 +1,5 @@ +import { goto } from "$app/navigation"; +import type { GlyphKey, NavigationRoute } from "$lib"; import type { AnchorRoute, CallbackPromiseGeneric, LabelFieldKind, NavigationParamTuple, NavigationRouteParamKey } from "$lib/types/client"; import type { ColorMode, ThemeKey, ThemeLayer } from "@radroots/theme"; @@ -18,11 +20,11 @@ export const fmt_cl = (classes?: string): string => { return classes ? classes : ``; }; -export function get_label_classes(layer: ThemeLayer, label_kind: LabelFieldKind | undefined, hide_active: boolean): string { +export const get_label_classes = (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`}`}` }; -export function parse_layer(layer?: number, layer_default?: ThemeLayer): ThemeLayer { +export const parse_layer = (layer?: number, layer_default?: ThemeLayer): ThemeLayer => { switch (layer) { case 0: case 1: @@ -33,16 +35,18 @@ export function parse_layer(layer?: number, layer_default?: ThemeLayer): ThemeLa }; }; -export function fmt_trellis(hide_border_t: boolean, hide_border_b: boolean): string { +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 function encode_qp(params_list?: NavigationParamTuple[]): string { - if (!params_list || !params_list.length) return ``; - const params = params_list.filter(i => i[0] && i[1]) - let urlp = ``; - if (params_list.length) for (const [i, [k, v]] of params.entries()) urlp += `${i === 0 ? `?` : ``}&${k.trim()}=${encodeURI(v.trim())}`.trim(); - return urlp; +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 = (route: NavigationRoute, params_list?: NavigationParamTuple[]): string => { + return `${route}/${encode_qp(params_list)}` }; export const decode_qp = (query_param: string): AnchorRoute => { @@ -53,8 +57,9 @@ export const decode_qp = (query_param: string): AnchorRoute => { export function parse_qp(param: string): NavigationRouteParamKey | undefined { switch (param) { case "cmd": - case "pk": + case "nostr_pk": case "id": + case "rkey": return param; default: return undefined; @@ -95,4 +100,17 @@ export const clipboard_copy = async (text: string, callback: CallbackPromiseGene } catch (e) { console.log(`(error) clipboard_copy `, e); } -}; -\ No newline at end of file +}; + +export const as_glyph_key = (val: string): GlyphKey => { + return val as GlyphKey; +} + +export const route = async (route: NavigationRoute, params_list?: NavigationParamTuple[]): Promise<void> => { + try { + if (params_list && params_list.length) await goto(encode_qp_route(route, params_list)); + else await goto(route); + } catch (e) { + console.log(`(error) route `, e); + } +} +\ No newline at end of file diff --git a/apps-lib/src/lib/utils/routes.ts b/apps-lib/src/lib/utils/routes.ts @@ -0,0 +1,43 @@ +export type NavigationRoute = + | "/" + | "/models/location-gcs" + | "/models/location-gcs/view-map" + | "/models/nostr-profile" + | "/models/nostr-profile/edit/field" + | "/models/trade-product" + | "/models/trade-product/add" + | "/models/trade-product/add/preview" + | "/nostr" + | "/nostr/keys" + | "/nostr/notes" + | "/nostr/notes/post" + | "/nostr/profile" + | "/settings" + | "/test" + | "/init" + | "/map"; + +export function parse_route(route: string): NavigationRoute { + switch (route) { + case "/": + case "/models/location-gcs": + case "/models/location-gcs/view-map": + case "/models/nostr-profile": + case "/models/nostr-profile/edit/field": + case "/models/trade-product": + case "/models/trade-product/add": + case "/models/trade-product/add/preview": + case "/nostr": + case "/nostr/keys": + case "/nostr/notes": + case "/nostr/notes/post": + case "/nostr/profile": + case "/settings": + case "/test": + case "/init": + case "/map": + return route; + default: + return "/"; + }; +}; +\ No newline at end of file