web_lib

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

commit a39e8157b0e3d9e5627d44f5e6671819ce955abf
parent a9cfe61e53baac595ce39942b79abb512f4b9044
Author: triesap <triesap@radroots.dev>
Date:   Fri, 21 Nov 2025 02:32:23 +0000

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

Diffstat:
Aapps-lib-pwa/src/lib/components/button/button-round-nav.svelte | 26++++++++++++++++++++++++++
Mapps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte | 2+-
Aapps-lib-pwa/src/lib/components/lib/float-page.svelte | 12++++++++++++
Aapps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte | 46++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib-pwa/src/lib/index.ts | 4++++
Mapps-lib-pwa/src/lib/types/components/lib.ts | 8++++----
Rapps-lib-pwa/src/lib/types/views/farm.ts -> apps-lib-pwa/src/lib/types/views/farms.ts | 0
Aapps-lib-pwa/src/lib/types/views/profile.ts | 12++++++++++++
Mapps-lib-pwa/src/lib/utils/farm/schema.ts | 2+-
Mapps-lib-pwa/src/lib/views/farms/farms-add.svelte | 2+-
Mapps-lib-pwa/src/lib/views/farms/farms.svelte | 2+-
Aapps-lib-pwa/src/lib/views/profile/profile.svelte | 341+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 449 insertions(+), 8 deletions(-)

diff --git a/apps-lib-pwa/src/lib/components/button/button-round-nav.svelte b/apps-lib-pwa/src/lib/components/button/button-round-nav.svelte @@ -0,0 +1,26 @@ +<script lang="ts"> + import { LoadSymbol } from "$lib"; + import type { IButtonNavRound } from "$lib/types/components/lib"; + import { Glyph } from "@radroots/apps-lib"; + + let { basis }: { basis: IButtonNavRound } = $props(); +</script> + +<button + class={`flex flex-row h-12 w-12 justify-center items-center bg-ly1 rounded-full el-re`} + disabled={!!basis.disabled} + onclick={basis.callback} +> + {#if basis.loading} + <LoadSymbol /> + {:else} + <Glyph + basis={{ + classes: `text-ly0-gl`, + dim: `sm+`, + + key: basis.glyph, + }} + /> + {/if} +</button> diff --git a/apps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte b/apps-lib-pwa/src/lib/components/farm/farms-preview-card.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import MapMarkerArea from "$lib/components/map/map-marker-area.svelte"; import Map from "$lib/components/map/map.svelte"; - import type { FarmExtended } from "$lib/types/views/farm"; + import type { FarmExtended } from "$lib/types/views/farms"; import { get_context } from "@radroots/apps-lib"; import { fmt_geolocation_address, diff --git a/apps-lib-pwa/src/lib/components/lib/float-page.svelte b/apps-lib-pwa/src/lib/components/lib/float-page.svelte @@ -0,0 +1,12 @@ +<script lang="ts"> + import type { IFloatPage } from "$lib/types/components/lib"; + import type { Snippet } from "svelte"; + + let { basis, children }: { basis: IFloatPage; children: Snippet } = + $props(); +</script> + +<div class={`absolute top-10 ${basis.posx}-6 flex flex-row`}> + {@render children()} +</div> +<div class="hidden left-6 right-6"></div> diff --git a/apps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte b/apps-lib-pwa/src/lib/components/media/image-upload-photo-add.svelte @@ -0,0 +1,46 @@ +<script lang="ts"> + import { get_context, Glyph } from "@radroots/apps-lib"; + import LoadSymbol from "../lib/load-symbol.svelte"; + + const { ls, lc_photos_add } = get_context(`lib`); + + let { + basis, + photo_path = $bindable(``), + }: { + basis: { + loading?: boolean; + }; + photo_path: string; + } = $props(); +</script> + +<div class={`relative flex flex-row w-full justify-center items-center`}> + <button + class={`flex flex-row h-[5rem] w-[5rem] justify-center items-center bg-ly1/60 rounded-full`} + onclick={async () => { + const photo_paths_add = await lc_photos_add(); + if (photo_paths_add) photo_path = photo_paths_add[0]; + }} + > + {#if basis.loading} + <LoadSymbol basis={{ dim: `md` }} /> + {:else} + <Glyph + basis={{ + classes: `text-[40px] text-ly2-gl`, + dim: `sm`, + key: `camera`, + }} + /> + {/if} + + <div + class={`absolute -bottom-[1.8rem] flex flex-row justify-start items-center`} + > + <p class={`font-arch font-[600] text-sm text-ly0-gl capitalize`}> + {`${$ls(`icu.add_*`, { value: `${$ls(`common.photo`)}` })}`} + </p> + </div> + </button> +</div> diff --git a/apps-lib-pwa/src/lib/index.ts b/apps-lib-pwa/src/lib/index.ts @@ -3,6 +3,7 @@ export { default as ButtonLabelDashed } from "./components/button/button-label-d export { default as ButtonLayoutBottom } from "./components/button/button-layout-bottom.svelte"; export { default as ButtonLayoutPair } from "./components/button/button-layout-pair.svelte"; export { default as ButtonLayout } from "./components/button/button-layout.svelte"; +export { default as ButtonRoundNav } from "./components/button/button-round-nav.svelte"; export { default as ButtonSimple } from "./components/button/button-simple.svelte"; export { default as FarmsAddDetail } from "./components/farm/farms-add-detail.svelte"; export { default as FarmsAddMap } from "./components/farm/farms-add-map.svelte"; @@ -18,6 +19,7 @@ export { default as LayoutWindow } from "./components/layout/layout-window.svelt export { default as CarouselContainer } from "./components/lib/carousel-container.svelte"; export { default as CarouselItem } from "./components/lib/carousel-item.svelte"; export { default as Css } from "./components/lib/css.svelte"; +export { default as FloatPage } from "./components/lib/float-page.svelte"; export { default as InputPwa } from "./components/lib/input-pwa.svelte"; export { default as InputValue } from "./components/lib/input-value.svelte"; export { default as LoadCircle } from "./components/lib/load-circle.svelte"; @@ -31,9 +33,11 @@ export { default as WrapBorder } from "./components/lib/wrap-border.svelte"; export { default as MapMarkerAreaDisplay } from "./components/map/map-marker-area-display.svelte"; export { default as MapMarkerArea } from "./components/map/map-marker-area.svelte"; export { default as Map } from "./components/map/map.svelte"; +export { default as ImageUploadPhotoAdd } from "./components/media/image-upload-photo-add.svelte"; export { default as NavigationTabs } from "./components/navigation/navigation-tabs.svelte"; export { default as PageHeader } from "./components/navigation/page-header.svelte"; export { default as PageToolbar } from "./components/navigation/page-toolbar.svelte"; export { default as FarmsAdd } from "./views/farms/farms-add.svelte"; export { default as Farms } from "./views/farms/farms.svelte"; +export { default as Profile } from "./views/profile/profile.svelte"; export { default as Home } from "./views/root/home.svelte"; diff --git a/apps-lib-pwa/src/lib/types/components/lib.ts b/apps-lib-pwa/src/lib/types/components/lib.ts @@ -26,10 +26,6 @@ export type IGlyphCircle = { glyph: IGlyph }; -export type IFloatPage = { - posx: Omit<GeometryScreenPositionHorizontal, "center">; -}; - export type IButtonNavRound = ICb & IDisabledOpt & ILoadingOpt & IGlyphKey; export type CarouselMouseEvent = MouseEvent & { @@ -54,4 +50,8 @@ export type ICarouselItem<T extends string> = IClOpt & { export type ILoadCircle = IClOpt & { dim?: LoadingDimension; +}; + +export type IFloatPage = { + posx: Omit<GeometryScreenPositionHorizontal, "center">; }; \ No newline at end of file diff --git a/apps-lib-pwa/src/lib/types/views/farm.ts b/apps-lib-pwa/src/lib/types/views/farms.ts diff --git a/apps-lib-pwa/src/lib/types/views/profile.ts b/apps-lib-pwa/src/lib/types/views/profile.ts @@ -0,0 +1,12 @@ +import type { NostrProfile } from "@radroots/tangle-schema-bindings"; + +export type IViewProfileData = { + profile: NostrProfile; +}; + +export type ViewProfileEditFieldKey = `name` | `display_name` | `about`; + +export type IViewProfileEditData = { + public_key: string; + field: ViewProfileEditFieldKey; +}; diff --git a/apps-lib-pwa/src/lib/utils/farm/schema.ts b/apps-lib-pwa/src/lib/utils/farm/schema.ts @@ -1,5 +1,5 @@ import { dev } from "$app/environment"; -import type { IViewFarmsAddSubmission, IViewFarmsProductsAddSubmitPayload } from "$lib/types/views/farm"; +import type { IViewFarmsAddSubmission, IViewFarmsProductsAddSubmitPayload } from "$lib/types/views/farms"; import { form_fields, schema_geocode_result, schema_geolocation_point, util_rxp, zf_numf_pos, zf_numi_pos, zf_price } from "@radroots/utils"; import { z } from "zod"; diff --git a/apps-lib-pwa/src/lib/views/farms/farms-add.svelte b/apps-lib-pwa/src/lib/views/farms/farms-add.svelte @@ -8,7 +8,7 @@ import CarouselItem from "$lib/components/lib/carousel-item.svelte"; import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; import { app_platform } from "$lib/stores/app"; - import type { IViewFarmsAddSubmission } from "$lib/types/views/farm"; + import type { IViewFarmsAddSubmission } from "$lib/types/views/farms"; import { schema_view_farms_add_submission } from "$lib/utils/farm/schema"; import { focus_map_marker } from "$lib/utils/map"; import { diff --git a/apps-lib-pwa/src/lib/views/farms/farms.svelte b/apps-lib-pwa/src/lib/views/farms/farms.svelte @@ -6,7 +6,7 @@ import LayoutView from "$lib/components/layout/layout-view.svelte"; import PageToolbar from "$lib/components/navigation/page-toolbar.svelte"; import type { IViewBasis } from "$lib/types/views"; - import type { IViewFarmsData } from "$lib/types/views/farm"; + import type { IViewFarmsData } from "$lib/types/views/farms"; import { Fade, get_context, diff --git a/apps-lib-pwa/src/lib/views/profile/profile.svelte b/apps-lib-pwa/src/lib/views/profile/profile.svelte @@ -0,0 +1,341 @@ +<script lang="ts"> + import { NavigationTabs, SelectMenu } from "$lib"; + import ButtonRoundNav from "$lib/components/button/button-round-nav.svelte"; + import FloatPage from "$lib/components/lib/float-page.svelte"; + import ImageUploadPhotoAdd from "$lib/components/media/image-upload-photo-add.svelte"; + import type { IViewBasis } from "$lib/types/views"; + import type { + IViewProfileData, + ViewProfileEditFieldKey, + } from "$lib/types/views/profile"; + import { + get_context, + Glyph, + idb_kv_init_page, + ImagePath, + symbols, + type IViewOnDestroy, + } from "@radroots/apps-lib"; + import { + handle_err, + type CallbackPromise, + type CallbackPromiseGeneric, + } from "@radroots/utils"; + import { onDestroy, onMount } from "svelte"; + + const { ls } = get_context(`lib`); + + let { + basis, + photo_path = $bindable(``), + }: { + basis: IViewBasis<{ + data?: IViewProfileData; + loading_photo_upload: boolean; + loading_photo_upload_open: boolean; + on_handle_back: CallbackPromiseGeneric<{ + is_photo_existing: boolean; + }>; + on_handle_photo_options: CallbackPromise; + on_handle_edit_profile_field: CallbackPromiseGeneric<{ + field: ViewProfileEditFieldKey; + }>; + }> & + IViewOnDestroy<{ public_key: string }>; + photo_path: string; + } = $props(); + + type ViewDisplay = `photos` | `following` | `followers`; + let view_display: ViewDisplay = $state(`photos`); + + let val_sel_options_button = $state(``); + + onMount(async () => { + try { + if (!basis.kv_init_prevent) await idb_kv_init_page(); + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + onDestroy(async () => { + try { + if (basis.data?.profile.public_key) + await basis.on_destroy({ + public_key: basis.data.profile.public_key, + }); + } catch (e) { + handle_err(e, `on_destroy`); + } + }); + + const photo_overlay_visible = $derived( + !!(basis.data?.profile.picture || photo_path), + ); + + const classes_photo_overlay_glyph = $derived( + photo_overlay_visible ? `text-white` : `text-ly0-gl`, + ); + + const classes_photo_overlay_glyph_opt = $derived( + photo_overlay_visible ? `text-gray-300` : `text-ly0-gl`, + ); + + const classes_photo_overlay_glyph_opt_selected = $derived( + photo_overlay_visible ? `text-white` : `text-ly1-gl_d`, + ); +</script> + +{#if basis.data} + <div + class={`relative flex flex-col min-h-[525px] h-[525px] w-full justify-center items-center bg-ly2 fade-in`} + > + <FloatPage + basis={{ + posx: `left`, + }} + > + <ButtonRoundNav + basis={{ + glyph: `arrow-left`, + loading: basis.loading_photo_upload, + callback: async () => { + await basis.on_handle_back({ + is_photo_existing: photo_overlay_visible, + }); + }, + }} + /> + </FloatPage> + <FloatPage + basis={{ + posx: `right`, + }} + > + <SelectMenu + bind:value={val_sel_options_button} + basis={{ + layer: 0, + options: [ + { + entries: [ + { + value: `*add-new`, + label: `Add new photo`, + }, + ], + }, + ], + }} + > + <ButtonRoundNav + basis={{ + glyph: `images-square`, + callback: basis.on_handle_photo_options, + }} + /> + </SelectMenu> + </FloatPage> + {#if basis.data.profile.picture || photo_path} + {@const img_path = photo_path || basis.data.profile.picture || ``} + <ImagePath basis={{ path: img_path }} /> + {:else} + <div + class={`flex flex-row justify-start items-center -translate-y-8`} + > + <ImageUploadPhotoAdd + bind:photo_path + basis={{ + loading: basis.loading_photo_upload_open, + }} + /> + </div> + {/if} + <div + class={`absolute bottom-0 left-0 flex flex-col h-[calc(100%-100%/1.618)] w-full px-6 gap-2 justify-end items-center`} + > + <div + class={`flex flex-col w-full gap-[2px] justify-center items-center`} + > + <div + class={`flex flex-row h-10 w-full justify-start items-center`} + > + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `display_name`, + }); + }} + > + <p + class={`font-sansd font-[600] text-[2rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.name + ? `` + : `capitalize opacity-active` + } el-re`} + > + {#if basis.data?.profile.display_name} + {`${basis.data?.profile.display_name}`} + {:else if basis.data?.profile.name} + {`${ + basis.data?.profile.display_name || + basis.data?.profile.name || + `` + }`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.profile_name`)}`, + })}`}`} + {/if} + </p> + </button> + </div> + <div + class={`flex flex-row w-full gap-[6px] justify-start items-center`} + > + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `name`, + }); + }} + > + <p + class={`font-sansd font-[600] text-[1.1rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.name + ? `` + : `capitalize opacity-active` + } el-re`} + > + {#if basis.data?.profile.name} + {`@${basis.data.profile.name}`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.username`)}`, + })}`}`} + {/if} + </p> + </button> + <p + class={`font-sans font-[400] ${classes_photo_overlay_glyph}`} + > + {symbols.bullet} + </p> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + alert(`@todo!`); + }} + > + <Glyph + basis={{ + classes: `${classes_photo_overlay_glyph}`, + dim: `xs`, + + key: `link-simple`, + }} + /> + </button> + </div> + <div class={`flex flex-row w-full justify-start items-center`}> + <button + class={`group flex flex-row justify-center items-center`} + onclick={async () => { + await basis.on_handle_edit_profile_field({ + field: `about`, + }); + }} + > + <p + class={`font-sansd font-[400] text-[1.1rem] ${classes_photo_overlay_glyph} ${ + basis.data?.profile.about + ? `` + : `capitalize opacity-active` + }`} + > + {#if basis.data?.profile.about} + {`${basis.data.profile.about}`} + {:else} + {`+ ${`${$ls(`icu.add_*`, { + value: `${$ls(`common.bio`)}`, + })}`}`} + {/if} + </p> + </button> + </div> + </div> + <div + class={`flex flex-row w-full pt-2 pb-6 gap-2 justify-start items-center`} + > + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `photos`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `photos` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.photos`)}`} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `following`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `following` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.following`)}`} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + onclick={async () => { + view_display = `followers`; + }} + > + <p + class={`font-sans text-[1.1rem] font-[600] capitalize ${ + view_display === `followers` + ? classes_photo_overlay_glyph_opt_selected + : classes_photo_overlay_glyph_opt + } el-re`} + > + {`${$ls(`common.followers`)}`} + </p> + </button> + </div> + </div> + </div> + <div + class={`flex flex-col w-full min-h-[500px] justify-start items-center`} + > + {#if view_display === `photos`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {:else if view_display === `following`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {:else if view_display === `followers`} + <p class={`font-sans font-[400] text-ly0-gl`}> + {view_display} + </p> + {/if} + </div> + <NavigationTabs /> +{/if}