app

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

commit 1d3d85bc8ded58d66a66b273ff49f2b8cb92efd5
parent af2c4a39e9c3a0ec704b23233d1e86fefc90e5fe
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Sun, 10 Nov 2024 05:03:19 +0000

Edit `nostr_profile_relay` model fields. Edit `/models/trade-product/add` update photo upload component, add photo upload handlers. Update `/cfg/init` components. Edit map components. Add geocode uitls. Add/edit styles, utils.

Diffstat:
Mcrates/tauri/migrations/0005_nostr_profile_relay.sql | 8++++----
Mpackage.json | 1+
Msrc/app.css | 1+
Dsrc/lib/components/image_upload_display.svelte | 84-------------------------------------------------------------------------------
Asrc/lib/components/image_upload_multi.svelte | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/components/image_upload_row.svelte | 107-------------------------------------------------------------------------------
Dsrc/lib/components/map_choose_location.svelte | 102-------------------------------------------------------------------------------
Rsrc/lib/components/map_popup_location_info.svelte -> src/lib/components/map_popup_point_geolocation.svelte | 0
Asrc/lib/components/map_select_point.svelte | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/utils/geocode.ts | 14++++++++++++++
Msrc/lib/utils/kv.ts | 37+++++--------------------------------
Msrc/lib/utils/trade_product.ts | 42+++++++++++++++++++++++++++++++++++++++---
Msrc/routes/(app)/+page.svelte | 4+++-
Msrc/routes/(app)/models/trade-product/add/+page.svelte | 941+++++++++++++------------------------------------------------------------------
Msrc/routes/(app)/test/+page.svelte | 5++++-
Msrc/routes/(cfg)/cfg/init/+page.svelte | 2--
Mtailwind.config.ts | 27+++++++++++++++++++++++++--
17 files changed, 746 insertions(+), 1131 deletions(-)

diff --git a/crates/tauri/migrations/0005_nostr_profile_relay.sql b/crates/tauri/migrations/0005_nostr_profile_relay.sql @@ -1,7 +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 + 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/package.json b/package.json @@ -22,6 +22,7 @@ "@sveltejs/adapter-static": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@tailwindcss/aspect-ratio": "^0.4.2", "@tauri-apps/cli": "2.0.4", "@types/node": "^20.12.7", "@types/sql.js": "^1.4.9", diff --git a/src/app.css b/src/app.css @@ -8,6 +8,7 @@ @import "/static/webfonts/lust/styles.css"; @import "/static/webfonts/magda-text/styles.css"; @import "/static/webfonts/sf-pro-display/styles.css"; +@import "/static/webfonts/sf-pro-rounded/styles.css"; @import "/static/webfonts/circular/styles.css"; @import "/static/webfonts/archivo/styles.css"; @import "/static/webfonts/space-grotesk/styles.css"; diff --git a/src/lib/components/image_upload_display.svelte b/src/lib/components/image_upload_display.svelte @@ -1,84 +0,0 @@ -<script lang="ts"> - import { fs } from "$lib/client"; - import { - Glyph, - ImageBlob, - type CallbackPromise, - type CallbackPromiseGeneric, - } from "@radroots/svelte-lib"; - - export let basis: { - loading: boolean; - file_paths: string[]; - callback_add: CallbackPromise; - callback_edit: CallbackPromiseGeneric<number>; - }; -</script> - -<div - 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`}> - <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.file_paths[0]} - {#await fs.read_bin(basis.file_paths[0]) then file_data} - <ImageBlob - basis={{ - data: file_data, - }} - /> - {/await} - {/if} - <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> - </div> - </button> - </div> - <div - class={`grid grid-cols-12 flex flex-row gap-y-2 justify-start items-center`} - > - {#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-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(); - }} - > - {#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} - </div> -</div> diff --git a/src/lib/components/image_upload_multi.svelte b/src/lib/components/image_upload_multi.svelte @@ -0,0 +1,397 @@ +<script lang="ts"> + import { dialog, fs } from "$lib/client"; + import { + app_layout, + envelope_visible, + EnvelopeLower, + Glyph, + ImageBlob, + t, + time_iso, + } from "@radroots/svelte-lib"; + import { + format_file_bytes, + list_assign, + list_move_index, + parse_file_name, + } from "@radroots/utils"; + import { fade } from "svelte/transition"; + + export let photo_paths: string[]; + + let photo_file_path_0: string; + let photo_file_path_1: string; + let photo_file_path_2: string; + let photo_file_path_3: string; + let photo_file_path_4: string; + let photo_file_path_5: string; + let photo_file_path_6: string; + let photo_edit_envelope: { index: number; file_path: string } | undefined; + + $: envelope_visible.set(!!photo_edit_envelope); + + $: if (photo_paths.length > 0) { + if (photo_paths[0]) photo_file_path_0 = photo_paths[0]; + if (photo_paths[1]) photo_file_path_1 = photo_paths[1]; + if (photo_paths[2]) photo_file_path_2 = photo_paths[2]; + if (photo_paths[3]) photo_file_path_3 = photo_paths[3]; + if (photo_paths[4]) photo_file_path_4 = photo_paths[4]; + if (photo_paths[5]) photo_file_path_5 = photo_paths[5]; + if (photo_paths[6]) photo_file_path_6 = photo_paths[6]; + } + + const handle_photo_add = async (): Promise<void> => { + try { + const photo_paths_select = await dialog.open_photos(); + if (!photo_paths_select) return; + photo_paths = list_assign(photo_paths, photo_paths_select.results); + } catch (e) { + console.log(`(error) handle_photo_add `, e); + } + }; + + const handle_photo_envelope_edit = async ( + opts_photo_index: number, + ): Promise<void> => { + try { + if (!photo_paths[opts_photo_index]) { + photo_edit_envelope = undefined; + return; + } + photo_edit_envelope = { + index: opts_photo_index, + file_path: photo_paths[opts_photo_index], + }; + } catch (e) { + console.log(`(error) handle_photo_envelope_edit `, e); + } + }; + + const handle_photo_envelope_edit_move = async ( + opts_photo_index: number, + ): Promise<void> => { + try { + photo_paths = list_move_index(photo_paths, opts_photo_index, 0); + } catch (e) { + console.log(`(error) handle_photo_envelope_edit_move `, e); + } + }; +</script> + +<div class={`flex flex-col h-[13rem] w-full px-4 justify-center items-center`}> + <div + class={`flex flex-row h-[11rem] w-[22rem] justify-center items-center bg-layer-1-surface rounded-[2rem] overflow-hidden`} + > + <button + on:click|stopPropagation={async () => { + if (photo_file_path_0) await handle_photo_envelope_edit(0); + else await handle_photo_add(); + }} + class={`group relative flex flex-col h-[11rem] w-[11rem] justify-center items-center border-r-line border-layer-0-glyph_d rounded-tl-3xl rounded-bl-3xl overflow-hidden el-re`} + > + {#if photo_file_path_0} + {#await fs.read_bin(photo_file_path_0) then file_data} + <ImageBlob + basis={{ + data: file_data, + }} + /> + {/await} + {:else} + <div + in:fade={{ duration: 200 }} + out:fade={{ + delay: 0, + duration: 50, + }} + class={`flex flex-row justify-start items-center`} + > + <Glyph + basis={{ + classes: `text-layer-0-glyph group-active:text-layer-0-glyph/60 delay-100 duration-300 ease-in-out transition-all`, + dim: `lg`, + key: `camera`, + }} + /> + </div> + {/if} + </button> + <div + class={`flex flex-row flex-wrap h-full w-[11rem] justify-start items-start`} + > + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-b-line border-r-line border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_1) await handle_photo_envelope_edit(1); + else await handle_photo_add(); + }} + > + {#if photo_file_path_1} + {#await fs.read_bin(photo_file_path_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: `plus`, + }} + /> + {/if} + </button> + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-b-line border-r-line border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_2) await handle_photo_envelope_edit(2); + else await handle_photo_add(); + }} + > + {#if photo_file_path_2} + {#await fs.read_bin(photo_file_path_2) 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: `plus`, + }} + /> + {/if} + </button> + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-b-line border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_3) await handle_photo_envelope_edit(3); + else await handle_photo_add(); + }} + > + {#if photo_file_path_3} + {#await fs.read_bin(photo_file_path_3) 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: `plus`, + }} + /> + {/if} + </button> + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-r-line border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_4) await handle_photo_envelope_edit(4); + else await handle_photo_add(); + }} + > + {#if photo_file_path_4} + {#await fs.read_bin(photo_file_path_4) 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: `plus`, + }} + /> + {/if} + </button> + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-r-line border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_5) await handle_photo_envelope_edit(5); + else await handle_photo_add(); + }} + > + {#if photo_file_path_5} + {#await fs.read_bin(photo_file_path_5) 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: `plus`, + }} + /> + {/if} + </button> + <button + class={`flex flex-row h-[5.5rem] w-[calc(11rem/3)] justify-center items-center border-layer-0-glyph_d overflow-hidden`} + on:click|stopPropagation={async () => { + if (photo_file_path_6) await handle_photo_envelope_edit(6); + else await handle_photo_add(); + }} + > + {#if photo_file_path_6} + {#await fs.read_bin(photo_file_path_6) 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: `plus`, + }} + /> + {/if} + </button> + </div> + </div> + <div + class={`flex flex-row h-8 w-${$app_layout} justify-start items-center`} + > + <div class={`flex flex-row w-[11rem] justify-center items-center`}> + <p class={`font-sans font-[500] text-sm text-layer-0-glyph`}> + {`${$t(`icu.primary_*`, { value: `${$t(`common.photo`)}` })}`} + </p> + </div> + </div> +</div> +<EnvelopeLower + basis={{ + close: async () => { + photo_edit_envelope = undefined; + }, + }} +> + {#if photo_edit_envelope} + <div + class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} + > + <div + class={`flex flex-row w-full justify-center items-center round-44 overflow-hidden`} + > + {#await fs.read_bin(photo_edit_envelope.file_path) then file_data} + <ImageBlob + basis={{ + data: file_data, + }} + /> + {/await} + </div> + <div + class={`flex flex-col w-full pb-16 gap-4 justify-center items-center`} + > + <button + class={`flex flex-row h-touch_guide w-full justify-center items-center rounded-2xl bg-layer-1-glyph-hl`} + on:click={async () => { + if (!photo_edit_envelope) { + photo_edit_envelope = undefined; + return; + } else if (photo_edit_envelope.index === 0) return; + await handle_photo_envelope_edit_move( + photo_edit_envelope.index, + ); + photo_edit_envelope = undefined; + }} + > + <p class={`font-sans font-[600] text-lg text-white`}> + {#if photo_edit_envelope.index === 0} + {`${$t(`icu.primary_*`, { value: `${$t(`common.photo`)}`.toLowerCase() })}`} + {:else} + {`${$t(`common.make_primary`)}`} + {/if} + </p> + </button> + {#await fs.info(photo_edit_envelope.file_path) then fs_info} + {#if fs_info} + <div + class={`flex flex-col w-full px-4 gap-3 justify-start items-start`} + > + <div + class={`flex flex-col gap-1 justify-start items-start`} + > + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${$t(`common.file_name`)}:`} + </p> + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${parse_file_name(photo_edit_envelope.file_path)}`} + </p> + </div> + <div + class={`flex flex-col gap-1 justify-start items-start`} + > + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${$t(`common.file_size`)}:`} + </p> + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${format_file_bytes(fs_info.size, `mb`)}`} + </p> + </div> + <div + class={`flex flex-col gap-1 justify-start items-start`} + > + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${$t(`common.date_created`)}:`} + </p> + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${time_iso(fs_info.birthtime?.toISOString(), `file_info`).replaceAll(`,`, ` ${`${$t(`common.at`)}`.toLowerCase()}`)}`} + </p> + </div> + <div + class={`flex flex-col gap-1 justify-start items-start`} + > + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${$t(`common.date_modified`)}:`} + </p> + <p + class={`col-span-8 font-sans font-[400] text-[1.05rem] text-layer-0-glyph`} + > + {`${time_iso(fs_info.mtime?.toISOString(), `file_info`).replaceAll(`,`, ` ${`${$t(`common.at`)}`.toLowerCase()}`)}`} + </p> + </div> + </div> + {/if} + {/await} + </div> + </div> + {/if} +</EnvelopeLower> diff --git a/src/lib/components/image_upload_row.svelte b/src/lib/components/image_upload_row.svelte @@ -1,107 +0,0 @@ -<script lang="ts"> - import { fs } from "$lib/client"; - import { - Glyph, - ImageBlob, - Loading, - t, - type CallbackPromise, - } from "@radroots/svelte-lib"; - import { fade } from "svelte/transition"; - - export let basis: { - loading: boolean; - file_paths: string[]; - callback_add: CallbackPromise; - }; -</script> - -<div - 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.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.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.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.file_paths.length} - {#await fs.read_bin(basis.file_paths[0]) then file_data} - <ImageBlob - basis={{ - data: file_data, - }} - /> - {/await} - {:else if basis.loading} - <div - in:fade={{ duration: 200 }} - out:fade={{ - delay: 0, - duration: 50, - }} - class={`flex flex-row justify-start items-center`} - > - <Loading /> - </div> - {:else} - <div - in:fade={{ duration: 200 }} - out:fade={{ - delay: 0, - duration: 50, - }} - class={`flex flex-row justify-start items-center`} - > - <Glyph - basis={{ - classes: `text-layer-0-glyph group-active:text-layer-0-glyph/60 el-re`, - dim: `lg`, - weight: `bold`, - key: `download-simple`, - }} - /> - </div> - {/if} - </div> - <div class={`flex flex-row justify-start items-center`}> - <button - class={`flex flex-row justify-center items-center`} - on:click={async () => { - await basis.callback_add(); - }} - > - <p - class={`font-sans font-[500] text-[1.1rem] tracking-tight text-layer-0-glyph group-active:text-layer-0-glyph/60 el-re`} - > - {`${$t(`icu.add_*`, { value: `${$t(`common.photo`)}`.toLowerCase() })}`} - </p> - </button> - </div> - </button> - {#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(file_path) then file_data} - <ImageBlob - basis={{ - data: file_data, - }} - /> - {/await} - </button> - {/each} - {/if} - </div> -</div> diff --git a/src/lib/components/map_choose_location.svelte b/src/lib/components/map_choose_location.svelte @@ -1,102 +0,0 @@ -<script lang="ts"> - import { geoc } from "$lib/client"; - import { cfg } from "$lib/conf"; - import type { GeocoderReverseResult } from "@radroots/geocoder"; - 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_wrap?: string; - classes?: string; - }; - $: basis = basis; - - 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 && - map_point_center.lat !== 0 && - map_point_center.lng !== 0 - ) { - (async () => { - try { - const geoc_res = await geoc.reverse({ - point: map_point_center, - }); - 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) { - console.log(`(error) map choose location`, e); - } - })(); - } - } -</script> - -<div - class={`${fmt_cl(basis.classes_wrap)} flex flex-col justify-start items-center`} -> - <div - class={`relative flex flex-col w-full justify-center items-center bg-layer-1-surface overflow-hidden`} - > - <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} - > - <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]; - }} - > - <button - class={`flex flex-row justify-center items-center transform -translate-x-[42%]`} - on:click={async () => {}} - > - <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_point_geolocation.svelte diff --git a/src/lib/components/map_select_point.svelte b/src/lib/components/map_select_point.svelte @@ -0,0 +1,105 @@ +<script lang="ts"> + import { geoc } from "$lib/client"; + import { cfg } from "$lib/conf"; + import type { GeocoderReverseResult } from "@radroots/geocoder"; + 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 MapPopupPointGeolocation from "./map_popup_point_geolocation.svelte"; + + export let basis: { + classes_wrap?: string; + classes?: string; + }; + $: basis = basis; + + let map_visible = false; + + export let map_point_select: GeolocationCoordinatesPoint; + export let map_point_select_geoc: GeocoderReverseResult | undefined = + undefined; + + let map_point_center: GeolocationCoordinatesPoint = cfg.map.coords.default; + + onMount(async () => { + try { + await sleep(300); + } catch (e) { + } finally { + map_visible = true; + } + }); + + $: if (!map_visible) { + map_point_center = { + lat: map_point_select.lat, + lng: map_point_select.lng - 0.065, + }; + } + + $: { + if ( + map_point_center && + map_point_center.lat !== 0 && + map_point_center.lng !== 0 + ) { + (async () => { + try { + const geoc_res = await geoc.reverse({ + point: map_point_select, + }); + 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) { + console.log(`(error) map choose location`, e); + } + })(); + } + } +</script> + +<div + class={`${fmt_cl(basis.classes_wrap)} flex flex-col justify-start items-center`} +> + <div + class={`relative flex flex-col w-full justify-center items-center bg-layer-1-surface overflow-hidden`} + > + <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} + > + <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]; + }} + > + <button + class={`flex flex-row justify-center items-center transform -translate-x-[42%]`} + on:click={async () => {}} + > + <MapPopupPointGeolocation + basis={{ + point: map_point_select, + geoc: map_point_select_geoc, + }} + /> + </button> + <MapMarkerDot /> + </Marker> + </MapLibre> + </div> +</div> diff --git a/src/lib/utils/geocode.ts b/src/lib/utils/geocode.ts @@ -0,0 +1,13 @@ +import { geoc } from "$lib/client"; +import type { GeocoderReverseResult } from "@radroots/geocoder"; +import type { GeolocationCoordinatesPoint } from "@radroots/utils"; + +export const geoc_rev = async (point: GeolocationCoordinatesPoint): Promise<GeocoderReverseResult | undefined> => { + try { + const geoc_res = await geoc.reverse({ point }); + if (`results` in geoc_res && geoc_res.results.length > 0) + return geoc_res.results[0]; + } catch (e) { + console.log(`(error) geoc_rev `, e); + } +}; +\ No newline at end of file diff --git a/src/lib/utils/kv.ts b/src/lib/utils/kv.ts @@ -1,6 +1,4 @@ -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 { @@ -13,38 +11,13 @@ export const kv_init_page = async (): Promise<void> => { } }; -export const kv_init_trade_product_fields = async (kv_pref: string): Promise<void> => { +export const kv_sync = async (list: [string, string][]): Promise<void> => { try { - for (const k of Object.keys( - trade_product_form_fields, - )) { - const field_k = parse_trade_product_form_keys(k); - if (!field_k) continue; - const field_id = `${kv_pref}-${field_k}` - await kv.delete(field_id); + for (const [key, val] of list) { + await kv.set(key, val); + // await sleep(50); } - - } catch (e) { - console.log(`(error) kv_init_trade_product_fields `, e); - } -}; - -export const validate_trade_product_fields = async (opts: { - kv_pref: string; - fields: string[]; -}): Promise<ResultPass | ErrorMessage<string>> => { - try { - for (const field of opts.fields) { - const field_k = parse_trade_product_form_keys(field); - if (!field_k) return err_msg(field); - const field_id = `${opts.kv_pref}-${field_k}`; - const field_val = await kv.get(field_id); - console.log(`${field_k}: '${field_val}'`) - if (!trade_product_form_fields[field_k].validation.test(field_val)) return err_msg(field_k); - } - return { pass: true }; } catch (e) { - console.log(`(error) validate_trade_product_fields `, e); - return err_msg(String(e)) + console.log(`(error) kv_sync `, e); } }; \ No newline at end of file diff --git a/src/lib/utils/trade_product.ts b/src/lib/utils/trade_product.ts @@ -1,6 +1,6 @@ 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"; +import { err_msg, type ErrorMessage, type ResultPass } from "@radroots/utils"; const trade_products_field_validate = (field: IModelsForm, field_val: string): boolean => { if ( @@ -72,7 +72,7 @@ export const trade_product_fields_validate = async (opts: { } }; -export const trade_product_fields_kv_validate = async (opts?: { +export const tradeproduct_validate_kv = async (opts?: { kv_pref?: string; fields_pass?: string[] | true; }): Promise<TradeProductFormFields | ErrorMessage<string>> => { @@ -96,7 +96,42 @@ export const trade_product_fields_kv_validate = async (opts?: { } return vals; } catch (e) { - console.log(`(error) trade_product_fields_kv_validate `, e); + console.log(`(error) tradeproduct_validate_kv `, e); return err_msg(String(e)) } }; + + +export const tradeproduct_init_kv = async (kv_pref: string): Promise<void> => { + try { + for (const k of Object.keys( + trade_product_form_fields, + )) { + const field_k = parse_trade_product_form_keys(k); + if (!field_k) continue; + const field_id = `${kv_pref}-${field_k}` + await kv.delete(field_id); + } + } catch (e) { + console.log(`(error) tradeproduct_init_kv `, e); + } +}; + +export const tradeproduct_validate_fields = async (opts: { + kv_pref: string; + fields: string[]; +}): Promise<ResultPass | ErrorMessage<string>> => { + try { + for (const field of opts.fields) { + const field_k = parse_trade_product_form_keys(field); + if (!field_k) return err_msg(field); + const field_id = `${opts.kv_pref}-${field_k}`; + const field_val = await kv.get(field_id); + if (!trade_product_form_fields[field_k].validation.test(field_val)) return err_msg(field_k); + } + return { pass: true }; + } catch (e) { + console.log(`(error) tradeproduct_validate_fields `, e); + return err_msg(String(e)) + } +}; +\ No newline at end of file diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte @@ -240,7 +240,9 @@ { icon: `squares-four`, label: `Menu`, - callback: async () => {}, + callback: async () => { + await route(`/settings`); + }, }, ], }} diff --git a/src/routes/(app)/models/trade-product/add/+page.svelte b/src/routes/(app)/models/trade-product/add/+page.svelte @@ -1,300 +1,120 @@ <script lang="ts"> - import { dialog } from "$lib/client"; - import ImageUploadDisplay from "$lib/components/image_upload_display.svelte"; - import ImageUploadRow from "$lib/components/image_upload_row.svelte"; + import ImageUploadMulti from "$lib/components/image_upload_multi.svelte"; + import { tradeproduct_init_kv } from "$lib/utils/trade_product"; import { - kv_init_trade_product_fields, - validate_trade_product_fields, - } from "$lib/utils/kv"; - import { trade_product_form_fields } from "@radroots/models"; - import { - carousel_dec, - carousel_inc, carousel_index, carousel_index_max, carousel_num, - el_id, - EntryLine, - EntryMultiline, - EntrySelect, - EntryWrap, fmt_id, - fmt_price, - InputElement, - kv, + layout_view_cover, LayoutTrellis, - LayoutTrellisLine, LayoutView, Nav, - SelectElement, t, view_effect, } from "@radroots/svelte-lib"; - import { - fiat_currencies, - fmt_trade_quantity_tup, - mass_units, - parse_trade_key, - trade, - trade_keys, - type TradeKey, - } from "@radroots/utils"; + import { type TradeKey } from "@radroots/utils"; import { onMount } from "svelte"; - import { writable } from "svelte/store"; import { fade } from "svelte/transition"; + const CAROUSEL_INDEX_MAP = 2; + type CarouselParam = { label_prev?: string; label_next: string; }; const page_param: { + carousel: Record<View, Map<number, CarouselParam>>; trade_product: { key_default: TradeKey; }; - carousel: Map<number, CarouselParam>; } = { - carousel: new Map<number, CarouselParam>([ - [ - 0, - { - label_next: `${$t(`common.add`)}`, - }, - ], - [ - 1, - { - label_next: `${$t(`icu.add_*`, { value: `${$t(`common.location`)}` })}`, - }, - ], - [ - 2, - { - label_next: `${$t(`icu.add_*`, { value: `${$t(`common.listing`)}` })}`, - }, - ], - [ - 3, - { - label_next: `${$t(`common.preview`)}`, - }, - ], - [ - 4, - { - label_next: `${$t(`common.publish`)}`, - }, - ], - ]), + carousel: { + fl_1: new Map<number, CarouselParam>([ + [ + 0, + { + label_next: `${$t(`common.add`)}`, + }, + ], + [ + 1, + { + label_next: `${$t(`common.location`)}`, + }, + ], + [ + 2, + { + label_next: `*`, + }, + ], + [ + 3, + { + label_next: `Post`, + }, + ], + ]), + }, trade_product: { key_default: `coffee`, }, }; - type View = `main` | `finish`; - let view: View = `main`; + let view_init: View = `fl_1`; + type View = `fl_1`; + let view: View = view_init; $: { view_effect<View>(view); } - let loading_submit = false; - - let photo_add_loading = false; - let photo_add_list: { file_path: string }[] = []; + let load_submit = false; let tradeproduct_key_sel_toggle = false; - const tradeproduct_key_sel = writable<string>(``); - $: tradeproduct_key_parsed = parse_trade_key($tradeproduct_key_sel); - $: ls_trade_product_quantities = tradeproduct_key_parsed - ? trade.key[tradeproduct_key_parsed].quantity - : trade.default.quantity; - - const tradeproduct_price_curr_sel = writable<string>(``); - const tradeproduct_price_qty_unit_sel = writable<string>(``); + let tradeproduct_key_sel = ``; - const tradeproduct_qty_unit_tup_sel = writable<string>(``); - let tradeproduct_qty_unit_tup_sel_toggle = false; - - const tradeproduct_process_sel = writable<string>(``); - let tradeproduct_process_sel_toggle = false; - $: ls_trade_product_processes = tradeproduct_key_parsed - ? trade.key[tradeproduct_key_parsed].process - : []; + let photo_add_paths: string[] = []; onMount(async () => { try { - await kv_init(); - await handle_view(`main`); + await handle_view(view_init); + layout_view_cover.set(false); carousel_index.set(0); - carousel_index_max.set(page_param.carousel.size - 1); - tradeproduct_price_curr_sel.set(`eur`); - tradeproduct_price_qty_unit_sel.set(`kg`); - await setup_test(); + carousel_index_max.set(page_param.carousel[view].size - 1); + await setup_tests(); } catch (e) { } finally { - await handle_view(`main`); } }); - tradeproduct_key_sel.subscribe(async (_tradeproduct_key_sel) => { - await kv.set(fmt_id(`key`), _tradeproduct_key_sel); - }); - - $: if (tradeproduct_key_parsed) { - tradeproduct_qty_unit_tup_sel.set( - fmt_trade_quantity_tup( - trade.key[tradeproduct_key_parsed].quantity[0], - ), - ); - tradeproduct_process_sel.set( - trade.key[tradeproduct_key_parsed].process[0], - ); - } - - tradeproduct_price_curr_sel.subscribe( - async (_tradeproduct_price_curr_sel) => { - await kv.set( - fmt_id(`price_currency`), - _tradeproduct_price_curr_sel, - ); - }, - ); - - tradeproduct_price_qty_unit_sel.subscribe( - async (_tradeproduct_price_qty_unit_sel) => { - await kv.set( - fmt_id(`price_qty_unit`), - _tradeproduct_price_qty_unit_sel, - ); - }, - ); - - tradeproduct_qty_unit_tup_sel.subscribe( - async (_tradeproduct_qty_unit_tup_sel) => { - await kv.set(fmt_id(`qty_unit`), _tradeproduct_qty_unit_tup_sel); - }, - ); - - const setup_test = async (): Promise<void> => { - try { - tradeproduct_key_sel.set(page_param.trade_product.key_default); - - await kv.set( - fmt_id(`summary`), - [`This is the first line`, `This is the another line`].join( - `.\n`, - ), - ); - } catch (e) { - console.log(`(error) setup_test `, e); - } - }; - - const kv_init = async (): Promise<void> => { + const setup_tests = 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, ``))); + //const photo1 = `file:///Users/treesap/Library/Developer/CoreSimulator/Devices/04252089-B59A-4955-B1E0-84ECBAC1C28D/data/Containers/Data/Application/73E35A31-5CCD-4C1A-97C3-B5FA617DDB80/Library/Caches/IMG_0111.jpeg`; + //photo_add_list_paths = [photo1]; } catch (e) { - console.log(`(error) kv_init `, e); + console.log(`(error) setup_tests `, e); } }; - const handle_view = async (new_view: View): Promise<void> => { + const handle_view = async (view_new: View): Promise<void> => { try { - view = new_view; + /* + const index_max_new = page_param.carousel[view_new].size - 1; + carousel_index_max.set(index_max_new); + if (view === `fl_2` && view_new === `fl_1`) + carousel_index.set(index_max_new); + else carousel_index.set(0); + */ + carousel_index.set(0); + view = view_new; } catch (e) { console.log(`(error) handle_view `, e); } }; - const toggle_tradeproduct_key = async ( - visible_input: boolean, - ): Promise<void> => { - try { - tradeproduct_key_sel_toggle = visible_input; - if (visible_input) { - tradeproduct_key_sel.set(``); - } else { - //@todo tradeproduct_key_sel = trade_keys[0]; - } - } catch (e) { - console.log(`(error) toggle_tradeproduct_key `, e); - } - }; - - const toggle_tradeproduct_qty_amt = async ( - visible_input: boolean, - ): Promise<void> => { - try { - tradeproduct_qty_unit_tup_sel_toggle = visible_input; - if (visible_input) { - tradeproduct_qty_unit_tup_sel.set(mass_units[0]); - } else { - $tradeproduct_qty_unit_tup_sel = tradeproduct_key_parsed - ? fmt_trade_quantity_tup( - trade.key[tradeproduct_key_parsed].quantity[0], - ) - : ``; - } - } catch (e) { - console.log(`(error) toggle_tradeproduct_qty_amt `, e); - } - }; - - const toggle_tradeproduct_process = async ( - visible_input: boolean, - ): Promise<void> => { - try { - tradeproduct_process_sel_toggle = visible_input; - if (visible_input) { - tradeproduct_process_sel.set(``); - } else { - tradeproduct_process_sel.set( - ls_trade_product_processes.length - ? ls_trade_product_processes[0] - : ``, - ); - } - } catch (e) { - console.log(`(error) toggle_tradeproduct_process `, e); - } - }; - - const handle_photo_add = async (): Promise<void> => { - try { - photo_add_loading = true; - const photo_paths = await dialog.open_photos(); - if (!photo_paths) { - return; //@todo - } - const file_path = photo_paths.results[0]; - photo_add_list = [ - ...photo_add_list, - { - file_path, - }, - ]; - } catch (e) { - console.log(`(error) handle_photo_add `, e); - } finally { - photo_add_loading = false; - } - }; - - const handle_back = async (carousel_offset?: number): Promise<void> => { + const handle_back = async (): Promise<void> => { try { - switch ($carousel_index) { - default: - { - await carousel_dec(view); - } - break; - } - if (carousel_offset) { - carousel_num.set(carousel_offset); - carousel_index.set($carousel_index - (carousel_offset - 1)); - } } catch (e) { console.log(`(error) handle_back `, e); } @@ -302,50 +122,23 @@ const handle_continue = async (): Promise<void> => { try { - switch ($carousel_index) { - case 0: - { - const validate_fields = - await validate_trade_product_fields({ - kv_pref: fmt_id(), - fields: [`key`, `summary`], - }); - if (`err` in validate_fields) { - await dialog.alert( - `${$t(`icu.enter_a_*`, { value: `${$t(`model.trade_product.${validate_fields.err}`)}`.toLowerCase() })}`, - ); - return; - } - - if (photo_add_list.length < 1) { - await dialog.alert(`A primary photo is required`); - return; //@todo - } - - await carousel_inc(view); - } - break; - case 1: + switch (view) { + case `fl_1`: { - const validate_fields = - await validate_trade_product_fields({ - kv_pref: fmt_id(), - fields: [ - `price_amt`, - `price_currency`, - `price_qty_unit`, - ], - }); - if (`err` in validate_fields) { - await dialog.alert( - `${$t(`icu.enter_the_*`, { value: `${$t(`model.trade_product.${validate_fields.err}`)}`.toLowerCase() })}`, - ); - return; + switch ($carousel_index) { + case 0: + { + console.log( + JSON.stringify( + photo_add_paths, + null, + 4, + ), + `photo_add_paths`, + ); + } + break; } - console.log( - JSON.stringify(validate_fields, null, 4), - `validate_fields`, - ); } break; } @@ -364,521 +157,83 @@ <LayoutView> <div - in:fade={{ delay: 100, duration: 200 }} - out:fade={{ delay: 0, duration: 200 }} - data-carousel-container={`main`} - class={`carousel-container flex h-full w-full`} + data-view={`fl_1`} + class={`flex flex-col h-full w-full justify-start items-center`} > <div - data-carousel-item={`main`} - class={`carousel-item flex flex-col w-full pt-4 justify-start items-center`} - > - <LayoutTrellis> - <ImageUploadRow - basis={{ - loading: photo_add_loading, - file_paths: photo_add_list.map( - ({ file_path }) => file_path, - ), - callback_add: handle_photo_add, - }} - /> - <LayoutTrellisLine - basis={{ - label: { - value: `${$t(`common.product`)}`, - }, - notify: tradeproduct_key_sel_toggle - ? { - label: { - value: `${$t(`common.back`)}`, - }, - glyph: { - key: `selection-foreground`, - weight: `bold`, - dim: `xs`, - }, - callback: async () => { - const kv_other = await kv.get( - fmt_id(`key`), - ); - if (kv_other) { - const confirm = await dialog.confirm({ - message: `${$t(`icu.the_current_entry_*_will_be_deleted`, { value: kv_other })}. ${$t(`common.do_you_want_to_continue_q`)}`, - }); - if (confirm === false) return; - } - await toggle_tradeproduct_key(false); - }, - } - : undefined, - }} - > - {#if !tradeproduct_key_sel_toggle} - <EntrySelect - bind:value={$tradeproduct_key_sel} - basis={{ - wrap: { - id: fmt_id(`key_wrap`), - }, - el: { - id: fmt_id(`key`), - layer: 1, - options: [ - { - entries: [ - { - value: ``, - label: `Choose product`, - disabled: true, - }, - ...trade_keys.map((i) => ({ - value: i, - label: `${$t(`trade.product.key.${i}.name`, { default: i })}`, - })), - { - value: `other`, - label: `${$t(`common.other`)}`, - }, - ], - }, - ], - callback: async (opt) => { - const el = el_id(fmt_id(`key_wrap`)); - el?.classList.remove( - `entry-layer-1-highlight`, - ); - if (opt.value === `other`) { - await toggle_tradeproduct_key(true); - } - }, - }, - }} - /> - {:else} - <EntryLine - basis={{ - wrap: { - id: fmt_id(`key_wrap`), - }, - el: { - classes: `fade-in-long`, - id: fmt_id(`key`), - sync: true, - placeholder: `${$t(`icu.enter_the_*`, { value: `${$t(`icu.*_name`, { value: `${$t(`common.product`)}` })}`.toLowerCase() })}`, - field: { - charset: - trade_product_form_fields.title - .charset, - validate: - trade_product_form_fields.title - .validation, - validate_keypress: true, - }, - }, - }} - /> - {/if} - </LayoutTrellisLine> - <LayoutTrellisLine - basis={{ - label: { - value: `${$t(`common.description`)}`, - }, - }} - > - <EntryMultiline - basis={{ - wrap: { - id: fmt_id(`summary_wrap`), - }, - el: { - classes: `h-[14rem]`, - id: fmt_id(`summary`), - sync: true, - placeholder: `${$t(`icu.enter_the_*`, { value: `${$t(`icu.*_description`, { value: `${$t(`common.listing`)}` })}`.toLowerCase() })}`, - field: { - charset: - trade_product_form_fields.summary - .charset, - validate: - trade_product_form_fields.summary - .validation, - validate_keypress: true, - }, - }, - }} - /> - </LayoutTrellisLine> - </LayoutTrellis> - </div> - <div - data-carousel-item={`main`} - class={`carousel-item flex flex-col w-full pt-4 justify-start items-center`} + data-carousel-container={`fl_1`} + class={`carousel-container flex h-full w-full`} > - <ImageUploadDisplay - basis={{ - loading: photo_add_loading, - file_paths: photo_add_list.map( - ({ file_path }) => file_path, - ), - callback_add: handle_photo_add, - callback_edit: async () => {}, - }} - /> - <LayoutTrellis> - <LayoutTrellisLine - basis={{ - label: { - value: `${$t(`icu.*_price`, { value: `${$t(`common.product`)}` })}`, - }, - }} - > - <EntryWrap - basis={{ - classes: `pl-3`, - id: fmt_id(`price_wrap`), - }} - > - <div - class={`flex flex-row justify-start items-center pr-3`} - > - <SelectElement - bind:value={$tradeproduct_price_curr_sel} - basis={{ - id: fmt_id(`price_currency`), - classes: `w-fit font-circ font-[500] text-[1.2rem] -translate-y-[1px]`, - layer: false, - options: [ - { - entries: fiat_currencies.map( - (i) => ({ - value: `${i}`, - label: `${$t(`currency.${i}.symbol`, { default: i })}`, - }), - ), - }, - ], - }} - /> - </div> - <InputElement - basis={{ - id: fmt_id(`price_amt`), - layer: 1, - sync: true, - sync_init: true, - placeholder: `${$t(`common.price`)}`, - field: { - charset: - trade_product_form_fields.price_amt - .charset, - validate: - trade_product_form_fields.price_amt - .validation, - validate_keypress: true, - }, - callback: async ({ value, pass }) => { - const lastchar = value[value.length - 1]; - const period_count = - value.split(".").length - 1; - if ( - (pass && - lastchar !== `.` && - period_count < 2) || - value.length < 1 - ) { - const el = el_id(fmt_id(`price_wrap`)); - el?.classList.remove( - `entry-layer-1-highlight`, - ); - } else { - const el = el_id(fmt_id(`price_wrap`)); - el?.classList.add( - `entry-layer-1-highlight`, - ); - } - }, - callback_blur: async ({ el }) => { - if (!el.value) return; - el.value = fmt_price( - $tradeproduct_price_curr_sel, - el.value, - ).slice(1); - }, - }} - /> - <div - class={`flex flex-row gap-2 justify-end items-center text-layer-1-glyph/70`} - > - <p class={`font-circ font-[500] text-[1.05rem]`}> - {`${$t(`common.per`)}`} - </p> - <SelectElement - bind:value={$tradeproduct_price_qty_unit_sel} - basis={{ - id: fmt_id(`price_qty_unit`), - classes: `w-fit font-circ font-[500] text-[1.05rem]`, - layer: false, - options: [ - { - entries: mass_units.map((i) => ({ - value: i, - label: `${$t(`measurement.mass.unit.${i}_ab`, { default: i })}`.toLowerCase(), - })), - }, - ], - }} - /> - </div> - </EntryWrap> - </LayoutTrellisLine> - <LayoutTrellisLine - basis={{ - label: { - value: `${$t(`common.quantity`)}`, - }, - notify: tradeproduct_qty_unit_tup_sel_toggle - ? { - label: { - value: `${$t(`common.back`)}`, - }, - glyph: { - key: `selection-foreground`, - weight: `bold`, - dim: `xs`, - }, - callback: async () => { - await toggle_tradeproduct_qty_amt(false); - }, - } - : undefined, - }} - > - {#if !tradeproduct_qty_unit_tup_sel_toggle} - <EntrySelect - bind:value={$tradeproduct_qty_unit_tup_sel} - basis={{ - wrap: { - id: fmt_id(`qty_wrap`), - }, - el: { - layer: 1, - options: [ - { - entries: [ - ...ls_trade_product_quantities.map( - (i) => ({ - value: fmt_trade_quantity_tup( - i, - ), - label: `${i.mass} ${$t(`measurement.mass.unit.${i.mass_unit}_ab`, { default: i.mass_unit })} ${i.label}`, - }), - ), - { - value: `other`, - label: `${$t(`common.other`)}`, - }, - ], - }, - ], - callback: async ({ value }) => { - el_id( - fmt_id(`qty_wrap`), - )?.classList.remove( - `entry-layer-1-highlight`, - ); - if (value === `other`) { - await toggle_tradeproduct_qty_amt( - true, - ); - } else { - await kv.set( - fmt_id(`qty_avail`), - `1`, - ); - } - }, - }, - }} - /> - {:else} - <EntryWrap - basis={{ - id: fmt_id(`qty_wrap`), - }} - > - <InputElement - basis={{ - id: fmt_id(`qty_amt`), - layer: 1, - sync: true, - placeholder: `${$t(`icu.enter_*_per_order`, { value: `${$t(`common.quantity`)}`.toLowerCase() })}`, - field: { - charset: - trade_product_form_fields.qty_amt - .charset, - validate: - trade_product_form_fields.qty_amt - .validation, - validate_keypress: true, - }, - }} - /> - <div - class={`absolute top-0 right-0 flex flex-row h-full pr-4 justify-end items-center`} - > - <SelectElement - bind:value={$tradeproduct_price_qty_unit_sel} - basis={{ - id: fmt_id(`qty_unit`), - classes: `w-fit font-circ font-[500] text-[1.1rem] text-layer-1-glyph/70`, - layer: false, - options: [ - { - entries: mass_units.map( - (i) => ({ - value: i, - label: `${$t(`measurement.mass.unit.${i}_ab`, { default: i })}`, - }), - ), - }, - ], - }} - /> - </div> - </EntryWrap> - {/if} - </LayoutTrellisLine> - <LayoutTrellisLine - basis={{ - label: { - value: `${$t(`common.process`)}`, - }, - notify: tradeproduct_process_sel_toggle - ? { - label: { - value: `${$t(`common.back`)}`, - }, - glyph: { - key: `selection-foreground`, - weight: `bold`, - dim: `xs`, - }, - glyph_last: true, - callback: async () => { - await toggle_tradeproduct_process(false); - }, - } - : undefined, - }} - > - {#if !tradeproduct_process_sel_toggle} - <EntrySelect - bind:value={$tradeproduct_process_sel} - basis={{ - wrap: { - id: fmt_id(`process_wrap`), - }, - el: { - layer: 1, - options: [ - { - entries: [ - ...ls_trade_product_processes.map( - (i) => ({ - value: i, - label: `${$t(`trade.product.key.${tradeproduct_key_parsed}.process.${i}`)}`, - }), - ), - { - value: `other`, - label: `${$t(`common.other`)}`, - }, - ], - }, - ], - callback: async ({ value }) => { - el_id( - fmt_id(`process_wrap`), - )?.classList.remove( - `entry-layer-1-highlight`, - ); - if (value === `other`) { - await toggle_tradeproduct_process( - true, - ); - } - }, - }, - }} - /> - {:else} - <EntryWrap - basis={{ - id: fmt_id(`process_wrap`), - }} - > - <InputElement - basis={{ - id: fmt_id(`process`), - layer: 1, - sync: true, - placeholder: `Enter the process`, - field: { - charset: - trade_product_form_fields.process - .charset, - validate: - trade_product_form_fields.process - .validation, - validate_keypress: true, - }, - }} - /> - </EntryWrap> - {/if} - </LayoutTrellisLine> - </LayoutTrellis> + <div + data-carousel-item={`fl_1`} + class={`carousel-item flex flex-col w-full justify-start items-center`} + > + <LayoutTrellis> + <ImageUploadMulti bind:photo_paths={photo_add_paths} /> + </LayoutTrellis> + </div> </div> </div> </LayoutView> -<Nav - basis={{ - prev: { - label: `${$t(`common.back`)}`, - route: `/models/trade-product`, - prevent_route: - $carousel_index > 0 - ? { - callback: async () => { - await handle_back(); +{#if $carousel_index !== CAROUSEL_INDEX_MAP} + <div + in:fade={{ delay: 0, duration: 50 }} + out:fade={{ delay: 50, duration: 200 }} + class={`flex flex-col w-full justify-start items-center fade-in`} + > + <Nav + basis={{ + prev: { + label: `${$t(`common.back`)}`, + route: `/models/trade-product`, + prevent_route: + view === `fl_1` && $carousel_index === 0 + ? undefined + : { + callback: async () => { + await handle_back(); + }, + }, + callback: async () => { + await tradeproduct_init_kv(fmt_id()); + }, + }, + title: + $carousel_index === CAROUSEL_INDEX_MAP + ? undefined + : { + label: { + value: `${$t(`icu.new_*`, { value: `${$t(`common.product`)}` })}`, + }, + callback: async () => {}, }, - } - : undefined, - callback: async () => { - await kv_init_trade_product_fields(fmt_id()); - }, - }, - title: { - label: { - value: `${$t(`icu.new_*`, { value: `${$t(`common.product`)}` })}`, - }, - callback: async () => {}, - }, - option: { - loading: loading_submit, - label: { - value: - $carousel_num > 1 - ? `${$t(`common.return`)}` - : page_param.carousel.get($carousel_index) - ?.label_next || ``, - }, - callback: async () => { - if ($carousel_index === $carousel_index_max) await submit(); - else await handle_continue(); - }, - }, - }} -/> + option: { + loading: load_submit, + label: + $carousel_index === CAROUSEL_INDEX_MAP + ? undefined + : { + value: + $carousel_num > 1 + ? `${$t(`common.return`)}` + : page_param.carousel[view].get( + $carousel_index, + )?.label_next || ``, + glyph: + $carousel_index > 0 + ? { + key: `caret-right`, + classes: `text-layer-1-glyph-hl`, + } + : undefined, + }, + callback: async () => { + if ($carousel_index === $carousel_index_max) + await submit(); + else await handle_continue(); + }, + }, + }} + /> + </div> +{/if} diff --git a/src/routes/(app)/test/+page.svelte b/src/routes/(app)/test/+page.svelte @@ -1,4 +1,7 @@ <script lang="ts"> + import { LayoutView } from "@radroots/svelte-lib"; </script> -<div>test</div> +<LayoutView> + <div class={`flex flex-col w-full justify-center items-center`}></div> +</LayoutView> diff --git a/src/routes/(cfg)/cfg/init/+page.svelte b/src/routes/(cfg)/cfg/init/+page.svelte @@ -820,7 +820,6 @@ classes: `h-entry_guide w-${$app_layout} bg-layer-1-surface rounded-touch font-mono text-lg placeholder:opacity-60 items-end text-center`, id: fmt_id(page_param.kv.nostr_secretkey), sync: true, - sync_init: true, placeholder: `${$t(`icu.enter_*`, { value: `nostr nsec/hex` })}`, field: { charset: regex.profile_name_ch, @@ -891,7 +890,6 @@ classes: `font-mono text-lg text-center placeholder:opacity-60`, id: fmt_id(page_param.kv.nostr_profilename), sync: true, - sync_init: true, placeholder: `${$t(`icu.enter_*`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })}`, field: { charset: regex.profile_name_ch, diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -1,5 +1,6 @@ import { theme_colors, themes } from "@radroots/theme"; import { wind } from "@radroots/utils"; +import aspect_ratio from "@tailwindcss/aspect-ratio"; import daisyui from "daisyui"; import type { Config } from "tailwindcss"; import tailwind_default from "tailwindcss/defaultTheme"; @@ -70,6 +71,27 @@ const config: Config = { mobile_y: { raw: `(orientation: portrait) and (min-height: ${wind.app.layout.mobile_y}px)` }, mobile_base: { raw: `(orientation: portrait) and (max-height: ${wind.app.layout.mobile_base}px)` }, }, + aspectRatio: { + auto: 'auto', + square: '1 / 1', + video: '16 / 9', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', + 14: '14', + 15: '15', + 16: '16', + }, extend: { colors: { ...theme_colors, @@ -77,7 +99,7 @@ const config: Config = { 'chart-red': 'var(--chart-color-red)', }, fontFamily: { - sans: ['SF Pro Display', ...tw_font.sans], + sans: ['SF Pro Rounded', ...tw_font.sans], serif: [...tw_font.serif], mono: [...tw_font.mono], apercu: ['Apercu Mono Pro'], @@ -152,7 +174,8 @@ const config: Config = { } }, plugins: [ - daisyui + daisyui, + aspect_ratio ], daisyui: { themes: [