chore: remove orphaned legacy files and move QR scanner to elements/

- Trashed 10 unreferenced files: core .legacy types, bak files, element_modal_v1, element_websocket_v2, AE_MetadataFooter_not_ref, element_qr_scanner_v2
- Removed empty placeholder dirs: ae_db/, hooks/
- Moved element_qr_scanner_v3.svelte from lib root into elements/
- Updated 2 import paths for QR scanner (badges + leads)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-20 09:45:57 -04:00
parent 60461de9d9
commit 0d960435f8
11 changed files with 3 additions and 2622 deletions

View File

@@ -1,23 +0,0 @@
export interface Account {
id: string;
// id_random: string;
account_id: string;
account_id_random: string;
code?: string;
name: string;
short_name?: null | string;
description?: null | string;
enable: null | boolean;
enable_from?: null | Date;
enable_to?: null | Date;
hide?: null | boolean;
priority?: null | boolean;
sort?: null | number;
group?: null | string;
notes?: null | string;
created_on: Date;
updated_on?: null | Date;
}

View File

@@ -1,736 +0,0 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { db_core } from '$lib/ae_core/db_core';
const ae_promises: key_val = {};
// Updated 2025-06-10
export async function load_ae_obj_id__person({
api_cfg,
person_id,
params = {},
try_cache = false,
log_lvl = 0
}: {
api_cfg: any;
person_id: string;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** load_ae_obj_id__person() *** person_id=${person_id}`);
}
ae_promises.load__person_obj = await api
.get_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: 'person',
obj_id: person_id,
use_alt_table: false,
use_alt_base: false,
params: params,
log_lvl: log_lvl
})
.then(async function (person_obj_get_result) {
if (person_obj_get_result) {
if (try_cache) {
// Process the results first
const processed_obj_li = await process_ae_obj__person_props({
obj_li: [person_obj_get_result],
log_lvl: log_lvl
});
if (log_lvl) {
console.log('Processed object list:', processed_obj_li);
}
// Save the updated results list to the database
if (log_lvl) {
console.log('Saving to DB...');
}
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl: log_lvl
});
if (log_lvl) {
console.log('DB save completed.');
}
// // This is expecting a list
// db_save_ae_obj_li__person({
// obj_type: 'person',
// obj_li: [person_obj_get_result],
// log_lvl: log_lvl
// });
}
return person_obj_get_result;
} else {
console.log('No results returned.');
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
if (log_lvl) {
console.log('ae_promises.load__person_obj:', ae_promises.load__person_obj);
}
return ae_promises.load__person_obj;
}
// Updated 2025-06-10
export async function load_ae_obj_li__person({
api_cfg,
for_obj_type = 'account',
for_obj_id,
qry_email = null,
qry_user_id = null,
enabled = 'enabled',
hidden = 'not_hidden',
limit = 99,
offset = 0,
order_by_li = [
{ family_name: 'ASC' },
{ given_name: 'ASC' },
{ updated_on: 'DESC' },
{ created_on: 'DESC' }
],
// params_json = {},
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
for_obj_type: string;
for_obj_id: string;
qry_email?: string | null;
qry_user_id?: string | null;
enabled?: 'enabled' | 'all' | 'not_enabled' | undefined; // all, disabled, enabled
hidden?: 'hidden' | 'all' | 'not_hidden' | undefined; // all, hidden, not_hidden
limit?: number;
offset?: number;
order_by_li?: { [key: string]: 'ASC' | 'DESC' }[] | null;
// params_json?: null|key_val,
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** load_ae_obj_li__person() *** for_obj_type=${for_obj_type} for_obj_id=${for_obj_id} enabled=${enabled} hidden=${hidden} limit=${limit} offset=${offset}`
);
}
const params_json: key_val = {};
// console.log('params_json:', params_json);
if (qry_user_id) {
// params_json['and_qry'] = {};
// params_json['and_qry']['user_id_random'] = qry_user_id;
params_json['qry'] = [];
const qry_param = {
type: 'AND',
field: 'user_id_random',
operator: '=',
value: qry_user_id
};
params_json['qry'].push(qry_param);
}
if (log_lvl) {
console.log('params_json:', params_json);
}
ae_promises.load__person_obj_li = await api
.get_ae_obj_li_for_obj_id_crud_v2({
api_cfg: api_cfg,
obj_type: 'person',
for_obj_type: for_obj_type,
for_obj_id: for_obj_id,
use_alt_tbl: false,
use_alt_mdl: false,
use_alt_exp: false,
enabled: enabled,
hidden: hidden,
order_by_li: order_by_li,
limit: limit,
offset: offset,
params_json: params_json,
params: params,
log_lvl: log_lvl
})
.then(async function (person_obj_li_get_result) {
if (person_obj_li_get_result) {
if (try_cache) {
// Process the results first
const processed_obj_li = await process_ae_obj__person_props({
obj_li: person_obj_li_get_result,
log_lvl: log_lvl
});
if (log_lvl) {
console.log('Processed object list:', processed_obj_li);
}
// Save the updated results list to the database
if (log_lvl) {
console.log('Saving to DB...');
}
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl: log_lvl
});
if (log_lvl) {
console.log('DB save completed.');
}
// db_save_ae_obj_li__person({
// obj_type: 'person',
// obj_li: person_obj_li_get_result,
// log_lvl: log_lvl
// });
}
return person_obj_li_get_result;
} else {
return [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
console.log('ae_promises.load__person_obj_li:', ae_promises.load__person_obj_li);
return ae_promises.load__person_obj_li;
}
// Updated 2025-06-10
export async function create_ae_obj__person({
api_cfg,
user_id,
data_kv,
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
user_id?: string;
data_kv: key_val;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** create_ae_obj__person() *** user_id=${user_id}`);
}
ae_promises.create__person = await api
.create_ae_obj_crud({
api_cfg: api_cfg,
obj_type: 'person',
fields: {
user_id_random: user_id,
...data_kv
},
key: api_cfg.api_crud_super_key,
params: params,
return_obj: true,
log_lvl: log_lvl
})
.then(async function (person_obj_create_result) {
if (person_obj_create_result) {
if (try_cache) {
// Process the results first
const processed_obj_li = await process_ae_obj__person_props({
obj_li: [person_obj_create_result],
log_lvl: log_lvl
});
if (log_lvl) {
console.log('Processed object list:', processed_obj_li);
}
// Save the updated results list to the database
if (log_lvl) {
console.log('Saving to DB...');
}
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl: log_lvl
});
if (log_lvl) {
console.log('DB save completed.');
}
// db_save_ae_obj_li__person(
// {
// obj_type: 'person',
// obj_li: [person_obj_create_result],
// log_lvl: log_lvl
// });
}
return person_obj_create_result;
} else {
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
})
.finally(function () {});
if (log_lvl) {
console.log('ae_promises.create__person:', ae_promises.create__person);
}
return ae_promises.create__person;
}
// Updated 2025-05-10
export async function delete_ae_obj_id__person({
api_cfg,
person_id,
method = 'delete', // 'delete', 'disable', 'hide'
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
person_id: string;
method?: string;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__person() *** person_id=${person_id}`);
}
ae_promises.delete__person_obj = await api
.delete_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: 'person',
obj_id: person_id,
key: api_cfg.api_crud_super_key,
params: params,
method: method,
log_lvl: log_lvl
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
})
.finally(async function () {
if (try_cache) {
if (log_lvl) {
console.log(`Attempting to remove IDB entry for person_id=${person_id}`);
}
await db_core.person.delete(person_id);
}
});
if (log_lvl) {
console.log('ae_promises.delete__person_obj:', ae_promises.delete__person_obj);
}
return ae_promises.delete__person_obj;
}
// Updated 2025-06-10
export async function update_ae_obj__person({
api_cfg,
person_id,
data_kv,
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
person_id: string;
data_kv: key_val;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** update_ae_obj__person() *** person_id=${person_id}`, data_kv);
}
// log_lvl = 1;
// Perform the API update
const result = await api.update_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: 'person',
obj_id: person_id,
fields: data_kv,
key: api_cfg.api_crud_super_key,
params: params,
return_obj: true,
log_lvl: log_lvl
});
// Handle the result
if (result) {
if (try_cache) {
// Process the results first
const processed_obj_li = await process_ae_obj__person_props({
obj_li: [result],
log_lvl: log_lvl
});
if (log_lvl) {
console.log('Processed object list:', processed_obj_li);
}
// Save the updated results list to the database
if (log_lvl) {
console.log('Saving to DB...');
}
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl: log_lvl
});
if (log_lvl) {
console.log('DB save completed.');
}
// await db_save_ae_obj_li__person({
// obj_type: 'person',
// obj_li: [result],
// log_lvl: log_lvl,
// });
}
return result;
} else {
console.error('Failed to update person.');
return null;
}
}
// Updated 2024-06-10
export function db_save_ae_obj_li__person({
obj_type,
obj_li,
log_lvl = 0
}: {
obj_type: string;
obj_li: any;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** db_save_ae_obj_li__person() ***`);
}
if (obj_li && obj_li.length) {
obj_li.forEach(async function (obj: any) {
if (log_lvl) {
console.log(`ae_obj ${obj_type}:`, obj);
}
const obj_record = {
id: obj.person_id_random,
// id_random: obj.person_id_random,
person_id: obj.person_id_random,
person_id_random: obj.person_id_random,
external_id: obj.external_id,
external_sys_id: obj.external_sys_id,
code: obj.code,
account_id: obj.account_id_random,
account_id_random: obj.account_id_random,
person_profile_id: obj.person_profile_id_random,
person_profile_id_random: obj.person_profile_id_random, // The new table person_profile will be used soon...
user_id: obj.user_id_random,
user_id_random: obj.user_id_random,
pronouns: obj.pronouns,
informal_name: obj.informal_name,
title_names: obj.title_names,
given_name: obj.given_name,
middle_name: obj.middle_name,
family_name: obj.family_name,
designations: obj.designations,
professional_title: obj.professional_title,
full_name: obj.full_name,
full_name_override: obj.full_name_override, // was display_name and display_name_override
affiliations: obj.affiliations,
primary_email: obj.primary_email,
biography: obj.biography,
agree: obj.agree,
comments: obj.comments,
allow_auth_key: obj.allow_auth_key, // For sign in without password
// auth_key: obj.auth_key,
passcode: obj.passcode,
data_json: obj.data_json,
enable: obj.enable,
hide: obj.hide,
priority: obj.priority,
sort: obj.sort,
group: obj.group,
notes: obj.notes,
created_on: obj.created_on,
updated_on: obj.updated_on,
// From SQL view
username: obj.username,
user_name: obj.user_name,
user_email: obj.user_email,
user_allow_auth_key: obj.user_allow_auth_key, // For sign in without password
user_super: obj.user_super,
user_manager: obj.user_manager,
user_administrator: obj.user_administrator,
user_public: obj.user_public
};
let id_random = null;
try {
id_random = await db_core.person.update(obj_record.id, obj_record);
} catch (error) {
console.log(`Error: Failed to update ${obj_record.id}: ${error}`);
}
if (!id_random) {
if (log_lvl) {
console.log(`Failed to update record with ID: ${obj_record.id}. Trying put...`);
}
try {
id_random = await db_core.person.put(obj_record);
} catch (error) {
console.log(`Error: Failed to put ${obj.person_id_random}: ${error}`);
}
} else {
if (log_lvl) {
console.log(`Updated record with ID: ${obj_record.id}`);
}
}
if (!id_random) {
console.log(`Failed to save record with ID: ${obj_record.id}`);
} else {
if (log_lvl) {
console.log(`Saved record with ID: ${obj_record.id}`);
}
}
});
return true;
}
}
// Updated 2025-06-10
const properties_to_save = [
'id',
'person_id',
// 'person_id_random',
'external_id',
'external_sys_id',
'code',
'account_id',
// 'account_id_random',
'person_profile_id',
// 'person_profile_id_random', // The new table person_profile will be used soon...
'user_id',
// 'user_id_random',
'pronouns',
'informal_name',
'title_names',
'given_name',
'middle_name',
'family_name',
'designations',
'professional_title',
'full_name',
'full_name_override', // was display_name and display_name_override
'affiliations',
'primary_email',
'biography',
'agree',
'comments',
'allow_auth_key', // For sign in without password
// 'auth_key', // Should this be saved locally?
'passcode',
// 'passcode_timeout',
// 'passcode_read', // For LLM (AI) generated summary...???
// 'passcode_read_expire',
// 'passcode_write',
// 'passcode_write_expire',
// 'private_passcode',
// 'alert',
// 'alert_msg',
'data_json',
'enable',
'hide',
'priority',
'sort',
'group',
'notes',
'created_on',
'updated_on',
// Generated fields for sorting locally only
'tmp_sort_1',
'tmp_sort_2',
'tmp_sort_3',
// From SQL view
'username',
// 'user_username', // Same as username
'user_name',
'user_email',
'user_allow_auth_key', // For sign in without password
'user_super',
'user_manager',
'user_administrator',
'user_public',
'organization_id',
// 'organization_id_random',
'organization_name',
'contact_id',
// 'contact_id_random',
'contact_name',
'contact_email',
'contact_cc_email',
'contact_phone_mobile',
'contact_phone_home',
'contact_phone_office',
'contact_phone_land',
'contact_phone_fax',
'contact_phone_other',
'address_id',
// 'address_id_random',
'address_city',
'address_country_alpha_2_code' // contact_address_country_alpha_2_code
];
/**
* NON-EXPORTED LOCAL HELPER
* Processes a list of Aether objects by applying common and specific transformations.
*/
async function _process_generic_props<T extends Record<string, any>>({
obj_li,
obj_type,
log_lvl = 0,
specific_processor
}: {
obj_li: T[];
obj_type: string;
log_lvl?: number;
specific_processor?: (obj: T) => Promise<T> | T;
}): Promise<T[]> {
if (log_lvl > 0) {
console.log(
`*** _process_generic_props: Processing ${obj_li.length} objects of type "${obj_type}" ***`
);
}
if (!obj_li || obj_li.length === 0) {
if (log_lvl > 0) console.log('No objects to process.');
return [];
}
const processed_obj_li: T[] = [];
for (const original_obj of obj_li) {
let processed_obj = { ...original_obj };
// --- Common Transformations ---
// 1. Standardize ID and other '_random' fields
// The API often returns fields like 'person_id_random', which need to be aliased to 'person_id'.
for (const key in processed_obj) {
if (key.endsWith('_random')) {
const new_key = key.slice(0, -7); // Remove '_random' suffix
(processed_obj as any)[new_key] = processed_obj[key];
}
}
// Ensure 'id' is set from '[obj_type]_id_random'
const random_id_key = `${obj_type}_id_random`;
if (processed_obj[random_id_key]) {
(processed_obj as any).id = processed_obj[random_id_key];
}
// 2. Create common computed properties for client-side sorting.
const group = processed_obj.group ?? '0';
const priority = processed_obj.priority ? 1 : 0;
const sort = processed_obj.sort ?? '0';
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
// --- Specific Transformations ---
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
}
processed_obj_li.push(processed_obj as T);
}
return processed_obj_li;
}
// Updated 2025-06-10
export async function process_ae_obj__person_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'person',
log_lvl,
specific_processor: (obj) => {
// Person-specific computed sort fields, overriding generic ones if needed
obj.tmp_sort_1 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${
obj.sort ?? '0'
}_${obj.updated_on}_${obj.created_on}`;
obj.tmp_sort_2 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${
obj.sort ?? '0'
}_${obj.updated_on ?? obj.created_on}`;
obj.tmp_sort_3 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${
obj.sort ?? '0'
}_${obj.name ?? ''}_${obj.updated_on ?? obj.created_on}`;
return obj;
}
});
}

View File

@@ -1,50 +0,0 @@
export interface Site {
id: string;
// id_random: string;
site_id: string;
site_id_random?: string;
code?: string;
account_id: string;
account_id_random?: string;
name: string;
description?: null | string;
restrict_access?: null | boolean;
access_key?: null | string;
access_code_kv_json?: null | string;
logo_path?: null | string;
logo_bg_color?: null | string; // Not really used currently.
// background_image_path?: null|string; // Legacy field
// background_bg_color?: null|string; // Legacy field
title?: null | string;
// header_html?: null|string; // Legacy field
// header_css?: null|string; // Legacy field
// header_image_path?: null|string; // Legacy field
// header_image_bg_color?: null|string; // Legacy field
// body_html?: null|string; // Legacy field
tagline?: null | string;
// site_header_h1?: null|string; // Legacy field
// site_header_h2?: null|string; // Legacy field
style_href?: null | string; // Legacy field
// script_src?: null|string; // Legacy field
google_tracking_id?: null | string;
cfg_json?: null | string; // key value config json
enable: null | boolean;
enable_from?: null | Date;
enable_to?: null | Date;
hide?: null | boolean;
priority?: null | boolean;
sort?: null | number;
group?: null | string;
notes?: null | string;
created_on: Date;
updated_on?: null | Date;
}

View File

@@ -1,352 +0,0 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_core } from '$lib/ae_core/db_core';
/*
* *** LEGACY AUTHENTICATION HEADER LOGIC ***
*
* The functions in this file interact with legacy Aether API authentication endpoints
* (e.g., /user/authenticate, /user/lookup_email).
*
* Unlike V3 endpoints which handle context automatically or via standard headers,
* these legacy endpoints have specific requirements:
*
* 1. They often require the `x-account-id` header to be explicitly set to the target
* account ID to find the user within that specific account context.
* 2. The standard API wrapper logic might strip `x-account-id` if `x-no-account-id`
* is present (Bootstrap Paradox logic). We must explicitly remove `x-no-account-id`
* and set `x-account-id` to ensure the request is routed correctly.
* 3. Some endpoints accept `account_id` as a query parameter, while others (like email sending)
* may crash (500 Error) if unexpected parameters are passed.
*/
const ae_promises: key_val = {};
// Updated 2025-04-04
// This function handles username/password authentication.
// It explicitly sets the x-account-id header to ensure the user is looked up in the correct account.
export async function auth_ae_obj__username_password({
api_cfg,
account_id,
null_account_id = false,
username,
password,
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
null_account_id?: boolean;
username: string;
password: string;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** auth_ae_obj__username_password() *** account_id=${account_id} username=${username} password=${password}`
);
}
const endpoint = '/user/authenticate';
// Prepare API config with correct headers to override global guest settings
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
if (account_id) {
use_api_cfg.headers['x-account-id'] = account_id;
delete use_api_cfg.headers['x-no-account-id'];
params['account_id'] = account_id;
}
if (null_account_id) {
params['null_account_id'] = true;
}
params['username'] = username; // Required
params['password'] = password; // Required
params['inc_jwt'] = true; // Request a JWT in the response
if (log_lvl > 1) {
console.log(`auth_ae_obj__username_password() - params:`, params);
}
ae_promises.auth__username_password = await api
.get_object({
api_cfg: use_api_cfg,
endpoint: endpoint,
params: params,
// data: {},
log_lvl: log_lvl
})
.then(async function (user_obj_get_result) {
if (user_obj_get_result) {
// if (try_cache) {
// // This is expecting a list
// db_save_ae_obj_li__user({
// obj_type: 'user',
// obj_li: [user_obj_get_result],
// log_lvl: log_lvl
// });
// }
return user_obj_get_result;
} else {
console.log('No results returned.');
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
if (log_lvl) {
console.log('ae_promises.auth__username_password:', ae_promises.auth__username_password);
}
return ae_promises.auth__username_password;
}
// Updated 2025-04-04
// This function handles authentication using a User ID and a one-time auth key.
export async function auth_ae_obj__user_id_user_auth_key({
api_cfg,
account_id,
user_id,
user_auth_key,
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
user_id: string;
user_auth_key: string;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** auth_ae_obj__user_id_user_auth_key() *** account_id=${account_id} user_id=${user_id}`
);
}
const endpoint = '/user/authenticate';
// Prepare API config with correct headers to override global guest settings
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
if (account_id) {
use_api_cfg.headers['x-account-id'] = account_id;
delete use_api_cfg.headers['x-no-account-id'];
params['account_id'] = account_id;
}
params['user_id'] = user_id; // Required
params['auth_key'] = user_auth_key; // Required
params['inc_jwt'] = true; // Request a JWT in the response
if (log_lvl > 1) {
console.log(`auth_ae_obj__user_id_user_auth_key() - params:`, params);
}
ae_promises.auth__user_id_user_key = await api
.get_object({
api_cfg: use_api_cfg,
endpoint: endpoint,
params: params,
log_lvl: log_lvl
})
.then(async function (user_obj_get_result) {
if (user_obj_get_result) {
return user_obj_get_result;
} else {
console.log('No results returned.');
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
if (log_lvl) {
console.log('ae_promises.auth__user_id_user_key:', ae_promises.auth__user_id_user_key);
}
return ae_promises.auth__user_id_user_key;
}
// Send an email to the user with a new one time use authentication key. The new key must be generated and returned first.
// Updated 2025-04-08
// NOTE: This legacy endpoint is sensitive to extra query parameters and will 500 if account_id is passed in the URL.
export async function send_email_auth_ae_obj__user_id({
api_cfg,
account_id,
user_id,
base_url,
key_param_name = 'user_key', // API defaults to 'auth_key'
params = {},
// try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
user_id: string;
base_url?: string;
key_param_name?: string;
params?: key_val;
// try_cache?: boolean,
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** send_email_auth_ae_obj__user_id() *** account_id=${account_id} user_id=${user_id}`
);
}
if (log_lvl > 1) {
console.log(api_cfg);
}
const email_auth_key_endpoint = `/user/${user_id}/email_auth_key_url`;
params = {
root_url: base_url,
key_param_name: key_param_name
};
// Prepare API config with correct headers to override global guest settings
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
if (account_id) {
use_api_cfg.headers['x-account-id'] = account_id;
delete use_api_cfg.headers['x-no-account-id'];
// WARNING: Do NOT add account_id to params here, as it causes a 500 error on the legacy backend.
}
ae_promises.auth_key__send_email = await api.get_object({
api_cfg: use_api_cfg,
endpoint: email_auth_key_endpoint,
params: params,
log_lvl: log_lvl
});
return ae_promises.auth_key__send_email;
}
// Look up user based on email address provided
// Updated 2025-04-08
export async function qry_ae_obj_li__user_email({
api_cfg,
account_id,
null_account_id = false,
email,
params = {},
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
null_account_id?: boolean;
email: string;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** qry_ae_obj_li__user_email() *** account_id=${account_id} email=${email}`);
}
const endpoint = '/user/lookup_email';
// Prepare API config with correct headers to override global guest settings
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
if (account_id) {
use_api_cfg.headers['x-account-id'] = account_id;
delete use_api_cfg.headers['x-no-account-id'];
params['account_id'] = account_id;
}
params['email'] = email; // Required
params['null_account_id'] = null_account_id || false;
if (log_lvl > 1) {
console.log(`qry_ae_obj_li__user_email() - params:`, params);
}
ae_promises.qry__user_email = await api
.get_object({
api_cfg: use_api_cfg,
endpoint: endpoint,
params: params,
log_lvl: log_lvl
})
.then(async function (user_obj_get_result) {
if (user_obj_get_result) {
return user_obj_get_result;
} else {
console.log('No results returned.');
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
if (log_lvl) {
console.log('ae_promises.qry__user_email:', ae_promises.qry__user_email);
}
return ae_promises.qry__user_email;
}
// Change user password
// endpoint: PATCH /user/{user_id}/change_password
// params:
// data_kv: password (the new password)
// Updated 2025-04-11
export async function auth_ae_obj__user_id_change_password({
api_cfg,
account_id,
user_id,
password,
params = {},
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
user_id: string;
password: string;
params?: key_val;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** auth_ae_obj__user_id_change_password() *** account_id=${account_id} user_id=${user_id}`
);
}
const endpoint = `/user/${user_id}/change_password`;
params['user_id'] = user_id; // Required
if (log_lvl > 1) {
console.log(`auth_ae_obj__user_id_change_password() - params:`, params);
}
ae_promises.change_password__user_id = await api
.patch_object({
api_cfg: api_cfg,
endpoint: endpoint,
params: params,
data: { password: password },
log_lvl: log_lvl
})
.then(async function (change_password_result) {
if (change_password_result) {
return change_password_result;
} else {
console.log('No results returned.');
return null;
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
if (log_lvl) {
console.log('ae_promises.change_password__user_id:', ae_promises.change_password__user_id);
}
return ae_promises.change_password__user_id;
}

View File

@@ -1,65 +0,0 @@
<script lang="ts">
/**
* AE_MetadataFooter.svelte
* GENERIC Aether Metadata Display
* Reusable across all modules to standardize created/updated/original info.
*/
import { ae_util } from '$lib/ae_utils/ae_utils';
import { ae_loc } from '$lib/stores/ae_stores';
import { Clock, CalendarClock, Globe } from '@lucide/svelte';
interface Props {
obj: any;
showOriginal?: boolean;
containerClass?: string;
}
let {
obj,
showOriginal = true,
containerClass = "ae_meta flex flex-col gap-2 p-4 border-t border-surface-500/10 text-xs text-surface-500 w-full"
}: Props = $props();
</script>
<footer class={containerClass}>
<!-- Original Date/Time Info (if applicable) -->
{#if showOriginal && (obj?.original_datetime || obj?.original_timezone)}
<div class="flex flex-row flex-wrap gap-x-4 gap-y-1 items-center bg-surface-500/5 p-2 rounded">
<span class="flex items-center gap-1">
<Globe size="1.1em" class="opacity-70" />
<span class="font-bold uppercase tracking-tighter">Original:</span>
{obj?.original_datetime ? ae_util.iso_datetime_formatter(obj.original_datetime, 'datetime_iso_12_no_seconds') : '--'}
</span>
{#if obj?.original_timezone}
<span class="flex items-center gap-1">
<span class="font-bold uppercase tracking-tighter">TZ:</span>
{obj.original_timezone}
</span>
{/if}
</div>
{/if}
<!-- System Timestamps -->
<div class="flex flex-col sm:flex-row justify-between items-center gap-2 px-1">
<div class="flex flex-wrap gap-4 justify-center sm:justify-start">
<span class="flex items-center gap-1" title="Creation date">
<CalendarClock size="1.1em" class="opacity-70 text-primary-500" />
<span class="font-semibold uppercase tracking-tighter">Created:</span>
{ae_util.iso_datetime_formatter(obj?.created_on, 'datetime_iso_12_no_seconds')}
</span>
{#if obj?.updated_on}
<span class="flex items-center gap-1" title="Last update">
<Clock size="1.1em" class="opacity-70 text-secondary-500" />
<span class="font-semibold uppercase tracking-tighter">Updated:</span>
{ae_util.iso_datetime_formatter(obj.updated_on, 'datetime_iso_12_no_seconds')}
</span>
{/if}
</div>
{#if obj?.journal_entry_type || obj?.type}
<span class="badge variant-soft-surface text-[10px] uppercase font-bold tracking-widest">
Type: {obj?.journal_entry_type || obj?.type}
</span>
{/if}
</div>
</footer>

View File

@@ -1,591 +0,0 @@
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
// *** Import Svelte core
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
// import 'html5-qrcode';
import {
Html5Qrcode,
Html5QrcodeSupportedFormats
} from 'html5-qrcode';
// *** Import Lucide icons
import { Camera, Crosshair, Keyboard, QrCode, Send } from '@lucide/svelte';
// *** Import Aether core variables and functions
import { api } from '$lib/api/api';
import { ae_api } from '$lib/stores/ae_stores';
// *** Import Aether core components
// import Element_input from './element_input.svelte';
// import ae from '/element_input.svelte';
// import Input_element from '/element_input.svelte';
// *** Import Aether module variables and functions
// *** Import Aether module components
interface Props {
// *** Export/Exposed variables and functions for component
start_qr_scanner?: boolean;
show_pause_btn?: boolean; // pause and resume buttons
show_qr_manual_text_entry_option?: boolean;
show_qr_manual_badge_id_entry_option?: boolean;
show_qr_scan_result?: boolean;
qr_fps?: number;
qr_viewfinder_width?: number; // 275 seems good... Need to not let the this be larger than the container which changes based on the width of the screen/window.
qr_facing_mode?: string; // environment, user, { exact: 'environment'}, { exact: 'user'}
}
let {
start_qr_scanner = $bindable(true),
show_pause_btn = false,
show_qr_manual_text_entry_option = false,
show_qr_manual_badge_id_entry_option = false,
show_qr_scan_result = true,
qr_fps = 10,
qr_viewfinder_width = 275,
qr_facing_mode = 'environment'
}: Props = $props();
const dispatch = createEventDispatcher();
// *** Set initial variables
let scanning_status: string = $state('not_started');
let qr_scan_result: null | string = $state(null);
let qr_found_text: null | string = null;
let qr_entered_text: null | string = $state(null);
let qr_entered_badge_id: null | string = $state(null);
let show_qr_manual_entry: null | boolean = $state(null);
let disable_submit_badge_id_btn: boolean = $state(true);
let search_query_str = $state('');
function handle_oninput_search_query_str(e: Event) {
search_query_str = (e.target as HTMLInputElement).value;
}
let user_media_status = 'not_requested';
let debug_comment: string = 'Debugging QR Scanner';
let debug_info: any;
let html5_qr_code: any | null | string = null;
// let qr_scan_cfg = { fps: 10, qrbox: 400 }; // default was 250 and using 300 when 600px
let qr_scan_cfg = $derived({ fps: qr_fps, qrbox: qr_viewfinder_width }); // 275 seems good... Need to not let the this be larger than the container which changes based on the width of the screen/window.
// const html5QrCode = new Html5Qrcode(
// 'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
// );
onMount(() => {
console.log('** Element Mounted: ** QR Scanner');
// html5_qr_code = new Html5Qrcode(
// 'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
// );
// navigator.mediaDevices.getUserMedia({video: true})
// .then(get_user_media_success, get_user_media_error);
// console.log('** Element Mounted: ** QR Scanner - getUserMedia in setTimeout');
});
onDestroy(async () => {
console.log('** Element Destroyed: ** QR Scanner');
qr_scan_result = null;
qr_found_text = null;
await handle_stop_qr_scanning();
});
var get_user_media_success = function (error: any) {
console.log('Camera access allowed');
user_media_status = 'allowed';
debug_comment = 'Camera Access Allowed';
debug_info = JSON.stringify(error);
if (html5_qr_code) {
console.log('html5_qr_code object found. Clearing and creating new Html5Qrcode...');
debug_info = 'html5_qr_code object found. Clearing and creating new Html5Qrcode...';
html5_qr_code.clear();
// document.getElementById('qr_scanner_viewfinder').classList.remove('d_none');
} else {
console.log('html5_qr_code not found. Creating new Html5Qrcode...');
debug_info = 'html5_qr_code not found. Creating new Html5Qrcode...';
html5_qr_code = new Html5Qrcode('qr_scanner_viewfinder', {
formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE],
verbose: false
});
}
// html5_qr_code = new Html5Qrcode(
// 'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
// );
debug_info = 'new Html5Qrcode for element id=qr_scanner_viewfinder';
// Intentional comparison used to disable this block during development/testing
if (start_qr_scanner && (1 as any) == 2) {
console.log('Ready to start QR scanning! (after x500ms)');
debug_comment = 'Starting QR after 500ms...';
debug_info = 'Ready to start QR scanning! (after 2500ms)';
setTimeout(() => {
console.log('Inside QR scanning timeout after x500ms...');
handle_start_qr_scanning_trust();
}, 2500);
console.log('Started QR scanning after x500ms...');
}
// let subject = 'Camera Access Allowed';
// let message = error;
// send_init_confirm_email(subject, message);
console.log('Dispatching qr_camera');
debug_info = 'Dispatching qr_camera';
dispatch('qr_camera', {
status: 'allowed'
});
};
var get_user_media_error = function (error: any) {
if (error.name == 'NotAllowedError') {
console.log('Camera access not allowed!');
user_media_status = 'denied';
debug_comment = 'Camera Access Denied';
debug_info = JSON.stringify(error);
// alert('Error trying to start camera');
// alert(error);
let subject = 'Camera Access Denied';
let message = error;
send_init_confirm_email(subject, message);
// dispatch('qr_camera', {
// status: 'denied',
// });
}
};
// $: if (start_qr_scanner && user_media_status == 'allowed' && (scanning_status == 'not_started' || scanning_status == 'paused')) {
// console.log('START QR SCANNING');
// handle_start_qr_scanning();
// } else {
// // console.log('STOP QR SCANNING');
// // handle_stop_qr_scanning();
// }
// $: if (mounted && start_qr_scanner) {
// console.log('START QR SCANNING');
// handle_start_qr_scanning();
// } else if (mounted && !start_qr_scanner) {
// console.log('STOP QR SCANNING');
// handle_stop_qr_scanning();
// }
async function handle_start_qr_scanning_trust() {
console.log('*** handle_start_qr_scanning_trust() ***');
qr_scan_result = null;
qr_found_text = null;
debug_comment = 'Starting trusting QR scanning...';
debug_info = 'Just start!';
await html5_qr_code.clear();
html5_qr_code
.start(
{ facingMode: qr_facing_mode },
qr_scan_cfg,
handle_qr_scan_success,
handle_qr_scan_error
)
.then((ignore: any) => {
console.log('Scanning has started');
scanning_status = 'scanning';
debug_info = 'Scanning has started';
// let subject = 'QR Scanning Started';
// let message = ignore;
// send_init_confirm_email(subject, message);
return true;
})
.catch((err: any) => {
console.log('There was an error while trying to start the QR scanner');
scanning_status = 'start_error';
debug_info = 'Error starting scanner: ' + JSON.stringify(err);
// let subject = 'QR Scanning Start Error';
// let message = err;
// send_init_confirm_email(subject, message);
// Error getting userMedia, error = NotReadableError: Could not start video source
return false;
});
return true;
}
async function handle_stop_qr_scanning() {
start_qr_scanner = false;
if (!html5_qr_code) {
console.log('html5_qr_code object found. Nothing to stop?');
scanning_status = 'not_started';
return false;
}
await html5_qr_code.stop();
scanning_status = 'not_started';
await html5_qr_code.clear();
return true;
}
// Callback function for QrcodeSuccessCallback (decodedText: string, result: Html5QrcodeResult)
function handle_qr_scan_success(decoded_text: string, decoded_result: any) {
console.log(
`*** handle_qr_scan_success() *** QR scanned = ${decoded_text}`,
decoded_result
);
qr_scan_result = decoded_text; // NOTE: decoded_result is not currently used by html5-qrcode
qr_found_text = decoded_text;
dispatch('qr_scan_result', {
result: qr_scan_result, // This text will need to be parsed to get more info.
text: qr_found_text, // This text will need to be parsed to get more info.
entry_method: 'QR'
});
// handle_pause_qr_scanning();
handle_stop_qr_scanning();
}
// Callback function for QrcodeErrorCallback (errorMessage: string, error: Html5QrcodeError)
// NOTE: Most of the time this is normal and not an actual error. It just did not find something to scan.
function handle_qr_scan_error(qr_error_message: string, qr_code_error: any) {
// console.log('*** handle_qr_scan_error() ***');
if (qr_code_error.type) {
console.log(`Error scanning code = ${qr_error_message}`, qr_code_error);
return;
}
}
run(() => {
if (
qr_entered_badge_id &&
qr_entered_badge_id.length >= 11 &&
qr_entered_badge_id &&
qr_entered_badge_id.length <= 14
) {
disable_submit_badge_id_btn = false;
} else {
disable_submit_badge_id_btn = true;
}
});
function handle_qr_manual_entry() {
console.log('*** handle_qr_manual_entry() ***');
if (qr_entered_text) {
console.log(`QR entered text = ${qr_entered_text}`);
} else if (qr_entered_badge_id) {
console.log(`QR entered badge ID = ${qr_entered_badge_id}`);
qr_entered_text = `OBJ:ot:event_badge,oi:${qr_entered_badge_id}`;
console.log(`Parse to proper QR badge ID = ${qr_entered_text}`);
}
// html5_qr_code.stop().then((ignore) => {
// console.log('Scanning has stopped');
// document.getElementById('qr_scanner_viewfinder').classList.add('d_none');
// }).catch((err) => {
// console.log('There was an error while trying to stop the scanning');
// });
qr_scan_result = qr_entered_text;
dispatch('qr_scan_result', {
result: qr_scan_result,
entry_method: 'manual'
});
qr_scan_result = null;
qr_entered_text = null;
}
function send_init_confirm_email(subject: string, message: any) {
console.log(`*** send_init_confirm_email() *** ${subject}`);
let to_email = 'scott.idem+skdev@oneskyit.com';
// let origin_url = encodeURI(`${data.url.origin}`);
let full_subject = `${subject} Aether QR Scanner Debugging`;
let body_html = `
<div>Scott,
<p>This is an automatic debug email from the Aether QR scanner.</p>
</div>
<br>
<div>
Message:<br>
<pre>
${JSON.stringify(message)}
</pre>
</div>
`;
api.send_email({
api_cfg: $ae_api,
from_email: 'noreply+ae_qr_debug@oneskyit.com',
from_name: 'AE QR Debug',
to_email: to_email,
subject: full_subject,
body_html: body_html
});
}
</script>
<section
class="ae_element qr_scanner border-2 border-slate-500/10 space-y-2 flex flex-col gap-1 justify-center items-center min-w-full max-w-full"
class:not_started={scanning_status == 'not_started'}
class:paused={scanning_status == 'paused'}
class:scanning={scanning_status == 'scanning'}
>
<!-- <header>
<h2>QR Scanner</h2>
</header> -->
<!-- <fieldset class=""> -->
<!-- <legend class="d_none">QR Scanner:</legend> -->
<div class="ae_container qr_scanning_container">
<div class="ae_options flex flex-row justify-center items-center gap-1 m-1">
<button
type="button"
onclick={() => {
navigator.mediaDevices
.getUserMedia({ video: true })
.then(get_user_media_success, get_user_media_error);
}}
class="ae_btn__allow_camera btn btn-sm preset-tonal-primary"
>
<Camera size="1em" class="mx-1 inline-block" />
Allow Camera Access
</button>
<button
type="button"
onclick={() => {
handle_start_qr_scanning_trust();
// Select back camera or fail with `OverconstrainedError`.
// html5_qr_code.start({ facingMode: { exact: "environment"} }, config, qrCodeSuccessCallback);
}}
class="ae_btn__start btn btn-sm preset-tonal-primary"
>
<QrCode size="1em" class="mx-1 inline-block" />
Start Scanning
</button>
<!-- <button
on:click={ () => {
html5_qr_code.start({ facingMode: { exact: "environment"} }, config, qrCodeSuccessCallback);
}}
class="ae_btn__resume btn btn-sm preset-tonal-primary">
<span class="fas fa-play"></span>
Resume
</button> -->
{#if scanning_status == 'scanning'}
<button
onclick={handle_stop_qr_scanning}
class="ae_btn__stop btn btn-sm preset-tonal-secondary"
>
<Crosshair size="1em" class="animate-spin opacity-50 m-1" />
<!-- <span class="fas fa-stop-circle m-1"></span> -->
Stop
</button>
{/if}
</div>
<div
id="qr_scanner_viewfinder"
class="qr_scanner_viewfinder grow flex flex-col justify-center items-center"
style=""
></div>
<!-- width: 600px -->
</div>
{#if show_qr_manual_text_entry_option}
<div class="ae_container qr_manual_entry text_entry">
{#if show_qr_manual_entry}
<label for="entered_text" class="">Enter text</label>
<input
type="text"
name="entered_text"
id="entered_text"
bind:value={qr_entered_text}
/>
<button onclick={handle_qr_manual_entry} class="btn btn-md preset-tonal-warning"
><Send size="1em" class="inline-block" /> Submit Text</button
>
<div class="search_by_text">
<input
type="text"
placeholder="Name or Email"
aria-label="Name or Email"
value={search_query_str}
oninput={handle_oninput_search_query_str}
/>
</div>
{:else}
<button
onclick={() => {
handle_stop_qr_scanning();
show_qr_manual_entry = true;
}}
class="btn btn-md preset-tonal-warning m-1"
>
<Keyboard size="1em" class="mx-1 inline-block" /> Enter Text
</button>
{/if}
</div>
{/if}
{#if show_qr_manual_badge_id_entry_option}
<div class="ae_container qr_manual_entry badge_id_entry">
{#if show_qr_manual_entry}
<form onsubmit={preventDefault(() => handle_qr_manual_entry)} class="flex">
<!-- <label for="entered_badge_id" class="">Enter badge ID</label>
<input type="text" name="entered_badge_id" id="entered_badge_id" bind:value="{qr_entered_badge_id}"> -->
<input
bind:value={qr_entered_badge_id}
type="text"
name="entered_badge_id"
id="entered_badge_id"
required
placeholder="Enter Badge ID"
class="input max-w-52"
/>
<button
type="submit"
onclick={handle_qr_manual_entry}
disabled={disable_submit_badge_id_btn}
class="btn btn-md preset-tonal-primary border border-primary-500 m-1"
class:btn_default={disable_submit_badge_id_btn}
class:btn_primary={!disable_submit_badge_id_btn}
>
<Send size="1em" class="mx-1 inline-block" /> Submit Badge ID
</button>
</form>
{:else}
<button
onclick={() => {
handle_stop_qr_scanning();
show_qr_manual_entry = true;
}}
class="btn btn-md preset-tonal-secondary m-1"
>
<Keyboard size="1em" class="mx-1 inline-block" /> Enter Badge ID
</button>
{/if}
</div>
{/if}
{#if show_qr_scan_result && qr_scan_result}
<div class="ae_container qr_scan_result">
<span class="label">Raw Result:</span>
<span id="qr_scan_result_value" class="value">{qr_scan_result}</span>
</div>
{/if}
<!-- {debug_comment ?? 'Debugging QR Scanner'}
{#if debug_info}
<div class="ae_container debug_info">
<span class="label">Debug Info:</span>
<span class="value">{debug_info}</span>
</div>
{/if} -->
<!-- </fieldset> -->
<p>
v2 - Try pressing the "Allow Camera Access" button and then the "Start Scanning" button if
it does not start on its own. This fix is not perfect. A permanent solution is actively
being worked on in the development version.
</p>
</section>
<style>
.not_started {
background-color: hsla(0, 100%, 75%, 0.3);
border-color: hsla(0, 100%, 75%, 0.6);
}
.paused {
background-color: hsla(60, 100%, 75%, 0.3);
border-color: hsla(60, 100%, 75%, 0.6);
}
.scanning {
background-color: hsla(120, 100%, 75%, 0.3);
border-color: hsla(120, 100%, 75%, 0.6);
}
.qr_scanner {
/* outline: solid thin pink; */
max-width: 100vw;
/* overflow-x: scroll; */
display: flex;
flex-direction: column;
/* flex-wrap: wrap; */
justify-content: flex-start;
align-items: center; /* center */
align-content: stretch;
}
.qr_scanner .qr_scanner_viewfinder {
/* outline: dashed medium blue; */
min-width: 400px;
width: 100%;
/* max-width: 100%; */
max-width: 500px;
/* max-width: 100vw; */
/* outline: solid thin red; */
contain: contain;
overflow-x: scroll;
}
@media (max-width: 767px) {
.qr_scanner .qr_scanner_viewfinder {
/* outline: dashed medium red; */
min-width: 80vw;
/* width: 100%; */
/* max-width: 100%; */
/* max-width: 450px; */
max-width: 100vw;
margin: 0;
padding: 0;
}
}
</style>

View File

@@ -1,205 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
interface Props {
open?: boolean;
title?: string;
autoclose?: boolean;
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
placement?: 'top-center' | 'center' | 'bottom-center';
class_li?: string; // Additional classes for the dialog
header?: import('svelte').Snippet;
children?: import('svelte').Snippet;
footer?: import('svelte').Snippet;
}
let {
open = $bindable(false),
title = '',
autoclose = true,
size = 'md',
placement = 'center',
class_li = '',
header,
children,
footer
}: Props = $props();
let dialog_element: HTMLDialogElement;
// Open/close dialog reactively
$effect(() => {
if (dialog_element) {
if (open) {
dialog_element.showModal();
} else {
dialog_element.close();
}
}
});
onMount(() => {
// Handle backdrop click to close (if autoclose is true)
dialog_element.addEventListener('click', (event) => {
if (autoclose && event.target === dialog_element) {
open = false;
}
});
// Handle Escape key to close
const handle_keydown = (event: KeyboardEvent) => {
if (event.key === 'Escape' && open) {
event.preventDefault(); // Prevent default browser escape behavior (e.g., page back)
open = false;
}
};
window.addEventListener('keydown', handle_keydown);
onDestroy(() => {
window.removeEventListener('keydown', handle_keydown);
});
});
// Determine max-width based on size prop
let max_width_class = $derived(
size === 'sm'
? 'max-w-sm'
: size === 'md'
? 'max-w-md'
: size === 'lg'
? 'max-w-lg'
: size === 'xl'
? 'max-w-xl'
: size === '2xl'
? 'max-w-2xl'
: size === '3xl'
? 'max-w-3xl'
: size === '4xl'
? 'max-w-4xl'
: size === '5xl'
? 'max-w-5xl'
: size === '6xl'
? 'max-w-6xl'
: size === '7xl'
? 'max-w-7xl'
: 'max-w-md'
);
// Determine placement classes
let placement_class = $derived(
placement === 'top-center'
? 'justify-center items-start pt-[5vh]'
: placement === 'center'
? 'justify-center items-center'
: placement === 'bottom-center'
? 'justify-center items-end pb-[5vh]'
: 'justify-center items-center' // Default to center
);
</script>
<dialog
bind:this={dialog_element}
class="
p-0 bg-transparent overflow-visible
backdrop:bg-black/50 backdrop:backdrop-blur-sm
"
onclose={() => (open = false)}
>
<div
class="
bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg shadow-xl
flex flex-col
mx-auto
{max_width_class}
w-full
{class_li}
"
>
<!-- Modal Header -->
{#if title || header}
<header
class="flex items-center justify-between border-b border-gray-200 p-4 dark:border-gray-700"
>
{#if header}
{@render header()}
{:else}
<h3 class="text-xl font-semibold">{title}</h3>
{/if}
<button
onclick={() => (open = false)}
class="rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700"
title="Close modal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</header>
{/if}
<!-- Modal Body -->
<main class="max-h-[70vh] overflow-y-auto p-4">
{#if children}
{@render children()}
{/if}
</main>
<!-- Modal Footer -->
{#if footer}
<footer class="border-t border-gray-200 p-4 dark:border-gray-700">
{@render footer()}
</footer>
{/if}
</div>
</dialog>
<style lang="postcss">
dialog {
display: flex; /* Override default to allow flexbox positioning */
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
}
dialog[open] {
opacity: 0;
animation: fade-in 0.15s forwards ease-out;
}
dialog:not([open]) {
opacity: 1;
animation: fade-out 0.15s forwards ease-out;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
pointer-events: none; /* Disable interaction while fading out */
}
}
</style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
/**
* src/lib/element_qr_scanner_v3.svelte
* src/lib/elements/element_qr_scanner_v3.svelte
* QR Scanner v3 — Svelte 5 runes, auto-starts, no manual permission step.
*
* html5-qrcode's .start() handles camera permission internally.

View File

@@ -1,597 +0,0 @@
<script lang="ts">
interface Props {
log_lvl?: number;
ws_connect?: boolean; // If true then we should be trying to connect to the WS server.
ws_connect_status?: null | string;
ws_server?: string;
ws_retry_delay?: number;
ws_retry_count?: number;
base_url?: any;
group_id?: string;
client_id?: any;
cmd?: null | string;
msg?: null | string;
type?: null | string; // msg, cmd, json, hello, bye
trigger_send?: any;
trigger_connect?: boolean;
trigger_disconnect?: boolean;
classes?: string;
hide__ws_element?: boolean;
hide__ws_form?: boolean;
hide__ws_messages?: boolean;
hide__ws_commands?: boolean;
ws_conn_status?: any;
ws_recv_status?: any;
ws_sent_status?: any;
}
let {
log_lvl = 0,
ws_connect = $bindable(false),
ws_connect_status = $bindable(null),
ws_server = 'dev-api.oneskyit.com',
ws_retry_delay = 3500,
ws_retry_count = 0,
base_url = `wss://${ws_server}/ws`,
group_id = $bindable('ae-grp-99'),
client_id = $bindable(Date.now()),
cmd = $bindable(null),
msg = $bindable(null),
type = null,
trigger_send = $bindable(null),
trigger_connect = $bindable(false),
trigger_disconnect = $bindable(false),
classes = 'container p-1 bg-pink-100 text-xs mx-auto pb-16 mb-20 sm:mb-12 md:mb-8',
hide__ws_element = $bindable(false),
hide__ws_form = $bindable(true),
hide__ws_messages = $bindable(false),
hide__ws_commands = $bindable(false),
ws_conn_status = $bindable(null),
ws_recv_status = $bindable(null),
ws_sent_status = $bindable(null)
}: Props = $props();
// import { run, prevent_default } from 'svelte/legacy';
// import { createEventDispatcher, onMount } from 'svelte';
// *** Set initial variables
// const dispatch = createEventDispatcher();
// JSON formatted data
let ws_data: {
client_id: string | null;
target: string;
type: string;
msg: string | null;
cmd: string | null;
} = $state({
client_id: null, // The device or browser ID if available.
// 'src': null, // Sending client
// 'account_id': null, // Essentially the person ID or user ID if available.
// 'dest': null, // Destination client
target: 'echo', // echo, dm (direct), grp (group), all (broadcast)
type: 'cmd', // msg, cmd, json, hello, bye
// 'grp': null, // Destination group
msg: null, // Message string
cmd: null // Command string
// 'data': null,
// 'b64': null,
});
let ws_received_list_cmd: any[] = $state([]);
let ws_received_list_other: any[] = $state([]);
let ws_received_list_msg: string[] = [];
// onMount(async () => {
// console.log('** Component Mounted: ** Element Websocket v2');
// });
// *** Functions and Logic
function ws_connect_group_id({ group_id, client_id }: { group_id: string, client_id: any }) {
if (!group_id) {
group_id = 'ae-grp-99';
console.log(
`WS: No group_id specified! Setting to default: ${group_id}`
);
return false;
}
if (!client_id) {
client_id = Date.now();
console.log(
`WS: No client_id specified! Setting to current timestamp: ${client_id}`
);
// return false;
}
console.log(
`WS Connect URL: ${base_url}/group/${group_id}/client/${client_id}`
);
let ws_connection = new WebSocket(
`${base_url}/group/${group_id}/client/${client_id}`
);
ws_connection.onopen = function () {
console.log('WS: connected');
ws_connect_status = 'connected';
// dispatch('ws_conn', {
// 'status': 'connected'
// });
ws_conn_status = 'connected';
ws_retry_count = 0;
// ws_connection.send(JSON.stringify({
// client_id: client_id,
// target: 'echo',
// type: 'hello',
// group_id: group_id,
// msg: 'You are connected!'
// }));
ws_connection.send(
JSON.stringify({
client_id: client_id,
target: 'all',
type: 'hello',
group_id: group_id,
msg: `Client ${client_id.toString().slice(-5)} connected!`
})
);
};
ws_connection.onmessage = function (event) {
if (log_lvl) {
console.log('WS: message received', event);
}
let ws_recv_data = JSON.parse(event.data);
if (log_lvl) {
console.log('WS: Received data:', ws_recv_data);
}
if (client_id == ws_recv_data.client_id) {
if (log_lvl) {
console.log(
'WS: Message received was sent by self and is an echo that can be ignored.'
);
}
return false;
}
if (ws_recv_data.type == 'cmd') {
console.log(`WS: Type CMD: ${ws_recv_data.cmd}`);
ws_received_list_cmd.unshift(ws_recv_data); // Add to the beginning of the list
// ws_received_list_cmd.push(ws_recv_data); // Add to the end of the list
ws_received_list_cmd = ws_received_list_cmd; // trigger Svelte update -2024-10-04
} else {
console.log('WS: Type other');
ws_received_list_other.unshift(ws_recv_data); // Add to the beginning of the list
// ws_received_list_other.push(ws_recv_data); // Add to the end of the list
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
}
// dispatch('ws_recv', {
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
// 'src': ws_recv_data.client_id, // The device or browser ID if available.
// // 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
// 'dest': group_id, // Destination client
// 'target': ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
// 'type': ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
// // 'grp': ws_recv_data.grp, // Destination group
// 'msg': ws_recv_data.msg, // Message string
// 'cmd': ws_recv_data.cmd, // Command string
// });
ws_recv_status = {
// 'client_id': ws_data.client_id, // The device or browser ID if available.
src: ws_recv_data.client_id, // The device or browser ID if available.
// 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
dest: group_id, // Destination client
target: ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
type: ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
// 'grp': ws_recv_data.grp, // Destination group
msg: ws_recv_data.msg, // Message string
cmd: ws_recv_data.cmd // Command string
};
};
ws_connection.onclose = function (event) {
console.log('WS: connection closed');
ws_connection.send(
JSON.stringify({
client_id: client_id,
target: 'all',
type: 'hello',
group_id: group_id,
msg: `Client ${client_id} is disconnecting!`
})
);
ws_connect_status = 'disconnected';
let fake_ws_recv_data = {
client_id: client_id,
target: 'local',
type: 'bye',
group_id: group_id,
msg: `LOCAL Client ${client_id} has disconnected!`
};
ws_received_list_other.unshift(fake_ws_recv_data);
ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
// dispatch('ws_conn', {
// 'status': 'disconnected'
// });
ws_conn_status = 'disconnected';
if (ws_connect) {
if (ws_retry_count >= 10) {
ws_retry_delay = ws_retry_delay += 4999; // Set to a very long time
ws_retry_delay = Math.min(ws_retry_delay, 120000); // Max of 2 minutes
console.log(
`WS: Retry count exceeded. Increasing the delay. ws_retry_count=${ws_retry_count} ws_retry_delay=${ws_retry_delay}`
);
}
setTimeout(function () {
console.log('WS: Disconnected... Try again!');
ws_retry_count += 1;
ws_connect_group_id({
group_id: group_id,
client_id: client_id
});
console.log('WS: Again done?');
}, ws_retry_delay);
}
};
ws_connection.onerror = function (event) {
console.log('WS: connection error???');
ws_connection.send(
JSON.stringify({
client_id: client_id,
target: 'all',
type: 'hello',
group_id: group_id,
msg: `Client ${client_id} is having trouble?!`
})
);
};
// NOTE WARNING: Uncommenting this seems to break FastAPI somehow???
// NOTE: from FastAPI log: RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close'.
// ws_connection.onerror = function(err) {
// console.error('WS socket error: ', err.message, 'Closing socket');
// // ws_connection.close();
// };
return ws_connection;
}
// Start the WS function
let ws_group: any = $state(null);
function handle_send_ws_data() {
console.log(ws_data);
if (!ws_data) {
return false;
}
if (!ws_group) {
console.log('WS: No connection!');
return false;
}
let ws_data_json_str = JSON.stringify(ws_data);
let resp = ws_group.send(ws_data_json_str);
console.log(`WS: Send data response:`, resp);
// dispatch('ws_sent', {
// // 'client_id': ws_data.client_id, // The device or browser ID if available.
// 'src': ws_data.client_id, // The device or browser ID if available.
// // 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
// 'dest': group_id, // Destination client
// 'group_id': group_id,
// 'target': ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
// 'type': ws_data.type, // Message type (msg, cmd, json, hello, bye)
// // 'grp': ws_data.grp, // Destination group
// 'msg': ws_data.msg, // Message string
// 'cmd': ws_data.cmd, // Command string
// });
ws_sent_status = {
// 'client_id': ws_data.client_id, // The device or browser ID if available.
src: ws_data.client_id, // The device or browser ID if available.
// 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
dest: group_id, // Destination client
group_id: group_id,
target: ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
type: ws_data.type, // Message type (msg, cmd, json, hello, bye)
// 'grp': ws_data.grp, // Destination group
msg: ws_data.msg, // Message string
cmd: ws_data.cmd // Command string
};
cmd = '';
msg = '';
}
$effect(() => {
if (ws_connect && group_id) {
console.log('HERE!!!!!');
ws_group = ws_connect_group_id({
group_id: group_id,
client_id: client_id
});
// } else if (!ws_connect) {
// console.log('HERE!!!!!');
// log_lvl = 1;
// if (log_lvl) {
// console.log(`WS: WS not set to connect. Need to close WS Group connection.`);
// }
// ws_group?.close();
// ws_connect_status = 'disconnected';
} else {
console.log('HERE!!!!!');
log_lvl = 1;
if (log_lvl) {
console.log(
`WS: Not connecting. ws_connect=${ws_connect} group_id=${group_id}`
);
}
ws_group?.close();
ws_connect_status = 'disconnected';
}
});
$effect(() => {
if (trigger_send && cmd) {
trigger_send = null;
console.log('WS: Send triggered!');
console.log(cmd);
ws_data.target = 'group';
ws_data.type = 'cmd';
ws_data.cmd = cmd;
handle_send_ws_data();
cmd = '';
}
});
$effect(() => {
if (trigger_connect) {
trigger_connect = false;
if (!ws_connect) {
ws_connect = true;
}
console.log('WS: Connect triggered!');
}
});
$effect(() => {
if (trigger_disconnect) {
console.log('WS: Disconnect triggered!');
trigger_disconnect = false;
if (ws_connect) {
ws_connect = false;
}
if (ws_group) {
ws_group.close();
ws_group = null;
ws_connect_status = 'disconnected';
}
}
});
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.preventDefault();
fn(event);
};
}
</script>
<section
class:hidden={!ws_connect || hide__ws_element}
class="ae_element__websocket container p-1 bg-pink-100 text-xs mx-auto pb-16 mt-32 mb-32 relative"
>
<span class="absolute top-0 right-0 flex flex-col gap-1">
<button
type="button"
onclick={() => {
hide__ws_form = !hide__ws_form;
}}
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
class:preset-tonal-tertiary={hide__ws_form}
class:preset-filled-tertiary-500={!hide__ws_form}
>
{#if hide__ws_form}
Show Form
{:else}
Hide Form?
{/if}
</button>
<button
type="button"
onclick={() => {
hide__ws_messages = !hide__ws_messages;
}}
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
class:preset-tonal-tertiary={hide__ws_messages}
class:preset-filled-tertiary-500={!hide__ws_messages}
>
{#if hide__ws_messages}
Show Messages
{:else}
Hide Messages?
{/if}
</button>
<button
type="button"
onclick={() => {
hide__ws_commands = !hide__ws_commands;
}}
class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
class:preset-tonal-tertiary={hide__ws_commands}
class:preset-filled-tertiary-500={!hide__ws_commands}
>
{#if hide__ws_commands}
Show Commands
{:else}
Hide Commands?
{/if}
</button>
</span>
<header>
<h1 class="h6 text-center">Websocket Messages & Commands</h1>
</header>
<!-- <form on:submit|prevent_default={handle_send_message}>
<select bind:value={type}>
<option value="">None</option>
<option value="echo">Echo</option>
<option value="dm">Direct Message</option>
<option value="group">Group Message</option>
<option value="all">Broadcast to All</option>
<option value="cmd">Command</option>
</select>
<select bind:value={group_id}>
<option value="">None</option>
<option value="test_grp_123">123</option>
<option value="test_grp_999">999</option>
<option value="test_grp_poster">A Poster Group</option>
</select>
<input type="text" bind:value={message_text} placeholder="Your message"/>
<button>Send</button>
</form> -->
{#if !hide__ws_form}
<form onsubmit={prevent_default(handle_send_ws_data)}>
<select bind:value={ws_data.type} class="input text-sm w-24">
<option value="">None</option>
<option value="echo">Echo</option>
<option value="dm">Direct Message</option>
<option value="group">Group Message</option>
<option value="all">Broadcast to All</option>
<option value="cmd">Command</option>
</select>
<input
type="text"
bind:value={group_id}
placeholder="Group ID"
class="input text-sm w-36"
/>
<!-- <select bind:value={group_id}>
<option value="">None</option>
<option value="test_grp_123">123</option>
<option value="test_grp_999">999</option>
<option value="test_grp_poster">A Poster Group</option>
</select> -->
<!-- <input type="text" bind:value={dm_client_id} placeholder="Direct message client ID"/> -->
<input
type="text"
bind:value={ws_data.cmd}
placeholder="Your command"
class="input text-sm w-36"
/>
<input
type="text"
bind:value={ws_data.msg}
placeholder="Your message"
class="input text-xs w-96"
/>
<button type="submit" class="btn btn-sm preset-tonal-warning"
>Send Group</button
>
</form>
{/if}
<!-- <pre>
ae_load:event_session=jEG9APQRUs8 (Poster Session #3: Work Never Ends - Pythagoras)
ae_open:event_file=CHqU5sW7xbc (jpg)
ae_open:event_file=Kljq0uiTlXt (video)
</pre> -->
<!-- <hr> -->
<section class:hidden={hide__ws_messages}>
<h2 class="text-center underline">
Messages [grp, client, target, type]
</h2>
<ol class="list-decimal list-outside max-h-24 overflow-y-auto messages">
{#each ws_received_list_other as msg_entry, index (index)}
<li class="ml-4">
<div class="flex flex-row justify-between gap-1 w-full">
<span>
[{msg_entry.group_id || 'No Group ID'}]
{msg_entry.client_id.toString().slice(-5) ||
'No Client ID'}
{msg_entry.target || 'No Target'}
|
<!-- &ndash; -->
{msg_entry.type || 'No Type'}:
</span>
<span class="justify-self-end">
"{msg_entry.msg}"
</span>
<!-- <br>{JSON.stringify(msg_entry)} -->
</div>
</li>
{/each}
</ol>
</section>
<!-- End Messages -->
<hr />
<section class:hidden={hide__ws_commands}>
<h2 class="text-center underline">Commands</h2>
<ol class="list-decimal list-outside max-h-24 overflow-y-auto commands">
{#each ws_received_list_cmd as cmd_entry, index (index)}
<li class="ml-4">
<div class="flex flex-row justify-between gap-1 w-full">
<span>
[{cmd_entry.group_id || 'No Group ID'}]
{cmd_entry.client_id.toString().slice(-5) ||
'No Client ID'}
{cmd_entry.target || 'No Target'}
|
<!-- &mdash; -->
{cmd_entry.type || 'No Type'}:
</span>
<span class="justify-self-end">
"{cmd_entry.cmd}"
</span>
</div>
</li>
{/each}
</ol>
</section>
<!-- End Commands -->
</section>
<style>
/* .websocket_element {
background-color: white;
border-top: dashed medium gray;
color: gray;
font-size: .7em;
}
.websocket_element header {
font-size: .7em;
}
.websocket_element h2 {
font-size: .9em;
} */
</style>

View File

@@ -10,7 +10,7 @@
import { Library, LoaderCircle, QrCode, RemoveFormatting, Search } from '@lucide/svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
import Element_qr_scanner_v3 from '$lib/element_qr_scanner_v3.svelte';
import Element_qr_scanner_v3 from '$lib/elements/element_qr_scanner_v3.svelte';
import { ae_util } from '$lib/ae_utils/ae_utils';
// ISHLT 2024 badge type codes

View File

@@ -14,7 +14,7 @@
import { ae_api } from '$lib/stores/ae_stores';
import { events_loc } from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
import Element_qr_scanner_v3 from '$lib/element_qr_scanner_v3.svelte';
import Element_qr_scanner_v3 from '$lib/elements/element_qr_scanner_v3.svelte';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { CircleAlert, CircleCheck, Eye, LoaderCircle, ShieldOff, UserPlus } from '@lucide/svelte';
import type { ae_EventBadge } from '$lib/types/ae_types';