web


git clone https://radroots.dev/git/web.git
Log | Files | Refs | Submodules | README | LICENSE

commit c3c5a9b4e04d2461e2919d8aa70e2cbf14d9a620
parent 49e6a77e3661ca273b28841e4f097ac566f6739e
Author: triesap <triesap@radroots.dev>
Date:   Thu, 20 Nov 2025 16:52:08 +0000

Add geolocation field mapping with farm creation and location linking, refactor app utilities and localisations, update `/farms/add` with new view component integrated from `@radroots/apps-lib-pwa`.

Diffstat:
MCONTRIBUTING.md | 4++--
Mapp/.env.example | 4+---
Mapp/package.json | 2+-
Dapp/src/lib/utils/app/app.ts | 46----------------------------------------------
Mapp/src/lib/utils/app/index.ts | 49+++++++++++++++++++++++++++++++++++++++++++++++--
Mapp/src/lib/utils/backup/export.ts | 14+++++++++-----
Mapp/src/lib/utils/backup/import.ts | 55+++++++++++++++++++++++++++++++++++++------------------
Mapp/src/lib/utils/config.ts | 5+++--
Aapp/src/lib/utils/geo/lib.ts | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/routes/(app)/+layout.ts | 3+--
Mapp/src/routes/(app)/+page.svelte | 6+++---
Mapp/src/routes/(app)/farms/+page.svelte | 3+--
Mapp/src/routes/(app)/farms/add/+page.svelte | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mapp/src/routes/(app)/farms/info/+page.svelte | 3++-
Mapp/src/routes/(cfg)/+layout.ts | 3+--
Mapp/src/routes/(cfg)/import/+page.svelte | 26+++++++++++++++-----------
Mapp/src/routes/(cfg)/setup/+page.svelte | 176++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mapp/src/routes/+layout.svelte | 3++-
Mpackage.json | 4++--
Myarn.lock | 9++-------
20 files changed, 372 insertions(+), 172 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md @@ -72,8 +72,8 @@ yarn Configure local environment variables: ```bash echo 'PUBLIC_NOSTR_RELAY_DEFAULTS=ws://localhost:8080,ws://localhost:8081 -PUBLIC_RADROOTS_RELAY_URL=ws://localhost:8082 -PUBLIC_RADROOTS_URL=https://radroots.org +VITE_PUBLIC_RADROOTS_RELAY=ws://localhost:8082 +VITE_PUBLIC_RADROOTS_API=https://radroots.org VITE_PUBLIC_KEYVAL_NAME=rad-roots-pwa-dev-v1 VITE_PUBLIC_NDK_CACHE_NAME=rad-roots-pwa-dev-v1 VITE_PUBLIC_NDK_CLIENT_NAME=rad roots' > app/.env.development diff --git a/app/.env.example b/app/.env.example @@ -1,7 +1,5 @@ PUBLIC_NOSTR_RELAY_DEFAULTS= -PUBLIC_RADROOTS_RELAY_URL= -PUBLIC_RADROOTS_UPLOAD_IMAGES_URL= -PUBLIC_RADROOTS_URL= +VITE_PUBLIC_RADROOTS_API= VITE_PUBLIC_KEYVAL_NAME= VITE_PUBLIC_NDK_CACHE_NAME= VITE_PUBLIC_NDK_CLIENT_NAME= diff --git a/app/package.json b/app/package.json @@ -49,7 +49,7 @@ "chart.js": "^4.4.5", "css-paint-polyfill": "^3.4.0", "idb-keyval": "^6.2.1", - "sql.js": "^1.13.0", + "sql.js": "1.13.0", "svelte-maplibre": "^1.2.0", "zod": "^3.23.8" } diff --git a/app/src/lib/utils/app/app.ts b/app/src/lib/utils/app/app.ts @@ -1,46 +0,0 @@ -import { goto } from "$app/navigation"; -import { datastore, db, nostr_keys, notif } from "$lib/utils/app"; -import { ls } from "$lib/utils/i18n"; -import { create_router, get_store, handle_err } from "@radroots/apps-lib"; -import { app_notify } from "@radroots/apps-lib-pwa/stores/app"; -import type { CallbackPromise } from "@radroots/utils"; -import { reset_sql_cipher } from "./cipher"; -import type { NavigationRoute } from "./routes"; - -export const route = create_router<NavigationRoute>(); - -export const restart = async (opts?: { - notify_message?: string; - route?: NavigationRoute; -}): Promise<void> => { - try { - goto(opts?.route || `/`); - if (opts?.notify_message) app_notify.set(opts.notify_message); - location.reload(); - } catch (e) { - handle_err(e, `restart`); - } -}; - -export const reset = async (): Promise<void> => { - try { - const $ls = get_store(ls); - const confirm = await notif.confirm({ - message: `${$ls(`notification.device.reset`)}. ${$ls(`common.this_action_is_irreversible`)}. ${$ls(`common.do_you_want_to_continue_q`)}` - }); - if (!confirm) return; - await nostr_keys.reset(); - await datastore.reset(); - await reset_sql_cipher(db.get_store_key()); - await db.reinit(); - goto(`/`); - app_notify.set(`${$ls(`notification.device.reset_complete`)}`); - } catch (e) { - handle_err(e, `reset`); - } -}; - -export const message_callback = async (message: string, callback: CallbackPromise): Promise<void> => { - notif.alert(message); - await callback(); -}; diff --git a/app/src/lib/utils/app/index.ts b/app/src/lib/utils/app/index.ts @@ -1,5 +1,8 @@ -import { PUBLIC_RADROOTS_URL } from "$env/static/public"; +import { goto } from "$app/navigation"; import { cfg_data, cfg_datastore_key_map, cfg_datastore_key_obj_map, cfg_datastore_key_param_map } from "$lib/utils/config"; +import { ls } from "$lib/utils/i18n"; +import { create_router, get_store, handle_err } from "@radroots/apps-lib"; +import { app_notify } from "@radroots/apps-lib-pwa/stores/app"; import { WebDatastore } from "@radroots/client/datastore"; import { WebFs } from "@radroots/client/fs"; import { WebGeolocation } from "@radroots/client/geolocation"; @@ -9,6 +12,9 @@ import { WebNotifications } from "@radroots/client/notifications"; import { WebClientRadroots } from "@radroots/client/radroots"; import { WebTangleDatabase } from "@radroots/client/tangle"; import { Geocoder } from "@radroots/geocoder"; +import type { CallbackPromise } from "@radroots/utils"; +import { reset_sql_cipher } from "./cipher"; +import type { NavigationRoute } from "./routes"; export const datastore = new WebDatastore( cfg_datastore_key_map, @@ -23,7 +29,7 @@ export const geol = new WebGeolocation(); export const geoc = new Geocoder(); export const http = new WebHttp(); export const notif = new WebNotifications(); -export const radroots = new WebClientRadroots(PUBLIC_RADROOTS_URL); +export const radroots = new WebClientRadroots(); export const nostr_keys = new WebKeystoreNostr({ database: "radroots-pwa-v1-keystore-nostr" }); @@ -44,3 +50,42 @@ export const create_db = async (): Promise<WebTangleDatabase> => { } return db_i; }; + +export const route = create_router<NavigationRoute>(); + +export const restart = async (opts?: { + notify_message?: string; + route?: NavigationRoute; +}): Promise<void> => { + try { + goto(opts?.route || `/`); + if (opts?.notify_message) app_notify.set(opts.notify_message); + location.reload(); + } catch (e) { + handle_err(e, `restart`); + } +}; + +export const reset = async (): Promise<void> => { + try { + const $ls = get_store(ls); + const confirm = await notif.confirm({ + message: `${$ls(`notification.device.reset`)}. ${$ls(`common.this_action_is_irreversible`)}. ${$ls(`common.do_you_want_to_continue_q`)}` + }); + if (!confirm) return; + await nostr_keys.reset(); + await datastore.reset(); + await reset_sql_cipher(db.get_store_key()); + await db.reinit(); + goto(`/`); + app_notify.set(`${$ls(`notification.device.reset_complete`)}`); + } catch (e) { + handle_err(e, `reset`); + } +}; + +export const message_callback = async (message: string, callback: CallbackPromise): Promise<void> => { + notif.alert(message); + await callback(); +}; + diff --git a/app/src/lib/utils/backup/export.ts b/app/src/lib/utils/backup/export.ts @@ -1,10 +1,13 @@ import { datastore, db, nostr_keys, notif } from "$lib/utils/app"; -import { download_json, handle_err } from "@radroots/apps-lib"; +import { ls } from "$lib/utils/i18n"; +import { download_json, get_store, handle_err } from "@radroots/apps-lib"; import type { ExportedAppState } from "@radroots/apps-lib-pwa/types/app"; import { throw_err } from "@radroots/utils"; import { createStore, get as idb_get, keys as idb_keys, type UseStore } from "idb-keyval"; import { app_cfg } from "../app/config"; +const $ls = get_store(ls); + const parse_idb_entry = async (key: IDBValidKey, store: UseStore): Promise<[string, string] | undefined> => { if (typeof key !== "string") return undefined; const value = await idb_get(key, store); @@ -15,7 +18,7 @@ const parse_idb_entry = async (key: IDBValidKey, store: UseStore): Promise<[stri const export_datastore_state = async (): Promise<ExportedAppState["datastore"]> => { await datastore.init(); const ds_cfg = datastore.get_config(); - if (!ds_cfg) throw_err("error.app.export.missing_datastore_config"); + if (!ds_cfg) throw_err($ls(`error.app.export.missing_datastore_config`)); const ds_store = createStore(ds_cfg.database, ds_cfg.store); const ds_keys = await idb_keys(ds_store); const entries: Record<string, unknown> = {}; @@ -28,7 +31,7 @@ const export_datastore_state = async (): Promise<ExportedAppState["datastore"]> const export_nostr_keystore_state = async (): Promise<ExportedAppState["nostr_keystore"]> => { const nostr_cfg = nostr_keys.get_config(); - if (!nostr_cfg) throw_err("error.app.export.missing_nostr_keystore_config"); + if (!nostr_cfg) throw_err($ls(`error.app.export.missing_nostr_keystore_config`)); const keys: ExportedAppState["nostr_keystore"]["keys"] = []; try { @@ -81,9 +84,10 @@ export const export_app_state = async (): Promise<void> => { database: tangle_db_state }; const ts = payload.exported_at.replace(/[:.]/g, "-"); - download_json(payload, `radroots-app-state-${ts}.json`); + const filename_prefix = $ls(`common.radroots_app_state_filename_prefix`); + download_json(payload, `${filename_prefix}-${ts}.json`); } catch (e) { handle_err(e, `export_app_state`); - await notif.alert(`Unable to export app state. Please try again.`); + await notif.alert(`${$ls(`error.backup.export_failure`)}`); } }; diff --git a/app/src/lib/utils/backup/import.ts b/app/src/lib/utils/backup/import.ts @@ -1,5 +1,6 @@ import { datastore, db, nostr_keys } from "$lib/utils/app"; -import { handle_err, parse_file_json } from "@radroots/apps-lib"; +import { ls } from "$lib/utils/i18n"; +import { get_store, handle_err, parse_file_json } from "@radroots/apps-lib"; import type { ImportableAppState } from "@radroots/apps-lib-pwa/types/app"; import type { IError } from "@radroots/types-bindings"; import type { IdbClientConfig } from "@radroots/utils"; @@ -8,6 +9,8 @@ import { createStore, set as idb_set } from "idb-keyval"; import { reset_sql_cipher } from "../app/cipher"; import { app_cfg } from "../app/config"; +const $ls = get_store(ls); + export type AppImportStateResult = { pass: boolean; message?: string; @@ -20,38 +23,47 @@ const assert_config_match = ( ): void => { if (current.database !== incoming.database || current.store !== incoming.store) { throw_err( - `Import failed: ${label} storage mismatch (app=${current.database}/${current.store}, backup=${incoming.database}/${incoming.store})` + $ls(`error.configuration.import.storage_mismatch`, { + label, + app_database: current.database, + app_store: current.store, + backup_database: incoming.database, + backup_store: incoming.store, + }) ); } }; export const validate_import_file = async (file: File | null): Promise<ImportableAppState> => { const parsed: any = await parse_file_json(file) - if (!parsed) throw_err("error.app.import.invalid_file_contents") + if (!parsed) throw_err($ls(`error.configuration.import.invalid_file_contents`)) return await validate_import_state(parsed); }; export const validate_import_state = async (state: any): Promise<ImportableAppState> => { - if (!state || typeof state !== "object") throw_err("Import file is empty."); + if (!state || typeof state !== "object") throw_err($ls(`error.configuration.import.empty_file`)); if (state.backup_version !== app_cfg.backup.version) { throw_err( - `Unsupported backup version (${state.backup_version}); expected ${app_cfg.backup.version}.` + $ls(`error.configuration.import.unsupported_backup_version`, { + backup_version: state.backup_version, + expected_version: app_cfg.backup.version, + }) ); } const backup_format = state?.versions?.backup_format ?? state?.versions?.dump_format; if (!state.versions || !state.versions.app || !state.versions.tangle_sql || !backup_format) { - throw_err("Import file is missing version metadata."); + throw_err($ls(`error.configuration.import.missing_version_metadata`)); } const database = state.database ?? state.tangle_db; const backup = database?.backup ?? database?.dump; if (!state.datastore || !state.nostr_keystore || !database) { - throw_err("Import file is missing required sections."); + throw_err($ls(`error.configuration.import.missing_required_sections`)); } if (!backup || backup.format_version !== backup_format) { - throw_err("Import file database backup format does not match metadata."); + throw_err($ls(`error.configuration.import.database_format_mismatch`)); } if (backup.tangle_sql_version !== state.versions.tangle_sql) { - throw_err("Import file tangle-sql version does not match metadata."); + throw_err($ls(`error.configuration.import.tangle_sql_version_mismatch`)); } return { ...state, @@ -63,7 +75,7 @@ export const validate_import_state = async (state: any): Promise<ImportableAppSt const restore_datastore_state = async (state: ImportableAppState["datastore"]): Promise<void> => { const ds_cfg = datastore.get_config(); - assert_config_match(ds_cfg, state.config, "datastore"); + assert_config_match(ds_cfg, state.config, $ls(`common.datastore`)); const reset_res = await datastore.reset(); if (reset_res && "err" in reset_res) throw_err(reset_res.err); const store = createStore(ds_cfg.database, ds_cfg.store); @@ -77,7 +89,7 @@ const restore_nostr_keystore_state = async ( state: ImportableAppState["nostr_keystore"] ): Promise<void> => { const nostr_cfg = nostr_keys.get_config(); - assert_config_match(nostr_cfg, state.config, "nostr keystore"); + assert_config_match(nostr_cfg, state.config, $ls(`common.nostr_keystore`)); const reset_res = await nostr_keys.reset(); if (reset_res && "err" in reset_res) throw_err(reset_res.err); for (const key of state.keys) { @@ -90,7 +102,10 @@ const restore_tangle_db_state = async (state: ImportableAppState["database"]): P const current_store_key = db.get_store_key(); if (current_store_key !== state.store_key) { throw_err( - `Import failed: tangle DB store key mismatch (app=${current_store_key}, backup=${state.store_key}).` + $ls(`error.configuration.import.tangle_store_key_mismatch`, { + app_store_key: current_store_key, + backup_store_key: state.store_key, + }) ); } await reset_sql_cipher(current_store_key); @@ -100,14 +115,18 @@ const restore_tangle_db_state = async (state: ImportableAppState["database"]): P export const import_app_state = async (payload: ImportableAppState): Promise<AppImportStateResult | IError<string>> => { try { - if (typeof window === "undefined") return err_msg("error.client.undefined_window"); + if (typeof window === "undefined") return err_msg($ls(`error.client.undefined_window`)); const import_state = await validate_import_state(payload); - assert_config_match(datastore.get_config(), import_state.datastore.config, "datastore"); - assert_config_match(nostr_keys.get_config(), import_state.nostr_keystore.config, "nostr keystore"); + assert_config_match(datastore.get_config(), import_state.datastore.config, $ls(`common.datastore`)); + assert_config_match(nostr_keys.get_config(), import_state.nostr_keystore.config, $ls(`common.nostr_keystore`)); const current_store_key = db.get_store_key(); if (current_store_key !== import_state.database.store_key) { - console.error(`Import failed: tangle DB store key mismatch (app=${current_store_key}, backup=${import_state.database.store_key}).`); - return err_msg("error.database.store_key_mismatch"); + const message = $ls(`error.configuration.import.tangle_store_key_mismatch`, { + app_store_key: current_store_key, + backup_store_key: import_state.database.store_key, + }); + console.error(message); + return err_msg(message); } await restore_datastore_state(import_state.datastore); await restore_nostr_keystore_state(import_state.nostr_keystore); @@ -116,7 +135,7 @@ export const import_app_state = async (payload: ImportableAppState): Promise<App return { pass: true } } catch (e) { handle_err(e, `import_app_state`); - return { pass: false, message: "Unable to import app state. Please verify the file and try again." } + return { pass: false, message: $ls(`error.configuration.import.failure`) } } }; diff --git a/app/src/lib/utils/config.ts b/app/src/lib/utils/config.ts @@ -1,4 +1,4 @@ -import { PUBLIC_RADROOTS_RELAY_URL } from "$env/static/public"; +import { _envLib } from "@radroots/apps-lib"; import type { AppConfigRole } from "@radroots/apps-lib-pwa/types/app"; import { root_symbol } from "@radroots/utils"; import type { NostrEventTagClient } from "@radroots/utils-nostr"; @@ -32,7 +32,7 @@ export const cfg_delay = { }; export const cfg_nostr = { - relay_url: PUBLIC_RADROOTS_RELAY_URL, + relay_url: _envLib.RADROOTS_RELAY, relay_pubkey: radroots_nostr_pubkey, relay_polling_count_max: 10, }; @@ -61,6 +61,7 @@ export type ConfigData = { nostr_profile?: string; role?: AppConfigRole; nip05_request?: boolean; + nip05_result?: string; }; export type AppData = { diff --git a/app/src/lib/utils/geo/lib.ts b/app/src/lib/utils/geo/lib.ts @@ -0,0 +1,65 @@ +import { handle_err, } from "@radroots/apps-lib"; +import type { ILocationGcsFields } from "@radroots/tangle-schema-bindings"; +import type { IError } from "@radroots/types-bindings"; +import { err_msg, location_geohash, type GeocoderReverseResult, type GeolocationPoint } from "@radroots/utils"; +import { geoc } from "../app"; + +export const geolocation_fields_from_point = async (opts: { + label?: string; + tag_0?: string; + geol_p: GeolocationPoint; +}): Promise<ILocationGcsFields | IError<string>> => { + const { label, geol_p } = opts; + try { + const fields: ILocationGcsFields = { + lat: geol_p.lat, + lng: geol_p.lng, + geohash: location_geohash(geol_p), + tag_0: opts.tag_0, + } + if (label) fields.label = label; + const geoc_rev = await geoc.reverse({ lat: geol_p.lat, lng: geol_p.lng }); + if ("err" in geoc_rev) return err_msg(geoc_rev); + else if ("results" in geoc_rev && geoc_rev.results.length > 0) { + const geoc_res = geoc_rev.results[0]; + fields.gc_id = geoc_res.id.toString(); + fields.gc_name = geoc_res.name; + fields.gc_admin1_id = geoc_res.admin1_id.toString(); + fields.gc_admin1_name = geoc_res.admin1_name; + fields.gc_country_id = geoc_res.country_id; + fields.gc_country_name = geoc_res.country_name; + }; + return fields; + } catch (e) { + handle_err(e, `geolocation_fields_from_point`); + return err_msg(`*`) + } +}; + +export const geolocation_fields_from_point_with_geocode = async (opts: { + label?: string; + tag_0?: string; + geoc_r: GeocoderReverseResult; + geol_p: GeolocationPoint; +}): Promise<ILocationGcsFields | IError<string>> => { + const { label, geoc_r, geol_p } = opts; + try { + const fields: ILocationGcsFields = { + lat: geol_p.lat, + lng: geol_p.lng, + geohash: location_geohash(geol_p), + tag_0: opts.tag_0 || undefined, + gc_id: geoc_r.id.toString(), + gc_name: geoc_r.name, + gc_admin1_id: geoc_r.admin1_id.toString(), + gc_admin1_name: geoc_r.admin1_name, + gc_country_id: geoc_r.country_id, + gc_country_name: geoc_r.country_name, + }; + if (label) fields.label = label; + return fields; + } catch (e) { + handle_err(e, `geolocation_fields_from_point_with_geocode`); + return err_msg(`*`) + } +}; diff --git a/app/src/routes/(app)/+layout.ts b/app/src/routes/(app)/+layout.ts @@ -1,6 +1,5 @@ -import { datastore, nostr_keys } from '$lib/utils/app'; -import { route } from '$lib/utils/app/app'; +import { datastore, nostr_keys, route } from '$lib/utils/app'; import { type AppData } from '$lib/utils/config'; import { handle_err } from '@radroots/apps-lib'; import type { LayoutLoad, LayoutLoadEvent } from '../$types'; diff --git a/app/src/routes/(app)/+page.svelte b/app/src/routes/(app)/+page.svelte @@ -1,6 +1,6 @@ <script lang="ts"> - import { notif } from "$lib/utils/app"; - import { route } from "$lib/utils/app/app"; + import { notif, route } from "$lib/utils/app"; + import { ls } from "$lib/utils/i18n"; import { handle_err, sleep } from "@radroots/apps-lib"; import { Home } from "@radroots/apps-lib-pwa"; import { qp_ref } from "@radroots/apps-lib-pwa/stores/app"; @@ -10,7 +10,7 @@ if (_qp_ref?.toString() === "backup_imported") { await sleep(100); await notif.alert( - "The application backup has been succesfully imported.", + `${$ls(`notification.backup.import_success`)}`, ); qp_ref.set(null); } diff --git a/app/src/routes/(app)/farms/+page.svelte b/app/src/routes/(app)/farms/+page.svelte @@ -1,6 +1,5 @@ <script lang="ts"> - import { db } from "$lib/utils/app"; - import { route } from "$lib/utils/app/app"; + import { db, route } from "$lib/utils/app"; import { handle_err } from "@radroots/apps-lib"; import { Farms } from "@radroots/apps-lib-pwa"; import type { diff --git a/app/src/routes/(app)/farms/add/+page.svelte b/app/src/routes/(app)/farms/add/+page.svelte @@ -1,8 +1,60 @@ -<script lang="ts"> +<script> + import { db, route } from "$lib/utils/app"; + import { geolocation_fields_from_point_with_geocode } from "$lib/utils/geo/lib"; + import { FarmsAdd } from "@radroots/apps-lib-pwa"; + import { handle_err, throw_err } from "@radroots/utils"; </script> -<div class={`flex flex-col w-full justify-start items-start`}> - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`farms add`} - </p> -</div> +<FarmsAdd + basis={{ + callback_route: { route: `/farms` }, + on_submit: async ({ payload }) => { + try { + console.log(JSON.stringify(payload, null, 4), `payload`); + + const farm_create = await db.farm_create({ + name: payload.farm_name, + area: payload.farm_area + ? payload.farm_area.toString() + : undefined, + area_unit: payload.farm_area_unit, + }); + console.log( + JSON.stringify(farm_create, null, 4), + `farm_create`, + ); + if ("err" in farm_create) throw_err(farm_create); + const location_fields = + await geolocation_fields_from_point_with_geocode({ + geoc_r: payload.geocode_result, + geol_p: payload.geolocation_point, + }); + if ("err" in location_fields) throw_err(location_fields); + console.log( + JSON.stringify(location_fields, null, 4), + `location_fields`, + ); + const location_gcs_create = + await db.location_gcs_create(location_fields); + if ("err" in location_gcs_create) + throw_err(location_gcs_create); + const farm_location_set = await db.farm_location_set({ + farm: { + id: farm_create.result.id, + }, + location_gcs: { + id: location_gcs_create.result.id, + }, + }); + console.log( + JSON.stringify(farm_location_set, null, 4), + `farm_location_set`, + ); + if (`err` in farm_location_set) throw_err(farm_location_set); + await route(`/farms`); + } catch (e) { + handle_err(e, `on_submit`); + } + }, + }} +/> diff --git a/app/src/routes/(app)/farms/info/+page.svelte b/app/src/routes/(app)/farms/info/+page.svelte @@ -1,8 +1,9 @@ <script lang="ts"> + import { ls } from "$lib/utils/i18n"; </script> <div class={`flex flex-col w-full justify-start items-start`}> <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`farms info`} + {`${$ls(`icu.*_information`, { value: `${$ls(`common.farm`)}` })}`} </p> </div> diff --git a/app/src/routes/(cfg)/+layout.ts b/app/src/routes/(cfg)/+layout.ts @@ -1,6 +1,5 @@ -import { datastore, nostr_keys } from '$lib/utils/app'; -import { route } from '$lib/utils/app/app'; +import { datastore, nostr_keys, route } from '$lib/utils/app'; import { handle_err } from '@radroots/apps-lib'; import type { LayoutLoad, LayoutLoadEvent } from './$types'; diff --git a/app/src/routes/(cfg)/import/+page.svelte b/app/src/routes/(cfg)/import/+page.svelte @@ -1,7 +1,6 @@ <script lang="ts"> import { goto } from "$app/navigation"; - import { notif } from "$lib/utils/app"; - import { route } from "$lib/utils/app/app"; + import { notif, route } from "$lib/utils/app"; import { import_app_state_from_file, validate_import_file, @@ -9,6 +8,7 @@ import { Fade, Glyph } from "@radroots/apps-lib"; import { LogoCircle, SelectMenu } from "@radroots/apps-lib-pwa"; import type { ExportedAppState } from "@radroots/apps-lib-pwa/types/app"; + import { ls } from "$lib/utils/i18n"; import { handle_err } from "@radroots/utils"; let loading = $state(false); @@ -43,7 +43,7 @@ try { if (!current_file) return void notif.alert( - "error.configuration.import.no_file_chosen", + `${$ls(`error.configuration.import.no_file_chosen`)}`, ); loading = true; const import_result = @@ -54,7 +54,8 @@ else if (import_result.pass === true) return void (await goto(`/?ref=backup_imported`)); await notif.alert( - import_result.message || "error.configuration.import.failure", + import_result.message || + `${$ls(`error.configuration.import.failure`)}`, ); } catch (e) { handle_err(e, `submit`); @@ -75,12 +76,12 @@ entries: [ { value: "", - label: "Choose Options", + label: `${$ls(`common.choose_options`)}`, disabled: true, }, { value: "back", - label: "Go Back", + label: `${$ls(`common.go_back`)}`, }, ], }, @@ -106,10 +107,10 @@ class={`flex flex-col w-full px-1 gap-1 justify-start items-start`} > <p class={`font-sans font-[600] text-lg text-ly0-gl`}> - {`Import app state`} + {`${$ls(`common.import_app_state`)}`} </p> <p class={`font-sans font-[400] text-sm text-ly0-gl/80 max-w-xl`}> - {`Choose a JSON export created by this app to restore configuration, nostr keys, and database information.`} + {`${$ls(`notification.configuration.import_description`)}`} </p> </div> <div class={`flex flex-row w-full justify-center items-center`}> @@ -136,7 +137,10 @@ }} /> <p class={`font-sans font-[500] text-sm text-lime-600`}> - {`The backup file is valid (version: ${current_file_validated.backup_version})`} + {`${$ls( + `notification.configuration.backup_file_valid`, + { backup_version: current_file_validated.backup_version }, + )}`} </p> {:else} <Glyph @@ -147,7 +151,7 @@ }} /> <p class={`font-sans font-[500] text-sm text-red-400`}> - {`Not a valid backup file`} + {`${$ls(`notification.configuration.backup_file_invalid`)}`} </p> {/if} </Fade> @@ -161,7 +165,7 @@ disabled={loading} onclick={submit} > - {`Import`} + {`${$ls(`common.import`)}`} {#if loading} <Glyph basis={{ diff --git a/app/src/routes/(cfg)/setup/+page.svelte b/app/src/routes/(cfg)/setup/+page.svelte @@ -1,7 +1,13 @@ <script lang="ts"> import { goto } from "$app/navigation"; - import { datastore, db, nostr_keys, notif } from "$lib/utils/app"; - import { route } from "$lib/utils/app/app"; + import { + datastore, + db, + nostr_keys, + notif, + radroots, + route, + } from "$lib/utils/app"; import { reset_sql_cipher } from "$lib/utils/app/cipher"; import { cfg_delay, @@ -25,6 +31,8 @@ } from "@radroots/apps-lib"; import { ButtonLayoutPair, + CarouselContainer, + CarouselItem, EntryLine, LoadSymbol, LogoCircle, @@ -85,10 +93,16 @@ const nostr_keys_all = await nostr_keys.keys(); if ("results" in nostr_keys_all) { const confirm = await notif.confirm({ - message: `Clear the prior session?`, + message: `${$ls( + `notification.configuration.clear_prior_session`, + )}`, }); if (!confirm) { - alert("@todo add the prior session"); + await notif.alert( + `${$ls( + `notification.configuration.prior_session_pending`, + )}`, + ); return; } await page_reset(); @@ -243,12 +257,48 @@ )); if (!profile_name_valid) return void (await notif.alert( - `Profile name must be at least 3 characters`, //@todo + `${$ls(`error.configuration.profile.name_min_length`)}`, + )); + // nip-05 request + profile_name_loading = true; + const profile_req = await radroots.accounts_request({ + profile_name: profile_name_val, + secret_key: ks_nostr_key.secret_key, + }); + if ("err" in profile_req) + return void (await notif.alert( + `${$ls(profile_req.err, { + default: `${$ls( + `error.client.http.request_failure`, + )}`, + })}`, + )); + const confirm = await notif.confirm({ + message: `${`${$ls(`icu.the_*_is_available`, { + value: `${$ls( + `common.profile_name`, + ).toLowerCase()} "${profile_name_val}"`, + })}`}. ${`${$ls(`common.would_you_like_to_use_it_q`)}`}`, + cancel: `${$ls(`common.no`)}`, + ok: `${$ls(`common.yes`)}`, + }); + if (!confirm) return; + const profile_create = await radroots.accounts_create({ + tok: profile_req.result, + secret_key: ks_nostr_key.secret_key, + }); + if (`err` in profile_create) + return void (await notif.alert( + `${$ls(profile_create.err, { + default: `${$ls( + `error.client.http.request_failure`, + )}`, + })}`, )); await datastore.update_obj<ConfigData>("cfg_data", { nip05_request: true, + nip05_result: profile_create.result, }); - // @todo add nip-05 request } if (!profile_name_val) { @@ -361,7 +411,7 @@ ): Promise<ResultPass | IError<string>> => { const nostr_profile_add = await db.nostr_profile_create({ public_key, - name: profile_name ? profile_name : "default", + name: profile_name ? profile_name : `${$ls(`common.default`)}`, display_name: profile_name ? profile_name : undefined, }); if ("err" in nostr_profile_add) @@ -409,12 +459,12 @@ entries: [ { value: "", - label: "Choose Options", + label: `${$ls(`common.choose_options`)}`, disabled: true, }, { value: "import", - label: "Import Backup", + label: `${$ls(`common.import_backup`)}`, }, ], }, @@ -439,13 +489,16 @@ data-view={`cfg_key`} class={`flex flex-col h-full w-full justify-start items-center`} > - <div - data-carousel-container={`cfg_key`} - class={`carousel-container flex h-full w-full`} + <CarouselContainer + basis={{ + view: `cfg_key`, + }} > - <div - data-carousel-item={`cfg_key`} - class={`carousel-item flex flex-col h-full w-full justify-center items-center`} + <CarouselItem + basis={{ + view: `cfg_key`, + classes: `justify-center items-center`, + }} > <div class={`relative flex flex-col h-full w-full justify-center items-center`} @@ -500,16 +553,17 @@ </div> </div> </div> - </div> - <div - data-carousel-item={`cfg_key`} - class={`carousel-item flex flex-col h-full w-full justify-center items-center`} - role="button" - tabindex="0" - onclick={async () => { - cgf_key_opt = undefined; + </CarouselItem> + <CarouselItem + basis={{ + view: `cfg_key`, + classes: `justify-center items-center`, + role: `button`, + tabindex: 0, + callback_click: async () => { + cgf_key_opt = undefined; + }, }} - onkeydown={null} > <div class={`flex flex-col h-[16rem] gap-8 w-full justify-start items-center`} @@ -560,10 +614,12 @@ </button> </div> </div> - </div> - <div - data-carousel-item={`cfg_key`} - class={`carousel-item flex flex-col h-full w-full justify-center items-center`} + </CarouselItem> + <CarouselItem + basis={{ + view: `cfg_key`, + classes: `justify-center items-center`, + }} > <div class={`flex flex-col w-full gap-8 justify-start items-center`} @@ -590,7 +646,7 @@ classes: `font-sans text-[1.25rem] text-center placeholder:opacity-60`, layer: 1, placeholder: `${$ls(`icu.enter_*`, { - value: `nostr nsec/hex`, + value: `${$ls(`common.nostr_nsec_hex`)}`, })}`, callback_keydown: async ({ key_s, el }) => { if (key_s) { @@ -603,7 +659,7 @@ /> </div> </div> - </div> + </CarouselItem> <div class={`z-10 absolute ios0:bottom-2 bottom-10 left-0 flex flex-col w-full justify-center items-center`} > @@ -622,20 +678,23 @@ }} /> </div> - </div> + </CarouselContainer> </div> <div data-view={`cfg_profile`} class={`hidden flex flex-col h-full w-full justify-start items-center`} > - <div - data-carousel-container={`cfg_profile`} - class={`carousel-container flex h-full w-full`} + <CarouselContainer + basis={{ + view: `cfg_profile`, + }} > - <div - data-carousel-item={`cfg_profile`} - class={`carousel-item flex flex-col h-full w-full justify-center items-center`} + <CarouselItem + basis={{ + view: `cfg_profile`, + classes: `justify-center items-center`, + }} > <div class={`flex flex-col h-[16rem] w-full px-4 gap-6 justify-start items-center`} @@ -707,16 +766,17 @@ </div> </div> </div> - </div> - <div - data-carousel-item={`cfg_profile`} - class={`carousel-item flex flex-col h-full w-full justify-center items-center`} - role="button" - tabindex="0" - onclick={async () => { - cfg_role = undefined; + </CarouselItem> + <CarouselItem + basis={{ + view: `cfg_profile`, + classes: `justify-center items-center`, + role: `button`, + tabindex: 0, + callback_click: async () => { + cfg_role = undefined; + }, }} - onkeydown={null} > <div class={`flex flex-col h-[16rem] w-full gap-10 justify-start items-center`} @@ -761,8 +821,8 @@ </button> </div> </div> - </div> - </div> + </CarouselItem> + </CarouselContainer> <div class={`absolute ios0:bottom-2 bottom-10 left-0 flex flex-col w-full justify-center items-center`} > @@ -792,13 +852,17 @@ data-view={`eula`} class={`hidden flex flex-col h-full w-full ios0:pt-12 pt-24 justify-start items-center`} > - <div - data-carousel-container={`eula`} - class={`carousel-container flex h-full w-full rounded-2xl scroll-hide`} + <CarouselContainer + basis={{ + view: `eula`, + classes: `rounded-2xl scroll-hide`, + }} > - <div - data-carousel-item={`eula`} - class={`carousel-item flex flex-col h-full w-full justify-start items-center`} + <CarouselItem + basis={{ + view: `eula`, + classes: `justify-start items-center`, + }} > <div class={`flex flex-col h-full w-full px-4 justify-start items-center ${ @@ -1044,6 +1108,6 @@ </button> </div> </div> - </div> - </div> + </CarouselItem> + </CarouselContainer> </div> diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { dev, version as kit_version } from "$app/environment"; - import { db } from "$lib/utils/app"; + import { db, geoc } from "$lib/utils/app"; import { app_cfg } from "$lib/utils/app/config"; import { lc_color_mode, @@ -86,6 +86,7 @@ onMount(async () => { await db.init(); + await geoc.connect(); }); </script> diff --git a/package.json b/package.json @@ -6,8 +6,8 @@ "build": "turbo build", "build:app": "turbo build --filter=app --filter=@radroots/*", "build:pkg": "turbo run build --filter=./packages/*", - "dev:lib": "turbo dev --filter=@radroots/* --concurrency 20", - "dev:app": "cd app && yarn dev" + "dev:app": "cd app && yarn dev", + "dev:pkg": "turbo dev --filter=@radroots/* --concurrency 20" }, "devDependencies": { "turbo": "2.5.3", diff --git a/yarn.lock b/yarn.lock @@ -5052,14 +5052,9 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -sql.js@1.12.0: - version "1.12.0" - resolved "https://registry.npmjs.org/sql.js/-/sql.js-1.12.0.tgz" - integrity sha512-Bi+43yMx/tUFZVYD4AUscmdL6NHn3gYQ+CM+YheFWLftOmrEC/Mz6Yh7E96Y2WDHYz3COSqT+LP6Z79zgrwJlA== - -sql.js@^1.13.0: +sql.js@1.13.0: version "1.13.0" - resolved "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz" + resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-1.13.0.tgz#f73cba7eaba0bc881f466c9149e00cf598fb01e0" integrity sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA== stop-iteration-iterator@^1.1.0: