mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 13:37:28 +00:00
* test: verify pre-commit hook * fix: clean up code formatting and improve readability across multiple components * chore: update subproject commit reference in frontend/ee * chore: update eslint to version 9.26.0 and remove unused dependencies from package.json fix: update submodule reference in server/ee * chore: refactor ESLint configuration and add quiet linting script; update components to disable specific ESLint rules * chore: add GitHub Copilot review instructions for App Builder team Covers backward compatibility rules, styling conventions, state management, resolution system, widget definitions, and common review flags. * chore: add review instructions for App Builder, Data Migrations, Server Widget Config, Widget Components, and Widget Config * Enhance TypeScript support in frontend configuration - Added TypeScript parser and linting rules to ESLint configuration. - Updated Babel configuration to include TypeScript preset. - Modified package.json and package-lock.json to include TypeScript and related dependencies. - Introduced tsconfig.json for TypeScript compiler options. - Updated Webpack configuration to support .ts and .tsx file extensions. - Adjusted linting and formatting scripts to include TypeScript files. * chore: update TypeScript ESLint packages and subproject commits --------- Co-authored-by: kavinvenkatachalam <kavin.saratha@gmail.com> Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com>
107 lines
3.4 KiB
JavaScript
107 lines
3.4 KiB
JavaScript
import React, { Suspense, useEffect, createContext, useContext, useCallback, useRef, useState } from 'react';
|
|
import { TJLoader } from '@/_ui/TJLoader';
|
|
import cx from 'classnames';
|
|
|
|
const SuspenseCountContext = createContext();
|
|
|
|
// Added this to track the number of pending Suspense components
|
|
// deferCheck: When true, defers the resolution check to handle nested lazy loading (e.g., ModuleContainer -> Table)
|
|
export const SuspenseCountProvider = ({ onAllResolved, children, deferCheck = false }) => {
|
|
const pendingCount = useRef(0);
|
|
const hasInitialized = useRef(false);
|
|
const hasResolved = useRef(false);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const checkAndResolve = useCallback(() => {
|
|
if (pendingCount.current === 0 && hasInitialized.current && !hasResolved.current) {
|
|
hasResolved.current = true;
|
|
setIsLoading(false);
|
|
onAllResolved();
|
|
}
|
|
}, [onAllResolved]);
|
|
|
|
const increment = useCallback(() => {
|
|
pendingCount.current += 1;
|
|
}, []);
|
|
|
|
const decrement = useCallback(() => {
|
|
pendingCount.current -= 1;
|
|
if (deferCheck) {
|
|
// Defer to allow newly mounted component's effects to complete
|
|
setTimeout(() => checkAndResolve(), 0);
|
|
} else {
|
|
checkAndResolve();
|
|
}
|
|
}, [checkAndResolve, deferCheck]);
|
|
|
|
// After first render, mark initialized and check if already ready
|
|
useEffect(() => {
|
|
hasInitialized.current = true;
|
|
if (deferCheck) {
|
|
// Defer the check to ensure all child component effects have completed.
|
|
// This fixes a race condition in module preview where cached lazy components
|
|
// don't trigger Suspense fallbacks, causing onAllResolved to fire too early.
|
|
const timeoutId = setTimeout(() => checkAndResolve(), 0);
|
|
return () => clearTimeout(timeoutId);
|
|
} else {
|
|
checkAndResolve();
|
|
}
|
|
}, [checkAndResolve, deferCheck]);
|
|
|
|
return (
|
|
<SuspenseCountContext.Provider value={{ increment, decrement, isLoading }}>
|
|
{children}
|
|
</SuspenseCountContext.Provider>
|
|
);
|
|
};
|
|
|
|
// Hook to check if lazy components are still loading
|
|
export const useSuspenseLoading = () => {
|
|
const context = useContext(SuspenseCountContext);
|
|
return context?.isLoading ?? false;
|
|
};
|
|
|
|
// Fallback component that tracks mount/unmount
|
|
const SuspenseFallbackTracker = ({ fallback }) => {
|
|
const { increment, decrement } = useContext(SuspenseCountContext);
|
|
|
|
useEffect(() => {
|
|
increment();
|
|
return () => decrement();
|
|
}, [increment, decrement]);
|
|
|
|
return fallback;
|
|
};
|
|
|
|
// Drop-in replacement for Suspense that tracks loading state
|
|
export const TrackedSuspense = ({ fallback = null, children }) => {
|
|
const context = useContext(SuspenseCountContext);
|
|
|
|
// If no provider, fall back to regular Suspense
|
|
if (!context) {
|
|
return <Suspense fallback={fallback}>{children}</Suspense>;
|
|
}
|
|
|
|
return <Suspense fallback={<SuspenseFallbackTracker fallback={fallback} />}>{children}</Suspense>;
|
|
};
|
|
|
|
// Loading overlay shown while lazy components are resolving
|
|
export const SuspenseLoadingOverlay = ({ darkMode }) => {
|
|
const isLoading = useSuspenseLoading();
|
|
|
|
if (!isLoading) return null;
|
|
|
|
return (
|
|
<div
|
|
className={cx('suspense-loading-overlay tw-absolute tw-inset-0 tw-overflow-hidden', {
|
|
'theme-dark dark-theme': darkMode,
|
|
})}
|
|
>
|
|
<div className="tw-sticky tw-top-0 tw-h-screen tw-flex tw-items-center tw-justify-center">
|
|
<div className="suspense-loader-wrapper">
|
|
<TJLoader />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|