app

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

commit 28294772b1d1b97f1e84b57712a97e063e8fdb14
parent 5934eba13f9351afa291a9909f37d3cafbe6627b
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Sat,  2 Nov 2024 10:55:50 +0000

Edit `core` and `tauri` crates adding model `nostr_profile_relay` lib functions, command handlers, SQL up migration. Update `/cfg/init` database client. Edit root layout load. Edit home page envelope. Edit models routes. Edit image upload components. Edit map components. Add numbers config. Edit kv utils. Add trade product utils. Edit @layer base, font css imports. Add/edit styles.

Diffstat:
Mcrates/core/src/models/location_gcs.rs | 2+-
Mcrates/core/src/models/mod.rs | 3++-
Mcrates/core/src/models/nostr_profile.rs | 2+-
Acrates/core/src/models/nostr_profile_relay.rs | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/core/src/models/nostr_relay.rs | 2+-
Mcrates/core/src/models/trade_product.rs | 18+++++++++---------
Mcrates/tauri/capabilities/default.json | 1+
Acrates/tauri/migrations/0005_nostr_profile_relay.sql | 8++++++++
Mcrates/tauri/src/lib.rs | 3+++
Mcrates/tauri/src/models/mod.rs | 1+
Acrates/tauri/src/models/nostr_profile_relay.rs | 32++++++++++++++++++++++++++++++++
Msrc/app.css | 8++------
Dsrc/global.d.ts | 6------
Msrc/lib/client.ts | 4++--
Msrc/lib/components/image_upload_display.svelte | 64++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/lib/components/image_upload_row.svelte | 22++++++++++------------
Msrc/lib/components/map_choose_location.svelte | 132+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/lib/components/map_popup_location_info.svelte | 12++++++++----
Msrc/lib/conf.ts | 3+++
Msrc/lib/utils/kv.ts | 51+++++++++++++--------------------------------------
Asrc/lib/utils/trade_product.ts | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/(app)/+page.svelte | 5++---
Msrc/routes/(app)/models/nostr-profile/edit/field/+page.svelte | 3+++
Msrc/routes/(app)/models/trade-product/add/+page.svelte | 24+++++++++++++++---------
Msrc/routes/(cfg)/cfg/init/+page.svelte | 17+++++------------
Msrc/routes/+layout.svelte | 4++--
Msrc/routes/+layout.ts | 5+++--
Mtailwind.config.ts | 5++---
28 files changed, 381 insertions(+), 209 deletions(-)

diff --git a/crates/core/src/models/location_gcs.rs b/crates/core/src/models/location_gcs.rs @@ -106,7 +106,7 @@ pub type ILocationGcsDeleteResolve = (); pub type ILocationGcsUpdate = ILocationGcsQueryUpdate; pub type ILocationGcsUpdateResolve = (); -fn location_gcs_query_bind_values(opts: LocationGcsQueryBindValues) -> IModelsQueryBindValueTuple { +pub fn location_gcs_query_bind_values(opts: LocationGcsQueryBindValues) -> IModelsQueryBindValueTuple { match opts { LocationGcsQueryBindValues::Id(id) => ("id".to_string(), id), LocationGcsQueryBindValues::Geohash(geohash) => ("geohash".to_string(), geohash), diff --git a/crates/core/src/models/mod.rs b/crates/core/src/models/mod.rs @@ -1,4 +1,5 @@ pub mod location_gcs; -pub mod trade_product; pub mod nostr_profile; +pub mod nostr_profile_relay; pub mod nostr_relay; +pub mod trade_product; diff --git a/crates/core/src/models/nostr_profile.rs b/crates/core/src/models/nostr_profile.rs @@ -106,7 +106,7 @@ pub type INostrProfileDeleteResolve = (); pub type INostrProfileUpdate = INostrProfileQueryUpdate; pub type INostrProfileUpdateResolve = (); -fn nostr_profile_query_bind_values(opts: NostrProfileQueryBindValues) -> IModelsQueryBindValueTuple { +pub fn nostr_profile_query_bind_values(opts: NostrProfileQueryBindValues) -> IModelsQueryBindValueTuple { match opts { NostrProfileQueryBindValues::Id(id) => ("id".to_string(), id), NostrProfileQueryBindValues::PublicKey(public_key) => ("public_key".to_string(), public_key), diff --git a/crates/core/src/models/nostr_profile_relay.rs b/crates/core/src/models/nostr_profile_relay.rs @@ -0,0 +1,51 @@ +use crate::{ + error::ModelError, + models::nostr_profile::{nostr_profile_query_bind_values, NostrProfileQueryBindValues}, + models::nostr_relay::{nostr_relay_query_bind_values, NostrRelayQueryBindValues}, +}; + +pub type INostrProfileRelayRelationResolve = bool; + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct INostrProfileRelayRelation { + pub nostr_profile: NostrProfileQueryBindValues, + pub nostr_relay: NostrRelayQueryBindValues, +} + +pub async fn lib_model_nostr_profile_relay_set( + db: &sqlx::Pool<sqlx::Sqlite>, + opts: INostrProfileRelayRelation, +) -> Result<INostrProfileRelayRelationResolve, ModelError> { + let (bv_np_k, bv_np) = nostr_profile_query_bind_values(opts.nostr_profile); + let (bv_nr_k, bv_nr) = nostr_relay_query_bind_values(opts.nostr_relay); + let query_vals = vec![bv_np, bv_nr]; + let query = format!("INSERT INTO nostr_profile_relay (tb_pr_rl_0, tb_pr_rl_1) VALUES ((SELECT id FROM nostr_profile WHERE {} = ?1), (SELECT id FROM nostr_relay WHERE {} = ?2));", bv_np_k, bv_nr_k); + let mut query_builder = sqlx::query(&query); + for value in query_vals.iter() { + query_builder = query_builder.bind(value); + } + query_builder + .execute(db) + .await + .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; + Ok(true) +} + +pub async fn lib_model_nostr_profile_relay_unset( + db: &sqlx::Pool<sqlx::Sqlite>, + opts: INostrProfileRelayRelation, +) -> Result<INostrProfileRelayRelationResolve, ModelError> { + let (bv_np_k, bv_np) = nostr_profile_query_bind_values(opts.nostr_profile); + let (bv_nr_k, bv_nr) = nostr_relay_query_bind_values(opts.nostr_relay); + let query_vals = vec![bv_np, bv_nr]; + let query = format!("DELETE FROM nostr_profile_relay WHERE tb_pr_rl_0 = (SELECT id FROM nostr_profile WHERE {} = ?1) AND tb_pr_rl_1 = (SELECT id FROM nostr_relay WHERE {} = ?2);", bv_np_k, bv_nr_k); + let mut query_builder = sqlx::query(&query); + for value in query_vals.iter() { + query_builder = query_builder.bind(value); + } + query_builder + .execute(db) + .await + .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; + Ok(true) +} diff --git a/crates/core/src/models/nostr_relay.rs b/crates/core/src/models/nostr_relay.rs @@ -106,7 +106,7 @@ pub type INostrRelayDeleteResolve = (); pub type INostrRelayUpdate = INostrRelayQueryUpdate; pub type INostrRelayUpdateResolve = (); -fn nostr_relay_query_bind_values(opts: NostrRelayQueryBindValues) -> IModelsQueryBindValueTuple { +pub fn nostr_relay_query_bind_values(opts: NostrRelayQueryBindValues) -> IModelsQueryBindValueTuple { match opts { NostrRelayQueryBindValues::Id(id) => ("id".to_string(), id), NostrRelayQueryBindValues::Url(url) => ("url".to_string(), url), diff --git a/crates/core/src/models/trade_product.rs b/crates/core/src/models/trade_product.rs @@ -13,14 +13,14 @@ pub struct TradeProduct { key: String, title: String, summary: String, - process: Option<String>, - lot: Option<String>, - profile: Option<String>, + process: String, + lot: String, + profile: String, year: i32, qty_amt: i32, qty_unit: String, qty_label: String, - qty_avail: i32, + qty_avail: Option<i32>, price_amt: f64, price_currency: String, price_qty_amt: i32, @@ -33,14 +33,14 @@ pub struct ITradeProductFields { pub key: String, pub title: String, pub summary: String, - pub process: Option<String>, - pub lot: Option<String>, - pub profile: Option<String>, + pub process: String, + pub lot: String, + pub profile: String, pub year: String, pub qty_amt: String, pub qty_unit: String, pub qty_label: String, - pub qty_avail: String, + pub qty_avail: Option<String>, pub price_amt: String, pub price_currency: String, pub price_qty_amt: String, @@ -123,7 +123,7 @@ pub type ITradeProductDeleteResolve = (); pub type ITradeProductUpdate = ITradeProductQueryUpdate; pub type ITradeProductUpdateResolve = (); -fn trade_product_query_bind_values(opts: TradeProductQueryBindValues) -> IModelsQueryBindValueTuple { +pub fn trade_product_query_bind_values(opts: TradeProductQueryBindValues) -> IModelsQueryBindValueTuple { match opts { TradeProductQueryBindValues::Id(id) => ("id".to_string(), id), } diff --git a/crates/tauri/capabilities/default.json b/crates/tauri/capabilities/default.json @@ -9,6 +9,7 @@ "core:default", "dialog:default", "fs:default", + "fs:allow-stat", "geolocation:allow-check-permissions", "geolocation:allow-get-current-position", "geolocation:allow-request-permissions", diff --git a/crates/tauri/migrations/0005_nostr_profile_relay.sql b/crates/tauri/migrations/0005_nostr_profile_relay.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS nostr_profile_relay ( + tb_pr_rl_0 CHAR(36), + tb_pr_rl_1 CHAR(36), + FOREIGN KEY (tb_pr_rl_0) REFERENCES nostr_profile(id) ON DELETE CASCADE, + FOREIGN KEY (tb_pr_rl_1) REFERENCES nostr_relay(id) ON DELETE CASCADE, + PRIMARY KEY (tb_pr_rl_0, tb_pr_rl_1) +); +\ No newline at end of file diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs @@ -12,6 +12,7 @@ use models::{ model_nostr_profile_add, model_nostr_profile_delete, model_nostr_profile_get, model_nostr_profile_update, }, + nostr_profile_relay::{model_nostr_profile_relay_set, model_nostr_profile_relay_unset}, nostr_relay::{ model_nostr_relay_add, model_nostr_relay_delete, model_nostr_relay_get, model_nostr_relay_update, @@ -69,6 +70,8 @@ pub fn run() { model_nostr_relay_get, model_nostr_relay_delete, model_nostr_relay_update, + model_nostr_profile_relay_set, + model_nostr_profile_relay_unset ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/crates/tauri/src/models/mod.rs b/crates/tauri/src/models/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod location_gcs; pub(crate) mod nostr_profile; +pub(crate) mod nostr_profile_relay; pub(crate) mod nostr_relay; pub(crate) mod trade_product; diff --git a/crates/tauri/src/models/nostr_profile_relay.rs b/crates/tauri/src/models/nostr_profile_relay.rs @@ -0,0 +1,32 @@ +use crate::radroots::Radroots; +use radroots_core::{ + models::nostr_profile_relay::{lib_model_nostr_profile_relay_set, lib_model_nostr_profile_relay_unset, INostrProfileRelayRelation, INostrProfileRelayRelationResolve}, +}; + +#[tauri::command] +pub async fn model_nostr_profile_relay_set( + state: tauri::State<'_, Radroots>, + opts: INostrProfileRelayRelation, +) -> Result<INostrProfileRelayRelationResolve, String> { + match lib_model_nostr_profile_relay_set(&state.db, opts).await { + Ok(result) => Ok(result), + Err(e) => { + println!("ERROR {}", e); + Err(e.to_string()) + } + } +} + +#[tauri::command] +pub async fn model_nostr_profile_relay_unset( + state: tauri::State<'_, Radroots>, + opts: INostrProfileRelayRelation, +) -> Result<INostrProfileRelayRelationResolve, String> { + match lib_model_nostr_profile_relay_unset(&state.db, opts).await { + Ok(result) => Ok(result), + Err(e) => { + println!("ERROR {}", e); + Err(e.to_string()) + } + } +} diff --git a/src/app.css b/src/app.css @@ -12,17 +12,13 @@ @import "/static/webfonts/archivo/styles.css"; @import "/static/webfonts/space-grotesk/styles.css"; @import "/static/webfonts/spartan/styles.css"; +@import "/static/webfonts/outfit/styles.css"; @tailwind base; @tailwind components; @tailwind utilities; -@layer base { - .cds--cc--chart-wrapper { - --cds-charts-font-family: Circular; - --cds-charts-font-family-condensed: 'Circular'; - } -} +@layer base {} @layer components { .label-sg { diff --git a/src/global.d.ts b/src/global.d.ts @@ -1,6 +0,0 @@ -declare module 'jshashes' { - export class SHA256 { - b64(input: string): string; - b64_hmac(key: string, input: string): string; - } -} diff --git a/src/lib/client.ts b/src/lib/client.ts @@ -1,8 +1,8 @@ -import { ClientNostr, TauriClientDb, TauriClientDevice, TauriClientDialog, TauriClientFs, TauriClientGeolocation, TauriClientHaptics, TauriClientHttp, TauriClientKeying, TauriClientKeystore, TauriClientLogger, TauriClientMap, TauriClientNotification, TauriClientOs, TauriClientWindow } from "@radroots/client"; +import { ClientNostr, TauriClientDatabase, TauriClientDevice, TauriClientDialog, TauriClientFs, TauriClientGeolocation, TauriClientHaptics, TauriClientHttp, TauriClientKeying, TauriClientKeystore, TauriClientLogger, TauriClientMap, TauriClientNotification, TauriClientOs, TauriClientWindow } from "@radroots/client"; import { Geocoder } from "@radroots/geocoder"; export const geoc = new Geocoder(`/geonames/geonames.db`); -export const db = new TauriClientDb(); +export const db = new TauriClientDatabase(); export const device = new TauriClientDevice(); export const dialog = new TauriClientDialog(); export const fs = new TauriClientFs(); diff --git a/src/lib/components/image_upload_display.svelte b/src/lib/components/image_upload_display.svelte @@ -4,26 +4,29 @@ Glyph, ImageBlob, type CallbackPromise, + type CallbackPromiseGeneric, } from "@radroots/svelte-lib"; export let basis: { loading: boolean; - list: { - file_path: string; - }[]; + file_paths: string[]; callback_add: CallbackPromise; + callback_edit: CallbackPromiseGeneric<number>; }; </script> <div - class={`flex flex-row h-[12rem] w-full pl-12 pr-4 gap-4 justify-start items-center`} + class={`flex flex-row h-[12rem] w-full px-4 gap-4 justify-center items-center`} > <div class={`flex flex-col h-[11rem] gap-1 justify-center items-center`}> - <div - class={`relative flex flex-col h-36 w-36 justify-center items-center rounded-full border-edge border-layer-0-glyph ${basis.list.length === 0 ? `group-active:border-layer-0-glyph/60 ` : ``} overflow-hidden delay-100 duration-300 ease-in-out transition-all`} + <button + on:click={async () => { + await basis.callback_edit(0); + }} + class={`group relative flex flex-col h-36 w-36 justify-center items-center rounded-[4.2rem] border-edge border-layer-0-glyph active:border-layer-0-glyph/60 overflow-hidden el-re`} > - {#if basis.list[0]?.file_path} - {#await fs.read_bin(basis.list[0].file_path) then file_data} + {#if basis.file_paths[0]} + {#await fs.read_bin(basis.file_paths[0]) then file_data} <ImageBlob basis={{ data: file_data, @@ -31,36 +34,49 @@ /> {/await} {/if} - <button - class={`z-10 group absolute bottom-0 flex flex-row h-8 w-full justify-center items-start bg-layer-2-surface active:bg-layer-1-surface`} - on:click={async () => {}} + <div + class={`z-10 absolute bottom-0 flex flex-row h-8 w-full justify-center items-start bg-layer-2-surface active:bg-layer-1-surface`} > <p class={`font-circ font-[500] text-[0.8rem] tracking-tight text-layer-0-glyph group-active:text-layer-0-glyph/60 el-re`} > {`Primary photo`} </p> - </button> - </div> + </div> + </button> </div> <div - class={`grid grid-cols-12 flex flex-row w-full gap-y-2 justify-start items-center`} + class={`grid grid-cols-12 flex flex-row gap-y-2 justify-start items-center`} > - {#each Array(6).fill(0) as li} + {#each Array(6).fill(0) as _, li_i} <div class={`col-span-4 flex flex-row pr-2 justify-start items-center`} > <button - class={`group flex flex-row h-[2.8rem] w-[2.8rem] justify-center items-center rounded-full border-line border-layer-0-glyph/90 active:border-layer-0-glyph/60`} - on:click={async () => {}} + class={`group flex flex-row h-[2.8rem] w-[2.8rem] justify-center items-center rounded-touch border-line border-layer-0-glyph/90 active:border-layer-0-glyph/60 overflow-hidden`} + on:click={async () => { + if (basis.file_paths[li_i + 1]) + await basis.callback_edit(li_i + 1); + else await basis.callback_add(); + }} > - <Glyph - basis={{ - classes: `text-layer-0-glyph text-layer-0-glyph/80 group-active:text-layer-0-glyph/60 el-re`, - dim: `sm`, - key: `image-square`, - }} - /> + {#if basis.file_paths[li_i + 1]} + {#await fs.read_bin(basis.file_paths[li_i + 1]) then file_data} + <ImageBlob + basis={{ + data: file_data, + }} + /> + {/await} + {:else} + <Glyph + basis={{ + classes: `text-layer-0-glyph text-layer-0-glyph/80 group-active:text-layer-0-glyph/60 el-re`, + dim: `sm`, + key: `image-square`, + }} + /> + {/if} </button> </div> {/each} diff --git a/src/lib/components/image_upload_row.svelte b/src/lib/components/image_upload_row.svelte @@ -11,32 +11,30 @@ export let basis: { loading: boolean; - list: { - file_path: string; - }[]; + file_paths: string[]; callback_add: CallbackPromise; }; </script> <div - class={`flex flex-row w-[100vw] ${basis.list.length > 1 ? `justify-start` : `justify-center`} items-center overflow-x-auto scroll-hide`} + class={`flex flex-row w-[100vw] ${basis.file_paths.length > 1 ? `justify-start` : `justify-center`} items-center overflow-x-auto scroll-hide`} > <div - class={`flex flex-row ${basis.list.length > 1 ? `px-24` : ``} space-x-4 delay-[200ms] duration-[500ms] ease-in-out transition-all`} + class={`flex flex-row ${basis.file_paths.length > 1 ? `px-24` : ``} space-x-4 delay-[200ms] duration-[500ms] ease-in-out transition-all`} > <button in:fade={{ duration: 200 }} out:fade={{ delay: 0, duration: 200 }} on:click={async () => { - if (basis.list.length === 0) await basis.callback_add(); + if (basis.file_paths.length === 0) await basis.callback_add(); }} class={`group flex flex-col gap-2 justify-start items-center`} > <div - class={`flex flex-row ${basis.list.length ? `h-52 w-52` : `h-36 w-36`} justify-center items-center rounded-full border-edge border-layer-0-glyph ${basis.list.length === 0 ? `group-active:border-layer-0-glyph/60 ` : ``} overflow-hidden delay-100 duration-300 ease-in-out transition-all`} + class={`flex flex-row ${basis.file_paths.length ? `h-52 w-52` : `h-36 w-36`} justify-center items-center rounded-full border-edge border-layer-0-glyph ${basis.file_paths.length === 0 ? `group-active:border-layer-0-glyph/60 ` : ``} overflow-hidden delay-100 duration-300 ease-in-out transition-all`} > - {#if basis.list.length} - {#await fs.read_bin(basis.list[0].file_path) then file_data} + {#if basis.file_paths.length} + {#await fs.read_bin(basis.file_paths[0]) then file_data} <ImageBlob basis={{ data: file_data, @@ -89,13 +87,13 @@ </button> </div> </button> - {#if basis.list.length > 1} - {#each basis.list.slice(1) as li} + {#if basis.file_paths.length > 1} + {#each basis.file_paths.slice(1) as file_path} <button class={`flex flex-row h-52 w-52 justify-center items-center rounded-full border-edge border-layer-0-glyph group-active:border-layer-0-glyph/60 overflow-hidden delay-100 duration-300 ease-in-out transition-all fade-in`} on:click={async () => {}} > - {#await fs.read_bin(li.file_path) then file_data} + {#await fs.read_bin(file_path) then file_data} <ImageBlob basis={{ data: file_data, diff --git a/src/lib/components/map_choose_location.svelte b/src/lib/components/map_choose_location.svelte @@ -2,24 +2,40 @@ import { geoc } from "$lib/client"; import { cfg } from "$lib/conf"; import type { GeocoderReverseResult } from "@radroots/geocoder"; - import { app_thc, Loading } from "@radroots/svelte-lib"; - import { MapLibre, Marker, Popup } from "@radroots/svelte-maplibre"; + import { app_thc, fmt_cl, sleep } from "@radroots/svelte-lib"; + import { MapLibre, Marker } from "@radroots/svelte-maplibre"; import type { GeolocationCoordinatesPoint } from "@radroots/utils"; + import { onMount } from "svelte"; import MapMarkerDot from "./map_marker_dot.svelte"; import MapPopupLocationInfo from "./map_popup_location_info.svelte"; export let basis: { - classes_map: string; - loading?: boolean; - //reset?: CallbackPromise; + classes_wrap?: string; + classes?: string; }; $: basis = basis; - export let map_point_center: GeolocationCoordinatesPoint; + let map_visible = false; + + //export let map_point_center: GeolocationCoordinatesPoint; export let map_point_select: GeolocationCoordinatesPoint; export let map_point_select_geoc: GeocoderReverseResult | undefined = undefined; + let map_point_center: GeolocationCoordinatesPoint | undefined = undefined; + + onMount(async () => { + try { + map_point_center = { + ...map_point_select, + }; + await sleep(300); + } catch (e) { + } finally { + map_visible = true; + } + }); + $: { if ( map_point_center && @@ -34,83 +50,53 @@ if (`results` in geoc_res && geoc_res.results.length > 0) map_point_select_geoc = geoc_res.results[0]; else map_point_select_geoc = undefined; - } catch (e) {} + } catch (e) { + console.log(`(error) map choose location`, e); + } })(); } } - /* -{#if !basis.loading} - <div class={`flex flex-col h-8 w-full justify-end items-center`}> - <button - class={`flex flex-row justify-center items-center`} - on:click={async () => { - if (basis.reset) await basis.reset(); - }} - > - <p class={`font-mono font-[400] text-layer-0-glyph text-sm`}> - {`reset`} - </p> - </button> - </div> - {/if} - */ </script> <div - class={`relative flex flex-col justify-center items-center ${basis.classes_map} bg-layer-1-surface overflow-hidden`} + class={`${fmt_cl(basis.classes_wrap)} flex flex-col justify-start items-center`} > - <MapLibre - center={map_point_center} - zoom={10} - class={`${basis.classes_map} ${basis.loading ? `` : ``}`} - style={cfg.map.styles.base[$app_thc]} - attributionControl={false} + <div + class={`relative flex flex-col w-full justify-center items-center bg-layer-1-surface overflow-hidden`} > - <Marker - bind:lngLat={map_point_select} - draggable - on:dragend={async () => { - if (!map_point_select) return; - const geoc_res = await geoc.reverse({ - point: map_point_select, - limit: 1, - }); - if (`results` in geoc_res && geoc_res.results.length > 0) - map_point_select_geoc = geoc_res.results[0]; - }} + <MapLibre + center={map_point_center} + zoom={10} + class={`${fmt_cl(basis.classes || `h-full w-full`)} ${map_visible ? `fade-in` : `hidden`}`} + style={cfg.map.styles.base[$app_thc]} + attributionControl={false} > - <MapMarkerDot /> - <Popup - offset={cfg.map.popup.dot.offset} - open={true} - closeOnClickOutside={false} - closeButton={false} - > - <MapPopupLocationInfo - basis={{ + <Marker + bind:lngLat={map_point_select} + draggable + on:dragend={async () => { + if (!map_point_select) return; + const geoc_res = await geoc.reverse({ point: map_point_select, - geoc: map_point_select_geoc, - }} - /> - </Popup> - </Marker> - </MapLibre> - {#if basis.loading} - <div - class={`z-10 absolute inset-0 flex flex-col justify-center items-center bg-layer-0-surface`} - > - <div - class={`relative flex flex-col h-full w-full justify-center items-center`} + limit: 1, + }); + if (`results` in geoc_res && geoc_res.results.length > 0) + map_point_select_geoc = geoc_res.results[0]; + }} > - <Loading /> - <!-- - <p - class={`absolute top-1/2 left-1/2 -translate-x-1/2 translate-y-3 font-circ font-[300] text-[0.6rem] text-layer-0-glyph`} + <button + class={`flex flex-row justify-center items-center transform -translate-x-[42%]`} + on:click={async () => {}} > - {`Loading...`} - </p> - --> - </div> - </div> - {/if} + <MapPopupLocationInfo + basis={{ + point: map_point_select, + geoc: map_point_select_geoc, + }} + /> + </button> + <MapMarkerDot /> + </Marker> + </MapLibre> + </div> </div> diff --git a/src/lib/components/map_popup_location_info.svelte b/src/lib/components/map_popup_location_info.svelte @@ -9,18 +9,22 @@ </script> <button - class={`flex flex-row justify-center items-center transition-all`} + class={`flex flex-row justify-center items-center el-re`} on:click={async () => {}} > <div - class={`flex flex-col w-fit px-3 py-[0.5rem] gap-2 justify-start items-start`} + class={`flex flex-col w-fit px-4 py-[0.6rem] gap-2 justify-start items-start bg-layer-1-surface round-40`} > {#if basis.geoc} <div class={`flex flex-col w-full gap-1 justify-start items-start`}> - <p class={`font-mono font-[400] text-layer-2-glyph`}> + <p + class={`font-circ font-[400] text-[1.1rem] scale-y-[92%] tracking-tight text-layer-2-glyph`} + > {basis.geoc.name} </p> - <p class={`font-mono font-[400] text-layer-2-glyph`}> + <p + class={`font-circ font-[400] text-[1.1rem] scale-y-[92%] tracking-tight text-layer-2-glyph`} + > {`${basis.geoc.admin1_name}, ${basis.geoc.country_name}`} </p> </div> diff --git a/src/lib/conf.ts b/src/lib/conf.ts @@ -37,6 +37,9 @@ export const cfg = { nostr_relay_poll_document: 3000, entry_focus: 2000, }, + num: { + str: (num: number) => num.toString(), + }, cmd: { layout_route: `*-route` }, diff --git a/src/lib/utils/kv.ts b/src/lib/utils/kv.ts @@ -1,7 +1,18 @@ -import { parse_trade_product_form_keys, trade_product_form_fields, trade_product_form_vals, type TradeProductFormFields } from "@radroots/models"; -import { kv } from "@radroots/svelte-lib"; +import { parse_trade_product_form_keys, trade_product_form_fields } from "@radroots/models"; +import { fmt_id, kv } from "@radroots/svelte-lib"; import { err_msg, type ErrorMessage, type ResultPass } from "@radroots/utils"; +export const kv_init_page = async (): Promise<void> => { + try { + 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((k) => kv.set(k, ``))); + } catch (e) { + console.log(`(error) kv_init_page `, e); + } +}; + export const kv_init_trade_product_fields = async (kv_pref: string): Promise<void> => { try { for (const k of Object.keys( @@ -18,42 +29,6 @@ export const kv_init_trade_product_fields = async (kv_pref: string): Promise<voi } }; -export const kv_validate_trade_product_fields = async (opts: { - kv_pref: string; - no_validation?: string[] | true; -}): Promise<TradeProductFormFields | ErrorMessage<string>> => { - try { - let no_validation = opts.no_validation || []; - const vals = { - ...trade_product_form_vals - }; - - for (const [k, field] of Object.entries( - trade_product_form_fields, - )) { - const field_k = parse_trade_product_form_keys(k); - if (!field_k) continue; - const field_id = `${opts.kv_pref}-${field_k}`; - const field_val = await kv.get(field_id); - if (field_val) vals[field_k] = field_val; - if (no_validation === true) continue; - else if ( - (!field.optional && !field.validation.test(field_val)) || - (field.optional && - field_val && - !field.validation.test(field_val)) - ) { - if (no_validation.includes(field_k)) continue; - else return err_msg(field_k); - } - } - return vals; - } catch (e) { - console.log(`(error) kv_validate_trade_product_fields `, e); - return err_msg(String(e)) - } -}; - export const validate_trade_product_fields = async (opts: { kv_pref: string; fields: string[]; diff --git a/src/lib/utils/trade_product.ts b/src/lib/utils/trade_product.ts @@ -0,0 +1,102 @@ +import { parse_trade_product_form_keys, trade_product_form_fields, trade_product_form_vals, type IModelsForm, type TradeProductFormFields } from "@radroots/models"; +import { fmt_id, kv } from "@radroots/svelte-lib"; +import { err_msg, type ErrorMessage } from "@radroots/utils"; + +const trade_products_field_validate = (field: IModelsForm, field_val: string): boolean => { + if ( + (!field.optional && !field.validation.test(field_val)) || + (field.optional && + field_val && + !field.validation.test(field_val)) + ) return false; + return true; +}; + +export const trade_product_fields_assign = async (opts?: { + kv_pref?: string; + field_defaults?: [keyof typeof trade_product_form_vals, string][]; + field_pass?: string[] | true; +}): Promise<TradeProductFormFields | ErrorMessage<string>> => { + try { + const fields = { + ...trade_product_form_vals + }; + for (const [field_ks, field] of Object.entries( + trade_product_form_fields, + )) { + const field_k = parse_trade_product_form_keys(field_ks); + if (!field_k) continue; + const field_val = await kv.get(`${opts?.kv_pref || fmt_id()}-${field_k}`); + if (field_val) fields[field_k] = field_val; + } + if (opts?.field_defaults && opts?.field_defaults?.length > 0) for (const [field_k, field_v] of opts?.field_defaults) if (!fields[field_k]) fields[field_k] = field_v; + return fields; + } catch (e) { + console.log(`(error) trade_product_fields_assign `, e); + return err_msg(String(e)) + } +}; + +export const trade_product_fields_validate = async (opts: { + kv_pref?: string; + field_defaults?: [keyof typeof trade_product_form_vals, string][]; + fields_pass?: string[]; +}): Promise<TradeProductFormFields | ErrorMessage<string>> => { + try { + const fields = { + ...trade_product_form_vals + }; + for (const [field_ks, field] of Object.entries( + trade_product_form_fields, + )) { + const field_k = parse_trade_product_form_keys(field_ks); + if (!field_k) continue; + const field_val = await kv.get(`${opts?.kv_pref || fmt_id()}-${field_k}`); + if (field_val) fields[field_k] = field_val; + } + if (opts?.field_defaults && opts?.field_defaults?.length > 0) for (const [field_k, field_v] of opts?.field_defaults) if (!fields[field_k]) fields[field_k] = field_v; + + for (const [field_ks, field_val] of Object.entries(fields)) { + const field_k = parse_trade_product_form_keys(field_ks); + if (!field_k) continue; + const field = trade_product_form_fields[field_k] + if (!trade_products_field_validate(field, field_val)) { + if (opts.fields_pass?.includes(field_k)) continue; + return err_msg(field_k); + } + } + return fields; + } catch (e) { + console.log(`(error) trade_product_fields_validate `, e); + return err_msg(String(e)) + } +}; + +export const trade_product_fields_kv_validate = async (opts?: { + kv_pref?: string; + fields_pass?: string[] | true; +}): Promise<TradeProductFormFields | ErrorMessage<string>> => { + try { + const vals = { + ...trade_product_form_vals + }; + for (const [k, field] of Object.entries( + trade_product_form_fields, + )) { + const field_k = parse_trade_product_form_keys(k); + if (!field_k) continue; + const field_id = `${opts?.kv_pref || fmt_id()}-${field_k}`; + const field_val = await kv.get(field_id); + if (field_val) vals[field_k] = field_val; + if (opts?.fields_pass === true) continue; + else if (!trade_products_field_validate(field, field_val)) { + if (opts?.fields_pass?.includes(field_k)) continue; + else return err_msg(field_k); + } + } + return vals; + } catch (e) { + console.log(`(error) trade_product_fields_kv_validate `, e); + return err_msg(String(e)) + } +}; diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte @@ -4,6 +4,7 @@ app_cfg_type, app_layout, app_nostr_key, + envelope_visible, EnvelopeLower, Glyph, LayoutView, @@ -78,7 +79,6 @@ }, }; - let tmp_show_envelope = false; let tmp_show_no_profile = false; onMount(async () => { @@ -247,9 +247,8 @@ /> <EnvelopeLower basis={{ - visible: tmp_show_envelope, close: async () => { - tmp_show_envelope = !tmp_show_envelope; + envelope_visible.set(false); }, }} > diff --git a/src/routes/(app)/models/nostr-profile/edit/field/+page.svelte b/src/routes/(app)/models/nostr-profile/edit/field/+page.svelte @@ -165,11 +165,14 @@ basis: { id: fmt_id($qp_rkey), sync: true, + /* + @todo sync_init: ld?.nostr_profile[ ld?.field_key ] ? ld.nostr_profile[ld.field_key] : true, + */ classes: `placeholder:font-[300]`, placeholder: ld?.nostr_profile[ ld?.field_key diff --git a/src/routes/(app)/models/trade-product/add/+page.svelte b/src/routes/(app)/models/trade-product/add/+page.svelte @@ -6,7 +6,7 @@ kv_init_trade_product_fields, validate_trade_product_fields, } from "$lib/utils/kv"; - import { mass_units, trade_product_form_fields } from "@radroots/models"; + import { trade_product_form_fields } from "@radroots/models"; import { carousel_dec, carousel_inc, @@ -19,20 +19,21 @@ EntrySelect, EntryWrap, fmt_id, + fmt_price, InputElement, kv, LayoutTrellis, LayoutTrellisLine, LayoutView, Nav, - price_locale_fmt, SelectElement, t, view_effect, } from "@radroots/svelte-lib"; import { fiat_currencies, - fmt_trade_quantity_sel_val, + fmt_trade_quantity_tup, + mass_units, parse_trade_key, trade, trade_keys, @@ -140,7 +141,7 @@ $: if (tradeproduct_key_parsed) { tradeproduct_qty_unit_tup_sel.set( - fmt_trade_quantity_sel_val( + fmt_trade_quantity_tup( trade.key[tradeproduct_key_parsed].quantity[0], ), ); @@ -231,7 +232,7 @@ tradeproduct_qty_unit_tup_sel.set(mass_units[0]); } else { $tradeproduct_qty_unit_tup_sel = tradeproduct_key_parsed - ? fmt_trade_quantity_sel_val( + ? fmt_trade_quantity_tup( trade.key[tradeproduct_key_parsed].quantity[0], ) : ``; @@ -376,7 +377,9 @@ <ImageUploadRow basis={{ loading: photo_add_loading, - list: photo_add_list, + file_paths: photo_add_list.map( + ({ file_path }) => file_path, + ), callback_add: handle_photo_add, }} /> @@ -516,8 +519,11 @@ <ImageUploadDisplay basis={{ loading: photo_add_loading, - list: photo_add_list, + file_paths: photo_add_list.map( + ({ file_path }) => file_path, + ), callback_add: handle_photo_add, + callback_edit: async () => {}, }} /> <LayoutTrellis> @@ -595,7 +601,7 @@ }, callback_blur: async ({ el }) => { if (!el.value) return; - el.value = price_locale_fmt( + el.value = fmt_price( $tradeproduct_price_curr_sel, el.value, ).slice(1); @@ -663,7 +669,7 @@ entries: [ ...ls_trade_product_quantities.map( (i) => ({ - value: fmt_trade_quantity_sel_val( + value: fmt_trade_quantity_tup( i, ), label: `${i.mass} ${$t(`measurement.mass.unit.${i.mass_unit}_ab`, { default: i.mass_unit })} ${i.label}`, diff --git a/src/routes/(cfg)/cfg/init/+page.svelte b/src/routes/(cfg)/cfg/init/+page.svelte @@ -11,7 +11,7 @@ app_layout, app_loading, app_splash, - ButtonCarouselPair, + ButtonLayoutPair, carousel_dec, carousel_inc, carousel_index, @@ -647,7 +647,7 @@ return; // @todo } const nostr_profile_relay_add = - await db.set_nostr_profile_relay({ + await db.nostr_profile_relay_set({ nostr_profile: { id: nostr_profile_add.id, }, @@ -703,14 +703,7 @@ <button class={`flex flex-row justify-center items-center`} on:click={async () => { - const sk = nostr.lib.generate_key(); - const res1 = await db.nostr_profile_add({ - public_key: nostr.lib.public_key(sk), - }); - console.log( - JSON.stringify(res1, null, 4), - `res1`, - ); + //@todo dev }} > <p @@ -850,7 +843,7 @@ <div class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center`} > - <ButtonCarouselPair + <ButtonLayoutPair basis={{ continue: { disabled: $carousel_index === 1 && !cgf_init_key_option, @@ -997,7 +990,7 @@ <div class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center ${view === `cfg_main` ? `fade-in-long` : ``}`} > - <ButtonCarouselPair + <ButtonLayoutPair basis={{ continue: { disabled: diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte @@ -10,7 +10,7 @@ app_splash, app_th, app_thc, - AppControls, + Controls, CssStatic, CssStyles, LayoutWindow, @@ -100,7 +100,7 @@ {/if} <slot /> </LayoutWindow> -<AppControls /> +<Controls /> <CssStatic /> <CssStyles /> <div class="hidden h-entry_guide h-entry_line" /> diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts @@ -9,8 +9,9 @@ export const load: LayoutLoad = async ({ url }: LayoutLoadEvent) => { try { const { language: nav_locale } = navigator; let locale = default_locale.toString(); - if (locales.get().some(i => i === nav_locale.toLowerCase())) locale = navigator.language; - else if (locales.get().some(i => i === nav_locale.slice(0, 2).toLowerCase())) locale = nav_locale.slice(0, 2); + const locales_avail = locales.get(); + if (locales_avail.some(i => i === nav_locale.toLowerCase())) locale = navigator.language; + else if (locales_avail.some(i => i === nav_locale.slice(0, 2).toLowerCase())) locale = nav_locale.slice(0, 2); await load_translations(locale.toLowerCase(), url.pathname); await translations_loading.toPromise(); } catch (e) { diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -86,7 +86,8 @@ const config: Config = { circ: [`Circular`], arch: [`Archivo`], sg: [`Space Grotesk`], - sp: [`Spartan`] + sp: [`Spartan`], + of: [`Outfit`] }, fontSize: { line_label: [`1.3rem`, { lineHeight: `1.3rem` }], @@ -155,8 +156,6 @@ const config: Config = { ], daisyui: { themes: [ - themes.theme_earth_light, - themes.theme_earth_dark, themes.theme_os_dark, themes.theme_os_light, ],