web_lib

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

commit c2ce1c379d53ecab0e1053a233ea3e034eabd59b
parent 10d5f1a3cbf25e0dd30c6e7925bf384333594ca3
Author: triesap <triesap@radroots.dev>
Date:   Thu, 20 Nov 2025 15:44:34 +0000

apps-lib-pwa: migrate and refactor farms view from `@radroots/apps-lib`

Diffstat:
Aapps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte | 45+++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/button/button-label-dashed.svelte | 22++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/farm/farms-display-li-el.svelte | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/map/map-marker-area.svelte | 47+++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/map/map.svelte | 42++++++++++++++++++++++++++++++++++++++++++
Mapps-lib-pwa/src/lib/components/navigation/page-header.svelte | 2+-
Mapps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte | 2+-
Mapps-lib-pwa/src/lib/index.ts | 9+++++++++
Mapps-lib-pwa/src/lib/types/app.ts | 2+-
Dapps-lib-pwa/src/lib/types/components.ts | 33---------------------------------
Aapps-lib-pwa/src/lib/types/views/farm.ts | 18++++++++++++++++++
Aapps-lib-pwa/src/lib/views/farms/farms.svelte | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 444 insertions(+), 36 deletions(-)

diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph-simple.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import { + type GlyphKey, + type IClOpt, + fmt_cl, + Glyph, + } from "@radroots/apps-lib"; + import { type CallbackPromise } from "@radroots/utils"; + + let { + basis, + }: { + basis: IClOpt & { + kind?: `primary` | `neutral`; + label: string; + callback: CallbackPromise; + glyph?: GlyphKey; + }; + } = $props(); + + const classes_kind = $derived( + basis.kind === `neutral` ? `text-ly0-gl-shade` : `text-ly0-gl-hl`, + ); +</script> + +<button + class={`${fmt_cl(basis.classes)} group flex flex-row justify-center items-center`} + onclick={basis.callback} +> + {#if basis.glyph} + <Glyph + basis={{ + classes: `${classes_kind}`, + dim: `sm+`, + + key: basis.glyph, + }} + /> + {/if} + <p + class={`font-sans font-[600] text-line_label ${classes_kind} opacity-active`} + > + {basis.label} + </p> +</button> diff --git a/apps-lib-pwa/src/lib/components/button/button-label-dashed.svelte b/apps-lib-pwa/src/lib/components/button/button-label-dashed.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import type { ICb } from "@radroots/apps-lib"; + + let { + basis, + }: { + basis: ICb & { + label: string; + }; + } = $props(); +</script> + +<button + class={`flex flex-row h-line_button w-full justify-center items-center`} + onclick={async () => { + await basis.callback(); + }} +> + <p class={`font-sans font-[500] text-lg text-ly0-gl-hl tracking-wide`}> + {`- ${basis.label} -`} + </p> +</button> diff --git a/apps-lib-pwa/src/lib/components/farm/farms-display-li-el.svelte b/apps-lib-pwa/src/lib/components/farm/farms-display-li-el.svelte @@ -0,0 +1,109 @@ +<script lang="ts"> + import MapMarkerArea from "$lib/components/map/map-marker-area.svelte"; + import Map from "$lib/components/map/map.svelte"; + import type { FarmExtended } from "$lib/types/views/farm"; + import { get_context } from "@radroots/apps-lib"; + import { + fmt_geolocation_address, + geol_lat_fmt, + geol_lng_fmt, + parse_geol_point_tup, + parse_tup_geop_point, + type CallbackPromiseGeneric, + type GeolocationPointTuple, + } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls, locale } = get_context(`lib`); + + let { + basis, + on_handle_farm_view, + }: { + basis: FarmExtended; + on_handle_farm_view: CallbackPromiseGeneric<string>; + } = $props(); + + let map: maplibregl.Map | undefined = $state(undefined); + let map_center: GeolocationPointTuple = $state([0, 0]); + + onMount(async () => { + if (basis.location?.point) + map_center = parse_geol_point_tup(basis.location.point); + if (map) map.setCenter(map_center); + }); + + const map_geop = $derived(parse_tup_geop_point(map_center)); + + const farm_addr_fmt = $derived( + basis.location?.address + ? fmt_geolocation_address(basis.location.address) + : ``, + ); + + const farm_geop_lat = $derived( + basis.location?.point + ? geol_lat_fmt(basis.location.point.lat, `dms`, $locale, 3) + : ``, + ); + + const farm_geop_lng = $derived( + basis.location?.point + ? geol_lng_fmt(basis.location.point.lng, `dms`, $locale, 3) + : ``, + ); +</script> + +<button + class={`z-10 relative flex flex-col w-full p-4 gap-3 justify-start items-center bg-ly1 ly1-active-raise-less ly1-active-ring rounded-3xl el-re`} + onclick={async () => { + if (basis.farm.id) await on_handle_farm_view(basis.farm.id); + }} +> + <div class={`flex flex-col w-full gap-2 justify-center items-center`}> + <div class={`flex flex-row w-full justify-between items-center`}> + <p class={`font-sans font-[500] text-3xl text-ly0-gl`}> + {basis.farm.name} + </p> + + <div + class={`flex flex-row h-6 px-2 py-1 justify-center items-center bg-lime-400 rounded-lg`} + > + <p class={`font-sans font-[700] text-white`}> + {`${$ls(`common.farm`)}`} + </p> + </div> + </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-[500] text-lg text-ly0-gl`}> + {farm_addr_fmt} + </p> + </div> + <div class={`flex flex-row w-full justify-start items-center`}> + <p class={`font-sans font-[500] text-lg text-ly0-gl`}> + {farm_geop_lat && farm_geop_lng + ? `${farm_geop_lat}, ${farm_geop_lng}` + : ``} + </p> + </div> + </div> + </div> + <div + class={`flex flex-col h-[16rem] w-full justify-center items-center rounded-2xl overflow-hidden`} + > + <Map + bind:map + basis={{ + interactive: false, + }} + > + <MapMarkerArea + {map_geop} + basis={{ + no_drag: true, + }} + /> + </Map> + </div> +</button> diff --git a/apps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte b/apps-lib-pwa/src/lib/components/map/map-marker-area-display.svelte @@ -0,0 +1,57 @@ +<script lang="ts"> + import { Fade, Glyph, type IBasisOpt } from "@radroots/apps-lib"; + + let { + basis = undefined, + }: { + basis?: IBasisOpt<{ + primary: string; + admin: string; + country: string; + }>; + } = $props(); +</script> + +{#if basis} + <Fade + basis={{ + classes: `flex-col w-full justify-center items-start`, + }} + > + <div + class={`flex flex-col w-fit px-5 py-[10px] justify-start items-start bg-ly1 rounded-3xl shadow-lg`} + > + <div class={`flex 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-[600] text-[0.95rem] text-ly2-gl`} + > + {basis.primary} + </p> + <Glyph + basis={{ + classes: `text-ly2-gl -translate-y-[2px]`, + dim: `xs`, + + key: `map-pin-simple`, + }} + /> + </div> + <div + class={`flex flex-row w-full gap-1 justify-start items-center`} + > + <p + class={`font-sans font-[600] text-[0.95rem] tracking-tight text-ly2-gl`} + > + {`${basis.admin},`} + </p> + <p + class={`font-sans font-[600] text-[0.95rem] tracking-tight text-ly2-gl`} + > + {`${basis.country}`} + </p> + </div> + </div> + </div> + </Fade> +{/if} diff --git a/apps-lib-pwa/src/lib/components/map/map-marker-area.svelte b/apps-lib-pwa/src/lib/components/map/map-marker-area.svelte @@ -0,0 +1,47 @@ +<script lang="ts"> + import type { IMapMarkerArea } from "$lib/types/components/lib"; + import { get_context } from "@radroots/apps-lib"; + import { + type GeocoderReverseResult, + type GeolocationPoint, + } from "@radroots/utils"; + import { Marker, Popup } from "svelte-maplibre"; + import MapMarkerAreaDisplay from "./map-marker-area-display.svelte"; + + const { lc_geocode } = get_context(`lib`); + + let { + basis, + map_geop = $bindable(), + map_geoc = $bindable(undefined), + }: { + basis: IMapMarkerArea; + map_geop: GeolocationPoint; + map_geoc?: GeocoderReverseResult | undefined; + } = $props(); +</script> + +<Marker + bind:lngLat={map_geop} + draggable={!basis.no_drag} + class={`flex flex-row h-[100px] w-[100px] bg-blue-400/20 border-[2px] border-white justify-center items-center rounded-full shadow-lg`} + ondragend={async () => { + if (!map_geop) return; + const geoc = await lc_geocode(map_geop); + if (geoc) map_geoc = geoc; + }} +> + {#if basis.show_display} + <Popup open={basis.show_display} offset={[0, -55]}> + <MapMarkerAreaDisplay + basis={map_geoc + ? { + primary: map_geoc.name, + admin: map_geoc.admin1_name, + country: map_geoc.country_name, + } + : undefined} + /> + </Popup> + {/if} +</Marker> diff --git a/apps-lib-pwa/src/lib/components/map/map.svelte b/apps-lib-pwa/src/lib/components/map/map.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + import { cfg_map } from "$lib/utils/map"; + import { type IClOpt, fmt_cl, theme_mode } from "@radroots/apps-lib"; + import type { Snippet } from "svelte"; + import { MapLibre } from "svelte-maplibre"; + + let { + basis = undefined, + map = $bindable(undefined), + children, + }: { + basis?: IClOpt & { + interactive?: boolean; + zoom_click_off?: boolean; + }; + map?: maplibregl.Map; + interactive?: boolean; + children: Snippet; + } = $props(); + + const interactive = $derived( + typeof basis?.interactive === `boolean` ? basis?.interactive : true, + ); + + const zoomOnDoubleClick = $derived( + typeof basis?.zoom_click_off === `boolean` + ? basis?.zoom_click_off + : true, + ); +</script> + +<MapLibre + bind:map + class="{fmt_cl(basis?.classes)} relative h-full w-full" + zoom={10} + style={cfg_map.styles.base[$theme_mode]} + attributionControl={false} + {interactive} + {zoomOnDoubleClick} +> + {@render children()} +</MapLibre> diff --git a/apps-lib-pwa/src/lib/components/navigation/page-header.svelte b/apps-lib-pwa/src/lib/components/navigation/page-header.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { app_lo, ph_blur } from "$lib/stores/app"; - import type { IPageHeader } from "$lib/types/components"; + import type { IPageHeader } from "$lib/types/components/lib"; import { callback_route, Flex } from "@radroots/apps-lib"; import type { Snippet } from "svelte"; import { fade } from "svelte/transition"; diff --git a/apps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte b/apps-lib-pwa/src/lib/components/navigation/page-toolbar.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { goto } from "$app/navigation"; import { app_lo } from "$lib/stores/app"; - import type { IPageToolbar } from "$lib/types/components"; + import type { IPageToolbar } from "$lib/types/components/lib"; import { type IBasisOpt, Glyph } from "@radroots/apps-lib"; import type { Snippet } from "svelte"; import LogoCircleSm from "../lib/logo-circle-sm.svelte"; diff --git a/apps-lib-pwa/src/lib/index.ts b/apps-lib-pwa/src/lib/index.ts @@ -1,6 +1,9 @@ +export { default as ButtonGlyphSimple } from "./components/button/button-glyph-simple.svelte"; +export { default as ButtonLabelDashed } from "./components/button/button-label-dashed.svelte"; export { default as ButtonLayoutPair } from "./components/button/button-layout-pair.svelte"; export { default as ButtonLayout } from "./components/button/button-layout.svelte"; export { default as ButtonSimple } from "./components/button/button-simple.svelte"; +export { default as FarmsDisplayLiEl } from "./components/farm/farms-display-li-el.svelte"; export { default as EntryLine } from "./components/form/entry-line.svelte"; export { default as EntryWrap } from "./components/form/entry-wrap.svelte"; export { default as LayoutPage } from "./components/layout/layout-page.svelte"; @@ -9,9 +12,15 @@ export { default as LayoutWindow } from "./components/layout/layout-window.svelt export { default as Css } from "./components/lib/css.svelte"; export { default as InputValue } from "./components/lib/input-value.svelte"; export { default as LoadSymbol } from "./components/lib/load-symbol.svelte"; +export { default as LogoCircleSm } from "./components/lib/logo-circle-sm.svelte"; export { default as LogoCircle } from "./components/lib/logo-circle.svelte"; +export { default as LogoLetters } from "./components/lib/logo-letters.svelte"; export { default as SelectMenu } from "./components/lib/select-menu.svelte"; +export { default as MapMarkerAreaDisplay } from "./components/map/map-marker-area-display.svelte"; +export { default as MapMarkerArea } from "./components/map/map-marker-area.svelte"; +export { default as Map } from "./components/map/map.svelte"; export { default as NavigationTabs } from "./components/navigation/navigation-tabs.svelte"; export { default as PageHeader } from "./components/navigation/page-header.svelte"; export { default as PageToolbar } from "./components/navigation/page-toolbar.svelte"; +export { default as Farms } from "./views/farms/farms.svelte"; export { default as Home } from "./views/root/home.svelte"; diff --git a/apps-lib-pwa/src/lib/types/app.ts b/apps-lib-pwa/src/lib/types/app.ts @@ -1,4 +1,4 @@ -import type { TangleDatabaseBackup } from "@radroots/tangle-client"; +import type { TangleDatabaseBackup } from "@radroots/client/tangle"; import type { IdbClientConfig } from "@radroots/utils"; export type AppConfigRole = `farmer` | `personal` diff --git a/apps-lib-pwa/src/lib/types/components.ts b/apps-lib-pwa/src/lib/types/components.ts @@ -1,33 +0,0 @@ -import type { CallbackRoute, GeometryScreenPositionHorizontal, ICb, ICbOpt, IDisabledOpt, IGlyph, IGlyphKey, ILoadingOpt, ILyOpt } from "@radroots/apps-lib"; -import type { CallbackPromise } from "@radroots/utils"; - -export type IButtonSimple = ILyOpt & { - label: string; - callback: CallbackPromise; - allow_propogation?: boolean; -}; - -export type IPageHeader<T extends string> = { - label: string; - callback_route?: CallbackRoute<T>; -}; - -export type IPageToolbar<T extends string> = ICbOpt & { - header?: IPageHeader<T>; -}; - -export type IMapMarkerArea = { - show_display?: boolean; - no_drag?: boolean; -} - -export type IGlyphCircle = { - classes_wrap: string; - glyph: IGlyph -}; - -export type IFloatPage = { - posx: Omit<GeometryScreenPositionHorizontal, "center">; -}; - -export type IButtonNavRound = ICb & IDisabledOpt & ILoadingOpt & IGlyphKey; diff --git a/apps-lib-pwa/src/lib/types/views/farm.ts b/apps-lib-pwa/src/lib/types/views/farm.ts @@ -0,0 +1,17 @@ +import type { Farm } from "@radroots/tangle-schema-bindings"; +import type { LocationBasis } from "@radroots/utils"; + +export type FarmExtended = { + farm: Farm; + location?: LocationBasis; + lots?: FarmLotBasis[]; +}; + + +export type FarmLotBasis = { + id: string; + location?: LocationBasis; +}; +export type IViewFarmsData = { + list: FarmExtended[]; +}; +\ No newline at end of file diff --git a/apps-lib-pwa/src/lib/views/farms/farms.svelte b/apps-lib-pwa/src/lib/views/farms/farms.svelte @@ -0,0 +1,92 @@ +<script lang="ts"> + import ButtonGlyphSimple from "$lib/components/button/button-glyph-simple.svelte"; + import ButtonLabelDashed from "$lib/components/button/button-label-dashed.svelte"; + import FarmsDisplayLiEl from "$lib/components/farm/farms-display-li-el.svelte"; + import LayoutPage from "$lib/components/layout/layout-page.svelte"; + import LayoutView from "$lib/components/layout/layout-view.svelte"; + import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; + import type { IViewBasis } from "$lib/types/views"; + import type { IViewFarmsData } from "$lib/types/views/farm"; + import { + Fade, + get_context, + idb_kv_init_page, + type CallbackRoute, + } from "@radroots/apps-lib"; + import { + handle_err, + type CallbackPromise, + type CallbackPromiseGeneric, + } from "@radroots/utils"; + import { onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + }: { + basis: IViewBasis<{ + data?: IViewFarmsData; + callback_route?: CallbackRoute<string>; + on_handle_farm_add: CallbackPromise; + on_handle_farm_view: CallbackPromiseGeneric<string>; + }>; + } = $props(); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +<LayoutView> + <PageToolbar + basis={{ + header: { + label: `${$ls(`common.farms`)}`, + callback_route: basis.callback_route, + }, + }} + > + {#snippet header_option()} + {#if basis.data?.list.length} + <Fade> + <ButtonGlyphSimple + basis={{ + label: `${$ls(`icu.add_*`, { + value: `${$ls(`common.farm`)}`, + })}`, + callback: async () => { + await basis.on_handle_farm_add(); + }, + }} + /> + </Fade> + {/if} + {/snippet} + </PageToolbar> + <LayoutPage> + {#if basis.data} + {#if basis.data?.list.length} + {#each basis.data?.list || [] as li} + <FarmsDisplayLiEl + basis={li} + on_handle_farm_view={basis.on_handle_farm_view} + /> + {/each} + {:else} + <ButtonLabelDashed + basis={{ + label: `Add farm`, + callback: async () => { + await basis.on_handle_farm_add(); + }, + }} + /> + {/if} + {/if} + </LayoutPage> +</LayoutView>