64 Commits

Author SHA1 Message Date
Scott Idem
929f08b656 docs: add IDAA auth test lessons and untrack() reactive tracking guide
tests/README.md — new "IDAA Auth Tests" section with three lessons:
  1. ae_idaa_loc seed must include full bb/archives structure or
     verify_novi_uuid() throws silently and resets novi_uuid to null
  2. StorageEvent pattern for testing reactive persisted-store updates
     without pre-seeding Dexie or navigating twice
  3. getByText { exact: false } for UUID in multi-field spans

GUIDE__SvelteKit2_Svelte5_DexieJS.md — new "untrack() reactive tracking
trap" section: reading a store value inside untrack() makes it a one-shot
dependency; fix is to hoist the read outside untrack() and add a guard
to avoid redundant work on unrelated store updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 19:07:07 -04:00
Scott Idem
48a39b16d5 test(idaa): add Playwright auth tests for Novi UUID verification
Covers 5 scenarios with extensive inline comments explaining business
context and the 2026-03-25 stale-cache root-cause fix:

1. Auth gate (Sev-1 regression guard) — no UUID → Access Denied
2. Happy path — valid UUID + fresh cfg → access granted
3. Invalid UUID — Novi 404 → Access Denied
4. Stale cache — StorageEvent delivers fresh site_cfg_json →
   Effect 2 retries verification without reload (tests the reactive
   tracking fix in (idaa)/+layout.svelte)
5. iframe mode — Reload/Retry button visible on Access Denied

Key lesson found while writing: ae_idaa_loc seed must include the full
bb object or verify_novi_uuid() throws on bb.qry__hidden assignment,
caught silently, resetting novi_uuid to null even after a successful
Novi API call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 19:00:03 -04:00
Scott Idem
ab294c2a0b Sorry. Quick save to make something live before deadline. 2026-03-25 18:31:39 -04:00
Scott Idem
1de563203d fix(idaa): add reload button to Access Denied screen in iframe mode
WHY: Novi UUID verification is async — on first iframe load the API call
may not complete before the access gate renders, leaving the user stuck on
Access Denied with no way to retry without manually reloading the host page.
The Reload/Retry button calls location.reload() to re-trigger verification.
Only shown in iframe mode where the timing race is the known failure path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 18:04:53 -04:00
Scott Idem
0091fe3ff6 Updates to the documentation about the id_random legacy. 2026-03-25 17:43:15 -04:00
Scott Idem
0ad36a74b2 Fix: system bar hide logic for iframe and menu param overrides (IDAA embed reliability) 2026-03-25 15:49:41 -04:00
Scott Idem
fd244720a7 Update to AE API v3 for the hosted file hash check. 2026-03-25 13:17:25 -04:00
Scott Idem
362136e677 fix(upload): update clip_video endpoint to V3 action path
The legacy /hosted_file/{id}/clip_video route was decommissioned with the
rest of the hosted_file router. Updated to /v3/action/hosted_file/{id}/clip_video.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 12:30:23 -04:00
Scott Idem
a5a806e256 fix(upload): update hosted file upload endpoint to V3 action path
The legacy /hosted_file/upload_files router was decommissioned (commented
out in registry.py). Both upload components now point to the active V3
endpoint at /v3/action/hosted_file/upload. Response shape is identical
so no consumer-side changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 12:11:00 -04:00
Scott Idem
613e43114c fix(idaa): correct reactive loop fix + hide clutter in iframe sys bar
1. Replace incorrect untrack() with idempotent write guard in the
   sys_menu trusted-access effect. untrack() prevents new dep reads but
   ae_loc was already tracked from the outer condition reads, so the write
   still re-notified the effect every run. The guard (only write if value
   != false) breaks the cycle: run 2 finds value already false, skips the
   write, effect stops. Max 2 runs vs the previous infinite loop.

2. Hide auth shield, font-size cycler, and dark/light toggle in the sys
   bar when in iframe mode — host page owns those concerns. Edit mode
   toggle and the main expand button remain visible for staff.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:39:24 -04:00
Scott Idem
1c818e648b fix(idaa): break sys_menu reactive loop + restore menu on iframe=false
Two fixes in the IDAA root layout:

1. Add missing `untrack` import and wrap `$ae_loc.sys_menu.hide = false`
   in `untrack()` inside the trusted-access effect. Without this, reading
   $ae_loc.iframe/$ae_loc.trusted_access and then writing back to $ae_loc
   caused an infinite reactive loop → effect_update_depth_exceeded error.
   Only hit by trusted/admin users in iframe mode (regular Novi members
   at authenticated_access were unaffected).

2. When iframe URL param is explicitly set to 'false', restore
   $ae_loc.sys_menu.hide = false. The root layout sets it to true on
   iframe=true but never resets it, leaving the system bar permanently
   hidden after leaving iframe mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:20:44 -04:00
Scott Idem
5cd1d3b7ad feat(idaa): auto-show sys menu for trusted users in iframe mode
Trusted admins embedded in the Novi iframe can't append show_menu=true
to the src URL, so watch trusted_access reactively and unhide the sys
bar automatically when they authenticate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 22:29:01 -04:00
Scott Idem
66f0efb507 fix(store): guard localStorage calls for Node/SSR builds 2026-03-24 16:46:52 -04:00
Scott Idem
a637343544 chore(ci): add Docker BuildKit examples, .dockerignore, CI cache docs; tune vite config 2026-03-24 16:32:45 -04:00
Scott Idem
a8f3c29b9f Last round of prettier: npx prettier --write src/ 2026-03-24 13:27:40 -04:00
Scott Idem
23d25bf65a Prettier for everything else left 2026-03-24 12:28:28 -04:00
Scott Idem
12a9472064 Prettier for IDAA pages only 2026-03-24 12:28:07 -04:00
Scott Idem
b74c6d0e9c Prettier for Journals 2026-03-24 12:25:22 -04:00
Scott Idem
e1338b1a72 Other areas of the AE SvelteKit primary routes. 2026-03-24 12:18:27 -04:00
Scott Idem
6018a94499 Prettier for Events as a whole. Everything else under that primary directory. 2026-03-24 12:16:44 -04:00
Scott Idem
6e67534454 Prettier for Event ID 2026-03-24 12:16:11 -04:00
Scott Idem
693486bac9 Prettier for Event Pres Mgmt 2026-03-24 12:15:01 -04:00
Scott Idem
6d1d1e2658 Prettier for Event Exhibitor Leads 2026-03-24 12:14:30 -04:00
Scott Idem
7f6e286b73 Prettier for Event Launcher 2026-03-24 12:13:59 -04:00
Scott Idem
a3ed379b17 Prettier for Event Badges 2026-03-24 12:13:37 -04:00
Scott Idem
e9379be5a1 Now even prettier with the new Tailwind CSS plugin. Probably should have done this long ago... 2026-03-24 12:11:25 -04:00
Scott Idem
9a75243d9c Making the code easier to read and more consistent. 2026-03-24 12:05:22 -04:00
Scott Idem
94849137f0 I think pretty much all references to v1 and v2 have been removed. All files have been renamed from _v3 to just the function/var name with out the appended version. Assume no _vX is the current version. 2026-03-24 11:32:06 -04:00
Scott Idem
512e5ef87c Saving more code clean up and removal 2026-03-24 11:15:01 -04:00
Scott Idem
d27ec58fe9 More code clean up 2026-03-24 10:56:31 -04:00
Scott Idem
42358efe7d More code clean up 2026-03-24 10:54:40 -04:00
Scott Idem
8e61bd0ba1 More and more code removal and clean up 2026-03-24 10:42:40 -04:00
Scott Idem
0bc71391fc Cleaning up and removing old legacy code and files 2026-03-24 10:28:54 -04:00
Scott Idem
6e22639e6e fix(api): pass real account_id for lookup requests instead of bypass header
The x-no-account-id bypass was hardcoded to resolve account_id=1 on the
backend, causing account-scoped lookup overrides (e.g. custom country names)
to leak to all callers regardless of their account.

Removing the bypass lets get_object auto-promote the real account_id from
api_cfg, so the backend's existing account filter works correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 20:00:28 -04:00
Scott Idem
a6f8ff709e fix(idaa): fix country/subdivision/timezone dropdowns — switch to in-memory sort
- Country and state/province fields were showing as plain text inputs because
  liveQuery used orderBy() on non-indexed columns, causing silent Dexie errors
  that left the store as undefined indefinitely.
- Fix: replaced orderBy() with toArray() + in-memory sort across all three
  lookup types (country, country_subdivision, time_zone).
- Sort convention matches Aether backend: sort DESC (higher = first, NULL=0
  last), then name ASC — puts priority entries at the top.
- Added db_lookups.ts (IDB schema for lookup tables) and updated core__countries,
  core__country_subdivisions, core__time_zones to IDB-backed SWR pattern.
- Affected: archive edit, archive content edit, recovery meeting edit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:44:24 -04:00
Scott Idem
dafe79b3c6 ui(idaa): keep required asterisk inline with label text (embed in inline flex) 2026-03-23 18:23:24 -04:00
Scott Idem
a4927d37bd Updated documentation 2026-03-23 18:01:34 -04:00
Scott Idem
f3ab1c1050 fix(idaa/recovery_meetings): fix weekday chips, recurring fields, and timezone lookup
- Weekday chips: replace bind:checked (unreliable with dynamic bracket notation in
  {#each}) with explicit onchange handlers + class: directives; read weekdays from
  state in submit handler instead of FormData
- Recurring pattern/times: bind select and time inputs to working copy
  so values display and edit correctly
- Times clearing: map empty string to null so times can be cleared once set
- liveQuery guard: skip event_obj sync while edit form is open to prevent
  background refresh from overwriting in-progress user changes
- Timezone lookup: forward order_by_li, limit, offset through the full call chain
  so priority sort and result count params are actually sent to the API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:05:16 -04:00
Scott Idem
5bed167829 Bug fix for saving Zoom info. Removed more old commented out references to on: Svelte 4 code. 2026-03-23 14:27:46 -04:00
Scott Idem
a14320d9ed idaa(recovery_meetings): sanitize Zoom encrypted passcode to avoid saving literal 'null' and normalize related fields 2026-03-23 14:23:29 -04:00
Scott Idem
de8a016bda Minor bug fix to render some icons in HTML text. :-) 2026-03-20 19:25:03 -04:00
Scott Idem
1c8997bd4f docs: update Exhibitor Leads module doc — confirm modes, re-enable, capture identity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 19:16:43 -04:00
Scott Idem
fe23899479 feat: leads QR UX — merged confirm modes, faster scanning, correct capture identity
- Merge Rapid + Qualify scan modes into single Confirm mode with two-button card:
  "Add & Scan Next" (resets) and "Add & View Lead" (navigates to detail). Same
  two-button pattern on the reenable card: "Restore & Scan Next" / "Restore & View Lead".
  Stale 'qualify' localStorage values normalized to 'rapid' via $derived.by().
- QR scanner speed: fps 10→25, qrbox 82%→88%, useBarCodeDetectorIfSupported (native
  BarcodeDetector API on Chrome/Edge — significantly faster than ZXing JS fallback)
- Fix capture identity stored in external_person_id / group:
  licensed exhibit user → their email; shared passcode → 'shared_passcode' label
  (not the raw passcode); Aether user bypassing exhibit sign-in → access_type string
  ('trusted', 'manager', 'super', etc.). Consistent across all three lead capture
  components (single scanner, multi scanner, manual search).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 19:15:35 -04:00
Scott Idem
6662e82f40 feat: leads re-enable flow — detect removed leads on scan + Remove/Restore buttons
- QR scanner (single + multi): detect previously-removed leads via IDB enable flag;
  route to 'reenable' state instead of duplicate error; offer Re-activate button
- API fallback: if create fails and no IDB record, search API for disabled tracking
  record by event_exhibit_id + event_badge_id (adds qry_badge_id param to
  search__exhibit_tracking)
- Lead detail page: Replace raw enable checkbox with Remove Lead (two-click confirm,
  navigates back after) and Restore Lead card (shown when enable is falsy)
- Fix flash of disabled records in leads list: filter !enable in both filtered_lead_li
  derived and local IDB fast-path in handle_search_refresh
- eslint.config.js: disable svelte/no-navigation-without-resolve (no base path configured)
- Also includes _random field annotation cleanup (db_events, ae_types), iframe layout
  fixes, badge view tweaks, test updates, and doc updates from prior session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 18:18:10 -04:00
Scott Idem
4586e809d7 fix: leads scanner — handle falsy API result to prevent frozen 'adding' state
When create_ae_obj__exhibit_tracking returns false/null (API down, network
error, auth failure), the scanner was left frozen at 'adding' indefinitely.
Added else branch to surface an error state in both single and multi scanners.

Also fixes multi scanner which wasn't capturing the API return value at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:36:59 -04:00
Scott Idem
334c3a21bc feat: leads QR scanner — Auto/Multi modes, 4-mode fancy selector, UX polish
Scanner modes (now 4, persisted per exhibit):
- Rapid:   confirm tap → auto-reset (existing, fixed)
- Qualify: confirm tap → navigate to lead detail (existing, fixed)
- Auto:    badge found → auto-add immediately, no confirmation tap needed
- Multi:   BarcodeDetector batch scan → responsive grid of confirm cards

Multi scanner (new ae_comp__lead_qr_scanner_multi.svelte):
- Native BarcodeDetector API (Chrome/Edge/Safari 17+); Firefox fallback message
- 16:9 viewfinder with corner guides + "Align up to 4 badges flat" overlay
- Capture Batch tap → up to 8 QR codes detected in one frame
- Per-card states: loading skeleton, ready (Add/Skip), blocked (opt-out),
  already-captured (View/OK), adding spinner, success (auto-fade), error
- Add All (N) bulk action; cards fade+scale out smoothly on dismiss

Mode selector (ae_tab__add.svelte):
- Replaces Rapid/Qualify toggle with collapsible 4-mode fancy select
- Trigger shows active mode icon (color-coded) + name + description
- 2×2 options grid expands on tap, closes on selection

QR scanner element (element_qr_scanner_v3.svelte):
- object-fit: cover eliminates 4:3 camera letterbox dead zone
- 7-second start timeout with actionable error message
- Starting/error overlays with high-contrast styling
- Try Again button with RefreshCw icon

Style guide updated: icon+text button rule (§8), btn/preset-filled workaround (§12)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:30:38 -04:00
Scott Idem
14c2635df4 docs + fix: Leads module doc, badges onsite doc, license access fix, QR log cleanup
- Add MODULE__AE_Events_Exhibitor_Leads.md — full module reference (auth model, tabs,
  data model, routes, offline/PWA notes, OSIT admin notes)
- Add MODULE__AE_Events_Badges_Onsite.md — onsite printing guide (browser settings,
  CUPS/Linux setup, Zebra ZC10L section with test results, Epson stub, troubleshooting)
- Archive PROJECT__AE_Events_Exhibitor_Leads_v3*.md + Zebra test day doc → history/
- Fix leads license management access: ae_tab__manage.svelte license section now visible
  to administrator_access OR shared-passcode sign-in (type === 'shared'); matches spec
- Add type field to LeadsLocState.auth_exhibit_kv interface (was set at runtime but missing from type)
- Silence QR code console noise: entry log gated at log_lvl >= 2, data URL log removed
- vite.config.ts: exclude documentation/ and tests/ from Vite HMR file watcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:58:57 -04:00
Scott Idem
9673cbefe3 fix: PWA install prompt — capture beforeinstallprompt at module load time
The browser fires beforeinstallprompt very early (~1s after page load),
before Svelte's $effects run. Moving the event listener registration to
module level ensures we never miss the event regardless of when init()
is called from the root layout.

init() now only handles dismiss state (localStorage) and standalone
detection (DOM) — both safe to defer until after component mount.

Platforms:
- Chrome / Chromium / Android: native install button via captured prompt
- iOS Safari: manual Share → Add to Home Screen instructions (unchanged)
- Firefox desktop: no beforeinstallprompt support (browser-level limitation);
  Firefox shows its own install button in the address bar automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:14:17 -04:00
Scott Idem
9a43879535 feat: leads — add show/hide hidden records toggle + update stale README gaps
- Add Eye/EyeOff toggle to exhibit tracking search bar; wired to
  existing $events_loc.leads.show_hidden store field
- Guard + init the show_hidden field in +page.svelte
- Add show_hidden to search_params so toggling triggers a refresh
- Apply hide filter in local IDB search path (skip hidden unless toggled)
- Pass hidden: 'not_hidden' | 'all' to API search__exhibit_tracking call
- Apply hide filter in filtered_lead_li for the broad liveQuery fallback path
- README: remove stale allow_tracking/PWA/export gaps; add Implemented section;
  fix ae_events_functions import path (moved to ae_events/ subdir)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:09:16 -04:00
Scott Idem
2c570c82dc docs: archive 4 completed project docs, update stale references
Archived to documentation/history/:
- PROJECT__AE_Firefly_Theme_Repair_SUMMARY.md (complete)
- PROJECT__AE_Pres_Mgmt_Session_view_refactor_2026-02.md (resolved 2026-02-26)
- PROJECT__AE_Access_Control_UX.md (all steps done 2026-03-11)
- PROJECT__AE_combined_front_back_Docker.md (complete 2026-03-10)

Pre-archive housekeeping:
- CLAUDE.md: removed 3 resolved active issues; replaced stale session bug doc link with Badges Task 4.0 doc
- AE__Architecture.md: corrected stale "TipTap marked for removal" note — both editors are active
- PROJECT__AE_Firefly_Theme_Repair_SUMMARY.md: added archival note re element_modal_v1 retirement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:21:35 -04:00
Scott Idem
bf9aa9710c docs: add V3 field editor usage section to GUIDE__Development.md
Fulfills Phase 4 open item from PROJECT__AE_Object_Field_Editor_V3_upgrade.md.
Covers import, basic text usage, all field types, select with nullable FK,
key props table, and behavior notes (optimistic display, edit_mode visibility).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:09:19 -04:00
Scott Idem
942b3ddf5f docs: update component docs and check off completed Phase 3 datetime item
- PROJECT__AE_Object_Field_Editor_V3_upgrade.md: mark datetime support complete (was already implemented, just not checked off)
- AE__Components.md: replace stale ae_crud entry with element_ae_obj_field_editor_v3 description; correct TipTap "marked for removal" note (TipTap is actively used)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:06:24 -04:00
Scott Idem
bb0365f1e8 chore: move tiptap scss to styles/, trash orphaned codemirror files, update project doc
- element_tiptap_editor.scss → elements/styles/ (single importer path updated)
- Trashed element_codemirror_editor.svelte and element_codemirror_editor_wrapper.svelte (zero importers — active component is element_editor_codemirror.svelte)
- PROJECT__AE_Object_Field_Editor_V3_upgrade.md: mark v1/v2 removal complete, update status to 🟡 Mostly Complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:58:46 -04:00
Scott Idem
2b747de9bd chore: retire CRUD v1/v2 components — both were already unused
element_ae_crud.svelte and element_ae_crud_v2.svelte had zero active
importers; only a commented-out reference remained. Moved both to trash
and removed the dead comment from ae_comp__event_presentation_obj_li.svelte.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:54:33 -04:00
Scott Idem
519f5b949c chore: move ae_events_functions.ts into ae_events/ module
Relocates the functions file from lib root into its module directory,
matching the pattern used by all other modules (ae_journals, ae_archives, etc.).
Updated all 85 import paths from \$lib/ae_events_functions → \$lib/ae_events/ae_events_functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:52:13 -04:00
Scott Idem
bf834aa165 chore: rename editor components and analytics to follow element_* convention
- AE_Comp_Editor_CodeMirror.svelte → element_editor_codemirror.svelte
- AE_Comp_Editor_TipTap.svelte → element_editor_tiptap.svelte
- analytics.svelte → e_app_analytics.svelte (matches e_app_* prefix of siblings)
- Updated all import paths; import variable names unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:49:57 -04:00
Scott Idem
0d960435f8 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>
2026-03-20 09:45:57 -04:00
Scott Idem
60461de9d9 fix: badge view debug bar — center text and reserve fixed height to prevent layout bounce
Add justify-center + h-6 to the debug info row above the badge so it stays centered
and doesn't cause vertical shift when conditional elements show/hide on edit mode toggle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 19:18:38 -04:00
Scott Idem
da3b8dcf46 feat: badge print controls — per-template field visibility and edit access via other_json.controls_cfg
Add field_shown() and field_editable() functions driven by event_badge_template.other_json:
  controls_cfg: { shown?: string[], auth_editable?: string[] }

Access rules:
  - No authenticated_access → display-only, no edit buttons shown
  - authenticated only → can edit fields in auth_editable (default: title/affiliations/location/allow_tracking/pronouns)
  - trusted + edit_mode → always sees and edits all fields, ignores config

Each attendee field card (name, title, affiliations, location, allow_tracking, pronouns)
is now wrapped in {#if field_shown()} and its edit button/accordion gated by field_editable().
No backend changes needed — other_json is an existing longtext JSON column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 19:04:40 -04:00
Scott Idem
f628e7e3fc feat: badge print controls — quick print btn, compacted spacing, collapsible sections, overflow fix
- Add Quick Print button (30%) alongside canonical Print Badge (70%): calls window.print()
  only — no count increment, no navigation back to search
- Compact panel spacing: reduce space-y, pt/pb on card headers, standalone row py, font_ctrl py
- Add collapsible Attendee/Staff section groups reusing ctrl-accordion CSS pattern;
  attendee starts open, staff starts collapsed — auto-collapses the other on expand
- Add overflow-x-hidden to print page panel container to kill horizontal scrollbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 18:47:35 -04:00
Scott Idem
fdd8691e2e feat: badge print — two-line name toggle + leading-none tightening + calibration SVG
- Add name_two_lines toggle (default: true) — uses CSS horizontal padding scaled by
  character count to coax short names (e.g. "Scott Idem") into a natural two-line wrap
  without a hard <br>; three tiers: ≤12 chars (18%), ≤20 (8%), ≤28 (2%), >28 no pad
- Inner <div> (block element) used inside Element_fit_text for class: directives —
  Svelte scoped CSS requires static class names in the template; dynamic strings and
  class: on component elements both fail to match scoped CSS rules
- Add leading-none to all four Element_fit_text fields (name, title, affiliations,
  location) — line-height must be set at the wrapper div level where fit_text measures
  scrollHeight, otherwise the binary-search scaler returns inflated sizes
- name_two_lines state persisted to localStorage (ae_badge_print_tweaks key) alongside
  existing print_offset, hide_chrome, and banner_full_width tweaks
- Rewrite badge_header_calibration.svg as a precise SVG ruler with labeled tick marks
  (major at 1in intervals, minor at 0.25in) for accurate physical print calibration
- Gate debug outline CSS on html.debug_outlines class (set by controls panel) so
  outlines never appear in normal print mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 17:55:49 -04:00
Scott Idem
621a637b85 feat: badge print UX improvements — chrome toggle, banner width, overlap fix, header centering
- Replace ae_comp__badge_obj_view_v2 with ae_comp__badge_obj_view (consolidated component)
- Add hide-chrome toggle ([H] shortcut + button) to hide site nav/footer/sys bar for clean print workspace
  — syncs $ae_loc.sys_menu.hide + $ae_sess.disable_sys_nav/footer with restore-on-unmount
- Add banner_full_width toggle (default true=100% width, false=natural pixel size for calibration)
- Center badge header image (display:block; margin:0 auto) — was left-aligned when narrower than badge
- Fix controls panel overlap: move from bottom-0 to bottom-24 to clear sys bar (84px tall)
- Add [H] keyboard shortcut for chrome toggle (guards against focus in inputs)
- Persist hide_chrome and banner_full_width in ae_badge_print_tweaks localStorage key
- Add sample header image assets (calibration SVG/PNG, hex blue SVG/PNG, demo PNG)
- Update badge PVC CSS layout and module docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 15:42:22 -04:00
Scott Idem
639e436854 docs: update tests/README and GUIDE__Development with current test patterns
tests/README.md:
- Add shared helpers table (_helpers/ files and purpose)
- Update "Writing / modifying tests" to reference setup_badge_test_page;
  badge tests are now the canonical template
- Add "Hard-Won Lessons — Badge Print / IDB Tests" section covering:
  - __version guard: why ae_defaults.ts must include __version: 1 or
    store_versions.ts silently wipes ae_loc and loses trusted_access
  - IDB inject-then-reload pattern: why reload is required (liveQuery
    won't fire on raw IDB writes that bypass Dexie's notification system)
- Fix pre-existing lint warnings: bare URLs → code spans, trailing newline

GUIDE__Development.md:
- Add Required Check #5: run Playwright integration tests for auth/store
  or badge print changes; badge tests are the canonical template
- Add tests/README.md to Key Documentation table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:12:36 -04:00
Scott Idem
81741919a8 refactor: extract seed_trusted_session + setup_badge_test_page into shared test helpers
All 4 badge test files had identical ~35-line beforeEach blocks (pageerror listener,
inline V3 route mocks, addInitScript localStorage seed). Replaced with two helpers
in minimal_v3_mocks.ts:

  seed_trusted_session(page, event_id, account_id?)
    — seeds ae_loc localStorage with trusted/manager auth via addInitScript;
      account_id defaults to testing_account_id

  setup_badge_test_page(page, event_id)
    — one-call beforeEach: pageerror listener + attach_minimal_v3_routes +
      seed_trusted_session

Each test file's beforeEach is now 1-3 lines. All 12 tests still pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:05:22 -04:00
475 changed files with 56255 additions and 49514 deletions

View File

@@ -1,4 +1,28 @@
node_modules/
.svelte-kit
.vite
node_modules
dist
build
.cache
.DS_Store
.vscode
.idea
.git
.gitignore
coverage
tests
documentation
backups
*.log
*.bak
*.tgz
.env
.env.*
npm-debug.log
package-lock.json.bak
yarn-error.log
/.cache
/.parcel-cachenode_modules/
build/
.svelte-kit/
.git/

View File

@@ -4,6 +4,14 @@
"singleQuote": true,
"trailingComma": "none",
"printWidth": 80,
"plugins": ["prettier-plugin-svelte"],
"bracketSameLine": true,
"svelteSortOrder": "options-scripts-markup-styles",
"svelteIndentScriptAndStyle": false,
"svelteAllowShorthand": true,
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -100,10 +100,9 @@ src/routes/
## Active Issues (check TODO__Agents.md for current state)
- Session/Presentation page cold-start bug (Events Pres Mgmt) — unresolved
- Sev-1: `PUBLIC_AE_API_SECRET_KEY` audit
- CRUD v2 retirement → V3 editor
- 403/401 error boundaries ("Session Expired" UI)
- Sev-1: `PUBLIC_AE_API_SECRET_KEY` audit — see TODO__Agents.md (assessed acceptable, 2026-03-11)
- V3 CRUD migration — remaining legacy API wrappers in events/sponsorships/core (see `PROJECT__Use_AE_API_V3_CRUD_upgrade.md`)
- Style Review Phase 3 — IDAA + Pres Mgmt card polish deferred post-April 2026 conference
---
@@ -117,4 +116,4 @@ src/routes/
| `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` | Dexie + liveQuery patterns |
| `documentation/GEMINI__Svelte_and_Me.md` | Svelte 5 runes patterns |
| `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` | Electron/Launcher |
| `documentation/PROJECT__AE_Pres_Mgmt_Session_view_refactor_2026-02.md` | Session bug plan |
| `documentation/PROJECT__AE_Events_Badges_Review_Print.md` | Badges — kiosk editing (Task 4.0 open) |

View File

@@ -116,8 +116,8 @@ Built on the Events object.
Developer sandbox pages — not for production use.
- `/testing/ae_obj_field_editor_v3/` — V3 field editor playground
- `/testing/data_store_v3/` — Data store V3 playground
- `/testing/ae_obj_field_editor/` — V3 field editor playground
- `/testing/data_store/` — Data store V3 playground
- `/testing/editor_test/` — CodeMirror / TipTap editor tests
- `/testing/hosted_files/` — File upload tests

View File

@@ -0,0 +1,27 @@
## BuildKit-friendly multi-stage Dockerfile example for Aether frontend
# Stage 1: dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit --prefer-offline
# Stage 2: build
FROM node:20-alpine AS build
WORKDIR /app
# optionally reuse deps from previous stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# If you want to use BuildKit cache mounts during local development, uncomment the next line
# RUN --mount=type=cache,target=/root/.npm npm ci
RUN npm run build
# Stage 3: runtime (static site served by nginx)
FROM nginx:stable-alpine AS runtime
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# Notes:
# - Keep dependency installation separate from copying source to maximize cache hits when only application code changes.
# - For backend images, follow the same pattern: install deps early, copy source later, and keep a small final runtime image.

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
# Example CI script to build and push an image with buildx using registry cache.
# This script is provider-agnostic and intended to be run inside CI where
# Docker and buildx are available and authenticated against the registry.
REGISTRY=${REGISTRY:-ghcr.io/ORG/REPO}
IMAGE_TAG=${IMAGE_TAG:-staging}
CACHE_REF=${CACHE_REF:-${REGISTRY}:cache}
echo "Building ${REGISTRY}:${IMAGE_TAG} using registry cache ${CACHE_REF}"
docker buildx build \
--push \
--tag ${REGISTRY}:${IMAGE_TAG} \
--cache-from type=registry,ref=${CACHE_REF} \
--cache-to type=registry,ref=${CACHE_REF},mode=max \
.
echo "Build complete. Image: ${REGISTRY}:${IMAGE_TAG}"
# Optional: instruct devs how to run locally with a local cache
cat <<'EOF'
Local test with BuildKit and local cache:
DOCKER_BUILDKIT=1 docker build \
--tag myapp:staging \
--cache-to=type=local,dest=/tmp/docker-cache \
--cache-from=type=local,src=/tmp/docker-cache .
Prune local builder cache older than 72 hours:
docker builder prune --filter "until=72h" --force
EOF

View File

@@ -0,0 +1,30 @@
# AE Docker CI Cache Policy (recommendation)
Purpose
- Provide a straightforward policy to keep build caches useful but bounded.
Recommendations
- Primary CI cache: **registry-based buildx cache** (preferred). Use a single cache ref (e.g. `ghcr.io/ORG/REPO:cache`) reused by CI builds.
- Local dev cache: use `--cache-to type=local` for fast iteration but prune periodically.
- Retention: keep registry cache for 30 days by default. Implement registry GC or lifecycle rule to delete older cache blobs.
Rotation strategy
- Option A (simple): CI always writes to the same cache ref `:cache`. Periodically (monthly) run a job to `docker pull` and `docker image rm` older tags if you use date-based tagging.
- Option B (date-tag): CI writes cache to `cache-YYYYMMDD` and a small scheduled job deletes tags older than 30 days.
Pruning commands (developer)
- Remove local build cache older than 72 hours:
```bash
docker builder prune --filter "until=72h" --force
```
- Remove all builder cache (aggressive):
```bash
docker builder prune --all --force
```
CI runner requirements
- `docker` and `docker buildx` available in runner environment.
- Registry credentials provided via CI secrets with permission to push/pull images.
Security & Secrets
- Do not store registry credentials in repo. Use CI secret storage.

View File

@@ -575,7 +575,6 @@ This document provides a reference for the data structures of the core Aether AP
- `orders_info`: `Optional[dict]`
- `order_list`: `Optional[list]`
- `order_cart`: `Optional[dict]`
- `order_cart_v3`: `Optional[dict]`
- `organization`: `Optional[Union[Organization_Base, None]]`
- `post_list`: `Optional[list]`
- `user`: `Optional[Union[User_Base, None]]`

View File

@@ -16,9 +16,8 @@ The Aether project is a Svelte and SvelteKit based application, utilizing Tailwi
- Flowbite (Tailwind Components)
- Custom Components (a growing library of `ae_comp__*` and `element_*` components)
- **Text/Code Editors:**
- CodeMirror 6.x (primary text and code editor, planned for rich text editing)
- Edra (TipTap based Rich Text Editor)
- **Note:** ShadEditor TipTap is present but marked for removal, with CodeMirror being the preferred solution for all text editing needs.
- CodeMirror 6.x (`element_editor_codemirror.svelte`) — source/code editing, markdown
- TipTap (`element_editor_tiptap.svelte`) — WYSIWYG rich-text for content fields (IDAA, Journals, Leads notes)
- **Icons:** Lucide Icons (SVG Icons)
- **Markdown Parsing:** `marked` library
- **State Management:** Svelte stores, potentially with `liveQuery` from Dexie for reactive IndexedDB interactions.

View File

@@ -40,9 +40,9 @@ These are reusable components that provide common functionalities across differe
- **`copy_btn`**: A button to copy content to the clipboard.
- Properties: `clipboard`, `bind:value`, `btn_text`, `btn_html`.
- **`txt_editor`**: A basic text area editor.
- **`md_editor`**: Markdown editor.
- Uses CodeMirror (planned for rich text editing).
- **Note:** ShadEditor TipTap is present but marked for removal. ShadCN components are also being phased out in favor of CodeMirror for text editing.
- **`md_editor`**: Markdown/rich-text editing handled by two active components:
- `element_editor_codemirror.svelte` CodeMirror 6, used for source/code editing
- `element_editor_tiptap.svelte` — TipTap (WYSIWYG), used for rich-text content fields
- **`html_editor`**: HTML editor.
- **`media_player`**: Component for playing media files.
- Properties: `hosted_file`, `archive_content`, `media_player`.
@@ -62,13 +62,11 @@ These are reusable components that provide common functionalities across differe
- Bindings: `bind:trigger`, `bind:show_spinner`, `bind:show_percent`.
- Status: `started`, `downloading`, `finished`.
- **`data_store`**: Component for interacting with data stores.
- **`ae_crud`**: Generic CRUD (Create, Read, Update, Delete) component.
- **Note:** Needs simplification.
- Properties: `obj`, `prop`, `current_value`.
- Bindings: `bind:value`, `bind:trigger`, `inner fragment`.
- **`ae_obj_prop_val`**: A wrapper for a function to manage object property values.
- Bindings: `bind:obj_type`, `bind:obj_id`, `bind:obj_prop`, `bind:obj_value`, `bind:obj_new_value`, `bind:trigger`, `bind:show_spinner`, `bind:show_percent`.
- Status: `status`, `result`.
- **`element_ae_obj_field_editor`**: Standard single-field inline editor. Replaces retired `ae_crud` v1/v2 components.
- Props: `object_type`, `object_id`, `field_name`, `field_type`, `current_value`
- Field types: `text`, `textarea`, `select`, `tiptap`, `checkbox`, `date`, `datetime`, `number`
- Callbacks: `on_success`, `on_error`
- Respects `$ae_loc.edit_mode` — edit trigger hidden when edit mode is off.
- **`sql_qry`**: Component for executing SQL queries.
- **`obj_tbl`**: Object SQL results table or similar.
- **`qr_scanner`**: Component for scanning QR codes.

View File

@@ -68,39 +68,145 @@ Modify data in the system.
* **Header:** `x-ae-ignore-extra-fields: true`
* **Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database.
### D. ID Fields in Responses (Vision ID Convention)
> [!IMPORTANT]
> **V3 responses always use random string IDs — never database integers.**
After a successful `POST` create or any `GET`, the response contains:
| Field | Type | Use |
| :--- | :--- | :--- |
| `{obj_type}_id` | `string` | **Primary public ID.** Use this for subsequent `PATCH` calls and UI routing. |
| `{obj_type}_id_random` | `string` | Legacy alias. Same value as `{obj_type}_id`. Present for backward compat only. |
**Example — create then immediately PATCH:**
```ts
const created = await postArchiveContent(archiveId, payload);
const newId = created.data.archive_content_id; // random string e.g. "xK9mP3qRtL2"
// Use it directly in the PATCH URL — no lookup needed
await patchArchiveContent(newId, { name: 'Updated Name' });
// PATCH /v3/crud/archive/{archive_id}/archive_content/{newId}
```
> **Note on `_id_random` suffix:** The `{obj_type}_id_random` field is a legacy artifact from the pre-Vision model. Once you confirm `{obj_type}_id` is a random string (length 1122), you do not need `_id_random` as a fallback. New code should only read `{obj_type}_id`.
---
## 4. V3 Uniform Lookup System
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting.
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized reference tables (Countries, Timezones, etc.). It supports global defaults, account-level overrides, and object-level overrides, with optional site-specific whitelisting.
### How the hierarchy works
Each lookup table (`lu_v3_country`, `lu_v3_time_zone`, etc.) can hold multiple rows for the same logical item at different scopes:
| Scope | `account_id` | `for_type` / `for_id` | Wins over |
|---|---|---|---|
| Global default | `NULL` | `NULL` / `NULL` | nothing |
| Account override | set | `NULL` / `NULL` | Global default |
| Object override | set | set | Account override + Global default |
The API uses `ROW_NUMBER() PARTITION BY group` to collapse all rows for the same item down to the single highest-priority winner before returning results. **`group` is the identity key** — it is what makes two rows "the same item competing for priority."
> [!IMPORTANT]
> **The `group` field is not a display label.** It is the deduplication key. Each lookup type uses a different natural key for `group`:
>
> | Lookup type | `group` value | Example |
> |---|---|---|
> | `country` | ISO alpha-2 code | `"US"`, `"CA"`, `"GB"` |
> | `country_subdivision` | subdivision code | `"US-NY"`, `"CA-ON"` |
> | `time_zone` | IANA timezone name | `"America/New_York"`, `"US/Eastern"` |
>
> For `time_zone`, `group` and `name` must always be identical — there is no concept of "override all US timezones as a group." Each timezone is its own identity.
### A. List Lookups
Retrieve a ranked and filtered list of lookup items.
Retrieve the deduplicated, ranked list for a lookup type.
* **Endpoint:** `GET /v3/lookup/{lu_type}/list`
* **Available Types:** `country`, `country_subdivision`, `time_zone`
* **Parameters:**
* `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**.
* `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones).
* `for_type` / `for_id` (Optional): Context for object-specific overrides.
* `include_disabled` (Optional): Set to `true` to see shadowed/disabled records.
* `site_id` (Optional): Random ID of the site applies a **Whitelist Policy** (see §C).
* `only_priority` (Optional): `true` returns only `priority=1` items (e.g., common time zones).
* `for_type` / `for_id` (Optional): Object context — activates object-level override matching.
* `include_disabled` (Optional): `true` includes shadowed/disabled records (useful for admin views).
**Frontend keying:** Always key Svelte `{#each}` blocks on `group`, not `id` or `name`. `group` is guaranteed unique in the response. Keying on `id` will break if an account override wins (different `id`, same logical item).
### B. Resolve Identity
Resolves a string (code, group, or name) to a single record.
Resolves a string to a single lookup record.
* **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE`
* **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record.
* **Usage:** Use when you have an external code (e.g., ISO `"US"`) and need the full Aether record. Scans `name`, `group`, and other identity fields.
### C. Site Whitelist Policy
To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field.
**Schema:**
To restrict which lookup items appear for a specific site, add a `lookup_policy` to `site.cfg_json`:
```json
{
"lookup_policy": {
"country": ["US", "CA", "GB"],
"time_zone": ["America/New_York"]
"time_zone": ["America/New_York", "US/Eastern"]
}
}
```
*Note: Whitelist values must match the `group` field in the database.*
> **Whitelist values must match the `group` field** — i.e., the natural key for that type (ISO code for country, IANA name for time zone). Using a display name will silently return no results for that item.
### D. Adding and managing client overrides
When a client needs a customized label or wants to hide/reorder lookup items, create override records rather than modifying global defaults.
**Rules:**
1. **Never modify global default rows** (`account_id = NULL`). Those are shared across all accounts. Any change there affects every client.
2. **Set `group` to the exact same value as the global default row** for the item you are overriding. If `group` doesn't match, the override creates a new item instead of replacing the existing one.
3. **Set `account_id`** to the client's account ID. Leave `for_type` / `for_id` null unless the override is specific to a single object (e.g., one site).
**Example — rename "US/Eastern" for one account:**
```sql
INSERT INTO lu_v3_time_zone
(account_id, name, name_override, `group`, enable, priority, sort)
VALUES
(42, 'US/Eastern', 'Eastern Time (Client Label)', 'US/Eastern', 1, 1, 50);
```
The `name_override` field is the display label the frontend should prefer when set. `group = 'US/Eastern'` ensures this row competes with — and wins over — the global default in the `PARTITION BY group` deduplication.
**To disable an item for one account** (hide it from their dropdowns):
```sql
INSERT INTO lu_v3_time_zone
(account_id, name, `group`, enable)
VALUES
(42, 'US/Samoa', 'US/Samoa', 0);
```
Setting `enable = 0` on an account-scoped row shadows the global default for that account only.
**To remove a client override** (revert to global default):
Simply delete the row where `account_id = <client>` and `group = '<item>'`. The global default row is unaffected and immediately resumes winning.
### E. Adding new global lookup items
When seeding new lookup data (e.g., adding timezones in bulk):
1. Set `group = name` for every row (for `time_zone`). This is a hard invariant — if `group` is set to a regional label like `"United States"` instead of the timezone name, the entire group collapses to a single winner and all but one entry disappear from the API response.
2. Set `account_id = NULL` and `for_type = NULL` / `for_id = NULL` for global defaults.
3. After seeding, verify with:
```sql
-- Should return 0 rows; any result means multiple items will collapse into one
SELECT `group`, COUNT(*) AS cnt
FROM lu_v3_time_zone
WHERE account_id IS NULL
GROUP BY `group`
HAVING cnt > 1;
```
---

View File

@@ -1,5 +1,5 @@
# Aether UI — Design System Style Guidelines
> **Version:** 1.1 (2026-03-17)
> **Version:** 1.2 (2026-03-20)
> **Author:** One Sky IT / Scott Idem
> **Scope:** All Aether SvelteKit frontend components
> **Related:** `AE__UI_Component_Patterns.md`, `ae-firefly.css`, `documentation/AE__Components.md`
@@ -34,6 +34,7 @@ To maintain codebase health and performance, all new development must adhere to
- **Mandatory**: Use `preset-*` classes for interactive elements (e.g., `preset-tonal-primary`).
- **Forbidden**: Legacy Skeleton v3 `variant-*` classes.
- **Customization**: Use Tailwind 4 `@theme` blocks for project-wide overrides.
- **URLs**: Skeleton for Svelte for LLMs docs: https://www.skeleton.dev/llms-svelte.txt
### 🔣 Lucide Icons
- **Mandatory**: Use `@lucide/svelte` components (e.g., `<Calendar size="1em" />`).
@@ -192,6 +193,7 @@ Always wrap in `{#if $lq__obj}{...}{:else}...skeleton...{/if}` — **never** sho
| Form inputs | Visible `<label>` linked via `for` / `id`, or explicit `aria-label` |
| Color-only information | Always pair color coding with icon or text — never color alone |
| Minimum touch target | 44×44px effective hit area for all tap targets |
| Button label + icon | All buttons should include **both a Lucide icon and text label**. Icon-only is acceptable for space-constrained toolbar/header actions (with `title` attribute); text-only is acceptable when layout is extremely tight. The icon+text combination aids non-English-native users who may not read the label fluently. |
---
@@ -241,3 +243,34 @@ $events_sess.pres_mgmt.session_qr_url[$lq__obj.id] = result; // ← URL string
- **`text-sm leading-relaxed`**: Standard for body-level descriptive text in cards.
- **`tracking-wide uppercase`**: Use for section label/eyebrow text with `opacity-40`.
- **`whitespace-pre-wrap`**: Required for any `<pre>` or `<p>` displaying user-entered multi-line text (preserves breaks without horizontal overflow).
---
## 12. Known Issues & Workarounds
### `btn` + `preset-filled-*` resolves to transparent inside `card` components
**Symptom:** A button using `btn preset-filled-primary` (or any `preset-filled-*`) inside a `card` div renders with `background-color: transparent`, making it invisible against the card surface.
**Root cause:** The Skeleton v4 `btn` class sets a transparent background via a CSS variable chain. When nested inside a `card` element, the `preset-filled-*` class fails to win the specificity battle and the button appears invisible. This affects both light and dark mode.
**Workaround:** Skip `btn` and `preset-filled-*` entirely for buttons inside `card` elements. Use direct Tailwind token classes instead:
```svelte
<!-- ✅ Correct — works reliably inside cards -->
<button class="w-full rounded-xl py-5 font-bold flex items-center justify-center gap-2
bg-primary-500 text-white hover:brightness-110 transition-all cursor-pointer">
...
</button>
<!-- Secondary / cancel button inside a card -->
<button class="w-full rounded-lg py-3 text-sm font-medium flex items-center justify-center gap-2
border border-surface-500/40 hover:bg-surface-200-800 transition-colors cursor-pointer opacity-70">
...
</button>
<!-- ❌ Broken inside card — do not use -->
<button class="btn btn-xl preset-filled-primary">...</button>
```
**Scope:** `btn` + `preset-*` classes work correctly on standalone buttons (e.g. page headers, nav bars). The issue is specific to the `card` component context. If we migrate away from Skeleton `card`/`btn`, this issue goes away.

View File

@@ -11,6 +11,11 @@
2. **Type Safety:** Ensure interfaces in `src/lib/types/ae_types.ts` match backend schemas.
3. **Reactivity Check:** Verify Svelte 5 runes (`$state`, `$derived`) are not creating race conditions with Dexie `liveQuery`.
4. **Build Check:** For major changes, run `npm run build:staging` to ensure no SSR or build-time failures.
5. **Integration Tests:** For changes to badge print, event layouts, or auth/store logic, run the relevant Playwright test file(s):
```bash
npx playwright test tests/event_badge_render.test.ts tests/event_badge_attendee_workflow.test.ts
```
Run the full suite with `npm run test:integration`. The badge tests (`event_badge_*.test.ts`) are the canonical integration test template.
## 2. Commit Policy
- **Atomic Commits:** One component or one logic fix per commit. Do not batch unrelated changes.
@@ -46,8 +51,81 @@ You are not working in a vacuum. Coordinate with the Backend Agent via MCP tools
| `documentation/AE__Architecture.md` | System architecture overview |
| `documentation/AE__Naming_Conventions.md` | Naming rules |
| `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` | Electron/Launcher reference |
| `tests/README.md` | Playwright test guide — shared helpers, hard-won lessons, demo IDs |
## 6. URL Parameters
## 6. Inline Field Editing — `element_ae_obj_field_editor`
The standard component for single-field inline editing throughout the platform. Wraps a `PATCH /v3/crud/{obj_type}/{obj_id}` call behind a click-to-edit UI that respects `$ae_loc.edit_mode`.
```svelte
import Element_ae_obj_field_editor from '$lib/elements/element_ae_obj_field_editor.svelte';
```
### Basic usage — text field with custom display
Wrap the display content in the default snippet. The component renders it in view mode and swaps in the input on edit.
```svelte
<Element_ae_obj_field_editor
object_type={'event_session'}
object_id={session.id}
field_name={'name'}
field_type={'text'}
current_value={session.name}
on_success={() => events_func.load_ae_obj_id__event_session({ api_cfg: $ae_api, event_session_id: session.id })}
>
<h1 class="text-2xl font-bold">{session.name}</h1>
</Element_ae_obj_field_editor>
```
### Field types
| `field_type` | Input rendered |
| --- | --- |
| `text` (default) | `<input type="text">` — Enter key saves |
| `textarea` | `<textarea>` — use `textarea_rows` prop |
| `select` | `<select>` — pass `select_options={{ value: 'Label' }}` |
| `checkbox` | `<input type="checkbox">` — shows Enabled/Disabled |
| `tiptap` | TipTap rich-text editor |
| `date` | `<input type="date">` |
| `datetime` | `<input type="datetime-local">` |
| `number` | `<input type="number">` — Enter key saves |
### Select with nullable FK
```svelte
<Element_ae_obj_field_editor
object_type={'event_presenter'}
object_id={presenter.event_presenter_id}
field_name={'person_id'}
field_type={'select'}
current_value={presenter.person_id}
select_options={$slct.person_obj_kv}
allow_null={$ae_loc.administrator_access}
on_success={() => events_func.load_ae_obj_id__event_presenter({ api_cfg: $ae_api, event_presenter_id: presenter.event_presenter_id })}
>
{presenter.person_id ?? 'Not linked'}
</Element_ae_obj_field_editor>
```
### Key props
| Prop | Default | Notes |
| --- | --- | --- |
| `current_value` | — | Required. Bound with `$bindable` — liveQuery updates flow through automatically |
| `allow_null` | `false` | Shows a "Set Null" button in edit mode |
| `display_block` | `false` | Makes the wrapper `display: block` instead of `inline-block` |
| `on_success` | — | Callback after successful PATCH — use to trigger SWR cache refresh |
| `object_reload` | `true` | Triggers internal SWR reload after patch (in addition to `on_success`) |
### Behavior notes
- The edit trigger button is `visibility: hidden` (not `display: none`) when `$ae_loc.edit_mode` is off — this preserves layout so the page doesn't shift when edit mode toggles.
- Optimistic display: draft value is shown immediately after save; cleared once liveQuery confirms the update came back from the DB.
- `on_success` should always call the relevant `load_ae_obj_id__*` function to keep Dexie in sync.
---
## 7. URL Parameters
URL params consumed by the app. Params are read by layouts and applied on mount.

View File

@@ -280,6 +280,66 @@ If you must use non-blocking loads, you must pass the initial data to the compon
{/if}
```
## The `untrack()` Reactive-Tracking Trap
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you *need* to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
### Symptom
An effect runs once, reads a store value inside `untrack()`, takes an early-exit path (e.g. "no API key → skip"), and never retries — even after the store value is updated by a background process.
### Real Example (IDAA Novi Verification Bug — 2026-03-25)
The IDAA layout verifies Novi UUIDs. `site_cfg_json` (which contains the Novi API key) was read **inside** `untrack()`:
```typescript
// BUG: site_cfg_json read inside untrack → one-shot, never retries
$effect(() => {
if (!browser) return;
const uuid = data.url.searchParams.get('uuid'); // tracked ✓
untrack(() => {
const site_cfg_json = $ae_loc.site_cfg_json; // ← NOT tracked ✗
const api_key = site_cfg_json?.novi_idaa_api_key ?? null;
if (!api_key) return; // exits silently on first load with stale cache
verify_novi_uuid(uuid, api_key, ...);
});
});
```
On first load, the Dexie cache returned a stale `site_cfg_json` missing the API key. The effect exited early. The background refresh later updated `$ae_loc.site_cfg_json`, but because `site_cfg_json` was consumed inside `untrack()`, the effect never re-ran.
**Fix:** Move the dependency read **outside** `untrack()`:
```typescript
// FIX: site_cfg_json tracked outside untrack → effect re-runs when it changes
$effect(() => {
if (!browser) return;
const uuid = data.url.searchParams.get('uuid'); // tracked ✓
const site_cfg_json = $ae_loc.site_cfg_json; // tracked ✓ — effect re-runs on change
untrack(() => {
// Guard: already verified for this UUID — don't repeat the round-trip
if ($idaa_loc.novi_verified && $idaa_loc.novi_uuid === uuid) return;
const api_key = site_cfg_json?.novi_idaa_api_key ?? null;
if (!api_key) return;
verify_novi_uuid(uuid, api_key, ...);
});
});
```
The guard inside `untrack()` is important: without it, every unrelated change to `$ae_loc` would re-trigger verification.
### Rule of Thumb
Before wrapping a store read in `untrack()`, ask: **"Do I need this effect to re-run if this value changes?"**
- If yes → read it **outside** `untrack()`, and add a guard inside to prevent redundant work.
- If no → `untrack()` is correct.
---
## Svelte 5 Binding Pitfalls
### 1. `props_invalid_value` (The "Expression Binding" Error)

View File

@@ -358,7 +358,7 @@ Firefox users can use "Save to PDF" directly — it just works.
- [x] Wire `style_href` via `<svelte:head>` in print page — done in `print/+page.svelte`; also in `properties_to_save`. (2026-03-18 verified)
- [x] Add `duplex` to `properties_to_save` — done. (2026-03-18 verified)
- [x] Add `duplex`-driven suppression to `badge_back` section — done in `ae_comp__badge_obj_view_v2.svelte`; `show_badge_back` derived from `duplex` field. Note: v1 (`ae_comp__badge_obj_view.svelte`) was archived to `~/tmp/gemini_trash/`; v2 is canonical. (2026-03-18 verified)
- [x] Add `duplex`-driven suppression to `badge_back` section — done in `ae_comp__badge_obj_view.svelte`; `show_badge_back` derived from `duplex` field.
- [ ] Make `layout` field drive actual card dimensions in the badge component — currently the Zebra ZC10L layout CSS (`badge_layout_zebra_zc10l_pvc.css`) sets dimensions correctly via `[data-layout="..."]` scoping, but fanfold layouts still use Tailwind defaults. Needs proper CSS for each layout code.
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info` `{#if}` blocks from `ae_comp__badge_obj_view_v2.svelte` (if they were carried over from v1)
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info` `{#if}` blocks from `ae_comp__badge_obj_view.svelte` (if they were carried over from v1)
- [ ] Improve `ae_comp__badge_template_form.svelte` to edit all relevant fields (currently minimal)

View File

@@ -11,7 +11,7 @@
## Overview
The Badges module manages event attendee badges with support for:
- **External system imports** (iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
- **External system imports as needed** (CSV/Excel, iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
- **Field override protection** to prevent staff/attendee edits from being overwritten by automated syncs
- **Multi-tier access control** for field editing
- **QR code generation** for badge scanning
@@ -535,8 +535,8 @@ Button has `data-testid="badge-print-btn"` and shows loading/done/error states w
**Indexed Fields:**
```typescript
badge: `
event_badge_id_random, event_badge_id, id,
event_id, event_id_random,
event_badge_id, id,
event_id,
full_name, full_name_override, email, email_override,
affiliations, affiliations_override,
badge_type, badge_type_code, badge_type_code_override, badge_type_override,
@@ -704,7 +704,7 @@ delete_ae_obj_id__event_badge({ event_badge_id, event_id, method })
### Key Test Lessons Learned
**Search API path is FLAT, not nested.** `search_ae_obj_v3` builds `/v3/crud/{obj_type}/search` — always flat regardless of the parent relationship. Mocks must match this:
**Search API path is FLAT, not nested.** `search_ae_obj` builds `/v3/crud/{obj_type}/search` — always flat regardless of the parent relationship. Mocks must match this:
```typescript
// CORRECT — flat path
url.includes('/v3/crud/event_badge/search') && method === 'POST'
@@ -712,7 +712,7 @@ url.includes('/v3/crud/event_badge/search') && method === 'POST'
url.includes(`/v3/crud/event/${event_id}/event_badge/search`) && method === 'POST'
```
**List API (GET) is also FLAT with query params.** `get_ae_obj_li_v3` builds `/v3/crud/{obj_type}/?for_obj_id=...` — always flat. Mocks must check `url.includes('/v3/crud/event_badge_template/') && url.includes('for_obj_id')`.
**List API (GET) is also FLAT with query params.** `get_ae_obj_li` builds `/v3/crud/{obj_type}/?for_obj_id=...` — always flat. Mocks must check `url.includes('/v3/crud/event_badge_template/') && url.includes('for_obj_id')`.
**CSS `input[value*=...]` selectors don't work with Svelte bind:value.** The CSS selector checks the HTML *attribute*; Svelte's `bind:value` sets the DOM *property* only. In Playwright tests, use `page.getByLabel()` or `locator.inputValue()` instead.
@@ -726,9 +726,9 @@ All API mock responses in tests need these fields.
**Badge view requires both badge AND template.** `ae_comp__badge_obj_view.svelte` wraps everything in `{#if $lq__event_badge_obj && $lq__event_badge_template_obj}` — if the template isn't loaded, edit/print buttons and the badge itself don't render. Tests must mock the badge template endpoint.
**Badge GET endpoint (single object):** `/v3/crud/event_badge/{id}` (NOT nested under event). Matches `api.get_ae_obj_v3()` which uses the flat path.
**Badge GET endpoint (single object):** `/v3/crud/event_badge/{id}` (NOT nested under event). Matches `api.get_ae_obj()` which uses the flat path.
**Badge PATCH endpoint (update):** `/v3/crud/event/${event_id}/event_badge/${badge_id}` (nested under event). Matches `api.patch_ae_obj_v3()` which uses the nested path.
**Badge PATCH endpoint (update):** `/v3/crud/event/${event_id}/event_badge/${badge_id}` (nested under event). Matches `api.patch_ae_obj()` which uses the nested path.
**Use `data-testid` for test selectors.** Key buttons have targets: `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input`.

View File

@@ -0,0 +1,207 @@
# Aether Events — Onsite Badge Printing
Notes on setup, process, hardware, and browser behavior for onsite badge printing at events.
---
## Overview
Aether badge printing uses the browser's native `window.print()` — no special software or print
server needed. The badge render page (`/events/[event_id]/badges/print/[badge_id]`) outputs
print-ready HTML/CSS, and the browser sends it directly to the connected printer via CUPS (Linux)
or the OS print system (macOS/Windows).
Chrome (Chromium) is the recommended browser for onsite kiosk stations.
Firefox is a solid alternative, especially for Save-to-PDF workflows.
---
## Recommended Workflow — Onsite Kiosk
1. Open the event's badge printing page: `/events/[event_id]/badges`
2. Search for the attendee (name, badge ID, or QR scan)
3. Open the badge print page — review the rendered badge
4. Click **Print Badge** in the controls panel (or use keyboard shortcut)
5. In the browser print dialog:
- Set Margins to **None** (Chrome) or leave defaults (Firefox)
- Confirm paper/card size matches the stock loaded in the printer
- Print
6. `print_count` increments automatically on each print via the Print Badge button
For high-volume events, consider the **rapid QR scan** mode in the Leads module or using a
dedicated kiosk session where the operator only handles physical card handoff.
---
## Browser Settings
### Chrome / Chromium (Recommended for kiosk use)
Chrome is recommended for onsite badge printing stations. Key print dialog settings:
| Setting | Correct value | Notes |
|---|---|---|
| Margins | **None** or **Minimum** | Default margins add URL/date headers — breaks badge centering |
| Paper size | Match card stock (e.g. 3.5" × 5.5") | Zebra driver may override this automatically |
| Background graphics | **On** | Required for colored header/footer stripe to print |
| Pages | 1 | PVC single-sided — only front should print |
**Important:** Chrome ignores CSS `@page { size }` for Save to PDF — it defaults to letter/A4.
For physical printer output, the printer driver controls paper size. This is expected behavior.
To lock Chrome settings for a kiosk, set Margins to "None" once and Chrome remembers per-printer.
### Firefox
Firefox honors CSS `@page { size }` which makes it ideal for PDF generation.
For physical printing, Firefox generally "just works" without margin adjustments.
| Setting | Notes |
|---|---|
| Paper size | Can be set in dialog, but CSS `@page { size }` is honored |
| Margins | Default is usually fine; remove headers/footers if they appear |
| Background graphics | Enable for colored stripes and header images to print |
### General Notes
- **Background graphics must be enabled** in any browser — otherwise header images, footer
color stripes, and tonal backgrounds will not print.
- Private/incognito mode blocks PWA install prompts — use normal browser sessions for kiosk.
- For highest reliability, set the kiosk machine to auto-login and open Chrome to the event URL.
---
## Linux / CUPS Setup
For Linux workstations and dedicated kiosk machines running Linux:
1. Install CUPS if not already present: `sudo pacman -S cups` (Arch) or equivalent
2. Start the CUPS service: `sudo systemctl enable --now cups`
3. Open the CUPS web UI: `http://localhost:631`
4. Add the printer and install the appropriate driver (see per-printer sections below)
5. Print a test page from CUPS to confirm card feed and quality
6. In Chrome: select the CUPS printer name under Destination in the print dialog
On macOS and Windows, use the vendor-provided driver installer.
---
## Printers
---
### Zebra ZC10L — PVC Card Printer
**Card stock:** 3.5" × 5.5" PVC cards (CR80 extended)
**Tested:** 2026-03-17 (rental test day, Arch Linux)
**Status:** Working. Confirmed suitable for Axonius NYC (mid-April 2026).
#### Physical Setup
- Connect via USB (the ZC10L supports USB and Ethernet)
- Load PVC card stock per the Zebra loading instructions — cards face-up, landscape
- The ZC10L prints one side (single-sided dye-sub thermal); do not attempt duplex on PVC stock
#### Linux Driver
- Download the Zebra ZC10L CUPS driver from zebra.com (ZC Series Linux support)
- Install the `.deb` or extract the PPD file and add to CUPS manually
- In CUPS (`http://localhost:631`), add the printer and select the ZC10L PPD
- Set default paper size to **3.5" × 5.5"** (or CR80 Extended if listed)
- Print a blank test page from CUPS before using Chrome
> **Note:** Driver version tested: *(update here after confirming)*
> CUPS printer name used: *(update here after setup)*
#### Chrome Print Settings (ZC10L)
| Setting | Value |
|---|---|
| Destination | Zebra ZC10L (CUPS name) |
| Paper size | 3.5 × 5.5 in (or as set in CUPS) |
| Margins | **None** |
| Background graphics | On |
| Pages | 1 (front only) |
#### CSS Layout
The ZC10L uses the `badge_3.5x5.5_pvc` layout. The PVC layout CSS is at:
`src/routes/events/[event_id]/(badges)/badges/print/badge_layout_zebra_zc10l_pvc.css`
This layout hides `.badge_back` in `@media print` — only the front face prints.
`@page { size: 3.5in 5.5in; margin: 0; }` is set in the CSS.
#### Known Behaviors / Watch-outs
- Chrome with **Default** margins: inserts URL/date headers, offsets badge — use **None**
- Chrome with **None** or **Minimum** margins: correct output
- Firefox: works correctly out of the box with this layout
- Physical card alignment: if the badge appears offset on the card, a CSS margin tweak may
be needed in the PVC layout file — note the offset and adjust `print_margin_cfg` once that
field is wired to the UI
- Font sizes: if name/affiliation text appears too small at physical scale, adjust via the
font size controls (+ / ) in the print controls panel; note the preferred values for this
event's template
#### Test Results (2026-03-19)
- Card feeds and prints without jam: ✅
- Single-sided PVC confirmed (back does not print): ✅
- Chrome margins None/Minimum: correct output ✅
- Firefox: correct output ✅
- QR code scannable from printed card: ✅
- Print tracking (`print_count` increment): ✅
- Font sizes / visual quality: tested; specific calibration pending client design direction
- Driver version: *(record here)*
- Physical offset needed: *(record if any)*
---
### Epson — Fan-Fold / Label Printer
**Status:** Not yet tested. Section to be filled in after testing.
Common Epson models used for fan-fold name badge stock: TM-T88 series, C3500, LX series.
Fan-fold stock is typically 4" × 3" or 4" × 6" paper labels.
#### CSS Layout
Fan-fold badges would use a layout sized to the specific label stock.
A new CSS layout file will need to be created per stock size if not already present.
Naming convention: `badge_layout_epson_[model]_[size].css`
#### Setup Notes
*(To be filled in after testing — cover: driver source, CUPS setup, paper size, Chrome settings)*
#### Known Behaviors
*(To be filled in after testing)*
---
## Print Tracking
The badge print page tracks print counts per badge:
- `print_count` — increments on each **Print Badge** button click
- `print_first_datetime` — timestamp of first print
- An amber "Printed N×" chip appears in the print page header after the first print
The reprint shortcut (trusted access + edit mode) does **not** increment the count.
Only the **Print Badge** button path increments the count. This is intentional — reprints
for alignment or quality checks should not inflate the print count.
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| White border around printed badge | Chrome Default margins | Change to None or Minimum |
| URL / date printed at top or bottom | Chrome Default margins | Change to None |
| Header image / stripe not printing | Background graphics disabled | Enable in print dialog |
| Badge appears on wrong-size output | Paper size mismatch | Set correct size in CUPS and/or print dialog |
| Card jams | Card stock misloaded | Re-seat cards per printer manual; check stock orientation |
| Badge content clipped | Layout overflow | Check font size — use control to reduce if needed |
| Second blank card ejected | Duplex triggered on PVC | Confirm `.badge_back { display: none }` in print CSS for this layout |

View File

@@ -0,0 +1,262 @@
# Aether Events — Exhibitor Leads Module (v3)
**Status:** Implemented and ready for demo. Core lead capture flow works end-to-end.
**Platform:** PWA only — mobile-first, offline-capable.
**Target users:** Conference exhibitors scanning attendee badges at their booths.
---
## What It Does
The Exhibitor Leads module lets conference exhibitors capture and manage attendee leads directly
from their booth. Exhibitors scan or search attendee badges and build a list of contacts they met.
All data is cached locally (IndexedDB / Dexie.js) for spotty or offline venue Wi-Fi, with
background SWR revalidation against the API when the network is available.
Key capabilities:
- **Badge scanning** — QR scan or text search (name, email, affiliations, badge ID)
- **Lead list** — filterable/sortable, per-exhibitor or per-staff-member view
- **Lead detail** — custom question responses, notes (rich text), priority flag, hide/unhide
- **Export** — CSV/XLSX download of all leads for an exhibit
- **License management** — assign staff accounts (email + passcode) per max license count
- **Custom questions** — configurable per-exhibit follow-up questions (ratings, dropdowns, text)
- **Offline-first** — IndexedDB cache survives network drops; syncs on reconnect
- **PWA install** — Chrome/Android native install prompt; iOS Safari "Add to Home Screen" nudge
---
## Access Levels
Three sign-in levels are supported within this module:
| Level | How to sign in | What they can do |
|---|---|---|
| **Aether Platform Auth** | Standard Aether login (manager/trusted access) | Full admin bypass; all exhibit data |
| **Shared Exhibit Passcode** | Enter booth's `staff_passcode` | Manage licenses, view/add leads |
| **Licensed User** | Email + individual passcode from `license_li_json` | Add and manage leads for this booth |
Auth state is persisted in `$events_loc.leads.auth_exhibit_kv[exhibit_id]` (localStorage-backed).
A booth only shows in the landing page search to non-admins if it is marked `priority = true` (i.e. paid).
### `allow_tracking` Opt-In
Attendees must have `allow_tracking = true` on their badge record to be added as a lead.
Attendees without this flag are blocked at both the QR scanner and the manual search:
- QR scan shows a "Tracking Blocked" warning card (`ShieldOff` icon)
- Manual search shows an "Opt-Out" badge per result row; the "Add as Lead" button is suppressed
---
## Route Structure
```
/events/[event_id]/leads/
→ Exhibit search / landing page — find your booth
/events/[event_id]/leads/exhibit/[exhibit_id]/
→ Main exhibitor view — all 4 tabs
/events/[event_id]/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/
→ Lead detail view — edit notes, custom responses, flags
```
---
## Module Tabs
### Tab 1 — Start / Sign In
The only tab visible when not signed in as a licensed leads user.
- **Sign in with shared passcode** — grants booth management access (license management, passcode change)
- **Sign in as licensed user** — grants lead capture access (email + passcode)
- **PWA install prompt** — Chrome/Android native install button; iOS "Share → Add to Home Screen" instructions
- **License list** — shown when signed in via shared passcode or Aether admin; add/edit/remove staff slots
### Tab 2 — Add Leads
Visible only when signed in (licensed user or Aether auth).
- **Text search** — search by name, email, affiliations, badge ID
- **QR scan** — three modes (persisted per exhibit in `tab_scan_qualify`):
- **Confirm** (`rapid`) — scan, then choose per badge: **Add & Scan Next** (resets after 2s) or **Add & View Lead** (navigates to detail)
- **Auto** — no confirmation tap; adds immediately and auto-resets (high-throughput)
- **Multi** — BarcodeDetector batch scan; up to 4 badges in one frame as a confirm grid
- Previously-removed leads detected on scan — shown a "Previously Removed" card with **Restore & Scan Next** / **Restore & View Lead** buttons
- Results show "Add as Lead" or "View Lead" depending on whether already captured
- `external_person_id` and `group` resolved by auth type — see [Capture Identity](#capture-identity) below
### Tab 3 — Leads List
The main lead management view.
- **Search** — full-text across name, email, notes (local IDB fast path + API revalidation)
- **Sort** — Newest first, Oldest first, Name A→Z, Name Z→A
- **Filter by staff member** — "All Leads" or filter by individual licensed user
- **Show/hide hidden records** — toggles `hide` filter on IDB and API results
- **Export** — downloads CSV/XLSX for the exhibit (`leads_api_access` required)
### Tab 4 — Manage / Config
Exhibit configuration and app settings.
**Admin Tools** (manager_access only):
- Payment status toggle (`priority` field)
- Max licenses, small/large device counts
**Booth Profile** (all signed-in users):
- Exhibitor name, booth description (rich text)
**Access & Security**:
- View/change shared staff passcode
- Sign out button
**Lead Retrieval Config**:
- Exhibit Leads Licensees — manage staff accounts (`administrator_access` only; gap: should also allow shared-passcode users — see Known Gaps)
- Qualifiers & Questions — custom question config
- Licenses & Billing — stub (Stripe not yet implemented)
**App Settings**:
- Auto-hide header/footer toggle
- Show Payment Tab toggle
- Show Extra Details toggle
- Refresh interval (1120 seconds, default 25s), countdown timer, last-refresh timestamp
- Reload App, Clear IDB, Hard Reset (clears localStorage)
---
## Data Model
### `event_exhibit`
One exhibitor's presence at an event.
| Field | Purpose |
|---|---|
| `event_exhibit_id` | Primary / URL-safe ID |
| `name` | Exhibitor display name |
| `code` | Booth number |
| `staff_passcode` | Shared sign-in code |
| `priority` | `1` = paid/active |
| `license_max` | Max licensed staff slots |
| `license_li_json` | Array of `{ full_name, email, passcode }` |
| `leads_custom_questions_json` | Array of question definitions |
| `leads_device_sm_qty` / `leads_device_lg_qty` | Device count tracking |
### `event_exhibit_tracking`
One captured lead — links an exhibit to a badge.
| Field | Purpose |
|---|---|
| `event_exhibit_tracking_id` | Primary key |
| `event_exhibit_id` | Parent exhibit |
| `event_badge_id` | Captured attendee's badge |
| `external_person_id` | Capturing staff's email (from license) |
| `exhibitor_notes` | Rich text notes (HTML via TipTap) |
| `responses_json` | `{ [question_code]: { response: value } }` |
| `priority` | Star/flag for high-priority leads |
| `hide` | Soft-delete / hide from list |
| Denormalized badge fields | `event_badge_full_name`, `event_badge_email`, `event_badge_affiliations`, `event_badge_professional_title` |
---
## Key Files
### Routes
| File | Role |
|---|---|
| `leads/+page.svelte` | Exhibit search/landing |
| `leads/exhibit/[exhibit_id]/+page.svelte` | Main exhibitor view — orchestrates all tabs |
| `leads/exhibit/[exhibit_id]/+layout.svelte` / `+layout.ts` | Layout / data load |
| `leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte` | Lead detail |
### Components
| File | Role |
|---|---|
| `ae_tab__start.svelte` | Tab 1 — welcome, sign-in, license list |
| `ae_tab__add.svelte` | Tab 2 — QR scan + text search toggle |
| `ae_tab__manage.svelte` | Tab 4 — admin tools, booth config, app settings |
| `ae_comp__exhibit_signin.svelte` | Sign-in UI (shared passcode + licensed user) |
| `ae_comp__lead_qr_scanner.svelte` | QR scanner (rapid / qualify mode) |
| `ae_comp__lead_manual_search.svelte` | Manual badge search + add |
| `ae_comp__exhibit_tracking_search.svelte` | Lead list search/filter/sort bar |
| `ae_comp__exhibit_tracking_obj_li.svelte` | Lead list item renderer |
| `ae_comp__exhibit_license_list.svelte` | License slot manager |
| `ae_comp__exhibit_custom_questions.svelte` | Custom question config editor |
| `ae_comp__exhibit_payment.svelte` | **STUB** — Stripe placeholder |
| `ae_comp__exhibit_search.svelte` | Exhibit search on the landing page |
| `lead/ae_comp__lead_detail_form.svelte` | Custom question response editor |
### Lib Functions
| File | Role |
|---|---|
| `src/lib/ae_events/ae_events__exhibit.ts` | Exhibit load, search, create, update |
| `src/lib/ae_events/ae_events__exhibit_tracking.ts` | Tracking load, search, create, update, export |
Both aggregated into `events_func` via `src/lib/ae_events/ae_events_functions.ts`.
---
## Offline / PWA Notes
- All data is stored in `db_events` (Dexie.js) — `exhibit` and `exhibit_tracking` tables
- SWR pattern: IDB cache returned immediately; background API fetch updates IDB and triggers UI refresh
- Search: local IDB first pass (fast), then API revalidation via `search__exhibit_tracking`
- `beforeinstallprompt` event captured at module load time (`src/lib/pwa/pwa_install.svelte.ts`)
— fires within ~1 second of page load, before any Svelte `$effect` runs
- iOS Safari: no native install prompt; shows "Share → Add to Home Screen" instructions instead
---
## Capture Identity
`external_person_id` and `group` on every `event_exhibit_tracking` record record who captured the lead. Resolved at capture time in all three lead capture components (single scanner, multi scanner, manual search):
| Auth type | `kv.type` | Value stored |
| --- | --- | --- |
| Licensed exhibit user | `'licensed'` | Their email address (`kv.key`) |
| Shared exhibit passcode | `'shared'` | `'shared_passcode'` (label — raw passcode is NOT stored) |
| Aether user (admin bypass, no kv) | `undefined` | `$ae_loc.access_type` — e.g. `'trusted'`, `'manager'`, `'super'` |
`kv` = `$events_loc.leads.auth_exhibit_kv[exhibit_id]` (localStorage-persisted exhibit sign-in state).
---
## Lead Soft-Delete / Re-enable
Leads are never hard-deleted. "Remove Lead" sets `enable = false`. Key behaviors:
- **Leads list** always filters out `enable = false` records (both IDB fast-path and API results) — no flash of removed records
- **QR scanner**: if a previously-removed badge is scanned, the scanner detects it via `existing_leads_map` (IDB) or API fallback search (`search__exhibit_tracking` with `qry_badge_id` + `enabled: 'not_enabled'`) and shows the reenable card instead of an error
- **Lead detail page**: "Remove Lead" button (two-click confirm in header) sets `enable = false` and navigates back. "Restore Lead" card appears at the bottom of the right sidebar when `enable` is falsy.
- `search__exhibit_tracking` supports `qry_badge_id` param (added) and `enabled: 'not_enabled'` to find disabled records for a specific badge + exhibit combination
---
## Known Gaps
### Payment / Stripe
`ae_comp__exhibit_payment.svelte` is a stub. The Stripe integration is not implemented.
The payment tab can be hidden via "Show Payment Tab" in App Settings.
### License Management — Shared Passcode Access
Implemented. The license section in the Manage tab is visible to Aether admins and to anyone
signed in via the shared exhibit passcode (`auth_exhibit_kv[exhibit_id].type === 'shared'`).
Guard in [ae_tab__manage.svelte](src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte):
```svelte
{#if $ae_loc.administrator_access || $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.type === 'shared'}
```
---
## OSIT Admin Notes
- Mark `priority = 1` on an exhibit to make it visible in public search and to enable lead capture
- `license_max` controls how many licensed staff slots an exhibit can have
- Export endpoint: `GET /v3/action/event_exhibit/{id}/tracking_export` — requires `leads_api_access`
- Custom questions are stored per-exhibit in `leads_custom_questions_json` (not global)
- The exhibitor landing page link format: `/events/[event_id]/leads/exhibit/[exhibit_exhibit_id]/`

View File

@@ -0,0 +1,53 @@
# Project: AE Docker + CI BuildKit Implementation
**Status:** Proposed
**Goal:** Make Docker image builds for Aether cache-friendly using BuildKit/buildx and CI registry caching, while keeping local developer caches small and manageable.
Summary
- Implement a BuildKit-friendly multi-stage `Dockerfile` pattern for frontend and API images.
- Add CI `buildx` examples that push/read registry-based cache to avoid local disk bloat.
- Provide cache retention/rotation guidance and developer commands for safe pruning.
Scope
- Repository areas: `aether_container_env/`, root `Dockerfile` (if present), and CI pipeline definitions (Gitea/Drone or other).
- Non-goal: full CI pipeline migration to a new provider. This work provides CI snippets and a PR-ready set of files for your CI team.
Deliverables (this PR)
- `documentation/PROJECT__AE_Docker_CI_BuildKit_implement.md` (this file)
- `aether_container_env/Dockerfile.buildkit.example` — BuildKit-friendly multi-stage Dockerfile example.
- `aether_container_env/ci_buildx_example.sh` — standalone CI script examples (registry cache + local cache usage).
- `documentation/AE_Docker_CI_cache_policy.md` — cache rotation and prune guidance.
Tasks (implementation checklist)
- [ ] Review existing `Dockerfile`(s) under `aether_container_env/` and repository root.
- [ ] Replace/extend Dockerfile with multi-stage BuildKit-friendly layout (use example as guide).
- [ ] Ensure `.dockerignore` (already added) excludes large build artifacts.
- [ ] Add CI step using `docker buildx build` with `--cache-from` and `--cache-to` pointed at a registry cache.
- [ ] Add a scheduled job or registry lifecycle rule to delete old cache images (30 days default).
- [ ] Document required CI secrets and permissions (registry write/read) for the operations team.
- [ ] Run verification builds (dev local with BuildKit; CI runs with cache) and record timings.
Verification
- Local dev: `DOCKER_BUILDKIT=1` build with `--cache-to`/`--cache-from` shows cache hits on second run and faster build time.
- CI: subsequent CI runs log `cache hit` from `buildx` and total build time reduced vs baseline.
- Confirm registry contains `cache` image tags and that rotation job/prune removes old entries.
Notes about Gitea/CI
- Gitea does not include native Actions like GitHub; teams typically use Drone CI, Tekton, or a self-hosted runner that can execute the `docker`/`buildx` CLI.
- The provided `ci_buildx_example.sh` is intentionally provider-agnostic — pasteable into Drone, Jenkins, GitLab CI, or any shell-capable runner.
Risks & Mitigations
- Risk: Unbounded registry cache growth. Mitigation: enforce retention policy and rotation job; prefer a single `cache` tag reused by CI.
- Risk: Developers unfamiliar with BuildKit. Mitigation: examples show simple `DOCKER_BUILDKIT=1` usage and local cache prune commands.
Next steps for the container team
1. Review examples in `aether_container_env/` and adapt the Dockerfile to your runtime constraints (ssl certs, env injection, secrets).
2. Add a CI job using the `ci_buildx_example.sh` snippet; configure registry credentials as secrets.
3. Add a scheduled job to rotate/delete old cache images or configure registry lifecycle rules.
4. Run a before/after benchmark of `time npm run build:prod` inside the build stage to quantify improvement.
Files included in this PR for reference:
- `aether_container_env/Dockerfile.buildkit.example`
- `aether_container_env/ci_buildx_example.sh`
- `documentation/AE_Docker_CI_cache_policy.md`

View File

@@ -31,7 +31,7 @@
- Once satisfied, staff prints the badge.
- The key differentiator vs the review form: **the live badge render** shows exactly how
the badge will print. Attendees and staff can see changes immediately.
- Component: `ae_comp__badge_obj_view.svelte` / `ae_comp__badge_obj_view_v2.svelte`
- Component: `ae_comp__badge_obj_view.svelte`
- Route: `/events/[event_id]/badges/[badge_id]/print/`
### Permission Model — Same Logic, Both Flows
@@ -69,8 +69,8 @@ Work needed:
or whether it should share/reuse the review form component alongside the badge render.
- **Do NOT use `email_override` as the send-to address** — always use `event_badge.email`.
### 1. Auto-Scaling Badge Text (v2) — In Progress
`ae_comp__badge_obj_view_v2.svelte` using `element_fit_text.svelte` (binary search auto-scale).
### 1. Auto-Scaling Badge Text — In Progress
`ae_comp__badge_obj_view.svelte` using `element_fit_text.svelte` (binary search auto-scale).
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.
@@ -118,7 +118,7 @@ the MODULE doc TODO list was stale. `duplex` is in `properties_to_save`; v2 badg
**Files created/updated:**
- `src/lib/elements/action_fit_text.ts` — Svelte action
- `src/lib/elements/element_fit_text.svelte` — Component wrapper
- `src/routes/events/.../ae_comp__badge_obj_view_v2.svelte` — V2 badge render (canonical)
- `src/routes/events/.../ae_comp__badge_obj_view.svelte` — V2 badge render (canonical)
Debug blocks gated behind `$ae_loc.edit_mode` (hidden in production).
- `print/+page.svelte` — Always uses v2 now. v1/v2 toggle removed. Header redesigned for kiosk UX.
- `ae_comp__badge_print_controls.svelte` — Identity card at top, pronouns moved to attendee section,

View File

@@ -1,8 +1,8 @@
# Project Plan: Aether AE Obj Field Editor v3 (Consolidated)
> **Status:** 🔵 Active / Testing & Stabilization
> **Date:** February 13, 2026
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor_v3.svelte`
> **Status:** 🟡 Mostly Complete — Phase 3 items + GUIDE update remaining
> **Date:** February 13, 2026 (last updated: 2026-03-20)
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor.svelte`
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
## 1. Overview
@@ -32,12 +32,12 @@ Consolidate the legacy CRUD components into a single, high-performance "Aether O
### Phase 3: Field Type Parity (IN PROGRESS)
- [x] Support `text`, `textarea`, `select`, `tiptap`, and `checkbox`.
- [ ] Add `datetime` support using native browser pickers.
- [x] Add `datetime` support using native browser pickers`date` and `datetime-local` inputs implemented.
- [ ] Implement searchable dropdowns for the `select` type.
### Phase 4: Migration & Cleanup
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor_v3`).
- [ ] Deprecate and eventually remove `v1` and `v2` files.
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor`).
- [x] Deprecate and remove `v1` and `v2` files`element_ae_crud.svelte` and `element_ae_crud_v2.svelte` removed 2026-03-20.
- [ ] Update `GUIDE__Development.md` with the new usage patterns.
## ⚠️ Security & Reliability Stabilization (NEW)

View File

@@ -7,14 +7,14 @@
## 1. Project Overview
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (`aether_app_sveltekit`). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
**Context:** The backend transition to the generic `api_crud_v3` router is complete. Custom legacy routers have been removed. The frontend must now fully align with this pattern and provide a frictionless user experience.
**Context:** The backend transition to the generic `api_crud` router is complete. Custom legacy routers have been removed. The frontend must now fully align with this pattern and provide a frictionless user experience.
---
## 2. Core Objectives
### 🎯 Primary Goals
1. **V3 API Verification:** Ensure all CRUD operations utilize the generic `api_crud_v3` endpoints (Verified).
1. **V3 API Verification:** Ensure all CRUD operations utilize the generic `api_crud` endpoints (Verified).
2. **Quick Add UI:** Implement a specialized interface for rapid, friction-free entry creation.
3. **Append/Prepend UI:** Allow users to quickly add text to the beginning or end of existing entries without full edit mode.
4. **Interop & Portability:** Robust import/export logic for Markdown/HTML (Nextcloud Notes compatibility).
@@ -25,14 +25,14 @@ This document outlines the modernization of the Journals module UI in the Svelte
## 3. Technical Architecture
### Backend (Completed)
* **Router:** `api_crud_v3` (Generic)
* **Router:** `api_crud` (Generic)
* **Definitions:** `app/ae_obj_types_def.py` -> `app/object_definitions/journals.py`
* **Endpoints:** `/v3/crud/journal/...` and `/v3/crud/journal_entry/...`
### Frontend (In Progress)
* **State Management:** `src/lib/ae_journals/ae_journals_stores.ts`
* **Local Storage:** Dexie.js (`db_journals`)
* **API Client:** `src/lib/api/api.ts` -> `get_ae_obj_v3`
* **API Client:** `src/lib/api/api.ts` -> `get_ae_obj`
* **Export Engine:** Centralized templates in `src/lib/ae_journals/ae_journals_export_templates.ts`.
---

View File

@@ -60,26 +60,26 @@ For each file listed above, follow this standard refactoring pattern:
1. **Imports:**
* Remove imports of `create_ae_obj_crud`, `update_ae_obj_id_crud`, etc.
* Import V3 helpers: `get_ae_obj_v3`, `create_ae_obj_v3`, `update_ae_obj_v3`, `delete_ae_obj_v3`, `search_ae_obj_v3`.
* Import V3 helpers: `get_ae_obj`, `create_ae_obj`, `update_ae_obj`, `delete_ae_obj`, `search_ae_obj`.
2. **Pattern Replacement:**
* **Get (Single):**
* *Old:* `get_ae_obj_id_crud({ api_cfg, obj_type: 'event_session', obj_id: '...' })`
* *New:* `get_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: '...' })`
* *New:* `get_ae_obj({ api_cfg, obj_type: 'event_session', obj_id: '...' })`
* **Get (List):**
* *Old:* `get_ae_obj_li_for_obj_id_crud_v2(...)`
* *New:* `get_ae_obj_li_v3(...)` or `search_ae_obj_v3(...)` if complex filtering is needed.
* *New:* `get_ae_obj_li(...)` or `search_ae_obj(...)` if complex filtering is needed.
* **Update:**
* *Old:* `update_ae_obj_id_crud({ ..., fields: { name: 'New Name' } })`
* *New:* `update_ae_obj_v3({ ..., data: { name: 'New Name' } })`
* *New:* `update_ae_obj({ ..., data: { name: 'New Name' } })`
* *Note:* Ensure payload whitelisting is applied! V3 will 400 Error on unknown columns.
* **Create:**
* *Old:* `create_ae_obj_crud({ ..., fields: { ... } })`
* *New:* `create_ae_obj_v3({ ..., data: { ... } })`
* *New:* `create_ae_obj({ ..., data: { ... } })`
3. **Verification:**
* Verify the module still loads data (check Network tab for `/v3/` requests).
@@ -106,7 +106,7 @@ V3 returns detailed error metadata in the `meta.details` object.
**Symptom:** Providing a string ID in a search body that the backend maps to an integer can result in **Zero Results** if the underlying view expects a string.
**Final Solution (Body + Header Injection):**
1. **Body:** Inject the raw field name (e.g. `account_id_random`) into the `search_query.and` array to bypass automatic backend mapping.
1. **Body:** Inject the raw field name (e.g. `account_id`) into the `search_query.and` array to bypass automatic backend mapping.
2. **Headers:** Pass `headers: { 'x-account-id': ... }` manually to provide context for Auth validation.
3. **Isolation (IDAA):** Due to specific bugs in the IDAA module, it has been temporarily isolated to a legacy V2 search function (`qry_ae_obj_li__event_v2`) using `default_qry_str` for text searching, while the main module continues to use the V3 implementation.
@@ -114,7 +114,7 @@ V3 returns detailed error metadata in the `meta.details` object.
## 6. Final Cleanup
Once all checkboxes above are completed:
1. [ ] Remove legacy exports from `src/lib/api/api.ts`.
2. [ ] Delete `src/lib/ae_api/api_get__crud_obj_li_v1.ts`.
3. [ ] Delete `src/lib/ae_api/api_get__crud_obj_li_v2.ts`.
4. [ ] Delete `src/lib/ae_api/api_get__crud_obj_id.ts` (Legacy version).
1. [x] Remove legacy exports from `src/lib/api/api.ts`.
2. [x] Delete `src/lib/ae_api/api_get__crud_obj_li_v1.ts`.
3. [x] Delete `src/lib/ae_api/api_get__crud_obj_li_v2.ts`.
4. [x] Delete `src/lib/ae_api/api_get__crud_obj_id.ts` (Legacy version).

View File

@@ -1,6 +1,6 @@
# Frontend Agent Task List
> Use this file to track steps for complex features or bug fixes.
> **Status:** <20> Stable — ongoing development.
> **Status:** Stable — ongoing development.
## 🚧 Upcoming High Priority

View File

@@ -40,3 +40,6 @@
---
*Prepared by: Gemini CLI (March 17, 2026)*
---
*Archival note (2026-03-20): `element_modal_v1.svelte` (referenced in §2 as "new standard modal") was subsequently retired — it had zero active importers. Modal usage in the codebase relies on Flowbite `<Modal>` component. See `AE__UI_Component_Patterns.md` §11.*

View File

@@ -32,7 +32,9 @@ export default tseslint.config(
},
{
rules: {
'@typescript-eslint/no-unused-vars': 'warn'
'@typescript-eslint/no-unused-vars': 'warn',
// No base path configured — this rule is not applicable to this project
'svelte/no-navigation-without-resolve': 'off'
}
}
);

106
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "osit-aether-app-svelte",
"version": "3.12.08",
"version": "3.00.05",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "osit-aether-app-svelte",
"version": "3.12.08",
"version": "3.00.05",
"dependencies": {
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.0",
@@ -34,6 +34,7 @@
"lucide-svelte": "^0.*.0",
"marked": "^17.0.0",
"openai": "^6.10.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"qrcode": "^1.5.4",
"shadcn-svelte": "^1.0.11",
"svelte-persisted-store": "^0.12.0",
@@ -6161,6 +6162,16 @@
}
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/postcss-safe-parser": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
@@ -6242,7 +6253,6 @@
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -6258,13 +6268,91 @@
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz",
"integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz",
"integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==",
"license": "MIT",
"engines": {
"node": ">=20.19"
},
"peerDependencies": {
"@ianvs/prettier-plugin-sort-imports": "*",
"@prettier/plugin-hermes": "*",
"@prettier/plugin-oxc": "*",
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"@zackad/prettier-plugin-twig": "*",
"prettier": "^3.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
"prettier-plugin-multiline-arrays": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-sort-imports": "*",
"prettier-plugin-svelte": "*"
},
"peerDependenciesMeta": {
"@ianvs/prettier-plugin-sort-imports": {
"optional": true
},
"@prettier/plugin-hermes": {
"optional": true
},
"@prettier/plugin-oxc": {
"optional": true
},
"@prettier/plugin-pug": {
"optional": true
},
"@shopify/prettier-plugin-liquid": {
"optional": true
},
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"@zackad/prettier-plugin-twig": {
"optional": true
},
"prettier-plugin-astro": {
"optional": true
},
"prettier-plugin-css-order": {
"optional": true
},
"prettier-plugin-jsdoc": {
"optional": true
},
"prettier-plugin-marko": {
"optional": true
},
"prettier-plugin-multiline-arrays": {
"optional": true
},
"prettier-plugin-organize-attributes": {
"optional": true
},
"prettier-plugin-organize-imports": {
"optional": true
},
"prettier-plugin-sort-imports": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
}
}
},
"node_modules/proxy-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz",
@@ -7737,16 +7825,6 @@
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"devOptional": true,
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "osit-aether-app-svelte",
"version": "3.00.04",
"version": "3.00.07",
"description": "One Sky IT's Aether App created with Svelte, SvelteKit, Tailwind CSS, Lucide, Font Awesome, and Skeleton UI. -Scott Idem",
"homepage": "https://oneskyit.com/",
"private": true,
@@ -112,6 +112,7 @@
"lucide-svelte": "^0.*.0",
"marked": "^17.0.0",
"openai": "^6.10.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"qrcode": "^1.5.4",
"shadcn-svelte": "^1.0.11",
"svelte-persisted-store": "^0.12.0",

View File

@@ -63,50 +63,50 @@ html[data-theme='AE_Firefly_Indigo'] {
/* --- Color ramps (light mode) copied from dark block so both modes have full ramps --- */
html[data-theme='AE_Firefly_Indigo'] {
--color-primary-50: oklch(95.5% 0.040 270deg);
--color-primary-50: oklch(95.5% 0.04 270deg);
--color-primary-100: oklch(89.5% 0.072 270deg);
--color-primary-200: oklch(82.5% 0.108 269deg);
--color-primary-300: oklch(74.5% 0.135 268deg);
--color-primary-400: oklch(65.0% 0.155 267deg);
--color-primary-500: oklch(50.5% 0.160 266deg);
--color-primary-400: oklch(65% 0.155 267deg);
--color-primary-500: oklch(50.5% 0.16 266deg);
--color-primary-600: oklch(43.5% 0.152 265deg);
--color-primary-700: oklch(37.0% 0.138 264deg);
--color-primary-800: oklch(30.0% 0.120 263deg);
--color-primary-900: oklch(23.0% 0.100 262deg);
--color-primary-950: oklch(15.5% 0.080 261deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-700: oklch(37% 0.138 264deg);
--color-primary-800: oklch(30% 0.12 263deg);
--color-primary-900: oklch(23% 0.1 262deg);
--color-primary-950: oklch(15.5% 0.08 261deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-secondary-50: oklch(96.5% 0.032 297deg);
--color-secondary-50: oklch(96.5% 0.032 297deg);
--color-secondary-100: oklch(91.5% 0.058 295deg);
--color-secondary-200: oklch(85.5% 0.090 293deg);
--color-secondary-200: oklch(85.5% 0.09 293deg);
--color-secondary-300: oklch(78.5% 0.115 292deg);
--color-secondary-400: oklch(70.0% 0.132 291deg);
--color-secondary-500: oklch(60.0% 0.140 290deg);
--color-secondary-400: oklch(70% 0.132 291deg);
--color-secondary-500: oklch(60% 0.14 290deg);
--color-secondary-600: oklch(52.5% 0.135 289deg);
--color-secondary-700: oklch(45.0% 0.126 288deg);
--color-secondary-700: oklch(45% 0.126 288deg);
--color-secondary-800: oklch(37.5% 0.112 286deg);
--color-secondary-900: oklch(30.0% 0.094 284deg);
--color-secondary-950: oklch(22.0% 0.076 282deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-900: oklch(30% 0.094 284deg);
--color-secondary-950: oklch(22% 0.076 282deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-tertiary-50: oklch(96.5% 0.022 348deg);
--color-tertiary-100: oklch(91.0% 0.042 346deg);
--color-tertiary-50: oklch(96.5% 0.022 348deg);
--color-tertiary-100: oklch(91% 0.042 346deg);
--color-tertiary-200: oklch(84.5% 0.068 344deg);
--color-tertiary-300: oklch(76.5% 0.095 343deg);
--color-tertiary-400: oklch(68.0% 0.118 342deg);
--color-tertiary-400: oklch(68% 0.118 342deg);
--color-tertiary-500: oklch(57.5% 0.128 341deg);
--color-tertiary-600: oklch(50.0% 0.122 340deg);
--color-tertiary-700: oklch(43.0% 0.112 339deg);
--color-tertiary-600: oklch(50% 0.122 340deg);
--color-tertiary-700: oklch(43% 0.112 339deg);
--color-tertiary-800: oklch(35.5% 0.098 338deg);
--color-tertiary-900: oklch(28.0% 0.080 337deg);
--color-tertiary-900: oklch(28% 0.08 337deg);
--color-tertiary-950: oklch(20.5% 0.062 336deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -114,51 +114,51 @@ html[data-theme='AE_Firefly_Indigo'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-surface-50: oklch(99.0% 0.003 270deg);
--color-surface-50: oklch(99% 0.003 270deg);
--color-surface-100: oklch(96.5% 0.006 268deg);
--color-surface-200: oklch(92.5% 0.010 266deg);
--color-surface-300: oklch(87.0% 0.014 265deg);
--color-surface-200: oklch(92.5% 0.01 266deg);
--color-surface-300: oklch(87% 0.014 265deg);
--color-surface-400: oklch(78.5% 0.018 265deg);
--color-surface-500: oklch(66.5% 0.020 267deg);
--color-surface-500: oklch(66.5% 0.02 267deg);
--color-surface-600: oklch(54.5% 0.022 269deg);
--color-surface-700: oklch(42.5% 0.024 270deg);
--color-surface-800: oklch(31.0% 0.026 272deg);
--color-surface-900: oklch(20.5% 0.030 274deg);
--color-surface-950: oklch(13.0% 0.034 276deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-800: oklch(31% 0.026 272deg);
--color-surface-900: oklch(20.5% 0.03 274deg);
--color-surface-950: oklch(13% 0.034 276deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
}
@@ -182,20 +182,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* maintaining sufficient contrast at mid-range shades.
* At 500 (L≈50%): sufficient contrast with primary-50 text (≥4:1).
* =================================================================== */
--color-primary-50: oklch(95.5% 0.040 270deg);
--color-primary-50: oklch(95.5% 0.04 270deg);
--color-primary-100: oklch(89.5% 0.072 270deg);
--color-primary-200: oklch(82.5% 0.108 269deg);
--color-primary-300: oklch(74.5% 0.135 268deg);
--color-primary-400: oklch(65.0% 0.155 267deg);
--color-primary-500: oklch(50.5% 0.160 266deg);
--color-primary-400: oklch(65% 0.155 267deg);
--color-primary-500: oklch(50.5% 0.16 266deg);
--color-primary-600: oklch(43.5% 0.152 265deg);
--color-primary-700: oklch(37.0% 0.138 264deg);
--color-primary-800: oklch(30.0% 0.120 263deg);
--color-primary-900: oklch(23.0% 0.100 262deg);
--color-primary-950: oklch(15.5% 0.080 261deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-700: oklch(37% 0.138 264deg);
--color-primary-800: oklch(30% 0.12 263deg);
--color-primary-900: oklch(23% 0.1 262deg);
--color-primary-950: oklch(15.5% 0.08 261deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-100: var(--color-primary-contrast-dark);
--color-primary-contrast-200: var(--color-primary-contrast-dark);
--color-primary-contrast-300: var(--color-primary-contrast-dark);
@@ -214,20 +214,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* remaining clearly distinct from the primary.
* Used for secondary actions, badges, and soft highlights.
* =================================================================== */
--color-secondary-50: oklch(96.5% 0.032 297deg);
--color-secondary-50: oklch(96.5% 0.032 297deg);
--color-secondary-100: oklch(91.5% 0.058 295deg);
--color-secondary-200: oklch(85.5% 0.090 293deg);
--color-secondary-200: oklch(85.5% 0.09 293deg);
--color-secondary-300: oklch(78.5% 0.115 292deg);
--color-secondary-400: oklch(70.0% 0.132 291deg);
--color-secondary-500: oklch(60.0% 0.140 290deg);
--color-secondary-400: oklch(70% 0.132 291deg);
--color-secondary-500: oklch(60% 0.14 290deg);
--color-secondary-600: oklch(52.5% 0.135 289deg);
--color-secondary-700: oklch(45.0% 0.126 288deg);
--color-secondary-700: oklch(45% 0.126 288deg);
--color-secondary-800: oklch(37.5% 0.112 286deg);
--color-secondary-900: oklch(30.0% 0.094 284deg);
--color-secondary-950: oklch(22.0% 0.076 282deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-900: oklch(30% 0.094 284deg);
--color-secondary-950: oklch(22% 0.076 282deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-100: var(--color-secondary-contrast-dark);
--color-secondary-contrast-200: var(--color-secondary-contrast-dark);
--color-secondary-contrast-300: var(--color-secondary-contrast-dark);
@@ -247,20 +247,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* breaking against a deep indigo sky.
* Used for location chips, warm accents, tertiary elements.
* =================================================================== */
--color-tertiary-50: oklch(96.5% 0.022 348deg);
--color-tertiary-100: oklch(91.0% 0.042 346deg);
--color-tertiary-50: oklch(96.5% 0.022 348deg);
--color-tertiary-100: oklch(91% 0.042 346deg);
--color-tertiary-200: oklch(84.5% 0.068 344deg);
--color-tertiary-300: oklch(76.5% 0.095 343deg);
--color-tertiary-400: oklch(68.0% 0.118 342deg);
--color-tertiary-400: oklch(68% 0.118 342deg);
--color-tertiary-500: oklch(57.5% 0.128 341deg);
--color-tertiary-600: oklch(50.0% 0.122 340deg);
--color-tertiary-700: oklch(43.0% 0.112 339deg);
--color-tertiary-600: oklch(50% 0.122 340deg);
--color-tertiary-700: oklch(43% 0.112 339deg);
--color-tertiary-800: oklch(35.5% 0.098 338deg);
--color-tertiary-900: oklch(28.0% 0.080 337deg);
--color-tertiary-900: oklch(28% 0.08 337deg);
--color-tertiary-950: oklch(20.5% 0.062 336deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);
@@ -277,8 +277,8 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* Hue: ~152°. Consistent with AE_Firefly for recognizable semantic
* color meaning across OSIT themes.
* =================================================================== */
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -286,11 +286,11 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-100: var(--color-success-contrast-dark);
--color-success-contrast-200: var(--color-success-contrast-dark);
--color-success-contrast-300: var(--color-success-contrast-dark);
@@ -306,20 +306,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* WARNING — Amber Orange
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-100: var(--color-warning-contrast-dark);
--color-warning-contrast-200: var(--color-warning-contrast-dark);
--color-warning-contrast-300: var(--color-warning-contrast-dark);
@@ -335,20 +335,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* ERROR — Soft Coral/Rose
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-100: var(--color-error-contrast-dark);
--color-error-contrast-200: var(--color-error-contrast-dark);
--color-error-contrast-300: var(--color-error-contrast-dark);
@@ -370,20 +370,20 @@ html.dark[data-theme='AE_Firefly_Indigo'] {
* 50 → body-bg light: near-white with ImperceptibleISTIC purple cast
* 950 → body-bg dark: deep midnight with indigo depth
* =================================================================== */
--color-surface-50: oklch(99.0% 0.003 270deg);
--color-surface-50: oklch(99% 0.003 270deg);
--color-surface-100: oklch(96.5% 0.006 268deg);
--color-surface-200: oklch(92.5% 0.010 266deg);
--color-surface-300: oklch(87.0% 0.014 265deg);
--color-surface-200: oklch(92.5% 0.01 266deg);
--color-surface-300: oklch(87% 0.014 265deg);
--color-surface-400: oklch(78.5% 0.018 265deg);
--color-surface-500: oklch(66.5% 0.020 267deg);
--color-surface-500: oklch(66.5% 0.02 267deg);
--color-surface-600: oklch(54.5% 0.022 269deg);
--color-surface-700: oklch(42.5% 0.024 270deg);
--color-surface-800: oklch(31.0% 0.026 272deg);
--color-surface-900: oklch(20.5% 0.030 274deg);
--color-surface-950: oklch(13.0% 0.034 276deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-800: oklch(31% 0.026 272deg);
--color-surface-900: oklch(20.5% 0.03 274deg);
--color-surface-950: oklch(13% 0.034 276deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-100: var(--color-surface-contrast-dark);
--color-surface-contrast-200: var(--color-surface-contrast-dark);
--color-surface-contrast-300: var(--color-surface-contrast-dark);

View File

@@ -63,50 +63,50 @@ html[data-theme='AE_Firefly_Rainbow'] {
/* --- Color ramps (light mode) copied from dark block so both modes have full ramps --- */
html[data-theme='AE_Firefly_Rainbow'] {
--color-primary-50: oklch(97.0% 0.020 15deg);
--color-primary-100: oklch(92.0% 0.048 14deg);
--color-primary-200: oklch(86.0% 0.085 13deg);
--color-primary-300: oklch(79.0% 0.125 13deg);
--color-primary-400: oklch(71.0% 0.160 13deg);
--color-primary-500: oklch(60.0% 0.190 14deg);
--color-primary-50: oklch(97% 0.02 15deg);
--color-primary-100: oklch(92% 0.048 14deg);
--color-primary-200: oklch(86% 0.085 13deg);
--color-primary-300: oklch(79% 0.125 13deg);
--color-primary-400: oklch(71% 0.16 13deg);
--color-primary-500: oklch(60% 0.19 14deg);
--color-primary-600: oklch(52.5% 0.178 15deg);
--color-primary-700: oklch(45.0% 0.162 16deg);
--color-primary-700: oklch(45% 0.162 16deg);
--color-primary-800: oklch(37.5% 0.142 17deg);
--color-primary-900: oklch(30.0% 0.118 18deg);
--color-primary-900: oklch(30% 0.118 18deg);
--color-primary-950: oklch(22.5% 0.092 19deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-secondary-50: oklch(97.0% 0.040 152deg);
--color-secondary-50: oklch(97% 0.04 152deg);
--color-secondary-100: oklch(92.5% 0.072 150deg);
--color-secondary-200: oklch(87.0% 0.105 149deg);
--color-secondary-300: oklch(81.0% 0.132 148deg);
--color-secondary-200: oklch(87% 0.105 149deg);
--color-secondary-300: oklch(81% 0.132 148deg);
--color-secondary-400: oklch(74.5% 0.152 148deg);
--color-secondary-500: oklch(62.0% 0.160 148deg);
--color-secondary-500: oklch(62% 0.16 148deg);
--color-secondary-600: oklch(53.5% 0.148 148deg);
--color-secondary-700: oklch(45.5% 0.132 147deg);
--color-secondary-800: oklch(37.5% 0.112 146deg);
--color-secondary-900: oklch(29.5% 0.090 145deg);
--color-secondary-900: oklch(29.5% 0.09 145deg);
--color-secondary-950: oklch(21.5% 0.068 144deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-tertiary-50: oklch(96.5% 0.030 299deg);
--color-tertiary-100: oklch(91.0% 0.058 297deg);
--color-tertiary-50: oklch(96.5% 0.03 299deg);
--color-tertiary-100: oklch(91% 0.058 297deg);
--color-tertiary-200: oklch(84.5% 0.092 296deg);
--color-tertiary-300: oklch(77.0% 0.122 295deg);
--color-tertiary-300: oklch(77% 0.122 295deg);
--color-tertiary-400: oklch(68.5% 0.148 295deg);
--color-tertiary-500: oklch(57.0% 0.158 295deg);
--color-tertiary-600: oklch(49.5% 0.150 294deg);
--color-tertiary-500: oklch(57% 0.158 295deg);
--color-tertiary-600: oklch(49.5% 0.15 294deg);
--color-tertiary-700: oklch(42.5% 0.138 293deg);
--color-tertiary-800: oklch(35.5% 0.122 292deg);
--color-tertiary-900: oklch(28.5% 0.102 291deg);
--color-tertiary-950: oklch(21.0% 0.080 290deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-950: oklch(21% 0.08 290deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -114,51 +114,51 @@ html[data-theme='AE_Firefly_Rainbow'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-surface-50: oklch(99.2% 0.004 75deg);
--color-surface-100: oklch(97.0% 0.007 72deg);
--color-surface-200: oklch(93.5% 0.010 70deg);
--color-surface-50: oklch(99.2% 0.004 75deg);
--color-surface-100: oklch(97% 0.007 72deg);
--color-surface-200: oklch(93.5% 0.01 70deg);
--color-surface-300: oklch(88.5% 0.013 68deg);
--color-surface-400: oklch(81.5% 0.016 66deg);
--color-surface-500: oklch(70.5% 0.018 64deg);
--color-surface-600: oklch(59.0% 0.018 62deg);
--color-surface-600: oklch(59% 0.018 62deg);
--color-surface-700: oklch(47.5% 0.018 58deg);
--color-surface-800: oklch(35.5% 0.020 55deg);
--color-surface-900: oklch(24.5% 0.020 52deg);
--color-surface-800: oklch(35.5% 0.02 55deg);
--color-surface-900: oklch(24.5% 0.02 52deg);
--color-surface-950: oklch(15.5% 0.022 48deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
}
@@ -180,20 +180,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* Kept within sRGB gamut across the full ramp.
* At 500 (L≈60%): sufficient contrast with primary-50 text (≥4:1).
* =================================================================== */
--color-primary-50: oklch(97.0% 0.020 15deg);
--color-primary-100: oklch(92.0% 0.048 14deg);
--color-primary-200: oklch(86.0% 0.085 13deg);
--color-primary-300: oklch(79.0% 0.125 13deg);
--color-primary-400: oklch(71.0% 0.160 13deg);
--color-primary-500: oklch(60.0% 0.190 14deg);
--color-primary-50: oklch(97% 0.02 15deg);
--color-primary-100: oklch(92% 0.048 14deg);
--color-primary-200: oklch(86% 0.085 13deg);
--color-primary-300: oklch(79% 0.125 13deg);
--color-primary-400: oklch(71% 0.16 13deg);
--color-primary-500: oklch(60% 0.19 14deg);
--color-primary-600: oklch(52.5% 0.178 15deg);
--color-primary-700: oklch(45.0% 0.162 16deg);
--color-primary-700: oklch(45% 0.162 16deg);
--color-primary-800: oklch(37.5% 0.142 17deg);
--color-primary-900: oklch(30.0% 0.118 18deg);
--color-primary-900: oklch(30% 0.118 18deg);
--color-primary-950: oklch(22.5% 0.092 19deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-100: var(--color-primary-contrast-dark);
--color-primary-contrast-200: var(--color-primary-contrast-dark);
--color-primary-contrast-300: var(--color-primary-contrast-dark);
@@ -212,20 +212,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* it bridges the warm red primary and the cool violet tertiary.
* Used for secondary actions, success-adjacent highlights, badges.
* =================================================================== */
--color-secondary-50: oklch(97.0% 0.040 152deg);
--color-secondary-50: oklch(97% 0.04 152deg);
--color-secondary-100: oklch(92.5% 0.072 150deg);
--color-secondary-200: oklch(87.0% 0.105 149deg);
--color-secondary-300: oklch(81.0% 0.132 148deg);
--color-secondary-200: oklch(87% 0.105 149deg);
--color-secondary-300: oklch(81% 0.132 148deg);
--color-secondary-400: oklch(74.5% 0.152 148deg);
--color-secondary-500: oklch(62.0% 0.160 148deg);
--color-secondary-500: oklch(62% 0.16 148deg);
--color-secondary-600: oklch(53.5% 0.148 148deg);
--color-secondary-700: oklch(45.5% 0.132 147deg);
--color-secondary-800: oklch(37.5% 0.112 146deg);
--color-secondary-900: oklch(29.5% 0.090 145deg);
--color-secondary-900: oklch(29.5% 0.09 145deg);
--color-secondary-950: oklch(21.5% 0.068 144deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-100: var(--color-secondary-contrast-dark);
--color-secondary-contrast-200: var(--color-secondary-contrast-dark);
--color-secondary-contrast-300: var(--color-secondary-contrast-dark);
@@ -244,20 +244,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* brand color slots. Creates striking contrast with the warm primary.
* Used for location chips, deep accents, tertiary elements.
* =================================================================== */
--color-tertiary-50: oklch(96.5% 0.030 299deg);
--color-tertiary-100: oklch(91.0% 0.058 297deg);
--color-tertiary-50: oklch(96.5% 0.03 299deg);
--color-tertiary-100: oklch(91% 0.058 297deg);
--color-tertiary-200: oklch(84.5% 0.092 296deg);
--color-tertiary-300: oklch(77.0% 0.122 295deg);
--color-tertiary-300: oklch(77% 0.122 295deg);
--color-tertiary-400: oklch(68.5% 0.148 295deg);
--color-tertiary-500: oklch(57.0% 0.158 295deg);
--color-tertiary-600: oklch(49.5% 0.150 294deg);
--color-tertiary-500: oklch(57% 0.158 295deg);
--color-tertiary-600: oklch(49.5% 0.15 294deg);
--color-tertiary-700: oklch(42.5% 0.138 293deg);
--color-tertiary-800: oklch(35.5% 0.122 292deg);
--color-tertiary-900: oklch(28.5% 0.102 291deg);
--color-tertiary-950: oklch(21.0% 0.080 290deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-950: oklch(21% 0.08 290deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);
@@ -274,8 +274,8 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* Hue: ~152°. Consistent with AE_Firefly for recognizable semantic
* color meaning across OSIT themes.
* =================================================================== */
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -283,11 +283,11 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-100: var(--color-success-contrast-dark);
--color-success-contrast-200: var(--color-success-contrast-dark);
--color-success-contrast-300: var(--color-success-contrast-dark);
@@ -303,20 +303,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* WARNING — Amber Orange
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-100: var(--color-warning-contrast-dark);
--color-warning-contrast-200: var(--color-warning-contrast-dark);
--color-warning-contrast-300: var(--color-warning-contrast-dark);
@@ -332,20 +332,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* ERROR — Soft Coral/Rose
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-100: var(--color-error-contrast-dark);
--color-error-contrast-200: var(--color-error-contrast-dark);
--color-error-contrast-300: var(--color-error-contrast-dark);
@@ -366,20 +366,20 @@ html.dark[data-theme='AE_Firefly_Rainbow'] {
* 50 → body-bg light: warm near-white, like morning paper
* 950 → body-bg dark: deep warm charcoal, like a dim theatre
* =================================================================== */
--color-surface-50: oklch(99.2% 0.004 75deg);
--color-surface-100: oklch(97.0% 0.007 72deg);
--color-surface-200: oklch(93.5% 0.010 70deg);
--color-surface-50: oklch(99.2% 0.004 75deg);
--color-surface-100: oklch(97% 0.007 72deg);
--color-surface-200: oklch(93.5% 0.01 70deg);
--color-surface-300: oklch(88.5% 0.013 68deg);
--color-surface-400: oklch(81.5% 0.016 66deg);
--color-surface-500: oklch(70.5% 0.018 64deg);
--color-surface-600: oklch(59.0% 0.018 62deg);
--color-surface-600: oklch(59% 0.018 62deg);
--color-surface-700: oklch(47.5% 0.018 58deg);
--color-surface-800: oklch(35.5% 0.020 55deg);
--color-surface-900: oklch(24.5% 0.020 52deg);
--color-surface-800: oklch(35.5% 0.02 55deg);
--color-surface-900: oklch(24.5% 0.02 52deg);
--color-surface-950: oklch(15.5% 0.022 48deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-100: var(--color-surface-contrast-dark);
--color-surface-contrast-200: var(--color-surface-contrast-dark);
--color-surface-contrast-300: var(--color-surface-contrast-dark);

View File

@@ -60,50 +60,50 @@ html[data-theme='AE_Firefly_SteelBlue'] {
--radius-base: 0.375rem;
--radius-container: 0.875rem;
/* --- Color ramps (light mode) copied from dark block so both modes have full ramps --- */
--color-primary-50: oklch(96.5% 0.022 214deg);
--color-primary-100: oklch(91.0% 0.045 213deg);
--color-primary-50: oklch(96.5% 0.022 214deg);
--color-primary-100: oklch(91% 0.045 213deg);
--color-primary-200: oklch(84.5% 0.072 212deg);
--color-primary-300: oklch(76.5% 0.097 212deg);
--color-primary-400: oklch(67.0% 0.115 213deg);
--color-primary-500: oklch(56.0% 0.115 214deg);
--color-primary-600: oklch(49.0% 0.112 214deg);
--color-primary-400: oklch(67% 0.115 213deg);
--color-primary-500: oklch(56% 0.115 214deg);
--color-primary-600: oklch(49% 0.112 214deg);
--color-primary-700: oklch(41.5% 0.105 213deg);
--color-primary-800: oklch(34.0% 0.095 212deg);
--color-primary-900: oklch(26.5% 0.080 211deg);
--color-primary-800: oklch(34% 0.095 212deg);
--color-primary-900: oklch(26.5% 0.08 211deg);
--color-primary-950: oklch(18.5% 0.065 210deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-secondary-50: oklch(97.5% 0.055 56deg);
--color-secondary-100: oklch(93.5% 0.090 55deg);
--color-secondary-200: oklch(89.5% 0.120 54deg);
--color-secondary-50: oklch(97.5% 0.055 56deg);
--color-secondary-100: oklch(93.5% 0.09 55deg);
--color-secondary-200: oklch(89.5% 0.12 54deg);
--color-secondary-300: oklch(85.5% 0.148 53deg);
--color-secondary-400: oklch(81.5% 0.162 52deg);
--color-secondary-500: oklch(76.5% 0.162 51deg);
--color-secondary-600: oklch(68.5% 0.152 50deg);
--color-secondary-700: oklch(60.5% 0.138 49deg);
--color-secondary-800: oklch(52.0% 0.122 48deg);
--color-secondary-800: oklch(52% 0.122 48deg);
--color-secondary-900: oklch(43.5% 0.102 47deg);
--color-secondary-950: oklch(35.0% 0.084 46deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-950: oklch(35% 0.084 46deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-tertiary-50: oklch(95.5% 0.025 232deg);
--color-tertiary-50: oklch(95.5% 0.025 232deg);
--color-tertiary-100: oklch(89.5% 0.048 231deg);
--color-tertiary-200: oklch(82.5% 0.072 230deg);
--color-tertiary-300: oklch(74.5% 0.095 229deg);
--color-tertiary-400: oklch(65.5% 0.120 229deg);
--color-tertiary-400: oklch(65.5% 0.12 229deg);
--color-tertiary-500: oklch(54.5% 0.135 230deg);
--color-tertiary-600: oklch(47.0% 0.132 230deg);
--color-tertiary-600: oklch(47% 0.132 230deg);
--color-tertiary-700: oklch(39.5% 0.122 229deg);
--color-tertiary-800: oklch(32.0% 0.108 228deg);
--color-tertiary-900: oklch(25.0% 0.090 227deg);
--color-tertiary-800: oklch(32% 0.108 228deg);
--color-tertiary-900: oklch(25% 0.09 227deg);
--color-tertiary-950: oklch(17.5% 0.072 226deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -111,51 +111,51 @@ html[data-theme='AE_Firefly_SteelBlue'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-surface-50: oklch(99.0% 0.004 220deg);
--color-surface-50: oklch(99% 0.004 220deg);
--color-surface-100: oklch(96.5% 0.008 218deg);
--color-surface-200: oklch(92.5% 0.012 217deg);
--color-surface-300: oklch(87.0% 0.016 216deg);
--color-surface-400: oklch(78.5% 0.020 215deg);
--color-surface-300: oklch(87% 0.016 216deg);
--color-surface-400: oklch(78.5% 0.02 215deg);
--color-surface-500: oklch(66.5% 0.022 217deg);
--color-surface-600: oklch(54.5% 0.025 220deg);
--color-surface-700: oklch(42.5% 0.028 223deg);
--color-surface-800: oklch(31.0% 0.032 226deg);
--color-surface-800: oklch(31% 0.032 226deg);
--color-surface-900: oklch(20.5% 0.035 228deg);
--color-surface-950: oklch(13.0% 0.040 232deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-950: oklch(13% 0.04 232deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
}
@@ -175,20 +175,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* Approx: #4682B4 (CSS SteelBlue) sits at oklch(56%, 0.113, 214°).
* At 500 (L≈56%): sufficient contrast with primary-50 text (≥4:1).
* =================================================================== */
--color-primary-50: oklch(96.5% 0.022 214deg);
--color-primary-100: oklch(91.0% 0.045 213deg);
--color-primary-50: oklch(96.5% 0.022 214deg);
--color-primary-100: oklch(91% 0.045 213deg);
--color-primary-200: oklch(84.5% 0.072 212deg);
--color-primary-300: oklch(76.5% 0.097 212deg);
--color-primary-400: oklch(67.0% 0.115 213deg);
--color-primary-500: oklch(56.0% 0.115 214deg);
--color-primary-600: oklch(49.0% 0.112 214deg);
--color-primary-400: oklch(67% 0.115 213deg);
--color-primary-500: oklch(56% 0.115 214deg);
--color-primary-600: oklch(49% 0.112 214deg);
--color-primary-700: oklch(41.5% 0.105 213deg);
--color-primary-800: oklch(34.0% 0.095 212deg);
--color-primary-900: oklch(26.5% 0.080 211deg);
--color-primary-800: oklch(34% 0.095 212deg);
--color-primary-900: oklch(26.5% 0.08 211deg);
--color-primary-950: oklch(18.5% 0.065 210deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-100: var(--color-primary-contrast-dark);
--color-primary-contrast-200: var(--color-primary-contrast-dark);
--color-primary-contrast-300: var(--color-primary-contrast-dark);
@@ -206,20 +206,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* of steel blue. The classic "metal on metal" contrast pairing —
* used for secondary actions, badges, and call-to-action highlights.
* =================================================================== */
--color-secondary-50: oklch(97.5% 0.055 56deg);
--color-secondary-100: oklch(93.5% 0.090 55deg);
--color-secondary-200: oklch(89.5% 0.120 54deg);
--color-secondary-50: oklch(97.5% 0.055 56deg);
--color-secondary-100: oklch(93.5% 0.09 55deg);
--color-secondary-200: oklch(89.5% 0.12 54deg);
--color-secondary-300: oklch(85.5% 0.148 53deg);
--color-secondary-400: oklch(81.5% 0.162 52deg);
--color-secondary-500: oklch(76.5% 0.162 51deg);
--color-secondary-600: oklch(68.5% 0.152 50deg);
--color-secondary-700: oklch(60.5% 0.138 49deg);
--color-secondary-800: oklch(52.0% 0.122 48deg);
--color-secondary-800: oklch(52% 0.122 48deg);
--color-secondary-900: oklch(43.5% 0.102 47deg);
--color-secondary-950: oklch(35.0% 0.084 46deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-950: oklch(35% 0.084 46deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-100: var(--color-secondary-contrast-dark);
--color-secondary-contrast-200: var(--color-secondary-contrast-dark);
--color-secondary-contrast-300: var(--color-secondary-contrast-dark);
@@ -237,20 +237,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* like the heavy cobalt-blue depths under polished chrome.
* Used for accents, location chips, and depth elements.
* =================================================================== */
--color-tertiary-50: oklch(95.5% 0.025 232deg);
--color-tertiary-50: oklch(95.5% 0.025 232deg);
--color-tertiary-100: oklch(89.5% 0.048 231deg);
--color-tertiary-200: oklch(82.5% 0.072 230deg);
--color-tertiary-300: oklch(74.5% 0.095 229deg);
--color-tertiary-400: oklch(65.5% 0.120 229deg);
--color-tertiary-400: oklch(65.5% 0.12 229deg);
--color-tertiary-500: oklch(54.5% 0.135 230deg);
--color-tertiary-600: oklch(47.0% 0.132 230deg);
--color-tertiary-600: oklch(47% 0.132 230deg);
--color-tertiary-700: oklch(39.5% 0.122 229deg);
--color-tertiary-800: oklch(32.0% 0.108 228deg);
--color-tertiary-900: oklch(25.0% 0.090 227deg);
--color-tertiary-800: oklch(32% 0.108 228deg);
--color-tertiary-900: oklch(25% 0.09 227deg);
--color-tertiary-950: oklch(17.5% 0.072 226deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);
@@ -267,8 +267,8 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* Hue: ~152°. Consistent with AE_Firefly for recognizable semantic
* color meaning across OSIT themes.
* =================================================================== */
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -276,11 +276,11 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-100: var(--color-success-contrast-dark);
--color-success-contrast-200: var(--color-success-contrast-dark);
--color-success-contrast-300: var(--color-success-contrast-dark);
@@ -296,20 +296,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* WARNING — Amber Orange
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-100: var(--color-warning-contrast-dark);
--color-warning-contrast-200: var(--color-warning-contrast-dark);
--color-warning-contrast-300: var(--color-warning-contrast-dark);
@@ -325,20 +325,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* ERROR — Soft Coral/Rose
* Consistent with AE_Firefly for recognizable semantic meaning.
* =================================================================== */
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-100: var(--color-error-contrast-dark);
--color-error-contrast-200: var(--color-error-contrast-dark);
--color-error-contrast-300: var(--color-error-contrast-dark);
@@ -359,20 +359,20 @@ html.dark[data-theme='AE_Firefly_SteelBlue'] {
* 50 → body-bg light: brilliant near-white with a chrome whisper
* 950 → body-bg dark: deep gunmetal with subtle cool-blue depth
* =================================================================== */
--color-surface-50: oklch(99.0% 0.004 220deg);
--color-surface-50: oklch(99% 0.004 220deg);
--color-surface-100: oklch(96.5% 0.008 218deg);
--color-surface-200: oklch(92.5% 0.012 217deg);
--color-surface-300: oklch(87.0% 0.016 216deg);
--color-surface-400: oklch(78.5% 0.020 215deg);
--color-surface-300: oklch(87% 0.016 216deg);
--color-surface-400: oklch(78.5% 0.02 215deg);
--color-surface-500: oklch(66.5% 0.022 217deg);
--color-surface-600: oklch(54.5% 0.025 220deg);
--color-surface-700: oklch(42.5% 0.028 223deg);
--color-surface-800: oklch(31.0% 0.032 226deg);
--color-surface-800: oklch(31% 0.032 226deg);
--color-surface-900: oklch(20.5% 0.035 228deg);
--color-surface-950: oklch(13.0% 0.040 232deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-950: oklch(13% 0.04 232deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-100: var(--color-surface-contrast-dark);
--color-surface-contrast-200: var(--color-surface-contrast-dark);
--color-surface-contrast-300: var(--color-surface-contrast-dark);

View File

@@ -63,50 +63,50 @@ html[data-theme='AE_Firefly'] {
/* --- Color ramps (light mode) copied from dark block so both modes have full ramps --- */
html[data-theme='AE_Firefly'] {
--color-primary-50: oklch(96.5% 0.025 192deg);
--color-primary-100: oklch(91.0% 0.050 190deg);
--color-primary-50: oklch(96.5% 0.025 192deg);
--color-primary-100: oklch(91% 0.05 190deg);
--color-primary-200: oklch(84.5% 0.078 188deg);
--color-primary-300: oklch(76.5% 0.105 186deg);
--color-primary-400: oklch(67.5% 0.125 185deg);
--color-primary-500: oklch(50.5% 0.130 184deg);
--color-primary-600: oklch(44.0% 0.125 183deg);
--color-primary-500: oklch(50.5% 0.13 184deg);
--color-primary-600: oklch(44% 0.125 183deg);
--color-primary-700: oklch(37.5% 0.115 182deg);
--color-primary-800: oklch(30.5% 0.105 181deg);
--color-primary-900: oklch(23.5% 0.090 180deg);
--color-primary-950: oklch(16.0% 0.075 179deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-900: oklch(23.5% 0.09 180deg);
--color-primary-950: oklch(16% 0.075 179deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-secondary-50: oklch(97.5% 0.060 102deg);
--color-secondary-50: oklch(97.5% 0.06 102deg);
--color-secondary-100: oklch(93.5% 0.095 100deg);
--color-secondary-200: oklch(89.5% 0.128 98deg);
--color-secondary-300: oklch(85.5% 0.155 95deg);
--color-secondary-400: oklch(81.0% 0.170 93deg);
--color-secondary-500: oklch(76.0% 0.170 90deg);
--color-secondary-600: oklch(68.5% 0.160 87deg);
--color-secondary-400: oklch(81% 0.17 93deg);
--color-secondary-500: oklch(76% 0.17 90deg);
--color-secondary-600: oklch(68.5% 0.16 87deg);
--color-secondary-700: oklch(60.5% 0.145 85deg);
--color-secondary-800: oklch(52.0% 0.130 83deg);
--color-secondary-900: oklch(43.5% 0.110 81deg);
--color-secondary-950: oklch(35.0% 0.090 79deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-800: oklch(52% 0.13 83deg);
--color-secondary-900: oklch(43.5% 0.11 81deg);
--color-secondary-950: oklch(35% 0.09 79deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-tertiary-50: oklch(95.5% 0.042 283deg);
--color-tertiary-100: oklch(89.0% 0.068 281deg);
--color-tertiary-50: oklch(95.5% 0.042 283deg);
--color-tertiary-100: oklch(89% 0.068 281deg);
--color-tertiary-200: oklch(81.5% 0.092 279deg);
--color-tertiary-300: oklch(73.5% 0.112 278deg);
--color-tertiary-400: oklch(65.0% 0.132 277deg);
--color-tertiary-400: oklch(65% 0.132 277deg);
--color-tertiary-500: oklch(55.5% 0.142 276deg);
--color-tertiary-600: oklch(48.5% 0.138 275deg);
--color-tertiary-700: oklch(41.5% 0.128 274deg);
--color-tertiary-800: oklch(34.5% 0.112 273deg);
--color-tertiary-900: oklch(27.5% 0.098 272deg);
--color-tertiary-950: oklch(20.0% 0.082 271deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-950: oklch(20% 0.082 271deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -114,51 +114,51 @@ html[data-theme='AE_Firefly'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-surface-50: oklch(99.2% 0.003 220deg);
--color-surface-100: oklch(97.0% 0.006 217deg);
--color-surface-50: oklch(99.2% 0.003 220deg);
--color-surface-100: oklch(97% 0.006 217deg);
--color-surface-200: oklch(93.5% 0.009 215deg);
--color-surface-300: oklch(88.5% 0.012 213deg);
--color-surface-400: oklch(81.5% 0.015 212deg);
--color-surface-500: oklch(70.5% 0.016 215deg);
--color-surface-600: oklch(59.0% 0.018 218deg);
--color-surface-700: oklch(47.5% 0.020 222deg);
--color-surface-600: oklch(59% 0.018 218deg);
--color-surface-700: oklch(47.5% 0.02 222deg);
--color-surface-800: oklch(30.5% 0.022 226deg);
--color-surface-900: oklch(24.5% 0.025 229deg);
--color-surface-950: oklch(15.5% 0.028 233deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
}
@@ -174,20 +174,20 @@ html.dark[data-theme='AE_Firefly'] {
* PRIMARY — Luminescent Firefly Teal
* ...existing code...
*/
--color-primary-50: oklch(96.5% 0.025 192deg);
--color-primary-100: oklch(91.0% 0.050 190deg);
--color-primary-50: oklch(96.5% 0.025 192deg);
--color-primary-100: oklch(91% 0.05 190deg);
--color-primary-200: oklch(84.5% 0.078 188deg);
--color-primary-300: oklch(76.5% 0.105 186deg);
--color-primary-400: oklch(67.5% 0.125 185deg);
--color-primary-500: oklch(50.5% 0.130 184deg);
--color-primary-600: oklch(44.0% 0.125 183deg);
--color-primary-500: oklch(50.5% 0.13 184deg);
--color-primary-600: oklch(44% 0.125 183deg);
--color-primary-700: oklch(37.5% 0.115 182deg);
--color-primary-800: oklch(30.5% 0.105 181deg);
--color-primary-900: oklch(23.5% 0.090 180deg);
--color-primary-950: oklch(16.0% 0.075 179deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-900: oklch(23.5% 0.09 180deg);
--color-primary-950: oklch(16% 0.075 179deg);
--color-primary-contrast-dark: var(--color-primary-950);
--color-primary-contrast-light: var(--color-primary-50);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-50: var(--color-primary-contrast-dark);
--color-primary-contrast-100: var(--color-primary-contrast-dark);
--color-primary-contrast-200: var(--color-primary-contrast-dark);
--color-primary-contrast-300: var(--color-primary-contrast-dark);
@@ -200,20 +200,20 @@ html.dark[data-theme='AE_Firefly'] {
--color-primary-contrast-950: var(--color-primary-contrast-light);
/* ...existing code for secondary, tertiary, success, warning, error, surface... */
--color-secondary-50: oklch(97.5% 0.060 102deg);
--color-secondary-50: oklch(97.5% 0.06 102deg);
--color-secondary-100: oklch(93.5% 0.095 100deg);
--color-secondary-200: oklch(89.5% 0.128 98deg);
--color-secondary-300: oklch(85.5% 0.155 95deg);
--color-secondary-400: oklch(81.0% 0.170 93deg);
--color-secondary-500: oklch(76.0% 0.170 90deg);
--color-secondary-600: oklch(68.5% 0.160 87deg);
--color-secondary-400: oklch(81% 0.17 93deg);
--color-secondary-500: oklch(76% 0.17 90deg);
--color-secondary-600: oklch(68.5% 0.16 87deg);
--color-secondary-700: oklch(60.5% 0.145 85deg);
--color-secondary-800: oklch(52.0% 0.130 83deg);
--color-secondary-900: oklch(43.5% 0.110 81deg);
--color-secondary-950: oklch(35.0% 0.090 79deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-800: oklch(52% 0.13 83deg);
--color-secondary-900: oklch(43.5% 0.11 81deg);
--color-secondary-950: oklch(35% 0.09 79deg);
--color-secondary-contrast-dark: var(--color-secondary-950);
--color-secondary-contrast-light: var(--color-secondary-50);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-50: var(--color-secondary-contrast-dark);
--color-secondary-contrast-100: var(--color-secondary-contrast-dark);
--color-secondary-contrast-200: var(--color-secondary-contrast-dark);
--color-secondary-contrast-300: var(--color-secondary-contrast-dark);
@@ -225,20 +225,20 @@ html.dark[data-theme='AE_Firefly'] {
--color-secondary-contrast-900: var(--color-secondary-contrast-light);
--color-secondary-contrast-950: var(--color-secondary-contrast-light);
--color-tertiary-50: oklch(95.5% 0.042 283deg);
--color-tertiary-100: oklch(89.0% 0.068 281deg);
--color-tertiary-50: oklch(95.5% 0.042 283deg);
--color-tertiary-100: oklch(89% 0.068 281deg);
--color-tertiary-200: oklch(81.5% 0.092 279deg);
--color-tertiary-300: oklch(73.5% 0.112 278deg);
--color-tertiary-400: oklch(65.0% 0.132 277deg);
--color-tertiary-400: oklch(65% 0.132 277deg);
--color-tertiary-500: oklch(55.5% 0.142 276deg);
--color-tertiary-600: oklch(48.5% 0.138 275deg);
--color-tertiary-700: oklch(41.5% 0.128 274deg);
--color-tertiary-800: oklch(34.5% 0.112 273deg);
--color-tertiary-900: oklch(27.5% 0.098 272deg);
--color-tertiary-950: oklch(20.0% 0.082 271deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-950: oklch(20% 0.082 271deg);
--color-tertiary-contrast-dark: var(--color-tertiary-950);
--color-tertiary-contrast-light: var(--color-tertiary-50);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-50: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-100: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-200: var(--color-tertiary-contrast-dark);
--color-tertiary-contrast-300: var(--color-tertiary-contrast-dark);
@@ -250,8 +250,8 @@ html.dark[data-theme='AE_Firefly'] {
--color-tertiary-contrast-900: var(--color-tertiary-contrast-light);
--color-tertiary-contrast-950: var(--color-tertiary-contrast-light);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152.00deg);
--color-success-50: oklch(95.77% 0.05 152.69deg);
--color-success-100: oklch(91.59% 0.06 152deg);
--color-success-200: oklch(87.45% 0.08 152.08deg);
--color-success-300: oklch(83.57% 0.09 150.85deg);
--color-success-400: oklch(79.47% 0.11 150.71deg);
@@ -259,11 +259,11 @@ html.dark[data-theme='AE_Firefly'] {
--color-success-600: oklch(67.65% 0.11 149.94deg);
--color-success-700: oklch(59.71% 0.09 150.42deg);
--color-success-800: oklch(51.74% 0.08 150.24deg);
--color-success-900: oklch(43.20% 0.06 151.12deg);
--color-success-950: oklch(34.20% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-900: oklch(43.2% 0.06 151.12deg);
--color-success-950: oklch(34.2% 0.04 151.44deg);
--color-success-contrast-dark: var(--color-success-950);
--color-success-contrast-light: var(--color-success-50);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-50: var(--color-success-contrast-dark);
--color-success-contrast-100: var(--color-success-contrast-dark);
--color-success-contrast-200: var(--color-success-contrast-dark);
--color-success-contrast-300: var(--color-success-contrast-dark);
@@ -275,20 +275,20 @@ html.dark[data-theme='AE_Firefly'] {
--color-success-contrast-900: var(--color-success-contrast-light);
--color-success-contrast-950: var(--color-success-contrast-light);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.090 75deg);
--color-warning-200: oklch(89.5% 0.120 73deg);
--color-warning-50: oklch(97.5% 0.065 78deg);
--color-warning-100: oklch(93.5% 0.09 75deg);
--color-warning-200: oklch(89.5% 0.12 73deg);
--color-warning-300: oklch(85.5% 0.145 70deg);
--color-warning-400: oklch(81.5% 0.160 67deg);
--color-warning-500: oklch(77.0% 0.165 65deg);
--color-warning-400: oklch(81.5% 0.16 67deg);
--color-warning-500: oklch(77% 0.165 65deg);
--color-warning-600: oklch(69.5% 0.155 64deg);
--color-warning-700: oklch(61.5% 0.140 63deg);
--color-warning-700: oklch(61.5% 0.14 63deg);
--color-warning-800: oklch(53.5% 0.125 62deg);
--color-warning-900: oklch(45.0% 0.105 61deg);
--color-warning-950: oklch(37.0% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-900: oklch(45% 0.105 61deg);
--color-warning-950: oklch(37% 0.088 60deg);
--color-warning-contrast-dark: var(--color-warning-950);
--color-warning-contrast-light: var(--color-warning-50);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-50: var(--color-warning-contrast-dark);
--color-warning-contrast-100: var(--color-warning-contrast-dark);
--color-warning-contrast-200: var(--color-warning-contrast-dark);
--color-warning-contrast-300: var(--color-warning-contrast-dark);
@@ -300,20 +300,20 @@ html.dark[data-theme='AE_Firefly'] {
--color-warning-contrast-900: var(--color-warning-contrast-light);
--color-warning-contrast-950: var(--color-warning-contrast-light);
--color-error-50: oklch(95.0% 0.040 18deg);
--color-error-100: oklch(88.0% 0.070 20deg);
--color-error-200: oklch(80.0% 0.105 21deg);
--color-error-300: oklch(72.0% 0.140 22deg);
--color-error-400: oklch(64.5% 0.170 23deg);
--color-error-50: oklch(95% 0.04 18deg);
--color-error-100: oklch(88% 0.07 20deg);
--color-error-200: oklch(80% 0.105 21deg);
--color-error-300: oklch(72% 0.14 22deg);
--color-error-400: oklch(64.5% 0.17 23deg);
--color-error-500: oklch(57.5% 0.195 24deg);
--color-error-600: oklch(51.5% 0.182 25deg);
--color-error-700: oklch(45.5% 0.165 26deg);
--color-error-800: oklch(39.5% 0.148 27deg);
--color-error-900: oklch(33.0% 0.128 28deg);
--color-error-900: oklch(33% 0.128 28deg);
--color-error-950: oklch(26.5% 0.108 29deg);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-dark: var(--color-error-950);
--color-error-contrast-light: var(--color-error-50);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-50: var(--color-error-contrast-dark);
--color-error-contrast-100: var(--color-error-contrast-dark);
--color-error-contrast-200: var(--color-error-contrast-dark);
--color-error-contrast-300: var(--color-error-contrast-dark);
@@ -325,20 +325,20 @@ html.dark[data-theme='AE_Firefly'] {
--color-error-contrast-900: var(--color-error-contrast-light);
--color-error-contrast-950: var(--color-error-contrast-light);
--color-surface-50: oklch(99.2% 0.003 220deg);
--color-surface-100: oklch(97.0% 0.006 217deg);
--color-surface-50: oklch(99.2% 0.003 220deg);
--color-surface-100: oklch(97% 0.006 217deg);
--color-surface-200: oklch(93.5% 0.009 215deg);
--color-surface-300: oklch(88.5% 0.012 213deg);
--color-surface-400: oklch(81.5% 0.015 212deg);
--color-surface-500: oklch(70.5% 0.016 215deg);
--color-surface-600: oklch(59.0% 0.018 218deg);
--color-surface-700: oklch(47.5% 0.020 222deg);
--color-surface-600: oklch(59% 0.018 218deg);
--color-surface-700: oklch(47.5% 0.02 222deg);
--color-surface-800: oklch(35.5% 0.022 226deg);
--color-surface-900: oklch(24.5% 0.025 229deg);
--color-surface-950: oklch(15.5% 0.028 233deg);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-dark: var(--color-surface-950);
--color-surface-contrast-light: var(--color-surface-50);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-50: var(--color-surface-contrast-dark);
--color-surface-contrast-100: var(--color-surface-contrast-dark);
--color-surface-contrast-200: var(--color-surface-contrast-dark);
--color-surface-contrast-300: var(--color-surface-contrast-dark);

View File

@@ -10,12 +10,15 @@
/* Sync native browser control rendering (select dropdowns, scrollbars, etc.)
with the app's dark/light mode toggle. Without this, native controls follow
the OS theme rather than the app's .dark/.light class on <html>. */
html.dark { color-scheme: dark; }
html.light { color-scheme: light; }
html.dark {
color-scheme: dark;
}
html.light {
color-scheme: light;
}
@import '@skeletonlabs/skeleton';
/* Register Preset Themes */
/* @import '@skeletonlabs/skeleton/themes/{theme-name}'; */
@import '@skeletonlabs/skeleton/themes/cerberus';
@@ -154,13 +157,13 @@ html.light { color-scheme: light; }
.dark .input:not([type='checkbox']):not([type='radio']):not([type='range']),
.dark .select,
.dark .textarea {
color: rgb(243 244 246); /* gray-100 */
color: rgb(243 244 246); /* gray-100 */
background-color: rgb(55 65 81); /* gray-700 */
border-color: rgb(75 85 99); /* gray-600 */
border-color: rgb(75 85 99); /* gray-600 */
}
.dark .input::placeholder,
.dark .textarea::placeholder {
color: rgb(156 163 175); /* gray-400 — legible at reduced opacity */
color: rgb(156 163 175); /* gray-400 — legible at reduced opacity */
}
/* Option elements in dark selects — forces browser native dark chrome */
.dark .select option {
@@ -198,8 +201,12 @@ body {
/* Font size accessibility modes — cycled via the font size button in the sys menu.
Applied as a class on <html> by the layout DOM effect.
The 'default' mode has no class (browser default, typically 16px). */
html.font-size-larger { font-size: 112.5%; } /* ~18px base */
html.font-size-smaller { font-size: 87.5%; } /* ~14px base */
html.font-size-larger {
font-size: 112.5%;
} /* ~18px base */
html.font-size-smaller {
font-size: 87.5%;
} /* ~14px base */
html.super_access #appShell {
background-color: hsla(0, 100%, 50%, 0.5);
@@ -356,51 +363,51 @@ html.trusted_access #appShell {
/* @apply preset-tonal-primary border border-primary-500 transition-all; */
}
.ae_btn_secondary {
@apply preset-tonal-secondary border border-secondary-500 transition-all;
@apply preset-tonal-secondary border-secondary-500 border transition-all;
/* hover:preset-filled-secondary-500 */
}
.ae_btn_tertiary {
@apply preset-tonal-tertiary border border-tertiary-500 transition-all;
@apply preset-tonal-tertiary border-tertiary-500 border transition-all;
}
.ae_btn_success {
@apply preset-tonal-success border border-success-500 transition-all;
@apply preset-tonal-success border-success-500 border transition-all;
}
.ae_btn_warning {
@apply preset-tonal-warning border border-warning-500 text-warning-950-50 transition-all;
@apply preset-tonal-warning border-warning-500 text-warning-950-50 border transition-all;
}
.ae_btn_error {
@apply preset-tonal-error border border-error-500 transition-all;
@apply preset-tonal-error border-error-500 border transition-all;
}
.ae_btn_surface {
@apply preset-tonal-surface border border-surface-500 transition-all;
@apply preset-tonal-surface border-surface-500 border transition-all;
}
/* Buttons customized for Aether using Skeleton Tailwind preset classes */
.ae_btn_info {
@apply border text-cyan-950 dark:text-cyan-50 bg-cyan-50 dark:bg-cyan-950 border-cyan-100 dark:border-cyan-900 hover:bg-cyan-200 hover:dark:bg-cyan-800 transition-all;
@apply border border-cyan-100 bg-cyan-50 text-cyan-950 transition-all hover:bg-cyan-200 dark:border-cyan-900 dark:bg-cyan-950 dark:text-cyan-50 hover:dark:bg-cyan-800;
}
/* Buttons are for filled and outlined presets */
.ae_btn_secondary_filled {
@apply preset-filled-secondary-200-800 border border-secondary-500 transition-all;
@apply preset-filled-secondary-200-800 border-secondary-500 border transition-all;
/* hover:preset-filled-secondary-500 */
}
.ae_btn_secondary_outlined {
@apply preset-outlined-secondary-200-800 hover:preset-filled-secondary-400-600 text-secondary-950-50 transition-all;
}
.ae_btn_success_filled {
@apply preset-filled-success-200-800 border border-success-500 transition-all;
@apply preset-filled-success-200-800 border-success-500 border transition-all;
}
.ae_btn_success_outlined {
@apply preset-outlined-success-200-800 hover:preset-filled-success-400-600 text-success-950-50 transition-all;
}
.ae_btn_warning_filled {
@apply preset-filled-warning-200-800 border border-warning-500 transition-all;
@apply preset-filled-warning-200-800 border-warning-500 border transition-all;
}
.ae_btn_warning_outlined {
@apply preset-outlined-warning-200-800 hover:preset-filled-warning-400-600 text-warning-950-50 transition-all;
}
.ae_btn_surface_filled {
@apply preset-filled-surface-200-800 border border-surface-500 transition-all;
@apply preset-filled-surface-200-800 border-surface-500 border transition-all;
}
.ae_btn_surface_outlined {
@apply preset-outlined-surface-200-800 hover:preset-filled-surface-400-600 text-surface-950-50 transition-all;
@@ -409,10 +416,10 @@ html.trusted_access #appShell {
@apply preset-outlined-error-200-800 hover:preset-filled-error-400-600 text-error-950-50 transition-all;
}
.ae_btn_info_filled {
@apply border text-cyan-950 dark:text-cyan-50 bg-cyan-200 dark:bg-cyan-800 border-cyan-200 dark:border-cyan-800 transition-all;
@apply border border-cyan-200 bg-cyan-200 text-cyan-950 transition-all dark:border-cyan-800 dark:bg-cyan-800 dark:text-cyan-50;
}
.ae_btn_info_outlined {
@apply border text-cyan-950 dark:text-cyan-50 bg-cyan-50 dark:bg-cyan-950 border-cyan-200 dark:border-cyan-800 transition-all;
@apply border border-cyan-200 bg-cyan-50 text-cyan-950 transition-all dark:border-cyan-800 dark:bg-cyan-950 dark:text-cyan-50;
}
/* Containers customized for Aether using Skeleton Tailwind preset classes */
@@ -436,7 +443,7 @@ html.trusted_access #appShell {
.ae_module_header {
/* LCI request 3a5997 */
/* bg-gray-300 */
@apply preset-tonal-surface rounded-md flex flex-col md:flex-row flex-wrap gap-0.25 items-center justify-between w-full max-w-7xl p-1 px-2;
@apply preset-tonal-surface flex w-full max-w-7xl flex-col flex-wrap items-center justify-between gap-0.25 rounded-md p-1 px-2 md:flex-row;
}
[data-theme='AE_c_LCI'] .ae_module_header {
@@ -453,32 +460,18 @@ html.trusted_access #appShell {
@apply container;
}
.ae_container_module_menu {
@apply w-full max-w-7xl flex flex-col items-center justify-center gap-1 p-1
border rounded-md border-gray-200 dark:border-gray-800 hover:bg-gray-100 dark:hover:bg-gray-900 transition-all duration-700 hover:duration-300;
@apply flex w-full max-w-7xl flex-col items-center justify-center gap-1 rounded-md border border-gray-200 p-1 transition-all duration-700 hover:bg-gray-100 hover:duration-300 dark:border-gray-800 dark:hover:bg-gray-900;
}
.ae_container_module_options {
@apply text-cyan-950 dark:text-cyan-50
bg-cyan-50 dark:bg-cyan-950 hover:bg-cyan-100 dark:hover:bg-cyan-900
border border-cyan-200 dark:border-cyan-800 hover:border-cyan-400 dark:hover:border-cyan-600
rounded-md
flex flex-row flex-wrap items-center justify-around
w-full max-w-full
p-2
transition-all;
@apply flex w-full max-w-full flex-row flex-wrap items-center justify-around rounded-md border border-cyan-200 bg-cyan-50 p-2 text-cyan-950 transition-all hover:border-cyan-400 hover:bg-cyan-100 dark:border-cyan-800 dark:bg-cyan-950 dark:text-cyan-50 dark:hover:border-cyan-600 dark:hover:bg-cyan-900;
}
.ae_container_module_help {
@apply text-yellow-950 dark:text-yellow-50
bg-yellow-50 dark:bg-yellow-950 hover:bg-yellow-100 dark:hover:bg-yellow-900
border border-yellow-200 dark:border-yellow-800 hover:border-yellow-400 dark:hover:border-yellow-600
rounded-md
w-lg max-w-full
p-2
transition-all;
@apply w-lg max-w-full rounded-md border border-yellow-200 bg-yellow-50 p-2 text-yellow-950 transition-all hover:border-yellow-400 hover:bg-yellow-100 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-50 dark:hover:border-yellow-600 dark:hover:bg-yellow-900;
/* bg-yellow-100 border border-yellow-400 p-2 rounded-md max-w-xl */
}
.ae_container_actions {
@apply container preset-tonal-success border border-success-500 rounded-md flex flex-row items-center my-2 p-2;
@apply preset-tonal-success border-success-500 container my-2 flex flex-row items-center rounded-md border p-2;
}
.ae_container_results {
@apply container;
@@ -500,23 +493,11 @@ html.trusted_access #appShell {
@apply container;
}
.ae_container_help {
@apply text-yellow-950 dark:text-yellow-50
bg-yellow-50 dark:bg-yellow-950 hover:bg-yellow-100 dark:hover:bg-yellow-900
border border-yellow-200 dark:border-yellow-800 hover:border-yellow-400 dark:hover:border-yellow-600
rounded-md
max-w-full
p-2
transition-all;
@apply max-w-full rounded-md border border-yellow-200 bg-yellow-50 p-2 text-yellow-950 transition-all hover:border-yellow-400 hover:bg-yellow-100 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-50 dark:hover:border-yellow-600 dark:hover:bg-yellow-900;
/* bg-yellow-100 border border-yellow-400 p-2 rounded-md max-w-xl */
}
.ae_container_info {
@apply text-cyan-950 dark:text-cyan-50
bg-cyan-50 dark:bg-cyan-950 hover:bg-cyan-100 dark:hover:bg-cyan-900
border border-cyan-200 dark:border-cyan-800 hover:border-cyan-400 dark:hover:border-cyan-600
rounded-md
max-w-full
p-2
transition-all;
@apply max-w-full rounded-md border border-cyan-200 bg-cyan-50 p-2 text-cyan-950 transition-all hover:border-cyan-400 hover:bg-cyan-100 dark:border-cyan-800 dark:bg-cyan-950 dark:text-cyan-50 dark:hover:border-cyan-600 dark:hover:bg-cyan-900;
}
.ae_container_msg {
@apply container;

2
src/app.d.ts vendored
View File

@@ -14,7 +14,7 @@ declare global {
namespace App {
interface Platform {}
}
interface Window {
native_app: any;
}

View File

@@ -12,16 +12,13 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet"
/>
rel="stylesheet" />
<!-- <link href="app.css" rel="stylesheet"> -->

View File

@@ -43,7 +43,9 @@ export const delete_object = async function delete_object({
// Construct the URL with query parameters
const url = new URL(endpoint, api_cfg['base_url']);
if (params) {
Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url.searchParams.append(key, params[key])
);
}
// Clean and merge headers without mutating the original api_cfg
@@ -70,8 +72,13 @@ export const delete_object = async function delete_object({
}
// Auto-inject Authorization header if JWT is present but header is missing
const jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || api_cfg['jwt'];
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
const jwt =
headers_cleaned['jwt'] || headers_cleaned['JWT'] || api_cfg['jwt'];
if (
jwt &&
!headers_cleaned['Authorization'] &&
!headers_cleaned['authorization']
) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
@@ -93,20 +100,26 @@ export const delete_object = async function delete_object({
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
console.error(`API DELETE request timed out after ${timeout}ms.`);
console.error(
`API DELETE request timed out after ${timeout}ms.`
);
controller.abort();
}, timeout);
const fetchOptions: RequestInit = {
method: 'DELETE',
headers: headers_cleaned,
body: Object.keys(data).length > 0 ? JSON.stringify(data) : undefined,
body:
Object.keys(data).length > 0
? JSON.stringify(data)
: undefined,
signal: controller.signal
};
const response = await fetch_method(url.toString(), fetchOptions).catch(function (
error: any
) {
const response = await fetch_method(
url.toString(),
fetchOptions
).catch(function (error: any) {
console.log(
'API DELETE Object *fetch* request was aborted or failed in an unexpected way.',
error
@@ -121,7 +134,9 @@ export const delete_object = async function delete_object({
}
if (log_lvl) {
console.log(`Response: status=${response.status} attempt=${attempt}`);
console.log(
`Response: status=${response.status} attempt=${attempt}`
);
}
if (!response.ok) {
@@ -129,15 +144,20 @@ export const delete_object = async function delete_object({
console.warn('404 Not Found. Returning null.');
return null;
}
const errorBody = await response.text();
console.error(`HTTP error! status: ${response.status}`, errorBody);
console.error(
`HTTP error! status: ${response.status}`,
errorBody
);
if (response.status >= 400 && response.status < 404) {
return false;
}
throw new Error(`HTTP error! status: ${response.status} - ${errorBody}`);
throw new Error(
`HTTP error! status: ${response.status} - ${errorBody}`
);
}
const json = await response.json();
@@ -148,7 +168,11 @@ export const delete_object = async function delete_object({
// Return the response data or metadata
// Robustly handle V3 response envelopes
return return_meta ? json : (json.data !== undefined ? json.data : json);
return return_meta
? json
: json.data !== undefined
? json.data
: json;
} catch (error) {
console.error(`API DELETE error on attempt ${attempt}:`, error);

View File

@@ -44,7 +44,9 @@ export async function get_ae_obj_id_crud({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** get_ae_obj_id_crud() *** Type: ${obj_type} ID: ${obj_id}`);
console.log(
`*** get_ae_obj_id_crud() *** Type: ${obj_type} ID: ${obj_id}`
);
}
// V3 Standard: Unified endpoint for all objects
@@ -77,7 +79,10 @@ export async function get_ae_obj_id_crud({
log_lvl: log_lvl,
return_meta: return_meta
}).catch(function (error: any) {
console.error(`API GET CRUD object ID request failed for ${obj_type}/${obj_id}`, error);
console.error(
`API GET CRUD object ID request failed for ${obj_type}/${obj_id}`,
error
);
return false;
});
@@ -86,4 +91,4 @@ export async function get_ae_obj_id_crud({
}
return result;
}
}

View File

@@ -14,7 +14,7 @@ interface GetAeObjV3Params {
/**
* Get a single object by ID (V3)
*/
export async function get_ae_obj_v3({
export async function get_ae_obj({
api_cfg,
obj_type,
obj_id,
@@ -27,7 +27,7 @@ export async function get_ae_obj_v3({
const query_params: key_val = { view, ...params };
if (log_lvl) {
console.log('*** get_ae_obj_v3 ***');
console.log('*** get_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
}
@@ -56,7 +56,7 @@ interface GetNestedAeObjV3Params {
/**
* Get a single nested object by ID (V3)
*/
export async function get_nested_ae_obj_v3({
export async function get_nested_ae_obj({
api_cfg,
parent_type,
parent_id,
@@ -71,7 +71,7 @@ export async function get_nested_ae_obj_v3({
const query_params: key_val = { view, ...params };
if (log_lvl) {
console.log('*** get_nested_ae_obj_v3 ***');
console.log('*** get_nested_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
}
@@ -95,14 +95,17 @@ interface GetAeObjLiV3Params {
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[] | null;
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[]
| null;
delay_ms?: number;
params?: key_val;
headers?: key_val;
log_lvl?: number;
}
export async function get_ae_obj_li_v3({
export async function get_ae_obj_li({
api_cfg,
obj_type,
for_obj_type,
@@ -137,7 +140,7 @@ export async function get_ae_obj_li_v3({
if (delay_ms > 0) query_params['delay_ms'] = delay_ms;
if (log_lvl) {
console.log('*** get_ae_obj_li_v3 ***');
console.log('*** get_ae_obj_li ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
console.log('Headers:', headers);
@@ -162,12 +165,15 @@ interface GetNestedObjLiV3Params {
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[] | null;
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[]
| null;
delay_ms?: number;
log_lvl?: number;
}
export async function get_nested_obj_li_v3({
export async function get_nested_obj_li({
api_cfg,
parent_type,
parent_id,
@@ -195,7 +201,7 @@ export async function get_nested_obj_li_v3({
if (delay_ms > 0) params['delay_ms'] = delay_ms;
if (log_lvl) {
console.log('*** get_nested_obj_li_v3 ***');
console.log('*** get_nested_obj_li ***');
console.log('Endpoint:', endpoint);
console.log('Params:', params);
}
@@ -206,4 +212,4 @@ export async function get_nested_obj_li_v3({
params,
log_lvl
});
}
}

View File

@@ -1,229 +0,0 @@
import type { key_val } from '$lib/stores/ae_stores';
import { get_object } from './api_get_object';
// The lookup "obj_type" should broken out into a separate function. - 2024-08-07
// Updated 2023-11-15
export async function get_ae_obj_li_for_obj_id_crud({
api_cfg,
obj_type,
for_obj_type,
for_obj_id, // NOTE: Changed 2023-12-06 to no longer required
use_alt_table = false,
use_alt_base = false,
// inc = {},
enabled = 'enabled',
hidden = 'not_hidden',
order_by_li = null,
limit = 999999,
offset = 0,
// key,
// jwt = null,
headers = {},
params_json = null, // NOTE: This is a JSON object that needs to be safely converted to a string for the params. This is used for the API endpoint. Example: { "fulltext_search": { "default_qry_str": "Search string for default", "address_default_qry_str": "Search string for address", "contact_1_default_qry_str": "Search string for contact_1" } }
// json_obj = null, // NOTE: This is a JSON object that needs to be safely converted to a string for the params. This is used for the search endpoint.
params = {},
return_meta = false,
log_lvl = 0
}: {
api_cfg: any;
obj_type: string;
for_obj_type: null | string;
for_obj_id?: string;
use_alt_table?: boolean;
use_alt_base?: boolean;
// inc?: key_val
enabled?: 'enabled' | 'all' | 'not_enabled' | undefined;
hidden?: 'hidden' | 'all' | 'not_hidden' | undefined;
order_by_li?: any;
limit?: number;
offset?: number;
// key: string,
// jwt?: string,
headers?: any;
params_json?: any;
// json_obj?: any,
params?: key_val;
return_meta?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** get_ae_obj_li_for_obj_id_crud() *** [${obj_type}]`);
}
// data = {};
// data['super_key'] = key;
// data['jwt'] = jwt;
// NOTE: The key and or JWT should be in the header of the DELETE, GET, PATCH, POST
// const endpoint = `/crud/${obj_type}/list`;
let endpoint = '';
if (obj_type == 'account') {
endpoint = `/crud/account/list`;
} else if (obj_type == 'address') {
endpoint = `/crud/address/list`;
} else if (obj_type == 'archive') {
endpoint = `/crud/archive/list`;
} else if (obj_type == 'archive_content') {
endpoint = `/crud/archive/content/list`;
} else if (obj_type == 'contact') {
endpoint = `/crud/contact/list`;
} else if (obj_type == 'data_store') {
endpoint = `/crud/data_store/list`;
} else if (obj_type == 'event') {
endpoint = `/crud/event/list`;
} else if (obj_type == 'event_abstract') {
endpoint = `/crud/event/abstract/list`;
} else if (obj_type == 'event_badge') {
endpoint = `/crud/event/badge/list`;
} else if (obj_type == 'event_device') {
endpoint = `/crud/event/device/list`;
} else if (obj_type == 'event_exhibit') {
endpoint = `/crud/event/exhibit/list`;
} else if (obj_type == 'event_exhibit_tracking') {
endpoint = `/crud/event/exhibit/tracking/list`;
} else if (obj_type == 'event_file') {
endpoint = `/crud/event/file/list`;
} else if (obj_type == 'event_location') {
endpoint = `/crud/event/location/list`;
} else if (obj_type == 'event_person') {
endpoint = `/crud/event/person/list`;
} else if (obj_type == 'event_presentation') {
endpoint = `/crud/event/presentation/list`;
} else if (obj_type == 'event_presenter') {
endpoint = `/crud/event/presenter/list`;
} else if (obj_type == 'event_session') {
endpoint = `/crud/event/session/list`;
} else if (obj_type == 'event_track') {
endpoint = `/crud/event/track/list`;
} else if (obj_type == 'grant') {
endpoint = `/crud/grant/list`;
} else if (obj_type == 'hosted_file') {
endpoint = `/crud/hosted_file/list`;
} else if (obj_type == 'journal') {
endpoint = `/crud/journal/list`;
} else if (obj_type == 'journal_entry') {
endpoint = `/crud/journal/entry/list`;
} else if (obj_type == 'order') {
endpoint = `/crud/order/list`;
} else if (obj_type == 'order_line') {
endpoint = `/crud/order/line/list`;
} else if (obj_type == 'page') {
endpoint = `/crud/page/list`;
} else if (obj_type == 'person') {
endpoint = `/crud/person/list`;
} else if (obj_type == 'post') {
endpoint = `/crud/post/list`;
} else if (obj_type == 'post_comment') {
endpoint = `/crud/post/comment/list`;
} else if (obj_type == 'site') {
endpoint = `/crud/site/list`;
} else if (obj_type == 'sponsorship_cfg') {
endpoint = `/crud/sponsorship/cfg/list`;
} else if (obj_type == 'sponsorship') {
endpoint = `/crud/sponsorship/list`;
// } else if (obj_type == 'user') {
// endpoint = `/crud/user/list`;
} else if (obj_type == 'lu' && for_obj_type == 'country_subdivision') {
endpoint = `/crud/lu/country_subdivision/list`;
for_obj_type = null;
} else if (obj_type == 'lu' && for_obj_type == 'country') {
endpoint = `/crud/lu/country/list`;
for_obj_type = null;
} else if (obj_type == 'lu' && for_obj_type == 'time_zone') {
endpoint = `/crud/lu/time_zone/list`;
for_obj_type = null;
} else {
console.log(`Unknown object type: ${obj_type}`);
return false;
}
if (log_lvl) {
console.log('Endpoint:', endpoint);
}
if (for_obj_type) {
params['for_obj_type'] = for_obj_type;
}
if (for_obj_id) {
params['for_obj_id'] = for_obj_id;
}
params['use_alt_table'] = use_alt_table;
params['use_alt_base'] = use_alt_base;
/* Need to deal with inc params here */
const allowed_enabled_list = ['all', 'enabled', 'not_enabled'];
if (allowed_enabled_list.includes(enabled)) {
params['enabled'] = enabled;
}
const allowed_hidden_list = ['all', 'hidden', 'not_hidden'];
if (allowed_hidden_list.includes(hidden)) {
params['hidden'] = hidden;
}
// NOTE: The order_by_li variable is in the "headers" because if is a the URL GET params do not handle multiple values very well. Maybe base64 encore in the future or something? Reminder that GET requests should not have a body (no JSON).
// NOTE: The order_by_li should be a key value pair of the property/DB field to sort and how to sort (ASC or DESC)
if (order_by_li) {
if (log_lvl) {
console.log('Order By:', order_by_li);
}
headers['order_by_li'] = order_by_li;
}
if (limit >= 0) {
params['limit'] = limit;
}
if (offset >= 0) {
params['offset'] = offset;
}
if (params_json) {
// NOTE: This is a JSON object that needs to be safely converted to a string for the params. This is used for the search endpoint.
// Max characters for a GET request is 2083. This is a limitation of the browser (Microsoft IE and Edge).
if (log_lvl) {
console.log('JSON Object:', params_json);
console.log(JSON.stringify(params_json));
}
// NOTE: "jp" stands for "JSON Params"
params['jp'] = encodeURIComponent(JSON.stringify(params_json));
if (params['jp'].length > 2083) {
console.log(
`The JSON object is too large to be used as a GET parameter. The overall max URL length is 2083 characters. Please use the POST endpoint instead. Length = ${params['jp'].length} [THIS DOES NOT EXIST YET]`
);
return false;
}
}
// if (json_obj) {
// // NOTE: This is a JSON object that needs to be safely converted to a string for the params. This is used for the search endpoint.
// // Max characters for a GET request is 2083. This is a limitation of the browser (Microsoft IE and Edge).
// console.log('JSON Object:', json_obj);
// params['json_str'] = encodeURIComponent(JSON.stringify(json_obj));
// if (params['json_str'].length > 2083) {
// console.log(`The JSON object is too large to be used as a GET parameter. The overall max URL length is 2083 characters. Please use the POST endpoint instead. Length = ${params['json_str'].length} [THIS DOES NOT EXIST YET]`);
// return false;
// }
// }
if (log_lvl) {
console.log('Params:', params);
}
const object_li_get_promise = await get_object({
api_cfg: api_cfg,
endpoint: endpoint,
headers: headers,
params: params,
return_meta: return_meta,
log_lvl: log_lvl
});
if (log_lvl > 1) {
console.log(object_li_get_promise);
}
return object_li_get_promise;
}

View File

@@ -1,175 +0,0 @@
import type { key_val } from '$lib/stores/ae_stores';
import { get_object } from './api_get_object';
// Refactored 2025-11-13 to use a lookup map for endpoints.
const objTypeToEndpointMap: Record<string, string> = {
account: '/crud/account/list',
address: '/crud/address/list',
archive: '/crud/archive/list',
archive_content: '/crud/archive/content/list',
activity_log: '/crud/activity_log/list',
contact: '/crud/contact/list',
data_store: '/crud/data_store/list',
event: '/crud/event/list',
event_abstract: '/crud/event/abstract/list',
event_badge: '/crud/event/badge/list',
event_badge_template: '/crud/event/badge/template/list',
event_device: '/crud/event/device/list',
event_exhibit: '/crud/event/exhibit/list',
event_exhibit_tracking: '/crud/event/exhibit/tracking/list',
event_file: '/crud/event/file/list',
event_location: '/crud/event/location/list',
event_person: '/crud/event/person/list',
event_presentation: '/crud/event/presentation/list',
event_presenter: '/crud/event/presenter/list',
event_session: '/crud/event/session/list',
event_track: '/crud/event/track/list',
grant: '/crud/grant/list',
hosted_file: '/crud/hosted_file/list',
journal: '/crud/journal/list',
journal_entry: '/crud/journal/entry/list',
order: '/crud/order/list',
order_line: '/crud/order/line/list',
page: '/crud/page/list',
person: '/crud/person/list',
post: '/crud/post/list',
post_comment: '/crud/post/comment/list',
site: '/crud/site/list',
sponsorship_cfg: '/crud/sponsorship/cfg/list',
sponsorship: '/crud/sponsorship/list',
// user: '/crud/user/list',
'lu-country_subdivision': '/crud/lu/country_subdivision/list',
'lu-country': '/crud/lu/country/list',
'lu-time_zone': '/crud/lu/time_zone/list'
};
function getEndpointForObjType(obj_type: string, for_obj_type?: string): string {
if (obj_type === 'lu' && for_obj_type) {
const key = `lu-${for_obj_type}`;
const endpoint = objTypeToEndpointMap[key];
if (endpoint) return endpoint;
}
const endpoint = objTypeToEndpointMap[obj_type];
if (endpoint) return endpoint;
throw new Error(`Unknown object type: ${obj_type}`);
}
type OrderBy = { [key: string]: 'ASC' | 'DESC' };
interface GetAeObjLiForObjIdCrudV2Params {
api_cfg: any; // Consider defining a specific type for api_cfg
obj_type: string;
for_obj_type: string;
for_obj_id?: string;
use_alt_tbl?: boolean | string;
use_alt_mdl?: boolean | string;
use_alt_exp?: boolean | string;
inc?: key_val;
enabled?: 'all' | 'enabled' | 'not_enabled';
hidden?: 'all' | 'hidden' | 'not_hidden';
order_by_li?: OrderBy | OrderBy[] | null;
limit?: number;
offset?: number;
headers?: Record<string, string>;
params_json?: any;
params?: key_val;
log_lvl?: number;
}
export async function get_ae_obj_li_for_obj_id_crud_v2({
api_cfg,
obj_type,
for_obj_type,
for_obj_id,
use_alt_tbl = false,
use_alt_mdl = false,
use_alt_exp = false,
enabled = 'enabled',
hidden = 'not_hidden',
order_by_li = null,
limit = 999999,
offset = 0,
headers = {},
params_json = null,
params = {},
log_lvl = 0
}: GetAeObjLiForObjIdCrudV2Params) {
if (log_lvl) {
console.log('*** get_ae_obj_li_for_obj_id_crud_v2() ***');
}
try {
const endpoint = `/v2${getEndpointForObjType(obj_type, for_obj_type)}`;
if (log_lvl) {
console.log('Endpoint:', endpoint);
}
// We need to remove a few parameters from the params object that are not allowed.
delete params['qry__enabled'];
delete params['qry__hidden'];
delete params['qry__limit'];
delete params['qry__offset'];
if (for_obj_type) params['for_obj_type'] = for_obj_type;
if (for_obj_id) params['for_obj_id'] = for_obj_id;
if (use_alt_tbl === true) params['tbl_alt'] = 'alt';
if (use_alt_mdl === true) params['mdl_alt'] = 'alt';
if (use_alt_exp === true) params['exp_alt'] = 'alt';
const allowed_enabled_list = ['all', 'enabled', 'not_enabled'];
if (allowed_enabled_list.includes(enabled)) {
params['enabled'] = enabled;
}
const allowed_hidden_list = ['all', 'hidden', 'not_hidden'];
if (allowed_hidden_list.includes(hidden)) {
params['hidden'] = hidden;
}
// NOTE: The order_by_li variable is in the "headers" because URL GET params do not handle complex objects very well.
if (order_by_li) {
headers['order_by_li'] = JSON.stringify(order_by_li);
}
if (limit > 0) params['limit'] = limit;
if (offset > 0) params['offset'] = offset;
if (params_json) {
// NOTE: "jp" stands for "JSON Params". This is a JSON object that needs to be safely converted to a string for the params.
// Max characters for a GET request is ~2000. This is a limitation of the browser.
const json_params_str = encodeURIComponent(JSON.stringify(params_json));
if (json_params_str.length > 2083) {
// Using console.error instead of throwing an error to avoid crashing the app for a known limitation.
console.error(
`The JSON object is too large to be used as a GET parameter. Max length is 2083 characters. Length = ${json_params_str.length}`
);
return false;
}
params['jp'] = json_params_str;
}
if (log_lvl) {
console.log('Params:', params);
}
const object_li_get_promise = await get_object({
api_cfg: api_cfg,
endpoint: endpoint,
headers: headers,
params: params,
log_lvl: log_lvl
});
if (log_lvl > 1) {
console.log(object_li_get_promise);
}
return object_li_get_promise;
} catch (error) {
console.error('Error in get_ae_obj_li_for_obj_id_crud_v2:', error);
return false; // Or handle the error as appropriate
}
}

View File

@@ -15,7 +15,7 @@ interface GetDataStoreV3Params {
* Uses hierarchical fallback logic (Specific -> Account -> Global)
* Path: GET /v3/data_store/code/{code}
*/
export async function get_data_store_v3({
export async function get_data_store({
api_cfg,
code,
for_type = null,
@@ -24,7 +24,9 @@ export async function get_data_store_v3({
log_lvl = 0
}: GetDataStoreV3Params): Promise<any> {
if (log_lvl) {
console.log(`*** get_data_store_v3() *** code=${code} no_account_id=${no_account_id}`);
console.log(
`*** get_data_store() *** code=${code} no_account_id=${no_account_id}`
);
}
const endpoint = `/v3/data_store/code/${code}`;

View File

@@ -5,10 +5,10 @@ import type { key_val } from '$lib/stores/ae_stores';
* Get a list of lookup objects (V3)
* Standardized lookup data like countries, timezones, and subdivisions.
* Updated 2026-02-20
*
*
* Endpoint: GET /v3/lookup/{lu_type}/list
*/
export async function get_ae_lookup_li_v3({
export async function get_ae_lookup_li({
api_cfg,
lu_type,
site_id,
@@ -16,6 +16,9 @@ export async function get_ae_lookup_li_v3({
for_id,
include_disabled = false,
only_priority = false,
order_by_li = null,
limit = null,
offset = null,
params = {},
headers = {},
log_lvl = 0
@@ -27,28 +30,34 @@ export async function get_ae_lookup_li_v3({
for_id?: string;
include_disabled?: boolean;
only_priority?: boolean;
order_by_li?: Record<string, 'ASC' | 'DESC'> | null;
limit?: number | null;
offset?: number | null;
params?: key_val;
headers?: Record<string, string>;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** get_ae_lookup_li_v3() *** lu_type=${lu_type}`);
console.log(`*** get_ae_lookup_li() *** lu_type=${lu_type}`);
}
const endpoint = `/v3/lookup/${lu_type}/list`;
// Build query params
if (site_id) params['site_id'] = site_id;
if (for_type) params['for_type'] = for_type;
if (for_id) params['for_id'] = for_id;
if (include_disabled) params['include_disabled'] = true;
if (only_priority) params['only_priority'] = true;
if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li);
if (limit != null) params['limit'] = limit;
if (offset != null) params['offset'] = offset;
// Lookup data is often global; ensure account context is handled if needed,
// Lookup data is often global; ensure account context is handled if needed,
// but GUIDE says it uses site Whitelist Policy.
// If no account_id is present in api_cfg, we might need 'x-no-account-id'
// If no account_id is present in api_cfg, we might need 'x-no-account-id'
// for some lookups if they are public.
return await get_object({
api_cfg,
endpoint,

View File

@@ -41,7 +41,9 @@ export const get_object = async function get_object({
retry_count?: number;
}) {
if (log_lvl) {
console.log(`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`);
console.log(
`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`
);
console.log('Params:', params);
if (log_lvl > 1) {
console.log('Data:', data);
@@ -55,7 +57,10 @@ export const get_object = async function get_object({
// FAIL FAST: Check if we are explicitly offline to avoid long browser timeouts
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('get_object: Browser is offline. Failing fast to allow cache fallback.');
if (log_lvl)
console.log(
'get_object: Browser is offline. Failing fast to allow cache fallback.'
);
return false;
}
@@ -64,7 +69,9 @@ export const get_object = async function get_object({
}
const url = new URL(endpoint, api_cfg['base_url']);
Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url.searchParams.append(key, params[key])
);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
@@ -96,14 +103,19 @@ export const get_object = async function get_object({
}
// Handle "Bootstrap Paradox" for unauthenticated requests
const bypass_val = merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass = bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
const bypass_val =
merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass =
bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
if (is_valid_bypass) {
if (log_lvl > 1) console.log('api_get_object: Valid bypass detected. Stripping account ID context.');
if (log_lvl > 1)
console.log(
'api_get_object: Valid bypass detected. Stripping account ID context.'
);
delete merged_headers['x-account-id'];
delete merged_headers['x_account_id'];
} else {
@@ -126,11 +138,12 @@ export const get_object = async function get_object({
}
// Auto-inject Authorization header if JWT is present but header is missing
let jwt = headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
let jwt =
headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
// Final Fallback: Direct check of primary ae_loc key
if (!jwt && typeof localStorage !== 'undefined') {
@@ -145,7 +158,11 @@ export const get_object = async function get_object({
}
}
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
if (
jwt &&
!headers_cleaned['Authorization'] &&
!headers_cleaned['authorization']
) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
@@ -174,18 +191,29 @@ export const get_object = async function get_object({
for (let attempt = 1; attempt <= retry_count; attempt++) {
// FAIL FAST: Check if we are explicitly offline to avoid long browser timeouts
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log(`get_object: Browser is offline (attempt ${attempt}). Failing fast to allow cache fallback.`);
if (log_lvl)
console.log(
`get_object: Browser is offline (attempt ${attempt}). Failing fast to allow cache fallback.`
);
return false;
}
try {
const response = await fetch_method(url.toString(), fetchOptions).catch(function (
error: any
) {
const response = await fetch_method(
url.toString(),
fetchOptions
).catch(function (error: any) {
// SILENCE NOISE: Aborted requests (common in SWR/Background loads) shouldn't spam logs
if (error.name === 'AbortError' || error.message?.includes('aborted') || error.name === 'TypeError') {
if (
error.name === 'AbortError' ||
error.message?.includes('aborted') ||
error.name === 'TypeError'
) {
if (log_lvl > 1) {
console.log('API GET: Request was aborted or terminated by browser. This is expected during navigation.', error);
console.log(
'API GET: Request was aborted or terminated by browser. This is expected during navigation.',
error
);
}
return error; // Return error to be handled below
}
@@ -199,11 +227,19 @@ export const get_object = async function get_object({
clearTimeout(timeoutId);
// Check if we should stop due to abort or network failure
if (response instanceof Error || (response && (response.name === 'TypeError' || response.name === 'AbortError'))) {
if (
response instanceof Error ||
(response &&
(response.name === 'TypeError' ||
response.name === 'AbortError'))
) {
// If it was an explicit abort, definitely stop
if (response.name === 'AbortError') return false;
if (log_lvl > 1) console.log('API GET Object: Detected NetworkError or TypeError. Failing fast.');
if (log_lvl > 1)
console.log(
'API GET Object: Detected NetworkError or TypeError. Failing fast.'
);
return false;
}
@@ -231,24 +267,45 @@ export const get_object = async function get_object({
if (!response.ok) {
if (response.status === 404) {
if (log_lvl) {
console.log('The response was a 404 not found "error". Returning null.');
console.log(
'The response was a 404 not found "error". Returning null.'
);
}
return null;
}
// FAIL FAST (Section 2D): Do not retry on Auth or Client errors (400, 401, 403, 422)
if (response.status === 400 || response.status === 401 || response.status === 403 || response.status === 422) {
if (log_lvl) console.error(`API Client Failure (${response.status}). Failing fast.`);
if (
response.status === 400 ||
response.status === 401 ||
response.status === 403 ||
response.status === 422
) {
if (log_lvl)
console.error(
`API Client Failure (${response.status}). Failing fast.`
);
if (response.status === 401 || response.status === 403) {
console.warn(`AUTH DIAGNOSTICS: Headers sent for ${endpoint}:`, {
has_auth: !!headers_cleaned['Authorization'],
has_api_key: !!headers_cleaned['x-aether-api-key'],
has_account_id: !!headers_cleaned['x-account-id'],
jwt_preview: jwt ? `${jwt.slice(0, 8)}...` : 'MISSING'
});
console.warn(
`AUTH DIAGNOSTICS: Headers sent for ${endpoint}:`,
{
has_auth: !!headers_cleaned['Authorization'],
has_api_key:
!!headers_cleaned['x-aether-api-key'],
has_account_id:
!!headers_cleaned['x-account-id'],
jwt_preview: jwt
? `${jwt.slice(0, 8)}...`
: 'MISSING'
}
);
// Signal the root layout to show the session-expired banner.
if (browser) ae_auth_error.set({ type: 'expired', ts: Date.now() });
if (browser)
ae_auth_error.set({
type: 'expired',
ts: Date.now()
});
}
// Structured Error Handling (V3): Attempt to get rich error metadata
@@ -259,7 +316,11 @@ export const get_object = async function get_object({
// Not JSON
}
if (log_lvl) console.log('The response was not ok. Structured Error Check:', error_json);
if (log_lvl)
console.log(
'The response was not ok. Structured Error Check:',
error_json
);
if (error_json?.meta?.details) {
return error_json;
@@ -273,7 +334,10 @@ export const get_object = async function get_object({
status_code: response.status,
details: {
category: 'validation',
message: typeof error_json.detail === 'string' ? error_json.detail : JSON.stringify(error_json.detail),
message:
typeof error_json.detail === 'string'
? error_json.detail
: JSON.stringify(error_json.detail),
raw: error_json.detail
}
}
@@ -307,7 +371,9 @@ export const get_object = async function get_object({
chunks.push(value);
receivedLength += value.length;
const percent_completed = Math.round((receivedLength * 100) / contentLength);
const percent_completed = Math.round(
(receivedLength * 100) / contentLength
);
if (log_lvl > 1) {
console.log(
'GET Blob Progress:',
@@ -359,7 +425,10 @@ export const get_object = async function get_object({
}
}
} catch (error) {
console.log(`API GET object request *fetch* error on attempt ${attempt}:`, error);
console.log(
`API GET object request *fetch* error on attempt ${attempt}:`,
error
);
if (attempt === retry_count) {
console.log('Max retry attempts reached. Returning false.');

View File

@@ -1,524 +0,0 @@
import axios from 'axios';
import type { key_val } from '$lib/stores/ae_stores';
export let temp_get_blob_percent_completed = 0;
// export let get_blob_percent_completed = readable(temp_get_blob_percent_completed);
export const get_blob_percent_completed = temp_get_blob_percent_completed;
export let temp_get_object_percent_completed = 0;
// export let get_object_percent_completed = readable(temp_get_object_percent_completed);
export const get_object_percent_completed = temp_get_object_percent_completed;
// Updated 2024-05-23
export const get_object = async function get_object({
api_cfg = null,
endpoint = '',
headers = {},
params = {},
data = {},
timeout = 60000,
return_meta = false,
return_blob = false,
filename = '',
auto_download = false,
as_list = false,
// The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event.
task_id = crypto.randomUUID(),
log_lvl = 0
}: {
api_cfg: any;
endpoint: string;
headers?: any;
params?: any;
data?: any;
timeout?: number;
return_meta?: boolean;
return_blob?: boolean;
filename?: null | string;
auto_download?: boolean;
as_list?: boolean;
task_id?: string;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`);
console.log('Params:', params);
if (log_lvl > 1) {
console.log('Data:', data);
console.log(`Base URL: ${api_cfg['base_url']}; Timeout: ${timeout}`);
console.log('API Config:', api_cfg);
}
if (log_lvl > 2) {
console.log(
`Return Meta: ${return_meta}; Return Blob: ${return_blob}; Filename: ${filename}; Auto Download: ${auto_download}`
);
}
}
if (!api_cfg) {
console.log('No API Config was provided. Returning false.');
return false;
}
const axios_api = axios.create({
baseURL: api_cfg['base_url'],
timeout: timeout // in milliseconds; 60000 = 60 seconds
/* other custom settings */
});
axios_api.defaults.headers = api_cfg['headers'];
if (log_lvl) {
console.log('axios_api.defaults.headers:', axios_api.defaults.headers);
console.log('Additional headers:', headers);
}
// console.log('Clean the headers. No _underscores_!')
const headers_cleaned: key_val = {};
for (const prop in headers) {
// No underscores allowed in the header parameters!
const prop_cleaned = prop.replaceAll('_', '-');
// The value must be a string for the header!
if (typeof headers[prop] != 'string') {
headers[prop] = JSON.stringify(headers[prop]);
}
headers_cleaned[prop_cleaned] = headers[prop];
if (log_lvl) {
console.log(`${prop_cleaned}: ${headers_cleaned[prop_cleaned]}`);
}
}
headers = headers_cleaned;
if (log_lvl) {
console.log('All headers cleaned:', headers);
}
if (log_lvl) {
console.log('URL params:');
}
for (const prop in params) {
if (log_lvl > 1) {
console.log(`URL param: ${prop}: ${params[prop]}`);
}
if (params[prop] === null) {
params[prop] = 'null';
}
}
// Handle the case where there is no Blob expected to be returned. Mainly JSON and text data.
if (!return_blob) {
const response_data_promise = await axios_api
.get(endpoint, {
headers: headers,
params: params,
onDownloadProgress: (progressEvent) => {
const total = progressEvent.total ?? 0;
const percent_completed = total > 0 ? Math.round((progressEvent.loaded * 100) / total) : 0;
if (log_lvl > 1) {
console.log(
'GET Data Progress:',
progressEvent.progress,
'Total:',
total,
'Loaded:',
progressEvent.loaded,
'Percent Completed',
percent_completed
);
}
temp_get_object_percent_completed = percent_completed;
// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
try {
// Check if window is defined. This is to prevent errors in SvelteKit.
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_data',
status: 'downloading',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: total,
size_loaded: progressEvent.loaded,
percent_completed: percent_completed
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
}
})
.then(function (response) {
if (log_lvl) {
console.log(
`GET Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${JSON.stringify(response.config.params)}`
);
}
if (log_lvl > 1) {
console.log('GET Response:', response);
}
// Post file download message
try {
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_data',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 100
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
if (!Array.isArray(response.data['data']) && as_list) {
if (log_lvl) {
console.log(
'Data result is a dictionary/object, not an array/list. Forcing return as an array/list'
);
}
const return_data = [];
return_data.push(response.data['data']);
return return_data;
} else if (response.data['data']) {
const return_data = response.data['data'];
if (log_lvl) {
if (Array.isArray(return_data)) {
console.log(
`Data result is an array/list. Array length: ${return_data.length}`
);
} else {
console.log(`Data result is a dictionary/object, not an array/list.`);
}
}
return return_data;
} else {
const return_data = response.data;
if (log_lvl) {
if (Array.isArray(return_data)) {
console.log(
`Not a standard response from Aether's API. Data result is an array/list. Array length: ${return_data.length}`
);
} else {
console.log(
`Not a standard response from Aether's API. Data result is a dictionary/object, not an array/list.`
);
}
}
return return_data;
}
})
.catch(function (error: any) {
// Handle the common and expected 404 "error" first
if (error.response && error.response.status === 404) {
if (log_lvl) {
console.log('The response was a 404 not found "error". Returning null.');
}
if (log_lvl > 1) {
console.log(error.response);
}
if (log_lvl > 2) {
console.log(error);
}
// Post file download message
try {
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_data',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 0
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
return null; // Returning null since there were no results
}
if (log_lvl) {
console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`);
console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion???
if (error.response) {
// The request was made and the server responded with a status code that falls out of the range of 2xx
console.log('Error Response Data', error.response.data);
console.log('Error Response Status', error.response.status);
console.log('Error Response Headers', error.response.headers);
} else if (error.request) {
// The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js
if (log_lvl > 1) {
console.log('Error Request', error.request);
}
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error Message', error.message);
}
}
if (log_lvl > 2) {
console.log('Error:', error);
console.log(error.config);
}
if (error.code === 'ECONNABORTED') {
// Timeout Error (You can implement retry here where suitable)
console.log('Timeout Error: ', error.message);
}
if (log_lvl) {
console.log('The response was an error. Returning false.');
}
return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns.
// return error;
});
if (log_lvl > 1) {
// console.log(`Response Data: ${response_data_promise}`);
console.log(`Response Data:`, response_data_promise);
// console.log(response_data_promise);
}
if (response_data_promise) {
// The most common and expected response.
// console.log('Returning result. This is generally expected.');
return response_data_promise;
} else if (response_data_promise === null) {
// Less common, but expected response if no results were returned.
if (log_lvl) {
console.log('Returning null. This is expected if no results were found. (404)');
}
return response_data_promise;
} else if (response_data_promise === false) {
// Not common, but expected response if the request to the API had an issue.
console.log('Returning false. There may have been an issue with this request.');
return response_data_promise;
} else {
// This generally should not happen. It likely means the query was bad or an API issue.
console.log('Returning (JSON/text) unknown. This should not happen in most cases.');
Promise.reject(new Error('fail')).then(resolved, rejected);
}
// Handle the case where a Blob is expected to be returned.
} else {
// console.log('Expecting a Blob to be returned...');
const response_data_promise = await axios_api
.get(endpoint, {
params: params,
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const total = progressEvent.total ?? 0;
const percent_completed = total > 0 ? Math.round((progressEvent.loaded * 100) / total) : 0;
console.log(
'GET Blob Progress:',
progressEvent.progress,
'Total:',
total,
'Loaded:',
progressEvent.loaded,
'Percent Completed',
percent_completed
);
temp_get_blob_percent_completed = percent_completed;
// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
try {
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_blob',
status: 'downloading',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: total,
size_loaded: progressEvent.loaded,
percent_completed: percent_completed
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
}
})
.then(function (response) {
if (log_lvl) {
console.log(
`GET (blob) Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${response.config.params}`
);
}
if (log_lvl > 1) {
console.log('GET (blob) Response:', response);
}
const { data, headers } = response;
// Careful if this download filename needs to be changed to a different file extension. The browser/client may not know how to handle it.
if (filename) {
} else if (headers['content-disposition']) {
filename = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1');
} else {
filename = 'unknown_file.ext';
}
// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
try {
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_blob',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 100
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
if (auto_download) {
if (log_lvl) {
console.log(`Auto Download: ${filename}`);
}
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename || 'download');
document.body.appendChild(link);
link.click();
return true;
} else {
return response;
}
})
.catch(function (error: any) {
// Handle the common and expected 404 "error" first
if (error.response && error.response.status === 404) {
if (log_lvl) {
console.log('The response was a 404 not found "error". Returning null.');
}
if (log_lvl > 1) {
console.log(error.response);
}
if (log_lvl > 2) {
console.log(error);
}
// Post file download message
try {
if (typeof window !== 'undefined') {
window.postMessage(
{
type: 'api_download_blob',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 0
},
'*'
);
}
} catch (error) {
console.log('Error posting message to window:', error);
}
return null; // Returning null since there were no results
}
if (log_lvl) {
console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`);
console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion???
if (error.response) {
// The request was made and the server responded with a status code that falls out of the range of 2xx
console.log('Error Response Data', error.response.data);
console.log('Error Response Status', error.response.status);
console.log('Error Response Headers', error.response.headers);
} else if (error.request) {
// The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js
if (log_lvl > 1) {
console.log('Error Request', error.request);
}
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error Message', error.message);
}
}
if (error.code === 'ECONNABORTED') {
// Timeout Error (You can implement retry here where suitable)
console.log('Timeout Error: ', error.message);
}
if (log_lvl) {
console.log('The response was an error. Returning false.');
}
return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns.
// return error;
});
if (response_data_promise) {
// The most common and expected response.
// console.log('Returning result. This is generally expected.');
// let test_blob = new Blob([response_data_promise.data]);
// console.log(test_blob);
// return test_blob;
// console.log(response_data_promise.blob());
return response_data_promise;
} else if (response_data_promise === null) {
// Less common, but expected response if no results were returned.
if (log_lvl) {
console.log('Returning null. This is expected if no results were found. (404)');
}
return response_data_promise;
} else if (response_data_promise === false) {
// Not common, but expected response if the request to the API had an issue.
console.log('Returning false. There may have been an issue with this request.');
return response_data_promise;
} else {
// This generally should not happen. It likely means the query was bad or an API issue.
console.log('Returning (blob) unknown. This should not happen in most cases.');
Promise.reject(new Error('fail')).then(resolved, rejected);
}
}
};
function resolved(result: any) {
console.log('Resolved');
}
function rejected(result: any) {
console.error(result);
}

View File

@@ -45,7 +45,9 @@ export const patch_object = async function patch_object({
// Construct the URL with query parameters
const url = new URL(endpoint, api_cfg['base_url']);
if (params) {
Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url.searchParams.append(key, params[key])
);
}
// Clean and merge headers without mutating the original api_cfg
@@ -75,14 +77,19 @@ export const patch_object = async function patch_object({
}
// Handle "Bootstrap Paradox" for unauthenticated requests
const bypass_val = merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass = bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
const bypass_val =
merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass =
bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
if (is_valid_bypass) {
if (log_lvl > 1) console.log('api_patch_object: Valid bypass detected. Stripping account ID context.');
if (log_lvl > 1)
console.log(
'api_patch_object: Valid bypass detected. Stripping account ID context.'
);
delete merged_headers['x-account-id'];
delete merged_headers['x_account_id'];
} else {
@@ -104,11 +111,12 @@ export const patch_object = async function patch_object({
}
// Auto-inject Authorization header if JWT is present but header is missing
let jwt = headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
let jwt =
headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
// Final Fallback: Direct check of primary ae_loc key
if (!jwt && typeof localStorage !== 'undefined') {
@@ -123,7 +131,11 @@ export const patch_object = async function patch_object({
}
}
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
if (
jwt &&
!headers_cleaned['Authorization'] &&
!headers_cleaned['authorization']
) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
@@ -145,7 +157,9 @@ export const patch_object = async function patch_object({
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
console.error(`API PATCH request timed out after ${timeout}ms.`);
console.error(
`API PATCH request timed out after ${timeout}ms.`
);
controller.abort();
}, timeout);
@@ -156,9 +170,10 @@ export const patch_object = async function patch_object({
signal: controller.signal
};
const response = await fetch_method(url.toString(), fetchOptions).catch(function (
error: any
) {
const response = await fetch_method(
url.toString(),
fetchOptions
).catch(function (error: any) {
console.log(
'API PATCH Object *fetch* request was aborted or failed in an unexpected way.',
error
@@ -173,30 +188,53 @@ export const patch_object = async function patch_object({
}
if (log_lvl) {
console.log(`Response: status=${response.status} attempt=${attempt}`);
console.log(
`Response: status=${response.status} attempt=${attempt}`
);
}
if (!response.ok) {
if (response.status === 404) {
if (log_lvl) {
console.log('The response was a 404 not found "error". Returning null.');
console.log(
'The response was a 404 not found "error". Returning null.'
);
}
return null;
}
// FAIL FAST (Section 2D): Do not retry on Auth or Client errors (400, 401, 403, 422)
if (response.status === 400 || response.status === 401 || response.status === 403 || response.status === 422) {
if (log_lvl) console.error(`API Client Failure (${response.status}). Failing fast.`);
if (
response.status === 400 ||
response.status === 401 ||
response.status === 403 ||
response.status === 422
) {
if (log_lvl)
console.error(
`API Client Failure (${response.status}). Failing fast.`
);
if (response.status === 401 || response.status === 403) {
console.warn(`AUTH DIAGNOSTICS (PATCH): Headers sent for ${endpoint}:`, {
has_auth: !!headers_cleaned['Authorization'],
has_api_key: !!headers_cleaned['x-aether-api-key'],
has_account_id: !!headers_cleaned['x-account-id'],
jwt_preview: jwt ? `${jwt.slice(0, 8)}...` : 'MISSING'
});
console.warn(
`AUTH DIAGNOSTICS (PATCH): Headers sent for ${endpoint}:`,
{
has_auth: !!headers_cleaned['Authorization'],
has_api_key:
!!headers_cleaned['x-aether-api-key'],
has_account_id:
!!headers_cleaned['x-account-id'],
jwt_preview: jwt
? `${jwt.slice(0, 8)}...`
: 'MISSING'
}
);
// Signal the root layout to show the session-expired banner.
if (browser) ae_auth_error.set({ type: 'expired', ts: Date.now() });
if (browser)
ae_auth_error.set({
type: 'expired',
ts: Date.now()
});
}
// Structured Error Handling (V3): Attempt to get rich error metadata
@@ -207,7 +245,11 @@ export const patch_object = async function patch_object({
// Not JSON
}
if (log_lvl) console.log('The response was not ok. Structured Error Check:', error_json);
if (log_lvl)
console.log(
'The response was not ok. Structured Error Check:',
error_json
);
if (error_json?.meta?.details) {
return error_json;
@@ -221,7 +263,10 @@ export const patch_object = async function patch_object({
status_code: response.status,
details: {
category: 'validation',
message: typeof error_json.detail === 'string' ? error_json.detail : JSON.stringify(error_json.detail),
message:
typeof error_json.detail === 'string'
? error_json.detail
: JSON.stringify(error_json.detail),
raw: error_json.detail
}
}
@@ -242,7 +287,11 @@ export const patch_object = async function patch_object({
// Return the response data or metadata
// Robustly handle V3 response envelopes
return return_meta ? json : (json.data !== undefined ? json.data : json);
return return_meta
? json
: json.data !== undefined
? json.data
: json;
} catch (error) {
console.error(`API PATCH error on attempt ${attempt}:`, error);

View File

@@ -15,7 +15,7 @@ interface CreateAeObjV3Params {
log_lvl?: number;
}
export async function create_ae_obj_v3({
export async function create_ae_obj({
api_cfg,
obj_type,
fields,
@@ -23,9 +23,9 @@ export async function create_ae_obj_v3({
log_lvl = 0
}: CreateAeObjV3Params) {
const endpoint = `/v3/crud/${obj_type}/`;
if (log_lvl) {
console.log('*** create_ae_obj_v3 ***');
console.log('*** create_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Fields:', fields);
}
@@ -33,7 +33,11 @@ export async function create_ae_obj_v3({
// Standard Aether Pattern: Auto-serialize any key ending in _json
const cleaned_fields = { ...fields };
for (const key in cleaned_fields) {
if (key.endsWith('_json') && cleaned_fields[key] !== null && typeof cleaned_fields[key] === 'object') {
if (
key.endsWith('_json') &&
cleaned_fields[key] !== null &&
typeof cleaned_fields[key] === 'object'
) {
if (log_lvl) console.log(`Auto-serializing field: ${key}`);
cleaned_fields[key] = JSON.stringify(cleaned_fields[key]);
}
@@ -61,13 +65,13 @@ interface CreateNestedObjV3Params {
for_obj_type?: string;
for_obj_id?: string;
obj_type?: string;
fields: key_val;
params?: key_val;
log_lvl?: number;
}
export async function create_nested_obj_v3({
export async function create_nested_obj({
api_cfg,
parent_type,
parent_id,
@@ -86,7 +90,7 @@ export async function create_nested_obj_v3({
const endpoint = `/v3/crud/${p_type}/${p_id}/${c_type}/`;
if (log_lvl) {
console.log('*** create_nested_obj_v3 ***');
console.log('*** create_nested_obj ***');
console.log('Endpoint:', endpoint);
console.log('Fields:', fields);
}
@@ -94,7 +98,11 @@ export async function create_nested_obj_v3({
// Standard Aether Pattern: Auto-serialize any key ending in _json
const cleaned_fields = { ...fields };
for (const key in cleaned_fields) {
if (key.endsWith('_json') && cleaned_fields[key] !== null && typeof cleaned_fields[key] === 'object') {
if (
key.endsWith('_json') &&
cleaned_fields[key] !== null &&
typeof cleaned_fields[key] === 'object'
) {
cleaned_fields[key] = JSON.stringify(cleaned_fields[key]);
}
}
@@ -121,7 +129,7 @@ interface UpdateAeObjV3Params {
log_lvl?: number;
}
export async function update_ae_obj_v3({
export async function update_ae_obj({
api_cfg,
obj_type,
obj_id,
@@ -132,7 +140,7 @@ export async function update_ae_obj_v3({
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
if (log_lvl) {
console.log('*** update_ae_obj_v3 ***');
console.log('*** update_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Fields:', fields);
}
@@ -140,7 +148,11 @@ export async function update_ae_obj_v3({
// Standard Aether Pattern: Auto-serialize any key ending in _json
const cleaned_fields = { ...fields };
for (const key in cleaned_fields) {
if (key.endsWith('_json') && cleaned_fields[key] !== null && typeof cleaned_fields[key] === 'object') {
if (
key.endsWith('_json') &&
cleaned_fields[key] !== null &&
typeof cleaned_fields[key] === 'object'
) {
if (log_lvl > 1) console.log(`Auto-serializing field: ${key}`);
cleaned_fields[key] = JSON.stringify(cleaned_fields[key]);
}
@@ -172,7 +184,7 @@ interface UpdateNestedObjV3Params {
log_lvl?: number;
}
export async function update_nested_obj_v3({
export async function update_nested_obj({
api_cfg,
parent_type,
parent_id,
@@ -194,7 +206,7 @@ export async function update_nested_obj_v3({
const endpoint = `/v3/crud/${p_type}/${p_id}/${c_type}/${c_id}`;
if (log_lvl) {
console.log('*** update_nested_obj_v3 ***');
console.log('*** update_nested_obj ***');
console.log('Endpoint:', endpoint);
console.log('Fields:', fields);
}
@@ -202,7 +214,11 @@ export async function update_nested_obj_v3({
// Standard Aether Pattern: Auto-serialize any key ending in _json
const cleaned_fields = { ...fields };
for (const key in cleaned_fields) {
if (key.endsWith('_json') && cleaned_fields[key] !== null && typeof cleaned_fields[key] === 'object') {
if (
key.endsWith('_json') &&
cleaned_fields[key] !== null &&
typeof cleaned_fields[key] === 'object'
) {
cleaned_fields[key] = JSON.stringify(cleaned_fields[key]);
}
}
@@ -233,7 +249,7 @@ interface DeleteAeObjV3Params {
* Delete a single object by ID (V3)
* Supports 'delete' (hard), 'soft_delete', 'disable' (enable=false), and 'hide' (hide=true).
*/
export async function delete_ae_obj_v3({
export async function delete_ae_obj({
api_cfg,
obj_type,
obj_id,
@@ -245,7 +261,7 @@ export async function delete_ae_obj_v3({
const query_params = { ...params, method };
if (log_lvl) {
console.log('*** delete_ae_obj_v3 ***');
console.log('*** delete_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
}
@@ -278,7 +294,7 @@ interface DeleteNestedAeObjV3Params {
/**
* Delete a single nested object by ID (V3)
*/
export async function delete_nested_ae_obj_v3({
export async function delete_nested_ae_obj({
api_cfg,
parent_type,
parent_id,
@@ -301,7 +317,7 @@ export async function delete_nested_ae_obj_v3({
const query_params = { ...params, method };
if (log_lvl) {
console.log('*** delete_nested_ae_obj_v3 ***');
console.log('*** delete_nested_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
}

View File

@@ -10,7 +10,10 @@ interface SearchAeObjV3Params {
view?: string;
for_obj_type?: string;
for_obj_id?: string;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[] | null;
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[]
| null;
limit?: number;
offset?: number;
delay_ms?: number;
@@ -19,7 +22,7 @@ interface SearchAeObjV3Params {
log_lvl?: number;
}
export async function search_ae_obj_v3({
export async function search_ae_obj({
api_cfg,
obj_type,
search_query,
@@ -55,13 +58,16 @@ export async function search_ae_obj_v3({
// Serialize any complex objects in the query params (e.g. ft_qry, lk_qry)
for (const key in query_params) {
if (typeof query_params[key] === 'object' && query_params[key] !== null) {
if (
typeof query_params[key] === 'object' &&
query_params[key] !== null
) {
query_params[key] = JSON.stringify(query_params[key]);
}
}
if (log_lvl) {
console.log('*** search_ae_obj_v3 ***');
console.log('*** search_ae_obj ***');
console.log('Endpoint:', endpoint);
console.log('Params:', query_params);
console.log('Search Query:', search_query);
@@ -76,4 +82,4 @@ export async function search_ae_obj_v3({
data: search_query,
log_lvl
});
}
}

View File

@@ -41,7 +41,9 @@ export const post_object = async function post_object({
retry_count?: number;
}) {
if (log_lvl) {
console.log(`*** post_object() *** Endpoint: ${endpoint} Task ID: ${task_id}`);
console.log(
`*** post_object() *** Endpoint: ${endpoint} Task ID: ${task_id}`
);
console.log('Params:', params);
if (log_lvl > 1) {
console.log('Data:', data);
@@ -65,7 +67,9 @@ export const post_object = async function post_object({
// Construct the URL with query parameters
const url = new URL(endpoint, api_cfg['base_url']);
if (params) {
Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url.searchParams.append(key, params[key])
);
}
// Clean and merge headers
@@ -95,14 +99,19 @@ export const post_object = async function post_object({
}
// Handle "Bootstrap Paradox" for unauthenticated requests
const bypass_val = merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass = bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
const bypass_val =
merged_headers['x-no-account-id'] || merged_headers['x_no_account_id'];
const is_valid_bypass =
bypass_val === 'bypass' ||
bypass_val === 'Nothing to See Here' ||
params['key'] ||
bypass_val === 'direct-download';
if (is_valid_bypass) {
if (log_lvl > 1) console.log('api_post_object: Valid bypass detected. Stripping account ID context.');
if (log_lvl > 1)
console.log(
'api_post_object: Valid bypass detected. Stripping account ID context.'
);
delete merged_headers['x-account-id'];
delete merged_headers['x_account_id'];
} else {
@@ -124,11 +133,12 @@ export const post_object = async function post_object({
}
// Auto-inject Authorization header if JWT is present but header is missing
let jwt = headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
let jwt =
headers_cleaned['jwt'] ||
headers_cleaned['JWT'] ||
api_cfg['jwt'] ||
api_cfg['headers']?.['jwt'] ||
api_cfg['headers']?.['JWT'];
// Final Fallback: Direct check of primary ae_loc key
if (!jwt && typeof localStorage !== 'undefined') {
@@ -143,7 +153,11 @@ export const post_object = async function post_object({
}
}
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
if (
jwt &&
!headers_cleaned['Authorization'] &&
!headers_cleaned['authorization']
) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
@@ -186,13 +200,21 @@ export const post_object = async function post_object({
console.log('Fetch Options:', fetchOptions);
}
const response = await fetch_method(url.toString(), fetchOptions).catch(function (
error: any
) {
const response = await fetch_method(
url.toString(),
fetchOptions
).catch(function (error: any) {
// SILENCE NOISE: Aborted requests shouldn't spam logs at log_lvl 0
if (error.name === 'AbortError' || error.message?.includes('aborted') || error.name === 'TypeError') {
if (
error.name === 'AbortError' ||
error.message?.includes('aborted') ||
error.name === 'TypeError'
) {
if (log_lvl > 1) {
console.log('API POST: Request was aborted or terminated by browser. Expected during navigation.', error);
console.log(
'API POST: Request was aborted or terminated by browser. Expected during navigation.',
error
);
}
return error;
}
@@ -206,9 +228,17 @@ export const post_object = async function post_object({
clearTimeout(timeoutId);
// Check if we should stop due to abort or network failure
if (response instanceof Error || (response && (response.name === 'TypeError' || response.name === 'AbortError'))) {
if (
response instanceof Error ||
(response &&
(response.name === 'TypeError' ||
response.name === 'AbortError'))
) {
if (response.name === 'AbortError') return false;
if (log_lvl > 1) console.log('API POST Object: Detected NetworkError or TypeError. Failing fast.');
if (log_lvl > 1)
console.log(
'API POST Object: Detected NetworkError or TypeError. Failing fast.'
);
return false;
}
@@ -219,30 +249,53 @@ export const post_object = async function post_object({
}
if (log_lvl) {
console.log(`Response: status=${response.status} attempt=${attempt}`);
console.log(
`Response: status=${response.status} attempt=${attempt}`
);
}
if (!response.ok) {
if (response.status === 404) {
if (log_lvl) {
console.log('The response was a 404 not found "error". Returning null.');
console.log(
'The response was a 404 not found "error". Returning null.'
);
}
return null;
}
// FAIL FAST (Section 2D): Do not retry on Auth or Client errors (400, 401, 403, 422)
if (response.status === 400 || response.status === 401 || response.status === 403 || response.status === 422) {
if (log_lvl) console.error(`API Client Failure (${response.status}). Failing fast.`);
if (
response.status === 400 ||
response.status === 401 ||
response.status === 403 ||
response.status === 422
) {
if (log_lvl)
console.error(
`API Client Failure (${response.status}). Failing fast.`
);
if (response.status === 401 || response.status === 403) {
console.warn(`AUTH DIAGNOSTICS (POST): Headers sent for ${endpoint}:`, {
has_auth: !!headers_cleaned['Authorization'],
has_api_key: !!headers_cleaned['x-aether-api-key'],
has_account_id: !!headers_cleaned['x-account-id'],
jwt_preview: jwt ? `${jwt.slice(0, 8)}...` : 'MISSING'
});
console.warn(
`AUTH DIAGNOSTICS (POST): Headers sent for ${endpoint}:`,
{
has_auth: !!headers_cleaned['Authorization'],
has_api_key:
!!headers_cleaned['x-aether-api-key'],
has_account_id:
!!headers_cleaned['x-account-id'],
jwt_preview: jwt
? `${jwt.slice(0, 8)}...`
: 'MISSING'
}
);
// Signal the root layout to show the session-expired banner.
if (browser) ae_auth_error.set({ type: 'expired', ts: Date.now() });
if (browser)
ae_auth_error.set({
type: 'expired',
ts: Date.now()
});
}
// Structured Error Handling (V3): Attempt to get rich error metadata
@@ -253,7 +306,11 @@ export const post_object = async function post_object({
// Not JSON
}
if (log_lvl) console.log('The response was not ok. Structured Error Check:', error_json);
if (log_lvl)
console.log(
'The response was not ok. Structured Error Check:',
error_json
);
if (error_json?.meta?.details) {
return error_json;
@@ -267,7 +324,10 @@ export const post_object = async function post_object({
status_code: response.status,
details: {
category: 'validation',
message: typeof error_json.detail === 'string' ? error_json.detail : JSON.stringify(error_json.detail),
message:
typeof error_json.detail === 'string'
? error_json.detail
: JSON.stringify(error_json.detail),
raw: error_json.detail
}
}
@@ -311,7 +371,11 @@ export const post_object = async function post_object({
// Return the response data or metadata
// Robustly handle V3 response envelopes
return return_meta ? json : (json.data !== undefined ? json.data : json);
return return_meta
? json
: json.data !== undefined
? json.data
: json;
} else {
const blob = await response.blob();

View File

@@ -36,11 +36,13 @@ export async function load_ae_obj_id__archive({
log_lvl?: number;
}): Promise<ae_Archive | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__archive() *** archive_id=${archive_id}`);
console.log(
`*** load_ae_obj_id__archive() *** archive_id=${archive_id}`
);
}
ae_promises.load__archive_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg: api_cfg,
obj_type: 'archive',
obj_id: archive_id,
@@ -52,10 +54,11 @@ export async function load_ae_obj_id__archive({
.then(async function (archive_obj_get_result) {
if (archive_obj_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__archive_props({
obj_li: [archive_obj_get_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__archive_props({
obj_li: [archive_obj_get_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_archives,
table_name: 'archive',
@@ -76,19 +79,21 @@ export async function load_ae_obj_id__archive({
if (inc_content_li && ae_promises.load__archive_obj) {
// Load the contents for the archive
const load_archive_content_obj_li = await load_ae_obj_li__archive_content({
api_cfg: api_cfg,
for_obj_type: 'archive',
for_obj_id: archive_id,
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
try_cache: try_cache,
log_lvl: log_lvl
});
ae_promises.load__archive_obj.archive_content_li = load_archive_content_obj_li;
const load_archive_content_obj_li =
await load_ae_obj_li__archive_content({
api_cfg: api_cfg,
for_obj_type: 'archive',
for_obj_id: archive_id,
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
try_cache: try_cache,
log_lvl: log_lvl
});
ae_promises.load__archive_obj.archive_content_li =
load_archive_content_obj_li;
}
return ae_promises.load__archive_obj;
@@ -125,7 +130,9 @@ export async function load_ae_obj_li__archive({
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[];
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[];
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
@@ -135,15 +142,17 @@ export async function load_ae_obj_li__archive({
`*** load_ae_obj_li__archive() *** for_obj_type=${for_obj_type} for_obj_id=${for_obj_id}`
);
}
// DEBUG: Trace massive content loads
if (inc_content_li) {
console.warn(`load_ae_obj_li__archive: Loading content for ALL archives in list! Limit: ${limit}`);
// console.trace();
console.warn(
`load_ae_obj_li__archive: Loading content for ALL archives in list! Limit: ${limit}`
);
// console.trace();
}
ae_promises.load__archive_obj_li = await api
.get_ae_obj_li_v3({
.get_ae_obj_li({
api_cfg,
obj_type: 'archive',
for_obj_type,
@@ -159,10 +168,11 @@ export async function load_ae_obj_li__archive({
.then(async function (archive_obj_li_get_result) {
if (archive_obj_li_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__archive_props({
obj_li: archive_obj_li_get_result,
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__archive_props({
obj_li: archive_obj_li_get_result,
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_archives,
table_name: 'archive',
@@ -220,10 +230,12 @@ export async function create_ae_obj__archive({
log_lvl?: number;
}): Promise<ae_Archive | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__archive() *** account_id=${account_id}`);
console.log(
`*** create_ae_obj__archive() *** account_id=${account_id}`
);
}
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'archive',
fields: {
@@ -268,10 +280,12 @@ export async function delete_ae_obj_id__archive({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__archive() *** archive_id=${archive_id}`);
console.log(
`*** delete_ae_obj_id__archive() *** archive_id=${archive_id}`
);
}
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'archive',
obj_id: archive_id,
@@ -304,10 +318,13 @@ export async function update_ae_obj__archive({
log_lvl?: number;
}): Promise<ae_Archive | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__archive() *** archive_id=${archive_id}`, data_kv);
console.log(
`*** update_ae_obj__archive() *** archive_id=${archive_id}`,
data_kv
);
}
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'archive',
obj_id: archive_id,
@@ -366,14 +383,18 @@ export async function qry__archive({
const search_query: any = { and: [] };
if (account_id) {
search_query.and.push({ field: 'account_id_random', op: 'eq', value: account_id });
search_query.and.push({
field: 'account_id_random',
op: 'eq',
value: account_id
});
}
if (qry_str) {
search_query.q = qry_str;
}
ae_promises.load__archive_obj_li = await api.search_ae_obj_v3({
ae_promises.load__archive_obj_li = await api.search_ae_obj({
api_cfg,
obj_type: 'archive',
search_query,
@@ -452,11 +473,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -489,4 +514,4 @@ export async function process_ae_obj__archive_props({
return obj;
}
});
}
}

View File

@@ -30,7 +30,7 @@ export async function load_ae_obj_id__archive_content({
}
ae_promises.load__archive_content_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg: api_cfg,
obj_type: 'archive_content',
obj_id: archive_content_id,
@@ -41,10 +41,11 @@ export async function load_ae_obj_id__archive_content({
.then(async function (archive_content_obj_get_result) {
if (archive_content_obj_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__archive_content_props({
obj_li: [archive_content_obj_get_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__archive_content_props({
obj_li: [archive_content_obj_get_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_archives,
table_name: 'content',
@@ -96,7 +97,9 @@ export async function load_ae_obj_li__archive_content({
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[];
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[];
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
@@ -108,7 +111,7 @@ export async function load_ae_obj_li__archive_content({
}
ae_promises.load__archive_content_obj_li = await api
.get_ae_obj_li_v3({
.get_ae_obj_li({
api_cfg: api_cfg,
obj_type: 'archive_content',
for_obj_type,
@@ -124,10 +127,11 @@ export async function load_ae_obj_li__archive_content({
.then(async function (archive_content_obj_li_get_result) {
if (archive_content_obj_li_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__archive_content_props({
obj_li: archive_content_obj_li_get_result,
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__archive_content_props({
obj_li: archive_content_obj_li_get_result,
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_archives,
table_name: 'content',
@@ -162,15 +166,19 @@ export async function create_ae_obj__archive_content({
log_lvl?: number;
}): Promise<ae_ArchiveContent | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__archive_content() *** archive_id=${archive_id}`);
console.log(
`*** create_ae_obj__archive_content() *** archive_id=${archive_id}`
);
}
if (!archive_id) {
console.log(`ERROR: Archives - Content - archive_id required to create`);
console.log(
`ERROR: Archives - Content - archive_id required to create`
);
return null;
}
const result = await api.create_nested_obj_v3({
const result = await api.create_nested_obj({
api_cfg,
parent_type: 'archive',
parent_id: archive_id,
@@ -219,7 +227,7 @@ export async function delete_ae_obj_id__archive_content({
);
}
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'archive_content',
obj_id: archive_content_id,
@@ -258,7 +266,7 @@ export async function update_ae_obj__archive_content({
);
}
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'archive_content',
obj_id: archive_content_id,
@@ -357,11 +365,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -399,4 +411,4 @@ export async function process_ae_obj__archive_content_props({
return obj;
}
});
}
}

View File

@@ -14,14 +14,14 @@ import type { key_val } from '$lib/stores/ae_stores';
*/
export interface Archive {
id: string;
// id_random: string;
// id_random: string; // NO LONGER USE "_random"
archive_id: string;
// archive_id_random: string;
// archive_id_random: string; // NO LONGER USE "_random"
code?: null | string;
account_id: string;
// account_id_random: string;
// account_id_random: string; // NO LONGER USE "_random"
// archive_type: string;
@@ -80,12 +80,12 @@ export interface Archive {
*/
export interface Archive_Content {
id: string;
// id_random: string;
// id_random: string; // NO LONGER USE "_random"
archive_content_id: string;
// archive_content_id_random: string;
// archive_content_id_random: string; // NO LONGER USE "_random"
archive_id: string;
// archive_id_random: string;
// archive_id_random: string; // NO LONGER USE "_random"
archive_content_type: string;
@@ -169,10 +169,6 @@ export class MySubClassedDexie extends Dexie {
enable, hide, priority, sort, group, notes, created_on, updated_on, [group+priority+sort+updated_on]`
});
// file_path,
// filename, file_extension,
// original_datetime, original_timezone, original_location, original_url, original_url_text,
// enable_for_public,
}
}

View File

@@ -1,188 +1,191 @@
<script lang="ts">
// Imports
// Import components and elements
// import Element_input_files_tbl from '$lib/element_input_files_tbl.svelte';
// Imports
// Import components and elements
// import Element_input_files_tbl from '$lib/element_input_files_tbl.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import { Check, Download, LoaderCircle, MinusCircle, Scissors } from '@lucide/svelte';
// Exports
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import {
Check,
Download,
LoaderCircle,
MinusCircle,
Scissors
} from '@lucide/svelte';
// Exports
// export let input_name = 'file_list';
// export let multiple: boolean = true;
// export let required: boolean = true;
// export let input_name = 'file_list';
// export let multiple: boolean = true;
// export let required: boolean = true;
// export let input_class_li: string[] = ['file_drop_area'];
// export let input_class_li: string[] = ['file_drop_area'];
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
// export let accept: string = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh';
class_li_default?: string;
class_li?: string;
// export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , 'text-sm'];
clip_complete?: boolean;
// export let upload_complete: boolean = false;
submit_status?: null | string;
// hosted_file_id_li?: string[];
// hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
video_clip_file_kv?: key_val;
}
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
// export let accept: string = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh';
class_li_default?: string;
class_li?: string;
// export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , 'text-sm'];
clip_complete?: boolean;
// export let upload_complete: boolean = false;
submit_status?: null | string;
// hosted_file_id_li?: string[];
// hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
video_clip_file_kv?: key_val;
}
let {
log_lvl = $bindable(0),
link_to_type = $bindable(),
link_to_id = $bindable(),
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = $bindable(''),
clip_complete = $bindable(false),
submit_status = $bindable(null),
// hosted_file_id_li = [],
// hosted_file_obj_li = [],
hosted_file_obj_kv = $bindable({}),
video_clip_file_kv = $bindable({})
}: Props = $props();
let {
log_lvl = $bindable(0),
link_to_type = $bindable(),
link_to_id = $bindable(),
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = $bindable(''),
clip_complete = $bindable(false),
submit_status = $bindable(null),
// hosted_file_id_li = [],
// hosted_file_obj_li = [],
hosted_file_obj_kv = $bindable({}),
video_clip_file_kv = $bindable({})
}: Props = $props();
// Local Variables
let task_id = link_to_id;
// let input_file_list: any = null;
let ae_promises: key_val = $state({});
// let ae_promises_clipping: key_val = {};
// let ae_triggers: key_val = {};
// Local Variables
let task_id = link_to_id;
// let input_file_list: any = null;
let ae_promises: key_val = $state({});
// let ae_promises_clipping: key_val = {};
// let ae_triggers: key_val = {};
// let input_element_id = 'ae_comp__hosted_files_upload__input';
// let input_element_id = 'ae_comp__hosted_files_upload__input';
// let form_kv: key_val = {
// start_time: null,
// end_time: null,
// reencode: null,
// video_file: null,
// };
// let download_clip_src: string;
// let download_clip_filename: string;
// let form_kv: key_val = {
// start_time: null,
// end_time: null,
// reencode: null,
// video_file: null,
// };
// let download_clip_src: string;
// let download_clip_filename: string;
$ae_sess.files.obj = {
obj: null
$ae_sess.files.obj = {
obj: null
};
// *** Functions and Logic
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.preventDefault();
fn(event);
};
}
function handle_clip_video(event: Event) {
console.log('*** handle_clip_video() ***');
submit_status = 'clipping';
clip_complete = false;
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
let hosted_file_id = formData.get('hosted_file_id') as string;
let start_time = formData.get('start_time') as string;
let end_time = formData.get('end_time') as string;
let reencode = formData.get('reencode') as string;
let scale_down = formData.get('scale_down') as string;
let new_filename = formData.get('new_filename') as string;
$ae_sess.files.processed_file_kv[hosted_file_id] = {};
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status = 'clipping';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete = false;
// $ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_loc.files.processed_file_kv[hosted_file_id] = {};
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status = 'clipping';
$ae_loc.files.processed_file_kv[hosted_file_id].start_time = start_time;
$ae_loc.files.processed_file_kv[hosted_file_id].end_time = end_time;
$ae_loc.files.processed_file_kv[hosted_file_id].reencode = reencode;
$ae_loc.files.processed_file_kv[hosted_file_id].scale_down = scale_down;
$ae_loc.files.processed_file_kv[hosted_file_id].new_filename = new_filename;
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete = false;
let endpoint = `/v3/action/hosted_file/${hosted_file_id}/clip_video`;
let params = {
link_to_type: link_to_type,
link_to_id: link_to_id,
filename_no_ext: new_filename.replace('.mp4', ''),
from_type: 'mp4', // Video file type being converted
to_type: 'mp4', // Video file type to convert to
start_time: start_time,
end_time: end_time,
reencode: reencode,
scale_down: scale_down
};
// *** Functions and Logic
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.preventDefault();
fn(event);
};
}
ae_promises[hosted_file_id] = {};
// .convert__hosted_file_obj
ae_promises[hosted_file_id] = api
.get_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
timeout: 300000, // 5 minutes
// return_blob: true,
// filename: event.target.new_filename.value,
// auto_download: false,
task_id: task_id,
log_lvl: log_lvl
})
.then(function (result) {
console.log(result);
function handle_clip_video(event: Event) {
console.log('*** handle_clip_video() ***');
video_clip_file_kv[result.hosted_file_id] = {};
video_clip_file_kv[result.hosted_file_id] = result;
submit_status = 'clipping';
clip_complete = false;
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = {};
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = result;
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete =
true;
let hosted_file_id = formData.get('hosted_file_id') as string;
let start_time = formData.get('start_time') as string;
let end_time = formData.get('end_time') as string;
let reencode = formData.get('reencode') as string;
let scale_down = formData.get('scale_down') as string;
let new_filename = formData.get('new_filename') as string;
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete =
true;
$ae_sess.files.processed_file_kv[hosted_file_id] = {};
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipping';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete = false;
submit_status = 'clipped';
clip_complete = true;
// $ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_loc.files.processed_file_kv[hosted_file_id] = {};
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipping';
$ae_loc.files.processed_file_kv[hosted_file_id].start_time = start_time;
$ae_loc.files.processed_file_kv[hosted_file_id].end_time = end_time;
$ae_loc.files.processed_file_kv[hosted_file_id].reencode = reencode;
$ae_loc.files.processed_file_kv[hosted_file_id].scale_down = scale_down;
$ae_loc.files.processed_file_kv[hosted_file_id].new_filename =
new_filename;
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete = false;
// let file_blob = new Blob([result.data]);
// // console.log(file_blob);
// let file_obj_url = window.URL.createObjectURL(file_blob); // The img src
// // const url = window.URL.createObjectURL(new Blob([result.data]));
// download_clip_src = file_obj_url;
// // download_filename = file_obj_url;
let endpoint = `/hosted_file/${hosted_file_id}/clip_video`;
let params = {
link_to_type: link_to_type,
link_to_id: link_to_id,
filename_no_ext: new_filename.replace('.mp4', ''),
from_type: 'mp4', // Video file type being converted
to_type: 'mp4', // Video file type to convert to
start_time: start_time,
end_time: end_time,
reencode: reencode,
scale_down: scale_down
};
ae_promises[hosted_file_id] = {};
// .convert__hosted_file_obj
ae_promises[hosted_file_id] = api
.get_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
timeout: 300000, // 5 minutes
// return_blob: true,
// filename: event.target.new_filename.value,
// auto_download: false,
task_id: task_id,
log_lvl: log_lvl
})
.then(function (result) {
console.log(result);
video_clip_file_kv[result.hosted_file_id] = {};
video_clip_file_kv[result.hosted_file_id] = result;
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = {};
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = result;
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete =
true;
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete =
true;
submit_status = 'clipped';
clip_complete = true;
// let file_blob = new Blob([result.data]);
// // console.log(file_blob);
// let file_obj_url = window.URL.createObjectURL(file_blob); // The img src
// // const url = window.URL.createObjectURL(new Blob([result.data]));
// download_clip_src = file_obj_url;
// // download_filename = file_obj_url;
return true;
});
}
return true;
});
}
</script>
<section class="{class_li_default} {class_li}">
@@ -191,11 +194,11 @@
</h3>
{#each Object.entries(hosted_file_obj_kv) as [hosted_file_id, hosted_file_obj] (hosted_file_id)}
<div class="border border-surface-500/20 rounded-lg p-2 m-2 preset-tonal-surface">
<div
class="border-surface-500/20 preset-tonal-surface m-2 rounded-lg border p-2">
<!-- Download Button (Standardized) -->
<div
class="flex flex-row flex-wrap gap-1 justify-center items-center w-full"
>
class="flex w-full flex-row flex-wrap items-center justify-center gap-1">
<!-- Remove from uploaded file kv list -->
<button
type="button"
@@ -219,8 +222,7 @@
);
}}
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500"
title={`Remove this file from list of videos:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}
>
title={`Remove this file from list of videos:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}>
<MinusCircle size="1em" class="m-1" />
<span class="">Remove</span>
</button>
@@ -234,60 +236,49 @@
variant="tonal"
classes="novi_btn btn-sm lg:btn-md min-w-72 lg:min-w-96 !justify-start"
show_divider={true}
max_filename={30}
/>
max_filename={30} />
</div>
<span
>{ae_util.shorten_filename({
filename: hosted_file_obj?.filename,
max_length: 30
})}</span
>
})}</span>
<span>
<span class="text-sm font-bold"> File ID: </span>
{hosted_file_obj.hosted_file_id}</span
>
{hosted_file_obj.hosted_file_id}</span>
<span>
<span class="text-sm font-bold"> Type: </span>
{hosted_file_obj.extension}</span
>
{hosted_file_obj.extension}</span>
<!-- <span>{hosted_file_obj.filename}</span> -->
</div>
<form
onsubmit={prevent_default(handle_clip_video)}
class="{class_li_default} {class_li}"
>
class="{class_li_default} {class_li}">
<!-- {$ae_sess?.files[hosted_file_obj?.hosted_file_id ?? 'obj'].submit_status ?? 'not set'} -->
<input
type="hidden"
name="hosted_file_id"
value={hosted_file_obj.hosted_file_id}
/>
value={hosted_file_obj.hosted_file_id} />
<div
class="flex flex-row gap-1 justify-center items-center w-full"
>
<span class="text-xs font-bold w-32">New Filename:</span>
class="flex w-full flex-row items-center justify-center gap-1">
<span class="w-32 text-xs font-bold">New Filename:</span>
<input
type="text"
class="input w-full text-sm variant-filled-surface"
class="input variant-filled-surface w-full text-sm"
name="new_filename"
value={hosted_file_obj.filename}
/>
value={hosted_file_obj.filename} />
</div>
<div
class="max-w-(--breakpoint-sm) flex flex-row gap-1 justify-center items-center w-full"
>
class="flex w-full max-w-(--breakpoint-sm) flex-row items-center justify-center gap-1">
<label
class="label w-48"
title="The start time of the clip. This is the time in the video where the clip will start. You may need to subtract a few seconds to get the exact start time."
>
title="The start time of the clip. This is the time in the video where the clip will start. You may need to subtract a few seconds to get the exact start time.">
<span class="text-xs font-bold"
>Start time (HH:MM:SS)</span
>
>Start time (HH:MM:SS)</span>
<input
type="text"
name="start_time"
@@ -300,17 +291,14 @@
].start_time
: '00:00:00'}
placeholder="HH:MM:SS (00:01:30)"
class="input w-32 variant-filled-surface"
/>
class="input variant-filled-surface w-32" />
</label>
<label
class="label w-48"
title="The end time of the clip. This is the time in the video where the clip will end. You may need to add a few seconds to get the exact end time."
>
title="The end time of the clip. This is the time in the video where the clip will end. You may need to add a few seconds to get the exact end time.">
<span class="text-xs font-bold"
>End time (HH:MM:SS)</span
>
>End time (HH:MM:SS)</span>
<input
type="text"
name="end_time"
@@ -323,14 +311,12 @@
].end_time
: '00:45:59'}
placeholder="HH:MM:SS (01:05:25)"
class="input w-32 variant-filled-surface"
/>
class="input variant-filled-surface w-32" />
</label>
<span
class="flex flex-col gap-1 items-center justify-center"
title="Re-encode the video file? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files."
>
class="flex flex-col items-center justify-center gap-1"
title="Re-encode the video file? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files.">
<span class="text-xs font-bold"> Re-encode? </span>
<label class="inline-block">
<input
@@ -338,8 +324,7 @@
name="reencode"
value="true"
class="radio"
checked
/>
checked />
True
</label>
<label class="inline-block">
@@ -347,16 +332,14 @@
type="radio"
name="reencode"
value="false"
class="radio"
/>
class="radio" />
False
</label>
</span>
<span
class="flex flex-col gap-1 items-center justify-center"
title="Scale the video file down to 1920x1080? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files."
>
class="flex flex-col items-center justify-center gap-1"
title="Scale the video file down to 1920x1080? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files.">
<span class="text-xs font-bold"> Scale down? </span>
<label class="inline-block">
<input
@@ -364,8 +347,7 @@
name="scale_down"
value="true"
class="radio"
checked
/>
checked />
True
</label>
<label class="inline-block">
@@ -373,8 +355,7 @@
type="radio"
name="scale_down"
value="false"
class="radio"
/>
class="radio" />
False
</label>
</span>
@@ -382,9 +363,8 @@
<button
type="submit"
class="btn btn-lg btn-primary preset-tonal-primary border border-primary-500 hover:preset-filled-primary-500 transition-colors"
disabled={submit_status == 'clipping'}
>
class="btn btn-lg btn-primary preset-tonal-primary border-primary-500 hover:preset-filled-primary-500 border transition-colors"
disabled={submit_status == 'clipping'}>
<!-- {#await ae_promises[hosted_file_id]} -->
{#if $ae_loc.files.processed_file_kv[hosted_file_id] && $ae_loc.files.processed_file_kv[hosted_file_id].submit_status == 'clipping'}
<LoaderCircle size="1em" class="m-1 animate-spin" />
@@ -407,8 +387,7 @@
{#await ae_promises[hosted_file_id]}
<LoaderCircle size="1em" class="m-1 animate-spin" />
<span class="highlight"
>Processing... This may take a few minutes.</span
>
>Processing... This may take a few minutes.</span>
{:then}
{#if ae_promises[hosted_file_id]}
<Download size="1em" /> Ready to download below!

View File

@@ -1,48 +1,48 @@
<script lang="ts">
// Imports
// Import components and elements
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// Imports
// Import components and elements
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
// Exports
// Exports
// export let hosted_file_id_li: string[] = [];
// export let hosted_file_obj_li: any[] = [];
// export let hosted_file_id_li: string[] = [];
// export let hosted_file_obj_li: any[] = [];
interface Props {
log_lvl?: number;
// export let hosted_file_obj_kv: key_val = {};
video_clip_file_kv?: key_val;
class_li_default?: string;
class_li?: string;
link_to_type: string;
link_to_id: string;
}
interface Props {
log_lvl?: number;
// export let hosted_file_obj_kv: key_val = {};
video_clip_file_kv?: key_val;
class_li_default?: string;
class_li?: string;
link_to_type: string;
link_to_id: string;
}
let {
log_lvl = 0,
video_clip_file_kv = $bindable({}),
class_li_default = 'flex flex-row flex-wrap gap-2 items-center justify-center w-full max-w-2xl p-2 mx-auto my-1 border border-surface-500/20 rounded-lg preset-tonal-surface',
class_li = '',
link_to_type,
link_to_id
}: Props = $props();
let {
log_lvl = 0,
video_clip_file_kv = $bindable({}),
class_li_default = 'flex flex-row flex-wrap gap-2 items-center justify-center w-full max-w-2xl p-2 mx-auto my-1 border border-surface-500/20 rounded-lg preset-tonal-surface',
class_li = '',
link_to_type,
link_to_id
}: Props = $props();
let ae_promises: key_val = $state({});
let ae_promises: key_val = $state({});
</script>
<h3 class="h3">{Object.entries(video_clip_file_kv).length}× files clipped</h3>
@@ -54,7 +54,6 @@
max_filename={30}
classes="btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
linked_to_type={link_to_type}
linked_to_id={link_to_id}
/>
linked_to_id={link_to_id} />
{/each}
</div>

View File

@@ -1,243 +1,275 @@
<script lang="ts">
// *** Import Svelte specific
import * as Lucide from 'lucide-svelte';
import { fade } from 'svelte/transition';
// *** Import Svelte specific
import * as Lucide from 'lucide-svelte';
import { fade } from 'svelte/transition';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
import {
ae_loc,
ae_sess,
ae_api
} from '$lib/stores/ae_stores';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
interface Props {
log_lvl?: number;
hosted_file_id: null | string;
hosted_file_obj: null | key_val;
filename?: null | string;
max_filename?: number;
auto_download?: boolean;
linked_to_type?: null | string;
linked_to_id?: null | string;
download_complete?: null | boolean;
download_percent?: number;
download_status_msg?: string;
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
show_divider?: boolean;
show_direct_download?: boolean;
require_auth?: boolean;
classes?: string;
click?: () => void | Promise<any>;
label?: import('svelte').Snippet;
interface Props {
log_lvl?: number;
hosted_file_id: null | string;
hosted_file_obj: null | key_val;
filename?: null | string;
max_filename?: number;
auto_download?: boolean;
linked_to_type?: null | string;
linked_to_id?: null | string;
download_complete?: null | boolean;
download_percent?: number;
download_status_msg?: string;
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
color?:
| 'primary'
| 'secondary'
| 'tertiary'
| 'success'
| 'warning'
| 'error'
| 'surface';
show_divider?: boolean;
show_direct_download?: boolean;
require_auth?: boolean;
classes?: string;
click?: () => void | Promise<any>;
label?: import('svelte').Snippet;
}
let {
log_lvl = 0,
hosted_file_id,
hosted_file_obj,
filename = $bindable(null),
max_filename = $bindable(30),
auto_download = true,
linked_to_type = $bindable(null),
linked_to_id = $bindable(null),
download_complete = $bindable(),
download_percent = $bindable(),
download_status_msg = $bindable('Not started'),
variant = 'tonal',
color = 'primary',
show_divider = true,
show_direct_download = false,
require_auth = true,
classes = '',
click,
label
}: Props = $props();
// Map variant/color to classes using literal strings so Tailwind can find them
const color_map: Record<string, Record<string, string>> = {
primary: {
tonal: 'preset-tonal-primary border border-primary-500/30 hover:preset-filled-primary-500',
filled: 'preset-filled-primary-500 hover:preset-filled-primary-600',
outline: 'border border-primary-500 hover:preset-tonal-primary',
ghost: 'hover:preset-tonal-primary'
},
secondary: {
tonal: 'preset-tonal-secondary border border-secondary-500/30 hover:preset-filled-secondary-500',
filled: 'preset-filled-secondary-500 hover:preset-filled-secondary-600',
outline: 'border border-secondary-500 hover:preset-tonal-secondary',
ghost: 'hover:preset-tonal-secondary'
},
tertiary: {
tonal: 'preset-tonal-tertiary border border-tertiary-500/30 hover:preset-filled-tertiary-500',
filled: 'preset-filled-tertiary-500 hover:preset-filled-tertiary-600',
outline: 'border border-tertiary-500 hover:preset-tonal-tertiary',
ghost: 'hover:preset-tonal-tertiary'
},
success: {
tonal: 'preset-tonal-success border border-success-500/30 hover:preset-filled-success-500',
filled: 'preset-filled-success-500 hover:preset-filled-success-600',
outline: 'border border-success-500 hover:preset-tonal-success',
ghost: 'hover:preset-tonal-success'
},
warning: {
tonal: 'preset-tonal-warning border border-warning-500/30 hover:preset-filled-warning-500',
filled: 'preset-filled-warning-500 hover:preset-filled-warning-600',
outline: 'border border-warning-500 hover:preset-tonal-warning',
ghost: 'hover:preset-tonal-warning'
},
error: {
tonal: 'preset-tonal-error border border-error-500/30 hover:preset-filled-error-500',
filled: 'preset-filled-error-500 hover:preset-filled-error-600',
outline: 'border border-error-500 hover:preset-tonal-error',
ghost: 'hover:preset-tonal-error'
},
surface: {
tonal: 'preset-tonal-surface border border-surface-500/30 hover:preset-filled-surface-500',
filled: 'preset-filled-surface-500 hover:preset-filled-surface-600',
outline: 'border border-surface-500 hover:preset-tonal-surface',
ghost: 'hover:preset-tonal-surface'
}
};
let variant_classes = $derived.by(() => {
const base =
'btn btn-sm lg:btn-md min-w-48 transition-all overflow-hidden px-3';
const style = color_map[color]?.[variant] || color_map.primary.tonal;
return `${base} ${style} ${classes}`.trim();
});
let show_filename_view = $state(true);
let status_interval: any;
$effect(() => {
if (log_lvl) {
console.log(
`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`,
hosted_file_obj
);
}
});
let ae_promises: key_val = $state({});
$effect(() => {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
download_percent = $ae_sess.api_download_kv[file_id].percent_completed;
}
});
// Reactive timer to alternate views during active download
$effect(() => {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
const is_actively_downloading =
ae_promises[file_id] && download_complete === undefined;
if (is_actively_downloading) {
if (!status_interval) {
status_interval = setInterval(() => {
show_filename_view = !show_filename_view;
}, 3000);
}
} else {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
show_filename_view = true; // Default view when not downloading
}
let {
log_lvl = 0,
hosted_file_id,
hosted_file_obj,
filename = $bindable(null),
max_filename = $bindable(30),
auto_download = true,
linked_to_type = $bindable(null),
linked_to_id = $bindable(null),
download_complete = $bindable(),
download_percent = $bindable(),
download_status_msg = $bindable('Not started'),
variant = 'tonal',
color = 'primary',
show_divider = true,
show_direct_download = false,
require_auth = true,
classes = '',
click,
label
}: Props = $props();
// Map variant/color to classes using literal strings so Tailwind can find them
const color_map: Record<string, Record<string, string>> = {
primary: {
tonal: 'preset-tonal-primary border border-primary-500/30 hover:preset-filled-primary-500',
filled: 'preset-filled-primary-500 hover:preset-filled-primary-600',
outline: 'border border-primary-500 hover:preset-tonal-primary',
ghost: 'hover:preset-tonal-primary'
},
secondary: {
tonal: 'preset-tonal-secondary border border-secondary-500/30 hover:preset-filled-secondary-500',
filled: 'preset-filled-secondary-500 hover:preset-filled-secondary-600',
outline: 'border border-secondary-500 hover:preset-tonal-secondary',
ghost: 'hover:preset-tonal-secondary'
},
tertiary: {
tonal: 'preset-tonal-tertiary border border-tertiary-500/30 hover:preset-filled-tertiary-500',
filled: 'preset-filled-tertiary-500 hover:preset-filled-tertiary-600',
outline: 'border border-tertiary-500 hover:preset-tonal-tertiary',
ghost: 'hover:preset-tonal-tertiary'
},
success: {
tonal: 'preset-tonal-success border border-success-500/30 hover:preset-filled-success-500',
filled: 'preset-filled-success-500 hover:preset-filled-success-600',
outline: 'border border-success-500 hover:preset-tonal-success',
ghost: 'hover:preset-tonal-success'
},
warning: {
tonal: 'preset-tonal-warning border border-warning-500/30 hover:preset-filled-warning-500',
filled: 'preset-filled-warning-500 hover:preset-filled-warning-600',
outline: 'border border-warning-500 hover:preset-tonal-warning',
ghost: 'hover:preset-tonal-warning'
},
error: {
tonal: 'preset-tonal-error border border-error-500/30 hover:preset-filled-error-500',
filled: 'preset-filled-error-500 hover:preset-filled-error-600',
outline: 'border border-error-500 hover:preset-tonal-error',
ghost: 'hover:preset-tonal-error'
},
surface: {
tonal: 'preset-tonal-surface border border-surface-500/30 hover:preset-filled-surface-500',
filled: 'preset-filled-surface-500 hover:preset-filled-surface-600',
outline: 'border border-surface-500 hover:preset-tonal-surface',
ghost: 'hover:preset-tonal-surface'
return () => {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
};
});
let variant_classes = $derived.by(() => {
const base = 'btn btn-sm lg:btn-md min-w-48 transition-all overflow-hidden px-3';
const style = color_map[color]?.[variant] || color_map.primary.tonal;
return `${base} ${style} ${classes}`.trim();
});
let show_filename_view = $state(true);
let status_interval: any;
$effect(() => {
if (log_lvl) {
console.log(
`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`,
hosted_file_obj
);
}
});
let ae_promises: key_val = $state({});
$effect(() => {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
download_percent =
$ae_sess.api_download_kv[file_id].percent_completed;
}
});
// Reactive timer to alternate views during active download
$effect(() => {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
const is_actively_downloading = ae_promises[file_id] && download_complete === undefined;
if (is_actively_downloading) {
if (!status_interval) {
status_interval = setInterval(() => {
show_filename_view = !show_filename_view;
}, 3000);
}
} else {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
show_filename_view = true; // Default view when not downloading
}
return () => {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
};
});
let final_filename = $derived(filename ?? hosted_file_obj?.filename ?? 'unknown');
let shortened_filename = $derived(ae_util.shorten_filename({
let final_filename = $derived(
filename ?? hosted_file_obj?.filename ?? 'unknown'
);
let shortened_filename = $derived(
ae_util.shorten_filename({
filename: final_filename,
max_length: max_filename
}));
})
);
let direct_download_url = $derived.by(() => {
if (!show_direct_download || !hosted_file_obj) return '';
// IMPORTANT: For Direct Link Mode, we MUST use the V3 Action endpoint to support Random String IDs.
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
const file_id = hosted_file_obj.event_file_id || hosted_file_obj.hosted_file_id || hosted_file_id;
const obj_type_path = hosted_file_obj.event_file_id ? 'event_file' : 'hosted_file';
return `${$ae_api.base_url}/v3/action/${obj_type_path}/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
});
let direct_download_url = $derived.by(() => {
if (!show_direct_download || !hosted_file_obj) return '';
// IMPORTANT: For Direct Link Mode, we MUST use the V3 Action endpoint to support Random String IDs.
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
const file_id =
hosted_file_obj.event_file_id ||
hosted_file_obj.hosted_file_id ||
hosted_file_id;
const obj_type_path = hosted_file_obj.event_file_id
? 'event_file'
: 'hosted_file';
return `${$ae_api.base_url}/v3/action/${obj_type_path}/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
});
async function handle_click() {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
download_complete = undefined;
download_status_msg = 'Downloading...';
async function handle_click() {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
download_complete = undefined;
download_status_msg = 'Downloading...';
if (click) {
const result = click();
// If the override returns a promise, track it so the UI shows progress
if (result instanceof Promise) {
ae_promises[file_id] = result;
}
return;
if (click) {
const result = click();
// If the override returns a promise, track it so the UI shows progress
if (result instanceof Promise) {
ae_promises[file_id] = result;
}
ae_promises[file_id] = download_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
return_file: true,
filename: final_filename,
auto_download: auto_download,
log_lvl: log_lvl
})
.then((result) => {
if (result === null) {
console.log('File not found (404)');
download_complete = null;
download_status_msg = 'File not found';
} else if (result === false) {
console.log(
'Possible error with API server (check network and server status)'
);
download_complete = false;
download_status_msg = 'Failed to download';
} else {
// console.log('File found and downloaded');
download_complete = true;
download_status_msg = 'File downloaded';
}
return result;
});
return;
}
ae_promises[file_id] = download_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
return_file: true,
filename: final_filename,
auto_download: auto_download,
log_lvl: log_lvl
}).then((result) => {
if (result === null) {
console.log('File not found (404)');
download_complete = null;
download_status_msg = 'File not found';
} else if (result === false) {
console.log(
'Possible error with API server (check network and server status)'
);
download_complete = false;
download_status_msg = 'Failed to download';
} else {
// console.log('File found and downloaded');
download_complete = true;
download_status_msg = 'File downloaded';
}
return result;
});
}
</script>
{#snippet content()}
{@const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id}
{@const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id}
{#await ae_promises[file_id]}
<div class="flex items-center w-full min-h-[1.5rem]">
<div class="flex min-h-[1.5rem] w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<Lucide.LoaderCircle class="animate-spin" size={18} />
</div>
<div class="grow relative text-left h-full">
<div class="relative h-full grow text-left">
{#if show_filename_view}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="flex items-center h-full">
<div
in:fade={{ duration: 250 }}
out:fade={{ duration: 250 }}
class="flex h-full items-center">
<span class="truncate">
{shortened_filename}
</span>
</div>
{:else}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="absolute inset-0 flex items-center h-full">
<div
in:fade={{ duration: 250 }}
out:fade={{ duration: 250 }}
class="absolute inset-0 flex h-full items-center">
<span class="font-bold whitespace-nowrap">
Downloading:
{#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id].percent_completed}%
{$ae_sess.api_download_kv[file_id]
.percent_completed}%
{:else}
...
{/if}
@@ -250,18 +282,22 @@
{#if label}
{@render label()}
{:else}
{@const IconComp = ae_util.file_extension_icon_lucide(hosted_file_obj?.extension)}
<div class="flex items-center w-full">
{@const IconComp = ae_util.file_extension_icon_lucide(
hosted_file_obj?.extension
)}
<div class="flex w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<IconComp size={18} />
</div>
<span class="grow truncate text-left">
{shortened_filename}
</span>
{#if hosted_file_obj?.file_purpose || hosted_file_obj?.group}
<span class="badge preset-tonal-success ml-2 text-[10px] uppercase font-bold shrink-0">
<span
class="badge preset-tonal-success ml-2 shrink-0 text-[10px] font-bold uppercase">
{hosted_file_obj.file_purpose || hosted_file_obj.group}
</span>
{/if}
@@ -270,22 +306,25 @@
{/await}
{#if download_complete === null}
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap">File not found</span>
<span class="ml-2 whitespace-nowrap text-red-800 dark:text-red-200"
>File not found</span>
{:else if download_complete === false}
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap text-xs">Failed!</span>
<span
class="ml-2 text-xs whitespace-nowrap text-red-800 dark:text-red-200"
>Failed!</span>
{/if}
{/snippet}
{#if hosted_file_id && hosted_file_obj}
{@const file_id = hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
{@const file_id =
hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
{#if show_direct_download}
<a
href={direct_download_url}
download={ae_util.clean_filename(final_filename)}
class={variant_classes}
title={`Direct download (V3 Action):\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}`}
>
title={`Direct download (V3 Action):\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}`}>
{@render content()}
</a>
{:else}
@@ -294,20 +333,24 @@
disabled={require_auth && !$ae_loc.authenticated_access}
class={variant_classes}
onclick={handle_click}
title={`Download this file:\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}
>
title={`Download this file:\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}>
{@render content()}
</button>
{/if}
{:else}
<button type="button" disabled class={variant_classes} title="No file selected">
<div class="flex items-center w-full">
<button
type="button"
disabled
class={variant_classes}
title="No file selected">
<div class="flex w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<Lucide.FileX size={18} />
</div>
<span class="grow text-left"> No file info </span>
</div>
</button>
{/if}
{/if}

View File

@@ -1,281 +1,286 @@
<script lang="ts">
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
// Imports
// Import components and elements
import * as Lucide from 'lucide-svelte';
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
// Imports
// Import components and elements
import * as Lucide from 'lucide-svelte';
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
// Exports
// Exports
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
input_name?: string;
multiple?: boolean;
required?: boolean;
accept?: string;
class_li_default?: string;
class_li?: string;
input_class_li?: string[];
table_class_li?: string[];
upload_complete?: boolean;
submit_status?: null | string;
hosted_file_id_li?: string[];
hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
label?: import('svelte').Snippet;
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
input_name?: string;
multiple?: boolean;
required?: boolean;
accept?: string;
class_li_default?: string;
class_li?: string;
input_class_li?: string[];
table_class_li?: string[];
upload_complete?: boolean;
submit_status?: null | string;
hosted_file_id_li?: string[];
hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
label?: import('svelte').Snippet;
}
let {
log_lvl = 0,
link_to_type,
link_to_id,
input_name = 'file_list',
multiple = true,
required = true,
accept = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh',
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = '',
input_class_li = ['file_drop_area'],
table_class_li = ['table', 'table-sm', 'table-striped', '', 'text-sm'],
upload_complete = $bindable(false),
submit_status = $bindable(null),
hosted_file_id_li = $bindable([]),
hosted_file_obj_li = $bindable([]),
hosted_file_obj_kv = $bindable({}),
label
}: Props = $props();
// Local Variables
let task_id: string = $state('');
let input_file_list: any = $state(null);
let ae_promises: key_val = $state({}); // Promise<any>;
let ae_triggers: key_val = {};
let input_element_id = 'ae_comp__hosted_files_upload__input';
$effect(() => {
if (log_lvl) {
console.log(`*** ae_comp__hosted_files_upload.svelte ***`);
console.log(`link_to_type: ${link_to_type} link_to_id: ${link_to_id}`);
}
});
$effect(() => {
// Sync task_id with link_to_id prop so it resets when navigating to a different object.
task_id = link_to_id;
});
// *** Functions and Logic
async function handle_submit_form_files(event: SubmitEvent) {
console.log('*** handle_submit_form() ***');
event.preventDefault();
if (!event) {
return;
}
let {
log_lvl = 0,
link_to_type,
link_to_id,
input_name = 'file_list',
multiple = true,
required = true,
accept = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh',
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = '',
input_class_li = ['file_drop_area'],
table_class_li = ['table', 'table-sm', 'table-striped', '', 'text-sm'],
upload_complete = $bindable(false),
submit_status = $bindable(null),
hosted_file_id_li = $bindable([]),
hosted_file_obj_li = $bindable([]),
hosted_file_obj_kv = $bindable({}),
label
}: Props = $props();
$ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_sess.files.submit_status = 'saving';
submit_status = 'saving';
upload_complete = false;
// Local Variables
let task_id: string = $state('');
let input_file_list: any = $state(null);
let ae_promises: key_val = $state({}); // Promise<any>;
let ae_triggers: key_val = {};
hosted_file_id_li = [];
hosted_file_obj_li = [];
hosted_file_obj_kv = {};
let input_element_id = 'ae_comp__hosted_files_upload__input';
let hosted_file_results;
$effect(() => {
if (log_lvl) {
console.log(`*** ae_comp__hosted_files_upload.svelte ***`);
console.log(`link_to_type: ${link_to_type} link_to_id: ${link_to_id}`);
}
});
const target = event.currentTarget as HTMLFormElement;
const file_input = target
? (target[input_element_id] as HTMLInputElement)
: null;
$effect(() => {
// Sync task_id with link_to_id prop so it resets when navigating to a different object.
task_id = link_to_id;
});
if (
target &&
file_input &&
file_input.files &&
file_input.files.length > 0
) {
task_id = link_to_id; // Ideally this should be the file hash, but we may be uploading multiple files at once. This should be done with a loop instead?
// *** Functions and Logic
async function handle_submit_form_files(event: SubmitEvent) {
console.log('*** handle_submit_form() ***');
event.preventDefault();
// Loop through each file and upload them individually in event.target[input_element_id].files
// The task_id should be the file hash.
// processed_file_list[i] has the file hash_sha256, hash_sha256_match, warnings, uploaded, uploaded_bytes, filename, and file_size_bytes.
for (let i = 0; i < file_input.files.length; i++) {
let tmp_file = file_input.files[i];
if (!event) {
return;
}
task_id = $ae_sess.files.processed_file_list[i].hash_sha256;
$ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_sess.files.submit_status = 'saving';
submit_status = 'saving';
upload_complete = false;
hosted_file_id_li = [];
hosted_file_obj_li = [];
hosted_file_obj_kv = {};
let hosted_file_results;
const target = event.currentTarget as HTMLFormElement;
const file_input = target ? (target[input_element_id] as HTMLInputElement) : null;
if (
target &&
file_input &&
file_input.files &&
file_input.files.length > 0
) {
task_id = link_to_id; // Ideally this should be the file hash, but we may be uploading multiple files at once. This should be done with a loop instead?
// Loop through each file and upload them individually in event.target[input_element_id].files
// The task_id should be the file hash.
// processed_file_list[i] has the file hash_sha256, hash_sha256_match, warnings, uploaded, uploaded_bytes, filename, and file_size_bytes.
for (let i = 0; i < file_input.files.length; i++) {
let tmp_file = file_input.files[i];
task_id = $ae_sess.files.processed_file_list[i].hash_sha256;
// hosted_file_results = await handle_input_upload_files([tmp_file], task_id);
hosted_file_results = await handle_input_upload_files({
input_upload_files: [tmp_file],
task_id: task_id
});
if (hosted_file_results) {
console.log(`hosted_file_results:`, hosted_file_results);
} else {
console.log(`hosted_file_results:`, hosted_file_results);
}
}
// hosted_file_results = await handle_input_upload_files(event.target[input_element_id].files, task_id);
$ae_sess.files.processed_file_list = [];
$ae_sess = $ae_sess; // Is this needed? 2025-03-17
target.reset();
// await tick();
if (log_lvl) {
console.log(`hosted_file_id_li: ${hosted_file_id_li}`, hosted_file_id_li);
} else if (log_lvl > 1) {
console.log('hosted_file_results:', hosted_file_results);
}
}
$ae_sess.files.disable_submit__hosted_file_obj = false;
$ae_sess.files.submit_status = 'saved';
submit_status = 'saved';
upload_complete = true;
}
async function handle_input_upload_files({
input_upload_files,
task_id
}: {
input_upload_files: any[];
task_id: string;
}) {
console.log('*** handle_input_upload_files() ***');
const form_data = new FormData();
form_data.append('account_id', $ae_loc.account_id);
form_data.append('link_to_type', link_to_type);
form_data.append('link_to_id', link_to_id);
for (let i = 0; i < input_upload_files.length; i++) {
form_data.append(`file_list`, input_upload_files[i]);
}
// hash_sha256, uploaded, uploaded_bytes
// $ae_sess.files.processed_file_list[i] = {
// ...$ae_sess.files.processed_file_list[i],
// uploaded: $ae_sess.api_upload_kv[link_to_id].percent_completed,
// uploaded_bytes: $ae_sess.api_upload_kv[link_to_id].uploaded_bytes,
// };
let params = null;
let endpoint = '/hosted_file/upload_files';
console.log(form_data);
params = null;
// Uncomment and the post_promise is not seen by the "await" below
// post_promise = await api.post_object({api_cfg: $cfg.api, endpoint: endpoint, params: params, data:form_data});
// Uncomment so that the post_promise is not seen by the "await" below
ae_promises.upload__hosted_file_obj = api
.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
// params: params,
form_data: form_data,
task_id: task_id,
log_lvl: log_lvl
// retry_count: 1,
})
.then(async function (result) {
// WARNING!!!! ONLY ONE FILE IS EXPECTED TO BE UPLOADED AT A TIME!!!
// NOTE: The /hosted_file/upload_files endpoint will always return a list of successful files uploaded. In this case we are only uploading one file and expecting a list of one item.
let x = 0;
console.log(result[x]);
let hosted_file_obj = result[x];
let hosted_file_id = hosted_file_obj.hosted_file_id;
hosted_file_id_li.push(hosted_file_id);
hosted_file_obj_li.push(hosted_file_obj);
let hosted_file_data: key_val = {};
hosted_file_data['id'] = hosted_file_id; // Same as the hosted_file_id
hosted_file_data['hosted_file_id'] = hosted_file_id;
hosted_file_data['for_type'] = link_to_type;
hosted_file_data['for_id'] = link_to_id;
hosted_file_data['hash_sha256'] = hosted_file_obj.hash_sha256;
hosted_file_data['filename'] = hosted_file_obj.filename;
hosted_file_data['extension'] = hosted_file_obj.extension;
hosted_file_data['content_type'] = hosted_file_obj.content_type;
hosted_file_data['size'] = hosted_file_obj.size;
hosted_file_data['enable'] = true;
hosted_file_data['created_on'] = hosted_file_obj.created_on;
hosted_file_data['updated_on'] = hosted_file_obj.updated_on;
console.log(hosted_file_data);
hosted_file_obj_kv[hosted_file_id] = hosted_file_data;
if (log_lvl) {
console.log(`hosted_file_data:`, hosted_file_data);
}
return hosted_file_data;
// $ae_sess.files.new_upload_list[i].uploaded_bytes = 10; // fake 10 bytes at least...
// let event_file_id = await events_func.create_hosted_file_obj_from_hosted_file_async({
// api_cfg: $ae_api,
// hosted_file_id: hosted_file_id,
// data: event_file_data,
// log_lvl: log_lvl
// })
// .then(function (create_result) {
// console.log(create_result); // NOTE: This should be the event_file_id string
// // let event_file_id = create_result;
// return create_result;
// });
// return event_file_id;
})
// .then(function (hosted_file_data) {
// return hosted_file_data;
// })
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(function () {
$slct_trigger = 'load__hosted_file_obj_li';
// hosted_file_results = await handle_input_upload_files([tmp_file], task_id);
hosted_file_results = await handle_input_upload_files({
input_upload_files: [tmp_file],
task_id: task_id
});
if (log_lvl) {
console.log(`Waiting for upload__hosted_file_obj promise...`);
if (hosted_file_results) {
console.log(`hosted_file_results:`, hosted_file_results);
} else {
console.log(`hosted_file_results:`, hosted_file_results);
}
}
let hosted_file_result = ae_promises.upload__hosted_file_obj;
// hosted_file_results = await handle_input_upload_files(event.target[input_element_id].files, task_id);
$ae_sess.files.processed_file_list = [];
$ae_sess = $ae_sess; // Is this needed? 2025-03-17
target.reset();
// await tick();
return hosted_file_result;
if (log_lvl) {
console.log(
`hosted_file_id_li: ${hosted_file_id_li}`,
hosted_file_id_li
);
} else if (log_lvl > 1) {
console.log('hosted_file_results:', hosted_file_results);
}
}
$ae_sess.files.disable_submit__hosted_file_obj = false;
$ae_sess.files.submit_status = 'saved';
submit_status = 'saved';
upload_complete = true;
}
async function handle_input_upload_files({
input_upload_files,
task_id
}: {
input_upload_files: any[];
task_id: string;
}) {
console.log('*** handle_input_upload_files() ***');
const form_data = new FormData();
form_data.append('account_id', $ae_loc.account_id);
form_data.append('link_to_type', link_to_type);
form_data.append('link_to_id', link_to_id);
for (let i = 0; i < input_upload_files.length; i++) {
form_data.append(`file_list`, input_upload_files[i]);
}
// hash_sha256, uploaded, uploaded_bytes
// $ae_sess.files.processed_file_list[i] = {
// ...$ae_sess.files.processed_file_list[i],
// uploaded: $ae_sess.api_upload_kv[link_to_id].percent_completed,
// uploaded_bytes: $ae_sess.api_upload_kv[link_to_id].uploaded_bytes,
// };
let params = null;
let endpoint = '/v3/action/hosted_file/upload';
console.log(form_data);
params = null;
// Uncomment and the post_promise is not seen by the "await" below
// post_promise = await api.post_object({api_cfg: $cfg.api, endpoint: endpoint, params: params, data:form_data});
// Uncomment so that the post_promise is not seen by the "await" below
ae_promises.upload__hosted_file_obj = api
.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
// params: params,
form_data: form_data,
task_id: task_id,
log_lvl: log_lvl
// retry_count: 1,
})
.then(async function (result) {
// WARNING!!!! ONLY ONE FILE IS EXPECTED TO BE UPLOADED AT A TIME!!!
// NOTE: The upload endpoint always returns a list of successfully uploaded files. In this case we are only uploading one file and expecting a list of one item.
let x = 0;
console.log(result[x]);
let hosted_file_obj = result[x];
let hosted_file_id = hosted_file_obj.hosted_file_id;
hosted_file_id_li.push(hosted_file_id);
hosted_file_obj_li.push(hosted_file_obj);
let hosted_file_data: key_val = {};
hosted_file_data['id'] = hosted_file_id; // Same as the hosted_file_id
hosted_file_data['hosted_file_id'] = hosted_file_id;
hosted_file_data['for_type'] = link_to_type;
hosted_file_data['for_id'] = link_to_id;
hosted_file_data['hash_sha256'] = hosted_file_obj.hash_sha256;
hosted_file_data['filename'] = hosted_file_obj.filename;
hosted_file_data['extension'] = hosted_file_obj.extension;
hosted_file_data['content_type'] = hosted_file_obj.content_type;
hosted_file_data['size'] = hosted_file_obj.size;
hosted_file_data['enable'] = true;
hosted_file_data['created_on'] = hosted_file_obj.created_on;
hosted_file_data['updated_on'] = hosted_file_obj.updated_on;
console.log(hosted_file_data);
hosted_file_obj_kv[hosted_file_id] = hosted_file_data;
if (log_lvl) {
console.log(`hosted_file_data:`, hosted_file_data);
}
return hosted_file_data;
// $ae_sess.files.new_upload_list[i].uploaded_bytes = 10; // fake 10 bytes at least...
// let event_file_id = await events_func.create_hosted_file_obj_from_hosted_file_async({
// api_cfg: $ae_api,
// hosted_file_id: hosted_file_id,
// data: event_file_data,
// log_lvl: log_lvl
// })
// .then(function (create_result) {
// console.log(create_result); // NOTE: This should be the event_file_id string
// // let event_file_id = create_result;
// return create_result;
// });
// return event_file_id;
})
// .then(function (hosted_file_data) {
// return hosted_file_data;
// })
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(function () {
$slct_trigger = 'load__hosted_file_obj_li';
});
if (log_lvl) {
console.log(`Waiting for upload__hosted_file_obj promise...`);
}
let hosted_file_result = ae_promises.upload__hosted_file_obj;
return hosted_file_result;
}
</script>
<!-- class:hidden={!$ae_loc.trusted_access} -->
<form onsubmit={handle_submit_form_files} class="{class_li_default} {class_li}">
{#await ae_promises.upload__hosted_file_obj}
<div class="text-lg flex flex-row gap-1 items-center justify-center">
<Lucide.LoaderCircle class="animate-spin m-1" />
<div class="flex flex-row items-center justify-center gap-1 text-lg">
<Lucide.LoaderCircle class="m-1 animate-spin" />
<span class="">
Uploading
{#if $ae_sess.api_upload_kv[task_id]}
@@ -288,14 +293,14 @@
<label
for="ae_comp__hosted_files_upload__input"
class="svelte_input_file_label text-center"
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}
>
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}>
{#if label}{@render label()}{:else}
<div class="flex items-center justify-center gap-2 mb-2">
<div class="mb-2 flex items-center justify-center gap-2">
<Lucide.Upload class="text-primary-500" />
<strong class="preset-tonal-primary px-3 py-1 rounded-full">Select Files</strong>
<strong class="preset-tonal-primary rounded-full px-3 py-1"
>Select Files</strong>
</div>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="text-sm text-gray-600 italic dark:text-gray-400">
<strong>Supported formats</strong><br />
(PowerPoint, Keynote, PDF, Media, etc)
</span>
@@ -313,33 +318,30 @@
class="
svelte_input_file_element
file-dropzone-input
px-1
block w-full text-lg
preset-filled-surface-50-950
text-surface-900 dark:text-surface-100
border border-surface-300 dark:border-surface-700 rounded-lg
cursor-pointer
focus:outline-hidden focus:ring-2 focus:ring-primary-500
text-surface-900 dark:text-surface-100 border-surface-300
dark:border-surface-700
focus:ring-primary-500 block
w-full cursor-pointer rounded-lg border
px-1
text-lg focus:ring-2 focus:outline-hidden
{input_class_li.join(' ')}
"
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}
/>
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj} />
<Element_input_files_tbl
bind:input_file_list
bind:file_list_status={$ae_sess.files.status__file_list}
bind:processed_file_list={$ae_sess.files.processed_file_list}
{table_class_li}
/>
{table_class_li} />
<button
type="submit"
class="btn btn-lg btn-primary preset-tonal-primary border border-primary-500 hover:preset-tonal-success hover:border-success-500 w-54"
class="btn btn-lg btn-primary preset-tonal-primary border-primary-500 hover:preset-tonal-success hover:border-success-500 w-54 border"
disabled={$ae_sess.files.disable_submit__hosted_file_obj ||
$ae_sess.files.status__file_list != 'ready'}
>
$ae_sess.files.status__file_list != 'ready'}>
{#await ae_promises.upload__hosted_file_obj}
<Lucide.LoaderCircle class="animate-spin m-1" />
<Lucide.LoaderCircle class="m-1 animate-spin" />
<span class="">
{#if $ae_sess.api_upload_kv[task_id]}
{$ae_sess.api_upload_kv[task_id].percent_completed}%
@@ -350,9 +352,12 @@
{:then}
<Lucide.UploadCloud class="m-1" size={20} />
<span class="text-sm"> Upload </span>
<span class="grow font-bold ml-2">
<span class="ml-2 grow font-bold">
{#if $ae_sess.files.processed_file_list?.length > 0}
{$ae_sess.files.processed_file_list.length} { $ae_sess.files.processed_file_list.length === 1 ? 'file' : 'files' }
{$ae_sess.files.processed_file_list.length}
{$ae_sess.files.processed_file_list.length === 1
? 'file'
: 'files'}
{:else}
<span class="text-xs"> 0 </span>
{/if}

View File

@@ -1,235 +1,358 @@
<script lang="ts">
import { untrack } from 'svelte';
/**
* AE_Comp_Site_Config_Editor.svelte
* Specialized UI for managing site.cfg_json settings.
* Supports General, AI, Performance, and IDAA-specific configurations.
*/
import { Modal } from 'flowbite-svelte';
import { Brain, CodeXml, ExternalLink, Globe, Mail, Minus, Palette, Plus, Save, ShieldCheck, Timer } from '@lucide/svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/AE_Comp_Editor_CodeMirror.svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { untrack } from 'svelte';
/**
* AE_Comp_Site_Config_Editor.svelte
* Specialized UI for managing site.cfg_json settings.
* Supports General, AI, Performance, and IDAA-specific configurations.
*/
import { Modal } from 'flowbite-svelte';
import {
Brain,
CodeXml,
ExternalLink,
Globe,
Mail,
Minus,
Palette,
Plus,
Save,
ShieldCheck,
Timer
} from '@lucide/svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
import { ae_loc } from '$lib/stores/ae_stores';
interface Props {
cfg_json: any;
on_save?: () => void;
}
interface Props {
cfg_json: any;
on_save?: () => void;
}
let { cfg_json = $bindable({}), on_save }: Props = $props();
let { cfg_json = $bindable({}), on_save }: Props = $props();
// Ensure we have a valid object (handle strings/nulls)
$effect(() => {
if (typeof cfg_json === 'string') {
try {
cfg_json = JSON.parse(cfg_json);
} catch (e) {
cfg_json = {};
}
// Ensure we have a valid object (handle strings/nulls)
$effect(() => {
if (typeof cfg_json === 'string') {
try {
cfg_json = JSON.parse(cfg_json);
} catch (e) {
cfg_json = {};
}
if (!cfg_json) cfg_json = {};
});
// Internal State
let active_tab: 'visuals' | 'email' | 'ai' | 'refresh' | 'idaa' | 'raw' = $state('visuals');
let raw_json_str = $state('');
// Ensure we have a valid object
}
if (!cfg_json) cfg_json = {};
});
function add_to_list(key: string) {
if (!cfg_json[key]) cfg_json[key] = [];
const val = prompt('Enter Novi UUID:');
if (val) cfg_json[key].push(val);
// Internal State
let active_tab: 'visuals' | 'email' | 'ai' | 'refresh' | 'idaa' | 'raw' =
$state('visuals');
let raw_json_str = $state('');
// Ensure we have a valid object
if (!cfg_json) cfg_json = {};
function add_to_list(key: string) {
if (!cfg_json[key]) cfg_json[key] = [];
const val = prompt('Enter Novi UUID:');
if (val) cfg_json[key].push(val);
}
function remove_from_list(key: string, index: number) {
cfg_json[key].splice(index, 1);
}
// Sync Raw JSON string when entering the tab
$effect(() => {
if (active_tab === 'raw') {
untrack(() => {
raw_json_str = JSON.stringify(cfg_json, null, 2);
});
}
});
function remove_from_list(key: string, index: number) {
cfg_json[key].splice(index, 1);
// Update cfg_json when raw string changes
$effect(() => {
if (active_tab === 'raw' && raw_json_str) {
try {
const parsed = JSON.parse(raw_json_str);
cfg_json = parsed;
} catch (e) {
// Ignore invalid JSON while typing
}
}
// Sync Raw JSON string when entering the tab
$effect(() => {
if (active_tab === 'raw') {
untrack(() => {
raw_json_str = JSON.stringify(cfg_json, null, 2);
});
}
});
// Update cfg_json when raw string changes
$effect(() => {
if (active_tab === 'raw' && raw_json_str) {
try {
const parsed = JSON.parse(raw_json_str);
cfg_json = parsed;
} catch (e) {
// Ignore invalid JSON while typing
}
}
});
});
</script>
<div class="ae-site-config-editor flex flex-col h-full space-y-4">
<div class="ae-site-config-editor flex h-full flex-col space-y-4">
<!-- Tab Navigation -->
<div class="flex flex-wrap gap-1 p-1 bg-surface-500/10 rounded-lg max-w-fit">
<button class="btn btn-sm transition-all {active_tab === 'visuals' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'visuals'}>
<div
class="bg-surface-500/10 flex max-w-fit flex-wrap gap-1 rounded-lg p-1">
<button
class="btn btn-sm transition-all {active_tab === 'visuals'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'visuals')}>
<Palette size="1.1em" class="mr-1" /> Visuals
</button>
<button class="btn btn-sm transition-all {active_tab === 'email' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'email'}>
<button
class="btn btn-sm transition-all {active_tab === 'email'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'email')}>
<Mail size="1.1em" class="mr-1" /> Email
</button>
<button class="btn btn-sm transition-all {active_tab === 'ai' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'ai'}>
<button
class="btn btn-sm transition-all {active_tab === 'ai'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'ai')}>
<Brain size="1.1em" class="mr-1" /> AI/LLM
</button>
<button class="btn btn-sm transition-all {active_tab === 'refresh' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'refresh'}>
<button
class="btn btn-sm transition-all {active_tab === 'refresh'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'refresh')}>
<Timer size="1.1em" class="mr-1" /> Refresh
</button>
<button class="btn btn-sm transition-all {active_tab === 'idaa' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'idaa'}>
<button
class="btn btn-sm transition-all {active_tab === 'idaa'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'idaa')}>
<ShieldCheck size="1.1em" class="mr-1" /> IDAA
</button>
<button class="btn btn-sm transition-all {active_tab === 'raw' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'raw'}>
<button
class="btn btn-sm transition-all {active_tab === 'raw'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'raw')}>
<CodeXml size="1.1em" class="mr-1" /> Raw JSON
</button>
</div>
<!-- Scrollable Content Area -->
<div class="grow overflow-y-auto p-1 pr-2 space-y-6 max-h-[60vh]">
<div class="max-h-[60vh] grow space-y-6 overflow-y-auto p-1 pr-2">
{#if active_tab === 'visuals'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Theme Name</span>
<input type="text" bind:value={cfg_json.theme_name} class="input variant-form-material" placeholder="e.g. AE_OSIT_default" />
<span class="text-xs font-bold uppercase opacity-50"
>Theme Name</span>
<input
type="text"
bind:value={cfg_json.theme_name}
class="input variant-form-material"
placeholder="e.g. AE_OSIT_default" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Theme Mode</span>
<select bind:value={cfg_json.theme_mode} class="select variant-form-material">
<span class="text-xs font-bold uppercase opacity-50"
>Theme Mode</span>
<select
bind:value={cfg_json.theme_mode}
class="select variant-form-material">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="auto">Auto (System)</option>
</select>
</label>
<label class="label md:col-span-2">
<span class="text-xs font-bold uppercase opacity-50">Header Image Path (URL)</span>
<span class="text-xs font-bold uppercase opacity-50"
>Header Image Path (URL)</span>
<div class="flex gap-2">
<input type="text" bind:value={cfg_json.header_image_path} class="input variant-form-material grow" placeholder="https://..." />
<input
type="text"
bind:value={cfg_json.header_image_path}
class="input variant-form-material grow"
placeholder="https://..." />
{#if cfg_json.header_image_path}
<a href={cfg_json.header_image_path} target="_blank" rel="noopener noreferrer" class="btn-icon variant-soft-surface"><ExternalLink size="1.2em" /></a>
<a
href={cfg_json.header_image_path}
target="_blank"
rel="noopener noreferrer"
class="btn-icon variant-soft-surface"
><ExternalLink size="1.2em" /></a>
{/if}
</div>
</label>
</div>
{:else if active_tab === 'email'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<section class="space-y-4 border-r border-surface-500/10 pr-4">
<h4 class="text-sm font-black text-primary-500">Admin Contact</h4>
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<section class="border-surface-500/10 space-y-4 border-r pr-4">
<h4 class="text-primary-500 text-sm font-black">
Admin Contact
</h4>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Admin Name</span>
<input type="text" bind:value={cfg_json.admin_name} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Admin Name</span>
<input
type="text"
bind:value={cfg_json.admin_name}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Admin Email</span>
<input type="email" bind:value={cfg_json.admin_email} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Admin Email</span>
<input
type="email"
bind:value={cfg_json.admin_email}
class="input variant-form-material" />
</label>
</section>
<section class="space-y-4">
<h4 class="text-sm font-black text-secondary-500">System (No-Reply)</h4>
<h4 class="text-secondary-500 text-sm font-black">
System (No-Reply)
</h4>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">No-Reply Name</span>
<input type="text" bind:value={cfg_json.noreply_name} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>No-Reply Name</span>
<input
type="text"
bind:value={cfg_json.noreply_name}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">No-Reply Email</span>
<input type="email" bind:value={cfg_json.noreply_email} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>No-Reply Email</span>
<input
type="email"
bind:value={cfg_json.noreply_email}
class="input variant-form-material" />
</label>
</section>
</div>
{:else if active_tab === 'ai'}
<div class="space-y-4 animate-in fade-in duration-200">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="animate-in fade-in space-y-4 duration-200">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">LLM API Base URL</span>
<input type="text" bind:value={cfg_json.llm__api_base_url} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>LLM API Base URL</span>
<input
type="text"
bind:value={cfg_json.llm__api_base_url}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">LLM Model</span>
<input type="text" bind:value={cfg_json.llm__api_model} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>LLM Model</span>
<input
type="text"
bind:value={cfg_json.llm__api_model}
class="input variant-form-material" />
</label>
</div>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">API Token</span>
<input type="password" bind:value={cfg_json.llm__api_token} class="input variant-form-material font-mono" />
<span class="text-xs font-bold uppercase opacity-50"
>API Token</span>
<input
type="password"
bind:value={cfg_json.llm__api_token}
class="input variant-form-material font-mono" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">System Prompt</span>
<textarea bind:value={cfg_json.llm__system_prompt} class="textarea variant-form-material h-24 text-sm"></textarea>
<span class="text-xs font-bold uppercase opacity-50"
>System Prompt</span>
<textarea
bind:value={cfg_json.llm__system_prompt}
class="textarea variant-form-material h-24 text-sm"
></textarea>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" bind:checked={cfg_json.llm__api_dangerous_browser} class="checkbox" />
<span class="text-xs font-bold uppercase opacity-50">Allow Browser Fetch (Dangerously)</span>
<input
type="checkbox"
bind:checked={cfg_json.llm__api_dangerous_browser}
class="checkbox" />
<span class="text-xs font-bold uppercase opacity-50"
>Allow Browser Fetch (Dangerously)</span>
</label>
</div>
{:else if active_tab === 'refresh'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Default (Minutes)</span>
<input type="number" bind:value={cfg_json.default_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Default (Minutes)</span>
<input
type="number"
bind:value={cfg_json.default_refresh_minutes}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Authenticated (Minutes)</span>
<input type="number" bind:value={cfg_json.authenticated_refresh_time} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Authenticated (Minutes)</span>
<input
type="number"
bind:value={cfg_json.authenticated_refresh_time}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Trusted (Minutes)</span>
<input type="number" bind:value={cfg_json.trusted_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Trusted (Minutes)</span>
<input
type="number"
bind:value={cfg_json.trusted_refresh_minutes}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Manager (Minutes)</span>
<input type="number" bind:value={cfg_json.manager_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Manager (Minutes)</span>
<input
type="number"
bind:value={cfg_json.manager_refresh_minutes}
class="input variant-form-material" />
</label>
</div>
{:else if active_tab === 'idaa'}
<div class="space-y-6 animate-in fade-in duration-200">
<div class="animate-in fade-in space-y-6 duration-200">
<!-- Novi API -->
<section class="space-y-4 p-4 bg-surface-500/5 rounded-xl border border-surface-500/10">
<h4 class="text-sm font-black flex items-center gap-2">
<section
class="bg-surface-500/5 border-surface-500/10 space-y-4 rounded-xl border p-4">
<h4 class="flex items-center gap-2 text-sm font-black">
<Globe size="1.1em" class="text-primary-500" /> Novi API Connection
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Root URL</span>
<input type="text" bind:value={cfg_json.novi_api_root_url} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Root URL</span>
<input
type="text"
bind:value={cfg_json.novi_api_root_url}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">API Key</span>
<input type="password" bind:value={cfg_json.novi_idaa_api_key} class="input variant-form-material font-mono" />
<span class="text-xs font-bold uppercase opacity-50"
>API Key</span>
<input
type="password"
bind:value={cfg_json.novi_idaa_api_key}
class="input variant-form-material font-mono" />
</label>
</div>
</section>
<!-- UUID Lists -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-4">
{#each [
{ key: 'novi_admin_li', label: 'Novi Admins', color: 'text-error-500' },
{ key: 'novi_trusted_li', label: 'Novi Trusted', color: 'text-warning-500' },
{ key: 'novi_jitsi_mod_li', label: 'Jitsi Moderators', color: 'text-primary-500' },
{ key: 'novi_idaa_group_guid_li', label: 'Member Group GUIDs', color: 'text-secondary-500' }
] as list (list.key)}
<div class="space-y-2 p-3 bg-surface-500/5 rounded-lg">
<header class="flex justify-between items-center">
<span class="text-[10px] font-black uppercase tracking-wider {list.color}">{list.label}</span>
<button class="btn btn-icon btn-icon-sm variant-soft-primary" onclick={() => add_to_list(list.key)}>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
{#each [{ key: 'novi_admin_li', label: 'Novi Admins', color: 'text-error-500' }, { key: 'novi_trusted_li', label: 'Novi Trusted', color: 'text-warning-500' }, { key: 'novi_jitsi_mod_li', label: 'Jitsi Moderators', color: 'text-primary-500' }, { key: 'novi_idaa_group_guid_li', label: 'Member Group GUIDs', color: 'text-secondary-500' }] as list (list.key)}
<div class="bg-surface-500/5 space-y-2 rounded-lg p-3">
<header class="flex items-center justify-between">
<span
class="text-[10px] font-black tracking-wider uppercase {list.color}"
>{list.label}</span>
<button
class="btn btn-icon btn-icon-sm variant-soft-primary"
onclick={() => add_to_list(list.key)}>
<Plus size="12" />
</button>
</header>
<div class="space-y-1">
{#each cfg_json[list.key] ?? [] as uuid, i (uuid)}
<div class="flex gap-1 items-center bg-surface-500/10 p-1 rounded font-mono text-[10px]">
<span class="grow truncate">{uuid}</span>
<button class="text-error-500 hover:scale-110 transition-transform" onclick={() => remove_from_list(list.key, i)}>
<div
class="bg-surface-500/10 flex items-center gap-1 rounded p-1 font-mono text-[10px]">
<span class="grow truncate"
>{uuid}</span>
<button
class="text-error-500 transition-transform hover:scale-110"
onclick={() =>
remove_from_list(list.key, i)}>
<Minus size="12" />
</button>
</div>
@@ -240,62 +363,96 @@
</section>
<!-- Notifications -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-surface-500/5 rounded-xl">
<section
class="bg-surface-500/5 grid grid-cols-1 gap-4 rounded-xl p-4 md:grid-cols-2">
<div class="space-y-2">
<h4 class="text-[10px] font-black uppercase opacity-50">Bulletin Board</h4>
<h4 class="text-[10px] font-black uppercase opacity-50">
Bulletin Board
</h4>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_staff_new_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_staff_new_email}
class="checkbox checkbox-sm" />
<span>Notify Staff (New)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_staff_update_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.bb_send_staff_update_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (Update)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_poster_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_poster_email}
class="checkbox checkbox-sm" />
<span>Notify Poster</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_commenter_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_commenter_email}
class="checkbox checkbox-sm" />
<span>Notify Commenters</span>
</label>
</div>
<div class="space-y-2">
<h4 class="text-[10px] font-black uppercase opacity-50">Recovery Meetings</h4>
<h4 class="text-[10px] font-black uppercase opacity-50">
Recovery Meetings
</h4>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.recovery_mtg_send_staff_new_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.recovery_mtg_send_staff_new_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (New)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.recovery_mtg_send_staff_update_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.recovery_mtg_send_staff_update_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (Update)</span>
</label>
</div>
</section>
</div>
{:else if active_tab === 'raw'}
<div class="h-[50vh] animate-in fade-in duration-200">
<div class="animate-in fade-in h-[50vh] duration-200">
<AE_Comp_Editor_CodeMirror
content={raw_json_str}
bind:new_content={raw_json_str}
language="json"
theme_mode={$ae_loc.theme_mode}
class_li="h-full border border-surface-500/20 rounded-lg shadow-inner"
/>
class_li="h-full border border-surface-500/20 rounded-lg shadow-inner" />
</div>
{/if}
</div>
<!-- Action Bar -->
<div class="flex justify-between items-center pt-4 border-t border-surface-500/10">
<div
class="border-surface-500/10 flex items-center justify-between border-t pt-4">
<div class="flex items-center gap-2">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" bind:checked={cfg_json.test} class="checkbox" />
<span class="text-xs font-bold uppercase text-warning-500">Test Mode</span>
<label class="flex cursor-pointer items-center space-x-2">
<input
type="checkbox"
bind:checked={cfg_json.test}
class="checkbox" />
<span class="text-warning-500 text-xs font-bold uppercase"
>Test Mode</span>
</label>
</div>
<button class="btn btn-sm variant-filled-primary font-bold shadow-lg" onclick={on_save}>
<button
class="btn btn-sm variant-filled-primary font-bold shadow-lg"
onclick={on_save}>
<Save size="1.1em" class="mr-2" /> Save Config
</button>
</div>

View File

@@ -24,11 +24,13 @@ export async function load_ae_obj_id__account({
log_lvl?: number;
}): Promise<ae_Account | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__account() *** account_id=${account_id}`);
console.log(
`*** load_ae_obj_id__account() *** account_id=${account_id}`
);
}
ae_promises.load__account_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg: api_cfg,
obj_type: 'account',
obj_id: account_id,
@@ -39,10 +41,11 @@ export async function load_ae_obj_id__account({
.then(async function (account_obj_get_result) {
if (account_obj_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: [account_obj_get_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: [account_obj_get_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -100,7 +103,7 @@ export async function load_ae_obj_li__account({
}
ae_promises.load__account_obj_li = await api
.get_ae_obj_li_v3({
.get_ae_obj_li({
api_cfg,
obj_type: 'account',
enabled,
@@ -114,10 +117,11 @@ export async function load_ae_obj_li__account({
.then(async function (account_obj_li_get_result) {
if (account_obj_li_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: account_obj_li_get_result,
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: account_obj_li_get_result,
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -154,7 +158,7 @@ export async function create_ae_obj__account({
}
ae_promises.create__account = await api
.create_ae_obj_v3({
.create_ae_obj({
api_cfg: api_cfg,
obj_type: 'account',
fields: data_kv,
@@ -164,10 +168,11 @@ export async function create_ae_obj__account({
.then(async function (account_obj_create_result) {
if (account_obj_create_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: [account_obj_create_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: [account_obj_create_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -206,10 +211,13 @@ export async function update_ae_obj__account({
log_lvl?: number;
}): Promise<ae_Account | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__account() *** account_id=${account_id}`, data_kv);
console.log(
`*** update_ae_obj__account() *** account_id=${account_id}`,
data_kv
);
}
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'account',
obj_id: account_id,
@@ -256,11 +264,13 @@ export async function delete_ae_obj_id__account({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__account() *** account_id=${account_id}`);
console.log(
`*** delete_ae_obj_id__account() *** account_id=${account_id}`
);
}
ae_promises.delete__account_obj = await api
.delete_ae_obj_v3({
.delete_ae_obj({
api_cfg,
obj_type: 'account',
obj_id: account_id,
@@ -337,11 +347,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -19,10 +19,12 @@ export async function load_ae_obj_id__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__activity_log() *** activity_log_id=${activity_log_id}`);
console.log(
`*** load_ae_obj_id__activity_log() *** activity_log_id=${activity_log_id}`
);
}
ae_promises.load__activity_log_obj = await api.get_ae_obj_v3({
ae_promises.load__activity_log_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'activity_log',
obj_id: activity_log_id,
@@ -61,10 +63,12 @@ export async function load_ae_obj_li__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__activity_log() *** for_obj_id=${for_obj_id}`);
console.log(
`*** load_ae_obj_li__activity_log() *** for_obj_id=${for_obj_id}`
);
}
ae_promises.load__activity_log_obj_li = await api.get_ae_obj_li_v3({
ae_promises.load__activity_log_obj_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'activity_log',
for_obj_type,
@@ -96,15 +100,19 @@ export async function create_ae_obj__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__activity_log() *** account_id=${account_id}`);
console.log(
`*** create_ae_obj__activity_log() *** account_id=${account_id}`
);
}
if (!account_id) {
console.log(`ERROR: Core - Activity Log - account_id required to create`);
console.log(
`ERROR: Core - Activity Log - account_id required to create`
);
return null;
}
ae_promises.create__activity_log = await api.create_ae_obj_v3({
ae_promises.create__activity_log = await api.create_ae_obj({
api_cfg,
obj_type: 'activity_log',
fields: {
@@ -133,10 +141,12 @@ export async function update_ae_obj__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__activity_log() *** activity_log_id=${activity_log_id}`);
console.log(
`*** update_ae_obj__activity_log() *** activity_log_id=${activity_log_id}`
);
}
ae_promises.update__activity_log_obj = await api.update_ae_obj_v3({
ae_promises.update__activity_log_obj = await api.update_ae_obj({
api_cfg,
obj_type: 'activity_log',
obj_id: activity_log_id,
@@ -151,7 +161,6 @@ export async function update_ae_obj__activity_log({
// Updated 2026-01-07
export async function qry__activity_log({
api_cfg,
account_id,
@@ -173,9 +182,7 @@ export async function qry__activity_log({
order_by_li = { created_on: 'DESC' },
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
@@ -197,49 +204,36 @@ export async function qry__activity_log({
order_by_li?: Record<string, 'ASC' | 'DESC'>;
log_lvl?: number;
}): Promise<ae_ActivityLog[]> {
const search_query: any = {};
const filters: any[] = [];
if (account_id) {
filters.push({ field: 'account_id_random', op: 'eq', value: account_id });
filters.push({
field: 'account_id_random',
op: 'eq',
value: account_id
});
}
if (qry_person_id) {
filters.push({ field: 'person_id_random', op: 'eq', value: qry_person_id });
filters.push({
field: 'person_id_random',
op: 'eq',
value: qry_person_id
});
}
if (filters.length > 0) {
search_query.and = filters;
}
if (qry_str) {
search_query.q = qry_str;
}
ae_promises.load__activity_log_obj_li = await api.search_ae_obj_v3({
ae_promises.load__activity_log_obj_li = await api.search_ae_obj({
api_cfg,
obj_type: 'activity_log',
@@ -259,13 +253,9 @@ export async function qry__activity_log({
order_by_li,
log_lvl
});
return ae_promises.load__activity_log_obj_li;
}
// Updated 2026-02-16
@@ -338,14 +328,21 @@ async function _process_generic_props<T extends Record<string, any>>({
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -367,5 +364,3 @@ export async function process_ae_obj__activity_log_props({
log_lvl
});
}

View File

@@ -23,29 +23,34 @@ export async function load_ae_obj_id__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address | null> {
ae_promises.load__address_obj = await api.get_ae_obj_v3({
api_cfg,
obj_type: 'address',
obj_id: address_id,
view,
params,
log_lvl
}).then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__address_obj = await api
.get_ae_obj({
api_cfg,
obj_type: 'address',
obj_id: address_id,
view,
params,
log_lvl
})
.then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return null;
});
return null;
});
return ae_promises.load__address_obj;
}
@@ -77,34 +82,39 @@ export async function load_ae_obj_li__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address[]> {
ae_promises.load__address_obj_li = await api.get_ae_obj_li_v3({
api_cfg,
obj_type: 'address',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
}).then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: result, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__address_obj_li = await api
.get_ae_obj_li({
api_cfg,
obj_type: 'address',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
})
.then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__address_props({
obj_li: result,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return [];
});
return [];
});
return ae_promises.load__address_obj_li;
}
@@ -124,7 +134,7 @@ export async function create_ae_obj__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address | null> {
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'address',
fields: {
@@ -136,7 +146,10 @@ export async function create_ae_obj__address({
});
if (result && try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
@@ -164,7 +177,7 @@ export async function update_ae_obj__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'address',
obj_id: address_id,
@@ -174,7 +187,10 @@ export async function update_ae_obj__address({
});
if (result && try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
@@ -202,7 +218,7 @@ export async function delete_ae_obj_id__address({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'address',
obj_id: address_id,
@@ -269,7 +285,9 @@ async function _process_generic_props<T extends Record<string, any>>({
}
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -290,4 +308,4 @@ export async function process_ae_obj__address_props({
obj_type: 'address',
log_lvl
});
}
}

View File

@@ -23,29 +23,34 @@ export async function load_ae_obj_id__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact | null> {
ae_promises.load__contact_obj = await api.get_ae_obj_v3({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
view,
params,
log_lvl
}).then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__contact_obj = await api
.get_ae_obj({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
view,
params,
log_lvl
})
.then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return null;
});
return null;
});
return ae_promises.load__contact_obj;
}
@@ -75,34 +80,39 @@ export async function load_ae_obj_li__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact[]> {
ae_promises.load__contact_obj_li = await api.get_ae_obj_li_v3({
api_cfg,
obj_type: 'contact',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
}).then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: result, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__contact_obj_li = await api
.get_ae_obj_li({
api_cfg,
obj_type: 'contact',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
})
.then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({
obj_li: result,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return [];
});
return [];
});
return ae_promises.load__contact_obj_li;
}
@@ -122,7 +132,7 @@ export async function create_ae_obj__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact | null> {
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'contact',
fields: {
@@ -134,7 +144,10 @@ export async function create_ae_obj__contact({
});
if (result && try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
@@ -162,7 +175,7 @@ export async function update_ae_obj__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
@@ -172,7 +185,10 @@ export async function update_ae_obj__contact({
});
if (result && try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
@@ -200,7 +216,7 @@ export async function delete_ae_obj_id__contact({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
@@ -267,7 +283,9 @@ async function _process_generic_props<T extends Record<string, any>>({
}
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -288,4 +306,4 @@ export async function process_ae_obj__contact_props({
obj_type: 'contact',
log_lvl
});
}
}

View File

@@ -14,7 +14,7 @@ export async function load_ae_obj_id__organization({
organization_id: string;
log_lvl?: number;
}): Promise<ae_Organization | null> {
return await api.get_ae_obj_v3({
return await api.get_ae_obj({
api_cfg,
obj_type: 'organization',
obj_id: organization_id,

View File

@@ -28,7 +28,7 @@ export async function load_ae_obj_id__person({
}
ae_promises.load__person_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg,
obj_type: 'person',
obj_id: person_id,
@@ -39,10 +39,12 @@ export async function load_ae_obj_id__person({
.then(async function (result) {
if (result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props({
obj_li: [result],
log_lvl
});
const processed_obj_li = await process_ae_obj__person_props(
{
obj_li: [result],
log_lvl
}
);
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
@@ -98,7 +100,9 @@ export async function load_ae_obj_li__person({
log_lvl?: number;
}): Promise<ae_Person[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__person() *** for_obj_id=${for_obj_id}`);
console.log(
`*** load_ae_obj_li__person() *** for_obj_id=${for_obj_id}`
);
}
let promise;
@@ -109,11 +113,19 @@ export async function load_ae_obj_li__person({
};
if (qry_user_id) {
search_query.and.push({ field: 'user_id_random', op: 'eq', value: qry_user_id });
search_query.and.push({
field: 'user_id_random',
op: 'eq',
value: qry_user_id
});
}
if (qry_email) {
search_query.and.push({ field: 'primary_email', op: 'eq', value: qry_email });
search_query.and.push({
field: 'primary_email',
op: 'eq',
value: qry_email
});
}
if (for_obj_id) {
@@ -136,7 +148,7 @@ export async function load_ae_obj_li__person({
search_query.and.push({ field: 'hide', op: 'eq', value: false });
}
promise = api.search_ae_obj_v3({
promise = api.search_ae_obj({
api_cfg,
obj_type: 'person',
search_query,
@@ -146,7 +158,7 @@ export async function load_ae_obj_li__person({
log_lvl
});
} else {
promise = api.get_ae_obj_li_v3({
promise = api.get_ae_obj_li({
api_cfg,
obj_type: 'person',
for_obj_type,
@@ -161,26 +173,30 @@ export async function load_ae_obj_li__person({
});
}
ae_promises.load__person_obj_li = await promise.then(async function (result_li) {
if (result_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props({
obj_li: result_li,
log_lvl
});
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
});
ae_promises.load__person_obj_li = await promise.then(
async function (result_li) {
if (result_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props(
{
obj_li: result_li,
log_lvl
}
);
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
});
}
return result_li;
} else {
return [];
}
return result_li;
} else {
return [];
}
});
);
return ae_promises.load__person_obj_li;
}
@@ -207,7 +223,7 @@ export async function create_ae_obj__person({
if (account_id) fields.account_id_random = account_id;
if (user_id) fields.user_id_random = user_id;
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'person',
fields,
@@ -248,7 +264,7 @@ export async function update_ae_obj__person({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Person | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'person',
obj_id: person_id,
@@ -290,7 +306,7 @@ export async function delete_ae_obj_id__person({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'person',
obj_id: person_id,
@@ -411,11 +427,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.full_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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -1,4 +1,6 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_loc } from '$lib/stores/ae_stores';
import { get } from 'svelte/store';
import { api } from '$lib/api/api';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
@@ -11,6 +13,88 @@ const ae_promises: key_val = {};
* --- SITE CRUD ---
*/
// Legacy version
// export async function lookup_site_domain({
// api_cfg,
// fqdn,
// view = 'default',
// log_lvl = 0
// }: {
// api_cfg: any;
// fqdn: string;
// view?: string;
// log_lvl?: number;
// }): Promise<ae_SiteDomain | null> {
// if (log_lvl) {
// console.log(`*** lookup_site_domain() *** fqdn=${fqdn}`);
// }
// try {
// // We use get_ae_obj_id_crud because we are looking up by a unique field (fqdn) rather than ID.
// // This is the older method that uses the /crud/site/domain/:id endpoint.
// const result = await api.get_ae_obj_id_crud({
// api_cfg,
// no_account_id: true,
// obj_type: 'site_domain',
// obj_id: fqdn,
// use_alt_table: true,
// use_alt_base: true,
// log_lvl
// });
// if (result) {
// // Standardize and save to cache
// const processed_obj_li = await process_ae_obj__site_domain_props({
// obj_li: [result],
// log_lvl
// });
// await db_save_ae_obj_li__ae_obj({
// db_instance: db_core,
// table_name: 'site_domain',
// obj_li: processed_obj_li,
// properties_to_save: properties_to_save__site_domain,
// log_lvl
// });
// return result;
// }
// } catch (error: any) {
// console.log('Site domain lookup failed (API Error).', error);
// }
// if (log_lvl) console.log('Attempting to load site domain from local cache...');
// const cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
// if (cached) {
// return cached as any;
// }
// // CRITICAL FALLBACK: If both API and Cache fail, return a "Ghost" site domain object
// // to prevent the 403 error from blocking the UI. Page components will handle empty data.
// console.error('AE_SITE_CRITICAL: Site domain lookup failed API and CACHE. Returning ghost object.');
// return {
// id: 'ghost',
// id_random: 'ghost',
// site_domain_id: 'ghost',
// site_domain_id_random: 'ghost',
// site_id: 'ghost',
// site_id_random: 'ghost',
// account_id: 'ghost',
// account_id_random: 'ghost',
// account_code: 'ghost',
// account_name: 'Ghost Account (Offline)',
// fqdn: fqdn,
// enable: '1',
// header_image_path: '',
// style_href: '',
// google_tracking_id: '',
// access_code_kv_json: {},
// cfg_json: {},
// access_key: '',
// site_domain_access_key: ''
// } as any;
// }
// Updated 2026-01-26 (Cache-First Optimization)
export async function lookup_site_domain({
api_cfg,
fqdn,
@@ -23,88 +107,7 @@ export async function lookup_site_domain({
log_lvl?: number;
}): Promise<ae_SiteDomain | null> {
if (log_lvl) {
console.log(`*** lookup_site_domain() *** fqdn=${fqdn}`);
}
try {
// We use get_ae_obj_id_crud because we are looking up by a unique field (fqdn) rather than ID.
// This is the older method that uses the /crud/site/domain/:id endpoint.
const result = await api.get_ae_obj_id_crud({
api_cfg,
no_account_id: true,
obj_type: 'site_domain',
obj_id: fqdn,
use_alt_table: true,
use_alt_base: true,
log_lvl
});
if (result) {
// Standardize and save to cache
const processed_obj_li = await process_ae_obj__site_domain_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'site_domain',
obj_li: processed_obj_li,
properties_to_save: properties_to_save__site_domain,
log_lvl
});
return result;
}
} catch (error: any) {
console.log('Site domain lookup failed (API Error).', error);
}
if (log_lvl) console.log('Attempting to load site domain from local cache...');
const cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
if (cached) {
return cached as any;
}
// CRITICAL FALLBACK: If both API and Cache fail, return a "Ghost" site domain object
// to prevent the 403 error from blocking the UI. Page components will handle empty data.
console.error('AE_SITE_CRITICAL: Site domain lookup failed API and CACHE. Returning ghost object.');
return {
id: 'ghost',
id_random: 'ghost',
site_domain_id: 'ghost',
site_domain_id_random: 'ghost',
site_id: 'ghost',
site_id_random: 'ghost',
account_id: 'ghost',
account_id_random: 'ghost',
account_code: 'ghost',
account_name: 'Ghost Account (Offline)',
fqdn: fqdn,
enable: '1',
header_image_path: '',
style_href: '',
google_tracking_id: '',
access_code_kv_json: {},
cfg_json: {},
access_key: '',
site_domain_access_key: ''
} as any;
}
// Updated 2026-01-26 (Cache-First Optimization)
export async function lookup_site_domain_v3({
api_cfg,
fqdn,
view = 'default',
log_lvl = 0
}: {
api_cfg: any;
fqdn: string;
view?: string;
log_lvl?: number;
}): Promise<ae_SiteDomain | null> {
if (log_lvl) {
console.log(`*** lookup_site_domain_v3() *** fqdn=${fqdn} (Cache-First)`);
console.log(`*** lookup_site_domain() *** fqdn=${fqdn} (Cache-First)`);
}
// 1. FAST PATH: Check local cache first
@@ -112,11 +115,19 @@ export async function lookup_site_domain_v3({
try {
cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
if (cached) {
if (log_lvl) console.log('BOOTSTRAP: Cache hit. Returning cached site domain immediately.');
if (log_lvl)
console.log(
'BOOTSTRAP: Cache hit. Returning cached site domain immediately.'
);
// Trigger background refresh to keep cache fresh, but don't await it
_refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl: 0 });
_refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl: 0
});
return cached as any;
}
} catch (err) {
@@ -124,26 +135,36 @@ export async function lookup_site_domain_v3({
}
// 2. SLOW PATH: Wait for API if cache is empty
return await _refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl });
return await _refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl
});
}
/**
* Internal helper to perform the actual API fetch and cache update
*/
async function _refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl }: any) {
async function _refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl
}: any) {
try {
const guest_api_cfg = { ...api_cfg };
guest_api_cfg.headers = { ...api_cfg.headers };
const auth_props = [
'x-account-id',
'Authorization',
'authorization',
'jwt',
'x-account-id',
'Authorization',
'authorization',
'jwt',
'JWT'
];
auth_props.forEach(prop => {
auth_props.forEach((prop) => {
delete guest_api_cfg.headers[prop];
delete guest_api_cfg.headers[prop.toLowerCase()];
delete guest_api_cfg.headers[prop.replaceAll('-', '_')];
@@ -155,7 +176,7 @@ async function _refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
};
const result_li = await api.search_ae_obj_v3({
const result_li = await api.search_ae_obj({
api_cfg: guest_api_cfg,
obj_type: 'site_domain',
search_query,
@@ -179,6 +200,25 @@ async function _refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl
properties_to_save: properties_to_save__site_domain,
log_lvl
});
// WHY: The fast-path returns stale Dexie cache, then this background refresh
// runs after the page renders. If cfg_json changed server-side (e.g. a Novi
// API key was added), the stale cfg is already in $ae_loc. We push the fresh
// cfg_json into the store here so any layout tracking it (e.g. IDAA Novi
// verification) gets notified and can retry with the correct config.
if (result.cfg_json) {
const current_cfg = get(ae_loc).site_cfg_json;
if (
JSON.stringify(current_cfg) !==
JSON.stringify(result.cfg_json)
) {
ae_loc.update((loc) => ({
...loc,
site_cfg_json: result.cfg_json
}));
}
}
return result;
}
} catch (error: any) {
@@ -207,7 +247,7 @@ export async function load_ae_obj_id__site({
}
ae_promises.load__site_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg,
obj_type: 'site',
obj_id: site_id,
@@ -278,7 +318,7 @@ export async function load_ae_obj_li__site({
}
ae_promises.load__site_obj_li = await api
.get_ae_obj_li_v3({
.get_ae_obj_li({
api_cfg,
obj_type: 'site',
for_obj_type,
@@ -331,7 +371,7 @@ export async function create_ae_obj__site({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Site | null> {
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'site',
fields: {
@@ -375,7 +415,7 @@ export async function update_ae_obj__site({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Site | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'site',
obj_id: site_id,
@@ -417,7 +457,7 @@ export async function delete_ae_obj_id__site({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'site',
obj_id: site_id,
@@ -464,7 +504,7 @@ export async function load_ae_obj_li__site_domain({
log_lvl?: number;
}): Promise<ae_SiteDomain[]> {
ae_promises.load__site_domain_li = await api
.get_nested_obj_li_v3({
.get_nested_obj_li({
api_cfg,
parent_type: 'site',
parent_id: site_id,
@@ -480,11 +520,12 @@ export async function load_ae_obj_li__site_domain({
.then(async function (domain_li) {
if (domain_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__site_domain_props({
obj_li: domain_li,
site_id,
log_lvl
});
const processed_obj_li =
await process_ae_obj__site_domain_props({
obj_li: domain_li,
site_id,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'site_domain',
@@ -518,7 +559,7 @@ export async function create_ae_obj__site_domain({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_SiteDomain | null> {
const result = await api.create_nested_obj_v3({
const result = await api.create_nested_obj({
api_cfg,
parent_type: 'site',
parent_id: site_id,
@@ -564,7 +605,7 @@ export async function update_ae_obj__site_domain({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_SiteDomain | null> {
const result = await api.update_nested_obj_v3({
const result = await api.update_nested_obj({
api_cfg,
parent_type: 'site',
parent_id: site_id,
@@ -611,7 +652,7 @@ export async function delete_ae_obj_id__site_domain({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_nested_ae_obj_v3({
const result = await api.delete_nested_ae_obj({
api_cfg,
parent_type: 'site',
parent_id: site_id,
@@ -728,11 +769,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.name ?? processed_obj.fqdn ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -28,7 +28,7 @@ export async function load_ae_obj_id__user({
}
ae_promises.load__user_obj = await api
.get_ae_obj_v3({
.get_ae_obj({
api_cfg,
obj_type: 'user',
obj_id: user_id,
@@ -93,7 +93,9 @@ export async function load_ae_obj_li__user({
log_lvl?: number;
}): Promise<ae_User[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__user() *** for_obj_id=${for_obj_id} include_global=${include_global} qry_str=${qry_str}`);
console.log(
`*** load_ae_obj_li__user() *** for_obj_id=${for_obj_id} include_global=${include_global} qry_str=${qry_str}`
);
}
// SCENARIO A: Text Search
@@ -107,12 +109,20 @@ export async function load_ae_obj_li__user({
]
});
} else if (for_obj_id) {
search_query.and.push({ field: `account_id_random`, op: 'eq', value: for_obj_id });
search_query.and.push({
field: `account_id_random`,
op: 'eq',
value: for_obj_id
});
} else if (include_global) {
search_query.and.push({ field: `account_id_random`, op: 'eq', value: null });
search_query.and.push({
field: `account_id_random`,
op: 'eq',
value: null
});
}
return await api.search_ae_obj_v3({
return await api.search_ae_obj({
api_cfg,
obj_type: 'user',
search_query,
@@ -130,13 +140,33 @@ export async function load_ae_obj_li__user({
if (for_obj_id && include_global) {
if (log_lvl) console.log('Strategy: Multi-call (Account + Global)');
const [acct_users, global_users] = await Promise.all([
load_ae_obj_li__user({ api_cfg, for_obj_id, include_global: false, enabled, hidden, view, limit, log_lvl }),
load_ae_obj_li__user({ api_cfg, for_obj_id: null, include_global: true, enabled, hidden, view, limit, log_lvl })
load_ae_obj_li__user({
api_cfg,
for_obj_id,
include_global: false,
enabled,
hidden,
view,
limit,
log_lvl
}),
load_ae_obj_li__user({
api_cfg,
for_obj_id: null,
include_global: true,
enabled,
hidden,
view,
limit,
log_lvl
})
]);
// Merge and unique-ify by ID
const merged = [...acct_users, ...global_users];
const unique = Array.from(new Map(merged.map(u => [u.user_id_random, u])).values());
const unique = Array.from(
new Map(merged.map((u) => [u.user_id_random, u])).values()
);
return unique;
}
@@ -147,7 +177,7 @@ export async function load_ae_obj_li__user({
const search_query = {
and: [{ field: 'account_id_random', op: 'eq', value: null }]
};
return await api.search_ae_obj_v3({
return await api.search_ae_obj({
api_cfg,
obj_type: 'user',
search_query,
@@ -162,8 +192,9 @@ export async function load_ae_obj_li__user({
}
// SCENARIO D: Account Only or Everything (confirmed working List API)
if (log_lvl) console.log(`Strategy: Standard List API (for_obj_id=${for_obj_id})`);
return await api.get_ae_obj_li_v3({
if (log_lvl)
console.log(`Strategy: Standard List API (for_obj_id=${for_obj_id})`);
return await api.get_ae_obj_li({
api_cfg,
obj_type: 'user',
for_obj_type: for_obj_id ? for_obj_type : undefined,
@@ -197,7 +228,7 @@ export async function create_ae_obj__user({
const fields: key_val = { ...data_kv };
if (account_id) fields.account_id_random = account_id;
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'user',
fields,
@@ -238,7 +269,7 @@ export async function update_ae_obj__user({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_User | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'user',
obj_id: user_id,
@@ -280,7 +311,7 @@ export async function delete_ae_obj_id__user({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'user',
obj_id: user_id,
@@ -383,7 +414,10 @@ export async function auth_ae_obj__username_password({
});
if (log_lvl) {
console.log('ae_promises.auth__username_password:', ae_promises.auth__username_password);
console.log(
'ae_promises.auth__username_password:',
ae_promises.auth__username_password
);
}
return ae_promises.auth__username_password;
}
@@ -450,7 +484,10 @@ export async function auth_ae_obj__user_id_user_auth_key({
});
if (log_lvl) {
console.log('ae_promises.auth__user_id_user_key:', ae_promises.auth__user_id_user_key);
console.log(
'ae_promises.auth__user_id_user_key:',
ae_promises.auth__user_id_user_key
);
}
return ae_promises.auth__user_id_user_key;
}
@@ -525,7 +562,9 @@ export async function qry_ae_obj_li__user_email({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** qry_ae_obj_li__user_email() *** account_id=${account_id} email=${email}`);
console.log(
`*** qry_ae_obj_li__user_email() *** account_id=${account_id} email=${email}`
);
}
const endpoint = '/user/lookup_email';
@@ -540,7 +579,7 @@ export async function qry_ae_obj_li__user_email({
params['email'] = email; // Required
params['null_account_id'] = null_account_id || false;
ae_promises.qry__user_email = await api
.get_object({
api_cfg: use_api_cfg,
@@ -676,11 +715,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.username ?? 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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -701,4 +744,4 @@ export async function process_ae_obj__user_props({
obj_type: 'user',
log_lvl
});
}
}

View File

@@ -25,7 +25,10 @@ import {
auth_ae_obj__user_id_change_password
} from '$lib/ae_core/ae_core__user';
import { generate_qr_code, js_generate_qr_code } from '$lib/ae_core/core__qr_code';
import {
generate_qr_code,
js_generate_qr_code
} from '$lib/ae_core/core__qr_code';
import { check_hosted_file_obj_w_hash } from '$lib/ae_core/core__check_hosted_file_obj_w_hash';
@@ -161,7 +164,9 @@ async function load_ae_obj_code__data_store({
}
if (!get_ds_result.data_store_id_random) {
console.log('*ae_func* Something went wrong? No data store ID found.');
console.log(
'*ae_func* Something went wrong? No data store ID found.'
);
return false;
}
@@ -240,7 +245,10 @@ async function load_ae_obj_code__data_store({
get_ds_result
);
}
localStorage.setItem(`${key_prefix}${code}`, JSON.stringify(get_ds_result));
localStorage.setItem(
`${key_prefix}${code}`,
JSON.stringify(get_ds_result)
);
} else {
if (log_lvl) {
console.log(
@@ -327,102 +335,102 @@ async function update_ae_obj_id_crud({
return ae_promises.api_update__ae_obj;
}
// Core - Already imported above
// import { load_ae_obj_id__person } from "$lib/ae_core/core__person";
// import { load_ae_obj_id__user } from "$lib/ae_core/core__user";
// // Core - Already imported above
// // import { load_ae_obj_id__person } from "$lib/ae_core/core__person";
// // import { load_ae_obj_id__user } from "$lib/ae_core/core__user";
// Additional Modules
import { load_ae_obj_id__archive } from '$lib/ae_archives/ae_archives__archive';
import { load_ae_obj_id__archive_content } from '$lib/ae_archives/ae_archives__archive_content';
// // Additional Modules
// import { load_ae_obj_id__archive } from '$lib/ae_archives/ae_archives__archive';
// import { load_ae_obj_id__archive_content } from '$lib/ae_archives/ae_archives__archive_content';
import { load_ae_obj_id__event } from '$lib/ae_events/ae_events__event';
// import { load_ae_obj_id__event_badge } from "$lib/ae_events/ae_events__event_badge";
import { load_ae_obj_id__event_exhibit } from '$lib/ae_events/ae_events__exhibit';
import { load_ae_obj_id__event_device } from '$lib/ae_events/ae_events__event_device';
// import { load_ae_obj_id__event_exhibit } from "$lib/ae_events/ae_events__event_exhibit";
import { load_ae_obj_id__event_file } from '$lib/ae_events/ae_events__event_file';
import { load_ae_obj_id__event_location } from '$lib/ae_events/ae_events__event_location';
import { load_ae_obj_id__event_presentation } from '$lib/ae_events/ae_events__event_presentation';
import { load_ae_obj_id__event_presenter } from '$lib/ae_events/ae_events__event_presenter';
import { load_ae_obj_id__event_session } from '$lib/ae_events/ae_events__event_session';
// import { load_ae_obj_id__event } from '$lib/ae_events/ae_events__event';
// // import { load_ae_obj_id__event_badge } from "$lib/ae_events/ae_events__event_badge";
// import { load_ae_obj_id__event_exhibit } from '$lib/ae_events/ae_events__exhibit';
// import { load_ae_obj_id__event_device } from '$lib/ae_events/ae_events__event_device';
// // import { load_ae_obj_id__event_exhibit } from "$lib/ae_events/ae_events__event_exhibit";
// import { load_ae_obj_id__event_file } from '$lib/ae_events/ae_events__event_file';
// import { load_ae_obj_id__event_location } from '$lib/ae_events/ae_events__event_location';
// import { load_ae_obj_id__event_presentation } from '$lib/ae_events/ae_events__event_presentation';
// import { load_ae_obj_id__event_presenter } from '$lib/ae_events/ae_events__event_presenter';
// import { load_ae_obj_id__event_session } from '$lib/ae_events/ae_events__event_session';
import { load_ae_obj_id__journal } from '$lib/ae_journals/ae_journals__journal';
import { load_ae_obj_id__journal_entry } from '$lib/ae_journals/ae_journals__journal_entry';
// import { load_ae_obj_id__journal } from '$lib/ae_journals/ae_journals__journal';
// import { load_ae_obj_id__journal_entry } from '$lib/ae_journals/ae_journals__journal_entry';
import { load_ae_obj_id__post } from '$lib/ae_posts/ae_posts__post';
import { load_ae_obj_id__post_comment } from '$lib/ae_posts/ae_posts__post_comment';
// import { load_ae_obj_id__post } from '$lib/ae_posts/ae_posts__post';
// import { load_ae_obj_id__post_comment } from '$lib/ae_posts/ae_posts__post_comment';
// Updated 2025-09-30
async function update_ae_obj_id_crud_v2({
api_cfg,
object_type,
object_id,
object_reload = false,
field_name,
new_field_value,
params = {},
log_lvl = 0
}: {
api_cfg: any;
object_type: string;
object_id: string;
object_reload?: boolean;
field_name: string;
new_field_value: any;
params?: any | key_val;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** update_ae_obj_id_crud_v2() *** object_type=${object_type}, object_id=${object_id}, object_reload=${object_reload}, field_name=${field_name}, new_field_value=`,
new_field_value
);
}
// async function update_ae_obj_id_crud_v2({
// api_cfg,
// object_type,
// object_id,
// object_reload = false,
// field_name,
// new_field_value,
// params = {},
// log_lvl = 0
// }: {
// api_cfg: any;
// object_type: string;
// object_id: string;
// object_reload?: boolean;
// field_name: string;
// new_field_value: any;
// params?: any | key_val;
// log_lvl?: number;
// }) {
// if (log_lvl) {
// console.log(
// `*** update_ae_obj_id_crud_v2() *** object_type=${object_type}, object_id=${object_id}, object_reload=${object_reload}, field_name=${field_name}, new_field_value=`,
// new_field_value
// );
// }
const results = await api.update_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: object_type,
obj_id: object_id,
field_name: field_name,
field_value: new_field_value,
key: api_cfg.api_crud_super_key,
log_lvl: log_lvl
});
// const results = await api.update_ae_obj_id_crud({
// api_cfg: api_cfg,
// obj_type: object_type,
// obj_id: object_id,
// field_name: field_name,
// field_value: new_field_value,
// key: api_cfg.api_crud_super_key,
// log_lvl: log_lvl
// });
if (results) {
if (log_lvl) {
console.log(`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}`);
}
// if (results) {
// if (log_lvl) {
// console.log(`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}`);
// }
if (object_reload) {
if (log_lvl) {
console.log(`Reloading the object after patching...`);
}
// Trigger reloads based on object type. These are fire-and-forget or awaited internally by the library functions.
if (object_type == 'person') load_ae_obj_id__person({ api_cfg, person_id: object_id, log_lvl });
if (object_type == 'archive') load_ae_obj_id__archive({ api_cfg, archive_id: object_id, log_lvl });
if (object_type == 'archive_content') load_ae_obj_id__archive_content({ api_cfg, archive_content_id: object_id, log_lvl });
if (object_type == 'journal') load_ae_obj_id__journal({ api_cfg, journal_id: object_id, log_lvl });
if (object_type == 'journal_entry') load_ae_obj_id__journal_entry({ api_cfg, journal_entry_id: object_id, log_lvl });
if (object_type == 'event') load_ae_obj_id__event({ api_cfg, event_id: object_id, log_lvl });
if (object_type == 'event_exhibit') load_ae_obj_id__event_exhibit({ api_cfg, exhibit_id: object_id, log_lvl });
if (object_type == 'event_device') load_ae_obj_id__event_device({ api_cfg, event_device_id: object_id, log_lvl });
if (object_type == 'event_file') load_ae_obj_id__event_file({ api_cfg, event_file_id: object_id, log_lvl });
if (object_type == 'event_location') load_ae_obj_id__event_location({ api_cfg, event_location_id: object_id, log_lvl });
if (object_type == 'event_presentation') load_ae_obj_id__event_presentation({ api_cfg, event_presentation_id: object_id, log_lvl });
if (object_type == 'event_presenter') load_ae_obj_id__event_presenter({ api_cfg, event_presenter_id: object_id, log_lvl });
if (object_type == 'event_session') load_ae_obj_id__event_session({ api_cfg, event_session_id: object_id, log_lvl });
if (object_type == 'post') load_ae_obj_id__post({ api_cfg, post_id: object_id, log_lvl });
if (object_type == 'post_comment') load_ae_obj_id__post_comment({ api_cfg, post_comment_id: object_id, log_lvl });
}
} else {
if (log_lvl) {
console.log(`PATCH failed for ${object_type} ${object_id}`);
}
}
// if (object_reload) {
// if (log_lvl) {
// console.log(`Reloading the object after patching...`);
// }
// // Trigger reloads based on object type. These are fire-and-forget or awaited internally by the library functions.
// if (object_type == 'person') load_ae_obj_id__person({ api_cfg, person_id: object_id, log_lvl });
// if (object_type == 'archive') load_ae_obj_id__archive({ api_cfg, archive_id: object_id, log_lvl });
// if (object_type == 'archive_content') load_ae_obj_id__archive_content({ api_cfg, archive_content_id: object_id, log_lvl });
// if (object_type == 'journal') load_ae_obj_id__journal({ api_cfg, journal_id: object_id, log_lvl });
// if (object_type == 'journal_entry') load_ae_obj_id__journal_entry({ api_cfg, journal_entry_id: object_id, log_lvl });
// if (object_type == 'event') load_ae_obj_id__event({ api_cfg, event_id: object_id, log_lvl });
// if (object_type == 'event_exhibit') load_ae_obj_id__event_exhibit({ api_cfg, exhibit_id: object_id, log_lvl });
// if (object_type == 'event_device') load_ae_obj_id__event_device({ api_cfg, event_device_id: object_id, log_lvl });
// if (object_type == 'event_file') load_ae_obj_id__event_file({ api_cfg, event_file_id: object_id, log_lvl });
// if (object_type == 'event_location') load_ae_obj_id__event_location({ api_cfg, event_location_id: object_id, log_lvl });
// if (object_type == 'event_presentation') load_ae_obj_id__event_presentation({ api_cfg, event_presentation_id: object_id, log_lvl });
// if (object_type == 'event_presenter') load_ae_obj_id__event_presenter({ api_cfg, event_presenter_id: object_id, log_lvl });
// if (object_type == 'event_session') load_ae_obj_id__event_session({ api_cfg, event_session_id: object_id, log_lvl });
// if (object_type == 'post') load_ae_obj_id__post({ api_cfg, post_id: object_id, log_lvl });
// if (object_type == 'post_comment') load_ae_obj_id__post_comment({ api_cfg, post_comment_id: object_id, log_lvl });
// }
// } else {
// if (log_lvl) {
// console.log(`PATCH failed for ${object_type} ${object_id}`);
// }
// }
return results;
}
// return results;
// }
async function download_export__obj_type({
api_cfg,
@@ -491,7 +499,10 @@ async function download_export__obj_type({
log_lvl: log_lvl
});
console.log('ae_promises.download__export_file:', ae_promises.download__export_file);
console.log(
'ae_promises.download__export_file:',
ae_promises.download__export_file
);
return ae_promises.download__export_file;
}
@@ -533,7 +544,7 @@ const export_obj = {
auth_ae_obj__user_id_change_password: auth_ae_obj__user_id_change_password,
update_ae_obj_id_crud: update_ae_obj_id_crud,
update_ae_obj_id_crud_v2: update_ae_obj_id_crud_v2,
// update_ae_obj_id_crud_v2: update_ae_obj_id_crud_v2,
download_export__obj_type: download_export__obj_type,
generate_qr_code: generate_qr_code,
js_generate_qr_code: js_generate_qr_code

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

@@ -13,12 +13,17 @@ export function add_url_params({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** add_url_params() *** base_url=${base_url} endpoint=${endpoint}`, params);
console.log(
`*** add_url_params() *** base_url=${base_url} endpoint=${endpoint}`,
params
);
}
const url_obj = new URL(endpoint, base_url);
Object.keys(params).forEach((key) => url_obj.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url_obj.searchParams.append(key, params[key])
);
if (log_lvl) {
console.log('New URL:', url_obj.toString());
@@ -30,7 +35,13 @@ export function add_url_params({
// This is used to clean the header property names. Not underscores allowed in the header names.
// Updated 2025-01-28
export function clean_headers({ headers, log_lvl = 0 }: { headers: any; log_lvl?: number }) {
export function clean_headers({
headers,
log_lvl = 0
}: {
headers: any;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** clean_headers() ***`, headers);
}

View File

@@ -1,7 +1,7 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
// Updated 2024-10-02
// Updated 2026-03-25
export async function check_hosted_file_obj_w_hash({
api_cfg,
hosted_file_hash,
@@ -19,7 +19,7 @@ export async function check_hosted_file_obj_w_hash({
}) {
console.log('*** stores_event_api.js: check_hosted_file_obj_w_hash() ***');
const endpoint = `/hosted_file/hash/${hosted_file_hash}`;
const endpoint = `/v3/action/hosted_file/hash/${hosted_file_hash}`;
if (check_for_local) {
params['check_for_local'] = true;
}

View File

@@ -1,65 +1,72 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups';
import { db_core } from '$lib/ae_core/db_core';
/**
* Country lookup — IDB-backed SWR helper.
*
* Calling this function triggers a background API refresh if IDB is empty or
* older than 24 hours. The function returns immediately without awaiting the
* refresh. Components subscribe to db_lookups.lu_country via liveQuery and
* receive automatic updates when the refresh completes.
*
* Updated 2026-03-23 — replaced localStorage pattern with IDB + 24h TTL
*/
const ae_promises: key_val = {};
// Updated 2024-10-14
export async function load_ae_obj_li__country({
async function _refresh_lu_country_background({
api_cfg,
// account_id,
enabled = 'enabled',
hidden = 'not_hidden',
limit = 275, // There are roughly 249 as of 2026-02
offset = 0,
order_by_li = { sort: 'DESC', english_short_name: 'ASC', alpha_2_code: 'ASC' } as const,
params = {},
try_cache = true,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
// account_id: string,
enabled?: 'enabled' | 'all' | 'not_enabled' | undefined;
hidden?: 'hidden' | 'all' | 'not_hidden' | undefined;
limit?: number;
offset?: number;
order_by_li?: key_val;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** load_ae_obj_li__country() ***`);
}
const params_json: key_val = {};
// console.log('params_json:', params_json);
ae_promises.load__country_li = await api
.get_ae_obj_li_for_lu({
api_cfg: api_cfg,
if (log_lvl) console.log('*** _refresh_lu_country_background() ***');
try {
const result = await api.get_ae_obj_li_for_lu({
api_cfg,
for_lu_type: 'country',
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
log_lvl: log_lvl
})
.then(function (country_li_get_result) {
if (country_li_get_result) {
// handle_db_save_ae_obj_li__country({obj_type: 'country', obj_li: country_li_get_result});
return country_li_get_result;
} else {
return [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
enabled: 'enabled',
hidden: 'not_hidden',
limit: 275,
log_lvl
});
console.log('ae_promises.load__country_li:', ae_promises.load__country_li);
return ae_promises.load__country_li;
if (result?.length) {
await db_lookups.lu_country.clear();
await db_lookups.lu_country.bulkPut(result);
await db_lookups.lu_cache_meta.put({
lu_type: 'country',
refreshed_at: Date.now()
});
if (log_lvl)
console.log(
`lu_country: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_country refresh failed:', error);
}
}
export async function load_ae_obj_li__country({
api_cfg,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** load_ae_obj_li__country() ***');
const count = await db_lookups.lu_country.count();
const meta = await db_lookups.lu_cache_meta.get('country');
const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS;
if (count === 0 || is_stale) {
// Fire-and-forget — liveQuery subscribers receive updates when IDB is written
_refresh_lu_country_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(
`lu_country: IDB fresh (${count} records), skipping refresh`
);
}
}

View File

@@ -1,68 +1,71 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups';
import { db_core } from '$lib/ae_core/db_core';
/**
* Country subdivision lookup — IDB-backed SWR helper.
*
* Calling this function triggers a background API refresh if IDB is empty or
* older than 24 hours. Components subscribe to db_lookups.lu_country_subdivision
* via liveQuery and receive automatic updates when the refresh completes.
*
* Updated 2026-03-23 — replaced localStorage pattern with IDB + 24h TTL
*/
const ae_promises: key_val = {};
// Updated 2024-10-14
export async function load_ae_obj_li__country_subdivision({
async function _refresh_lu_country_subdivision_background({
api_cfg,
// account_id,
enabled = 'enabled',
hidden = 'not_hidden',
limit = 3500, // There are roughly 3434 as of 2026-02
offset = 0,
order_by_li = { sort: 'DESC', name: 'ASC', code: 'ASC' } as const,
params = {},
try_cache = true,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
// account_id: string,
enabled?: 'enabled' | 'all' | 'not_enabled' | undefined;
hidden?: 'hidden' | 'all' | 'not_hidden' | undefined;
limit?: number;
offset?: number;
order_by_li?: key_val;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** load_ae_obj_li__country_subdivision() ***`);
}
const params_json: key_val = {};
// console.log('params_json:', params_json);
ae_promises.load__country_subdivision_li = await api
.get_ae_obj_li_for_lu({
api_cfg: api_cfg,
if (log_lvl)
console.log('*** _refresh_lu_country_subdivision_background() ***');
try {
const result = await api.get_ae_obj_li_for_lu({
api_cfg,
for_lu_type: 'country_subdivision',
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
log_lvl: log_lvl
})
.then(function (country_subdivision_li_get_result) {
if (country_subdivision_li_get_result) {
// handle_db_save_ae_obj_li__country_subdivision({obj_type: 'country_subdivision', obj_li: country_subdivision_li_get_result});
return country_subdivision_li_get_result;
} else {
return [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
enabled: 'enabled',
hidden: 'not_hidden',
limit: 3500,
log_lvl
});
console.log(
'ae_promises.load__country_subdivision_li:',
ae_promises.load__country_subdivision_li
);
return ae_promises.load__country_subdivision_li;
if (result?.length) {
await db_lookups.lu_country_subdivision.clear();
await db_lookups.lu_country_subdivision.bulkPut(result);
await db_lookups.lu_cache_meta.put({
lu_type: 'country_subdivision',
refreshed_at: Date.now()
});
if (log_lvl)
console.log(
`lu_country_subdivision: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_country_subdivision refresh failed:', error);
}
}
export async function load_ae_obj_li__country_subdivision({
api_cfg,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** load_ae_obj_li__country_subdivision() ***');
const count = await db_lookups.lu_country_subdivision.count();
const meta = await db_lookups.lu_cache_meta.get('country_subdivision');
const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS;
if (count === 0 || is_stale) {
_refresh_lu_country_subdivision_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(
`lu_country_subdivision: IDB fresh (${count} records), skipping refresh`
);
}
}

View File

@@ -1,351 +0,0 @@
import { marked } from 'marked';
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';
// Define generic CRUD args
export interface GenericCrudArgs {
api_cfg: any;
obj_type: string;
obj_id?: string;
for_obj_type?: string;
for_obj_id?: string;
db_instance?: any; // Optional DB instance for caching
db_field_li?: string[]; // Optional list of fields to save in DB
// Flags to include related core object models
inc_account_li?: boolean;
inc_address_li?: boolean;
inc_contact_li?: boolean;
inc_person_li?: boolean;
inc_site_li?: boolean;
inc_site_domain_li?: boolean;
inc_user_li?: boolean;
// Flags to include related other object models
inc_archive_li?: boolean;
inc_archive_entry_li?: boolean;
inc_event_li?: boolean;
inc_event_session_li?: boolean;
inc_post_li?: boolean;
inc_post_comment_li?: boolean;
inc_journal_li?: boolean;
inc_journal_entry_li?: boolean;
inc_obj_type_li?: string[]; // Optional list of object types to include
data_kv?: key_val;
enabled?: 'enabled' | 'not_enabled' | 'all';
hidden?: 'not_hidden' | 'hidden' | 'all';
method?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[] | null;
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}
// Generic function: Load single object by ID
export async function load_ae_obj_id(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('load_ae_obj_id called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** load_ae_obj_id() *** obj_type=${obj_type} obj_id=${obj_id}`);
}
const result = await api.get_ae_obj_id_crud({
api_cfg,
obj_type,
obj_id,
params: {},
log_lvl
});
return result;
}
// Generic function: Load list of objects
export async function load_ae_obj_li(args: GenericCrudArgs): Promise<any> {
const {
api_cfg,
obj_type,
for_obj_type = '',
for_obj_id,
inc_obj_type_li,
enabled = 'enabled',
hidden = 'not_hidden',
limit = 99,
offset = 0,
order_by_li = {},
params = {},
try_cache = true,
log_lvl = 0
} = args;
if (log_lvl) {
console.log(
`*** load_ae_obj_li() *** obj_type=${obj_type} for_obj_type=${for_obj_type} for_obj_id=${for_obj_id}`
);
}
const params_json: key_val = {};
const result = await api.get_ae_obj_li_for_obj_id_crud_v2({
api_cfg,
obj_type,
for_obj_type,
for_obj_id: for_obj_id ?? '',
enabled,
hidden,
order_by_li,
limit,
offset,
params_json: {},
params,
log_lvl
});
return result;
}
// Generic function: Create object
export async function create_ae_obj(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, data_kv, log_lvl = 0 } = args;
if (log_lvl) {
console.log(`*** create_ae_obj() *** obj_type=${obj_type}`, data_kv);
}
const result = await api.create_ae_obj_crud({
api_cfg,
obj_type,
fields: data_kv,
key: api_cfg.api_crud_super_key,
params: {},
return_obj: true,
log_lvl
});
return result;
}
// Generic function: Update object
export async function update_ae_obj(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, data_kv, log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('update_ae_obj called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** update_ae_obj() *** obj_type=${obj_type} obj_id=${obj_id}`, data_kv);
}
const result = await api.update_ae_obj_id_crud({
api_cfg,
obj_type,
obj_id,
fields: data_kv,
key: api_cfg.api_crud_super_key,
params: {},
return_obj: true,
log_lvl
});
return result;
}
// Generic function: Delete object
export async function delete_ae_obj_id(args: GenericCrudArgs): Promise<any> {
const { api_cfg, obj_type, obj_id, method = 'delete', log_lvl = 0 } = args;
if (!obj_id) {
if (log_lvl) console.warn('delete_ae_obj_id called without obj_id');
return null;
}
if (log_lvl) {
console.log(`*** delete_ae_obj_id() *** obj_type=${obj_type} obj_id=${obj_id}`);
}
const result = await api.delete_ae_obj_id_crud({
api_cfg,
obj_type,
obj_id,
key: api_cfg.api_crud_super_key,
params: {},
method,
log_lvl
});
return result;
}
// Additional Modules that might be needed for reloads
import { load_ae_obj_id__archive } from '$lib/ae_archives/ae_archives__archive';
import { load_ae_obj_id__archive_content } from '$lib/ae_archives/ae_archives__archive_content';
import { load_ae_obj_id__event } from '$lib/ae_events/ae_events__event';
import { load_ae_obj_id__event_device } from '$lib/ae_events/ae_events__event_device';
import { load_ae_obj_id__event_file } from '$lib/ae_events/ae_events__event_file';
import { load_ae_obj_id__event_location } from '$lib/ae_events/ae_events__event_location';
import { load_ae_obj_id__event_presentation } from '$lib/ae_events/ae_events__event_presentation';
import { load_ae_obj_id__event_presenter } from '$lib/ae_events/ae_events__event_presenter';
import { load_ae_obj_id__event_session } from '$lib/ae_events/ae_events__event_session';
import { load_ae_obj_id__journal } from '$lib/ae_journals/ae_journals__journal';
import { load_ae_obj_id__journal_entry } from '$lib/ae_journals/ae_journals__journal_entry';
import { load_ae_obj_id__post } from '$lib/ae_posts/ae_posts__post';
import { load_ae_obj_id__post_comment } from '$lib/ae_posts/ae_posts__post_comment';
import { load_ae_obj_id__person } from '$lib/ae_core/ae_core__person';
export async function update_ae_obj_id_crud_v2({
api_cfg,
object_type,
object_id,
object_reload = false,
field_name,
new_field_value,
log_lvl = 0
}: {
api_cfg: any;
object_type: string;
object_id: string;
object_reload?: boolean;
field_name: string;
new_field_value: any;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(
`*** update_ae_obj_id_crud_v2() *** object_type=${object_type}, object_id=${object_id}, object_reload=${object_reload}, field_name=${field_name}, new_field_value=`,
new_field_value
);
}
try {
const results = await api.update_ae_obj_id_crud({
api_cfg: api_cfg,
obj_type: object_type,
obj_id: object_id,
field_name: field_name,
field_value: new_field_value,
key: api_cfg.api_crud_super_key,
log_lvl: log_lvl
});
if (!results) {
if (log_lvl) console.log(
`Not Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}; Account ID: ${api_cfg.account_id}`
);
return false;
}
if (log_lvl) console.log(`Patched - Field Name: ${field_name} with new Field Value: ${new_field_value}`);
if (object_reload) {
if (log_lvl) console.log(`Reloading the object after patching...`);
const reload_fns: { [key: string]: (args: any) => Promise<any> } = {
person: load_ae_obj_id__person,
archive: load_ae_obj_id__archive,
archive_content: load_ae_obj_id__archive_content,
journal: load_ae_obj_id__journal,
journal_entry: load_ae_obj_id__journal_entry,
event: load_ae_obj_id__event,
event_device: load_ae_obj_id__event_device,
event_file: load_ae_obj_id__event_file,
event_location: load_ae_obj_id__event_location,
event_presentation: load_ae_obj_id__event_presentation,
event_presenter: load_ae_obj_id__event_presenter,
event_session: load_ae_obj_id__event_session,
post: load_ae_obj_id__post,
post_comment: load_ae_obj_id__post_comment
};
const reload_fn = reload_fns[object_type];
if (reload_fn) {
const id_key = `${object_type}_id`;
return await reload_fn({ api_cfg, [id_key]: object_id, log_lvl });
}
}
return true;
} catch (error) {
console.log('Something went wrong patching the record.', error);
return false;
}
}
export async function download_export_li({
api_cfg,
get_obj_type,
for_obj_type,
for_obj_id,
exp_alt = null,
file_type = 'CSV',
return_file = true,
filename = 'no_filename.csv',
auto_download = false,
limit = 5000,
params = {},
log_lvl = 0
}: {
api_cfg: any;
get_obj_type: string;
for_obj_type: string;
for_obj_id: string;
exp_alt?: null | string;
file_type?: string;
return_file?: boolean;
filename?: string;
auto_download?: boolean;
limit?: number;
params?: key_val;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** download_export_li() ***');
const endpoint = `/v2/crud/${get_obj_type}/list`;
params['for_obj_type'] = for_obj_type;
params['for_obj_id'] = for_obj_id;
if (file_type === 'CSV' || file_type === 'Excel') {
params['file_type'] = file_type;
}
params['return_file'] = true;
params['mdl_alt'] = 'out';
if (exp_alt) {
params['exp_alt'] = exp_alt;
}
const clean_filename = filename.replace(/[^a-zA-Z0-9\[\]-_.]/gi, '_');
if (limit >= 0) {
params['limit'] = limit;
}
const download_result = await api.get_object({
api_cfg: api_cfg,
endpoint: endpoint,
params: params,
timeout: 90000,
return_blob: return_file,
filename: clean_filename,
auto_download: auto_download,
task_id: for_obj_id,
log_lvl: log_lvl
});
if (log_lvl) console.log('download_result:', download_result);
return download_result;
}

View File

@@ -39,7 +39,7 @@ export async function load_ae_obj_by_code__data_store({
}
try {
const get_ds_result = await api.get_data_store_v3({
const get_ds_result = await api.get_data_store({
api_cfg,
code,
log_lvl
@@ -50,17 +50,25 @@ export async function load_ae_obj_by_code__data_store({
return null;
}
const ds_id = get_ds_result.data_store_id_random || get_ds_result.id_random;
const ds_id =
get_ds_result.data_store_id_random || get_ds_result.id_random;
if (!ds_id) {
if (log_lvl) console.log('*ae_func* Something went wrong? No data store ID found.');
if (log_lvl)
console.log(
'*ae_func* Something went wrong? No data store ID found.'
);
return null;
}
// Map content fields for convenience
const text_val = get_ds_result.text || '';
const json_val = get_ds_result.json || (get_ds_result.json_str ? JSON.parse(get_ds_result.json_str) : null);
const json_val =
get_ds_result.json ||
(get_ds_result.json_str
? JSON.parse(get_ds_result.json_str)
: null);
const mapped_ds: ae_DataStore = {
...get_ds_result,
id: ds_id,
@@ -77,7 +85,6 @@ export async function load_ae_obj_by_code__data_store({
if (data_type === 'html') return mapped_ds.html;
if (data_type === 'json') return mapped_ds.json;
return mapped_ds.text;
} catch (error) {
if (log_lvl) console.error('*ae_func* Fetch failed.', error);
return null;

View File

@@ -22,11 +22,13 @@ export async function load_ae_obj_id__hosted_file({
log_lvl?: number;
}): Promise<ae_HostedFile | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__hosted_file() *** [V3] id=${hosted_file_id}`);
console.log(
`*** load_ae_obj_id__hosted_file() *** [V3] id=${hosted_file_id}`
);
}
try {
ae_promises.load__hosted_file_obj = await api.get_ae_obj_v3({
ae_promises.load__hosted_file_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'hosted_file',
obj_id: hosted_file_id,
@@ -36,10 +38,11 @@ export async function load_ae_obj_id__hosted_file({
if (ae_promises.load__hosted_file_obj) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__hosted_file_props({
obj_li: [ae_promises.load__hosted_file_obj],
log_lvl
});
const processed_obj_li =
await process_ae_obj__hosted_file_props({
obj_li: [ae_promises.load__hosted_file_obj],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'file',
@@ -49,12 +52,14 @@ export async function load_ae_obj_id__hosted_file({
});
}
} else if (try_cache) {
ae_promises.load__hosted_file_obj = await db_core.file.get(hosted_file_id);
ae_promises.load__hosted_file_obj =
await db_core.file.get(hosted_file_id);
}
} catch (error: any) {
console.log('V3 Request failed.', error);
if (try_cache) {
ae_promises.load__hosted_file_obj = await db_core.file.get(hosted_file_id);
ae_promises.load__hosted_file_obj =
await db_core.file.get(hosted_file_id);
}
}
@@ -90,11 +95,13 @@ export async function load_ae_obj_li__hosted_file({
log_lvl?: number;
}): Promise<ae_HostedFile[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__hosted_file() *** [V3] for=${for_obj_type}:${for_obj_id}`);
console.log(
`*** load_ae_obj_li__hosted_file() *** [V3] for=${for_obj_type}:${for_obj_id}`
);
}
try {
ae_promises.load__hosted_file_obj_li = await api.get_ae_obj_li_v3({
ae_promises.load__hosted_file_obj_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'hosted_file',
for_obj_type,
@@ -109,10 +116,11 @@ export async function load_ae_obj_li__hosted_file({
if (ae_promises.load__hosted_file_obj_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__hosted_file_props({
obj_li: ae_promises.load__hosted_file_obj_li,
log_lvl
});
const processed_obj_li =
await process_ae_obj__hosted_file_props({
obj_li: ae_promises.load__hosted_file_obj_li,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'file',
@@ -123,14 +131,16 @@ export async function load_ae_obj_li__hosted_file({
}
} else if (try_cache) {
ae_promises.load__hosted_file_obj_li = await db_core.file
.where('for_id').equals(for_obj_id)
.where('for_id')
.equals(for_obj_id)
.toArray();
}
} catch (error: any) {
console.log('V3 List Request failed.', error);
if (try_cache) {
ae_promises.load__hosted_file_obj_li = await db_core.file
.where('for_id').equals(for_obj_id)
.where('for_id')
.equals(for_obj_id)
.toArray();
}
}
@@ -159,7 +169,9 @@ export async function delete_ae_obj_id__hosted_file({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__hosted_file() *** [Special] id=${hosted_file_id}`);
console.log(
`*** delete_ae_obj_id__hosted_file() *** [Special] id=${hosted_file_id}`
);
}
// Use the specialized hosted file delete endpoint
@@ -203,7 +215,9 @@ export async function download_ae_obj_id__hosted_file({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** download_ae_obj_id__hosted_file() *** id=${hosted_file_id}`);
console.log(
`*** download_ae_obj_id__hosted_file() *** id=${hosted_file_id}`
);
}
const task_id = hosted_file_id;
@@ -290,11 +304,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -315,4 +333,4 @@ export async function process_ae_obj__hosted_file_props({
obj_type: 'hosted_file',
log_lvl
});
}
}

View File

@@ -14,9 +14,9 @@ function find_object_id(
log_lvl: number
): string | number | undefined {
const potential_keys = [
'id',
'id_random',
`${table_name}_id`,
'id',
'id_random',
`${table_name}_id`,
`${table_name}_id_random`,
`event_${table_name}_id`,
`event_${table_name}_id_random`
@@ -115,7 +115,9 @@ export async function db_save_ae_obj_li__ae_obj<T extends Record<string, any>>({
if (data_to_save.length === 0) {
if (log_lvl > 0) {
console.warn('All objects were skipped, likely due to missing IDs.');
console.warn(
'All objects were skipped, likely due to missing IDs.'
);
}
return [];
}
@@ -124,7 +126,9 @@ export async function db_save_ae_obj_li__ae_obj<T extends Record<string, any>>({
// bulkPut efficiently handles both inserts and updates.
const keys = await db_table.bulkPut(data_to_save);
if (log_lvl > 0) {
console.log(`Successfully saved ${data_to_save.length} objects to "${table_name}".`);
console.log(
`Successfully saved ${data_to_save.length} objects to "${table_name}".`
);
}
return keys;
} catch (error) {

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

@@ -49,7 +49,8 @@ export async function generate_qr_code({
if (qr_type == 'vcard') {
if (qr_data.informal_name) {
params['n'] = `${qr_data.family_name};${qr_data.given_name};${qr_data.informal_name}`;
params['n'] =
`${qr_data.family_name};${qr_data.given_name};${qr_data.informal_name}`;
} else {
params['n'] = `${qr_data.family_name};${qr_data.given_name}`;
}
@@ -99,7 +100,8 @@ export async function generate_qr_code({
// If return_blob is true, ensure we return an object URL for use in <img src=...>
if (return_blob) {
const data = ae_promises.generate_qr_code.data ?? ae_promises.generate_qr_code;
const data =
ae_promises.generate_qr_code.data ?? ae_promises.generate_qr_code;
// If already a Blob, use it directly
if (data instanceof Blob) {
@@ -133,7 +135,8 @@ export async function generate_qr_code({
const blob = new Blob([data as BlobPart], { type: 'image/png' });
return URL.createObjectURL(blob);
} catch (e) {
if (log_lvl) console.error('Could not create QR code image blob:', e, data);
if (log_lvl)
console.error('Could not create QR code image blob:', e, data);
return null;
}
}
@@ -155,7 +158,10 @@ export async function generate_qr_code({
* @returns {Promise<string>} A promise that resolves to a Base64 data URL of the QR code image.
* @throws {Error} If the qr_type is unknown or data is missing.
*/
export async function js_generate_qr_code(qr_type: string, params: key_val = {}) {
export async function js_generate_qr_code(
qr_type: string,
params: key_val = {}
) {
const {
n = '',
fn = '',
@@ -175,10 +181,12 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
key,
val,
js,
str
str,
log_lvl = 0
} = params;
console.log(`*** js_generate_qr_code() *** qr_type=${qr_type}`, params);
if (log_lvl >= 2)
console.log(`*** js_generate_qr_code() *** qr_type=${qr_type}`, params);
let qr_data: string | null = null;
@@ -210,13 +218,15 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
case 'obj':
// Custom format: OBJ:ot:obj_type,oi:obj_id
if (!obj_type || !obj_id) throw new Error('Missing obj_type or obj_id for type "obj".');
if (!obj_type || !obj_id)
throw new Error('Missing obj_type or obj_id for type "obj".');
qr_data = `OBJ:ot:${obj_type},oi:${obj_id}`;
break;
case 'kv':
// Custom format: KV:k:"key",v:"val"
if (!key || !val) throw new Error('Missing key or val for type "kv".');
if (!key || !val)
throw new Error('Missing key or val for type "kv".');
qr_data = `KV:k:"${key}",v:"${val}"`;
break;
@@ -228,7 +238,8 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
case 'str':
// Raw string data
if (!str) throw new Error('Missing raw string data for type "str".');
if (!str)
throw new Error('Missing raw string data for type "str".');
qr_data = str;
break;
@@ -251,7 +262,6 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
scale: 10, // Corresponds to box_size
type: 'image/png'
});
console.log('Generated QR code data URL:', data_url);
return data_url;
} catch (error) {
console.error('Error generating QR code:', error);

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,69 +1,72 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_lookups, LOOKUP_TTL_MS } from '$lib/ae_core/db_lookups';
import { db_core } from '$lib/ae_core/db_core';
/**
* Time zone lookup — IDB-backed SWR helper.
*
* Fetches priority timezones (only_priority=true, ~72 records). Calling this
* function triggers a background API refresh if IDB is empty or older than
* 24 hours. Components subscribe to db_lookups.lu_time_zone via liveQuery and
* receive automatic updates when the refresh completes.
*
* Updated 2026-03-23 — replaced $ae_loc + localStorage pattern with IDB + 24h TTL
*/
const ae_promises: key_val = {};
// Updated 2026-02-20
export async function load_ae_obj_li__time_zone({
async function _refresh_lu_time_zone_background({
api_cfg,
// account_id,
enabled = 'enabled',
hidden = 'not_hidden',
limit = 1800, // There are roughly 1780 as of 2026-02
offset = 0,
// order_by_li = {'priority': 'DESC', 'group': 'ASC', 'sort': 'DESC', 'name': 'ASC'},
order_by_li = { priority: 'DESC', sort: 'DESC', name: 'ASC' } as const,
params = {},
only_priority = false,
try_cache = true,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
// account_id: string,
enabled?: 'enabled' | 'all' | 'not_enabled' | undefined;
hidden?: 'hidden' | 'all' | 'not_hidden' | undefined;
limit?: number;
offset?: number;
order_by_li?: key_val;
params?: key_val;
only_priority?: boolean;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** load_ae_obj_li__time_zone() *** only_priority=${only_priority}`);
}
const params_json: key_val = {};
// console.log('params_json:', params_json);
ae_promises.load__time_zone_li = await api
.get_ae_obj_li_for_lu({
api_cfg: api_cfg,
if (log_lvl) console.log('*** _refresh_lu_time_zone_background() ***');
try {
const result = await api.get_ae_obj_li_for_lu({
api_cfg,
for_lu_type: 'time_zone',
enabled: enabled,
hidden: hidden,
limit: limit,
offset: offset,
params: params,
only_priority: only_priority,
log_lvl: log_lvl
})
.then(function (time_zone_li_get_result) {
if (time_zone_li_get_result) {
// handle_db_save_ae_obj_li__time_zone({obj_type: 'time_zone', obj_li: time_zone_li_get_result});
return time_zone_li_get_result;
} else {
return [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
enabled: 'enabled',
hidden: 'not_hidden',
only_priority: true, // ~72 priority timezone records
limit: 1800,
log_lvl
});
console.log('ae_promises.load__time_zone_li:', ae_promises.load__time_zone_li);
return ae_promises.load__time_zone_li;
if (result?.length) {
await db_lookups.lu_time_zone.clear();
await db_lookups.lu_time_zone.bulkPut(result);
await db_lookups.lu_cache_meta.put({
lu_type: 'time_zone',
refreshed_at: Date.now()
});
if (log_lvl)
console.log(
`lu_time_zone: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_time_zone refresh failed:', error);
}
}
export async function load_ae_obj_li__time_zone({
api_cfg,
log_lvl = 0
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api_cfg: any;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** load_ae_obj_li__time_zone() ***');
const count = await db_lookups.lu_time_zone.count();
const meta = await db_lookups.lu_cache_meta.get('time_zone');
const is_stale = !meta || Date.now() - meta.refreshed_at > LOOKUP_TTL_MS;
if (count === 0 || is_stale) {
_refresh_lu_time_zone_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(
`lu_time_zone: IDB fresh (${count} records), skipping refresh`
);
}
}

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

@@ -0,0 +1,81 @@
import Dexie, { type Table } from 'dexie';
/**
* Lookup DB — IDB-backed cache for V3 Uniform Lookup System reference data.
*
* These tables store the deduplicated, priority-ranked list returned by
* GET /v3/lookup/{lu_type}/list. Data is refreshed automatically on a 24-hour
* TTL via the core__*.ts load helpers; components subscribe via liveQuery.
*
* Updated 2026-03-23
*/
export interface LuCountry {
id: number;
group: string; // dedup key = alpha_2_code (e.g. "US")
alpha_2_code: string;
name: string;
english_short_name?: string;
name_override?: string;
enable?: number;
hide?: number;
priority?: number;
sort?: number;
account_id?: number | null;
[key: string]: unknown; // allow extra fields from API without TS errors
}
export interface LuCountrySubdivision {
id: number;
group: string; // dedup key = code (e.g. "US-NY")
code: string;
name: string;
country_alpha_2_code?: string;
name_override?: string;
enable?: number;
hide?: number;
priority?: number;
sort?: number;
account_id?: number | null;
[key: string]: unknown;
}
export interface LuTimeZone {
id: number;
group: string; // dedup key = name (IANA identifier, e.g. "US/Eastern")
name: string;
name_override?: string; // display label override; prefer this over name when set
enable?: number;
hide?: number;
priority?: number;
sort?: number;
account_id?: number | null;
[key: string]: unknown;
}
export interface LuCacheMeta {
lu_type: 'country' | 'country_subdivision' | 'time_zone';
refreshed_at: number; // Unix timestamp ms — used for 24h TTL check
}
class LookupsDexie extends Dexie {
lu_country!: Table<LuCountry>;
lu_country_subdivision!: Table<LuCountrySubdivision>;
lu_time_zone!: Table<LuTimeZone>;
lu_cache_meta!: Table<LuCacheMeta>;
constructor() {
super('ae_lookups_db');
this.version(1).stores({
lu_country: 'id, alpha_2_code, group',
lu_country_subdivision: 'id, code, country_alpha_2_code, group',
lu_time_zone: 'id, name, group',
lu_cache_meta: 'lu_type'
});
}
}
export const db_lookups = new LookupsDexie();
/** 24-hour TTL in milliseconds */
export const LOOKUP_TTL_MS = 24 * 60 * 60 * 1000;

View File

@@ -1,125 +1,140 @@
<script lang="ts">
/**
* AE_AITools.svelte
* GENERIC Aether AI Toolset (Runes/Svelte 5)
* Extracted logic from Journals module to be system-wide.
*/
import OpenAI from 'openai';
import { Modal } from 'flowbite-svelte';
import {
Bot, BotMessageSquare, Loader, FileText,
Save, FilePenLine, RotateCcw, Settings,
RefreshCcw, Globe, Copy
} from '@lucide/svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import AE_Comp_Editor_CodeMirror from '$lib/elements/AE_Comp_Editor_CodeMirror.svelte';
/**
* AE_AITools.svelte
* GENERIC Aether AI Toolset (Runes/Svelte 5)
* Extracted logic from Journals module to be system-wide.
*/
import OpenAI from 'openai';
import { Modal } from 'flowbite-svelte';
import {
Bot,
BotMessageSquare,
Loader,
FileText,
Save,
FilePenLine,
RotateCcw,
Settings,
RefreshCcw,
Globe,
Copy
} from '@lucide/svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
interface Props {
// Core Props
content: string; // The text to summarize/analyze
summary: string; // The result (bindable)
// Configuration (Bindable for global settings persistence)
model?: string;
baseUrl?: string;
token?: string;
systemPrompt?: string;
maxTokens?: number;
temperature?: number;
// Callbacks
onSave?: (newSummary: string) => void;
onSyncConfig?: () => void; // Optional: callback to sync from global site config
// UI Customization
buttonClass?: string;
log_lvl?: number;
interface Props {
// Core Props
content: string; // The text to summarize/analyze
summary: string; // The result (bindable)
// Configuration (Bindable for global settings persistence)
model?: string;
baseUrl?: string;
token?: string;
systemPrompt?: string;
maxTokens?: number;
temperature?: number;
// Callbacks
onSave?: (newSummary: string) => void;
onSyncConfig?: () => void; // Optional: callback to sync from global site config
// UI Customization
buttonClass?: string;
log_lvl?: number;
}
let {
content,
summary = $bindable(),
model = $bindable(),
baseUrl = $bindable(),
token = $bindable(),
systemPrompt = $bindable(),
maxTokens = $bindable(),
temperature = $bindable(),
onSave,
onSyncConfig,
buttonClass = 'btn btn-sm preset-tonal-primary shadow-lg hover:scale-105 transition-all',
log_lvl = 0
}: Props = $props();
// Apply defaults if undefined (Safe for Svelte 5 Runes)
if (model === undefined) model = 'dgrzone-deepseek-8b-quick';
if (baseUrl === undefined) baseUrl = 'https://ai.dgrzone.com/api';
if (token === undefined) token = '';
if (systemPrompt === undefined) systemPrompt = 'You are a helpful assistant.';
if (maxTokens === undefined) maxTokens = 512;
if (temperature === undefined) temperature = 0.7;
// Internal State
let ae_promises: any = $state(null);
let show_modal = $state(false);
let active_tab: 'result' | 'settings' = $state('result');
let tmp_summary = $state('');
async function generate_ai_result() {
if (!content) {
alert('No content available to analyze.');
return;
}
let {
content,
summary = $bindable(),
model = $bindable(),
baseUrl = $bindable(),
token = $bindable(),
systemPrompt = $bindable(),
maxTokens = $bindable(),
temperature = $bindable(),
onSave,
onSyncConfig,
buttonClass = "btn btn-sm preset-tonal-primary shadow-lg hover:scale-105 transition-all",
log_lvl = 0
}: Props = $props();
active_tab = 'result';
// Apply defaults if undefined (Safe for Svelte 5 Runes)
if (model === undefined) model = 'dgrzone-deepseek-8b-quick';
if (baseUrl === undefined) baseUrl = 'https://ai.dgrzone.com/api';
if (token === undefined) token = '';
if (systemPrompt === undefined) systemPrompt = 'You are a helpful assistant.';
if (maxTokens === undefined) maxTokens = 512;
if (temperature === undefined) temperature = 0.7;
// Internal State
let ae_promises: any = $state(null);
let show_modal = $state(false);
let active_tab: 'result' | 'settings' = $state('result');
let tmp_summary = $state('');
async function generate_ai_result() {
if (!content) {
alert('No content available to analyze.');
return;
}
active_tab = 'result';
// If no token is provided, trigger a "Demo Mode" placeholder after a fake delay
if (!token || token === '') {
console.log('AE_AITools: No token provided. Entering Demo Mode.');
ae_promises = new Promise((resolve) => {
setTimeout(() => {
tmp_summary = `### AI Summary (DEMO MODE)\n\nThis is a placeholder summary because no API token was provided in the settings. \n\n**Original Content Length:** ${content.length} characters.\n\n**System Prompt:** ${systemPrompt}\n\n**Model:** ${model}`;
show_modal = true;
resolve(true);
}, 1500);
});
return;
}
const ai_client = new OpenAI({
apiKey: token,
baseURL: baseUrl,
dangerouslyAllowBrowser: true
// If no token is provided, trigger a "Demo Mode" placeholder after a fake delay
if (!token || token === '') {
console.log('AE_AITools: No token provided. Entering Demo Mode.');
ae_promises = new Promise((resolve) => {
setTimeout(() => {
tmp_summary = `### AI Summary (DEMO MODE)\n\nThis is a placeholder summary because no API token was provided in the settings. \n\n**Original Content Length:** ${content.length} characters.\n\n**System Prompt:** ${systemPrompt}\n\n**Model:** ${model}`;
show_modal = true;
resolve(true);
}, 1500);
});
return;
}
try {
ae_promises = ai_client.chat.completions.create({
const ai_client = new OpenAI({
apiKey: token,
baseURL: baseUrl,
dangerouslyAllowBrowser: true
});
try {
ae_promises = ai_client.chat.completions
.create({
model: model || 'dgrzone-deepseek-8b-quick',
max_tokens: maxTokens,
temperature: temperature,
messages: [
{ role: 'system', content: systemPrompt || 'You are a helpful assistant.' },
{
role: 'system',
content: systemPrompt || 'You are a helpful assistant.'
},
{ role: 'user', content: content }
]
}).then((resp) => {
const result = resp?.choices?.[0]?.message?.content || 'No result generated.';
})
.then((resp) => {
const result =
resp?.choices?.[0]?.message?.content ||
'No result generated.';
tmp_summary = result;
show_modal = true;
});
} catch (err: any) {
console.error('AE_AITools: AI Error:', err);
// Even on error, show the modal with the error message so the UI can be inspected
tmp_summary = `### AI Error\n\nFailed to connect to the AI service.\n\n**Error:** ${err.message}\n\nCheck your Settings tab for Base URL and Token configuration.`;
show_modal = true;
ae_promises = Promise.resolve();
}
} catch (err: any) {
console.error('AE_AITools: AI Error:', err);
// Even on error, show the modal with the error message so the UI can be inspected
tmp_summary = `### AI Error\n\nFailed to connect to the AI service.\n\n**Error:** ${err.message}\n\nCheck your Settings tab for Base URL and Token configuration.`;
show_modal = true;
ae_promises = Promise.resolve();
}
}
function handle_save() {
summary = tmp_summary;
if (onSave) onSave(tmp_summary);
show_modal = false;
}
function handle_save() {
summary = tmp_summary;
if (onSave) onSave(tmp_summary);
show_modal = false;
}
</script>
<div class="ae-ai-tools-wrapper inline-flex items-center gap-1">
@@ -128,13 +143,12 @@
type="button"
onclick={generate_ai_result}
class={buttonClass}
title="Generate AI summary/analysis"
>
title="Generate AI summary/analysis">
{#await ae_promises}
<Loader class="inline-block mr-1 animate-spin" size="1.2em" />
<Loader class="mr-1 inline-block animate-spin" size="1.2em" />
<span class="text-sm">Processing...</span>
{:then}
<BotMessageSquare class="inline-block mr-1" size="1.2em" />
<BotMessageSquare class="mr-1 inline-block" size="1.2em" />
<span class="text-sm">Summarize</span>
{:catch}
<span class="text-sm text-red-500">Error</span>
@@ -149,8 +163,7 @@
show_modal = true;
}}
class="btn btn-sm variant-soft-surface shadow-md"
title="AI Settings"
>
title="AI Settings">
<Settings size="1.2em" />
</button>
@@ -160,32 +173,37 @@
title="Aether AI Assistant"
bind:open={show_modal}
size="lg"
class="bg-white dark:bg-gray-800"
>
class="bg-white dark:bg-gray-800">
<div class="space-y-4 p-2">
<!-- Tab Navigation -->
<div class="flex gap-1 border-b border-surface-500/20 pb-2">
<button
class="btn btn-sm {active_tab === 'result' ? 'variant-filled-primary' : 'variant-soft-surface'}"
onclick={() => active_tab = 'result'}
>
<div class="border-surface-500/20 flex gap-1 border-b pb-2">
<button
class="btn btn-sm {active_tab === 'result'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'result')}>
<Bot size="1.1em" class="mr-1" /> Result
</button>
<button
class="btn btn-sm {active_tab === 'settings' ? 'variant-filled-secondary' : 'variant-soft-surface'}"
onclick={() => active_tab = 'settings'}
>
<button
class="btn btn-sm {active_tab === 'settings'
? 'variant-filled-secondary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'settings')}>
<Settings size="1.1em" class="mr-1" /> Settings
</button>
</div>
{#if active_tab === 'result'}
<div class="space-y-4 animate-in fade-in duration-200">
<div class="flex gap-2 justify-start">
<button class="btn btn-sm variant-filled-success" onclick={handle_save}>
<div class="animate-in fade-in space-y-4 duration-200">
<div class="flex justify-start gap-2">
<button
class="btn btn-sm variant-filled-success"
onclick={handle_save}>
<Save size="1.1em" class="mr-1" /> Save Result
</button>
<button class="btn btn-sm variant-ghost-primary" onclick={generate_ai_result}>
<button
class="btn btn-sm variant-ghost-primary"
onclick={generate_ai_result}>
<RotateCcw size="1.1em" class="mr-1" /> Re-run
</button>
</div>
@@ -195,57 +213,84 @@
bind:new_content={tmp_summary}
theme_mode={$ae_loc.theme_mode}
placeholder="AI Result will appear here..."
class_li="p-2 border rounded-lg h-96 shadow-inner bg-surface-500/5"
/>
class_li="p-2 border rounded-lg h-96 shadow-inner bg-surface-500/5" />
</div>
{:else}
<div class="space-y-6 animate-in slide-in-from-left-4 duration-200">
<div
class="animate-in slide-in-from-left-4 space-y-6 duration-200">
<!-- Connection Settings -->
<div class="space-y-4">
<h3 class="text-sm font-bold uppercase tracking-widest text-surface-500 flex items-center gap-2">
<h3
class="text-surface-500 flex items-center gap-2 text-sm font-bold tracking-widest uppercase">
<Globe size="1.1em" /> API Connection
</h3>
{#if onSyncConfig}
<button class="btn btn-sm variant-soft-primary" onclick={onSyncConfig}>
<Copy size="1.1em" class="mr-1" /> Sync Global Defaults
<button
class="btn btn-sm variant-soft-primary"
onclick={onSyncConfig}>
<Copy size="1.1em" class="mr-1" /> Sync Global
Defaults
</button>
{/if}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span>Base URL</span>
<input type="text" bind:value={baseUrl} class="input input-sm" />
<input
type="text"
bind:value={baseUrl}
class="input input-sm" />
</label>
<label class="label">
<span>Model</span>
<input type="text" bind:value={model} class="input input-sm" />
<input
type="text"
bind:value={model}
class="input input-sm" />
</label>
</div>
<label class="label">
<span>API Token</span>
<input type="password" bind:value={token} class="input input-sm font-mono" />
<input
type="password"
bind:value={token}
class="input input-sm font-mono" />
</label>
</div>
<!-- Model Parameters -->
<div class="space-y-4 pt-4 border-t border-surface-500/10">
<h3 class="text-sm font-bold uppercase tracking-widest text-surface-500 flex items-center gap-2">
<div
class="border-surface-500/10 space-y-4 border-t pt-4">
<h3
class="text-surface-500 flex items-center gap-2 text-sm font-bold tracking-widest uppercase">
<FilePenLine size="1.1em" /> Inference Parameters
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span>Temperature ({temperature})</span>
<input type="range" bind:value={temperature} min="0" max="1" step="0.1" class="range" />
<input
type="range"
bind:value={temperature}
min="0"
max="1"
step="0.1"
class="range" />
</label>
<label class="label">
<span>Max Tokens</span>
<input type="number" bind:value={maxTokens} class="input input-sm" />
<input
type="number"
bind:value={maxTokens}
class="input input-sm" />
</label>
</div>
<label class="label">
<span>System Prompt</span>
<textarea bind:value={systemPrompt} class="textarea h-24 text-xs font-mono"></textarea>
<textarea
bind:value={systemPrompt}
class="textarea h-24 font-mono text-xs"
></textarea>
</label>
</div>
</div>
@@ -253,4 +298,4 @@
</div>
</Modal>
{/if}
</div>
</div>

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,58 +1,64 @@
<script lang="ts">
/**
* AE_ObjectFlags.svelte
* GENERIC Aether Object Flags & Visibility Toggles
* Manages: alert, private, public, personal, professional, template
*/
import {
Siren, MessageSquareWarning, Fingerprint,
Globe, BookHeart, BriefcaseBusiness, NotepadTextDashed,
Settings
} from '@lucide/svelte';
import { ae_loc } from '$lib/stores/ae_stores';
/**
* AE_ObjectFlags.svelte
* GENERIC Aether Object Flags & Visibility Toggles
* Manages: alert, private, public, personal, professional, template
*/
import {
Siren,
MessageSquareWarning,
Fingerprint,
Globe,
BookHeart,
BriefcaseBusiness,
NotepadTextDashed,
Settings
} from '@lucide/svelte';
import { ae_loc } from '$lib/stores/ae_stores';
interface Props {
// The object containing the flags (bindable)
obj: any;
interface Props {
// The object containing the flags (bindable)
obj: any;
// Visibility configuration (optional overrides)
show_labels?: boolean;
hide_alert?: boolean;
hide_private?: boolean;
hide_public?: boolean;
hide_personal?: boolean;
hide_professional?: boolean;
hide_template?: boolean;
// Visibility configuration (optional overrides)
show_labels?: boolean;
hide_alert?: boolean;
hide_private?: boolean;
hide_public?: boolean;
hide_personal?: boolean;
hide_professional?: boolean;
hide_template?: boolean;
// Callbacks
on_toggle?: (prop: string, newValue: boolean) => void;
// Callbacks
on_toggle?: (prop: string, newValue: boolean) => void;
// Styling
container_class?: string;
}
// Styling
container_class?: string;
}
let {
obj = $bindable(),
show_labels = true,
hide_alert: hide_alert = false,
hide_private: hide_private = false,
hide_public: hide_public = false,
hide_personal: hide_personal = false,
hide_professional: hide_professional = false,
hide_template: hide_template = false,
on_toggle: onToggle,
container_class = "flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10"
}: Props = $props();
let {
obj = $bindable(),
show_labels = true,
hide_alert: hide_alert = false,
hide_private: hide_private = false,
hide_public: hide_public = false,
hide_personal: hide_personal = false,
hide_professional: hide_professional = false,
hide_template: hide_template = false,
on_toggle: onToggle,
container_class = 'flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10'
}: Props = $props();
function handle_toggle(prop: string) {
obj[prop] = !obj[prop];
if (onToggle) onToggle(prop, obj[prop]);
}
function handle_toggle(prop: string) {
obj[prop] = !obj[prop];
if (onToggle) onToggle(prop, obj[prop]);
}
</script>
<div class={container_class}>
{#if show_labels}
<span class="text-xs text-surface-500 flex items-center gap-1 uppercase font-bold tracking-wider mr-2">
<span
class="text-surface-500 mr-2 flex items-center gap-1 text-xs font-bold tracking-wider uppercase">
<Settings size="1.1em" /> Flags:
</span>
{/if}
@@ -63,9 +69,10 @@
type="button"
onclick={() => handle_toggle('alert')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Alert Status"
>
<Siren size="1.2em" class={obj?.alert ? 'text-error-500' : 'opacity-40'} />
title="Toggle Alert Status">
<Siren
size="1.2em"
class={obj?.alert ? 'text-error-500' : 'opacity-40'} />
</button>
{/if}
@@ -75,9 +82,10 @@
type="button"
onclick={() => handle_toggle('private')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Private/Encrypted"
>
<Fingerprint size="1.2em" class={obj?.private ? 'text-success-500' : 'opacity-40'} />
title="Toggle Private/Encrypted">
<Fingerprint
size="1.2em"
class={obj?.private ? 'text-success-500' : 'opacity-40'} />
</button>
{/if}
@@ -87,9 +95,10 @@
type="button"
onclick={() => handle_toggle('public')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Public Visibility"
>
<Globe size="1.2em" class={obj?.public ? 'text-success-500' : 'opacity-40'} />
title="Toggle Public Visibility">
<Globe
size="1.2em"
class={obj?.public ? 'text-success-500' : 'opacity-40'} />
</button>
{/if}
@@ -99,9 +108,10 @@
type="button"
onclick={() => handle_toggle('personal')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Personal Scope"
>
<BookHeart size="1.2em" class={obj?.personal ? 'text-success-500' : 'opacity-40'} />
title="Toggle Personal Scope">
<BookHeart
size="1.2em"
class={obj?.personal ? 'text-success-500' : 'opacity-40'} />
</button>
{/if}
@@ -111,9 +121,10 @@
type="button"
onclick={() => handle_toggle('professional')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Professional Scope"
>
<BriefcaseBusiness size="1.2em" class={obj?.professional ? 'text-success-500' : 'opacity-40'} />
title="Toggle Professional Scope">
<BriefcaseBusiness
size="1.2em"
class={obj?.professional ? 'text-success-500' : 'opacity-40'} />
</button>
{/if}
@@ -123,9 +134,10 @@
type="button"
onclick={() => handle_toggle('template')}
class="btn-icon btn-icon-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition"
title="Toggle Template Mode"
>
<NotepadTextDashed size="1.2em" class={obj?.template ? 'text-success-500' : 'opacity-40'} />
title="Toggle Template Mode">
<NotepadTextDashed
size="1.2em"
class={obj?.template ? 'text-success-500' : 'opacity-40'} />
</button>
{/if}
</div>

View File

@@ -1,95 +1,96 @@
<script lang="ts">
/**
* AE_Record_Controls.svelte
* GENERIC Aether Record Management Controls
* Manages: priority, hide, enable, alert, delete/disable
*
* Emits events — NO API calls. Parent is responsible for:
* 1. Calling the API (update_ae_obj_v3, delete_ae_obj_id__*)
* 2. Refreshing the object from cache/API
* 3. Navigating away on delete
*
* Usage:
* <AE_Record_Controls
* obj={$lq__event_session_obj}
* obj_label="session"
* allow_delete={$ae_loc.manager_access}
* allow_disable={$ae_loc.administrator_access}
* on_toggle={(field, val) => { ... }}
* on_delete={(method) => { ... }}
* />
*/
import {
Star,
Eye,
EyeOff,
ToggleLeft,
ToggleRight,
Bell,
BellOff,
Trash2,
CircleMinus,
Settings
} from '@lucide/svelte';
/**
* AE_Record_Controls.svelte
* GENERIC Aether Record Management Controls
* Manages: priority, hide, enable, alert, delete/disable
*
* Emits events — NO API calls. Parent is responsible for:
* 1. Calling the API (update_ae_obj, delete_ae_obj_id__*)
* 2. Refreshing the object from cache/API
* 3. Navigating away on delete
*
* Usage:
* <AE_Record_Controls
* obj={$lq__event_session_obj}
* obj_label="session"
* allow_delete={$ae_loc.manager_access}
* allow_disable={$ae_loc.administrator_access}
* on_toggle={(field, val) => { ... }}
* on_delete={(method) => { ... }}
* />
*/
import {
Star,
Eye,
EyeOff,
ToggleLeft,
ToggleRight,
Bell,
BellOff,
Trash2,
CircleMinus,
Settings
} from '@lucide/svelte';
interface Props {
// The object whose flags are being displayed (read-only — parent owns state)
obj: any;
interface Props {
// The object whose flags are being displayed (read-only — parent owns state)
obj: any;
// Human-readable label for confirm dialogs ("session", "presenter", "location", etc.)
obj_label?: string;
// Human-readable label for confirm dialogs ("session", "presenter", "location", etc.)
obj_label?: string;
// Visibility — hide any control that doesn't apply for this object type
show_alert?: boolean;
show_priority?: boolean;
show_enable?: boolean;
show_hide?: boolean;
show_labels?: boolean;
// Visibility — hide any control that doesn't apply for this object type
show_alert?: boolean;
show_priority?: boolean;
show_enable?: boolean;
show_hide?: boolean;
show_labels?: boolean;
// Permission gates — parent passes booleans derived from $ae_loc
allow_delete?: boolean; // Hard permanent delete (manager+)
allow_disable?: boolean; // Soft disable/remove (administrator+)
// Permission gates — parent passes booleans derived from $ae_loc
allow_delete?: boolean; // Hard permanent delete (manager+)
allow_disable?: boolean; // Soft disable/remove (administrator+)
// Callbacks — parent handles API + refresh + navigation
on_toggle?: (field: string, new_val: boolean) => void;
on_delete?: (method: 'delete' | 'disable') => void;
// Callbacks — parent handles API + refresh + navigation
on_toggle?: (field: string, new_val: boolean) => void;
on_delete?: (method: 'delete' | 'disable') => void;
// Styling
container_class?: string;
}
// Styling
container_class?: string;
}
let {
obj,
obj_label = 'record',
show_alert = true,
show_priority = true,
show_enable = true,
show_hide = true,
show_labels = true,
allow_delete = false,
allow_disable = false,
on_toggle,
on_delete,
container_class = 'flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10'
}: Props = $props();
let {
obj,
obj_label = 'record',
show_alert = true,
show_priority = true,
show_enable = true,
show_hide = true,
show_labels = true,
allow_delete = false,
allow_disable = false,
on_toggle,
on_delete,
container_class = 'flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10'
}: Props = $props();
function toggle(field: string) {
if (on_toggle) on_toggle(field, !obj?.[field]);
}
function toggle(field: string) {
if (on_toggle) on_toggle(field, !obj?.[field]);
}
function handle_delete(method: 'delete' | 'disable') {
const msg =
method === 'delete'
? `Permanently delete this ${obj_label}? This cannot be undone.`
: `Remove (disable) this ${obj_label}?`;
if (!confirm(msg)) return;
if (on_delete) on_delete(method);
}
function handle_delete(method: 'delete' | 'disable') {
const msg =
method === 'delete'
? `Permanently delete this ${obj_label}? This cannot be undone.`
: `Remove (disable) this ${obj_label}?`;
if (!confirm(msg)) return;
if (on_delete) on_delete(method);
}
</script>
<div class={container_class}>
{#if show_labels}
<span class="text-xs text-surface-500 flex items-center gap-1 uppercase font-bold tracking-wider mr-2">
<span
class="text-surface-500 mr-2 flex items-center gap-1 text-xs font-bold tracking-wider uppercase">
<Settings size="1.1em" /> Controls:
</span>
{/if}
@@ -103,12 +104,10 @@
class:preset-filled-warning-500={obj?.priority}
class:preset-tonal-secondary={!obj?.priority}
class:hover:preset-filled-warning-500={!obj?.priority}
title={obj?.priority ? 'Remove priority flag' : 'Mark as priority'}
>
title={obj?.priority ? 'Remove priority flag' : 'Mark as priority'}>
<Star
size="1.2em"
class={obj?.priority ? 'fill-current' : 'opacity-50'}
/>
class={obj?.priority ? 'fill-current' : 'opacity-50'} />
</button>
{/if}
@@ -121,8 +120,7 @@
class:preset-filled-warning-500={obj?.hide}
class:preset-tonal-secondary={!obj?.hide}
class:hover:preset-filled-warning-500={!obj?.hide}
title={obj?.hide ? 'Unhide this record' : 'Hide this record'}
>
title={obj?.hide ? 'Unhide this record' : 'Hide this record'}>
{#if obj?.hide}
<EyeOff size="1.2em" class="text-warning-500" />
{:else}
@@ -140,8 +138,7 @@
class:preset-filled-success-500={obj?.enable}
class:preset-filled-error-500={!obj?.enable}
class:hover:preset-filled-success-500={!obj?.enable}
title={obj?.enable ? 'Disable this record' : 'Enable this record'}
>
title={obj?.enable ? 'Disable this record' : 'Enable this record'}>
{#if obj?.enable}
<ToggleRight size="1.2em" class="text-success-300" />
{:else}
@@ -159,8 +156,7 @@
class:preset-filled-error-500={obj?.alert}
class:preset-tonal-secondary={!obj?.alert}
class:hover:preset-filled-error-500={!obj?.alert}
title={obj?.alert ? 'Remove alert status' : 'Mark as alert'}
>
title={obj?.alert ? 'Remove alert status' : 'Mark as alert'}>
{#if obj?.alert}
<Bell size="1.2em" class="text-error-300" />
{:else}
@@ -175,8 +171,7 @@
type="button"
onclick={() => handle_delete('delete')}
class="btn-icon btn-icon-sm preset-filled-error-500 hover:preset-filled-error-600 transition"
title="Permanently delete this {obj_label}"
>
title="Permanently delete this {obj_label}">
<Trash2 size="1.2em" />
</button>
{:else if allow_disable}
@@ -184,8 +179,7 @@
type="button"
onclick={() => handle_delete('disable')}
class="btn-icon btn-icon-sm preset-filled-warning-500 hover:preset-filled-warning-600 transition"
title="Disable / soft-remove this {obj_label}"
>
title="Disable / soft-remove this {obj_label}">
<CircleMinus size="1.2em" />
</button>
{/if}

View File

@@ -1,6 +1,5 @@
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { get_ae_obj_li_for_obj_id_crud_v2 } from '$lib/ae_api/api_get__crud_obj_li_v2';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { db_events } from '$lib/ae_events/db_events';
@@ -47,7 +46,9 @@ export async function load_ae_obj_id__event({
log_lvl?: number;
}): Promise<ae_Event | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event() *** event_id=${event_id} (SWR Optimization)`);
console.log(
`*** load_ae_obj_id__event() *** event_id=${event_id} (SWR Optimization)`
);
}
// Hierarchy Enforcement: Pulling presentations/presenters requires pulling sessions first
@@ -58,20 +59,43 @@ export async function load_ae_obj_id__event({
try {
const cached_event = await db_events.event.get(event_id);
if (cached_event) {
if (log_lvl) console.log('EVENT LOAD: Cache hit. Returning stale data immediately.');
if (log_lvl)
console.log(
'EVENT LOAD: Cache hit. Returning stale data immediately.'
);
// Trigger background refresh
_refresh_event_v3_background({
api_cfg, event_id, view, try_cache,
inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li,
enabled, hidden,
_refresh_event_background({
api_cfg,
event_id,
view,
try_cache,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
log_lvl: 0
});
// Still handle nested loads for the cached version to ensure UI richness
return await _handle_nested_loads(cached_event, {
api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li,
enabled, hidden, try_cache, log_lvl
api_cfg,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
try_cache,
log_lvl
});
}
} catch (e) {
@@ -80,10 +104,20 @@ export async function load_ae_obj_id__event({
}
// 2. SLOW PATH: Wait for API if cache is empty or try_cache is false
return await _refresh_event_v3_background({
api_cfg, event_id, view, try_cache,
inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li,
enabled, hidden,
return await _refresh_event_background({
api_cfg,
event_id,
view,
try_cache,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
log_lvl
});
}
@@ -91,10 +125,20 @@ export async function load_ae_obj_id__event({
/**
* Internal helper to perform the actual API fetch and cache update for events
*/
async function _refresh_event_v3_background({
api_cfg, event_id, view, try_cache,
inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li,
enabled, hidden,
async function _refresh_event_background({
api_cfg,
event_id,
view,
try_cache,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
log_lvl
}: any) {
// Check if offline
@@ -104,7 +148,7 @@ async function _refresh_event_v3_background({
}
try {
const result = await api.get_ae_obj_v3({
const result = await api.get_ae_obj({
api_cfg: api_cfg,
obj_type: 'event',
obj_id: event_id,
@@ -130,8 +174,18 @@ async function _refresh_event_v3_background({
}
return await _handle_nested_loads(processed_obj, {
api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li,
enabled, hidden, try_cache: false, log_lvl
api_cfg,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
try_cache: false,
log_lvl
});
}
} catch (error: any) {
@@ -143,8 +197,27 @@ async function _refresh_event_v3_background({
/**
* Shared logic for loading nested child collections
*/
async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_template_li, enabled, hidden, try_cache, log_lvl }: any) {
if (log_lvl) console.log(`Loading nested collections for event: ${event_obj.event_id} (Devices: ${inc_device_li}, Files: ${inc_file_li}, Locations: ${inc_location_li}, Sessions: ${inc_session_li}, Presentations: ${inc_presentation_li}, Presenters: ${inc_presenter_li}, Templates: ${inc_template_li})`);
async function _handle_nested_loads(
event_obj: any,
{
api_cfg,
inc_device_li,
inc_file_li,
inc_location_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_template_li,
enabled,
hidden,
try_cache,
log_lvl
}: any
) {
if (log_lvl)
console.log(
`Loading nested collections for event: ${event_obj.event_id} (Devices: ${inc_device_li}, Files: ${inc_file_li}, Locations: ${inc_location_li}, Sessions: ${inc_session_li}, Presentations: ${inc_presentation_li}, Presenters: ${inc_presenter_li}, Templates: ${inc_template_li})`
);
// String-Only ID Vision: the '_id' field IS the string ID
const current_event_id = event_obj.id || event_obj.event_id;
@@ -152,56 +225,66 @@ async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, in
const tasks = [];
if (inc_device_li) {
tasks.push(load_ae_obj_li__event_device({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
try_cache,
log_lvl
}).then(res => event_obj.event_device_obj_li = res));
tasks.push(
load_ae_obj_li__event_device({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
try_cache,
log_lvl
}).then((res) => (event_obj.event_device_obj_li = res))
);
}
if (inc_file_li) {
tasks.push(load_ae_obj_li__event_file({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
enabled: 'all',
limit: 100,
try_cache,
log_lvl
}).then(res => event_obj.event_file_li = res));
tasks.push(
load_ae_obj_li__event_file({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
enabled: 'all',
limit: 100,
try_cache,
log_lvl
}).then((res) => (event_obj.event_file_li = res))
);
}
if (inc_location_li) {
tasks.push(load_ae_obj_li__event_location({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
enabled,
hidden,
try_cache,
log_lvl
}).then(res => event_obj.event_location_obj_li = res));
tasks.push(
load_ae_obj_li__event_location({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
enabled,
hidden,
try_cache,
log_lvl
}).then((res) => (event_obj.event_location_obj_li = res))
);
}
if (inc_session_li) {
tasks.push(load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
try_cache,
log_lvl
}).then(res => event_obj.event_session_obj_li = res));
tasks.push(
load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
try_cache,
log_lvl
}).then((res) => (event_obj.event_session_obj_li = res))
);
}
if (inc_template_li) {
tasks.push(load_ae_obj_li__event_badge_template({
api_cfg,
event_id: current_event_id,
try_cache,
log_lvl
}).then(res => event_obj.event_badge_template_obj_li = res));
tasks.push(
load_ae_obj_li__event_badge_template({
api_cfg,
event_id: current_event_id,
try_cache,
log_lvl
}).then((res) => (event_obj.event_badge_template_obj_li = res))
);
}
if (tasks.length > 0) await Promise.all(tasks);
@@ -239,20 +322,25 @@ export async function load_ae_obj_li__event({
inc_presenter_li?: boolean;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | Record<string, 'ASC' | 'DESC'>[];
order_by_li?:
| Record<string, 'ASC' | 'DESC'>
| Record<string, 'ASC' | 'DESC'>[];
params?: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Event[]> {
// Hierarchy Enforcement: Pulling presentations/presenters requires pulling sessions first
if (inc_presenter_li || inc_presentation_li) inc_session_li = true;
// Check if offline
if (typeof navigator !== 'undefined' && !navigator.onLine) {
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
if (log_lvl)
console.log(
'Browser is offline. Skipping API and attempting cache load.'
);
ae_promises.load__event_obj_li = await db_events.event
.where('account_id').equals(for_obj_id)
.where('account_id')
.equals(for_obj_id)
.toArray();
return ae_promises.load__event_obj_li || [];
}
@@ -265,10 +353,14 @@ export async function load_ae_obj_li__event({
};
if (for_obj_id) {
search_query.and.push({ field: `${for_obj_type}_id`, op: 'eq', value: for_obj_id });
search_query.and.push({
field: `${for_obj_type}_id`,
op: 'eq',
value: for_obj_id
});
}
promise = api.search_ae_obj_v3({
promise = api.search_ae_obj({
api_cfg,
obj_type: 'event',
headers: { 'x-account-id': for_obj_id },
@@ -282,7 +374,7 @@ export async function load_ae_obj_li__event({
log_lvl
});
} else {
promise = api.get_ae_obj_li_v3({
promise = api.get_ae_obj_li({
api_cfg,
obj_type: 'event',
for_obj_type,
@@ -320,9 +412,11 @@ export async function load_ae_obj_li__event({
} else {
console.log('No results returned from API.');
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache...');
if (log_lvl)
console.log('Attempting to load from local cache...');
ae_promises.load__event_obj_li = await db_events.event
.where('account_id').equals(for_obj_id)
.where('account_id')
.equals(for_obj_id)
.toArray();
} else {
ae_promises.load__event_obj_li = [];
@@ -331,9 +425,13 @@ export async function load_ae_obj_li__event({
} catch (error: any) {
console.log('API request failed.', error);
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache after error...');
if (log_lvl)
console.log(
'Attempting to load from local cache after error...'
);
ae_promises.load__event_obj_li = await db_events.event
.where('account_id').equals(for_obj_id)
.where('account_id')
.equals(for_obj_id)
.toArray();
} else {
ae_promises.load__event_obj_li = [];
@@ -341,18 +439,20 @@ export async function load_ae_obj_li__event({
}
if (inc_session_li && ae_promises.load__event_obj_li) {
const session_tasks = ae_promises.load__event_obj_li.map((event_obj: any) => {
const current_event_id = event_obj.id || event_obj.event_id;
return load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
inc_presentation_li,
inc_presenter_li,
try_cache,
log_lvl
}).then((res) => (event_obj.event_session_obj_li = res));
});
const session_tasks = ae_promises.load__event_obj_li.map(
(event_obj: any) => {
const current_event_id = event_obj.id || event_obj.event_id;
return load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event',
for_obj_id: current_event_id,
inc_presentation_li,
inc_presenter_li,
try_cache,
log_lvl
}).then((res) => (event_obj.event_session_obj_li = res));
}
);
await Promise.all(session_tasks);
}
@@ -375,7 +475,7 @@ export async function create_ae_obj__event({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Event | null> {
const result = await api.create_ae_obj_v3({
const result = await api.create_ae_obj({
api_cfg,
obj_type: 'event',
fields: {
@@ -424,7 +524,7 @@ export async function delete_ae_obj_id__event({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'event',
obj_id: event_id,
@@ -456,7 +556,7 @@ export async function update_ae_obj__event({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Event | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'event',
obj_id: event_id,
@@ -490,10 +590,10 @@ export async function update_ae_obj__event({
// Updated 2026-01-21
/**
* Unified Search for Events (V3 API)
*
*
* STRATEGY: Hybrid Search/Filter
* 1. Server-side (V3 Search): Used for text search (qry_str) to reduce payload.
* 2. Client-side (Filter Layer): Handles all other filters (Type, Location, Person)
* 2. Client-side (Filter Layer): Handles all other filters (Type, Location, Person)
* to ensure correct inclusive OR logic and stable ID matching.
*/
export async function search__event({
@@ -544,21 +644,29 @@ export async function search__event({
};
const params: key_val = {};
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${qry_str.trim()}%` });
params['lk_qry'] = { 'default_qry_str': qry_str.trim() };
search_query.and.push({
field: 'default_qry_str',
op: 'like',
value: `%${qry_str.trim()}%`
});
params['lk_qry'] = { default_qry_str: qry_str.trim() };
if (for_obj_id) {
// V3 Standard: Use random string ID for body filters.
// V3 Standard: Use random string ID for body filters.
// The API resolves this to the integer column automatically.
search_query.and.push({ field: `${for_obj_type}_id_random`, op: 'eq', value: for_obj_id });
search_query.and.push({
field: `${for_obj_type}_id_random`,
op: 'eq',
value: for_obj_id
});
}
// NOTE: We do NOT push 'physical' and 'virtual' to the server-side query here.
// The V3 Search API uses AND logic for the body, which would exclude
// The V3 Search API uses AND logic for the body, which would exclude
// meetings that are only physical or only virtual if both filters are active.
// We handle this in the Client-side Filter Layer below for correct OR logic.
result_li = await api.search_ae_obj_v3({
result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event',
headers: { 'x-account-id': for_obj_id },
@@ -574,7 +682,7 @@ export async function search__event({
});
} else {
// Option B: List All
result_li = await api.get_ae_obj_li_v3({
result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event',
for_obj_type,
@@ -594,7 +702,11 @@ export async function search__event({
let valid_result_li: ae_Event[] = [];
if (Array.isArray(result_li)) {
valid_result_li = result_li;
} else if (result_li && typeof result_li === 'object' && Array.isArray((result_li as any).data)) {
} else if (
result_li &&
typeof result_li === 'object' &&
Array.isArray((result_li as any).data)
) {
valid_result_li = (result_li as any).data;
} else {
return [];
@@ -642,13 +754,12 @@ export async function search__event({
// Handle person ID filter
if (qry_person_id) {
const match = (
const match =
ev.external_person_id === qry_person_id ||
ev.poc_person_id === qry_person_id ||
ev.poc_person_id_random === qry_person_id ||
ev.poc_event_person_id === qry_person_id ||
ev.poc_event_person_id_random === qry_person_id
);
ev.poc_event_person_id_random === qry_person_id;
if (!match) return false;
}
@@ -656,7 +767,9 @@ export async function search__event({
});
if (log_lvl) {
console.log(`Filter results (Hybrid): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}`);
console.log(
`Filter results (Hybrid): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}`
);
}
return filtered_obj_li.slice(0, limit);
@@ -664,139 +777,6 @@ export async function search__event({
export const qry_ae_obj_li__event = search__event;
/**
* Specialized search function for IDAA module using legacy V2 endpoints.
* This is isolated to prevent V3 migration bugs from affecting Recovery Meetings.
*/
// Updated 2026-01-20
export async function qry_ae_obj_li__event_v2({
api_cfg,
for_obj_type = 'account',
for_obj_id,
qry_str,
qry_person_id = null,
qry_conference = null,
qry_physical = null,
qry_virtual = null,
qry_type = null,
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 99,
offset = 0,
order_by_li = { start_datetime: 'DESC' } as const,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
for_obj_type?: string;
for_obj_id: string;
qry_str?: string;
qry_person_id?: string | null;
qry_conference?: boolean | null;
qry_physical?: boolean | null;
qry_virtual?: boolean | null;
qry_type?: string | null;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'>;
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** qry_ae_obj_li__event_v2() ***');
const params_json: any = { qry: { and: [] } };
if (qry_str) {
// Use default_qry_str for searching as requested
params_json.qry.and.push({ field: 'default_qry_str', op: 'like', value: `%${qry_str}%` });
}
const result_li = await get_ae_obj_li_for_obj_id_crud_v2({
api_cfg,
obj_type: 'event',
for_obj_type,
for_obj_id,
enabled,
hidden,
limit,
offset,
order_by_li,
params_json,
log_lvl
});
if (!result_li) return [];
const processed_obj_li = await process_ae_obj__event_props({
obj_li: result_li,
log_lvl: log_lvl
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'event',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl: log_lvl
});
}
// Client-side Filter Layer
const filtered_obj_li = processed_obj_li.filter((ev: any) => {
// Handle conference filter
if (qry_conference != null) {
const ev_conf = ev.conference === true || ev.conference === 1 || ev.conference === '1';
if (ev_conf !== !!qry_conference) return false;
}
// Location Filtering (Inclusive OR logic)
// If either filter is explicitly true, we restrict results.
// If both are false or null, we show everything.
if (qry_physical === true || qry_virtual === true) {
const ev_physical = ev.physical === true || ev.physical === 1 || ev.physical === '1';
const ev_virtual = ev.virtual === true || ev.virtual === 1 || ev.virtual === '1';
let match = false;
if (qry_physical === true && ev_physical) match = true;
if (qry_virtual === true && ev_virtual) match = true;
if (!match) return false;
}
// Handle type filter (skip if null, undefined, 'all', or empty string)
if (qry_type != null && qry_type !== 'all' && qry_type !== '') {
if (ev.type !== qry_type) return false;
}
// Handle person ID filter
if (qry_person_id) {
const match = (
ev.external_person_id === qry_person_id ||
ev.poc_person_id === qry_person_id ||
ev.poc_person_id_random === qry_person_id ||
ev.poc_event_person_id === qry_person_id ||
ev.poc_event_person_id_random === qry_person_id
);
if (!match) return false;
}
return true;
});
if (log_lvl) {
console.log(`Filter results (V2): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}`);
}
return filtered_obj_li.slice(0, limit);
}
// Updated 2026-03-10
export const properties_to_save = [
'id',
@@ -913,14 +893,21 @@ async function _process_generic_props<T extends Record<string, any>>({
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -982,7 +969,8 @@ export function sync_config__event_pres_mgmt({
pres_mgmt_cfg_remote?.label__session_poc_name_short ?? 'POC';
pres_mgmt_cfg_local.label__session_poc_name =
pres_mgmt_cfg_remote?.label__session_poc_name ?? 'Point of Contact';
pres_mgmt_cfg_local.hide__session_poc = pres_mgmt_cfg_remote?.hide__session_poc ?? false;
pres_mgmt_cfg_local.hide__session_poc =
pres_mgmt_cfg_remote?.hide__session_poc ?? false;
pres_mgmt_cfg_local.require__presenter_agree =
pres_mgmt_cfg_remote?.require__presenter_agree ?? false;
pres_mgmt_cfg_local.require__session_agree =
@@ -1007,27 +995,31 @@ export function sync_config__event_pres_mgmt({
pres_mgmt_cfg_local.hide__presentation_datetime =
pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false;
pres_mgmt_cfg_local.show_content__presentation_description =
pres_mgmt_cfg_remote?.show_content__presentation_description ?? false;
pres_mgmt_cfg_remote?.show_content__presentation_description ??
false;
pres_mgmt_cfg_local.hide__presenter_code =
pres_mgmt_cfg_remote?.hide__presenter_code ?? false;
pres_mgmt_cfg_local.hide__presenter_biography =
pres_mgmt_cfg_remote?.hide__presenter_biography ?? false;
pres_mgmt_cfg_local.hide__session_code = pres_mgmt_cfg_remote?.hide__session_code ?? false;
pres_mgmt_cfg_local.hide__session_code =
pres_mgmt_cfg_remote?.hide__session_code ?? false;
pres_mgmt_cfg_local.hide__session_description =
pres_mgmt_cfg_remote?.hide__session_description ?? false;
pres_mgmt_cfg_local.hide__session_location =
pres_mgmt_cfg_remote?.hide__session_location ?? false;
pres_mgmt_cfg_local.hide__session_msg = pres_mgmt_cfg_remote?.hide__session_msg ?? false;
pres_mgmt_cfg_local.hide__session_msg =
pres_mgmt_cfg_remote?.hide__session_msg ?? false;
pres_mgmt_cfg_local.hide__session_poc_profile =
pres_mgmt_cfg_remote?.hide__session_poc_profile ?? false;
pres_mgmt_cfg_local.hide__session_poc_biography =
pres_mgmt_cfg_remote?.hide__session_poc_biography ?? false;
pres_mgmt_cfg_local.hide__session_poc_profile_pic =
pres_mgmt_cfg_remote?.hide__session_poc_profile_pic ?? false;
pres_mgmt_cfg_local.hide_launcher_link = pres_mgmt_cfg_remote?.hide_launcher_link ?? false;
pres_mgmt_cfg_local.hide_launcher_link =
pres_mgmt_cfg_remote?.hide_launcher_link ?? false;
pres_mgmt_cfg_local.hide_launcher_link_legacy =
pres_mgmt_cfg_remote?.hide_launcher_link_legacy ?? false;
}
return pres_mgmt_cfg_local;
}
}

View File

@@ -3,31 +3,45 @@ import { describe, it, expect, vi } from 'vitest';
// Use hoist-safe factories for vi.mock
vi.mock('$lib/api/api', () => ({
api: {
create_nested_obj_v3: vi.fn(),
update_nested_obj_v3: vi.fn(),
delete_nested_ae_obj_v3: vi.fn(),
search_ae_obj_v3: vi.fn()
create_nested_obj: vi.fn(),
update_nested_obj: vi.fn(),
delete_nested_ae_obj: vi.fn(),
search_ae_obj: vi.fn()
}
}));
vi.mock('$lib/ae_core/core__idb_dexie', () => ({ db_save_ae_obj_li__ae_obj: vi.fn() }));
vi.mock('$lib/ae_events/db_events', () => ({ db_events: { badge: { get: vi.fn(), delete: vi.fn() } } }));
vi.mock('$lib/ae_events/ae_events__event_badge_template', () => ({ load_ae_obj_id__event_badge_template: vi.fn() }));
vi.mock('$lib/ae_core/core__idb_dexie', () => ({
db_save_ae_obj_li__ae_obj: vi.fn()
}));
vi.mock('$lib/ae_events/db_events', () => ({
db_events: { badge: { get: vi.fn(), delete: vi.fn() } }
}));
vi.mock('$lib/ae_events/ae_events__event_badge_template', () => ({
load_ae_obj_id__event_badge_template: vi.fn()
}));
import { create_ae_obj__event_badge } from './ae_events__event_badge';
describe('create_ae_obj__event_badge', () => {
it('calls api.create_nested_obj_v3 with the correct params and returns the result', async () => {
it('calls api.create_nested_obj with the correct params and returns the result', async () => {
const mocked = await import('$lib/api/api');
const mockCreateNested = mocked.api.create_nested_obj_v3 as any;
const mockCreateNested = mocked.api.create_nested_obj as any;
const fakeResult = { event_badge_id: 'eb123', full_name: 'Test User' };
mockCreateNested.mockResolvedValue(fakeResult);
const api_cfg = { base_url: 'http://localhost', headers: {} };
const event_id = 'evt1';
const data_kv = { full_name_override: 'Test User', email: 't@example.com' };
const data_kv = {
full_name_override: 'Test User',
email: 't@example.com'
};
const result = await create_ae_obj__event_badge({ api_cfg, event_id, data_kv, try_cache: false });
const result = await create_ae_obj__event_badge({
api_cfg,
event_id,
data_kv,
try_cache: false
});
expect(mockCreateNested).toHaveBeenCalled();
expect(mockCreateNested).toHaveBeenCalledWith({
@@ -45,15 +59,22 @@ describe('create_ae_obj__event_badge', () => {
});
describe('update_ae_obj__event_badge', () => {
it('calls api.update_nested_obj_v3 with correct params and returns result', async () => {
it('calls api.update_nested_obj with correct params and returns result', async () => {
const mocked = await import('$lib/api/api');
const mockUpdate = mocked.api.update_nested_obj_v3 as any;
const mockUpdate = mocked.api.update_nested_obj as any;
const fakeResult = { event_badge_id: 'eb999', full_name: 'Updated' };
mockUpdate.mockResolvedValue(fakeResult);
const { update_ae_obj__event_badge } = await import('./ae_events__event_badge');
const { update_ae_obj__event_badge } =
await import('./ae_events__event_badge');
const api_cfg = { base_url: 'http://localhost', headers: {} };
const res = await update_ae_obj__event_badge({ api_cfg, event_id: 'evt1', event_badge_id: 'eb999', data_kv: { full_name_override: 'Updated' }, try_cache: false });
const res = await update_ae_obj__event_badge({
api_cfg,
event_id: 'evt1',
event_badge_id: 'eb999',
data_kv: { full_name_override: 'Updated' },
try_cache: false
});
expect(mockUpdate).toHaveBeenCalled();
expect(mockUpdate).toHaveBeenCalledWith({
@@ -71,18 +92,24 @@ describe('update_ae_obj__event_badge', () => {
});
describe('delete_ae_obj_id__event_badge', () => {
it('calls api.delete_nested_ae_obj_v3 and deletes from local DB when try_cache true', async () => {
it('calls api.delete_nested_ae_obj and deletes from local DB when try_cache true', async () => {
const mocked = await import('$lib/api/api');
const mockDelete = mocked.api.delete_nested_ae_obj_v3 as any;
const mockDelete = mocked.api.delete_nested_ae_obj as any;
mockDelete.mockResolvedValue({ success: true });
const db = await import('$lib/ae_events/db_events');
const dbDelete = db.db_events.badge.delete as any;
const { delete_ae_obj_id__event_badge } = await import('./ae_events__event_badge');
const { delete_ae_obj_id__event_badge } =
await import('./ae_events__event_badge');
const api_cfg = { base_url: 'http://localhost', headers: {} };
const res = await delete_ae_obj_id__event_badge({ api_cfg, event_id: 'evt1', event_badge_id: 'ebDel', try_cache: true });
const res = await delete_ae_obj_id__event_badge({
api_cfg,
event_id: 'evt1',
event_badge_id: 'ebDel',
try_cache: true
});
expect(mockDelete).toHaveBeenCalled();
expect(dbDelete).toHaveBeenCalledWith('ebDel');
@@ -91,15 +118,21 @@ describe('delete_ae_obj_id__event_badge', () => {
});
describe('search__event_badge', () => {
it('calls api.search_ae_obj_v3 and returns list (handles data envelope)', async () => {
it('calls api.search_ae_obj and returns list (handles data envelope)', async () => {
const mocked = await import('$lib/api/api');
const mockSearch = mocked.api.search_ae_obj_v3 as any;
const mockSearch = mocked.api.search_ae_obj as any;
const fakeList = [{ event_badge_id: 'eb1' }, { event_badge_id: 'eb2' }];
mockSearch.mockResolvedValue({ data: fakeList });
const { search__event_badge } = await import('./ae_events__event_badge');
const { search__event_badge } =
await import('./ae_events__event_badge');
const api_cfg = { base_url: 'http://localhost', headers: {} };
const res = await search__event_badge({ api_cfg, event_id: 'evt1', fulltext_search_qry_str: 'Test', try_cache: false });
const res = await search__event_badge({
api_cfg,
event_id: 'evt1',
fulltext_search_qry_str: 'Test',
try_cache: false
});
expect(mockSearch).toHaveBeenCalled();
expect(Array.isArray(res)).toBe(true);

View File

@@ -35,29 +35,36 @@ export async function load_ae_obj_id__event_badge({
log_lvl?: number;
}): Promise<ae_EventBadge | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_badge() *** event_badge_id=${event_badge_id}`);
console.log(
`*** load_ae_obj_id__event_badge() *** event_badge_id=${event_badge_id}`
);
}
try {
ae_promises.load__event_badge_obj = await api
.get_ae_obj_v3({
api_cfg,
obj_type: 'event_badge',
obj_id: event_badge_id,
view,
log_lvl
});
ae_promises.load__event_badge_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'event_badge',
obj_id: event_badge_id,
view,
log_lvl
});
if (ae_promises.load__event_badge_obj) {
if (try_cache) {
// In theory we should be able to use the event_id found in the Badge load object. 2026-02-04
// This keeps coming up as undefined: ae_promises.load__event_badge_obj.event_id
if (log_lvl) console.log(`Saving to local cache... Event ID: ${event_id} or ${ae_promises.load__event_badge_obj.event_id}`);
const processed_obj_li = await process_ae_obj__event_badge_props({
obj_li: [ae_promises.load__event_badge_obj],
event_id: event_id || ae_promises.load__event_badge_obj.event_id,
log_lvl
});
if (log_lvl)
console.log(
`Saving to local cache... Event ID: ${event_id} or ${ae_promises.load__event_badge_obj.event_id}`
);
const processed_obj_li =
await process_ae_obj__event_badge_props({
obj_li: [ae_promises.load__event_badge_obj],
event_id:
event_id ||
ae_promises.load__event_badge_obj.event_id,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge',
@@ -71,15 +78,21 @@ export async function load_ae_obj_id__event_badge({
} else {
console.log('No results returned from API.');
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache...');
ae_promises.load__event_badge_obj = await db_events.badge.get(event_badge_id);
if (log_lvl)
console.log('Attempting to load from local cache...');
ae_promises.load__event_badge_obj =
await db_events.badge.get(event_badge_id);
}
}
} catch (error: any) {
console.log('API request failed.', error);
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache after error...');
ae_promises.load__event_badge_obj = await db_events.badge.get(event_badge_id);
if (log_lvl)
console.log(
'Attempting to load from local cache after error...'
);
ae_promises.load__event_badge_obj =
await db_events.badge.get(event_badge_id);
} else {
ae_promises.load__event_badge_obj = null;
}
@@ -87,14 +100,16 @@ export async function load_ae_obj_id__event_badge({
if (inc_template && ae_promises.load__event_badge_obj) {
// Load the templates for the event badge
const current_template_id = ae_promises.load__event_badge_obj.event_badge_template_id;
const current_template_id =
ae_promises.load__event_badge_obj.event_badge_template_id;
if (current_template_id) {
ae_promises.load__event_badge_obj.event_badge_template = await load_ae_obj_id__event_badge_template({
api_cfg: api_cfg,
event_badge_template_id: current_template_id,
try_cache: try_cache,
log_lvl: log_lvl
});
ae_promises.load__event_badge_obj.event_badge_template =
await load_ae_obj_id__event_badge_template({
api_cfg: api_cfg,
event_badge_template_id: current_template_id,
try_cache: try_cache,
log_lvl: log_lvl
});
}
}
@@ -111,7 +126,12 @@ export async function load_ae_obj_li__event_badge({
view = 'default',
limit = 99,
offset = 0,
order_by_li = { priority: 'DESC', sort: 'DESC', updated_on: 'DESC', created_on: 'DESC' },
order_by_li = {
priority: 'DESC',
sort: 'DESC',
updated_on: 'DESC',
created_on: 'DESC'
},
params = {},
try_cache = true,
log_lvl = 0
@@ -130,32 +150,34 @@ export async function load_ae_obj_li__event_badge({
log_lvl?: number;
}): Promise<ae_EventBadge[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__event_badge() *** event_id=${event_id}`);
console.log(
`*** load_ae_obj_li__event_badge() *** event_id=${event_id}`
);
}
try {
ae_promises.load__event_badge_obj_li = await api
.get_ae_obj_li_v3({
api_cfg,
obj_type: 'event_badge',
for_obj_type: 'event',
for_obj_id: event_id,
enabled: enabled,
hidden: hidden,
view: view,
order_by_li: order_by_li,
limit: limit,
offset: offset,
log_lvl: log_lvl
});
ae_promises.load__event_badge_obj_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_badge',
for_obj_type: 'event',
for_obj_id: event_id,
enabled: enabled,
hidden: hidden,
view: view,
order_by_li: order_by_li,
limit: limit,
offset: offset,
log_lvl: log_lvl
});
if (ae_promises.load__event_badge_obj_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__event_badge_props({
obj_li: ae_promises.load__event_badge_obj_li,
event_id,
log_lvl
});
const processed_obj_li =
await process_ae_obj__event_badge_props({
obj_li: ae_promises.load__event_badge_obj_li,
event_id,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge',
@@ -169,9 +191,11 @@ export async function load_ae_obj_li__event_badge({
} else {
console.log('No results returned from API.');
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache...');
if (log_lvl)
console.log('Attempting to load from local cache...');
ae_promises.load__event_badge_obj_li = await db_events.badge
.where('event_id').equals(event_id)
.where('event_id')
.equals(event_id)
.toArray();
} else {
ae_promises.load__event_badge_obj_li = [];
@@ -180,9 +204,13 @@ export async function load_ae_obj_li__event_badge({
} catch (error: any) {
console.log('API request failed.', error);
if (try_cache) {
if (log_lvl) console.log('Attempting to load from local cache after error...');
if (log_lvl)
console.log(
'Attempting to load from local cache after error...'
);
ae_promises.load__event_badge_obj_li = await db_events.badge
.where('event_id').equals(event_id)
.where('event_id')
.equals(event_id)
.toArray();
} else {
ae_promises.load__event_badge_obj_li = [];
@@ -193,12 +221,13 @@ export async function load_ae_obj_li__event_badge({
for (const badge_obj of ae_promises.load__event_badge_obj_li) {
const current_template_id = badge_obj.event_badge_template_id;
if (current_template_id) {
badge_obj.event_badge_template = await load_ae_obj_id__event_badge_template({
api_cfg: api_cfg,
event_badge_template_id: current_template_id,
try_cache: try_cache,
log_lvl: log_lvl
});
badge_obj.event_badge_template =
await load_ae_obj_id__event_badge_template({
api_cfg: api_cfg,
event_badge_template_id: current_template_id,
try_cache: try_cache,
log_lvl: log_lvl
});
}
}
}
@@ -223,10 +252,12 @@ export async function create_ae_obj__event_badge({
log_lvl?: number;
}): Promise<ae_EventBadge | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__event_badge() *** event_id=${event_id}`);
console.log(
`*** create_ae_obj__event_badge() *** event_id=${event_id}`
);
}
const result = await api.create_nested_obj_v3({
const result = await api.create_nested_obj({
api_cfg,
parent_type: 'event',
parent_id: event_id,
@@ -275,10 +306,12 @@ export async function delete_ae_obj_id__event_badge({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__event_badge() *** event_badge_id=${event_badge_id}`);
console.log(
`*** delete_ae_obj_id__event_badge() *** event_badge_id=${event_badge_id}`
);
}
const result = await api.delete_nested_ae_obj_v3({
const result = await api.delete_nested_ae_obj({
api_cfg,
parent_type: 'event',
parent_id: event_id,
@@ -315,10 +348,12 @@ export async function update_ae_obj__event_badge({
log_lvl?: number;
}): Promise<ae_EventBadge | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__event_badge() *** event_badge_id=${event_badge_id}`);
console.log(
`*** update_ae_obj__event_badge() *** event_badge_id=${event_badge_id}`
);
}
const result = await api.update_nested_obj_v3({
const result = await api.update_nested_obj({
api_cfg,
parent_type: 'event',
parent_id: event_id,
@@ -391,7 +426,9 @@ export async function search__event_badge({
log_lvl?: number;
}): Promise<ae_EventBadge[] | false> {
if (log_lvl) {
console.log(`*** search__event_badge() *** event_id=${event_id} ft=${fulltext_search_qry_str}`);
console.log(
`*** search__event_badge() *** event_id=${event_id} ft=${fulltext_search_qry_str}`
);
}
const search_query: any = {
@@ -406,15 +443,23 @@ export async function search__event_badge({
// Multi-word support: Split query by spaces and search for all words (AND logic)
if (fulltext_search_qry_str && fulltext_search_qry_str.trim().length > 0) {
const qry = fulltext_search_qry_str.trim();
const words = qry.split(/\s+/).filter(w => w.length > 0);
const words = qry.split(/\s+/).filter((w) => w.length > 0);
if (words.length === 1) {
// Single word: use simple LIKE
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${words[0]}%` });
search_query.and.push({
field: 'default_qry_str',
op: 'like',
value: `%${words[0]}%`
});
} else if (words.length > 1) {
// Multiple words: each word must match (AND logic)
for (const word of words) {
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${word}%` });
search_query.and.push({
field: 'default_qry_str',
op: 'like',
value: `%${word}%`
});
}
}
@@ -424,7 +469,11 @@ export async function search__event_badge({
if (affiliations_qry_str && affiliations_qry_str.trim().length > 0) {
const qry = affiliations_qry_str.trim();
search_query.and.push({ field: 'affiliations', op: 'like', value: `%${qry}%` });
search_query.and.push({
field: 'affiliations',
op: 'like',
value: `%${qry}%`
});
params['lk_qry'] = params['lk_qry'] || {};
params['lk_qry']['affiliations'] = qry;
}
@@ -435,11 +484,19 @@ export async function search__event_badge({
}
if (external_event_id) {
search_query.and.push({ field: 'external_event_id', op: 'eq', value: external_event_id });
search_query.and.push({
field: 'external_event_id',
op: 'eq',
value: external_event_id
});
}
if (type_code) {
search_query.and.push({ field: 'badge_type_code', op: 'eq', value: type_code });
search_query.and.push({
field: 'badge_type_code',
op: 'eq',
value: type_code
});
}
if (printed_status === 'printed') {
@@ -448,14 +505,18 @@ export async function search__event_badge({
search_query.and.push({ field: 'print_count', op: 'eq', value: 0 });
}
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: false });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: false });
ae_promises.search__event_badge_obj_li = await api
.search_ae_obj_v3({
.search_ae_obj({
api_cfg: api_cfg,
obj_type: 'event_badge',
search_query,
@@ -473,17 +534,21 @@ export async function search__event_badge({
let result_li: ae_EventBadge[] = [];
if (Array.isArray(badge_obj_li_get_result)) {
result_li = badge_obj_li_get_result;
} else if (badge_obj_li_get_result?.data && Array.isArray(badge_obj_li_get_result.data)) {
} else if (
badge_obj_li_get_result?.data &&
Array.isArray(badge_obj_li_get_result.data)
) {
result_li = badge_obj_li_get_result.data;
}
if (result_li.length > 0) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__event_badge_props({
obj_li: result_li,
event_id,
log_lvl
});
const processed_obj_li =
await process_ae_obj__event_badge_props({
obj_li: result_li,
event_id,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge',
@@ -631,14 +696,21 @@ async function _process_generic_props<T extends Record<string, any>>({
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -663,7 +735,9 @@ export async function process_ae_obj__event_badge_props({
log_lvl,
specific_processor: (obj) => {
if (log_lvl) {
console.log(`*** process_ae_obj__event_badge_props() *** event_id=${event_id}`);
console.log(
`*** process_ae_obj__event_badge_props() *** event_id=${event_id}`
);
}
if (event_id) {
if (!obj.event_id) obj.event_id = event_id;
@@ -672,4 +746,4 @@ export async function process_ae_obj__event_badge_props({
return obj;
}
});
}
}

View File

@@ -87,11 +87,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -129,11 +133,13 @@ export async function load_ae_obj_id__event_badge_template({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_badge_template() *** [V3] id=${event_badge_template_id}`);
console.log(
`*** load_ae_obj_id__event_badge_template() *** [V3] id=${event_badge_template_id}`
);
}
try {
ae_promises.load__event_badge_template_obj = await api.get_ae_obj_v3({
ae_promises.load__event_badge_template_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
@@ -156,11 +162,13 @@ export async function load_ae_obj_id__event_badge_template({
});
}
} else if (try_cache) {
ae_promises.load__event_badge_template_obj = await db_events.badge_template.get(event_badge_template_id);
ae_promises.load__event_badge_template_obj =
await db_events.badge_template.get(event_badge_template_id);
}
} catch (error: any) {
if (try_cache) {
ae_promises.load__event_badge_template_obj = await db_events.badge_template.get(event_badge_template_id);
ae_promises.load__event_badge_template_obj =
await db_events.badge_template.get(event_badge_template_id);
}
}
@@ -197,19 +205,21 @@ export async function load_ae_obj_li__event_badge_template({
log_lvl?: number;
}) {
try {
ae_promises.load__event_badge_template_obj_li = await api.get_ae_obj_li_v3({
api_cfg,
obj_type: 'event_badge_template',
for_obj_type: 'event',
for_obj_id: event_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
});
ae_promises.load__event_badge_template_obj_li = await api.get_ae_obj_li(
{
api_cfg,
obj_type: 'event_badge_template',
for_obj_type: 'event',
for_obj_id: event_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
}
);
if (ae_promises.load__event_badge_template_obj_li) {
if (try_cache) {
@@ -226,15 +236,19 @@ export async function load_ae_obj_li__event_badge_template({
});
}
} else if (try_cache) {
ae_promises.load__event_badge_template_obj_li = await db_events.badge_template
.where('event_id').equals(event_id)
.toArray();
ae_promises.load__event_badge_template_obj_li =
await db_events.badge_template
.where('event_id')
.equals(event_id)
.toArray();
}
} catch (error: any) {
if (try_cache) {
ae_promises.load__event_badge_template_obj_li = await db_events.badge_template
.where('event_id').equals(event_id)
.toArray();
ae_promises.load__event_badge_template_obj_li =
await db_events.badge_template
.where('event_id')
.equals(event_id)
.toArray();
}
}
@@ -243,115 +257,115 @@ export async function load_ae_obj_li__event_badge_template({
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_badge_template({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
fields: { ...data_kv },
log_lvl
});
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
fields: { ...data_kv },
log_lvl
});
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_badge_template({
api_cfg,
event_id,
event_badge_template_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_badge_template_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_badge_template_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_badge_template_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
method,
log_lvl
});
const result = await api.delete_nested_ae_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
method,
log_lvl
});
if (try_cache) {
await db_events.badge_template.delete(event_badge_template_id);
}
return result;
if (try_cache) {
await db_events.badge_template.delete(event_badge_template_id);
}
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_badge_template({
api_cfg,
event_id,
event_badge_template_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_badge_template_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_badge_template_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_badge_template_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
fields: data_kv,
log_lvl
});
const result = await api.update_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
fields: data_kv,
log_lvl
});
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
}
// Updated 2026-01-20 to V3
@@ -388,13 +402,17 @@ export async function search__event_badge_template({
and: [{ field: 'event_id', op: 'eq', value: event_id }]
};
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: false });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: false });
const result_li = await api.search_ae_obj_v3({
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_badge_template',
search_query,
@@ -418,4 +436,4 @@ export async function search__event_badge_template({
});
}
return result_li || [];
}
}

View File

@@ -28,11 +28,13 @@ export async function load_ae_obj_id__event_device({
log_lvl?: number;
}): Promise<ae_EventDevice | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
console.log(
`*** load_ae_obj_id__event_device() *** [V3] id=${event_device_id}`
);
}
try {
const result = await api.get_ae_obj_v3({
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_device',
obj_id: event_device_id,
@@ -57,26 +59,30 @@ export async function load_ae_obj_id__event_device({
});
}
} else if (try_cache) {
ae_promises.load__event_device_obj = await db_events.device.get(event_device_id);
ae_promises.load__event_device_obj =
await db_events.device.get(event_device_id);
}
} catch (error: any) {
console.log('V3 Request failed.', error);
if (try_cache) {
ae_promises.load__event_device_obj = await db_events.device.get(event_device_id);
ae_promises.load__event_device_obj =
await db_events.device.get(event_device_id);
}
}
if (!ae_promises.load__event_device_obj) return null;
if (inc_location_id) {
const current_location_id = ae_promises.load__event_device_obj.event_location_id;
const current_location_id =
ae_promises.load__event_device_obj.event_location_id;
if (current_location_id) {
ae_promises.load__event_device_obj.event_location_obj = await load_ae_obj_id__event_location({
api_cfg,
event_location_id: current_location_id,
try_cache,
log_lvl
});
ae_promises.load__event_device_obj.event_location_obj =
await load_ae_obj_id__event_location({
api_cfg,
event_location_id: current_location_id,
try_cache,
log_lvl
});
}
}
@@ -115,11 +121,13 @@ export async function load_ae_obj_li__event_device({
log_lvl?: number;
}): Promise<ae_EventDevice[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__event_device() *** [V3] for=${for_obj_type}:${for_obj_id}`);
console.log(
`*** load_ae_obj_li__event_device() *** [V3] for=${for_obj_type}:${for_obj_id}`
);
}
try {
const result_li = await api.get_ae_obj_li_v3({
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_device',
for_obj_type,
@@ -150,14 +158,16 @@ export async function load_ae_obj_li__event_device({
}
} else if (try_cache) {
ae_promises.load__event_device_obj_li = await db_events.device
.where('event_id').equals(for_obj_id)
.where('event_id')
.equals(for_obj_id)
.toArray();
}
} catch (error: any) {
console.log('V3 List Request failed.', error);
if (try_cache) {
ae_promises.load__event_device_obj_li = await db_events.device
.where('event_id').equals(for_obj_id)
.where('event_id')
.equals(for_obj_id)
.toArray();
}
}
@@ -165,12 +175,13 @@ export async function load_ae_obj_li__event_device({
if (inc_location_id && ae_promises.load__event_device_obj_li) {
for (const device of ae_promises.load__event_device_obj_li) {
if (device.event_location_id) {
device.event_location_obj = await load_ae_obj_id__event_location({
api_cfg,
event_location_id: device.event_location_id,
try_cache,
log_lvl
});
device.event_location_obj =
await load_ae_obj_id__event_location({
api_cfg,
event_location_id: device.event_location_id,
try_cache,
log_lvl
});
}
}
}
@@ -180,155 +191,161 @@ export async function load_ae_obj_li__event_device({
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_device({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventDevice | null> {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('create_ae_obj__event_device: event_id is required');
return null;
}
if (log_lvl) {
console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`);
}
if (log_lvl) {
console.log(
`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`
);
}
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
fields: { ...data_kv },
log_lvl
});
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_device({
api_cfg,
event_id,
event_device_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_device_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
event_device_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
event_device_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('delete_ae_obj_id__event_device: event_id is required');
return null;
}
if (log_lvl) {
console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
}
if (log_lvl) {
console.log(
`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`
);
}
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
method,
log_lvl
});
const result = await api.delete_nested_ae_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
method,
log_lvl
});
if (try_cache) {
await db_events.device.delete(event_device_id);
}
if (try_cache) {
await db_events.device.delete(event_device_id);
}
return result;
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_device({
api_cfg,
event_id,
event_device_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_device_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
event_device_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
event_device_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventDevice | null> {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('update_ae_obj__event_device: event_id is required');
return null;
}
if (log_lvl) {
console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`);
}
if (log_lvl) {
console.log(
`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`
);
}
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
fields: data_kv,
log_lvl
});
const result = await api.update_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
return null;
}
// Updated 2026-01-20 to V3
@@ -361,7 +378,9 @@ export async function search__event_device({
log_lvl?: number;
}): Promise<ae_EventDevice[]> {
if (log_lvl) {
console.log(`*** search__event_device() *** [V3] event_id=${event_id} qry=${qry_str}`);
console.log(
`*** search__event_device() *** [V3] event_id=${event_id} qry=${qry_str}`
);
}
const search_query: any = {
@@ -370,13 +389,17 @@ export async function search__event_device({
};
// Logical filters
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: false });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: false });
const result_li = await api.search_ae_obj_v3({
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_device',
search_query,
@@ -499,11 +522,15 @@ async function _process_generic_props<T extends Record<string, any>>({
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}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -524,7 +551,7 @@ export async function process_ae_obj__event_device_props({
obj_type: 'event_device',
log_lvl,
specific_processor: (obj) => {
// Note: V3 API returns proper ISO strings.
// Note: V3 API returns proper ISO strings.
// We no longer manually append 'Z' to avoid timezone corruption.
return obj;
}

View File

@@ -66,7 +66,7 @@ async function _refresh_file_id_background({
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return null;
try {
const result = await api.get_ae_obj_v3({
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_file',
obj_id: event_file_id,
@@ -191,10 +191,12 @@ async function _refresh_file_li_background({
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
try {
if (log_lvl) {
console.log(`📡 [DEBUG] _refresh_file_li_background: Fetching files for ${for_obj_type}:${for_obj_id}`);
console.log(
`📡 [DEBUG] _refresh_file_li_background: Fetching files for ${for_obj_type}:${for_obj_id}`
);
}
const result_li = await api.get_ae_obj_li_v3({
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_file',
for_obj_type,
@@ -211,7 +213,10 @@ async function _refresh_file_li_background({
if (result_li) {
if (log_lvl) {
console.log(`📦 [DEBUG] Raw API results count:`, result_li.length);
console.log(
`📦 [DEBUG] Raw API results count:`,
result_li.length
);
if (result_li.length > 0) {
console.log(`🔍 [DEBUG] Sample Raw IDs:`, {
id: result_li[0].id,
@@ -253,7 +258,10 @@ async function _refresh_file_li_background({
}
if (try_cache) {
if (log_lvl) console.log(`💾 [DEBUG] Saving ${processed.length} records to ae_events_db.file`);
if (log_lvl)
console.log(
`💾 [DEBUG] Saving ${processed.length} records to ae_events_db.file`
);
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'file',
@@ -265,10 +273,15 @@ async function _refresh_file_li_background({
return processed;
}
} catch (e) {
if (log_lvl) console.error(`❌ [DEBUG] Error in _refresh_file_li_background:`, e);
if (log_lvl)
console.error(
`❌ [DEBUG] Error in _refresh_file_li_background:`,
e
);
}
return [];
}export async function create_event_file_obj_from_hosted_file_async({
}
export async function create_event_file_obj_from_hosted_file_async({
api_cfg,
hosted_file_id,
params = {},
@@ -318,7 +331,7 @@ export async function delete_ae_obj_id__event_file({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'event_file',
obj_id: event_file_id,
@@ -344,7 +357,7 @@ export async function update_ae_obj__event_file({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventFile | null> {
const result = await api.update_ae_obj_v3({
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'event_file',
obj_id: event_file_id,
@@ -427,7 +440,7 @@ export async function search__event_file({
op: 'eq',
value: qry_file_purpose
});
const result_li = await api.search_ae_obj_v3({
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_file',
search_query,
@@ -459,7 +472,7 @@ export async function search__event_file({
});
}
return processed; // Return processed data with mapped fields
return processed; // Return processed data with mapped fields
}
return [];
@@ -545,7 +558,11 @@ async function _process_generic_props<T extends Record<string, any>>({
if (key.endsWith('_random')) {
const newKey = key.slice(0, -7);
// ONLY overwrite if the random variant has a valid value
if (processed_obj[key] !== null && processed_obj[key] !== undefined && processed_obj[key] !== '') {
if (
processed_obj[key] !== null &&
processed_obj[key] !== undefined &&
processed_obj[key] !== ''
) {
(processed_obj as any)[newKey] = processed_obj[key];
}
}

View File

@@ -37,7 +37,9 @@ export async function load_ae_obj_id__event_location({
log_lvl?: number;
}): Promise<ae_EventLocation | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_location() *** [V3] id=${event_location_id} (SWR)`);
console.log(
`*** load_ae_obj_id__event_location() *** [V3] id=${event_location_id} (SWR)`
);
}
// 1. FAST PATH: Cache hit
@@ -45,40 +47,95 @@ export async function load_ae_obj_id__event_location({
try {
const cached = await db_events.location.get(event_location_id);
if (cached) {
_refresh_location_id_background({
api_cfg, event_location_id, view, try_cache,
inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl: 0
_refresh_location_id_background({
api_cfg,
event_location_id,
view,
try_cache,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl: 0
});
return await _handle_nested_loads(cached, {
api_cfg, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl
return await _handle_nested_loads(cached, {
api_cfg,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl
});
}
} catch (e) {}
}
// 2. SLOW PATH: Wait for API
return await _refresh_location_id_background({
api_cfg, event_location_id, view, try_cache,
inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl
return await _refresh_location_id_background({
api_cfg,
event_location_id,
view,
try_cache,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl
});
}
async function _refresh_location_id_background({ api_cfg, event_location_id, view, try_cache, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li, log_lvl }: any) {
async function _refresh_location_id_background({
api_cfg,
event_location_id,
view,
try_cache,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return null;
try {
const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, view, log_lvl });
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_location',
obj_id: event_location_id,
view,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__event_location_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return await _handle_nested_loads(processed_obj, {
api_cfg, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl
return await _handle_nested_loads(processed_obj, {
api_cfg,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl
});
}
} catch (e) {}
@@ -129,24 +186,48 @@ export async function load_ae_obj_li__event_location({
log_lvl?: number;
}): Promise<ae_EventLocation[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__event_location() *** [V3] for=${for_obj_type}:${for_obj_id} (SWR)`);
console.log(
`*** load_ae_obj_li__event_location() *** [V3] for=${for_obj_type}:${for_obj_id} (SWR)`
);
}
// 1. FAST PATH: Check cache
if (try_cache) {
try {
const cached_li = await db_events.location.where('event_id').equals(for_obj_id).toArray();
const cached_li = await db_events.location
.where('event_id')
.equals(for_obj_id)
.toArray();
if (cached_li && cached_li.length > 0) {
_refresh_location_li_background({
api_cfg, for_obj_type, for_obj_id,
inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
enabled, hidden, view, limit, offset, order_by_li, try_cache,
log_lvl: 0
_refresh_location_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl: 0
});
for (const loc of cached_li) {
_handle_nested_loads(loc, {
api_cfg, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl: 0
_handle_nested_loads(loc, {
api_cfg,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl: 0
});
}
return cached_li;
@@ -155,33 +236,91 @@ export async function load_ae_obj_li__event_location({
}
// 2. SLOW PATH: API
return await _refresh_location_li_background({
api_cfg, for_obj_type, for_obj_id,
inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
enabled, hidden, view, limit, offset, order_by_li, try_cache,
log_lvl
return await _refresh_location_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
});
}
async function _refresh_location_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }: any) {
async function _refresh_location_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
try {
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_location', for_obj_type, for_obj_id, enabled, hidden, view, limit, offset, order_by_li, log_lvl });
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_location',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_location_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_location_props({
obj_li: result_li,
log_lvl
});
// String-Only ID Vision: Ensure linking ID is set for indexing
if (for_obj_type === 'event') {
processed.forEach(loc => loc.event_id = for_obj_id);
processed.forEach((loc) => (loc.event_id = for_obj_id));
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: processed,
properties_to_save,
log_lvl
});
}
for (const loc of processed) {
_handle_nested_loads(loc, {
api_cfg, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li,
log_lvl: 0
_handle_nested_loads(loc, {
api_cfg,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl: 0
});
}
return processed;
@@ -193,34 +332,66 @@ async function _refresh_location_li_background({ api_cfg, for_obj_type, for_obj_
/**
* Handle nested data loads for a single location object.
*/
async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, inc_session_li, inc_presentation_li, inc_presenter_li, inc_device_li, inc_all_file_li, log_lvl }: any) {
const current_location_id = location_obj.id || location_obj.event_location_id;
async function _handle_nested_loads(
location_obj: any,
{
api_cfg,
inc_file_li,
inc_session_li,
inc_presentation_li,
inc_presenter_li,
inc_device_li,
inc_all_file_li,
log_lvl
}: any
) {
const current_location_id =
location_obj.id || location_obj.event_location_id;
if (!current_location_id) return location_obj;
const tasks = [];
if (inc_file_li) {
tasks.push(load_ae_obj_li__event_file({
api_cfg, for_obj_type: 'event_location', for_obj_id: current_location_id,
enabled: 'all', limit: 25, log_lvl
}).then(res => location_obj.event_file_li = res));
tasks.push(
load_ae_obj_li__event_file({
api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
enabled: 'all',
limit: 25,
log_lvl
}).then((res) => (location_obj.event_file_li = res))
);
}
if (inc_session_li) {
tasks.push(load_ae_obj_li__event_session({
api_cfg, for_obj_type: 'event_location', for_obj_id: current_location_id,
inc_file_li: inc_all_file_li,
inc_all_file_li: inc_all_file_li,
inc_presentation_li: inc_presentation_li,
inc_presenter_li: inc_presenter_li,
enabled: 'enabled', hidden: 'not_hidden', limit: 150, log_lvl
}).then(res => location_obj.event_session_obj_li = res));
tasks.push(
load_ae_obj_li__event_session({
api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
inc_file_li: inc_all_file_li,
inc_all_file_li: inc_all_file_li,
inc_presentation_li: inc_presentation_li,
inc_presenter_li: inc_presenter_li,
enabled: 'enabled',
hidden: 'not_hidden',
limit: 150,
log_lvl
}).then((res) => (location_obj.event_session_obj_li = res))
);
}
if (inc_device_li) {
tasks.push(load_ae_obj_li__event_device({
api_cfg, for_obj_type: 'event_location', for_obj_id: current_location_id,
enabled: 'all', limit: 50, log_lvl
}).then(res => location_obj.event_device_li = res));
tasks.push(
load_ae_obj_li__event_device({
api_cfg,
for_obj_type: 'event_location',
for_obj_id: current_location_id,
enabled: 'all',
limit: 50,
log_lvl
}).then((res) => (location_obj.event_device_li = res))
);
}
if (tasks.length > 0) await Promise.all(tasks);
@@ -229,132 +400,183 @@ async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, i
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_location({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventLocation | null> {
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
fields: { ...data_kv },
log_lvl
});
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_location_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_location({
api_cfg,
event_id,
event_location_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_location_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_location_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_location_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
method,
log_lvl
});
if (try_cache) await db_events.location.delete(event_location_id);
return result;
const result = await api.delete_nested_ae_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
method,
log_lvl
});
if (try_cache) await db_events.location.delete(event_location_id);
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_location({
api_cfg,
event_id,
event_location_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_location_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_location_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_location_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventLocation | null> {
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
export async function search__event_location({
api_cfg, event_id, qry_str = '', enabled = 'enabled', hidden = 'not_hidden', view = 'default', limit = 25, offset = 0, order_by_li = [{ sort: 'ASC' }, { name: 'ASC' }], try_cache = true, log_lvl = 0
api_cfg,
event_id,
qry_str = '',
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 25,
offset = 0,
order_by_li = [{ sort: 'ASC' }, { name: 'ASC' }],
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_id: string; qry_str?: string; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; view?: string; limit?: number; offset?: number; order_by_li?: any; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
qry_str?: string;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: any;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventLocation[]> {
const search_query: any = { q: qry_str, and: [{ field: 'event_id', op: 'eq', value: event_id }] };
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: false });
const search_query: any = {
q: qry_str,
and: [{ field: 'event_id', op: 'eq', value: event_id }]
};
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: true });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: false });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: true });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: false });
const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_location', search_query, order_by_li, view, limit, offset, log_lvl });
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_location',
search_query,
order_by_li,
view,
limit,
offset,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_location_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_location_props({
obj_li: result_li,
log_lvl
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return processed;
}
@@ -362,10 +584,40 @@ export async function search__event_location({
}
export const properties_to_save = [
'id', 'event_location_id', 'event_location_id_random', 'event_id', 'event_id_random', 'external_id', 'code', 'name', 'description', 'passcode', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on', 'tmp_sort_1', 'tmp_sort_2', 'event_name'
'id',
'event_location_id',
'event_location_id_random',
'event_id',
'event_id_random',
'external_id',
'code',
'name',
'description',
'passcode',
'enable',
'hide',
'priority',
'sort',
'group',
'notes',
'created_on',
'updated_on',
'tmp_sort_1',
'tmp_sort_2',
'event_name'
];
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[]> {
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 (!obj_li || obj_li.length === 0) return [];
const processed_obj_li: T[] = [];
for (const original_obj of obj_li) {
@@ -378,24 +630,42 @@ async function _process_generic_props<T extends Record<string, any>>({ obj_li, o
}
const randomIdKey = `${obj_type}_id_random`;
const baseIdKey = `${obj_type}_id`;
if (processed_obj[randomIdKey]) (processed_obj as any).id = processed_obj[randomIdKey];
else if (processed_obj[baseIdKey]) (processed_obj as any).id = processed_obj[baseIdKey];
if (processed_obj[randomIdKey])
(processed_obj as any).id = processed_obj[randomIdKey];
else if (processed_obj[baseIdKey])
(processed_obj as any).id = processed_obj[baseIdKey];
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}`;
if (specific_processor) processed_obj = await Promise.resolve(specific_processor(processed_obj));
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor)
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
processed_obj_li.push(processed_obj as T);
}
return processed_obj_li;
}
export async function process_ae_obj__event_location_props({ obj_li, log_lvl = 0 }: { obj_li: any[]; log_lvl?: number; }) {
return _process_generic_props({ obj_li, obj_type: 'event_location', log_lvl, specific_processor: (obj) => {
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}});
}
export async function process_ae_obj__event_location_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'event_location',
log_lvl,
specific_processor: (obj) => {
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}
});
}

View File

@@ -37,41 +37,116 @@ export async function load_ae_obj_id__event_presentation({
log_lvl?: number;
}): Promise<ae_EventPresentation | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_presentation() *** id=${event_presentation_id} (SWR)`);
console.log(
`*** load_ae_obj_id__event_presentation() *** id=${event_presentation_id} (SWR)`
);
}
// 1. FAST PATH: Cache hit
if (try_cache) {
try {
const cached = await db_events.presentation.get(event_presentation_id);
const cached = await db_events.presentation.get(
event_presentation_id
);
if (cached) {
// Background refresh (non-blocking)
_refresh_presentation_id_background({ api_cfg, event_presentation_id, view, try_cache, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, log_lvl: 0 });
_refresh_presentation_id_background({
api_cfg,
event_presentation_id,
view,
try_cache,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl: 0
});
// Await nested loads from cache to return a complete object
return await _handle_nested_loads(cached, { api_cfg, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl: 0 });
return await _handle_nested_loads(cached, {
api_cfg,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl: 0
});
}
} catch (e) {}
}
// 2. SLOW PATH: Wait for API
return await _refresh_presentation_id_background({ api_cfg, event_presentation_id, view, try_cache, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, log_lvl });
return await _refresh_presentation_id_background({
api_cfg,
event_presentation_id,
view,
try_cache,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl
});
}
/**
* Internal background refresh for a single presentation
*/
async function _refresh_presentation_id_background({ api_cfg, event_presentation_id, view, try_cache, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, log_lvl }: any) {
async function _refresh_presentation_id_background({
api_cfg,
event_presentation_id,
view,
try_cache,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return null;
try {
const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_presentation', obj_id: event_presentation_id, view, log_lvl });
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_presentation',
obj_id: event_presentation_id,
view,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__event_presentation_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: [processed_obj], properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
// During refresh, we disable child SWR to prevent a request storm
return await _handle_nested_loads(processed_obj, { api_cfg, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache: false, log_lvl });
return await _handle_nested_loads(processed_obj, {
api_cfg,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache: false,
log_lvl
});
}
} catch (e) {}
return null;
@@ -80,23 +155,54 @@ async function _refresh_presentation_id_background({ api_cfg, event_presentation
/**
* Helper to handle nested collection loads for a presentation
*/
async function _handle_nested_loads(presentation_obj: any, { api_cfg, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl }: any) {
const current_presentation_id = presentation_obj.id || presentation_obj.event_presentation_id;
async function _handle_nested_loads(
presentation_obj: any,
{
api_cfg,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl
}: any
) {
const current_presentation_id =
presentation_obj.id || presentation_obj.event_presentation_id;
if (!current_presentation_id) return presentation_obj;
const tasks = [];
if (inc_file_li) {
tasks.push(load_ae_obj_li__event_file({
api_cfg, for_obj_type: 'event_presentation', for_obj_id: current_presentation_id,
enabled, limit: 25, try_cache, log_lvl
}).then(res => presentation_obj.event_file_li = res));
tasks.push(
load_ae_obj_li__event_file({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: current_presentation_id,
enabled,
limit: 25,
try_cache,
log_lvl
}).then((res) => (presentation_obj.event_file_li = res))
);
}
if (inc_presenter_li) {
tasks.push(load_ae_obj_li__event_presenter({
api_cfg, for_obj_type: 'event_presentation', for_obj_id: current_presentation_id,
inc_file_li, enabled, hidden, limit, offset, try_cache, log_lvl
}).then(res => presentation_obj.event_presenter_li = res));
tasks.push(
load_ae_obj_li__event_presenter({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: current_presentation_id,
inc_file_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl
}).then((res) => (presentation_obj.event_presenter_li = res))
);
}
if (tasks.length > 0) await Promise.all(tasks);
@@ -140,17 +246,36 @@ export async function load_ae_obj_li__event_presentation({
log_lvl?: number;
}): Promise<ae_EventPresentation[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__event_presentation() *** for=${for_obj_type}:${for_obj_id} (SWR)`);
console.log(
`*** load_ae_obj_li__event_presentation() *** for=${for_obj_type}:${for_obj_id} (SWR)`
);
}
// 1. FAST PATH: Cache hit
if (try_cache) {
try {
// String-Only ID Vision: query event_session_id using the string ID
const cached_li = await db_events.presentation.where('event_session_id').equals(for_obj_id).toArray();
const cached_li = await db_events.presentation
.where('event_session_id')
.equals(for_obj_id)
.toArray();
if (cached_li && cached_li.length > 0) {
// Background refresh (non-blocking)
_refresh_presentation_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_presenter_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl: 0 });
_refresh_presentation_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl: 0
});
// Warm cache for nested loads in the background (FIRE AND FORGET)
// DEPRECATED Optimization: Don't fire child loads for every item in a list here.
@@ -167,23 +292,72 @@ export async function load_ae_obj_li__event_presentation({
}
// 2. SLOW PATH: Wait for API
return await _refresh_presentation_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_presenter_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl });
return await _refresh_presentation_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
});
}
async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_presenter_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }: any) {
async function _refresh_presentation_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
try {
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_presentation', for_obj_type, for_obj_id, enabled, hidden, view, limit, offset, order_by_li, log_lvl });
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_presentation',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_presentation_props({
obj_li: result_li,
log_lvl
});
// Ensure the linking ID is set correctly for indexing
if (for_obj_type === 'event_session') {
processed.forEach(p => p.event_session_id = for_obj_id);
processed.forEach((p) => (p.event_session_id = for_obj_id));
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: processed,
properties_to_save,
log_lvl
});
// CRITICAL FIX (2026-02-26): Yield to microtask queue so Dexie liveQuery observers
// fire before we return. Without this, component-mounted liveQueries may subscribe
// to IDB *before* the write completes, causing empty results on cold-start.
@@ -194,9 +368,21 @@ async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_
// before presenter data was loaded, causing "refresh twice" bug on cold-start.
// Now we await all nested loads AND preserve try_cache so presenters are written to IDB.
if (inc_file_li || inc_presenter_li) {
await Promise.all(processed.map(p =>
_handle_nested_loads(p, { api_cfg, inc_file_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl: 0 })
));
await Promise.all(
processed.map((p) =>
_handle_nested_loads(p, {
api_cfg,
inc_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl: 0
})
)
);
}
return processed;
}
@@ -206,42 +392,45 @@ async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_presentation({
api_cfg,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresentation | null> {
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event_session',
for_obj_id: event_session_id,
obj_type: 'event_presentation',
fields: { ...data_kv },
log_lvl
});
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event_session',
for_obj_id: event_session_id,
obj_type: 'event_presentation',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_presentation_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
@@ -258,8 +447,12 @@ export async function delete_ae_obj_id__event_presentation({
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
api_cfg, obj_type: 'event_presentation', obj_id: event_presentation_id, method, log_lvl
const result = await api.delete_ae_obj({
api_cfg,
obj_type: 'event_presentation',
obj_id: event_presentation_id,
method,
log_lvl
});
if (try_cache) await db_events.presentation.delete(event_presentation_id);
return result;
@@ -279,14 +472,27 @@ export async function update_ae_obj__event_presentation({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresentation | null> {
const result = await api.update_ae_obj_v3({
api_cfg, obj_type: 'event_presentation', obj_id: event_presentation_id, fields: data_kv, log_lvl
const result = await api.update_ae_obj({
api_cfg,
obj_type: 'event_presentation',
obj_id: event_presentation_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__event_presentation_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: [processed_obj], properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
@@ -295,24 +501,74 @@ export async function update_ae_obj__event_presentation({
// Updated 2026-01-21 to Restore Full Aether Search Logic
export async function search__event_presentation({
api_cfg, event_id, fulltext_search_qry_str = '', like_search_qry_str = '', enabled = 'enabled', hidden = 'not_hidden', view = 'default', limit = 50, offset = 0, order_by_li = [{ sort: 'ASC' }, { start_datetime: 'ASC' }, { name: 'ASC' }], try_cache = true, log_lvl = 0
api_cfg,
event_id,
fulltext_search_qry_str = '',
like_search_qry_str = '',
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 50,
offset = 0,
order_by_li = [{ sort: 'ASC' }, { start_datetime: 'ASC' }, { name: 'ASC' }],
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_id: string; fulltext_search_qry_str?: string; like_search_qry_str?: string; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; view?: string; limit?: number; offset?: number; order_by_li?: any; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
fulltext_search_qry_str?: string;
like_search_qry_str?: string;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: any;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresentation[]> {
const search_query: any = { q: '', and: [{ field: 'event_id', op: 'eq', value: event_id }] };
const search_query: any = {
q: '',
and: [{ field: 'event_id', op: 'eq', value: event_id }]
};
const params: key_val = {};
if (fulltext_search_qry_str && fulltext_search_qry_str.length > 2) params['ft_qry'] = { 'default_qry_str': fulltext_search_qry_str };
if (like_search_qry_str) params['lk_qry'] = { 'default_qry_str': like_search_qry_str };
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
if (fulltext_search_qry_str && fulltext_search_qry_str.length > 2)
params['ft_qry'] = { default_qry_str: fulltext_search_qry_str };
if (like_search_qry_str)
params['lk_qry'] = { default_qry_str: like_search_qry_str };
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_presentation', search_query, order_by_li, params, view, limit, offset, log_lvl });
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_presentation',
search_query,
order_by_li,
params,
view,
limit,
offset,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_presentation_props({
obj_li: result_li,
log_lvl
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return processed;
}
@@ -322,10 +578,53 @@ export async function search__event_presentation({
export const qry__event_presentation = search__event_presentation;
export const properties_to_save = [
'id', 'event_presentation_id', 'event_presentation_id_random', 'external_id', 'code', 'for_type', 'for_id', 'for_id_random', 'type_code', 'event_id', 'event_session_id', 'event_abstract_id', 'event_id_random', 'event_session_id_random', 'event_abstract_id_random', 'abstract_code', 'name', 'description', 'start_datetime', 'end_datetime', 'passcode', 'hide_event_launcher', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on', 'tmp_sort_1', 'tmp_sort_2', 'event_session_code', 'event_session_name'
'id',
'event_presentation_id',
'event_presentation_id_random',
'external_id',
'code',
'for_type',
'for_id',
'for_id_random',
'type_code',
'event_id',
'event_session_id',
'event_abstract_id',
'event_id_random',
'event_session_id_random',
'event_abstract_id_random',
'abstract_code',
'name',
'description',
'start_datetime',
'end_datetime',
'passcode',
'hide_event_launcher',
'enable',
'hide',
'priority',
'sort',
'group',
'notes',
'created_on',
'updated_on',
'tmp_sort_1',
'tmp_sort_2',
'event_session_code',
'event_session_name'
];
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[]> {
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 (!obj_li || obj_li.length === 0) return [];
const processed_obj_li: T[] = [];
for (const original_obj of obj_li) {
@@ -338,26 +637,48 @@ async function _process_generic_props<T extends Record<string, any>>({ obj_li, o
}
const randomIdKey = `${obj_type}_id_random`;
const baseIdKey = `${obj_type}_id`;
if (processed_obj[randomIdKey]) (processed_obj as any).id = processed_obj[randomIdKey];
else if (processed_obj[baseIdKey]) (processed_obj as any).id = processed_obj[baseIdKey];
if (processed_obj[randomIdKey])
(processed_obj as any).id = processed_obj[randomIdKey];
else if (processed_obj[baseIdKey])
(processed_obj as any).id = processed_obj[baseIdKey];
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
if (specific_processor) processed_obj = await Promise.resolve(specific_processor(processed_obj));
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor)
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
processed_obj_li.push(processed_obj as T);
}
return processed_obj_li;
}
export async function process_ae_obj__event_presentation_props({ obj_li, log_lvl = 0 }: { obj_li: any[]; log_lvl?: number; }) {
return _process_generic_props({ obj_li, obj_type: 'event_presentation', log_lvl, specific_processor: (obj) => {
// Ensure linking IDs are the string versions for indexing
if (obj.event_session_id_random) obj.event_session_id = obj.event_session_id_random;
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}});
}
export async function process_ae_obj__event_presentation_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'event_presentation',
log_lvl,
specific_processor: (obj) => {
// Ensure linking IDs are the string versions for indexing
if (obj.event_session_id_random)
obj.event_session_id = obj.event_session_id_random;
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}
});
}

View File

@@ -29,7 +29,9 @@ export async function load_ae_obj_id__event_presenter({
log_lvl?: number;
}): Promise<ae_EventPresenter | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__event_presenter() *** [V3] id=${event_presenter_id} (SWR)`);
console.log(
`*** load_ae_obj_id__event_presenter() *** [V3] id=${event_presenter_id} (SWR)`
);
}
// 1. FAST PATH: Cache hit
@@ -38,30 +40,71 @@ export async function load_ae_obj_id__event_presenter({
const cached = await db_events.presenter.get(event_presenter_id);
if (cached) {
// Background refresh (non-blocking)
_refresh_presenter_id_background({ api_cfg, event_presenter_id, view, try_cache, inc_file_li, log_lvl: 0 });
_refresh_presenter_id_background({
api_cfg,
event_presenter_id,
view,
try_cache,
inc_file_li,
log_lvl: 0
});
return cached;
}
} catch (e) {}
}
// 2. SLOW PATH: Wait for API
return await _refresh_presenter_id_background({ api_cfg, event_presenter_id, view, try_cache, inc_file_li, log_lvl });
return await _refresh_presenter_id_background({
api_cfg,
event_presenter_id,
view,
try_cache,
inc_file_li,
log_lvl
});
}
async function _refresh_presenter_id_background({ api_cfg, event_presenter_id, view, try_cache, inc_file_li, log_lvl }: any) {
async function _refresh_presenter_id_background({
api_cfg,
event_presenter_id,
view,
try_cache,
inc_file_li,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return null;
try {
const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, view, log_lvl });
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
view,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__event_presenter_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
if (inc_file_li) {
processed_obj.event_file_li = await load_ae_obj_li__event_file({
api_cfg, for_obj_type: 'event_presenter', for_obj_id: event_presenter_id,
enabled: 'all', limit: 25, try_cache: false, log_lvl
api_cfg,
for_obj_type: 'event_presenter',
for_obj_id: event_presenter_id,
enabled: 'all',
limit: 25,
try_cache: false,
log_lvl
});
}
return processed_obj;
@@ -104,7 +147,9 @@ export async function load_ae_obj_li__event_presenter({
log_lvl?: number;
}): Promise<ae_EventPresenter[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__event_presenter() *** [V3] for=${for_obj_type}:${for_obj_id} (SWR)`);
console.log(
`*** load_ae_obj_li__event_presenter() *** [V3] for=${for_obj_type}:${for_obj_id} (SWR)`
);
}
// 1. FAST PATH: Check cache using specific indices
@@ -112,31 +157,94 @@ export async function load_ae_obj_li__event_presenter({
try {
let cached_li: any[] = [];
if (for_obj_type === 'event_presentation') {
cached_li = await db_events.presenter.where('event_presentation_id').equals(for_obj_id).toArray();
cached_li = await db_events.presenter
.where('event_presentation_id')
.equals(for_obj_id)
.toArray();
} else if (for_obj_type === 'event_session') {
cached_li = await db_events.presenter.where('event_session_id').equals(for_obj_id).toArray();
cached_li = await db_events.presenter
.where('event_session_id')
.equals(for_obj_id)
.toArray();
} else if (for_obj_type === 'event') {
cached_li = await db_events.presenter.where('event_id').equals(for_obj_id).toArray();
cached_li = await db_events.presenter
.where('event_id')
.equals(for_obj_id)
.toArray();
}
if (cached_li && cached_li.length > 0) {
// Background refresh (non-blocking)
_refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl: 0 });
_refresh_presenter_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl: 0
});
return cached_li;
}
} catch (e) {}
}
// 2. SLOW PATH: Wait for API
return await _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl });
return await _refresh_presenter_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
});
}
async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }: any) {
async function _refresh_presenter_li_background({
api_cfg,
for_obj_type,
for_obj_id,
inc_file_li,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
try_cache,
log_lvl
}: any) {
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
try {
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_presenter', for_obj_type, for_obj_id, enabled, hidden, view, limit, offset, order_by_li, log_lvl });
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_presenter',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_presenter_props({
obj_li: result_li,
log_lvl
});
// String-Only ID Vision: Ensure linking ID is set for indexing
processed.forEach((p) => {
@@ -155,7 +263,13 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: processed,
properties_to_save,
log_lvl
});
// CRITICAL FIX (2026-02-26): Yield to microtask queue so Dexie liveQuery observers
// fire before we return. Without this, component-mounted liveQueries may subscribe
// to IDB *before* the write completes, causing empty results on cold-start.
@@ -164,10 +278,15 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj
// Background nested loads for refreshed items (FIRE AND FORGET)
if (inc_file_li) {
processed.forEach(p => {
processed.forEach((p) => {
load_ae_obj_li__event_file({
api_cfg, for_obj_type: 'event_presenter', for_obj_id: p.id,
enabled: 'all', limit: 25, try_cache: false, log_lvl: 0
api_cfg,
for_obj_type: 'event_presenter',
for_obj_id: p.id,
enabled: 'all',
limit: 25,
try_cache: false,
log_lvl: 0
});
});
}
@@ -180,128 +299,143 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_presenter({
api_cfg,
event_presentation_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_presentation_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresenter | null> {
if (!event_presentation_id) event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id)
event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id) {
console.error('create_ae_obj__event_presenter: event_presentation_id is required');
console.error(
'create_ae_obj__event_presenter: event_presentation_id is required'
);
return null;
}
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
fields: { ...data_kv },
log_lvl
});
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_presenter_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_presenter({
api_cfg,
event_presentation_id,
event_presenter_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
event_presenter_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_presentation_id?: string;
event_presenter_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id?: string;
event_presenter_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
if (!event_presentation_id) event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id)
event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id) {
console.error('delete_ae_obj_id__event_presenter: event_presentation_id is required');
console.error(
'delete_ae_obj_id__event_presenter: event_presentation_id is required'
);
return null;
}
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
method,
log_lvl
});
if (try_cache) await db_events.presenter.delete(event_presenter_id);
return result;
const result = await api.delete_nested_ae_obj({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
method,
log_lvl
});
if (try_cache) await db_events.presenter.delete(event_presenter_id);
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_presenter({
api_cfg,
event_presentation_id,
event_presenter_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
event_presenter_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_presentation_id?: string;
event_presenter_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id?: string;
event_presenter_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresenter | null> {
if (!event_presentation_id) event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id)
event_presentation_id = get(events_slct).event_presentation_id;
if (!event_presentation_id) {
console.error('update_ae_obj__event_presenter: event_presentation_id is required');
console.error(
'update_ae_obj__event_presenter: event_presentation_id is required'
);
return null;
}
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-21 to Restore Full Aether Search Logic
@@ -320,7 +454,11 @@ export async function search__event_presenter({
view = 'default',
limit = 25,
offset = 0,
order_by_li = [{ sort: 'ASC' }, { given_name: 'ASC' }, { family_name: 'ASC' }],
order_by_li = [
{ sort: 'ASC' },
{ given_name: 'ASC' },
{ family_name: 'ASC' }
],
try_cache = true,
log_lvl = 0
}: {
@@ -342,29 +480,71 @@ export async function search__event_presenter({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresenter[]> {
const search_query: any = { q: '', and: [{ field: 'event_id', op: 'eq', value: event_id }] };
const search_query: any = {
q: '',
and: [{ field: 'event_id', op: 'eq', value: event_id }]
};
const params: key_val = {};
if (fulltext_search_qry_str && fulltext_search_qry_str.length > 2)
params['ft_qry'] = { default_qry_str: fulltext_search_qry_str };
if (ft_presenter_search_qry_str && ft_presenter_search_qry_str.length > 2)
params['ft_qry'] = { ...params['ft_qry'], event_presenter_li_qry_str: ft_presenter_search_qry_str };
if (like_search_qry_str) params['lk_qry'] = { default_qry_str: like_search_qry_str };
params['ft_qry'] = {
...params['ft_qry'],
event_presenter_li_qry_str: ft_presenter_search_qry_str
};
if (like_search_qry_str)
params['lk_qry'] = { default_qry_str: like_search_qry_str };
if (like_presentation_search_qry_str)
params['lk_qry'] = { ...params['lk_qry'], event_presentation_li_qry_str: like_presentation_search_qry_str };
params['lk_qry'] = {
...params['lk_qry'],
event_presentation_li_qry_str: like_presentation_search_qry_str
};
if (like_presenter_search_qry_str)
params['lk_qry'] = { ...params['lk_qry'], event_presenter_li_qry_str: like_presenter_search_qry_str };
if (agree !== null) search_query.and.push({ field: 'agree', op: 'eq', value: agree ? 1 : 0 });
if (biography === true) search_query.and.push({ field: 'biography', op: 'ne', value: '' });
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
params['lk_qry'] = {
...params['lk_qry'],
event_presenter_li_qry_str: like_presenter_search_qry_str
};
if (agree !== null)
search_query.and.push({
field: 'agree',
op: 'eq',
value: agree ? 1 : 0
});
if (biography === true)
search_query.and.push({ field: 'biography', op: 'ne', value: '' });
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', search_query, order_by_li, params, view, limit, offset, log_lvl });
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_presenter',
search_query,
order_by_li,
params,
view,
limit,
offset,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_presenter_props({
obj_li: result_li,
log_lvl
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return processed;
}
@@ -400,8 +580,16 @@ export async function email_sign_in__event_presenter({
session_name?: string | null;
presentation_name?: string | null;
}) {
if (!to_email || !person_id || !person_passcode || !event_id || !event_presenter_id) {
console.error('Missing required parameters for email_sign_in__event_presenter');
if (
!to_email ||
!person_id ||
!person_passcode ||
!event_id ||
!event_presenter_id
) {
console.error(
'Missing required parameters for email_sign_in__event_presenter'
);
return null;
}
const subject = `Pres Mgmt Hub Sign In Link for Presenter: ${to_name ?? 'Presenter'}`;
@@ -420,10 +608,75 @@ export async function email_sign_in__event_presenter({
}
export const properties_to_save = [
'id', 'event_presenter_id', 'event_presenter_id_random', 'external_id', 'code', 'event_id', 'event_session_id', 'event_presentation_id', 'event_person_id', 'person_id', 'person_profile_id', 'person_id_random', 'person_profile_id_random', 'pronouns', 'informal_name', 'title_names', 'given_name', 'middle_name', 'family_name', 'designations', 'professional_title', 'full_name', 'affiliations', 'email', 'biography', 'agree', 'comments', 'passcode', 'hide_event_launcher', 'data_json', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on', 'tmp_sort_1', 'tmp_sort_2', 'file_count', 'event_session_code', 'event_session_name', 'event_session_start_datetime', 'event_presentation_code', 'event_presentation_name', 'event_presentation_start_datetime', 'person_external_id', 'person_external_sys_id', 'person_given_name', 'person_family_name', 'person_full_name', 'person_professional_title', 'person_affiliations', 'person_primary_email', 'person_passcode'
'id',
'event_presenter_id',
'event_presenter_id_random',
'external_id',
'code',
'event_id',
'event_session_id',
'event_presentation_id',
'event_person_id',
'person_id',
'person_profile_id',
'person_id_random',
'person_profile_id_random',
'pronouns',
'informal_name',
'title_names',
'given_name',
'middle_name',
'family_name',
'designations',
'professional_title',
'full_name',
'affiliations',
'email',
'biography',
'agree',
'comments',
'passcode',
'hide_event_launcher',
'data_json',
'enable',
'hide',
'priority',
'sort',
'group',
'notes',
'created_on',
'updated_on',
'tmp_sort_1',
'tmp_sort_2',
'file_count',
'event_session_code',
'event_session_name',
'event_session_start_datetime',
'event_presentation_code',
'event_presentation_name',
'event_presentation_start_datetime',
'person_external_id',
'person_external_sys_id',
'person_given_name',
'person_family_name',
'person_full_name',
'person_professional_title',
'person_affiliations',
'person_primary_email',
'person_passcode'
];
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[]> {
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 (!obj_li || obj_li.length === 0) return [];
const processed_obj_li: T[] = [];
for (const original_obj of obj_li) {
@@ -439,28 +692,50 @@ async function _process_generic_props<T extends Record<string, any>>({ obj_li, o
if (processed_obj[randomIdKey]) {
(processed_obj as any).id = processed_obj[randomIdKey];
(processed_obj as any)[baseIdKey] = processed_obj[randomIdKey];
}
else if (processed_obj[baseIdKey]) (processed_obj as any).id = processed_obj[baseIdKey];
} else if (processed_obj[baseIdKey])
(processed_obj as any).id = processed_obj[baseIdKey];
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
if (specific_processor) processed_obj = await Promise.resolve(specific_processor(processed_obj));
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor)
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
processed_obj_li.push(processed_obj as T);
}
return processed_obj_li;
}
export async function process_ae_obj__event_presenter_props({ obj_li, log_lvl = 0 }: { obj_li: any[]; log_lvl?: number; }) {
return _process_generic_props({ obj_li, obj_type: 'event_presenter', log_lvl, specific_processor: (obj) => {
// String-Only ID Vision: Ensure linking IDs are the string versions for indexing
if (obj.event_presenter_id_random) obj.event_presenter_id = obj.event_presenter_id_random;
if (obj.event_presentation_id_random) obj.event_presentation_id = obj.event_presentation_id_random;
if (obj.event_session_id_random) obj.event_session_id = obj.event_session_id_random;
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}});
export async function process_ae_obj__event_presenter_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'event_presenter',
log_lvl,
specific_processor: (obj) => {
// String-Only ID Vision: Ensure linking IDs are the string versions for indexing
if (obj.event_presenter_id_random)
obj.event_presenter_id = obj.event_presenter_id_random;
if (obj.event_presentation_id_random)
obj.event_presentation_id = obj.event_presentation_id_random;
if (obj.event_session_id_random)
obj.event_session_id = obj.event_session_id_random;
if (obj.event_id_random) obj.event_id = obj.event_id_random;
return obj;
}
});
}

View File

@@ -44,7 +44,9 @@ export async function load_ae_obj_id__event_session({
}): Promise<ae_EventSession | null> {
const start_time = performance.now();
if (log_lvl) {
console.log(`🔎 [Trace] load_ae_obj_id__event_session: START (id=${event_session_id}, try_cache=${try_cache})`);
console.log(
`🔎 [Trace] load_ae_obj_id__event_session: START (id=${event_session_id}, try_cache=${try_cache})`
);
}
// Hierarchy Enforcement: Pulling presenters requires pulling presentations first
@@ -56,65 +58,167 @@ export async function load_ae_obj_id__event_session({
const cached = await db_events.session.get(event_session_id);
if (cached) {
const elapsed = (performance.now() - start_time).toFixed(2);
if (log_lvl) console.log(`✅ [Trace] load_ae_obj_id: CACHE HIT at ${elapsed}ms. Returning stale shell for id=${event_session_id}`);
if (log_lvl)
console.log(
`✅ [Trace] load_ae_obj_id: CACHE HIT at ${elapsed}ms. Returning stale shell for id=${event_session_id}`
);
// Background tasks: refresh parent and warm child caches (non-blocking)
_refresh_session_id_background({
api_cfg, event_session_id, view, try_cache,
inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li,
enabled, hidden, limit, offset, log_lvl: log_lvl > 1 ? log_lvl : 0
api_cfg,
event_session_id,
view,
try_cache,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl: log_lvl > 1 ? log_lvl : 0
});
// In SWR mode, we fire child loads in background to warm IDB for the view's LiveQueries
_handle_nested_loads(cached, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl: 0 });
_handle_nested_loads(cached, {
api_cfg,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl: 0
});
return cached; // Return immediately without awaiting nested loads
} else if (log_lvl) {
console.log(`⏳ [Trace] load_ae_obj_id: CACHE MISS at ${(performance.now() - start_time).toFixed(2)}ms for id=${event_session_id}`);
console.log(
`⏳ [Trace] load_ae_obj_id: CACHE MISS at ${(performance.now() - start_time).toFixed(2)}ms for id=${event_session_id}`
);
}
} catch (e) {
if (log_lvl) console.error(`❌ [Trace] load_ae_obj_id: Cache access error:`, e);
if (log_lvl)
console.error(
`❌ [Trace] load_ae_obj_id: Cache access error:`,
e
);
}
}
// 2. SLOW PATH: Wait for API
if (log_lvl) console.log(`🚀 [Trace] load_ae_obj_id: Proceeding to API path for id=${event_session_id}`);
return await _refresh_session_id_background({ api_cfg, event_session_id, view, try_cache, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, log_lvl });
if (log_lvl)
console.log(
`🚀 [Trace] load_ae_obj_id: Proceeding to API path for id=${event_session_id}`
);
return await _refresh_session_id_background({
api_cfg,
event_session_id,
view,
try_cache,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl
});
}
/**
* Internal background refresh for a single session
*/
async function _refresh_session_id_background({ api_cfg, event_session_id, view, try_cache, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, log_lvl }: any) {
async function _refresh_session_id_background({
api_cfg,
event_session_id,
view,
try_cache,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
log_lvl
}: any) {
const start_time = performance.now();
if (typeof navigator !== 'undefined' && !navigator.onLine) return null;
try {
if (log_lvl) console.log(`📡 [Trace] _refresh_session_id: API Fetching id=${event_session_id}`);
const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, view, log_lvl });
if (log_lvl)
console.log(
`📡 [Trace] _refresh_session_id: API Fetching id=${event_session_id}`
);
const result = await api.get_ae_obj({
api_cfg,
obj_type: 'event_session',
obj_id: event_session_id,
view,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__event_session_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
const elapsed = (performance.now() - start_time).toFixed(2);
if (log_lvl) console.log(`📦 [Trace] _refresh_session_id: Received from API at ${elapsed}ms (id=${processed_obj.id})`);
if (log_lvl)
console.log(
`📦 [Trace] _refresh_session_id: Received from API at ${elapsed}ms (id=${processed_obj.id})`
);
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
// CRITICAL FIX (2026-02-26): Yield to microtask queue so Dexie liveQuery observers
// fire before we return. Without this, component-mounted liveQueries may subscribe
// to IDB *before* the write completes, causing empty results on cold-start.
await Promise.resolve();
if (log_lvl) console.log(`💾 [Trace] _refresh_session_id: Saved to IDB cache.`);
if (log_lvl)
console.log(
`💾 [Trace] _refresh_session_id: Saved to IDB cache.`
);
}
// CRITICAL FIX (2026-02-26): Preserve parent's try_cache value when loading nested data.
// Previously set to `false`, which meant presentations/presenters were fetched from API
// but NEVER written to IndexedDB, causing "refresh twice" bug on cold-start.
// Now nested loads inherit parent's caching behavior for deterministic first-render.
return await _handle_nested_loads(processed_obj, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl });
return await _handle_nested_loads(processed_obj, {
api_cfg,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl
});
}
} catch (e) {
if (log_lvl) console.error(`❌ [Trace] _refresh_session_id: API error for id=${event_session_id}:`, e);
if (log_lvl)
console.error(
`❌ [Trace] _refresh_session_id: API error for id=${event_session_id}:`,
e
);
}
return null;
}
@@ -122,29 +226,65 @@ async function _refresh_session_id_background({ api_cfg, event_session_id, view,
/**
* Helper to handle nested collection loads for a session
*/
async function _handle_nested_loads(session_obj: any, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl }: any) {
async function _handle_nested_loads(
session_obj: any,
{
api_cfg,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl
}: any
) {
const start_time = performance.now();
const current_session_id = session_obj.id || session_obj.event_session_id;
if (!current_session_id) return session_obj;
const tasks = [];
if (inc_file_li) {
tasks.push(load_ae_obj_li__event_file({
api_cfg, for_obj_type: 'event_session', for_obj_id: current_session_id,
enabled, limit: 15, try_cache, log_lvl
}).then(res => session_obj.event_file_li = res));
tasks.push(
load_ae_obj_li__event_file({
api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
enabled,
limit: 15,
try_cache,
log_lvl
}).then((res) => (session_obj.event_file_li = res))
);
}
if (inc_presentation_li) {
tasks.push(load_ae_obj_li__event_presentation({
api_cfg, for_obj_type: 'event_session', for_obj_id: current_session_id,
inc_file_li: inc_all_file_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl
}).then(res => session_obj.event_presentation_li = res));
tasks.push(
load_ae_obj_li__event_presentation({
api_cfg,
for_obj_type: 'event_session',
for_obj_id: current_session_id,
inc_file_li: inc_all_file_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl
}).then((res) => (session_obj.event_presentation_li = res))
);
}
if (tasks.length > 0) {
await Promise.all(tasks);
if (log_lvl) console.log(`🔗 [Trace] _handle_nested_loads: Finished child collections in ${(performance.now() - start_time).toFixed(2)}ms`);
if (log_lvl)
console.log(
`🔗 [Trace] _handle_nested_loads: Finished child collections in ${(performance.now() - start_time).toFixed(2)}ms`
);
}
return session_obj;
}
@@ -191,7 +331,9 @@ export async function load_ae_obj_li__event_session({
}): Promise<ae_EventSession[]> {
const start_time = performance.now();
if (log_lvl) {
console.log(`🔎 [Trace] load_ae_obj_li__event_session: START (for=${for_obj_type}:${for_obj_id}, try_cache=${try_cache})`);
console.log(
`🔎 [Trace] load_ae_obj_li__event_session: START (for=${for_obj_type}:${for_obj_id}, try_cache=${try_cache})`
);
}
// Hierarchy Enforcement: Pulling presenters requires pulling presentations first
@@ -201,226 +343,409 @@ export async function load_ae_obj_li__event_session({
try {
// Robust lookup logic
let query;
if (for_obj_type === 'event_location') query = db_events.session.where('event_location_id').equals(for_obj_id);
else if (for_obj_type === 'event') query = db_events.session.where('event_id').equals(for_obj_id);
if (for_obj_type === 'event_location')
query = db_events.session
.where('event_location_id')
.equals(for_obj_id);
else if (for_obj_type === 'event')
query = db_events.session.where('event_id').equals(for_obj_id);
else query = db_events.session.where('for_id').equals(for_obj_id);
const cached_li = await query.toArray();
if (cached_li && cached_li.length > 0) {
const elapsed = (performance.now() - start_time).toFixed(2);
if (log_lvl) console.log(`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`);
if (log_lvl)
console.log(
`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`
);
// Background refresh (non-blocking)
_refresh_session_li_background({
api_cfg, for_obj_type, for_obj_id, view,
api_cfg,
for_obj_type,
for_obj_id,
view,
// Optimization: Disable nested loads for list members to prevent request storms
inc_file_li: false, inc_all_file_li: false, inc_presentation_li: false, inc_presenter_li: false,
enabled, hidden, limit, offset, order_by_li, try_cache,
inc_file_li: false,
inc_all_file_li: false,
inc_presentation_li: false,
inc_presenter_li: false,
enabled,
hidden,
limit,
offset,
order_by_li,
try_cache,
log_lvl: log_lvl > 1 ? log_lvl : 0
});
return cached_li;
} else if (log_lvl) {
console.log(`⏳ [Trace] load_ae_obj_li: CACHE MISS at ${(performance.now() - start_time).toFixed(2)}ms for type=${for_obj_type} id=${for_obj_id}`);
console.log(
`⏳ [Trace] load_ae_obj_li: CACHE MISS at ${(performance.now() - start_time).toFixed(2)}ms for type=${for_obj_type} id=${for_obj_id}`
);
}
} catch (e) {
if (log_lvl) console.error(`❌ [Trace] load_ae_obj_li: Cache access error:`, e);
if (log_lvl)
console.error(
`❌ [Trace] load_ae_obj_li: Cache access error:`,
e
);
}
}
return await _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, view, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl });
return await _refresh_session_li_background({
api_cfg,
for_obj_type,
for_obj_id,
view,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
order_by_li,
try_cache,
log_lvl
});
}
async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, view, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl }: any) {
async function _refresh_session_li_background({
api_cfg,
for_obj_type,
for_obj_id,
view,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
order_by_li,
try_cache,
log_lvl
}: any) {
const start_time = performance.now();
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
try {
if (log_lvl) console.log(`📡 [Trace] _refresh_session_li: API Fetching for=${for_obj_type}:${for_obj_id} (view=${view})`);
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_session', for_obj_type, for_obj_id, view, enabled, hidden, limit, offset, order_by_li, log_lvl });
if (log_lvl)
console.log(
`📡 [Trace] _refresh_session_li: API Fetching for=${for_obj_type}:${for_obj_id} (view=${view})`
);
const result_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'event_session',
for_obj_type,
for_obj_id,
view,
enabled,
hidden,
limit,
offset,
order_by_li,
log_lvl
});
if (result_li) {
const processed = await process_ae_obj__event_session_props({ obj_li: result_li, log_lvl });
const processed = await process_ae_obj__event_session_props({
obj_li: result_li,
log_lvl
});
const elapsed = (performance.now() - start_time).toFixed(2);
if (log_lvl) console.log(`📦 [Trace] _refresh_session_li: Received ${processed.length} items from API at ${elapsed}ms.`);
if (log_lvl)
console.log(
`📦 [Trace] _refresh_session_li: Received ${processed.length} items from API at ${elapsed}ms.`
);
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: processed, properties_to_save, log_lvl });
if (log_lvl) console.log(`💾 [Trace] _refresh_session_li: Saved to IDB cache.`);
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: processed,
properties_to_save,
log_lvl
});
if (log_lvl)
console.log(
`💾 [Trace] _refresh_session_li: Saved to IDB cache.`
);
}
// Fire nested loads for each session only if explicitly requested (usually only for single objects)
if (inc_file_li || inc_presentation_li) {
processed.forEach(s => {
_handle_nested_loads(s, { api_cfg, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, try_cache, log_lvl: 0 });
processed.forEach((s) => {
_handle_nested_loads(s, {
api_cfg,
inc_file_li,
inc_all_file_li,
inc_presentation_li,
inc_presenter_li,
enabled,
hidden,
limit,
offset,
try_cache,
log_lvl: 0
});
});
}
return processed;
}
} catch (e) {
if (log_lvl) console.error(`❌ [Trace] _refresh_session_li: API error:`, e);
if (log_lvl)
console.error(`❌ [Trace] _refresh_session_li: API error:`, e);
}
return [];
}
export async function create_ae_obj__event_session({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventSession | null> {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('create_ae_obj__event_session: event_id is required');
return null;
}
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
const result = await api.create_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
export async function delete_ae_obj_id__event_session({
api_cfg,
event_id,
event_session_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_session_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
event_session_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
event_session_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('delete_ae_obj_id__event_session: event_id is required');
return null;
}
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
method,
log_lvl
});
if (try_cache) await db_events.session.delete(event_session_id);
return result;
const result = await api.delete_nested_ae_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
method,
log_lvl
});
if (try_cache) await db_events.session.delete(event_session_id);
return result;
}
export async function update_ae_obj__event_session({
api_cfg,
event_id,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id?: string;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id?: string;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventSession | null> {
if (!event_id) event_id = get(slct).event_id;
if (!event_id) {
console.error('update_ae_obj__event_session: event_id is required');
return null;
}
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
export async function search__event_session({
api_cfg, event_id, fulltext_search_qry_str = '', ft_presenter_search_qry_str = '', like_search_qry_str = '', like_presentation_search_qry_str = '', like_presenter_search_qry_str = '', like_poc_name_qry_str = '', location_name = null, qry_files = null, qry_poc_agree = null, qry_poc_kv_json = null, qry_start_datetime = null, enabled = 'enabled', hidden = 'not_hidden', view = 'default', limit = 50, offset = 0, order_by_li = [{ sort: 'ASC' }, { start_datetime: 'ASC' }, { name: 'ASC' }], try_cache = true, log_lvl = 0
api_cfg,
event_id,
fulltext_search_qry_str = '',
ft_presenter_search_qry_str = '',
like_search_qry_str = '',
like_presentation_search_qry_str = '',
like_presenter_search_qry_str = '',
like_poc_name_qry_str = '',
location_name = null,
qry_files = null,
qry_poc_agree = null,
qry_poc_kv_json = null,
qry_start_datetime = null,
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 50,
offset = 0,
order_by_li = [{ sort: 'ASC' }, { start_datetime: 'ASC' }, { name: 'ASC' }],
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_id: string; fulltext_search_qry_str?: string; ft_presenter_search_qry_str?: string | null; like_search_qry_str?: string; like_presentation_search_qry_str?: string; like_presenter_search_qry_str?: string; like_poc_name_qry_str?: string; location_name?: null | string; qry_files?: null | boolean; qry_poc_agree?: null | boolean; qry_poc_kv_json?: null | boolean; qry_start_datetime?: string | null; enabled?: 'enabled' | 'all' | 'not_enabled'; hidden?: 'hidden' | 'all' | 'not_hidden'; view?: string; limit?: number; offset?: number; order_by_li?: any; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
fulltext_search_qry_str?: string;
ft_presenter_search_qry_str?: string | null;
like_search_qry_str?: string;
like_presentation_search_qry_str?: string;
like_presenter_search_qry_str?: string;
like_poc_name_qry_str?: string;
location_name?: null | string;
qry_files?: null | boolean;
qry_poc_agree?: null | boolean;
qry_poc_kv_json?: null | boolean;
qry_start_datetime?: string | null;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: any;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventSession[]> {
const search_query: any = { q: '', and: [{ field: 'event_id', op: 'eq', value: event_id }] };
const search_query: any = {
q: '',
and: [{ field: 'event_id', op: 'eq', value: event_id }]
};
if (fulltext_search_qry_str || ft_presenter_search_qry_str) {
const ft: any = {};
if (fulltext_search_qry_str && fulltext_search_qry_str.length > 2) ft['default_qry_str'] = fulltext_search_qry_str;
if (ft_presenter_search_qry_str && ft_presenter_search_qry_str.length > 2) ft['event_presenter_li_qry_str'] = ft_presenter_search_qry_str;
if (fulltext_search_qry_str && fulltext_search_qry_str.length > 2)
ft['default_qry_str'] = fulltext_search_qry_str;
if (
ft_presenter_search_qry_str &&
ft_presenter_search_qry_str.length > 2
)
ft['event_presenter_li_qry_str'] = ft_presenter_search_qry_str;
if (Object.keys(ft).length) search_query.params = { ft_qry: ft };
}
if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
if (enabled === 'enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
else if (enabled === 'not_enabled')
search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
if (hidden === 'hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
else if (hidden === 'not_hidden')
search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
if (location_name) {
search_query.and.push({ field: 'event_location_name', op: 'eq', value: location_name });
search_query.and.push({
field: 'event_location_name',
op: 'eq',
value: location_name
});
}
const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_session', search_query, order_by_li, view, limit, offset, log_lvl });
const result_li = await api.search_ae_obj({
api_cfg,
obj_type: 'event_session',
search_query,
order_by_li,
view,
limit,
offset,
log_lvl
});
// Handle V3 API envelope
let valid_result_li: ae_EventSession[] = [];
if (Array.isArray(result_li)) {
valid_result_li = result_li;
} else if (result_li && typeof result_li === 'object' && Array.isArray((result_li as any).data)) {
} else if (
result_li &&
typeof result_li === 'object' &&
Array.isArray((result_li as any).data)
) {
valid_result_li = (result_li as any).data;
}
if (valid_result_li && valid_result_li.length > 0) {
const processed = await process_ae_obj__event_session_props({ obj_li: valid_result_li, log_lvl });
const processed = await process_ae_obj__event_session_props({
obj_li: valid_result_li,
log_lvl
});
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: processed, properties_to_save, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return processed;
}
@@ -429,18 +754,103 @@ export async function search__event_session({
export const qry__event_session = search__event_session;
export async function email_sign_in__event_session({ api_cfg, to_email, to_name, base_url, person_id, person_passcode, event_id, event_session_id, session_name }: { api_cfg: any; to_email: string; to_name: string; base_url: string; person_id: string; person_passcode: string; event_id: string; event_session_id: string; session_name: string; }) {
export async function email_sign_in__event_session({
api_cfg,
to_email,
to_name,
base_url,
person_id,
person_passcode,
event_id,
event_session_id,
session_name
}: {
api_cfg: any;
to_email: string;
to_name: string;
base_url: string;
person_id: string;
person_passcode: string;
event_id: string;
event_session_id: string;
session_name: string;
}) {
const subject = `Pres Mgmt Hub Sign In Link for ${session_name}`;
const sign_in_url = encodeURI(`${base_url}/events/${event_id}/session/${event_session_id}?person_id=${person_id}&person_pass=${person_passcode}`);
const sign_in_url = encodeURI(
`${base_url}/events/${event_id}/session/${event_session_id}?person_id=${person_id}&person_pass=${person_passcode}`
);
const body_html = `<div>${to_name},<p>Your sign-in link for ${session_name}: <a href="${sign_in_url}">${sign_in_url}</a></p></div>`;
return await api.send_email({ api_cfg, from_email: 'noreply+presmgmt@oneskyit.com', from_name: 'Aether Pres Mgmt', to_email, subject, body_html });
return await api.send_email({
api_cfg,
from_email: 'noreply+presmgmt@oneskyit.com',
from_name: 'Aether Pres Mgmt',
to_email,
subject,
body_html
});
}
export const properties_to_save = [
'id', 'event_session_id', 'event_session_id_random', 'external_id', 'code', 'for_type', 'for_id', 'for_id_random', 'type_code', 'event_id', 'event_location_id', 'poc_person_id', 'poc_agree', 'poc_kv_json', 'name', 'description', 'start_datetime', 'end_datetime', 'passcode', 'hide_event_launcher', 'alert', 'alert_msg', 'data_json', 'ux_mode', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on', 'tmp_sort_1', 'tmp_sort_2', 'file_count', 'file_count_all', 'internal_use_count', 'event_file_id_li_json', 'poc_person_given_name', 'poc_person_family_name', 'poc_person_full_name', 'poc_person_primary_email', 'poc_person_passcode', 'event_name', 'event_location_code', 'event_location_name', 'event_presentation_li'
'id',
'event_session_id',
'event_session_id_random',
'external_id',
'code',
'for_type',
'for_id',
'for_id_random',
'type_code',
'event_id',
'event_location_id',
'poc_person_id',
'poc_agree',
'poc_kv_json',
'name',
'description',
'start_datetime',
'end_datetime',
'passcode',
'hide_event_launcher',
'alert',
'alert_msg',
'data_json',
'ux_mode',
'enable',
'hide',
'priority',
'sort',
'group',
'notes',
'created_on',
'updated_on',
'tmp_sort_1',
'tmp_sort_2',
'file_count',
'file_count_all',
'internal_use_count',
'event_file_id_li_json',
'poc_person_given_name',
'poc_person_family_name',
'poc_person_full_name',
'poc_person_primary_email',
'poc_person_passcode',
'event_name',
'event_location_code',
'event_location_name',
'event_presentation_li'
];
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[]> {
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 (!obj_li || obj_li.length === 0) return [];
const processed_obj_li: T[] = [];
for (const original_obj of obj_li) {
@@ -456,22 +866,40 @@ async function _process_generic_props<T extends Record<string, any>>({ obj_li, o
if (processed_obj[randomIdKey]) {
(processed_obj as any).id = processed_obj[randomIdKey];
(processed_obj as any)[baseIdKey] = processed_obj[randomIdKey];
}
else if (processed_obj[baseIdKey]) (processed_obj as any).id = processed_obj[baseIdKey];
} else if (processed_obj[baseIdKey])
(processed_obj as any).id = processed_obj[baseIdKey];
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 ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
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}`;
if (specific_processor) processed_obj = await Promise.resolve(specific_processor(processed_obj));
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor)
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
processed_obj_li.push(processed_obj as T);
}
return processed_obj_li;
}
export async function process_ae_obj__event_session_props({ obj_li, log_lvl = 0 }: { obj_li: any[]; log_lvl?: number; }) {
return _process_generic_props({ obj_li, obj_type: 'event_session', log_lvl });
}
export async function process_ae_obj__event_session_props({
obj_li,
log_lvl = 0
}: {
obj_li: any[];
log_lvl?: number;
}) {
return _process_generic_props({
obj_li,
obj_type: 'event_session',
log_lvl
});
}

Some files were not shown because too many files have changed in this diff Show More