pre-refactor
This commit is contained in:
@@ -29,6 +29,9 @@ export async function apiFetch(url, options = {}) {
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { method, headers, body: options.body, credentials: 'same-origin', ...opts });
|
||||
if (opts.signal?.aborted) {
|
||||
return { ok: false, data: null, error: 'Aborted', status: 0 };
|
||||
}
|
||||
if (res.status === 401) {
|
||||
window.location.reload();
|
||||
return { ok: false, data: null, error: 'Session expired', status: 401 };
|
||||
|
||||
@@ -20,13 +20,19 @@ import { reactive } from './reactivity.js';
|
||||
import { h } from './vdom.js';
|
||||
import { _compExpandedCache } from './render.js';
|
||||
|
||||
/**
|
||||
* Registry of mounted components: key → { state, subscriptions, loadAbort, entry }
|
||||
*/
|
||||
/** Registry of mounted components: key → { state, subscriptions, loadAbort, entry, isLoading } */
|
||||
const _mounted = new Map();
|
||||
|
||||
/**
|
||||
* External subscribe function from websocket.js.
|
||||
/** Check whether a state object belongs to a currently mounted component.
|
||||
* Used by websocket.js to skip auto-refresh for unmounted pages. */
|
||||
export function isComponentStateMounted(state) {
|
||||
for (const entry of _mounted.values()) {
|
||||
if (entry.state === state) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** External subscribe function from websocket.js.
|
||||
* Set via setSubscribeFn() when the websocket module initializes.
|
||||
*/
|
||||
let _subscribeFn = null;
|
||||
@@ -67,29 +73,45 @@ export function definePage(def) {
|
||||
* enters the tree for the first time.
|
||||
*/
|
||||
export function mountComponent(key, renderer) {
|
||||
// Prevent duplicate mounts when normalization loses #comp tracking
|
||||
if (_mounted.has(key)) return;
|
||||
|
||||
const pd = renderer._pageDef;
|
||||
if (!pd) return;
|
||||
|
||||
const entry = {
|
||||
state: pd.state,
|
||||
subscriptions: [],
|
||||
loadAbort: null,
|
||||
};
|
||||
let entry = _mounted.get(key);
|
||||
|
||||
_mounted.set(key, entry);
|
||||
if (entry) {
|
||||
// Re-mount of an already-mounted page: restart load with fresh AbortController
|
||||
if (entry.loadAbort) {
|
||||
entry.loadAbort.abort();
|
||||
}
|
||||
entry.requestId++;
|
||||
entry.loadAbort = null;
|
||||
} else {
|
||||
// Fresh mount
|
||||
entry = {
|
||||
state: pd.state,
|
||||
subscriptions: [],
|
||||
loadAbort: null,
|
||||
requestId: 0,
|
||||
};
|
||||
_mounted.set(key, entry);
|
||||
}
|
||||
|
||||
// Fire load
|
||||
// Clear error on re-mount; load() decides loading vs refreshing
|
||||
pd.state.error = null;
|
||||
|
||||
// Fire load with fresh AbortController
|
||||
if (pd.load) {
|
||||
const abortController = new AbortController();
|
||||
entry.loadAbort = abortController;
|
||||
pd.load(pd.state, abortController);
|
||||
entry.requestId++;
|
||||
entry.isLoading = true;
|
||||
Promise.resolve()
|
||||
.then(() => pd.load(pd.state, abortController, entry))
|
||||
.finally(() => { entry.isLoading = false; });
|
||||
}
|
||||
|
||||
// Register WS subscriptions
|
||||
if (_subscribeFn && pd.subscribe.length) {
|
||||
// Register WS subscriptions (only on fresh mount)
|
||||
if (!entry.subscriptions.length && _subscribeFn && pd.subscribe.length) {
|
||||
for (const topic of pd.subscribe) {
|
||||
const unsub = _subscribeFn(renderer, topic, pd.load, pd.state);
|
||||
if (unsub) entry.subscriptions.push(unsub);
|
||||
@@ -111,6 +133,8 @@ export function unmountComponent(key, renderer) {
|
||||
if (entry.loadAbort) {
|
||||
entry.loadAbort.abort();
|
||||
}
|
||||
// Invalidate any in-flight callbacks
|
||||
entry.requestId++;
|
||||
|
||||
// Unsubscribe from WS
|
||||
for (const unsub of entry.subscriptions) {
|
||||
|
||||
@@ -218,7 +218,11 @@ function diffContainer(container, prev, vnodes) {
|
||||
if (oldDom?.nodeType === Node.ELEMENT_NODE) sweepDom(oldDom);
|
||||
const nd = createDom(newV);
|
||||
_vnodeDom.set(newV, nd);
|
||||
if (oldDom?.parentNode) oldDom.parentNode.replaceChild(nd, oldDom);
|
||||
if (oldDom?.parentNode) {
|
||||
oldDom.parentNode.replaceChild(nd, oldDom);
|
||||
} else if (nd.parentNode !== container) {
|
||||
container.insertBefore(nd, lastDom ? lastDom.nextSibling : null);
|
||||
}
|
||||
lastDom = nd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* auto-refresh messages from the backend can trigger page reloads.
|
||||
*/
|
||||
|
||||
import { setSubscribeFn } from './component.js';
|
||||
import { setSubscribeFn, isComponentStateMounted } from './component.js';
|
||||
|
||||
const _wsSubs = new Map();
|
||||
let _wsConn = null;
|
||||
@@ -74,7 +74,7 @@ function handleMessage(msg) {
|
||||
}
|
||||
|
||||
for (const s of _wsSubs.values()) {
|
||||
if (s.unsubscribed) continue;
|
||||
if (s.unsubscribed || !isComponentStateMounted(s.state)) continue;
|
||||
if (s.topic === '*') {
|
||||
s.loadFn(s.state);
|
||||
} else if (topics.some(t => t === s.topic || t === '*')) {
|
||||
|
||||
Reference in New Issue
Block a user