Add model constant export to JS, rsxapp hydration, on_stop lifecycle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-10 09:08:20 +00:00
parent 611e269465
commit d047b49d39
58 changed files with 207 additions and 58 deletions

View File

@@ -222,6 +222,24 @@ class Rsx {
return !window.rsxapp.debug;
}
/**
* Get the current logged-in user model instance
* Returns the hydrated ORM model if available, or the raw data object
* @returns {Rsx_Js_Model|Object|null} User model instance or null if not logged in
*/
static user() {
return window.rsxapp?.user || null;
}
/**
* Get the current site model instance
* Returns the hydrated ORM model if available, or the raw data object
* @returns {Rsx_Js_Model|Object|null} Site model instance or null if not set
*/
static site() {
return window.rsxapp?.site || null;
}
// Generates a unique number for the application instance
static uid() {
if (typeof Rsx._uid == undef) {
@@ -601,6 +619,43 @@ class Rsx {
}
}
/**
* Hydrate rsxapp.user and rsxapp.site into ORM model instances
*
* Checks if window.rsxapp.user and window.rsxapp.site contain raw data objects
* with __MODEL markers, and if the corresponding model classes are available,
* replaces them with proper ORM instances.
*
* This enables code like:
* const user = Rsx.user();
* await user.some_relationship(); // Works because user is a proper model instance
*/
static _hydrate_rsxapp_models() {
if (!window.rsxapp) {
return;
}
// Hydrate user if present and has __MODEL marker
if (window.rsxapp.user && window.rsxapp.user.__MODEL) {
const UserClass = Manifest.get_class_by_name(window.rsxapp.user.__MODEL);
// Check class exists and extends Rsx_Js_Model - @JS-DEFENSIVE-01-EXCEPTION - dynamic model resolution
if (UserClass && Manifest.js_is_subclass_of(UserClass, Rsx_Js_Model)) {
window.rsxapp.user = new UserClass(window.rsxapp.user);
console_debug('RSX_INIT', `Hydrated rsxapp.user as ${window.rsxapp.user.__MODEL}`);
}
}
// Hydrate site if present and has __MODEL marker
if (window.rsxapp.site && window.rsxapp.site.__MODEL) {
const SiteClass = Manifest.get_class_by_name(window.rsxapp.site.__MODEL);
// Check class exists and extends Rsx_Js_Model - @JS-DEFENSIVE-01-EXCEPTION - dynamic model resolution
if (SiteClass && Manifest.js_is_subclass_of(SiteClass, Rsx_Js_Model)) {
window.rsxapp.site = new SiteClass(window.rsxapp.site);
console_debug('RSX_INIT', `Hydrated rsxapp.site as ${window.rsxapp.site.__MODEL}`);
}
}
}
/**
* Internal: Execute multi-phase initialization for all registered classes
* This runs various initialization phases in order to properly set up the application
@@ -617,6 +672,10 @@ class Rsx {
// Setup exception handlers first, before any initialization phases
Rsx._setup_exception_handlers();
// Hydrate rsxapp.user and rsxapp.site into ORM model instances
// This must happen early, before any code tries to use these objects
Rsx._hydrate_rsxapp_models();
// Get all registered classes from the manifest
const all_classes = Manifest.get_all_classes();
@@ -697,7 +756,6 @@ class Rsx {
y: window.scrollY
};
sessionStorage.setItem(Rsx._SCROLL_STORAGE_KEY, JSON.stringify(scroll_data));
console.log('[Rsx Scroll] Saved:', scroll_data.x, scroll_data.y, 'for', scroll_data.url);
}, 100); // 100ms debounce
}
@@ -707,61 +765,46 @@ class Rsx {
* @private
*/
static _restore_scroll_on_refresh() {
console.log('[Rsx Scroll] _restore_scroll_on_refresh called');
// Set up scroll listener to continuously save position
window.addEventListener('scroll', Rsx._save_scroll_position, { passive: true });
console.log('[Rsx Scroll] Scroll listener attached');
// Check if this is a page refresh using Performance API
const nav_entries = performance.getEntriesByType('navigation');
console.log('[Rsx Scroll] Navigation entries:', nav_entries.length);
if (nav_entries.length === 0) {
console.log('[Rsx Scroll] No navigation entries found, skipping restore');
return;
}
const nav_type = nav_entries[0].type;
console.log('[Rsx Scroll] Navigation type:', nav_type);
if (nav_type !== 'reload') {
console.log('[Rsx Scroll] Not a reload (type=' + nav_type + '), skipping restore');
return;
}
// This is a refresh - try to restore scroll position
const stored = sessionStorage.getItem(Rsx._SCROLL_STORAGE_KEY);
console.log('[Rsx Scroll] Stored scroll data:', stored);
if (!stored) {
console.log('[Rsx Scroll] No stored scroll position found');
return;
}
try {
const scroll_data = JSON.parse(stored);
const current_url = window.location.pathname + window.location.search;
console.log('[Rsx Scroll] Stored URL:', scroll_data.url, 'Current URL:', current_url);
// Only restore if URL matches
if (scroll_data.url !== current_url) {
console.log('[Rsx Scroll] URL mismatch, skipping restore');
return;
}
// Restore scroll position instantly
console.log('[Rsx Scroll] Restoring scroll to:', scroll_data.x, scroll_data.y);
window.scrollTo({
left: scroll_data.x,
top: scroll_data.y,
behavior: 'instant'
});
console.log('[Rsx Scroll] Restored scroll position on refresh:', scroll_data.x, scroll_data.y);
// Clear stored position after successful restore
sessionStorage.removeItem(Rsx._SCROLL_STORAGE_KEY);
} catch (e) {
// Invalid JSON or other error - ignore
console.log('[Rsx Scroll] Error restoring scroll:', e.message);
sessionStorage.removeItem(Rsx._SCROLL_STORAGE_KEY);
}
}