Skip to content

Commit

Permalink
Dom loader suspense (#625)
Browse files Browse the repository at this point in the history
* WIP

* update rq

* loader

* comment

* fix query key
  • Loading branch information
Janpot authored Jul 5, 2022
1 parent ac031f7 commit c535434
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 108 deletions.
2 changes: 1 addition & 1 deletion packages/toolpad-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"react-hook-form": "^7.33.1",
"react-inspector": "^5.1.1",
"react-is": "^18.1.0",
"react-query": "^3.39.1",
"react-query": "^4.0.0-beta.23",
"react-router-dom": "^6.3.0",
"react-split-pane": "^0.1.92",
"serialize-javascript": "^6.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface CodeComponentEditorContentProps {
function CodeComponentEditorContent({ theme, codeComponentNode }: CodeComponentEditorContentProps) {
const domApi = useDomApi();

const { data: typings } = useQuery<Record<string, string>>('/typings.json', async () => {
const { data: typings } = useQuery<Record<string, string>>(['/typings.json'], async () => {
return fetch('/typings.json').then((res) => res.json());
});

Expand Down
18 changes: 3 additions & 15 deletions packages/toolpad-app/src/components/AppEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from 'react';
import { styled, Alert, Box, CircularProgress } from '@mui/material';
import { styled } from '@mui/material';
import { Route, Routes, useParams, Navigate } from 'react-router-dom';
import { JsRuntimeProvider } from '@mui/toolpad-core/runtime';
import PageEditor from './PageEditor';
import DomProvider, { useDom, useDomLoader } from '../DomLoader';
import DomProvider, { useDom } from '../DomLoader';
import * as appDom from '../../appDom';
import CodeComponentEditor from './CodeComponentEditor';
import ConnectionEditor from './ConnectionEditor';
Expand Down Expand Up @@ -75,21 +75,9 @@ export interface EditorContentProps {
}

function EditorContent({ appId }: EditorContentProps) {
const domLoader = useDomLoader();

return (
<EditorRoot>
{domLoader.dom ? (
<FileEditor appId={appId} />
) : (
<Box flex={1} display="flex" alignItems="center" justifyContent="center">
{domLoader.loadError ? (
<Alert severity="error">{domLoader.loadError}</Alert>
) : (
<CircularProgress />
)}
</Box>
)}
<FileEditor appId={appId} />
</EditorRoot>
);
}
Expand Down
86 changes: 13 additions & 73 deletions packages/toolpad-app/src/components/DomLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,15 @@ import { update } from '../utils/immutability';
import client from '../api';
import useShortcut from '../utils/useShortcut';
import useDebouncedHandler from '../utils/useDebouncedHandler';
import { createProvidedContext } from '../utils/react';

export type DomAction =
| {
type: 'DOM_LOADING';
}
| {
type: 'DOM_LOADED';
dom: appDom.AppDom;
}
| {
type: 'DOM_SAVING';
}
| {
type: 'DOM_SAVED';
}
| {
type: 'DOM_LOADING_ERROR';
error: string;
}
| {
type: 'DOM_SAVING_ERROR';
error: string;
Expand Down Expand Up @@ -130,26 +120,6 @@ export function domLoaderReducer(state: DomLoader, action: DomAction): DomLoader
}

switch (action.type) {
case 'DOM_LOADING': {
return update(state, {
loading: true,
loadError: null,
});
}
case 'DOM_LOADED': {
return update(state, {
loading: false,
loadError: null,
dom: action.dom,
unsavedChanges: 0,
});
}
case 'DOM_LOADING_ERROR': {
return update(state, {
loading: false,
loadError: action.error,
});
}
case 'DOM_SAVING': {
return update(state, {
saving: true,
Expand All @@ -165,7 +135,6 @@ export function domLoaderReducer(state: DomLoader, action: DomAction): DomLoader
}
case 'DOM_SAVING_ERROR': {
return update(state, {
loading: false,
saving: false,
saveError: action.error,
});
Expand Down Expand Up @@ -249,30 +218,19 @@ function createDomApi(dispatch: React.Dispatch<DomAction>) {
}

interface DomLoader {
dom: appDom.AppDom | null;
dom: appDom.AppDom;
saving: boolean;
unsavedChanges: number;
loading: boolean;
loadError: string | null;
saveError: string | null;
}

const DomLoaderContext = React.createContext<DomLoader>({
saving: false,
unsavedChanges: 0,
loading: false,
loadError: null,
saveError: null,
dom: null,
});
const [useDomLoader, DomLoaderProvider] = createProvidedContext<DomLoader>('DomLoader');

const DomApiContext = React.createContext<DomApi>(createDomApi(() => undefined));

export type DomApi = ReturnType<typeof createDomApi>;

export function useDomLoader(): DomLoader {
return React.useContext(DomLoaderContext);
}
export { useDomLoader };

export function useDom(): appDom.AppDom {
const { dom } = useDomLoader();
Expand All @@ -292,38 +250,20 @@ export interface DomContextProps {
}

export default function DomProvider({ appId, children }: DomContextProps) {
const { data: dom } = client.useQuery('loadDom', [appId], { suspense: true });

if (!dom) {
throw new Error(`Invariant: suspense should load the dom`);
}

const [state, dispatch] = React.useReducer(domLoaderReducer, {
loading: false,
saving: false,
unsavedChanges: 0,
loadError: null,
saveError: null,
dom: null,
dom,
});
const api = React.useMemo(() => createDomApi(dispatch), []);

React.useEffect(() => {
let canceled = false;

dispatch({ type: 'DOM_LOADING' });
client.query
.loadDom(appId)
.then((dom) => {
if (!canceled) {
dispatch({ type: 'DOM_LOADED', dom });
}
})
.catch((err) => {
if (!canceled) {
dispatch({ type: 'DOM_LOADING_ERROR', error: err.message });
}
});

return () => {
canceled = true;
};
}, [appId]);

const lastSavedDom = React.useRef<appDom.AppDom | null>(null);
const handleSave = React.useCallback(() => {
if (!state.dom || lastSavedDom.current === state.dom) {
Expand Down Expand Up @@ -366,13 +306,13 @@ export default function DomProvider({ appId, children }: DomContextProps) {
useShortcut({ code: 'KeyS', metaKey: true }, handleSave);

return (
<DomLoaderContext.Provider value={state}>
<DomLoaderProvider value={state}>
<DomApiContext.Provider value={api}>{children}</DomApiContext.Provider>
<Snackbar open={!!state.saveError}>
<Alert severity="error" sx={{ width: '100%' }}>
Failed to save: {state.saveError}
</Alert>
</Snackbar>
</DomLoaderContext.Provider>
</DomLoaderProvider>
);
}
52 changes: 43 additions & 9 deletions packages/toolpad-app/src/components/Toolpad.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
import * as React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { NoSsr } from '@mui/material';
import { Box, CircularProgress, NoSsr, styled } from '@mui/material';
import { ErrorBoundary } from 'react-error-boundary';
import Release from './Release';
import Releases from './Releases';
import AppEditor from './AppEditor';
import Home from './Home';
import ErrorAlert from './AppEditor/PageEditor/ErrorAlert';

const Centered = styled('div')({
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});

function FullPageLoader() {
return (
<Centered>
<CircularProgress />
</Centered>
);
}

interface FullPageErrorProps {
error: Error;
}

function FullPageError({ error }: FullPageErrorProps) {
return (
<Centered>
<ErrorAlert error={error} />
</Centered>
);
}

function AppWorkspace() {
return (
Expand All @@ -25,14 +54,19 @@ export interface EditorProps {
export default function Toolpad({ basename }: EditorProps) {
return (
<NoSsr>
<React.Suspense fallback="loading...">
<BrowserRouter basename={basename}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/app/:appId/*" element={<AppWorkspace />} />
</Routes>
</BrowserRouter>
</React.Suspense>
{/* Container that allows children to size to it with height: 100% */}
<Box sx={{ height: '1px', minHeight: '100vh' }}>
<ErrorBoundary fallbackRender={({ error }) => <FullPageError error={error} />}>
<React.Suspense fallback={<FullPageLoader />}>
<BrowserRouter basename={basename}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/app/:appId/*" element={<AppWorkspace />} />
</Routes>
</BrowserRouter>
</React.Suspense>
</ErrorBoundary>
</Box>
</NoSsr>
);
}
4 changes: 2 additions & 2 deletions packages/toolpad-app/src/components/ToolpadShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export interface ToolpadShellProps {
}

const ToolpadShellRoot = styled('div')({
width: '100vw',
height: '100vh',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
});
Expand Down
2 changes: 1 addition & 1 deletion packages/toolpad-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@mui/x-data-grid-pro": "^5.12.3",
"quickjs-emscripten": "^0.21.0",
"react-error-boundary": "^3.1.4",
"react-query": "^3.39.1"
"react-query": "^4.0.0-beta.23"
},
"devDependencies": {
"react": "^18.1.0"
Expand Down
24 changes: 18 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.18.3":
"@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580"
integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==
Expand Down Expand Up @@ -2445,6 +2445,11 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==

"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==

"@types/yargs-parser@*":
version "21.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
Expand Down Expand Up @@ -7849,14 +7854,16 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==

react-query@^3.39.1:
version "3.39.1"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.1.tgz#3876c0fdac7a3b5a84e195534e5fa8fbdd628847"
integrity sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==
react-query@^4.0.0-beta.23:
version "4.0.0-beta.23"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-4.0.0-beta.23.tgz#76713547670ef1f991ae828b3d51df63ec6e035c"
integrity sha512-e6mNBVAYGy0M1OwX0mhRB/lCkOedKeqTUrbPjNCqvm8hQGUsJJobqfHVvTv8o6JJaOO2MFcxKF4vZM+PEKbHZA==
dependencies:
"@babel/runtime" "^7.5.5"
"@babel/runtime" "^7.17.9"
"@types/use-sync-external-store" "^0.0.3"
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"
use-sync-external-store "^1.1.0"

react-router-dom@^6.3.0:
version "6.3.0"
Expand Down Expand Up @@ -9088,6 +9095,11 @@ use-sync-external-store@1.1.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82"
integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==

use-sync-external-store@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==

util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down

0 comments on commit c535434

Please sign in to comment.