app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 9e4ca5e5a4076049b24c3c193f9e3c767b7512a5
parent 8ca0920ad497be22e4dd3f7303d877d7d2ad2bb9
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Sat,  7 Dec 2024 22:01:30 +0000

Add `/farm/land` and `/farm/land/add` routes. Edit `/settings` layout. Add map point select component. Add/edit styles.

Diffstat:
Asrc/lib/components/map_point_select.svelte | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/(app)/+page.svelte | 44+++++++-------------------------------------
Asrc/routes/(app)/farm/land/+page.svelte | 38++++++++++++++++++++++++++++++++++++++
Asrc/routes/(app)/farm/land/add/+page.svelte | 337+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/(app)/settings/+page.svelte | 22++++++++--------------
Mtailwind.config.ts | 1+
6 files changed, 471 insertions(+), 51 deletions(-)

diff --git a/src/lib/components/map_point_select.svelte b/src/lib/components/map_point_select.svelte @@ -0,0 +1,80 @@ +<script lang="ts"> + import { geoc } from "$lib/client"; + import { cfg } from "$lib/conf"; + import type { GeocoderReverseResult } from "@radroots/geocoder"; + import { app_thc } from "@radroots/svelte-lib"; + import { MapLibre, Marker } from "@radroots/svelte-maplibre"; + import type { GeolocationCoordinatesPoint } from "@radroots/utils"; + import MapMarkerDot from "./map_marker_dot.svelte"; + import MapPopupPointGeolocation from "./map_popup_point_geolocation.svelte"; + + export let map_point: GeolocationCoordinatesPoint; + export let map_geoc: GeocoderReverseResult | undefined = undefined; + export let map_center: GeolocationCoordinatesPoint = cfg.map.coords.default; + + $: if (map_point && map_center.lat === 0 && map_center.lng === 0) { + map_center = { + lat: map_point.lat, + lng: map_point.lng - 0.065, + }; + } + + $: { + if ( + map_point && + map_center && + map_center.lat !== 0 && + map_center.lng !== 0 + ) { + (async () => { + try { + const geoc_res = await geoc.reverse({ + point: map_point, + }); + if (`results` in geoc_res && geoc_res.results.length > 0) + map_geoc = geoc_res.results[0]; + else map_geoc = undefined; + } catch (e) { + console.log(`(error) map choose location`, e); + } + })(); + } + } +</script> + +{#if map_point} + <MapLibre + center={map_center} + zoom={10} + class={`h-full w-full`} + style={cfg.map.styles.base[$app_thc]} + attributionControl={false} + > + <Marker + bind:lngLat={map_point} + draggable + on:dragend={async () => { + if (!map_point) return; + const geoc_res = await geoc.reverse({ + point: map_point, + limit: 1, + }); + if (`results` in geoc_res && geoc_res.results.length > 0) + map_geoc = geoc_res.results[0]; + }} + > + <button + class={`flex flex-row justify-center items-center transform -translate-x-[42%]`} + on:click={async () => {}} + > + <MapPopupPointGeolocation + basis={{ + point: map_point, + geoc: map_geoc, + }} + /> + </button> + <MapMarkerDot /> + </Marker> + </MapLibre> +{/if} diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte @@ -4,11 +4,11 @@ app_nostr_key, envelope_visible, EnvelopeLower, - Glyph, LayoutView, - LogoCircleSm, ls, nav_prev, + NavToolbar, + PageHeader, route, TabsFloat, } from "@radroots/svelte-lib"; @@ -32,50 +32,20 @@ </script> <LayoutView> - <div class={`flex flex-row h-12 w-full px-6 justify-between items-center`}> - <div class={`flex flex-row gap-2 justify-start items-center`}> - <LogoCircleSm /> - <p - class={`font-sansd italic font-[700] text-[1.7rem] text-layer-0-glyph`} - > - {`radroots`} - </p> - </div> - <button - class={`flex flex-row justify-center items-center`} - on:click={async () => { - await route(`/settings`); - }} - > - <Glyph - basis={{ - classes: `text-layer-0-glyph`, - dim: `lg`, - weight: `bold`, - key: `gear`, - }} - /> - </button> - </div> - <div - class={`flex flex-col w-full pt-4 px-6 gap-4 justify-center items-center`} - > - <div class={`flex flex-row w-full justify-start items-center`}> - <p class={`font-sansd font-[600] text-2xl text-layer-0-glyph`}> - {`${$ls(`common.general`)}`} - </p> - </div> + <NavToolbar /> + <PageHeader basis={{ label: `${$ls(`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={async () => { - await route(`/settings/profile`); + await route(`/farm/land`); }} > <p class={`font-sans font-[700] text-xl text-layer-0-glyph capitalize tracking-wider opacity-active`} > - {`${$ls(`common.profile`)}`} + {`${$ls(`common.farm_land`)}`} </p> </button> </div> diff --git a/src/routes/(app)/farm/land/+page.svelte b/src/routes/(app)/farm/land/+page.svelte @@ -0,0 +1,38 @@ +<script lang="ts"> + import { + Fill, + LayoutView, + NavToolbar, + PageHeader, + TabsFloat, + ls, + route, + } from "@radroots/svelte-lib"; +</script> + +<LayoutView> + <NavToolbar /> + <PageHeader basis={{ label: `${$ls(`common.farm_land`)}` }} /> + <div class={`flex flex-col w-full px-4 justify-start items-center`}> + <button + class={`group flex flex-row h-[7rem] w-full px-8 justify-start items-center bg-layer-1-surface layer-1-active-surface round-36 el-re`} + on:click={async () => { + await route(`/farm/land/add`); + }} + > + <div + class={`flex flex-col h-[5rem] w-[5rem] justify-start items-center bg-layer-2-surface round-24 bg-repeat heropattern-topography-white`} + > + <Fill /> + </div> + <div class={`flex flex-row flex-grow justify-center items-center`}> + <p + class={`font-sans font-[500] text-[1.4rem] text-layer-0-glyph capitalize opacity-active el-re`} + > + {`${$ls(`icu.add_*`, { value: `${$ls(`common.land_plot`)}` })}`} + </p> + </div> + </button> + </div> +</LayoutView> +<TabsFloat /> diff --git a/src/routes/(app)/farm/land/add/+page.svelte b/src/routes/(app)/farm/land/add/+page.svelte @@ -0,0 +1,337 @@ +<script lang="ts"> + import { dialog, geol } from "$lib/client"; + import MapPointSelect from "$lib/components/map_point_select.svelte"; + import type { GeocoderReverseResult } from "@radroots/geocoder"; + import { + ButtonGlyphPrimary, + Fade, + InputElement, + LayoutView, + NavToolbar, + PageHeader, + carousel_dec, + carousel_inc, + carousel_index, + carousel_init, + fmt_geol_latitude, + fmt_geol_longitude, + fmt_id, + ls, + } from "@radroots/svelte-lib"; + import { type GeolocationCoordinatesPoint, regex } from "@radroots/utils"; + import { onMount } from "svelte"; + + let view_init: View = `c_1`; + type View = `c_1`; + let view: View = view_init; + + let _map_point: GeolocationCoordinatesPoint | undefined = undefined; + let _map_geoc: GeocoderReverseResult | undefined = undefined; + + onMount(async () => { + try { + await init_page(); + } catch (e) { + } finally { + } + }); + + const init_page = async (): Promise<void> => { + try { + carousel_init(1); + const geolc = await geol.current(); + if (`err` in geolc) { + await dialog.alert( + `${$ls(`icu.failure_*`, { value: `${$ls(`icu.reading_*`, { value: `${$ls(`common.geocode`)}`.toLowerCase() })}` })}`, + ); + return; + } + _map_point = { + lat: geolc.lat, + lng: geolc.lng, + }; + } catch (e) { + console.log(`(error) init_page `, e); + } + }; + + const handle_inc = async (): Promise<void> => { + try { + await carousel_inc(view); + } catch (e) { + console.log(`(error) handle_inc `, e); + } + }; + + const handle_dec = async (): Promise<void> => { + try { + await carousel_dec(view); + } catch (e) { + console.log(`(error) handle_dec `, e); + } + }; +</script> + +<LayoutView> + <NavToolbar /> + <PageHeader + basis={{ + label: `${$ls(`icu.add_*`, { value: `${$ls(`common.farm_land`)}` })}`, + }} + > + <div slot="option" class={`flex flex-row justify-start items-center`}> + {#if $carousel_index > 0} + <Fade> + <ButtonGlyphPrimary + basis={{ + label: `${$ls(`common.back`)}`, + callback: async () => { + await handle_dec(); + }, + }} + /> + </Fade> + {/if} + </div> + </PageHeader> + <div + data-view={`c_1`} + class={`flex flex-col h-full w-full justify-start items-center`} + > + <div + data-carousel-container={`c_1`} + class={`carousel-container flex h-full w-full`} + > + <div + data-carousel-item={`c_1`} + class={`carousel-item flex flex-col w-full justify-start items-center`} + > + <div + class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} + > + {#if _map_point} + <div + class={`flex flex-row h-[24rem] w-full justify-center items-center round-44 overflow-hidden`} + > + <MapPointSelect + bind:map_point={_map_point} + bind:map_geoc={_map_geoc} + /> + </div> + <div + class={`flex flex-col w-full pt-2 justify-center items-center`} + > + <ButtonGlyphPrimary + basis={{ + label: `${$ls(`icu.add_*`, { value: `${$ls(`common.location`)}` })}`, + callback: async () => { + if (_map_geoc) await handle_inc(); + }, + }} + /> + </div> + {/if} + </div> + </div> + <div + data-carousel-item={`c_1`} + class={`carousel-item flex flex-col w-full justify-start items-center`} + > + <div + class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} + > + <div + class={`flex flex-col h-[24rem] w-full px-2 gap-4 justify-start items-center`} + > + {#if _map_geoc && _map_point} + <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-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`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`} + > + {`${_map_geoc.name}, ${_map_geoc.admin1_id}, ${_map_geoc.country_name}`} + </p> + </div> + </div> + <div + class={`flex flex-col w-full gap-1 justify-start items-start`} + > + <p + class={`font-sansd text-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`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`} + > + {`${fmt_geol_latitude( + _map_point.lat, + `d`, + 4, + )}, ${fmt_geol_longitude( + _map_point.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-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`common.farm`)}/${`${$ls(`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`} + > + <InputElement + basis={{ + id: fmt_id(`farm_estate`), + sync: true, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + placeholder: `Name of farm or estate`, + field: { + charset: regex.description, + validate: regex.description_ch, + 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-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`common.area`)} (ac.)`} + </p> + </div> + <div + class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`} + > + <InputElement + basis={{ + id: fmt_id(`area`), + sync: true, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + placeholder: `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 justify-start items-center`} + > + <p + class={`font-sansd text-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`common.elevation`)}`} + </p> + </div> + <div + class={`flex flex-row h-12 w-full justify-start items-center border-y-line border-layer-0-surface-edge`} + > + <InputElement + basis={{ + id: fmt_id(`elevation`), + sync: true, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + placeholder: `Approx. 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-trellisTitle text-layer-0-glyph-label uppercase`} + > + {`${$ls(`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`} + > + <InputElement + basis={{ + id: fmt_id(`climate`), + sync: true, + layer: 0, + classes: `h-10 placeholder:text-[1.1rem]`, + placeholder: `Local climate`, + field: { + charset: regex.description, + validate: regex.description_ch, + validate_keypress: true, + }, + }} + /> + </div> + </div> + <div + class={`flex flex-row w-full pt-2 justify-center items-center`} + > + <ButtonGlyphPrimary + basis={{ + label: `${$ls(`icu.add_*`, { value: `${$ls(`common.location`)}` })}`, + callback: async () => { + if (_map_geoc) await handle_inc(); + }, + }} + /> + </div> + {/if} + </div> + </div> + </div> + </div> + </div> +</LayoutView> diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/+page.svelte @@ -7,7 +7,8 @@ LayoutTrellis, LayoutView, ls, - Nav, + NavToolbar, + PageHeader, route, Trellis, } from "@radroots/svelte-lib"; @@ -15,6 +16,12 @@ </script> <LayoutView> + <NavToolbar /> + <PageHeader + basis={{ + label: `${$ls(`common.settings`)}`, + }} + /> <LayoutTrellis> <Trellis basis={{ @@ -414,16 +421,3 @@ /> </LayoutTrellis> </LayoutView> -<Nav - basis={{ - prev: { - label: `${$ls(`common.home`)}`, - route: `/`, - }, - title: { - label: { - value: `${$ls(`common.settings`)}`, - }, - }, - }} -/> diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -116,6 +116,7 @@ const config: Config = { of: [`Outfit`] }, fontSize: { + guide: [`1.2rem`, { lineHeight: `1.2rem` }], line_label: [`1.3rem`, { lineHeight: `1.3rem` }], trellisTitle: [`0.8rem`, { lineHeight: `1rem`, fontWeight: 300 }], trellisTitleNote: [`0.76rem`, { lineHeight: `1rem`, fontWeight: 200 }],