commit aeed3ec102306f07ba47808842790d76021a34f0
parent 53b46820cad03092a5a373805c8fd4dd46930c7a
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Mon, 10 Feb 2025 10:19:29 +0000
utils: refactor library for app/0.0.1, migrate from direct index.ts entry to tsup compilation
Diffstat:
68 files changed, 2473 insertions(+), 1089 deletions(-)
diff --git a/utils/.gitignore b/utils/.gitignore
@@ -18,20 +18,30 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-# local env files
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
# turbo
.turbo
+
+# Output
+.output
+/build
+dist
+
+# local env files
+.env*
+!.env.example
+
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+
+# local
.tmp*
+.backup*
.dev*
.vscode
notes*.txt
notes*.md
-justfile
-dist
git-diff.txt
+justfile
diff --git a/utils/index.ts b/utils/index.ts
@@ -1 +0,0 @@
-export * from "./src";
diff --git a/utils/package.json b/utils/package.json
@@ -1,17 +1,28 @@
{
- "name": "@radroots/utils",
+ "name": "@radroots/util",
"version": "0.0.0",
"private": true,
"license": "GPLv3",
"type": "module",
- "module": "index.ts",
+ "main": "dist/index.js",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ }
+ },
"scripts": {
- "build": "just build && tsc",
- "dev": "tsc -w"
+ "build": "tsup",
+ "dev": "tsup --watch",
+ "watch": "tsc -w"
},
"devDependencies": {
"@types/ngeohash": "0.6.8",
+ "@types/node": "^22.13.1",
"@types/uuid": "^10.0.0",
+ "tsup": "^6.2.3",
"typescript": "^5.3.3"
},
"publishConfig": {
@@ -20,7 +31,9 @@
"dependencies": {
"@noble/curves": "^1.6.0",
"@noble/hashes": "^1.4.0",
- "@nostr-dev-kit/ndk": "^2.10.7",
+ "@nostr-dev-kit/ndk": "^2.11.0",
+ "@sveltekit-i18n/base": "^1.3.7",
+ "@sveltekit-i18n/parser-icu": "^1.0.8",
"convert": "^5.5.1",
"geohashing": "^2.0.1",
"nostr-geotags": "^0.7.1",
diff --git a/utils/src/*regex.ts b/utils/src/*regex.ts
@@ -0,0 +1,132 @@
+import { FormField } from "$root";
+
+export const util_rxp = {
+ product_key: /^[A-Za-z_]+$/,
+ product_key_ch: /^[A-Za-z_]$/,
+ product_title: /[A-Za-z0-9 ]+$/,
+ product_title_ch: /[A-Za-z0-9 ]$/,
+ float: /^[+-]?(\d+(\.\d*)?|\.\d+)$/,
+ float_ch: /^[0-9\.\+\-]$/,
+ float_pos: /^\d+(\.\d+)?$/,
+ float_pos_ch: /^[0-9\.]$/,
+ description: /^(?:\S+(?:\s+\S+)*)$/,
+ description_ch: /[^a-zA-Z0-9.,!?;:'"(){}[]\s\u0600-\u06FF\u0900-\u097F\u0400-\u04FF\u0500-\u052F\u1F00-\u1FFF\u4E00-\u9FFF\uAC00-\uD7AF\u3040-\u309F\u30A0-\u30FF ]+/,
+ nbsp: /[\u00A0]/g,
+ nbsp_rp: /[\u00A0]+/g,
+ rtlm: /[\u200F]/g,
+ rtlm_rp: /[\u200F]+/g,
+ commas: /[,]+/g,
+ periods: /[.]+/g,
+ word_only: /^[a-zA-Z]+$/,
+ alpha: /[a-zA-Z ]$/,
+ alpha_ch: /[a-zA-Z ]$/,
+ num: /^[0-9]+$/,
+ lat: /^[-+]?([1-8]?[0-9](\.\d{1,6})?|90(\.0{1,6})?)$/,
+ lat_ch: /^[\d\.\+\-]$/,
+ lng: /^[-+]?((1[0-7]?[0-9]|180)(\.\d{1,6})?|(\d{1,2})(\.\d{1,6})?)$/,
+ lng_ch: /^[\d\.\+\-]$/,
+ alphanum: /[a-zA-Z0-9., ]$/,
+ alphanum_ch: /[a-zA-Z0-9.,\s\u0600-\u06FF\u0900-\u097F\u0400-\u04FF\u0500-\u052F\u1F00-\u1FFF\u4E00-\u9FFF\uAC00-\uD7AF\u3040-\u309F\u30A0-\u30FF ]+/,
+ price: /^\d+(\.\d+)?$/,
+ price_ch: /[0-9.]$/,
+ price_cur: /^[A-Za-z]{3}$/,
+ price_cur_ch: /[A-Za-z]$/,
+ profile_name: /^[a-zA-Z0-9._]{3,30}$/,
+ profile_name_ch: /[a-zA-Z0-9._]/,
+ trade_product_key: /^(?:[a-zA-Z0-9]+(?:\s+[a-zA-Z0-9]+){0,2})$/,
+ trade_product_category: /^(?:[a-zA-Z0-9]+(?:\s+[a-zA-Z0-9]+){0,2})$/,
+ currency_symbol: /(?:[A-Za-z]{3,5}\$|\p{Sc})/u,
+ currency_marker: /(?:[A-Za-z]{2,4}[^\d\s]+|[^\d\s]{1,3}[A-Za-z]{2,4})/,
+ ws_proto: /^(wss:\/\/|ws:\/\/)/,
+ quantity_unit: /^(kg|lb|g)$/,
+ quantity_unit_ch: /[A-Za-z]$/,
+ url_image_upload: /^blob:https:\/\/domain\.tld\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
+ url_image_upload_dev: /^blob:http:\/\/localhost:\d+\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
+ country_code_a2: /^[A-Za-z]{2}$/,
+ addr_primary: /[a-zA-Z0-9., ]$/,
+ addr_admin: /[a-zA-Z0-9., ]$/,
+ num_int: /^[0-9]$/,
+ area_unit: /^(ac|ha|ft2|m2)$/,
+ area_unit_ch: /[A-Za-z2]$/,
+};
+
+export type FormFieldsKey =
+ | `product_title`
+ | `product_key`
+ | `product_process`
+ | `product_description`
+ | `price`
+ | `price_currency`
+ | `quantity_unit`
+ | `quantity`
+ | `quantity_label`
+ | `farm_name`
+ | `farm_size`
+ | `area`
+ | `area_unit`
+ | `contact_name`
+ | `profile_name`
+
+
+export const form_fields: Record<FormFieldsKey, FormField> = {
+ profile_name: {
+ charset: util_rxp.profile_name_ch,
+ validate: util_rxp.profile_name,
+ },
+ product_description: {
+ charset: util_rxp.alpha_ch,
+ validate: util_rxp.alpha,
+ },
+ product_key: {
+ charset: util_rxp.product_key_ch,
+ validate: util_rxp.product_key,
+ },
+ product_title: {
+ charset: util_rxp.product_title_ch,
+ validate: util_rxp.product_title,
+ },
+ product_process: {
+ charset: util_rxp.alphanum_ch,
+ validate: util_rxp.alphanum,
+ },
+ price: {
+ charset: util_rxp.price_ch,
+ validate: util_rxp.price,
+ },
+ price_currency: {
+ charset: util_rxp.price_cur_ch,
+ validate: util_rxp.price_cur,
+ },
+ quantity: {
+ charset: util_rxp.num,
+ validate: util_rxp.num,
+ },
+ quantity_unit: {
+ charset: util_rxp.quantity_unit_ch,
+ validate: util_rxp.quantity_unit,
+ },
+ quantity_label: {
+ charset: util_rxp.alphanum_ch,
+ validate: util_rxp.alphanum,
+ },
+ area: {
+ charset: util_rxp.float_ch,
+ validate: util_rxp.float,
+ },
+ area_unit: {
+ charset: util_rxp.area_unit_ch,
+ validate: util_rxp.area_unit,
+ },
+ farm_name: {
+ charset: util_rxp.alpha_ch,
+ validate: util_rxp.alpha,
+ },
+ farm_size: {
+ charset: util_rxp.num_int,
+ validate: util_rxp.num_int,
+ },
+ contact_name: {
+ charset: util_rxp.alpha_ch,
+ validate: util_rxp.alpha,
+ },
+};
diff --git a/utils/src/*validation.ts b/utils/src/*validation.ts
@@ -0,0 +1,49 @@
+import { GeolocationAddress, GeolocationPoint, parse_int, ResolveEnumArea_Unit, ResolveEnumQuantity_Unit, util_rxp } from "$root";
+import { z } from "zod";
+
+export const vunion_area_unit: z.ZodUnion<[
+ z.ZodLiteral<ResolveEnumArea_Unit>,
+ z.ZodLiteral<ResolveEnumArea_Unit>,
+ z.ZodLiteral<ResolveEnumArea_Unit>,
+ z.ZodLiteral<ResolveEnumArea_Unit>,
+]> = z.union([
+ z.literal(`ac`),
+ z.literal(`ft2`),
+ z.literal(`ha`),
+ z.literal(`m2`),
+]);
+
+export const vunion_mass_unit: z.ZodUnion<[
+ z.ZodLiteral<ResolveEnumQuantity_Unit>,
+ z.ZodLiteral<ResolveEnumQuantity_Unit>,
+ z.ZodLiteral<ResolveEnumQuantity_Unit>,
+]> = z.union([
+ z.literal(`kg`),
+ z.literal(`lb`),
+ z.literal(`g`),
+]);
+
+export const vs_geolocation_address: z.ZodSchema<GeolocationAddress> = z.object({
+ primary: z.string().regex(util_rxp.addr_primary),
+ admin: z.string().regex(util_rxp.addr_admin),
+ country: z.string().regex(util_rxp.country_code_a2)
+});
+
+export const vs_geolocation_point: z.ZodSchema<GeolocationPoint> = z.object({
+ lat: z.number().min(-90).max(90),
+ lng: z.number().min(-180).max(180),
+});
+
+export const ve_price_amount = z.preprocess((input) => {
+ return parse_int(String(input), 1.00);
+}, z.number().positive().multipleOf(0.01));
+
+export const ve_quantity_amount = z.preprocess((input) => {
+ return parse_int(String(input), 1);
+}, z.number().int().positive());
+
+export const zod_numf_price = z.number().positive().multipleOf(0.01);
+
+export const zod_numi_pos = z.number().int().positive();
+
+export const zod_numf_pos = z.number().positive();
+\ No newline at end of file
diff --git a/utils/src/_env.ts b/utils/src/_env.ts
@@ -0,0 +1,3 @@
+export const _env = {
+ PROD: process.env.NODE_ENV === `production`
+};
diff --git a/utils/src/app/document.ts b/utils/src/app/document.ts
@@ -0,0 +1,51 @@
+import { sleep, ThemeLayer } from "$root";
+
+export const el_id = (id: string): HTMLElement | undefined => {
+ const el = document.getElementById(id);
+ return el ? el : undefined;
+};
+
+export const el_toggle = (id: string, toggle_class: string): void => {
+ const el = document.getElementById(id);
+ if (el) el.classList.toggle(toggle_class);
+};
+
+export const els_id_pref = (id_pref: string): Element[] | undefined => {
+ const els = document.querySelectorAll(`[id^="${id_pref}"]`);
+ if (els && els.length) return Array.from(els);
+ return undefined;
+};
+
+export const els_id_pref_index = (id_pref: string, num_index: number, orientation: `greater` | `lesser` | `not` = `greater`, inclusive: boolean = true): Element[] | undefined => {
+ const els = document.querySelectorAll(`[id^="${`${id_pref}-`.replaceAll(`--`, `-`)}"]`);
+ if (els && els.length) return Array.from(els).filter(el => {
+ const match = el.id.match(/(?<=^|\-)[0-9]\d*(?=\-)/)
+ if (match) {
+ const num = parseInt(match[0], 10);
+ switch (orientation) {
+ case `greater`: {
+ if (inclusive) return num >= num_index;
+ else return num > num_index;
+ }
+ case `lesser`: {
+ if (inclusive) return num <= num_index;
+ else return num < num_index;
+ }
+ case `not`: {
+ return num !== num_index;
+ }
+ }
+ }
+ return false;
+ });
+ return undefined;
+};
+
+export const el_focus = async (id: string, callback: () => Promise<void>, layer: ThemeLayer = 1): Promise<void> => {
+ const el = el_id(id);
+ el?.classList.add(`entry-layer-${layer}-highlight`);
+ el?.focus();
+ await sleep(1200);
+ await callback();
+ el?.classList.remove(`entry-layer-${layer}-highlight`);
+};
diff --git a/utils/src/app/i18n.ts b/utils/src/app/i18n.ts
@@ -0,0 +1,22 @@
+import i18n, { type Loader } from "@sveltekit-i18n/base";
+import type { Config, Parser } from "@sveltekit-i18n/parser-icu";
+import parser from "@sveltekit-i18n/parser-icu";
+
+type LanguageConfig = {
+ default?: string;
+ value?: string;
+};
+export const i18n_conf = <T extends string>(opts: {
+ default_locale: T;
+ translations: Record<T, any>;
+ loaders: Loader.LoaderModule[]
+}): i18n<Parser.Params<LanguageConfig>> => {
+ const { default_locale: initLocale, translations, loaders } = opts;
+ const config: Config<LanguageConfig> = {
+ initLocale,
+ translations,
+ parser: parser(),
+ loaders,
+ };
+ return new i18n(config);
+};
+\ No newline at end of file
diff --git a/utils/src/app/lib.ts b/utils/src/app/lib.ts
@@ -0,0 +1,30 @@
+import type { AppLayoutKey } from "$root";
+
+type ConfigWindow = {
+ layout: Record<AppLayoutKey, {
+ h: number;
+ }>;
+ debounce: {
+ search: number;
+ }
+};
+
+export const cfg_app: ConfigWindow = {
+ layout: {
+ ios0: {
+ h: 600
+ },
+ ios1: {
+ h: 750
+ },
+ webm0: {
+ h: 600
+ },
+ webm1: {
+ h: 750
+ }
+ },
+ debounce: {
+ search: 200
+ },
+};
+\ No newline at end of file
diff --git a/utils/src/app/styles.ts b/utils/src/app/styles.ts
@@ -0,0 +1,44 @@
+import type { AppLayoutKey, GeometryGlyphDimension, IToastKind, LoadingDimension } from "$root";
+
+export const glyph_style_map: Map<GeometryGlyphDimension, { gl_1: number; dim_1?: number; }> = new Map([
+ ["xs--", { gl_1: 12 }],
+ ["xs-", { gl_1: 12, dim_1: 17 }],
+ ["xs", { gl_1: 15, dim_1: 18 }],
+ ["xs+", { gl_1: 18, dim_1: 20 }],
+ ["sm-", { gl_1: 19, dim_1: 22 }],
+ ["sm", { gl_1: 20, dim_1: 24 }],
+ ["sm+", { gl_1: 21 }],
+ ["md-", { gl_1: 23 }],
+ ["md", { gl_1: 24 }],
+ ["md+", { gl_1: 26 }],
+ ["lg-", { gl_1: 27 }],
+ ["lg", { gl_1: 28 }],
+ ["xl", { gl_1: 30 }],
+ ["xl+", { gl_1: 40 }],
+]);
+
+export const loading_style_map: Map<LoadingDimension, { dim_1: number; gl_2: number }> = new Map([
+ ["glyph-send-button", { dim_1: 20, gl_2: 20 }],
+ ["xs", { dim_1: 12, gl_2: 12 }],
+ ["sm", { dim_1: 16, gl_2: 16 }],
+ ["md", { dim_1: 20, gl_2: 20 }],
+ ["lg", { dim_1: 28, gl_2: 28 }],
+ ["xl", { dim_1: 36, gl_2: 36 }],
+]);
+
+export const toast_layout_map: Map<AppLayoutKey, string> = new Map([
+ [`ios0`, `pt-8`],
+ [`ios1`, `pt-16`],
+ [`webm0`, `pt-8`],
+ [`webm1`, `pt-16`],
+]);
+
+export const toast_style_map: Map<IToastKind, { inner: string; outer: string }> = new Map([
+ [
+ `simple`,
+ {
+ inner: `justify-center`,
+ outer: `min-h-toast_min w-full px-4 rounded-2xl shadow-sm`,
+ },
+ ],
+]);
+\ No newline at end of file
diff --git a/utils/src/app/types/app.ts b/utils/src/app/types/app.ts
@@ -0,0 +1,78 @@
+import type { GeocoderReverseResult, GeolocationPoint, GeometryDimension, IClientGeolocationPosition, INavigationRoute, ISelectOption } from "$root";
+
+export type ThemeLayer = 0 | 1 | 2;
+
+export type AppConfigType = `farmer` | `personal`
+
+export type AppLayoutKeyIOS = `ios0` | `ios1`;
+export type AppLayoutKeyWeb = `webm0` | `webm1`;
+export type AppLayoutKey = AppLayoutKeyIOS | AppLayoutKeyWeb;
+
+export type AppLayoutIOS<T extends string> = `${T}_${AppLayoutKeyIOS}`;
+export type AppLayoutWeb<T extends string> = `${T}_${AppLayoutKeyWeb}`;
+
+export type AppLayoutKeyHeight =
+ | `lo_bottom_button`
+ | `nav_tabs`
+ | `nav_page_header`
+ | `nav_page_toolbar`;
+
+export type AppLayoutKeyWidth =
+ | `lo`
+ | `lo_textdesc`;
+
+export type AppHeightsResponsiveIOS = AppLayoutIOS<AppLayoutKeyHeight>;
+export type AppHeightsResponsiveWeb = AppLayoutWeb<AppLayoutKeyHeight>;
+
+export type AppWidthsResponsiveIOS = AppLayoutIOS<AppLayoutKeyWidth>;
+export type AppWidthsResponsiveWeb = AppLayoutWeb<AppLayoutKeyWidth>;
+
+export type CallbackPromiseFigureResult<Ti, Tr> = (value: Ti) => Promise<Tr | undefined>;
+export type CallbackPromiseFull<Ti, Tr> = (value: Ti) => Promise<Tr>;
+export type CallbackPromiseGeneric<T> = (value: T) => Promise<void>;
+export type CallbackPromiseReturn<T> = () => Promise<T>;
+export type CallbackPromiseResult<Tr> = () => Promise<Tr | undefined>;
+
+export type CallbackPromise = () => Promise<void>;
+export type CallbackRoute<T extends string> = CallbackPromise | INavigationRoute<T>;
+
+export type ElementCallbackSelect = CallbackPromiseGeneric<ISelectOption<string>>
+
+export type EntryStyle = `guide` | `line`;
+
+
+export type LoadingBlades = 8 | 12;
+export type LoadingDimension = GeometryDimension | `glyph-send-button`; //@todo remove
+
+export type LayerGlyphBasisKind = `_a` | `_d` | `_pl`;
+
+export type NavigationRouteParamNostrPublicKey = `nostr_pk`;
+export type NavigationRouteParamRecordKey = `rkey`;
+export type NavigationRouteParamId = `id`;
+export type NavigationRouteParamLat = `lat`;
+export type NavigationRouteParamLng = `lng`;
+export type NavigationRouteParamKey = NavigationRouteParamNostrPublicKey | NavigationRouteParamId | NavigationRouteParamRecordKey | NavigationRouteParamLat | NavigationRouteParamLng;
+export type NavigationParamTuple = [NavigationRouteParamKey, string];
+export type NavigationPreviousParam<T extends string> = { route: T, label?: string; params?: NavigationParamTuple[] }
+
+export type EasingFunction = (t: number) => number;
+
+export interface SvelteTransitionConfig {
+ delay?: number;
+ duration?: number;
+ easing?: EasingFunction;
+ css?: (t: number, u: number) => string;
+ tick?: (t: number, u: number) => void;
+}
+
+export type LcGuiAlertCallback = CallbackPromiseFull<string, boolean>;
+export type LcGuiConfirmCallback = CallbackPromiseFull<string | { message: string; ok?: string; cancel?: string }, boolean>;
+
+//export type LcGeocodeCurrentCallback = CallbackPromiseFull<boolean | string, IClientGeolocationPosition | undefined>;
+export type LcGeocodeCurrentCallback = CallbackPromiseResult<IClientGeolocationPosition>;
+
+export type LcGeocodeCallback = CallbackPromiseFull<GeolocationPoint, GeocoderReverseResult | undefined>;
+export type LcPhotoAddCallback = CallbackPromiseResult<string>;
+export type LcPhotoAddMultipleCallback = CallbackPromiseResult<string[]>;
+
+export type ImageAspectRatio = `auto` | `1/1` | `4/3` | `16/9` | `3/4`;
+\ No newline at end of file
diff --git a/utils/src/app/types/basis.ts b/utils/src/app/types/basis.ts
@@ -0,0 +1,13 @@
+import type { CallbackPromise, CallbackPromiseReturn } from "$root";
+
+export type IBasisOpt<T extends object> = T | undefined;
+
+export type IViewBasis<T extends object> = {
+ kv_init_prevent?: boolean;
+ lc_on_mount?: CallbackPromise;
+ lc_on_destroy?: CallbackPromise;
+} & T;
+
+export type IViewBasisLoad<Tv extends object, Tl extends object> = IViewBasis<Tv> & {
+ lc_load: CallbackPromiseReturn<Tl | undefined>;
+};
+\ No newline at end of file
diff --git a/utils/src/app/types/component.ts b/utils/src/app/types/component.ts
@@ -0,0 +1,247 @@
+import type { CallbackPromise, CallbackRoute, ElementCallbackMount, ElementCallbackSelect, ElementCallbackValue, ElementCallbackValueBlur, ElementCallbackValueKeydown, FormField, GeometryGlyphDimension, GeometryScreenPosition, GlyphKey, GlyphWeight, ICb, ICbG, ICbGOpt, ICbOpt, IClOpt, IDisabledOpt, IEntryWrap, IGl, IGlOpt, IIdGOpt, IIdOpt, ILabel, ILabelOpt, ILabelTup, ILoadingOpt, ILy, ILyOpt, INavigationRoutePreventRouteNav, LcGeocodeCallback, NavigationParamTuple } from "$root";
+
+export type IToastKind = `simple`;
+
+export type IToast = IClOpt &
+ ILabel & IGlOpt & ILyOpt & {
+ styles?: IToastKind[];
+ position?: GeometryScreenPosition;
+ };
+
+export type IGlyph = ICbOpt & IIdOpt & ILyOpt & IClOpt & {
+ weight?: GlyphWeight;
+ key: GlyphKey;
+ dim?: GeometryGlyphDimension;
+};
+
+export type IInput<T extends string> = IIdGOpt<T> & IClOpt & ILyOpt & IDisabledOpt & {
+ placeholder?: string;
+ label?: string;
+ hidden?: boolean;
+ validate?: RegExp;
+ sync?: boolean;
+ field?: FormField;
+ field_constrain?: boolean;
+ callback?: ElementCallbackValue,
+ callback_keydown?: ElementCallbackValueKeydown<HTMLInputElement>,
+ callback_blur?: ElementCallbackValueBlur<HTMLInputElement>;
+ callback_focus?: ElementCallbackValueBlur<HTMLInputElement>;
+ callback_mount?: ElementCallbackMount<HTMLInputElement>;
+};
+
+export type IInputValue<T extends string> = Omit<IInput<T>, `id` | `sync`>;
+
+export type ISelectOption<T extends string> = IDisabledOpt & {
+ value: T;
+ label: string;
+};
+
+export type ISelect = IIdOpt & IClOpt & ILyOpt & {
+ callback?: ElementCallbackSelect;
+ sync?: boolean;
+ sync_init?: boolean;
+ options: { group?: string | true; entries: ISelectOption<string>[] }[];
+ show_arrows?: 'l' | 'r';
+};
+
+export type ITextArea = IIdOpt & IClOpt & ILyOpt & {
+ placeholder?: string;
+ label?: string;
+ hidden?: boolean;
+ validate?: RegExp;
+ sync?: true;
+ field?: FormField;
+ field_constrain?: boolean;
+ callback?: ElementCallbackValue,
+ callback_keydown?: ElementCallbackValueKeydown<HTMLTextAreaElement>,
+ callback_blur?: ElementCallbackValueBlur<HTMLTextAreaElement>;
+ callback_focus?: ElementCallbackValueBlur<HTMLTextAreaElement>;
+ callback_mount?: ElementCallbackMount<HTMLTextAreaElement>;
+};
+
+//export type IBasisOpt<T extends object> = T | undefined;
+
+export type INavigationRoute<T extends string> = {
+ route: T | [T, NavigationParamTuple[]];
+};
+
+export type IPageToolbar<T extends string> = ICbOpt & {
+ header?: IPageHeader<T>;
+};
+
+export type IPageHeader<T extends string> = {
+ label: string;
+ callback_route?: CallbackRoute<T>;
+};
+
+export type IGlyphCircle = {
+ classes_wrap: string;
+ glyph: IGlyph
+};
+
+export type ITrellis = ILy &
+ IClOpt &
+ ITrellisStyles & {
+ id?: string;
+ view?: string;
+ title?: ITrellisTitle;
+ description?: ITrellisDescription;
+ default_el?: ITrellisDefault;
+ list?: (ITrellisKind | undefined)[];
+ hide_offset?: true;
+ };
+
+export type ITrellisStyles = {
+ hide_rounded?: boolean;
+ hide_border_top?: boolean;
+ hide_border_bottom?: boolean;
+ set_title_background?: boolean;
+ set_default_background?: boolean;
+};
+
+export type ITrellisTitle = ICbOpt &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod,
+ value: string | true;
+ link?: ICbOpt &
+ IClOpt &
+ IGlOpt & ILabelOpt;
+ };
+
+export type ITrellisDescription = string | true;
+
+export type ITrellisBasisOffsetModKey = 'sm' | 'glyph';
+export type ITrellisBasisOffsetMod = ITrellisBasisOffsetModKey | (({ glyph: IGlyph } | { glyph_circle: IGlyphCircle }) & {
+ loading?: boolean;
+});
+
+export type ITrellisDefault = {
+ labels?: ITrellisDefaultLabel[];
+ show_title?: boolean;
+};
+
+export type ITrellisDefaultLabel = ICbOpt & {
+ label: string;
+ classes?: string;
+};
+
+export type ITrellisKind = (
+ | ITrellisKindTouch
+ | ITrellisKindInput
+ | ITrellisKindSelect
+);
+
+export type ITrellisBasis = {
+ loading?: boolean;
+ hide_active?: boolean;
+ hide_field?: boolean;
+ offset?: ITrellisBasisOffset;
+ full_rounded?: boolean;
+};
+
+export type ITrellisBasisOffset = ICbGOpt<MouseEvent> &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod;
+ classes?: string;
+ hide_space?: boolean;
+ hide_offset?: boolean;
+ };
+
+export type ITrellisKindDisplay = {
+ display?: ITrellisKindDisplayValue;
+}
+export type ITrellisKindDisplayValue = ICbGOpt<MouseEvent> & ILoadingOpt &
+ (ITrellisKindDisplayValueIcon | ILabel);
+
+
+export type ITrellisKindDisplayValueIcon = {
+ icon: {
+ classes?: string;
+ key: GlyphKey;
+ };
+};
+export type ITrellisKindTouch = ITrellisBasis & {
+ touch: ITrellisBasisTouch;
+};
+
+export type ITrellisBasisTouch = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & {
+ end?: ITrellisBasisTouchEnd;
+ };
+
+export type ITrellisKindInput = ITrellisBasis & {
+ input: ITrellisBasisInput;
+};
+
+export type ITrellisBasisInput = {
+ basis: IInput<string>;
+ line_label?: {
+ classes?: string;
+ value: string;
+ };
+ action?: {
+ visible: boolean;
+ loading?: boolean;
+ callback?: CallbackPromise;
+ glyph?: IGlyph
+ };
+};
+
+export type ITrellisKindSelect = ITrellisBasis & {
+ select: ITrellisBasisSelect;
+};
+
+export type ITrellisBasisSelect = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & ILoadingOpt & {
+ end?: ITrellisBasisTouchEnd;
+ el: ISelect & { value: string; };
+ };
+
+export type ITrellisBasisTouchEnd = ICbGOpt<MouseEvent> & IGl;
+
+export type INavBasisPrev = IClOpt & ICbG<
+ HTMLLabelElement | null
+> & IGlOpt & ILabelOpt & IDisabledOpt & {
+ loading?: boolean;
+};
+export type INavBasisOption = IClOpt & ICbG<
+ HTMLLabelElement | null
+> & IGlOpt & ILabelOpt & IDisabledOpt & {
+ loading?: boolean;
+};
+export type INavBasis<T extends string> = {
+ prev: ICbOpt & ILoadingOpt & INavigationRoute<T> & INavigationRoutePreventRouteNav & {
+ label?: string;
+ kind?: 'arrow'
+ };
+ title?: ICbOpt & ILabel;
+ option?: INavBasisOption;
+};
+
+export type IEntrySelect = ILoadingOpt & {
+ wrap: IEntryWrap;
+ el: ISelect;
+ hide_arrows?: boolean;
+};
+
+export type IButtonSimple = ILyOpt & {
+ label: string;
+ callback: CallbackPromise;
+ allow_propogation?: boolean;
+};
+
+export type IMapMarkerArea = {
+ show_display?: boolean;
+ no_drag?: boolean;
+ lc_geocode: LcGeocodeCallback;
+}
+
+export type ILayoutTrellisLine = ILabelOpt &
+ IClOpt & {
+ notify?: IClOpt &
+ ICb &
+ ILabelOpt &
+ IGlOpt & {
+ glyph_first?: boolean;
+ };
+ };
diff --git a/utils/src/app/types/element.ts b/utils/src/app/types/element.ts
@@ -0,0 +1,7 @@
+import type { CallbackPromiseGeneric } from "$root";
+
+export type ElementCallbackValue = CallbackPromiseGeneric<{ value: string; pass: boolean; }>;
+export type ElementCallbackValueKeydown<T extends HTMLElement> = CallbackPromiseGeneric<{ key: string; key_s: boolean; el: T }>;
+export type ElementCallbackValueBlur<T extends HTMLElement> = CallbackPromiseGeneric<{ el: T }>;
+export type ElementCallbackValueFocus<T extends HTMLElement> = CallbackPromiseGeneric<{ el: T }>;
+export type ElementCallbackMount<T extends HTMLElement> = CallbackPromiseGeneric<{ el: T }>;
diff --git a/utils/src/app/types/geometry.ts b/utils/src/app/types/geometry.ts
@@ -0,0 +1,15 @@
+export type GeometryScreenPositionHorizontal = `left` | `center` | `right`;
+export type GeometryScreenPositionVertical = `top` | `center` | `bottom`;
+export type GeometryScreenPosition = `${GeometryScreenPositionVertical}-${GeometryScreenPositionHorizontal}`;
+export type GeometryCardinalDirection = `up` | `down` | `left` | `right`;
+export type GeometryDimension =
+ `xs` |
+ `sm` |
+ `md` |
+ `lg` |
+ `xl`;
+export type GeometryGlyphDimension =
+ | `${GeometryDimension}`
+ | `${GeometryDimension}-`
+ | `${GeometryDimension}--`
+ | `${GeometryDimension}+`;
+\ No newline at end of file
diff --git a/utils/src/app/types/glyph.ts b/utils/src/app/types/glyph.ts
@@ -0,0 +1,125 @@
+import type { GeometryCardinalDirection } from "$root";
+
+export type GlyphKey = |
+ `crop` |
+ `map-trifold` |
+ `trash-simple` |
+ `backspace` |
+ `user-circle-check` |
+ `images-square` |
+ `bell` |
+ `columns` |
+ `bold` |
+ `article` |
+ `grid-four` |
+ `link-simple` |
+ `seal-check` |
+ `selection-foreground` |
+ `image-square` |
+ `image-broken` |
+ `funnel` |
+ `users-three` |
+ `note-blank` |
+ `user-circle-plus` |
+ `user-circle` |
+ `receipt` |
+ `invoice` |
+ `note` |
+ `arrow-left` |
+ `arrows-down-up` |
+ `basket` |
+ `arrow-right` |
+ `upload-simple` |
+ `printer` |
+ `download-simple` |
+ `list` |
+ `asterisk` |
+ `asterisk-simple` |
+ `subtitles-slash` |
+ `cardholder` |
+ `globe-x` |
+ `exclamation-mark` |
+ `network-x` |
+ `x-circle` |
+ `address-book-tabs` |
+ `paper-plane-tilt` |
+ `note-pencil` |
+ `share-fat` |
+ `folder` |
+ `trash` |
+ `plus-circle` |
+ `currency-${GlyphKeyCurrency}` |
+ `arrow-down` |
+ `caret-circle-down` |
+ `caret-circle-up` |
+ `shopping-bag-open` |
+ `coffee-bean` |
+ `compass` |
+ `map-pin-simple` |
+ `handbag-simple` |
+ `devices` |
+ `lock-key` |
+ `gear` |
+ `gear-fine` |
+ `bell-simple` |
+ `envelope` |
+ `house-line` |
+ `arrows-left-right` |
+ `list-plus` |
+ `squares-four` |
+ `list-plus` |
+ `app-window` |
+ `circle-notch` |
+ `subtract-square` |
+ `device-tablet-speaker` |
+ `weather-cloud` |
+ `warning` |
+ `circle-notch` |
+ `minus` |
+ `key` |
+ `arrow-u-up-left` |
+ `arrow-counter-clockwise` |
+ `circle` |
+ `check-circle` |
+ `circle-dashed` |
+ `dots-three` |
+ `cards-three` |
+ `lightning` |
+ `cards` |
+ `note-pencil` |
+ `tray` |
+ `calendar-dots` |
+ `notepad` |
+ `network` |
+ `calendar-blank` |
+ `chats-circle` |
+ `plant` |
+ `farm` |
+ `magnifying-glass` |
+ `chat-circle-dots` |
+ `dots-three-outline` |
+ `copy` |
+ `circles-four` |
+ `waveform` |
+ `film-strip` |
+ `arrow-up` |
+ `arrow-circle-up` |
+ `plus` |
+ `funnel-simple` |
+ `user` |
+ `camera` |
+ `check` |
+ `file` |
+ `share-network` |
+ `question` |
+ `minus-circle` |
+ `globe-simple` |
+ `globe` |
+ `warning-circle` |
+ `x` |
+ `info` |
+ `caret-${GeometryCardinalDirection}` |
+ `caret-up-down`;
+
+export type GlyphKeyCurrency = `dollar` | `eur`;
+export type GlyphWeight = `bold` | `fill`
diff --git a/utils/src/app/types/interface.ts b/utils/src/app/types/interface.ts
@@ -0,0 +1,214 @@
+import type { CallbackPromise, CallbackPromiseGeneric, EntryStyle, GlyphKey, IGlyph, IInput, IInputValue, ITextArea, LayerGlyphBasisKind, LoadingBlades, LoadingDimension, SvelteTransitionConfig, ThemeLayer } from "$root";
+
+export type IDisabled = {
+ disabled: boolean | never;
+};
+
+export type IDisabledOpt = Partial<IDisabled>;
+
+export type IBasis<T> = {
+ basis: T;
+};
+
+export type ICb = {
+ callback: CallbackPromise | never;
+};
+
+export type ICbOpt = Partial<ICb>;
+
+export type ICbG<T> = {
+ callback: CallbackPromiseGeneric<T> | never;
+};
+
+export type ICbGOpt<T> = Partial<ICbG<T>>;
+
+export type ICl = {
+ classes: string | never;
+};
+
+export type IClOpt = Partial<ICl>;
+
+export type IClWrap = {
+ classes_wr: string | never;
+};
+
+export type IClOptWrap = Partial<IClWrap>;
+
+export type IId = {
+ id: string | never;
+};
+
+export type IIdOpt = Partial<IId>;
+
+export type IGl = {
+ glyph: IGlyph | never;
+};
+
+export type IGlOpt = Partial<IGl>;
+
+export type IGlyphKey = {
+ glyph: GlyphKey
+};
+
+export type ILy = {
+ layer: ThemeLayer | never;
+};
+
+export type ILyOpt = Partial<ILy>;
+
+export type ILableFieldsSwap = {
+ toggle: boolean;
+ on: IClOpt & {
+ value: string;
+ },
+ off: IClOpt & {
+ value: string;
+ },
+};
+
+export type ILabelSwap = {
+ swap: ILableFieldsSwap;
+}
+
+export type ILabelTupFields = {
+ left?: ILableFields[];
+ right?: ILableFields[];
+};
+
+export type ILabelTup = {
+ label: ILabelTupFields;
+};
+
+export type LabelFieldKind = `link` | `on` | `shade`;
+
+export type ILableFields = & {
+ classes_wrap?: string
+ classes?: string;
+ kind?: LayerGlyphBasisKind;
+ hide_truncate?: boolean;
+ hide_active?: boolean;
+} & (
+ ({
+ value: string;
+ } | ILabelSwap)
+ | IGl
+ );
+
+export type ILabel = {
+ label: ILableFields;
+};
+
+export type ILabelOpt = Partial<ILabel>;
+
+export type ILoadSymbol = IClOpt & {
+ color?: 'white';
+ blades?: LoadingBlades;
+ dim?: LoadingDimension;
+};
+
+export type IIdG<T extends string> = {
+ id: T | never;
+};
+
+export type FormField = {
+ validate: RegExp;
+ charset: RegExp;
+};
+
+export type IIdGOpt<T extends string> = Partial<IIdG<T>>;
+
+export type IIdWrap = {
+ id_wrap: string | never;
+};
+
+export type IIdWrapOpt = Partial<IIdWrap>;
+
+export type ILabelValue = {
+ label: IClOpt & {
+ value: string;
+ };
+};
+
+export type ILabelDisplay = IIdWrapOpt & IClOpt & ILabelValue & ILyOpt & {
+ style?: EntryStyle;
+};
+
+
+export type ILoading = {
+ loading: boolean | never;
+};
+
+export type ILoadingOpt = Partial<ILoading>;
+
+export type IEntryWrap = IClOpt & IIdOpt & ILyOpt & {
+ style?: EntryStyle;
+ style_a?: true;
+ no_pad?: true;
+ fade?: {
+ in?: SvelteTransitionConfig;
+ out?: SvelteTransitionConfig;
+ };
+}
+
+export type IEntryLine = ILoadingOpt & {
+ wrap?: IEntryWrap;
+ el: IInputValue<string>;
+ notify_inline?: {
+ glyph: GlyphKey | IGlyph;
+ };
+};
+
+export type IEntryLineIdb = ILoadingOpt & {
+ wrap?: IEntryWrap;
+ el: IInput<string>;
+ notify_inline?: {
+ glyph: GlyphKey | IGlyph;
+ };
+};
+
+export type IEntryMultiLine = {
+ wrap?: IEntryWrap;
+ el: ITextArea;
+ notify_inline?: {
+ glyph: GlyphKey | IGlyph;
+ };
+}
+
+export type IEnvelopeLower = {
+ visible: boolean;
+ close: CallbackPromise;
+ full_cover?: boolean;
+ label_close?: string | true;
+};
+
+export type IButtonRound = IClOpt & ILoadingOpt & {
+ label: string;
+ callback: CallbackPromise;
+};
+
+export type INavigationRoutePreventRouteNav = {
+ prevent_route?: {
+ callback: CallbackPromise;
+ };
+};
+
+export type INavigationRoutePreventRoute = {
+ prevent_route: CallbackPromise;
+};
+
+export type IImageBlob = IIdOpt & {
+ data: Uint8Array | undefined;
+ alt?: string;
+};
+
+export type IImagePath = IClOpt &
+ ICbGOpt<
+ MouseEvent & {
+ currentTarget: EventTarget & HTMLImageElement;
+ }
+ > &
+ IIdOpt & {
+ path?: string;
+ alt?: string;
+ };
+
diff --git a/utils/src/app/types/resolve.ts b/utils/src/app/types/resolve.ts
@@ -0,0 +1,29 @@
+export type ResolveEnumArea_Unit = 'ac' | 'ft2' | 'ha' | 'm2';
+export type ResolveEnumBudget_Item_Type = 'capital_investment' | 'equipment' | 'fees' | 'infrastructure' | 'insurance' | 'labor' | 'materials' | 'other' | 'supplies';
+export type ResolveEnumBudget_Spending_Type = 'equipment' | 'labor' | 'maintenance' | 'other' | 'supplies' | 'utilities';
+export type ResolveEnumCredential = 'email' | 'phone';
+export type ResolveEnumPayment_Method = 'cash';
+export type ResolveEnumPayment_Period = 'biweekly' | 'hourly' | 'monthly' | 'weekly';
+export type ResolveEnumPayment_Status = 'confirmed' | 'pending';
+export type ResolveEnumQuantity_Unit = 'g' | 'kg' | 'lb' | 'ton';
+export type ResolveEnumRole = 'admin' | 'guest' | 'internal' | 'member';
+export type ResolveEnumWorker_Type = 'contractor' | 'laborer';
+export type ResolveAccountInfo = { id: string, created_at: string, updated_at: string, role: ResolveEnumRole, auth_ref: { credential: ResolveEnumCredential, email: { id: string, created_at: string, updated_at: string, address: string } }, profiles?: Array<{ id: string, created_at: string, updated_at: string, name: string, display_name?: string | null, primary: boolean, about?: string | null, emails: Array<{ id: string, created_at: string, updated_at: string, address: string }>, profile_photos?: Array<{ id: string, created_at: string, updated_at: string, primary: boolean, title?: string | null, description?: string | null, media_image: { id: string, created_at: string, updated_at: string, url: string } }> | null, nostr_keys: Array<{ id: string, created_at: string, updated_at: string, public_key: string }> }> | null, farms?: Array<{ id: string, created_at: string, updated_at: string, name: string, area?: number | null, area_unit: ResolveEnumArea_Unit, geolocation: { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } }, farm_products?: Array<{ id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }> | null, farm_lots?: Array<{ id: string, created_at: string, updated_at: string, name?: string | null, area?: number | null, area_unit: ResolveEnumArea_Unit, geolocation: { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } }, farm_lot_products?: Array<{ id: string, created_at: string, updated_at: string, area_planted?: number | null, area_unit: ResolveEnumArea_Unit, date_planted?: string | null, days_to_maturity?: number | null, farm_product: { id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }, farm_trade_products?: Array<{ id: string, created_at: string, updated_at: string, title: string, description: string, process?: string | null, trade_product_prices?: Array<{ id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null, trade_product_quantitys?: Array<{ id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null }> | null, farm_lot_harvests?: Array<{ id: string, created_at: string, updated_at: string, quantity_unit: ResolveEnumQuantity_Unit, quantity_harvested: number }> | null }> | null }> | null }> | null };
+export type ResolveAuthRefInfo = { credential: ResolveEnumCredential, email: { id: string, created_at: string, updated_at: string, address: string } };
+export type ResolveEmailInfo = { id: string, created_at: string, updated_at: string, address: string };
+export type ResolveProfileInfo = { id: string, created_at: string, updated_at: string, name: string, display_name?: string | null, primary: boolean, about?: string | null, emails: Array<{ id: string, created_at: string, updated_at: string, address: string }>, profile_photos?: Array<{ id: string, created_at: string, updated_at: string, primary: boolean, title?: string | null, description?: string | null, media_image: { id: string, created_at: string, updated_at: string, url: string } }> | null, nostr_keys: Array<{ id: string, created_at: string, updated_at: string, public_key: string }> };
+export type ResolveProfilePhotoInfo = { id: string, created_at: string, updated_at: string, primary: boolean, title?: string | null, description?: string | null, media_image: { id: string, created_at: string, updated_at: string, url: string } };
+export type ResolveMediaImageInfo = { id: string, created_at: string, updated_at: string, url: string };
+export type ResolveNostrKeyInfo = { id: string, created_at: string, updated_at: string, public_key: string };
+export type ResolveFarmInfo = { id: string, created_at: string, updated_at: string, name: string, area?: number | null, area_unit: ResolveEnumArea_Unit, geolocation: { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } }, farm_products?: Array<{ id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }> | null, farm_lots?: Array<{ id: string, created_at: string, updated_at: string, name?: string | null, area?: number | null, area_unit: ResolveEnumArea_Unit, geolocation: { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } }, farm_lot_products?: Array<{ id: string, created_at: string, updated_at: string, area_planted?: number | null, area_unit: ResolveEnumArea_Unit, date_planted?: string | null, days_to_maturity?: number | null, farm_product: { id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }, farm_trade_products?: Array<{ id: string, created_at: string, updated_at: string, title: string, description: string, process?: string | null, trade_product_prices?: Array<{ id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null, trade_product_quantitys?: Array<{ id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null }> | null, farm_lot_harvests?: Array<{ id: string, created_at: string, updated_at: string, quantity_unit: ResolveEnumQuantity_Unit, quantity_harvested: number }> | null }> | null }> | null };
+export type ResolveGeolocationInfo = { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } };
+export type ResolveAddressInfo = { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string };
+export type ResolveFarmLotInfo = { id: string, created_at: string, updated_at: string, name?: string | null, area?: number | null, area_unit: ResolveEnumArea_Unit, geolocation: { id: string, created_at: string, updated_at: string, point: { type: string, coordinates: Array<number> }, polygon?: { type: string, coordinates: Array<Array<Array<number>>> } | null, address: { id: string, created_at: string, updated_at: string, primary: string, admin: string, country: string } }, farm_lot_products?: Array<{ id: string, created_at: string, updated_at: string, area_planted?: number | null, area_unit: ResolveEnumArea_Unit, date_planted?: string | null, days_to_maturity?: number | null, farm_product: { id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }, farm_trade_products?: Array<{ id: string, created_at: string, updated_at: string, title: string, description: string, process?: string | null, trade_product_prices?: Array<{ id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null, trade_product_quantitys?: Array<{ id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null }> | null, farm_lot_harvests?: Array<{ id: string, created_at: string, updated_at: string, quantity_unit: ResolveEnumQuantity_Unit, quantity_harvested: number }> | null }> | null };
+export type ResolveFarmLotProductInfo = { id: string, created_at: string, updated_at: string, area_planted?: number | null, area_unit: ResolveEnumArea_Unit, date_planted?: string | null, days_to_maturity?: number | null, farm_product: { id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null }, farm_trade_products?: Array<{ id: string, created_at: string, updated_at: string, title: string, description: string, process?: string | null, trade_product_prices?: Array<{ id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null, trade_product_quantitys?: Array<{ id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null }> | null, farm_lot_harvests?: Array<{ id: string, created_at: string, updated_at: string, quantity_unit: ResolveEnumQuantity_Unit, quantity_harvested: number }> | null };
+export type ResolveFarmProductInfo = { id: string, created_at: string, updated_at: string, name: string, farm_lot_products?: Array<{ id: string }> | null };
+export type ResolveFarmTradeProductInfo = { id: string, created_at: string, updated_at: string, title: string, description: string, process?: string | null, trade_product_prices?: Array<{ id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null, trade_product_quantitys?: Array<{ id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null }> | null };
+export type ResolveTradeProductPriceInfo = { id: string, created_at: string, updated_at: string, amount: number, currency: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null };
+export type ResolveTradeProductQuantityInfo = { id: string, created_at: string, updated_at: string, quantity_amount?: number | null, quantity_unit: ResolveEnumQuantity_Unit, quantity_label?: string | null };
+export type ResolveFarmLotHarvestInfo = { id: string, created_at: string, updated_at: string, quantity_unit: ResolveEnumQuantity_Unit, quantity_harvested: number };
+export type ResolveGeometryPoint = { type: string, coordinates: Array<number> };
+export type ResolveGeometryPolygon = { type: string, coordinates: Array<Array<Array<number>>> };
diff --git a/utils/src/app/types/view.ts b/utils/src/app/types/view.ts
@@ -0,0 +1,33 @@
+import { GeolocationAddress, GeolocationPoint, ResolveGeolocationInfo, ResolveProfileInfo } from "$root";
+
+export type IViewSearchData = {
+ geolocations: ResolveGeolocationInfo[];
+ profiles: ResolveProfileInfo[];
+ nostr_relay: { id: string }[];
+ farm_products: { id: string }[];
+};
+
+export type IViewFarmsProductsAddSubmission = {
+ product: string;
+ process: string;
+ description: string;
+ price_amount: number;
+ price_currency: string;
+ price_quantity_unit: string;
+ photos: string[];
+ quantity_amount: number;
+ quantity_unit: string;
+ quantity_label: string;
+ geolocation_point: GeolocationPoint;
+ geolocation_address: GeolocationAddress;
+};
+
+export type IViewFarmsAddSubmission = {
+ farm_name: string;
+ farm_area: number;
+ farm_area_unit: string;
+ farm_contact_name: string;
+ geolocation_point: GeolocationPoint;
+ geolocation_address: GeolocationAddress;
+};
+
diff --git a/utils/src/app/util.ts b/utils/src/app/util.ts
@@ -0,0 +1,95 @@
+import type { AppLayoutKey, AppLayoutKeyIOS, AppLayoutKeyWeb, LabelFieldKind, NavigationParamTuple, ThemeLayer } from "$root";
+
+export const fmt_cl = (classes?: string): string => {
+ return classes ? classes : ``;
+};
+
+export const get_layout = (val: string | false): AppLayoutKey => {
+ switch (val) {
+ case `ios0`:
+ case `ios1`:
+ case `webm0`:
+ case `webm1`:
+ return val;
+ default:
+ return `ios0`;
+ };
+};
+
+export const get_ios_layout = (val: string | false): AppLayoutKeyIOS => {
+ switch (val) {
+ case `ios0`:
+ case `ios1`:
+ return val;
+ default:
+ return `ios0`;
+ };
+};
+
+export const get_web_layout = (val: string | false): AppLayoutKeyWeb => {
+ switch (val) {
+ case `webm0`:
+ case `webm1`:
+ return val;
+ default:
+ return `webm0`;
+ };
+};
+
+export const parse_layer = (layer?: number, layer_default?: ThemeLayer): ThemeLayer => {
+ switch (layer) {
+ case 0:
+ case 1:
+ case 2:
+ return layer;
+ default:
+ return layer_default ? layer_default : 0;
+ };
+};
+
+export const value_constrain = (regex_charset: RegExp, value: string): string => {
+ return value
+ .split(``)
+ .filter((char) => regex_charset.test(char))
+ .join(``);
+};
+
+export const value_constrain_textarea = (regex_charset: RegExp, value: string): string => {
+ return value
+ .replace(/\u00A0/g, ` `)
+ .split(/[\n]/)
+ .map(line => line
+ .split(``)
+ .filter((char) => regex_charset.test(char))
+ .join(``)
+ )
+ .join("\n");
+};
+
+export const encode_qp = (params_list?: NavigationParamTuple[]): string => {
+ const params = (params_list || []).filter(i => i[0] && i[1])
+ if (!params.length) return ``;
+ return params.map(([k, v], index) => `${index === 0 ? `?` : ``}&${k.trim()}=${encodeURI(v.trim())}`).join(``).trim();
+};
+
+export const encode_qp_route = <T extends string>(route: T, params_list?: NavigationParamTuple[]): string => {
+ return `${route}/${encode_qp(params_list)}`.replaceAll(`//`, `/`)
+};
+
+export const fmt_trellis = (hide_border_t: boolean, hide_border_b: boolean): string => {
+ return `${hide_border_t ? `group-first:border-t-0` : `group-first:border-t-line`} ${hide_border_b ? `group-last:border-b-0` : `group-last:border-b-line`}`;
+};
+
+export const get_label_classes_kind = (layer: ThemeLayer, label_kind: LabelFieldKind | undefined, hide_active: boolean): string => {
+ return `text-layer-${layer}-glyph${label_kind ? `-${label_kind}` : ``} ${hide_active ? `` : `group-active:text-layer-${layer}-glyph${label_kind ? `-${label_kind}_a` : `_a`}`}`
+};
+
+export const fmt_textarea_value = (value: string): string => {
+ return value.replace(/ /g, `\u00A0`);
+};
+
+export const list_assign = (list_curr: string[], list_new: string[]): string[] => {
+ return Array.from(
+ new Set([...list_curr, ...list_new]),
+ ).filter((i) => !!i);
+};
+\ No newline at end of file
diff --git a/utils/src/app/util/resolve-enum.ts b/utils/src/app/util/resolve-enum.ts
@@ -0,0 +1,165 @@
+export type ResolveEnumAreaUnitKey = `Ac` | `Ft2` | `Ha` | `M2`;
+
+export const parse_enum_area_unit_key = (val: string): ResolveEnumAreaUnitKey | undefined => {
+ switch (val) {
+ case `ac`:
+ return `Ac`;
+ case `ft2`:
+ return `Ft2`;
+ case `ha`:
+ return `Ha`;
+ case `m2`:
+ return `M2`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumBudgetItemTypeKey = `CapitalInvestment` | `Equipment` | `Fees` | `Infrastructure` | `Insurance` | `Labor` | `Materials` | `Other` | `Supplies`;
+
+export const parse_enum_budget_item_type_key = (val: string): ResolveEnumBudgetItemTypeKey | undefined => {
+ switch (val) {
+ case `capitalinvestment`:
+ return `CapitalInvestment`;
+ case `equipment`:
+ return `Equipment`;
+ case `fees`:
+ return `Fees`;
+ case `infrastructure`:
+ return `Infrastructure`;
+ case `insurance`:
+ return `Insurance`;
+ case `labor`:
+ return `Labor`;
+ case `materials`:
+ return `Materials`;
+ case `other`:
+ return `Other`;
+ case `supplies`:
+ return `Supplies`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumBudgetSpendingTypeKey = `Equipment` | `Labor` | `Maintenance` | `Other` | `Supplies` | `Utilities`;
+
+export const parse_enum_budget_spending_type_key = (val: string): ResolveEnumBudgetSpendingTypeKey | undefined => {
+ switch (val) {
+ case `equipment`:
+ return `Equipment`;
+ case `labor`:
+ return `Labor`;
+ case `maintenance`:
+ return `Maintenance`;
+ case `other`:
+ return `Other`;
+ case `supplies`:
+ return `Supplies`;
+ case `utilities`:
+ return `Utilities`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumCredentialKey = `Email` | `Phone`;
+
+export const parse_enum_credential_key = (val: string): ResolveEnumCredentialKey | undefined => {
+ switch (val) {
+ case `email`:
+ return `Email`;
+ case `phone`:
+ return `Phone`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumPaymentMethodKey = `Cash`;
+
+export const parse_enum_payment_method_key = (val: string): ResolveEnumPaymentMethodKey | undefined => {
+ switch (val) {
+ case `cash`:
+ return `Cash`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumPaymentPeriodKey = `Biweekly` | `Hourly` | `Monthly` | `Weekly`;
+
+export const parse_enum_payment_period_key = (val: string): ResolveEnumPaymentPeriodKey | undefined => {
+ switch (val) {
+ case `biweekly`:
+ return `Biweekly`;
+ case `hourly`:
+ return `Hourly`;
+ case `monthly`:
+ return `Monthly`;
+ case `weekly`:
+ return `Weekly`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumPaymentStatusKey = `Confirmed` | `Pending`;
+
+export const parse_enum_payment_status_key = (val: string): ResolveEnumPaymentStatusKey | undefined => {
+ switch (val) {
+ case `confirmed`:
+ return `Confirmed`;
+ case `pending`:
+ return `Pending`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumQuantityUnitKey = `G` | `Kg` | `Lb` | `Ton`;
+
+export const parse_enum_quantity_unit_key = (val: string): ResolveEnumQuantityUnitKey | undefined => {
+ switch (val) {
+ case `g`:
+ return `G`;
+ case `kg`:
+ return `Kg`;
+ case `lb`:
+ return `Lb`;
+ case `ton`:
+ return `Ton`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumRoleKey = `Admin` | `Guest` | `Internal` | `Member`;
+
+export const parse_enum_role_key = (val: string): ResolveEnumRoleKey | undefined => {
+ switch (val) {
+ case `admin`:
+ return `Admin`;
+ case `guest`:
+ return `Guest`;
+ case `internal`:
+ return `Internal`;
+ case `member`:
+ return `Member`;
+ default:
+ return undefined;
+ }
+};
+
+export type ResolveEnumWorkerTypeKey = `Contractor` | `Laborer`;
+
+export const parse_enum_worker_type_key = (val: string): ResolveEnumWorkerTypeKey | undefined => {
+ switch (val) {
+ case `contractor`:
+ return `Contractor`;
+ case `laborer`:
+ return `Laborer`;
+ default:
+ return undefined;
+ }
+};
+\ No newline at end of file
diff --git a/utils/src/app/util/resolve.ts b/utils/src/app/util/resolve.ts
@@ -0,0 +1,5 @@
+import { ResolveAddressInfo } from "../types/resolve";
+
+export const lib_address_fmt = (addr: ResolveAddressInfo): string => {
+ return `${addr.primary}, ${addr.admin}, ${addr.country}`
+};
+\ No newline at end of file
diff --git a/utils/src/app/util/search.ts b/utils/src/app/util/search.ts
@@ -0,0 +1,72 @@
+export type SearchServiceResult = Record<string, any> & { id: string, result_k: string; result_v: string; };
+export type SearchServiceFlattenedData = Record<string, any> & { list: string; };
+
+export type ISearchService = {
+ search(input: string): SearchServiceResult[]
+};
+
+export class SearchService implements ISearchService {
+ private _flattened_data: SearchServiceFlattenedData[] = [];
+ private _index_map: Map<string, Record<string, any>[]> = new Map();
+
+ constructor(data: Record<string, any[]>) {
+ this.flatten_data(data);
+ this.index_data();
+ }
+
+ private get flattened_data() {
+ return this._flattened_data;
+ }
+
+ private flatten_data(data: Record<string, any[]>) {
+ Object.keys(data).forEach((list_group) => {
+ const list = data[list_group];
+ list.forEach((item) => {
+ const flattened_item: SearchServiceFlattenedData = { ...item, group: list_group };
+ this._flattened_data.push(flattened_item);
+ });
+ });
+ }
+
+ private index_data(): void {
+ this.flattened_data.forEach((item) => {
+ Object.keys(item).forEach((key) => {
+ const key_lower = key.toLowerCase();
+ const value = item[key];
+ if (value != null) {
+ if (!this._index_map.has(key_lower)) this._index_map.set(key_lower, []);
+ this._index_map.get(key_lower)?.push(item);
+ }
+ });
+ });
+ }
+
+ public search(query: string): SearchServiceResult[] {
+ if (!query) return [];
+ const search_query = query.toLowerCase().trim();
+ let results: SearchServiceResult[] = [];
+ const results_seen = new Set<string>();
+ this._index_map.forEach((items) => {
+ items.forEach((item) => {
+ for (const [key, value] of Object.entries(item)) {
+ if (key === `id` || key === `created_at` || key === `updated_at` || key === `public_key` || key === `group`) continue;
+ if (value && value.toString().replace(/[()_-]/gi, ` `).toLowerCase().includes(search_query)) {
+ const { group, ...rest } = item;
+ if (!(`id` in item)) continue;
+ const result_key = item.id;
+ if (results_seen.has(result_key)) continue;
+ results_seen.add(result_key);
+ const reshaped_result: SearchServiceResult = {
+ id: item.id,
+ result_k: key,
+ result_v: value,
+ [group]: { ...rest },
+ };
+ results.push(reshaped_result);
+ }
+ };
+ });
+ });
+ return results;
+ }
+}
diff --git a/utils/src/app/validation/view.ts b/utils/src/app/validation/view.ts
@@ -0,0 +1,27 @@
+import { form_fields, IViewFarmsAddSubmission, IViewFarmsProductsAddSubmission, util_rxp, vs_geolocation_address, vs_geolocation_point, zod_numf_pos, zod_numf_price, zod_numi_pos } from "$root";
+import { _env } from "src/_env";
+import { z } from "zod";
+
+export const vs_view_farms_products_add_submission: z.ZodSchema<IViewFarmsProductsAddSubmission> = z.object({
+ product: z.string().regex(form_fields.product_key.validate),
+ process: z.string().regex(form_fields.product_process.validate),
+ description: z.string().regex(form_fields.product_description.validate),
+ price_amount: zod_numf_price,
+ price_currency: z.string().regex(form_fields.price_currency.validate),
+ price_quantity_unit: z.string().regex(form_fields.quantity_unit.validate),
+ photos: z.array(z.string().regex(_env.PROD ? util_rxp.url_image_upload : util_rxp.url_image_upload_dev)),
+ quantity_amount: zod_numi_pos,
+ quantity_unit: z.string().regex(form_fields.quantity_unit.validate),
+ quantity_label: z.string().regex(form_fields.quantity_label.validate),
+ geolocation_point: vs_geolocation_point,
+ geolocation_address: vs_geolocation_address,
+});
+
+export const vs_view_farms_add_submission: z.ZodSchema<IViewFarmsAddSubmission> = z.object({
+ farm_name: z.string().regex(form_fields.farm_name.validate),
+ farm_area: zod_numf_pos,
+ farm_area_unit: z.string().regex(form_fields.area_unit.validate),
+ farm_contact_name: z.string().regex(form_fields.contact_name.validate),
+ geolocation_point: vs_geolocation_point,
+ geolocation_address: vs_geolocation_address,
+});
diff --git a/utils/src/ascii.ts b/utils/src/ascii.ts
@@ -1,2 +0,0 @@
-export const root_symbol = "»-`--,";
-export const root_symbol_full = "»--`--,---";
-\ No newline at end of file
diff --git a/utils/src/client/geo.ts b/utils/src/client/geo.ts
@@ -0,0 +1,11 @@
+import type { ErrorMessage, IClientGeolocationPosition } from "$root";
+
+export type IGeolocationErrorMessage =
+ | `error.client.geolocation.permission_denied`
+ | `error.client.geolocation.location_unavailable`
+ | `error.client.geolocation.timeout`
+ | `*`;
+
+export type IClientGeolocation = {
+ current(): Promise<IClientGeolocationPosition | ErrorMessage<IGeolocationErrorMessage>>;
+};
+\ No newline at end of file
diff --git a/utils/src/client/geolocation.ts b/utils/src/client/geolocation.ts
@@ -1,15 +0,0 @@
-import { type ErrorMessage } from "..";
-
-export type IGeolocationErrorMessage = `*-permissions` | `*`;
-
-export type IClientGeolocationPosition = {
- lat: number;
- lng: number;
- accuracy?: number;
- altitude?: number;
- altitude_accuracy?: number;
-};
-
-export type IClientGeolocation = {
- current(): Promise<IClientGeolocationPosition | ErrorMessage<IGeolocationErrorMessage>>;
-};
-\ No newline at end of file
diff --git a/utils/src/client/gui.ts b/utils/src/client/gui.ts
@@ -1,12 +1,10 @@
-import { type ResultsList } from "..";
-
export type IClientGuiDialogKind = "info" | "warning" | "error";
export type IClientGuiDialogAlertOpts = string;
export type IClientGuiDialogConfirmOpts = string | { title?: string, kind?: IClientGuiDialogKind; message: string; cancel?: string; ok?: string; };
-export type IClientGuiDialogResolve = ResultsList<string>;
+export type IClientGuiDialogResolve = { results: string[]; };
export type IClientGuiNotifyPermission = "default" | "denied" | "granted";
export type IClientGuiNotifySendOptions = {
diff --git a/utils/src/config.ts b/utils/src/config.ts
@@ -0,0 +1,19 @@
+export const cfg_map = {
+ styles: {
+ base: {
+ light: `https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json`,
+ dark: `https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json`
+ }
+ },
+ popup: {
+ dot: {
+ offset: [0, -10] as [number, number]
+ }
+ },
+ coords: {
+ default: {
+ lat: 0,
+ lng: 0,
+ }
+ }
+};
+\ No newline at end of file
diff --git a/utils/src/currency.ts b/utils/src/currency.ts
@@ -1,162 +1,43 @@
-import { regex } from "./regex";
+import { util_rxp } from "$root";
export type FiatCurrency = `usd` | `eur`;
export const fiat_currencies: FiatCurrency[] = [`usd`, `eur`] as const;
-export type FiatCurrencyGlyphs = `dollar` | `eur`;
-
-export type CurrencyPriceFmt = [string, FiatCurrency, number, number]
-
-export function parse_currency(val?: string): FiatCurrency {
- const _val = val?.trim().toLowerCase()
- switch (_val) {
+export const parse_currency = (val?: string): FiatCurrency => {
+ const cur = val?.trim().toLowerCase()
+ switch (cur) {
case `usd`:
case `eur`:
- return _val;
+ return cur;
default:
return `usd`;
};
};
-export function parse_currency_glyph_key(val?: string): | `currency-${FiatCurrencyGlyphs}` {
- switch (val) {
- case "usd":
- return `currency-dollar`;
- case "eur":
- return `currency-eur`;
- default:
- return `currency-dollar`;
- };
-};
-
-export type CurrencyDecimalSeparator = `,` | `.`;
-
-export type CurrencyMetadata = {
- cur: FiatCurrency;
- /**
- * currency symbol
- */
- cur_s: string;
- /**
- * currency marker
- */
- cur_m: string;
- /**
- * true if symbol is at the start
- */
- cur_pos: boolean;
- dec_s: CurrencyDecimalSeparator;
-}
-
-
-export type CurrencyPrice = CurrencyMetadata & {
- /**
- * integer num
- */
- num_i: number;
- /**
- * fractional num
- */
- num_f: number;
- /**
- * integer value
- */
- val_i: string;
- /**
- * fractional value
- */
- val_f: string;
-};
-
-export const locale_fractional_decimal = (locale: string): CurrencyDecimalSeparator => {
- const formatter = new Intl.NumberFormat(locale);
- const formatted = formatter.format(1.1);
- return formatted.includes(',') ? `,` : `.`;
+export const fmt_price = (locale: string, value: string, currency: string): string => {
+ const fmt = new Intl.NumberFormat(locale, {
+ style: 'currency',
+ currency: currency.toUpperCase(),
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ });
+ return fmt.format(parseFloat(value));
};
export const parse_currency_marker = (locale: string, currency: string): string => {
const cur = parse_currency(currency);
- const fmt = new Intl.NumberFormat(`en-US`, {
+ const fmt = new Intl.NumberFormat(locale, {
style: 'currency',
currency: cur.toUpperCase(),
minimumFractionDigits: 2,
});
const fmt_basis = fmt.format(1);
let fmt_res: string | undefined = undefined;
- fmt_res = fmt_basis.match(regex.currency_marker)?.[0];
+ fmt_res = fmt_basis.match(util_rxp.currency_marker)?.[0];
if (fmt_res) return fmt_res;
- fmt_res = fmt_basis.match(regex.currency_symbol)?.[0];
+ fmt_res = fmt_basis.match(util_rxp.currency_symbol)?.[0];
if (fmt_res) return fmt_res;
fmt_res = fmt_basis.match(new RegExp(cur, `i`))?.[0];
if (fmt_res) return fmt_res;
return cur.toUpperCase();
-}
-
-export const parse_currency_price = (locale: string, currency: string, price: number | string): CurrencyPrice | undefined => {
- const num_amt = Math.max(typeof price === `number` ? price : Number(price.replace(/[^0-9.]/g, ``)), 0);
- const cur = parse_currency(currency);
- const fmt = new Intl.NumberFormat(locale, {
- style: 'currency',
- currency: cur.toUpperCase(),
- minimumFractionDigits: 2,
- });
- const fmt_amt = fmt.format(num_amt);
- const fmt_num = fmt_amt.replace(/[^0-9.,\s\u200F\u00A0]+/g, ``).trim()
- const cur_s = fmt_amt.match(regex.currency_symbol)?.[0] || fmt_amt.match(new RegExp(cur, `i`))?.[0];
- if (!cur_s) return undefined;
- const cur_m = fmt_amt.match(regex.currency_marker)?.[0] || cur_s;
- const cur_pos = fmt_amt.startsWith(cur_m);
- const dec_s = locale_fractional_decimal(locale);
- const [_val_i, _val_f] = fmt_num.split(dec_s);
- const val_i = _val_i?.replace(regex.nbsp_rp, ``).replace(regex.rtlm_rp, ``);
- const val_f = _val_f?.replace(regex.nbsp_rp, ``).replace(regex.rtlm_rp, ``);
- if (!val_i || !val_f) return undefined;
- const num_i = Math.max(Math.min(parseInt(val_i?.replace(dec_s === `,` ? regex.periods : regex.commas, ``) || `0`), 0));
- const num_f = Math.max(Math.min(parseInt(val_f || `0`), 0));
-
- return {
- cur,
- cur_s,
- cur_m,
- cur_pos,
- dec_s,
- num_i,
- num_f,
- val_i,
- val_f,
- };
-};
-
-export const fmt_currency_price = (currency_price: CurrencyPrice, hide_currency_marker?: boolean): string => {
- const cur_val = hide_currency_marker ? currency_price.cur_s : currency_price.cur_m;
- return `${currency_price.cur_pos ? `${cur_val} ` : ``}${currency_price.val_i}${currency_price.dec_s}${currency_price.val_f}${currency_price.cur_pos ? `` : ` ${cur_val}`}`
-};
-
-export const sum_currency_price = (currency_price: CurrencyPrice): number => {
- return currency_price.num_i + (currency_price.num_f / 100);
-};
-
-export const parse_currency_price_fmt = (locale: string, _currency: string, amount: number): CurrencyPriceFmt | undefined => {
- const currency = parse_currency(_currency);
- const fmt = new Intl.NumberFormat(locale, {
- style: 'currency',
- currency: currency.toUpperCase(),
- minimumFractionDigits: 2,
- });
- const fmt_amt = fmt.format(amount);
- const [symbol_val_i, val_f] = fmt_amt.split('.');
- if (!symbol_val_i || !val_f) return undefined;
- return [symbol_val_i.charAt(0), currency, Number(symbol_val_i.replaceAll(`,`, ``).slice(1)), Number(val_f)];
-};
-
-export const price_fmt = (locale: string, _currency: string): Intl.NumberFormat => {
- const currency = parse_currency(_currency);
- const fmt = new Intl.NumberFormat(locale, {
- style: 'currency',
- currency: currency.toUpperCase(),
- minimumFractionDigits: 2,
- });
- return fmt;
-};
-
-
+};
+\ No newline at end of file
diff --git a/utils/src/error.ts b/utils/src/error.ts
@@ -1,8 +1,14 @@
-import type { ErrorMessage, ErrorResponse } from "./types";
+export type IErrorCatchCallback = {
+ name: string;
+ message: string;
+ stack: string;
+ url: string;
+ func: string;
+};
-export const handle_error = (e: unknown, append?: string): ErrorMessage<string> => {
- const msg = (e as Error).message ? (e as Error).message : String(e);
- const err = `${msg}${append ? ` ${append}` : ``}`;
+export type ErrorMessage<T extends string> = { err: T };
+
+export const err_msg = <T extends string>(err: T): ErrorMessage<T> => {
return { err };
};
@@ -11,14 +17,8 @@ export const throw_err = (param: string | ErrorMessage<string>): undefined => {
else throw new Error(param.err);
};
-export const err_msg = <T extends string>(err: T): ErrorMessage<T> => {
+export const handle_error = (e: unknown, append?: string): ErrorMessage<string> => {
+ const msg = (e as Error).message ? (e as Error).message : String(e);
+ const err = `${msg}${append ? ` ${append}` : ``}`;
return { err };
-};
-
-export const err_res = <T extends object>(error: T): ErrorResponse<T> => {
- return { error };
-};
-
-export const err_system = (message: string): boolean => {
- return message.split(` `).length > 1
-};
+};
+\ No newline at end of file
diff --git a/utils/src/file.ts b/utils/src/file.ts
@@ -1,40 +0,0 @@
-import type { FileBytesFormat, FilePath } from "./types";
-
-export const parse_file_name = (file_path: string): string => {
- const file_path_dirs = file_path.split(`/`);
- if (file_path_dirs.length) {
- const res = file_path_dirs[file_path_dirs.length - 1];
- if (res) return res;
- };
- return ``;
-};
-
-export const format_file_bytes = (num_bytes: number, format: FileBytesFormat): string => {
- if (num_bytes < 0) throw new Error(`Number of bytes cannot be negative`);
- let factor: number;
- switch (format) {
- case 'kb':
- factor = 1024;
- break;
- case 'mb':
- factor = 1024 ** 2;
- break;
- case 'gb':
- factor = 1024 ** 3;
- break;
- }
- const result = num_bytes / factor;
- return `${result.toFixed(2)} ${format.toUpperCase()}`;
-};
-
-export const parse_file_path = (file_path: string): FilePath | undefined => {
- const file_path_spl = file_path.split(`/`);
- const file_path_file = file_path_spl[file_path_spl.length - 1] || ``;
- const [file_name, mime_type] = file_path_file.split(`.`);
- if (!file_name || !mime_type) return undefined;
- return {
- file_path,
- file_name,
- mime_type
- };
-};
-\ No newline at end of file
diff --git a/utils/src/format.ts b/utils/src/format.ts
@@ -1,4 +0,0 @@
-export const fmt_plural_agreement = (num: number = 0, val_s: string, val_pl: string): string => {
- if (num === 1) return val_s;
- return val_pl;
-};
-\ No newline at end of file
diff --git a/utils/src/geo.ts b/utils/src/geo.ts
@@ -0,0 +1,231 @@
+import { GeolocationPointTuple, GeometryPoint, ResolveAddressInfo, ResolveGeometryPoint } from "$root";
+import { decodeBase32, encodeBase32 } from "geohashing";
+
+export type GeolocationAddress = Omit<ResolveAddressInfo, "id" | "created_at" | "updated_at">;
+
+export type GeolocationPoint = {
+ lat: number;
+ lng: number;
+};
+
+export type LocationPoint = GeolocationCoordinatesPoint & {
+ error: GeolocationCoordinatesPoint;
+}
+
+export type GeocoderReverseResult = {
+ id: number;
+ name: string;
+ admin1_id: string | number;
+ admin1_name: string;
+ country_id: string;
+ country_name: string;
+ latitude: number;
+ longitude: number;
+};
+
+export type IClientGeolocationPosition = {
+ lat: number;
+ lng: number;
+ accuracy?: number;
+ altitude?: number;
+ altitude_accuracy?: number;
+};
+
+export type GeolocationCoordinatesPoint = {
+ lat: number;
+ lng: number;
+}
+
+export type GeolocationLatitudeFmtOption = 'dms' | 'd' | 'dm';
+
+export const geohash_encode = (opts: {
+ lat: string | number;
+ lng: string | number;
+}): string => {
+ const lat = typeof opts.lat === `string` ? parseFloat(opts.lat) : opts.lat;
+ const lng = typeof opts.lng === `string` ? parseFloat(opts.lng) : opts.lng;
+ const geohash = encodeBase32(lat, lng);
+ return geohash;
+};
+
+export const geohash_decode = (geohash: string): LocationPoint => {
+ const { lat, lng, error: { lat: lat_err, lng: lng_err } } = decodeBase32(geohash);
+ return {
+ lat,
+ lng,
+ error: {
+ lat: lat_err,
+ lng: lng_err
+ }
+ };
+};
+
+export const location_geohash = (point: GeolocationCoordinatesPoint): string => {
+ const { lat, lng } = point;
+ const res = geohash_encode({ lat, lng });
+ return res;
+};
+
+export const parse_geop_point = (point: GeolocationCoordinatesPoint): GeolocationPoint => {
+ const { lat, lng } = point;
+ return { lat, lng };
+};
+
+export const parse_geol_coords = (number: number): number => {
+ return Math.round(number * 1e7) / 1e7;
+};
+
+export const parse_geolocation_address = (addr?: ResolveAddressInfo): GeolocationAddress | undefined => {
+ if (!addr) return undefined;
+ const { primary, admin, country } = addr;
+ return { primary, admin, country };
+};
+
+export const parse_geolocation_point = (point?: ResolveGeometryPoint): GeolocationPoint | undefined => {
+ if (!point) return undefined;
+ return {
+ lat: point.coordinates[1],
+ lng: point.coordinates[0],
+ };
+};
+
+export const parse_geocode_address = (geoc?: GeocoderReverseResult): GeolocationAddress | undefined => {
+ if (!geoc) return undefined;
+ const { name: primary, admin1_name: admin, country_id: country } = geoc;
+ return { primary, admin, country };
+};
+
+export const fmt_geocode_address = (geoc: GeocoderReverseResult): string => {
+ const addr = parse_geocode_address(geoc);
+ return addr ? `${addr.primary}, ${addr.admin}, ${addr.country}` : ``;
+};
+
+export const fmt_geolocation_address = (addr: ResolveAddressInfo): string => {
+ return `${addr.primary}, ${addr.admin}, ${addr.country}`;
+};
+
+export const fmt_geometry_point_coords = (point: ResolveGeometryPoint, locale: string): string => {
+ const lat = geol_lat_fmt(point.coordinates[0], `dms`, locale, 3);
+ const lng = geol_lng_fmt(point.coordinates[1], `dms`, locale, 3);
+ return `${lat}, ${lng}`;
+};
+
+export const parse_geom_point_tup = (point: GeometryPoint): GeolocationPointTuple => {
+ return [
+ point.coordinates[0],
+ point.coordinates[1],
+ ];
+};
+
+export const parse_geol_point_tup = (point: GeolocationPoint): GeolocationPointTuple => {
+ return [
+ point.lng,
+ point.lat
+ ];
+};
+
+export const parse_tup_geop_point = (map_center: GeolocationPointTuple): GeolocationPoint => {
+ return {
+ lat: map_center[1],
+ lng: map_center[0]
+ }
+};
+
+export const geol_lat_fmt = (lat: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat(locale, options);
+ const fmt_sec = new Intl.NumberFormat(locale, options);
+ if (fmt_opt === 'dms') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = Math.floor((Math.abs(lat) - deg) * 60);
+ const sec = ((Math.abs(lat) - deg - min / 60) * 3600);
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${fmt_sec.format(sec)}" ${lat >= 0 ? 'N' : 'S'}`;
+ } else if (fmt_opt === 'dm') {
+ const deg = Math.floor(Math.abs(lat));
+ const min = (Math.abs(lat) - deg) * 60;
+ return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${lat >= 0 ? 'N' : 'S'}`;
+ } else {
+ return `${lat.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lat >= 0 ? 'N' : 'S'}`;
+ }
+};
+
+export const geol_lng_fmt = (lng: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
+ const options: Intl.NumberFormatOptions = {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ };
+ const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
+ const fmt_min = new Intl.NumberFormat(locale, options);
+ const fmt_sec = new Intl.NumberFormat(locale, options);
+ if (fmt_opt === 'dms') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = Math.floor((Math.abs(lng) - degrees) * 60);
+ const seconds = ((Math.abs(lng) - degrees - minutes / 60) * 3600);
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${fmt_sec.format(seconds)}" ${lng >= 0 ? 'E' : 'W'}`;
+ } else if (fmt_opt === 'dm') {
+ const degrees = Math.floor(Math.abs(lng));
+ const minutes = (Math.abs(lng) - degrees) * 60;
+ return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${lng >= 0 ? 'E' : 'W'}`;
+ } else {
+ return `${lng.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lng >= 0 ? 'E' : 'W'}`;
+ }
+};
+
+export const compute_bounding_box = (lat: number, lng: number, distance_km: number): { nw: GeolocationPoint; ne: GeolocationPoint; se: GeolocationPoint; sw: GeolocationPoint; } => {
+ const deg_to_rad = (deg: number) => deg * (Math.PI / 180);
+ const rad_to_deg = (rad: number) => rad * (180 / Math.PI);
+
+ const R = 6371;
+
+ function destination_point(lat: number, lng: number, bearing: number, distance_km: number): GeolocationPoint {
+ const lat1 = deg_to_rad(lat);
+ const lon1 = deg_to_rad(lng);
+ const angular_distance = distance_km / R;
+
+ const lat2 = Math.asin(Math.sin(lat1) * Math.cos(angular_distance) + Math.cos(lat1) * Math.sin(angular_distance) * Math.cos(deg_to_rad(bearing)));
+ const lon2 = lon1 + Math.atan2(Math.sin(deg_to_rad(bearing)) * Math.sin(angular_distance) * Math.cos(lat1), Math.cos(angular_distance) - Math.sin(lat1) * Math.sin(lat2));
+
+ return { lat: rad_to_deg(lat2), lng: rad_to_deg(lon2) };
+ }
+
+ const bearings = [0, 90, 180, 270];
+
+ const coords = bearings.map(bearing => destination_point(lat, lng, bearing, distance_km / Math.sqrt(2)));
+
+ return {
+ nw: coords[0],
+ ne: coords[1],
+ se: coords[2],
+ sw: coords[3]
+ };
+};
+
+export const geo_bounds_calc = (lat: number, lng: number, distance_km: number): { north: GeolocationPoint; south: GeolocationPoint; east: GeolocationPoint; west: GeolocationPoint; } => {
+ const deg_to_rad = (deg: number) => deg * (Math.PI / 180);
+ const rad_to_deg = (rad: number) => rad * (180 / Math.PI);
+
+ const R = 6371;
+
+ function destination_point(lat: number, lng: number, bearing: number, distance_km: number): GeolocationPoint {
+ const lat1 = deg_to_rad(lat);
+ const lon1 = deg_to_rad(lng);
+ const angular_distance = distance_km / R;
+
+ const lat2 = Math.asin(Math.sin(lat1) * Math.cos(angular_distance) + Math.cos(lat1) * Math.sin(angular_distance) * Math.cos(deg_to_rad(bearing)));
+ const lon2 = lon1 + Math.atan2(Math.sin(deg_to_rad(bearing)) * Math.sin(angular_distance) * Math.cos(lat1), Math.cos(angular_distance) - Math.sin(lat1) * Math.sin(lat2));
+
+ return { lat: rad_to_deg(lat2), lng: rad_to_deg(lon2) };
+ }
+
+ return {
+ north: destination_point(lat, lng, 0, distance_km),
+ south: destination_point(lat, lng, 180, distance_km),
+ east: destination_point(lat, lng, 90, distance_km),
+ west: destination_point(lat, lng, 270, distance_km)
+ };
+};
+
diff --git a/utils/src/geolocation.ts b/utils/src/geolocation.ts
@@ -1,87 +0,0 @@
-//import * as ngeohash from "ngeohash";
-import { decodeBase32, encodeBase32 } from "geohashing";
-import type { LocationPoint } from "./types";
-
-export type GeolocationCoordinatesPoint = {
- lat: number;
- lng: number;
-}
-
-export type GeolocationLatitudeFmtOption = 'dms' | 'd' | 'dm';
-
-
-export const geohash_encode = (opts: {
- lat: string | number;
- lng: string | number;
-}): string => {
- const lat = typeof opts.lat === `string` ? parseFloat(opts.lat) : opts.lat;
- const lng = typeof opts.lng === `string` ? parseFloat(opts.lng) : opts.lng;
- const geohash = encodeBase32(lat, lng);
- return geohash;
-};
-
-export const geohash_decode = (geohash: string): LocationPoint => {
- const { lat, lng, error: { lat: lat_err, lng: lng_err } } = decodeBase32(geohash);
- return {
- lat,
- lng,
- error: {
- lat: lat_err,
- lng: lng_err
- }
- };
-};
-
-export const location_geohash = (point: GeolocationCoordinatesPoint): string => {
- const { lat, lng } = point;
- const res = geohash_encode({ lat, lng });
- return res;
-};
-
-export const geol_lat_fmt = (lat: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
- const options: Intl.NumberFormatOptions = {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- };
- const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
- const fmt_min = new Intl.NumberFormat(locale, options);
- const fmt_sec = new Intl.NumberFormat(locale, options);
- if (fmt_opt === 'dms') {
- const deg = Math.floor(Math.abs(lat));
- const min = Math.floor((Math.abs(lat) - deg) * 60);
- const sec = ((Math.abs(lat) - deg - min / 60) * 3600);
- return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${fmt_sec.format(sec)}" ${lat >= 0 ? 'N' : 'S'}`;
- } else if (fmt_opt === 'dm') {
- const deg = Math.floor(Math.abs(lat));
- const min = (Math.abs(lat) - deg) * 60;
- return `${fmt_deg.format(deg)}° ${fmt_min.format(min)}' ${lat >= 0 ? 'N' : 'S'}`;
- } else {
- return `${lat.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lat >= 0 ? 'N' : 'S'}`;
- }
-};
-
-export const geol_lng_fmt = (lng: number, fmt_opt: GeolocationLatitudeFmtOption, locale: string, precision: number = 5): string => {
- const options: Intl.NumberFormatOptions = {
- minimumFractionDigits: 2,
- maximumFractionDigits: 2,
- };
- const fmt_deg = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
- const fmt_min = new Intl.NumberFormat(locale, options);
- const fmt_sec = new Intl.NumberFormat(locale, options);
- if (fmt_opt === 'dms') {
- const degrees = Math.floor(Math.abs(lng));
- const minutes = Math.floor((Math.abs(lng) - degrees) * 60);
- const seconds = ((Math.abs(lng) - degrees - minutes / 60) * 3600);
- return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${fmt_sec.format(seconds)}" ${lng >= 0 ? 'E' : 'W'}`;
- } else if (fmt_opt === 'dm') {
- const degrees = Math.floor(Math.abs(lng));
- const minutes = (Math.abs(lng) - degrees) * 60;
- return `${fmt_deg.format(degrees)}° ${fmt_min.format(minutes)}' ${lng >= 0 ? 'E' : 'W'}`;
- } else {
- return `${lng.toLocaleString(locale, { maximumFractionDigits: precision })}° ${lng >= 0 ? 'E' : 'W'}`;
- }
-};
-
-export const parse_geol_coords = (number: number): number => {
- return Math.round(number * 1e7) / 1e7;
-};
-\ No newline at end of file
diff --git a/utils/src/guard.ts b/utils/src/guard.ts
@@ -1,32 +0,0 @@
-import { type NotifyMessage } from ".";
-
-export const is_err_response = (response: any): response is { err: string } => {
- return "err" in response && typeof response.err === "string";
-}
-
-export const is_pass_response = (response: any): response is { pass: true } => {
- return "pass" in response && response.pass === true;
-}
-
-export const is_result_response = (response: any): response is { result: string } => {
- return "result" in response && typeof response.result === "string";
-}
-
-export const is_results_response = (response: any): response is { results: string[] } => {
- return "results" in response && Array.isArray(response.results);
-}
-
-export const is_error_response = (response: any): response is { error: string } => {
- return "error" in response && typeof response.result === "string";
-}
-
-export const is_message_response = (response: any): response is NotifyMessage => {
- return (
- typeof response === "object" &&
- response !== null &&
- "message" in response &&
- typeof response.message === "string" &&
- (response.ok === undefined || typeof response.ok === "string") &&
- (response.cancel === undefined || typeof response.cancel === "string")
- );
-};
-\ No newline at end of file
diff --git a/utils/src/hash.ts b/utils/src/hash.ts
@@ -1,28 +0,0 @@
-import { hmac } from '@noble/hashes/hmac';
-import { sha256 } from '@noble/hashes/sha2';
-
-export const hash_sha256 = (msg: string): Uint8Array => {
- const result = sha256(msg);
- return result;
-};
-
-export const hash_sha256_bin = (msg_data: number[]): Uint8Array => {
- const result = sha256
- .create()
- .update(Uint8Array.from(msg_data))
- .digest();
- return result;
-};
-
-export const hash_hmac = (key: string, msg: string): Uint8Array => {
- const result = hmac(sha256, key, msg);
- return result;
-};
-
-export const hash_hmac_bin = (key_data: number[], msg_data: number[]): Uint8Array => {
- const result = hmac
- .create(sha256, Uint8Array.from(key_data))
- .update(Uint8Array.from(msg_data))
- .digest();
- return result;
-};
diff --git a/utils/src/http.ts b/utils/src/http.ts
@@ -1,4 +1,8 @@
-import { type FieldRecord, is_error_response, is_message_response, type NotifyMessage } from ".";
+import { is_error_response, is_message_response, type ErrorMessage, type FieldRecord, type NotifyMessage } from "$root";
+
+export type IClientHttp = {
+ fetch(opts: IHttpOpts): Promise<IHttpResponse | ErrorMessage<string>>;
+};
export type IHttpImageResponse = {
status: number;
@@ -85,20 +89,21 @@ export const http_parse_response = async (res: Response): Promise<Promise<IHttpR
export const http_fetch = async (opts: IHttpOpts): Promise<IHttpResponse> => {
const { url, options } = http_fetch_opts(opts);
const response = await fetch(url, options);
- let response_data: any = null;
+ let data: any = null;
try {
const res_json = await response.json();
- response_data = typeof res_json === `string` ? JSON.parse(res_json) : res_json;
+ data = typeof res_json === `string` ? JSON.parse(res_json) : res_json;
} catch { }
- if (!response_data) {
+ if (!data) {
try {
const res_text = await response.text();
- response_data = res_text;
+ data = res_text;
} catch { }
}
return {
status: response.status,
url: response.url,
+ data,
headers: lib_http_parse_headers(response.headers)
};
};
diff --git a/utils/src/image.ts b/utils/src/image.ts
@@ -0,0 +1,7 @@
+export type MediaImageUploadResult = {
+ base_url: string;
+ file_hash: string;
+ file_ext: string;
+};
+
+export const fmt_media_image_upload_result_url = (res: MediaImageUploadResult): string => `${res.base_url}/${res.file_hash}.${res.file_ext}`;
diff --git a/utils/src/index.ts b/utils/src/index.ts
@@ -1,34 +1,49 @@
-export * from "./ascii"
-export * from "./client/database"
-export * from "./client/datastore"
-export * from "./client/geolocation"
-export * from "./client/gui"
-export * from "./currency"
-export * from "./error"
-export * from "./file"
-export * from "./format"
-export * from "./geolocation"
-export * from "./guard"
-export * from "./hash"
-export * from "./http"
-export * from "./list"
-export * from "./math"
-export * from "./model"
-export * from "./nostr/event"
-export * from "./nostr/key"
-export * from "./nostr/ndk"
-export * from "./nostr/types"
-export * from "./nostr-event-util/lib"
-export * from "./nostr-event-util/types"
-export * from "./nostr-key-util/lib"
-export * from "./nostr-key-util/types"
-export * from "./nostr-relay-util/lib"
-export * from "./nostr-relay-util/types"
-export * from "./object"
-export * from "./regex"
-export * from "./time"
-export * from "./trade"
-export * from "./types"
-export * from "./units"
-export * from "./uuid"
-export * from "./window"
+export * from "./*regex.js"
+export * from "./*validation.js"
+export * from "./app/document.js"
+export * from "./app/i18n.js"
+export * from "./app/lib.js"
+export * from "./app/styles.js"
+export * from "./app/types/app.js"
+export * from "./app/types/basis.js"
+export * from "./app/types/component.js"
+export * from "./app/types/element.js"
+export * from "./app/types/geometry.js"
+export * from "./app/types/glyph.js"
+export * from "./app/types/interface.js"
+export * from "./app/types/resolve.js"
+export * from "./app/types/view.js"
+export * from "./app/util/resolve-enum.js"
+export * from "./app/util/resolve.js"
+export * from "./app/util/search.js"
+export * from "./app/util.js"
+export * from "./app/validation/view.js"
+export * from "./client/database.js"
+export * from "./client/datastore.js"
+export * from "./client/geo.js"
+export * from "./client/gui.js"
+export * from "./config.js"
+export * from "./currency.js"
+export * from "./error.js"
+export * from "./geo.js"
+export * from "./http.js"
+export * from "./image.js"
+export * from "./lib.js"
+export * from "./list.js"
+export * from "./model.js"
+export * from "./nostr/nostr/event.js"
+export * from "./nostr/nostr/key.js"
+export * from "./nostr/nostr/ndk.js"
+export * from "./nostr/nostr/types.js"
+export * from "./nostr/nostr-event-util/lib.js"
+export * from "./nostr/nostr-event-util/types.js"
+export * from "./nostr/nostr-key-util/lib.js"
+export * from "./nostr/nostr-key-util/types.js"
+export * from "./number.js"
+export * from "./object.js"
+export * from "./response.js"
+export * from "./time.js"
+export * from "./trade.js"
+export * from "./types.js"
+export * from "./unit.js"
+export * from "./uuid.js"
diff --git a/utils/src/lib.ts b/utils/src/lib.ts
@@ -0,0 +1,49 @@
+import { CallbackPromise } from "$root";
+
+export const ascii = {
+ bullet: '•',
+ dash: `—`,
+ up: `↑`,
+ down: `↓`
+}
+
+export const root_symbol = "»--`--,---";
+
+export const sleep = async (ms: number): Promise<void> => {
+ await new Promise((resolve) => setTimeout(resolve, ms));
+};
+
+export const obj_keys_maxnum = (obj: any): number => Math.max(
+ ...Object.keys(obj).map(Number),
+);
+
+export const debounce_callback = (func: Function, delay: number) => {
+ let timer: ReturnType<typeof setTimeout>;
+ return function (this: any, ...args: any) {
+ clearTimeout(timer);
+ timer = setTimeout(() => func.apply(this, args), delay);
+ };
+};
+
+export const str_trunc = (val: string, max_length: number = 28): string => {
+ if (val.length <= max_length) return val;
+ return `${val.slice(0, max_length - 3)}...`;
+};
+
+export const exe_iter = async (callback: CallbackPromise, num: number = 1, delay: number = 400): Promise<void> => {
+ try {
+ const iter_fn = (count: number) => {
+ if (count > 0) {
+ callback();
+ if (count > 1) {
+ setTimeout(() => {
+ iter_fn(count - 1);
+ }, delay);
+ }
+ }
+ };
+ iter_fn(num);
+ } catch (e) {
+ console.log(`(error) exe_iter `, e);
+ }
+};
+\ No newline at end of file
diff --git a/utils/src/list.ts b/utils/src/list.ts
@@ -1,21 +1 @@
-export const list_remove_index = <T>(array: T[], index: number): T[] => {
- if (index < 0 || index >= array.length) throw new Error(`Index out of bounds`);
- return [...array.slice(0, index), ...array.slice(index + 1)];
-};
-
-export const list_move_index = <T>(array: T[], index_start: number, index_end: number): T[] => {
- if (index_start < 0 || index_start >= array.length || index_end < 0 || index_end >= array.length) throw new Error(`Index out of bounds`);
- const item = array[index_start];
- const arr_new = array.filter((_, index) => index !== index_start);
- const adjusted_final_index = index_end > index_start ? index_end - 1 : index_end;
- const arr_res: T[] = arr_new.slice(0, adjusted_final_index);
- if (item) arr_res.push(item)
- arr_res.push(...arr_new.slice(adjusted_final_index))
- return arr_res;
-};
-
-export const list_assign = (list_curr: string[], list_new: string[]): string[] => {
- return Array.from(
- new Set([...list_curr, ...list_new]),
- ).filter((i) => !!i);
-};
+export const list_defined = (i: any): boolean => typeof i !== `undefined`;
+\ No newline at end of file
diff --git a/utils/src/math.ts b/utils/src/math.ts
@@ -1,7 +0,0 @@
-export const round_to_5 = (num: number): number => {
- return Math.round(num / 5) * 5;
-};
-
-export const num_str = (num: number): string => num.toString();
-
-export const num_min = (num: number = 0, num_min: number = 0): number => Math.max(num, num_min);
-\ No newline at end of file
diff --git a/utils/src/nostr-event-util/lib.ts b/utils/src/nostr-event-util/lib.ts
@@ -1,98 +0,0 @@
-import { type NDKEvent } from "@nostr-dev-kit/ndk";
-import ngeotags, { type GeoTags as NostrGeotagsGeotags, type InputData as NostrGeotagsInputData } from "nostr-geotags";
-import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
-import type { INostrEventEventSign } from "..";
-import { lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode } from '../nostr/event';
-import type { INostrEventUtil, INostrEventUtilFormatTagsBasisNip99, INostrEventUtilNeventEncode, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagQuantity } from "./types";
-
-export class NostrEventUtil implements INostrEventUtil {
- public first_tag_value = (event: NDKEvent, tag_name: string): string => {
- const tag = event.getMatchingTags(tag_name)[0];
- return tag ? tag[1] : "";
- }
-
- private fmt_tag_price = (opts: NostrEventTagPrice): string[] => {
- const tag = [`price`, opts.amt, opts.currency, opts.qty_amt, opts.qty_unit];
- return tag;
- };
-
-
- private fmt_tag_quantity = (opts: NostrEventTagQuantity): string[] => {
- const tag = [`quantity`, opts.amt, opts.unit];
- if (opts.label) tag.push(opts.label);
- return tag;
- };
-
- private fmt_tag_location = (opts: NostrEventTagLocation): string[] => {
- const tag = [`location`];
- if (opts.city) tag.push(opts.city);
- if (opts.region_code && !isNaN(parseInt(opts.region_code))) tag.push(opts.region_code);
- else if (opts.region) tag.push(opts.region); //@todo
- if (opts.country_code) tag.push(opts.country_code);
- return tag;
- };
-
- private fmt_tag_image = (opts: NostrEventTagMediaUpload): string[] => {
- const tag = [`image`, opts.url];
- if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`)
- return tag;
- };
-
- private fmt_tag_client = (opts: NostrEventTagClient, d_tag?: string): string[] => {
- const tag = [`client`, opts.name];
- if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`);
- tag.push(opts.relay);
- return tag;
- };
-
- private fmt_tag_geotags = (opts: NostrEventTagLocation): NostrGeotagsGeotags[] => {
- const data: NostrGeotagsInputData = {
- lat: opts.lat,
- lon: opts.lng,
- city: opts.city,
- regionName: opts.region,
- countryName: opts.country,
- countryCode: opts.country_code
- };
- return ngeotags(data, {
- geohash: true,
- gps: true,
- city: true,
- iso31662: true,
- });
- };
-
- public fmt_tags_basis_nip99 = (opts: INostrEventUtilFormatTagsBasisNip99): string[][] => {
- const { d_tag, listing, quantity, price, location } = opts;
- const tags: string[][] = [[`d`, d_tag]];
- if (opts.client) tags.push(this.fmt_tag_client(opts.client, opts.d_tag));
- for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]);
- tags.push(this.fmt_tag_quantity(quantity));
- tags.push(this.fmt_tag_price(price));
- tags.push(this.fmt_tag_location(location));
- if (opts.images) for (const image of opts.images) tags.push(this.fmt_tag_image(image));
- tags.push(...this.fmt_tag_geotags(location));
- return tags;
- };
-
- public nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
- return lib_nostr_event_sign(opts);
- };
-
- public nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
- return lib_nostr_event_sign_attest(secret_key);
- };
-
- public nostr_event_verify = (event: NostrToolsEvent): boolean => {
- return lib_nostr_event_verify(event);
- };
-
- public nostr_event_verify_serialized = (event_serialized: string): boolean => {
- const result = lib_nostr_event_verify_serialized(event_serialized);
- return !!result;
- };
-
- public nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
- return lib_nostr_nevent_encode(opts);
- };
-}
-\ No newline at end of file
diff --git a/utils/src/nostr-event-util/types.ts b/utils/src/nostr-event-util/types.ts
@@ -1,80 +0,0 @@
-import { NDKEvent } from "@nostr-dev-kit/ndk";
-import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
-import type { INostrEventEventSign } from "..";
-
-export type INostrEventUtilFormatTagsBasisNip99 = {
- d_tag: string;
- listing: NostrEventTagListing;
- quantity: NostrEventTagQuantity;
- price: NostrEventTagPrice;
- location: NostrEventTagLocation;
- images?: NostrEventTagMediaUpload[];
- client?: NostrEventTagClient;
-};
-
-export type INostrEventUtilNeventEncode = {
- id: string;
- relays: string[];
- author: string;
- kind: number;
-};
-
-export type INostrEventUtil = {
- first_tag_value(event: NDKEvent, tag_name: string): string;
- fmt_tags_basis_nip99: (opts: INostrEventUtilFormatTagsBasisNip99) => string[][];
- nostr_event_sign: (opts: INostrEventEventSign) => NostrToolsEvent;
- nostr_event_sign_attest: (secret_key: string) => NostrToolsEvent;
- nostr_event_verify_serialized: (event_serialized: string) => boolean;
- nostr_event_verify: (event: NostrToolsEvent) => boolean;
- nevent_encode: (opts: INostrEventUtilNeventEncode) => string;
-};
-
-export type NostrEventTagListing = {
- key: string;
- title: string;
- category: string;
- summary?: string;
- process?: string;
- lot?: string;
- location?: string;
- profile?: string;
- year?: string;
-};
-
-export type NostrEventTagPrice = {
- amt: string;
- currency: string;
- qty_amt: string;
- qty_unit: string;
-};
-
-export type NostrEventTagQuantity = {
- amt: string;
- unit: string;
- label?: string;
-};
-
-export type NostrEventTagLocation = {
- city?: string;
- region?: string;
- region_code?: string;
- country?: string;
- country_code?: string;
- lat: number;
- lng: number;
- geohash: string;
-};
-
-export type NostrEventTagMediaUpload = {
- url: string;
- size?: {
- w: number;
- h: number;
- };
-};
-
-export type NostrEventTagClient = {
- name: string;
- pubkey: string;
- relay: string;
-};
diff --git a/utils/src/nostr-key-util/lib.ts b/utils/src/nostr-key-util/lib.ts
@@ -1,122 +0,0 @@
-import { getPublicKey, nip19 } from 'nostr-tools';
-import { lib_nostr_get_key_bytes, lib_nostr_key_generate, lib_nostr_nsec_decode, lib_nostr_nsec_encode } from '../nostr/key';
-import type { INostrKeyUtil } from './types';
-
-export class NostrKeyUtil implements INostrKeyUtil {
- /**
- *
- * @returns nostr secret key hex
- */
- public generate_key(): string {
- return lib_nostr_key_generate();
- };
-
-
- /**
- *
- * @returns nostr public key hex from secret key
- */
- public public_key(secret_key_hex: string | undefined): string {
- try {
- if (!secret_key_hex) return ``;
- const bytes = lib_nostr_get_key_bytes(secret_key_hex);
- const hex = getPublicKey(bytes)
- return hex;
- } catch (e) {
- return ``
- }
- }
-
- /**
- *
- * @returns nostr secret key to public key hex
- */
- public publickey_decode(secret_key_hex?: string): string | undefined {
- try {
- if (secret_key_hex) {
- return getPublicKey(lib_nostr_get_key_bytes(secret_key_hex))
- }
- return undefined;
- } catch (e) {
- return undefined;
- }
- }
-
- /**
- *
- * @returns nostr public key npub
- */
- public npub(public_key_hex: string | undefined, fallback_to_hex?: boolean): string {
- if (!public_key_hex) return ``;
- const npub = nip19.npubEncode(public_key_hex);
- return npub ? npub : fallback_to_hex ? public_key_hex : ``;
- }
-
- /**
- *
- * @returns public key hex from npub
- */
- public npub_decode(npub: string): string {
- const decode = nip19.decode(npub);
- console.log(`decode `, decode)
- if (decode && decode.type === `npub` && decode.data) return decode.data
- return ``;
- }
-
- /**
- *
- * @returns nostr secret key nsec
- */
- public nsec(secret_key_hex?: string): string | undefined {
- return lib_nostr_nsec_encode(secret_key_hex);
- }
-
- /**
- *
- * @returns nostr secret key hex from nsec
- */
- public nsec_decode(nsec: string): string | undefined {
- return lib_nostr_nsec_decode(nsec);
- }
-
- /**
- *
- * @returns
- */
- public nevent(event_pointer: nip19.EventPointer, relays: string[]): string {
- return nip19.neventEncode(event_pointer)
- }
-
- /**
- *
- * @returns nostr public key nprofile
- */
- public nprofile(public_key_hex: string, relays: string[]): string {
- if (!public_key_hex || !relays.length) return ``;
- return nip19.nprofileEncode({ pubkey: public_key_hex, relays })
- }
-
- /**
- *
- * @returns nostr public key nprofile
- */
- public nprofile_decode(nprofile: string): [string, string[]] | undefined {
- if (!nprofile) return undefined;
- const decode = nip19.decode(nprofile);
- if (decode && decode.type === `nprofile` && decode.data && decode.data.pubkey && decode.data.relays) return [decode.data.pubkey, decode.data.relays]
- return undefined;
- }
-
- /**
- *
- * @returns
- */
- public secretkey_to_publickey(nsec_or_hex: string): string | undefined {
- if (nsec_or_hex.startsWith(`nsec1`)) {
- return this.nsec_decode(nsec_or_hex);
- } else if (nsec_or_hex.length === 64) {
- return this.publickey_decode(nsec_or_hex)
- }
- return undefined;
- }
-};
diff --git a/utils/src/nostr-relay-util/lib.ts b/utils/src/nostr-relay-util/lib.ts
@@ -1,45 +0,0 @@
-import { type NDKEvent } from "@nostr-dev-kit/ndk";
-import type { INostrRealyUtil, NostrRelayInformationDocument, NostrRelayInformationDocumentFormFields } from "./types";
-
-export class NostrRelayUtil implements INostrRealyUtil {
- public first_tag_value(event: NDKEvent, tag_name: string): string {
- const tag = event.getMatchingTags(tag_name)[0];
- return tag ? tag[1] : "";
- }
-
- private parse_nostr_relay_information_document = (data: any): NostrRelayInformationDocument | undefined => {
- const obj = JSON.parse(data);
- return {
- id: typeof obj.id === 'string' ? obj.id : undefined,
- name: typeof obj.name === 'string' ? obj.name : undefined,
- description: typeof obj.description === 'string' ? obj.description : undefined,
- pubkey: typeof obj.pubkey === 'string' ? obj.pubkey : undefined,
- contact: typeof obj.contact === 'string' ? obj.contact : undefined,
- supported_nips: Array.isArray(obj.supported_nips) && obj.supported_nips.every((nip: any) => typeof nip === 'number')
- ? obj.supported_nips
- : undefined,
- software: typeof obj.software === 'string' ? obj.software : undefined,
- version: typeof obj.version === 'string' ? obj.version : undefined,
- limitation_payment_required: obj.limitation && typeof obj.limitation === 'object' && typeof obj.limitation.payment_required === 'string' ? obj.limitation.payment_required : undefined,
- limitation_restricted_writes: obj.limitation && typeof obj.limitation === 'object' && typeof obj.limitation.restricted_writes === 'boolean' ? obj.limitation.restricted_writes : undefined,
- };
- };
-
- public parse_nostr_relay_information_document_fields = (data: any): NostrRelayInformationDocumentFormFields | undefined => {
- const info_doc = this.parse_nostr_relay_information_document(data);
- if (!info_doc) return;
- const result: Partial<NostrRelayInformationDocumentFormFields> = {};
- Object.entries(info_doc).forEach(([key, value]) => {
- if (typeof value === 'boolean') {
- result[key as keyof NostrRelayInformationDocument] = value ? '1' : '0';
- } else if (Array.isArray(value)) {
- result[key as keyof NostrRelayInformationDocument] = value.join(', ');
- } else if (value === null || value === undefined) {
- result[key as keyof NostrRelayInformationDocument] = '';
- } else {
- result[key as keyof NostrRelayInformationDocument] = String(value);
- }
- });
- return result;
- };
-}
-\ No newline at end of file
diff --git a/utils/src/nostr-relay-util/types.ts b/utils/src/nostr-relay-util/types.ts
@@ -1,21 +0,0 @@
-import { NDKEvent } from "@nostr-dev-kit/ndk";
-
-export type INostrRealyUtil = {
- first_tag_value(event: NDKEvent, tag_name: string): string;
- parse_nostr_relay_information_document_fields: (data: any) => NostrRelayInformationDocumentFormFields | undefined;
-};
-
-export type NostrRelayInformationDocument = {
- id?: string;
- name?: string;
- description?: string;
- pubkey?: string;
- contact?: string;
- supported_nips?: number[];
- software?: string;
- version?: string;
- limitation_payment_required?: string;
- limitation_restricted_writes?: boolean;
-}
-
-export type NostrRelayInformationDocumentFormFields = { [K in keyof NostrRelayInformationDocument]: string; };
diff --git a/utils/src/nostr/event.ts b/utils/src/nostr/event.ts
@@ -1,45 +0,0 @@
-import { schnorr } from "@noble/curves/secp256k1";
-import { hexToBytes } from "@noble/hashes/utils";
-import { finalizeEvent, getEventHash, nip19, type NostrEvent as NostrToolsEvent } from "nostr-tools";
-import { type INostrEventEventSign, type INostrEventUtilNeventEncode, uuidv4 } from "..";
-
-export const lib_nostr_event_verify = (event: NostrToolsEvent): boolean => {
- const hash = getEventHash(event);
- if (hash !== event.id) return false
- const valid = schnorr.verify(event.sig, hash, event.pubkey);
- return valid;
-};
-
-export const lib_nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
- return finalizeEvent(opts.event, hexToBytes(opts.secret_key))
-};
-
-export const lib_nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
- return lib_nostr_event_sign({
- secret_key,
- event: {
- kind: 1,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: uuidv4(),
- },
- });
-};
-
-
-export const lib_nostr_event_verify_serialized = async (event_serialized: string): Promise<{ public_key: string } | undefined> => {
- try {
- const event = JSON.parse(event_serialized);
- const hash = getEventHash(event);
- if (hash !== event.id) return undefined;
- const valid = schnorr.verify(event.sig, hash, event.pubkey);
- if (valid) return { public_key: String(event.pubkey) };
- return undefined;
- } catch {
- return undefined;
- }
-};
-
-export const lib_nostr_nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
- return nip19.neventEncode(opts);
-};
-\ No newline at end of file
diff --git a/utils/src/nostr/ndk.ts b/utils/src/nostr/ndk.ts
@@ -1,75 +0,0 @@
-import NDK, { NDKEvent, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
-import { time_now_ms } from '..';
-import type { NostrMetadataTmp } from './types';
-
-export const ndk_init = async (opts: {
- $ndk: NDK;
- secret_key: string;
-}): Promise<NDKUser | undefined> => {
- try {
- const { $ndk: ndk, secret_key } = opts;
- const signer = new NDKPrivateKeySigner(secret_key);
- ndk.signer = signer;
-
- const user = await signer.user();
- if (user) {
- user.ndk = ndk;
- return user;
- }
- } catch (e) {
- console.log(`(error) ndk_init `, e);
- };
-};
-
-export const ndk_event_metadata = async (opts: {
- $ndk: NDK;
- $ndk_user: NDKUser;
- metadata: NostrMetadataTmp
-}): Promise<NDKEvent | undefined> => {
- try {
- const { $ndk, $ndk_user } = opts;
- const ev = await ndk_event({
- $ndk,
- $ndk_user,
- basis: {
- kind: 0,
- content: JSON.stringify(opts.metadata),
- },
- });
- return ev;
- } catch (e) {
- console.log(`(error) ndk_event_metadata `, e);
- }
-};
-
-export const ndk_event = async (opts: {
- $ndk: NDK;
- $ndk_user: NDKUser;
- basis: {
- kind: number;
- content: string;
- tags?: string[][];
- }
-}): Promise<NDKEvent | undefined> => {
- try {
- const { $ndk: ndk, $ndk_user: ndk_user, basis } = opts;
- const time_now = time_now_ms();
-
- const tags: string[][] = [
- ['published_at', time_now.toString()],
- ];
-
- if (basis.tags && basis.tags?.length > 0) for (const tag of basis.tags) tags.push(tag);
-
- const event: NDKEvent = new NDKEvent(ndk, {
- kind: basis.kind,
- pubkey: ndk_user.pubkey,
- content: basis.content,
- created_at: time_now,
- tags
- });
- return event;
- } catch (e) {
- console.log(`(error) ndk_event `, e);
- };
-};
diff --git a/utils/src/nostr/nostr-event-util/lib.ts b/utils/src/nostr/nostr-event-util/lib.ts
@@ -0,0 +1,96 @@
+import { lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode, type INostrEventEventSign, type INostrEventUtil, type INostrEventUtilFormatTagsBasisNip99, type INostrEventUtilNeventEncode, type NostrEventTagClient, type NostrEventTagLocation, type NostrEventTagMediaUpload, type NostrEventTagPrice, type NostrEventTagQuantity } from "$root";
+import { type NDKEvent } from "@nostr-dev-kit/ndk";
+import ngeotags, { type GeoTags as NostrGeotagsGeotags, type InputData as NostrGeotagsInputData } from "nostr-geotags";
+import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
+
+export class NostrEventUtil implements INostrEventUtil {
+ public first_tag_value = (event: NDKEvent, tag_name: string): string => {
+ const tag = event.getMatchingTags(tag_name)[0];
+ return tag ? tag[1] : "";
+ }
+
+ private fmt_tag_price = (opts: NostrEventTagPrice): string[] => {
+ const tag = [`price`, opts.amt, opts.currency, opts.qty_amt, opts.qty_unit];
+ return tag;
+ };
+
+
+ private fmt_tag_quantity = (opts: NostrEventTagQuantity): string[] => {
+ const tag = [`quantity`, opts.amt, opts.unit];
+ if (opts.label) tag.push(opts.label);
+ return tag;
+ };
+
+ private fmt_tag_location = (opts: NostrEventTagLocation): string[] => {
+ const tag = [`location`];
+ if (opts.city) tag.push(opts.city);
+ if (opts.region_code && !isNaN(parseInt(opts.region_code))) tag.push(opts.region_code);
+ else if (opts.region) tag.push(opts.region); //@todo
+ if (opts.country_code) tag.push(opts.country_code);
+ return tag;
+ };
+
+ private fmt_tag_image = (opts: NostrEventTagMediaUpload): string[] => {
+ const tag = [`image`, opts.url];
+ if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`)
+ return tag;
+ };
+
+ private fmt_tag_client = (opts: NostrEventTagClient, d_tag?: string): string[] => {
+ const tag = [`client`, opts.name];
+ if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`);
+ tag.push(opts.relay);
+ return tag;
+ };
+
+ private fmt_tag_geotags = (opts: NostrEventTagLocation): NostrGeotagsGeotags[] => {
+ const data: NostrGeotagsInputData = {
+ lat: opts.lat,
+ lon: opts.lng,
+ city: opts.city,
+ regionName: opts.region,
+ countryName: opts.country,
+ countryCode: opts.country_code
+ };
+ return ngeotags(data, {
+ geohash: true,
+ gps: true,
+ city: true,
+ iso31662: true,
+ });
+ };
+
+ public fmt_tags_basis_nip99 = (opts: INostrEventUtilFormatTagsBasisNip99): string[][] => {
+ const { d_tag, listing, quantity, price, location } = opts;
+ const tags: string[][] = [[`d`, d_tag]];
+ if (opts.client) tags.push(this.fmt_tag_client(opts.client, opts.d_tag));
+ for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]);
+ tags.push(this.fmt_tag_quantity(quantity));
+ tags.push(this.fmt_tag_price(price));
+ tags.push(this.fmt_tag_location(location));
+ if (opts.images) for (const image of opts.images) tags.push(this.fmt_tag_image(image));
+ tags.push(...this.fmt_tag_geotags(location));
+ return tags;
+ };
+
+ public nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
+ return lib_nostr_event_sign(opts);
+ };
+
+ public nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
+ return lib_nostr_event_sign_attest(secret_key);
+ };
+
+ public nostr_event_verify = (event: NostrToolsEvent): boolean => {
+ return lib_nostr_event_verify(event);
+ };
+
+ public nostr_event_verify_serialized = (event_serialized: string): boolean => {
+ const result = lib_nostr_event_verify_serialized(event_serialized);
+ return !!result;
+ };
+
+ public nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
+ return lib_nostr_nevent_encode(opts);
+ };
+}
+\ No newline at end of file
diff --git a/utils/src/nostr/nostr-event-util/types.ts b/utils/src/nostr/nostr-event-util/types.ts
@@ -0,0 +1,80 @@
+import type { INostrEventEventSign } from "$root";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { type NostrEvent as NostrToolsEvent } from "nostr-tools";
+
+export type INostrEventUtilFormatTagsBasisNip99 = {
+ d_tag: string;
+ listing: NostrEventTagListing;
+ quantity: NostrEventTagQuantity;
+ price: NostrEventTagPrice;
+ location: NostrEventTagLocation;
+ images?: NostrEventTagMediaUpload[];
+ client?: NostrEventTagClient;
+};
+
+export type INostrEventUtilNeventEncode = {
+ id: string;
+ relays: string[];
+ author: string;
+ kind: number;
+};
+
+export type INostrEventUtil = {
+ first_tag_value(event: NDKEvent, tag_name: string): string;
+ fmt_tags_basis_nip99: (opts: INostrEventUtilFormatTagsBasisNip99) => string[][];
+ nostr_event_sign: (opts: INostrEventEventSign) => NostrToolsEvent;
+ nostr_event_sign_attest: (secret_key: string) => NostrToolsEvent;
+ nostr_event_verify_serialized: (event_serialized: string) => boolean;
+ nostr_event_verify: (event: NostrToolsEvent) => boolean;
+ nevent_encode: (opts: INostrEventUtilNeventEncode) => string;
+};
+
+export type NostrEventTagListing = {
+ key: string;
+ title: string;
+ category: string;
+ summary?: string;
+ process?: string;
+ lot?: string;
+ location?: string;
+ profile?: string;
+ year?: string;
+};
+
+export type NostrEventTagPrice = {
+ amt: string;
+ currency: string;
+ qty_amt: string;
+ qty_unit: string;
+};
+
+export type NostrEventTagQuantity = {
+ amt: string;
+ unit: string;
+ label?: string;
+};
+
+export type NostrEventTagLocation = {
+ city?: string;
+ region?: string;
+ region_code?: string;
+ country?: string;
+ country_code?: string;
+ lat: number;
+ lng: number;
+ geohash: string;
+};
+
+export type NostrEventTagMediaUpload = {
+ url: string;
+ size?: {
+ w: number;
+ h: number;
+ };
+};
+
+export type NostrEventTagClient = {
+ name: string;
+ pubkey: string;
+ relay: string;
+};
diff --git a/utils/src/nostr/nostr-key-util/lib.ts b/utils/src/nostr/nostr-key-util/lib.ts
@@ -0,0 +1,121 @@
+import { lib_nostr_get_key_bytes, lib_nostr_key_generate, lib_nostr_nsec_decode, lib_nostr_nsec_encode, type INostrKeyUtil } from '$root';
+import { getPublicKey, nip19 } from 'nostr-tools';
+
+export class NostrKeyUtil implements INostrKeyUtil {
+ /**
+ *
+ * @returns nostr secret key hex
+ */
+ public generate_key(): string {
+ return lib_nostr_key_generate();
+ };
+
+
+ /**
+ *
+ * @returns nostr public key hex from secret key
+ */
+ public public_key(secret_key_hex: string | undefined): string {
+ try {
+ if (!secret_key_hex) return ``;
+ const bytes = lib_nostr_get_key_bytes(secret_key_hex);
+ const hex = getPublicKey(bytes)
+ return hex;
+ } catch (e) {
+ return ``
+ }
+ }
+
+ /**
+ *
+ * @returns nostr secret key to public key hex
+ */
+ public publickey_decode(secret_key_hex?: string): string | undefined {
+ try {
+ if (secret_key_hex) {
+ return getPublicKey(lib_nostr_get_key_bytes(secret_key_hex))
+ }
+ return undefined;
+ } catch (e) {
+ return undefined;
+ }
+ }
+
+ /**
+ *
+ * @returns nostr public key npub
+ */
+ public npub(public_key_hex: string | undefined, fallback_to_hex?: boolean): string {
+ if (!public_key_hex) return ``;
+ const npub = nip19.npubEncode(public_key_hex);
+ return npub ? npub : fallback_to_hex ? public_key_hex : ``;
+ }
+
+ /**
+ *
+ * @returns public key hex from npub
+ */
+ public npub_decode(npub: string): string {
+ const decode = nip19.decode(npub);
+ console.log(`decode `, decode)
+ if (decode && decode.type === `npub` && decode.data) return decode.data
+ return ``;
+ }
+
+ /**
+ *
+ * @returns nostr secret key nsec
+ */
+ public nsec(secret_key_hex?: string): string | undefined {
+ return lib_nostr_nsec_encode(secret_key_hex);
+ }
+
+ /**
+ *
+ * @returns nostr secret key hex from nsec
+ */
+ public nsec_decode(nsec: string): string | undefined {
+ return lib_nostr_nsec_decode(nsec);
+ }
+
+ /**
+ *
+ * @returns
+ */
+ public nevent(event_pointer: nip19.EventPointer, relays: string[]): string {
+ return nip19.neventEncode(event_pointer)
+ }
+
+ /**
+ *
+ * @returns nostr public key nprofile
+ */
+ public nprofile(public_key_hex: string, relays: string[]): string {
+ if (!public_key_hex || !relays.length) return ``;
+ return nip19.nprofileEncode({ pubkey: public_key_hex, relays })
+ }
+
+ /**
+ *
+ * @returns nostr public key nprofile
+ */
+ public nprofile_decode(nprofile: string): [string, string[]] | undefined {
+ if (!nprofile) return undefined;
+ const decode = nip19.decode(nprofile);
+ if (decode && decode.type === `nprofile` && decode.data && decode.data.pubkey && decode.data.relays) return [decode.data.pubkey, decode.data.relays]
+ return undefined;
+ }
+
+ /**
+ *
+ * @returns
+ */
+ public secretkey_to_publickey(nsec_or_hex: string): string | undefined {
+ if (nsec_or_hex.startsWith(`nsec1`)) {
+ return this.nsec_decode(nsec_or_hex);
+ } else if (nsec_or_hex.length === 64) {
+ return this.publickey_decode(nsec_or_hex)
+ }
+ return undefined;
+ }
+};
diff --git a/utils/src/nostr-key-util/types.ts b/utils/src/nostr/nostr-key-util/types.ts
diff --git a/utils/src/nostr/nostr/event.ts b/utils/src/nostr/nostr/event.ts
@@ -0,0 +1,45 @@
+import { uuidv4, type INostrEventEventSign, type INostrEventUtilNeventEncode } from "$root";
+import { schnorr } from "@noble/curves/secp256k1";
+import { hexToBytes } from "@noble/hashes/utils";
+import { finalizeEvent, getEventHash, nip19, type NostrEvent as NostrToolsEvent } from "nostr-tools";
+
+export const lib_nostr_event_verify = (event: NostrToolsEvent): boolean => {
+ const hash = getEventHash(event);
+ if (hash !== event.id) return false
+ const valid = schnorr.verify(event.sig, hash, event.pubkey);
+ return valid;
+};
+
+export const lib_nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => {
+ return finalizeEvent(opts.event, hexToBytes(opts.secret_key))
+};
+
+export const lib_nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => {
+ return lib_nostr_event_sign({
+ secret_key,
+ event: {
+ kind: 1,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [],
+ content: uuidv4(),
+ },
+ });
+};
+
+
+export const lib_nostr_event_verify_serialized = async (event_serialized: string): Promise<{ public_key: string } | undefined> => {
+ try {
+ const event = JSON.parse(event_serialized);
+ const hash = getEventHash(event);
+ if (hash !== event.id) return undefined;
+ const valid = schnorr.verify(event.sig, hash, event.pubkey);
+ if (valid) return { public_key: String(event.pubkey) };
+ return undefined;
+ } catch {
+ return undefined;
+ }
+};
+
+export const lib_nostr_nevent_encode = (opts: INostrEventUtilNeventEncode): string => {
+ return nip19.neventEncode(opts);
+};
+\ No newline at end of file
diff --git a/utils/src/nostr/key.ts b/utils/src/nostr/nostr/key.ts
diff --git a/utils/src/nostr/nostr/ndk.ts b/utils/src/nostr/nostr/ndk.ts
@@ -0,0 +1,74 @@
+import { time_now_ms, type NostrMetadataTmp } from '$root';
+import NDK, { NDKEvent, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
+
+export const ndk_init = async (opts: {
+ $ndk: NDK;
+ secret_key: string;
+}): Promise<NDKUser | undefined> => {
+ try {
+ const { $ndk: ndk, secret_key } = opts;
+ const signer = new NDKPrivateKeySigner(secret_key);
+ ndk.signer = signer;
+
+ const user = await signer.user();
+ if (user) {
+ user.ndk = ndk;
+ return user;
+ }
+ } catch (e) {
+ console.log(`(error) ndk_init `, e);
+ };
+};
+
+export const ndk_event_metadata = async (opts: {
+ $ndk: NDK;
+ $ndk_user: NDKUser;
+ metadata: NostrMetadataTmp
+}): Promise<NDKEvent | undefined> => {
+ try {
+ const { $ndk, $ndk_user } = opts;
+ const ev = await ndk_event({
+ $ndk,
+ $ndk_user,
+ basis: {
+ kind: 0,
+ content: JSON.stringify(opts.metadata),
+ },
+ });
+ return ev;
+ } catch (e) {
+ console.log(`(error) ndk_event_metadata `, e);
+ }
+};
+
+export const ndk_event = async (opts: {
+ $ndk: NDK;
+ $ndk_user: NDKUser;
+ basis: {
+ kind: number;
+ content: string;
+ tags?: string[][];
+ }
+}): Promise<NDKEvent | undefined> => {
+ try {
+ const { $ndk: ndk, $ndk_user: ndk_user, basis } = opts;
+ const time_now = time_now_ms();
+
+ const tags: string[][] = [
+ ['published_at', time_now.toString()],
+ ];
+
+ if (basis.tags && basis.tags?.length > 0) for (const tag of basis.tags) tags.push(tag);
+
+ const event: NDKEvent = new NDKEvent(ndk, {
+ kind: basis.kind,
+ pubkey: ndk_user.pubkey,
+ content: basis.content,
+ created_at: time_now,
+ tags
+ });
+ return event;
+ } catch (e) {
+ console.log(`(error) ndk_event `, e);
+ };
+};
diff --git a/utils/src/nostr/types.ts b/utils/src/nostr/nostr/types.ts
diff --git a/utils/src/number.ts b/utils/src/number.ts
@@ -0,0 +1,13 @@
+export const parse_int = (val: string, fallback: number = 0): number => {
+ const num = parseInt(val);
+ return isNaN(num) ? fallback : num;
+};
+
+export const parse_float = (val: string, fallback: number = 0): number => {
+ const num = parseFloat(val);
+ return isNaN(num) ? fallback : num;
+};
+
+export const num_str = (num: number): string => num.toString();
+
+export const num_interval_range = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1)) + min;
diff --git a/utils/src/regex.ts b/utils/src/regex.ts
@@ -1,33 +0,0 @@
-export const regex = {
- float: /^[+-]?(\d+(\.\d*)?|\.\d+)$/,
- float_ch: /^[0-9\.\+\-]$/,
- float_pos: /^\d+(\.\d+)?$/,
- float_pos_ch: /^[0-9\.]$/,
- description: /^(?:\S+(?:\s+\S+)*)$/,
- description_ch: /[^a-zA-Z0-9.,!?;:'"(){}[]\s\u0600-\u06FF\u0900-\u097F\u0400-\u04FF\u0500-\u052F\u1F00-\u1FFF\u4E00-\u9FFF\uAC00-\uD7AF\u3040-\u309F\u30A0-\u30FF ]+/,
- nbsp: /[\u00A0]/g,
- nbsp_rp: /[\u00A0]+/g,
- rtlm: /[\u200F]/g,
- rtlm_rp: /[\u200F]+/g,
- commas: /[,]+/g,
- periods: /[.]+/g,
- word_only: /^[a-zA-Z]+$/,
- alpha: /[a-zA-Z ]$/,
- alpha_ch: /[a-zA-Z ]$/,
- num: /^[0-9]+$/,
- lat: /^[-+]?([1-8]?[0-9](\.\d{1,6})?|90(\.0{1,6})?)$/,
- lat_ch: /^[\d\.\+\-]$/,
- lng: /^[-+]?((1[0-7]?[0-9]|180)(\.\d{1,6})?|(\d{1,2})(\.\d{1,6})?)$/,
- lng_ch: /^[\d\.\+\-]$/,
- alphanum: /[a-zA-Z0-9., ]$/,
- alphanum_ch: /[a-zA-Z0-9.,\s\u0600-\u06FF\u0900-\u097F\u0400-\u04FF\u0500-\u052F\u1F00-\u1FFF\u4E00-\u9FFF\uAC00-\uD7AF\u3040-\u309F\u30A0-\u30FF ]+/,
- price: /^\d+(\.\d+)?$/,
- price_ch: /[0-9.]$/,
- profile_name: /^[a-zA-Z0-9._]{3,30}$/,
- profile_name_ch: /[a-zA-Z0-9._]/,
- trade_product_key: /^(?:[a-zA-Z0-9]+(?:\s+[a-zA-Z0-9]+){0,2})$/,
- trade_product_category: /^(?:[a-zA-Z0-9]+(?:\s+[a-zA-Z0-9]+){0,2})$/,
- currency_symbol: /(?:[A-Za-z]{3,5}\$|\p{Sc})/u,
- currency_marker: /(?:[A-Za-z]{2,4}[^\d\s]+|[^\d\s]{1,3}[A-Za-z]{2,4})/,
- ws_proto: /^(wss:\/\/|ws:\/\/)/,
-};
diff --git a/utils/src/response.ts b/utils/src/response.ts
@@ -0,0 +1,32 @@
+import type { NotifyMessage } from "$root";
+
+export const is_err_response = (response: any): response is { err: string } => {
+ return "err" in response && typeof response.err === "string";
+}
+
+export const is_pass_response = (response: any): response is { pass: true } => {
+ return "pass" in response && response.pass === true;
+}
+
+export const is_result_response = (response: any): response is { result: string } => {
+ return "result" in response && typeof response.result === "string";
+}
+
+export const is_results_response = (response: any): response is { results: string[] } => {
+ return "results" in response && Array.isArray(response.results);
+}
+
+export const is_error_response = (response: any): response is { error: string } => {
+ return "error" in response && typeof response.result === "string";
+}
+
+export const is_message_response = (response: any): response is NotifyMessage => {
+ return (
+ typeof response === "object" &&
+ response !== null &&
+ "message" in response &&
+ typeof response.message === "string" &&
+ (response.ok === undefined || typeof response.ok === "string") &&
+ (response.cancel === undefined || typeof response.cancel === "string")
+ );
+};
+\ No newline at end of file
diff --git a/utils/src/trade.ts b/utils/src/trade.ts
@@ -1,4 +1,4 @@
-import { type MassUnit, parse_mass_unit } from "./units";
+import { parse_int, parse_mass_unit, type MassUnit } from "$root";
export type TradeKey = `coffee` | `cacao` | `maca`;
@@ -14,15 +14,15 @@ export const fmt_trade_quantity_tup = (obj: TradeQuantity): string => `${obj.mas
export const parse_trade_quantity_tup = (sel_key: string): TradeQuantity | undefined => {
const [qty_mass, qty_mass_u, qty_label] = sel_key.split(`-`);
- if (!qty_mass || !qty_mass_u || !qty_label) return undefined;
+ const mass_unit = parse_mass_unit(qty_mass_u);
+ if (!qty_mass || !qty_mass_u || !qty_label || !mass_unit) return undefined;
return {
- mass: parseInt(qty_mass),
- mass_unit: parse_mass_unit(qty_mass_u), //@todo
+ mass: parse_int(qty_mass, 1),
+ mass_unit,
label: qty_label
}
};
-
export const trade_keys: TradeKey[] = [`coffee`, `cacao`, `maca`] as const;
const trade_quantity_default: TradeQuantity[] = [
@@ -43,9 +43,16 @@ const trade_quantity_default: TradeQuantity[] = [
},
];
+const trade_process_default: string[] = [
+ `natural`,
+ `dried`,
+ `roasted`
+];
+
export type TradeParam = {
default: {
quantity: TradeQuantity[];
+ process: string[];
},
key: Record<TradeKey, {
quantity: TradeQuantity[];
@@ -56,6 +63,7 @@ export type TradeParam = {
export const trade: TradeParam = {
default: {
quantity: trade_quantity_default,
+ process: trade_process_default,
},
key: {
coffee: {
diff --git a/utils/src/types.ts b/utils/src/types.ts
@@ -1,9 +1,21 @@
-import type { GeolocationCoordinatesPoint } from "./geolocation";
+export type FieldRecord = Record<string, string>;
+export type NotifyMessage = {
+ message: string;
+ ok?: string;
+ cancel?: string;
+};
-export type ErrorResponse<T extends object> = { error: T; };
-export type ErrorMessage<T extends string> = { err: T };
+export type GeometryPoint = {
+ type: string;
+ coordinates: number[];
+};
-export type FieldRecord = Record<string, string>;
+export type GeometryPolygon = {
+ type: string;
+ coordinates: number[][][];
+};
+
+export type GeolocationPointTuple = [number, number];
export type ResultId = { id: string; };
export type ResultPass = { pass: true; };
@@ -12,21 +24,6 @@ export type ResultObj<T> = { result: T; };
export type ResultPublicKey = { public_key: string; };
export type ResultSecretKey = { secret_key: string; };
-export type LocationPoint = GeolocationCoordinatesPoint & {
- lat: number;
- lng: number;
- error: GeolocationCoordinatesPoint;
-}
-
-export type NumberTuple = [number, number];
-
export type FileBytesFormat = `kb` | `mb` | `gb`;
export type FileMimeType = string;
export type FilePath = { file_path: string; file_name: string; mime_type: FileMimeType; }
-
-
-export type NotifyMessage = {
- message: string;
- ok?: string;
- cancel?: string;
-};
-\ No newline at end of file
diff --git a/utils/src/unit.ts b/utils/src/unit.ts
@@ -0,0 +1,41 @@
+import type { vunion_area_unit, vunion_mass_unit } from "$root";
+import { z } from "zod";
+
+export type AreaUnit = z.infer<typeof vunion_area_unit>;
+export const area_units: AreaUnit[] = [`ac`, `ha`, `ft2`, `m2`] as const;
+
+export type MassUnit = z.infer<typeof vunion_mass_unit>;
+export const mass_units: MassUnit[] = [`kg`, `lb`, `g`] as const;
+
+export function parse_mass_unit_default(val?: string): MassUnit {
+ const unit = parse_mass_unit(val);
+ return unit ?? `kg`
+}
+
+export function parse_mass_unit(val?: string): MassUnit | undefined {
+ switch (val) {
+ case `kg`:
+ case `lb`:
+ case `g`:
+ return val;
+ default:
+ return undefined;
+ };
+};
+
+export function parse_area_unit_default(val?: string): AreaUnit {
+ const unit = parse_area_unit(val);
+ return unit ?? `ac`
+}
+
+export function parse_area_unit(val?: string): AreaUnit | undefined {
+ switch (val) {
+ case `ac`:
+ case `ha`:
+ case `ft2`:
+ case `m2`:
+ return val;
+ default:
+ return undefined;
+ };
+};
diff --git a/utils/src/units.ts b/utils/src/units.ts
@@ -1,55 +0,0 @@
-import convert from "convert";
-import { z } from "zod";
-
-export const MassUnitSchema = z.union([
- z.literal(`kg`),
- z.literal(`lb`),
- z.literal(`g`),
-]);
-
-export const mass_units: MassUnit[] = [`kg`, `lb`, `g`] as const;
-
-export type MassUnit = z.infer<typeof MassUnitSchema>;
-
-export function parse_mass_unit(val?: string): MassUnit {
- switch (val) {
- case `kg`:
- case `lb`:
- case `g`:
- return val;
- default:
- return `kg`;
- };
-};
-
-export function parse_mass_unit_u(val?: string): MassUnit | undefined {
- switch (val) {
- case `kg`:
- case `lb`:
- case `g`:
- return val;
- default:
- return undefined;
- };
-};
-
-export const mass_g = (units: MassUnit, amt: number): number => {
- return convert(amt, units).to(`g`);
-};
-
-export const mass_tf = (units_from: MassUnit, units_to: MassUnit, amt: number): number => {
- return convert(amt, units_from).to(units_to);
-};
-
-export const mass_tf_u = (units_from: MassUnit, units_to: string, amt: number): number => {
- const _units_to = parse_mass_unit_u(units_to);
- if (!_units_to) throw new Error(`Malformed units.`)
- return convert(amt, units_from).to(_units_to);
-};
-
-export const mass_tf_str = (units_from: string, units_to: string, amt: number): number => {
- const _units_from = parse_mass_unit_u(units_from);
- const _units_to = parse_mass_unit_u(units_to);
- if (!_units_from || !_units_to) throw new Error(`Malformed units.`)
- return convert(amt, _units_from).to(_units_to);
-};
-\ No newline at end of file
diff --git a/utils/src/window.ts b/utils/src/window.ts
@@ -1,31 +0,0 @@
-export type AppLayoutKey = 'mobile_base' | 'mobile_y';
-export type AppLayout<T1 extends string, T2 extends string> = `${T1}_${T2}`;
-export type AppHeightsResponsiveKey =
- | `tabs`
- | `nav`
- | `lo_view`
- | `view`
- | `view_offset`
- | `trellis_centered`;
-export type AppHeightsResponsive = AppLayout<AppHeightsResponsiveKey, AppLayoutKey>;
-
-type ClientWindow = {
- app: {
- layout: Record<AppLayoutKey, {
- h: number;
- }>;
- }
-};
-
-export const wind: ClientWindow = {
- app: {
- layout: {
- mobile_base: {
- h: 600
- },
- mobile_y: {
- h: 750
- }
- }
- }
-};
-\ No newline at end of file
diff --git a/utils/tsconfig.json b/utils/tsconfig.json
@@ -6,17 +6,23 @@
"es2021",
"dom"
],
- "module": "CommonJS",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "declaration": true,
+ "declarationMap": true,
"outDir": "./dist",
- "rootDir": "./src",
- "noEmit": true,
"esModuleInterop": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "paths": {
+ "$root": ["src/index.js"]
+ }
},
"include": [
"src"
],
"exclude": [
- "node_modules"
+ "node_modules",
+ "dist"
],
}
\ No newline at end of file
diff --git a/utils/tsup.config.ts b/utils/tsup.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ['src/index.ts'],
+ format: ['esm', 'cjs'],
+ dts: true,
+ outDir: 'dist',
+ splitting: false,
+ clean: true,
+ sourcemap: true,
+});