Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dom loader suspense #625

Merged
merged 7 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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