web_lib

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

commit 1d56f7190e62c2c9cf778475cc8358d41a5ef5de
parent 8586a8e533edd7d9ac7158a851172f366280e50f
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Thu, 24 Oct 2024 12:11:45 +0000

apps-lib: add button carousel pair, button opts display, button opts static, splash screen components. edit layout and nav components. add locales. edit stores, types, utils

Diffstat:
Aapps-lib/src/lib/components/button_carousel_pair.svelte | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/components/button_opts_display.svelte | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib/src/lib/components/button_opts_static.svelte | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib/src/lib/components/layout_view.svelte | 2+-
Mapps-lib/src/lib/components/loading_view.svelte | 2+-
Mapps-lib/src/lib/components/nav.svelte | 103+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Aapps-lib/src/lib/components/splash_screen.svelte | 13+++++++++++++
Mapps-lib/src/lib/components/tabs.svelte | 6+++---
Mapps-lib/src/lib/index.ts | 4++++
Mapps-lib/src/lib/locales/en/app.json | 2+-
Mapps-lib/src/lib/locales/en/common.json | 2++
Mapps-lib/src/lib/stores/client.ts | 7++++---
Mapps-lib/src/lib/types/client.ts | 1+
Mapps-lib/src/lib/types/components.ts | 1+
Mapps-lib/src/lib/types/ui.ts | 4++++
Mapps-lib/src/lib/utils/client.ts | 15++++++++++++---
Mapps-lib/src/lib/utils/routes.ts | 10++++------
Mapps-lib/src/lib/utils/time.ts | 8++++----
18 files changed, 354 insertions(+), 61 deletions(-)

diff --git a/apps-lib/src/lib/components/button_carousel_pair.svelte b/apps-lib/src/lib/components/button_carousel_pair.svelte @@ -0,0 +1,56 @@ +<script lang="ts"> + import { app_layout, Fill, t, type CallbackPromise } from "$lib"; + + export let basis: { + continue: { + disabled?: boolean; + label?: string; + callback: CallbackPromise; + }; + back?: { + visible: boolean; + disabled?: boolean; + label?: string; + callback: CallbackPromise; + }; + }; +</script> + +<div class={`flex flex-col justify-center items-center`}> + <button + class={`group flex flex-row h-touch_guide w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-touch ${basis.continue.disabled ? `opacity-60` : `touch-layer-1`} transition-all`} + on:click|stopPropagation={async () => { + if (!basis.continue.disabled) await basis.continue.callback(); + }} + > + <p + class={`font-sans font-[600] tracking-wide text-layer-1-glyph/80 ${basis.continue.disabled ? `` : `group-active:text-layer-1-glyph/40 `}transition-all`} + > + {basis.continue.label || `${$t(`common.continue`)}`} + </p> + </button> + {#if basis.back} + <div class={`flex flex-col justify-center items-center transition-all`}> + {#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`} transition-all`} + > + {basis.back?.label || `${$t(`common.back`)}`} + </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/button_opts_display.svelte b/apps-lib/src/lib/components/button_opts_display.svelte @@ -0,0 +1,80 @@ +<script lang="ts"> + import { Glyph, type CallbackPromise, type IClOpt } from "$lib"; + import { onDestroy, onMount } from "svelte"; + import { slide } from "svelte/transition"; + + export let basis: IClOpt & { + label: string; + list: { + label: string; + callback: CallbackPromise; + }[]; + }; + + let visible_menu = 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_menu = + typeof toggle_force === `boolean` ? toggle_force : !visible_menu; + }; +</script> + +<div class={`relative inline-block`}> + <button + class={`group flex flex-row h-[2rem] ${visible_menu ? `w-[160px] pl-5 pr-3` : `min-w-[120px] pl-3 pr-3`} gap-[0.5rem] justify-between items-center bg-layer-0-surface_w rounded-t-touch ${visible_menu ? `` : `rounded-b-touch`} border-line border-layer-0-surface-edge el-re`} + on:click|stopPropagation={async () => { + toggle(); + }} + > + <p + class={`font-circ font-[400] text-layer-1-glyph text-sm tracking-wide`} + > + {basis.label || ``} + </p> + <Glyph + basis={{ + classes: `text-layer-1-glyph ${visible_menu ? `rotate-90` : ``} duration-[350ms] el-re`, + dim: `xs`, + weight: `bold`, + key: `caret-down`, + }} + /> + </button> + {#if visible_menu} + <div + class={`absolute left-0 w-full bg-base-100 rounded-b-touch border-b-line border-l-line border-r-line border-layer-0-surface-edge overflow-hidden`} + in:slide={{ duration: 350 }} + out:slide={{ duration: 150 }} + > + <div class={`flex flex-col w-full justify-center items-start`}> + {#each basis.list as li} + <button + class={`group flex flex-row h-[2rem] w-full first:pt-1 last:pb-1 pl-5 pr-1 gap-[0.5rem] justify-start items-center overflow-hidden active:bg-layer-0-surface_a/40 el-re`} + on:click|stopPropagation={async () => { + toggle(false); + await li.callback(); + }} + > + <p + class={`font-circ font-[400] text-layer-1-glyph text-sm tracking-wide group-active:opacity-60 el-re`} + > + {li.label || ``} + </p> + </button> + {/each} + </div> + </div> + {/if} +</div> diff --git a/apps-lib/src/lib/components/button_opts_static.svelte b/apps-lib/src/lib/components/button_opts_static.svelte @@ -0,0 +1,99 @@ +<script lang="ts"> + import { Fill, Glyph, type CallbackPromise, type IClOpt } from "$lib"; + import { onDestroy, onMount } from "svelte"; + import { slide } from "svelte/transition"; + + export let basis: IClOpt & { + label: string; + list: { + label: string; + callback: CallbackPromise; + active: boolean; + }[]; + }; + + let visible_menu = 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_menu = + typeof toggle_force === `boolean` ? toggle_force : !visible_menu; + }; +</script> + +<div class={`relative inline-block`}> + <button + class={`group flex flex-row h-[2rem] ${visible_menu ? `w-[160px] pl-8 pr-3` : `w-[90px] pl-3 pr-3`} gap-[0.5rem] justify-between items-center bg-layer-0-surface_w rounded-t-touch ${visible_menu ? `` : `rounded-b-touch`} border-line border-layer-0-surface-edge el-re`} + on:click|stopPropagation={async () => { + toggle(); + }} + > + <p + class={`font-circ font-[400] text-layer-1-glyph text-sm tracking-wide`} + > + {basis.label || ``} + </p> + <Glyph + basis={{ + classes: `text-layer-1-glyph ${visible_menu ? `rotate-90` : ``} duration-[350ms] el-re`, + dim: `xs`, + weight: `bold`, + key: `caret-down`, + }} + /> + </button> + {#if visible_menu} + <div + class={`z-[20] absolute left-0 w-full bg-base-100 rounded-b-touch border-b-line border-l-line border-r-line border-layer-0-surface-edge overflow-hidden`} + in:slide={{ duration: 350 }} + out:slide={{ duration: 150 }} + > + <div + class={`z-[20] flex flex-col w-full justify-center items-start`} + > + {#each basis.list as li} + <button + class={`group flex flex-row h-[2rem] w-full first:pt-1 last:pb-1 pl-1 pr-1 gap-[0.5rem] justify-start items-center overflow-hidden active:bg-layer-0-surface_a/40 el-re`} + on:click|stopPropagation={async () => { + toggle(false); + await li.callback(); + }} + > + <div + class={`flex flex-row w-5 justify-end items-center`} + > + {#if li.active} + <Glyph + basis={{ + classes: `text-layer-1-glyph`, + dim: `xs-`, + weight: `bold`, + key: `check`, + }} + /> + {:else} + <Fill /> + {/if} + </div> + <p + class={`font-circ font-[400] text-layer-1-glyph text-sm tracking-wide group-active:opacity-60 el-re`} + > + {li.label || ``} + </p> + </button> + {/each} + </div> + </div> + {/if} +</div> diff --git a/apps-lib/src/lib/components/layout_view.svelte b/apps-lib/src/lib/components/layout_view.svelte @@ -55,7 +55,7 @@ <div bind:this={el} - class={`${fmt_cl(basis?.classes)} absolute top-0 left-0 flex flex-col h-[100vh] w-full overflow-y-scroll scroll-hide ${!basis?.hide_padding ? classes_layout : ``} ${classes_tabs} ${classes_fade}`} + 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 ${!basis?.hide_padding ? classes_layout : ``} ${classes_tabs} ${classes_fade}`} > <slot /> </div> diff --git a/apps-lib/src/lib/components/loading_view.svelte b/apps-lib/src/lib/components/loading_view.svelte @@ -3,7 +3,7 @@ </script> <div - class={`absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center`} + class={`z-50 absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`} > <Loading basis={{ dim: `lg` }} /> </div> diff --git a/apps-lib/src/lib/components/nav.svelte b/apps-lib/src/lib/components/nav.svelte @@ -12,6 +12,7 @@ type INavBasis, } from "$lib"; import { onDestroy, onMount } from "svelte"; + import ButtonArrow from "./button_arrow.svelte"; import NavOption from "./nav_option.svelte"; export let basis: INavBasis; @@ -39,6 +40,30 @@ } 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.pop(); + 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 @@ -52,49 +77,49 @@ <div class={`absolute bottom-[5px] left-0 grid grid-cols-12 flex flex-row h-8 w-full justify-start items-center`} > - <button - class={`group col-span-4 flex flex-row h-full pl-2 justify-start items-center`} - on:click={async () => { - 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.pop(); - if (nav_prev_li) - route_to = encode_qp_route( - nav_prev_li.route, - nav_prev_li.params, - ); - } - await goto(route_to); - }} + <div + class={`col-span-4 flex flex-row w-full justify-start items-center`} > - <Glyph - basis={{ - key: `caret-left`, - weight: `bold`, - dim: `md+`, - classes: `text-layer-1-glyph-hl group-active:opacity-70 transition-opacity`, - }} - /> - {#if nav_prev_label || basis.prev.label} - <p - class={`font-sans text-navPrevious text-layer-1-glyph-hl group-active:opacity-60 transition-opacity`} + {#if basis.prev.kind === `arrow`} + <div + class={`flex flex-row w-full pl-8 justify-start items-center`} > - {nav_prev_label || basis.prev.label} - </p> + <ButtonArrow + basis={{ + label: nav_prev_label || basis.prev.label, + callback: async () => { + await callback_prev(); + }, + }} + /> + </div> {:else} - <Fill /> + <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`, + }} + /> + {#if nav_prev_label || basis.prev.label} + <p + class={`font-sans text-navPrevious text-layer-1-glyph-hl group-active:opacity-60 transition-opacity`} + > + {nav_prev_label || basis.prev.label} + </p> + {:else} + <Fill /> + {/if} + </button> {/if} - </button> + </div> <div class={`col-span-4 flex flex-row h-full justify-center items-center`} > diff --git a/apps-lib/src/lib/components/splash_screen.svelte b/apps-lib/src/lib/components/splash_screen.svelte @@ -0,0 +1,13 @@ +<script lang="ts"> + import { Fill } from ".."; +</script> + +<div + class={`z-50 absolute top-0 left-0 flex flex-row h-[100vh] w-full justify-center items-center bg-layer-0-surface`} +> + <div + class={`flex flex-row h-10 w-10 justify-start items-center bg-layer-2-surface rounded-full`} + > + <Fill /> + </div> +</div> diff --git a/apps-lib/src/lib/components/tabs.svelte b/apps-lib/src/lib/components/tabs.svelte @@ -67,8 +67,8 @@ basis={{ classes: !basis.hide_active && $tabs_active === tab_i - ? `text-layer-1-glyph text-lineActiveBlue` - : `text-layer-1-glyph text-lineMd`, + ? `text-layer-2-glyph text-lineActiveBlue` + : `text-layer-2-glyph text-lineMd`, key: tab.icon, dim: `md`, weight: tab.force_weight @@ -83,7 +83,7 @@ }} /> <p - class={`font-circ font-[400] text-layer-1-glyph text-xs tracking-tight`} + class={`font-circ font-[400] text-layer-2-glyph text-xs tracking-tight`} > {tab.label || ``} </p> diff --git a/apps-lib/src/lib/index.ts b/apps-lib/src/lib/index.ts @@ -38,12 +38,16 @@ export { default as DisplayLine } from "./components/display_line.svelte"; export { default as LayoutTrellis } from "./components/layout_trellis.svelte"; export { default as Tabs } from "./components/tabs.svelte"; export { default as ButtonArrow } from "./components/button_arrow.svelte"; +export { default as ButtonOptsDisplay } from "./components/button_opts_display.svelte"; export { default as TrellisTitle } from "./components/trellis_title.svelte"; export { default as TrellisTouch } from "./components/trellis_touch.svelte"; export { default as Trellis } from "./components/trellis.svelte"; +export { default as ButtonOptsStatic } from "./components/button_opts_static.svelte"; export { default as AppControls } from "./components/app_controls.svelte"; export { default as EntryLine } from "./components/entry_line.svelte"; +export { default as SplashScreen } from "./components/splash_screen.svelte"; export { default as TrellisRowDisplayValue } from "./components/trellis_row_display_value.svelte"; +export { default as ButtonCarouselPair } from "./components/button_carousel_pair.svelte"; export { default as EnvelopeButtons } from "./components/envelope_buttons.svelte"; export { default as LayoutWindow } from "./components/layout_window.svelte"; export { default as EntryMultiline } from "./components/entry_multiline.svelte"; diff --git a/apps-lib/src/lib/locales/en/app.json b/apps-lib/src/lib/locales/en/app.json @@ -12,7 +12,7 @@ }, "name": "Radroots", "page": { - "conf": { + "cfg": { "init": { "notification": { "no_profile_name": "Your profile will be created without a name. You can change this later in Settings > Profile", diff --git a/apps-lib/src/lib/locales/en/common.json b/apps-lib/src/lib/locales/en/common.json @@ -58,6 +58,7 @@ "npub": "Npub", "nsec": "Nsec", "optional": "Optional", + "options_list": "Options list", "other": "Other", "outflows": "Outflows", "overview": "Overview", @@ -86,6 +87,7 @@ "secret_key": "Secret key", "settings": "Settings", "setup": "Setup", + "setup_for_farmer": "Setup for Farmer", "skip": "Skip", "socials": "Socials", "start_date": "Start date", diff --git a/apps-lib/src/lib/stores/client.ts b/apps-lib/src/lib/stores/client.ts @@ -1,4 +1,4 @@ -import { type AppLayoutKey, type IToast, type NavigationPreviousParam, } from "$lib"; +import { type AppConfigType, type AppLayoutKey, type IToast, type NavigationPreviousParam, } from "$lib"; import type { ColorMode, ThemeKey } from "@radroots/theme"; import { writable } from "svelte/store"; import { queryParam, queryParameters } from "sveltekit-search-params"; @@ -18,10 +18,11 @@ export let kv: Keyva; if (typeof window !== 'undefined') kv = new Keyva({ name: kv_name }); export const app_layout = writable<AppLayoutKey>(`mobile_base`); -export const app_config = writable<boolean>(true); +export const app_cfg_type = writable<AppConfigType>(`personal`); export const app_render = writable<boolean>(false); export const app_tilt = writable<boolean>(false); -export const app_loading = writable<boolean>(true); +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); diff --git a/apps-lib/src/lib/types/client.ts b/apps-lib/src/lib/types/client.ts @@ -190,3 +190,4 @@ export type IToast = IClOpt & position?: GeometryScreenPosition; }; +export type AppConfigType = `farmer` | `personal` diff --git a/apps-lib/src/lib/types/components.ts b/apps-lib/src/lib/types/components.ts @@ -90,6 +90,7 @@ export type INavBasis = { prevent_route?: { callback: CallbackPromise; }; + kind?: 'arrow' }; title?: ICbOpt & ILabel; option?: INavBasisOption; diff --git a/apps-lib/src/lib/types/ui.ts b/apps-lib/src/lib/types/ui.ts @@ -4,6 +4,10 @@ import type { ThemeLayer } from "@radroots/theme"; export type GlyphKeyCurrency = `dollar` | `eur`; export type GlyphKey = | + `users-three` | + `note-blank` | + `user-circle-plus` | + `user-circle` | `receipt` | `invoice` | `note` | diff --git a/apps-lib/src/lib/utils/client.ts b/apps-lib/src/lib/utils/client.ts @@ -1,5 +1,5 @@ import { goto } from "$app/navigation"; -import { app_toast, TOAST_MS, type AnchorRoute, type AppLayoutKey, type CallbackPromise, type CallbackPromiseGeneric, type GlyphKey, type IToast, type LabelFieldKind, type NavigationParamTuple, type NavigationRoute, type NavigationRouteParamKey } from "$lib"; +import { app_toast, TOAST_MS, type AnchorRoute, type AppConfigType, type AppLayoutKey, type CallbackPromise, type CallbackPromiseGeneric, type GlyphKey, type IToast, type LabelFieldKind, type NavigationParamTuple, type NavigationRoute, type NavigationRouteParamKey } from "$lib"; import type { ColorMode, ThemeKey, ThemeLayer } from "@radroots/theme"; export const sleep = async (ms: number): Promise<void> => { @@ -169,4 +169,14 @@ export const value_constrain = (regex_charset: RegExp, value: string): string => .split("") .filter((char) => regex_charset.test(char)) .join(""); -}; -\ No newline at end of file +}; + +export const parse_cfg_type = (value?: string): AppConfigType => { + switch (value) { + case `farmer`: + case `personal`: + return value; + default: + return `personal` + }; +}; diff --git a/apps-lib/src/lib/utils/routes.ts b/apps-lib/src/lib/utils/routes.ts @@ -1,6 +1,5 @@ export type NavigationRoute = | "/" - | "/farmer" | "/models/location-gcs" | "/models/nostr-profile" | "/models/nostr-profile/edit/field" @@ -10,13 +9,12 @@ export type NavigationRoute = | "/models/trade-product" | "/models/trade-product/add" | "/settings" - | "/device/error" - | "/device/init"; + | "/cfg/error" + | "/cfg/init"; export function parse_route(route: string): NavigationRoute { switch (route) { case "/": - case "/farmer": case "/models/location-gcs": case "/models/nostr-profile": case "/models/nostr-profile/edit/field": @@ -26,8 +24,8 @@ export function parse_route(route: string): NavigationRoute { case "/models/trade-product": case "/models/trade-product/add": case "/settings": - case "/device/error": - case "/device/init": + case "/cfg/error": + case "/cfg/init": return route; default: return "/"; diff --git a/apps-lib/src/lib/utils/time.ts b/apps-lib/src/lib/utils/time.ts @@ -8,17 +8,17 @@ const time_fmt: Record<string, DateTimeFormatOptions> = { time_24: DateTime.TIME_24_SIMPLE }; -export function time_fmt_epoch_s(locale: string, epoch_s: number | undefined, fmt_key: keyof typeof time_fmt = `default`): string { +export function time_fmt_epoch_s(epoch_s: number | undefined, fmt_key: keyof typeof time_fmt = `default`): string { const dt = DateTime.fromSeconds(epoch_s); if (!dt.isValid) return ``; - const time = dt.setLocale(locale).toLocaleString(time_fmt[fmt_key]); + const time = dt.setLocale(get(locale)).toLocaleString(time_fmt[fmt_key]); return time; }; -export function time_iso(locale: string, iso: string, fmt_key: keyof typeof time_fmt = `default`): string { +export function time_iso(iso: string, fmt_key: keyof typeof time_fmt = `default`): string { const dt = DateTime.fromISO(iso); if (!dt.isValid) return ``; - const time = dt.setLocale(locale).toLocaleString(time_fmt[fmt_key]); + const time = dt.setLocale(get(locale)).toLocaleString(time_fmt[fmt_key]); return time; };