Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot committed May 7, 2024
2 parents d057f7e + 0c2b514 commit f839a69
Show file tree
Hide file tree
Showing 23 changed files with 215 additions and 71 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Use the `--ui` flag to run the tests interactively.
1. If you haven't already, install the project dependencies using

```bash
pnpm
pnpm install
```

1. To start the documentation application in dev mode run
Expand Down
10 changes: 9 additions & 1 deletion docs/data/toolpad/studio/concepts/page-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ You can override this setting for any page using the `toolpad-display` query par
- `?toolpad-display=shell` - Same as App shell mode.
- `?toolpad-display=standalone` - Same as No shell mode.

{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/display-mode-override.png", "alt": "No shell display mode ", "caption": "Overriding the display mode", "indent": 1}}
{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/display-mode-override.png", "alt": "No shell display mode", "caption": "Overriding the display mode", "indent": 1}}

:::info
See the how-to guide on [how to embed Toolpad Studio pages](/toolpad/studio/how-to-guides/embed-pages/) using the display mode property.
:::

## Max width

Toolpad pages use a Material UI [Container](https://mui.com/material-ui/react-container/) as their top-level element. You can control the maximum width of this container in the page properties.

{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/max-width.png", "alt": "Page container max width", "caption": "Setting the maximum width of the page container", "zoom": false, "width": 306}}

Available values are **xs**, **sm**, **md**, **lg**, **xl**, or **None** to conform to the available width in the window.

## Page parameters

Page parameters allow you to pass external data into the Toolpad Studio page state via the URL query parameters.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/schemas/v1/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@
}
],
"description": "Display mode of the page. This can also be set at runtime with the toolpad-display query parameter"
},
"maxWidth": {
"type": "string",
"enum": ["xs", "sm", "md", "lg", "xl", "none"],
"description": "Top level element of the page."
}
},
"additionalProperties": false,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"devDependencies": {
"@argos-ci/core": "1.5.5",
"@mui/monorepo": "github:mui/material-ui#beef28264938138411e13e074dc4c60d498afbf8",
"@mui/monorepo": "github:mui/material-ui#fd764b5578ef6688f3c9c1a12d986de9d938de8d",
"@mui/x-charts": "7.3.2",
"@next/eslint-plugin-next": "14.2.3",
"@playwright/test": "1.43.1",
Expand Down Expand Up @@ -91,7 +91,7 @@
"lodash": "4.17.21",
"semver": "7.6.0",
"tsup": "8.0.2",
"tsx": "4.7.3",
"tsx": "4.9.3",
"vitest": "1.6.0",
"yargs": "17.7.2",
"zod": "3.22.4",
Expand Down
10 changes: 9 additions & 1 deletion packages/toolpad-studio-components/src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ export interface SerializableGridColumn
dateFormat?: DateFormat;
dateTimeFormat?: DateFormat;
codeComponent?: string;
visible?: boolean;
}

export type SerializableGridColumns = SerializableGridColumn[];
Expand Down Expand Up @@ -1281,6 +1282,10 @@ const DataGridComponent = React.forwardRef(function DataGridComponent(
const appHost = useAppHost();
const isProPlan = appHost.plan === 'pro';

const columnVisibilityModel = Object.fromEntries(
(columnsProp ?? []).map((column) => [column.field, column.visible ?? true]),
);

return (
<LicenseInfoProvider info={LICENSE_INFO}>
<Box ref={ref} sx={{ ...sx, width: '100%', height: '100%', position: 'relative' }}>
Expand Down Expand Up @@ -1308,7 +1313,10 @@ const DataGridComponent = React.forwardRef(function DataGridComponent(
getRowId={getRowId}
onRowSelectionModelChange={onSelectionModelChange}
rowSelectionModel={selectionModel}
initialState={{ pinnedColumns: { right: [ACTIONS_COLUMN_FIELD] } }}
initialState={{
columns: { columnVisibilityModel },
pinnedColumns: { right: [ACTIONS_COLUMN_FIELD] },
}}
disableAggregation={!isProPlan}
disableRowGrouping={!isProPlan}
{...props}
Expand Down
5 changes: 5 additions & 0 deletions packages/toolpad-studio-runtime/src/appDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export interface ConnectionNode<P = unknown> extends AppDomNodeBase {

export type PageDisplayMode = 'standalone' | 'shell';

export type ContainerWidth = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none';

export const DEFAULT_CONTAINER_WIDTH = 'lg' satisfies ContainerWidth;

export interface PageNode extends AppDomNodeBase {
readonly type: 'page';
readonly attributes: {
Expand All @@ -107,6 +111,7 @@ export interface PageNode extends AppDomNodeBase {
readonly module?: string;
readonly display?: PageDisplayMode;
readonly displayName?: string;
readonly maxWidth?: ContainerWidth;
readonly authorization?: {
readonly allowAll?: boolean;
readonly allowedRoles?: string[];
Expand Down
9 changes: 5 additions & 4 deletions packages/toolpad-studio-runtime/src/jsBrowserRuntime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { TOOLPAD_LOADING_MARKER } from './jsRuntime';
import { BindingEvaluationResult, JsRuntime } from './types';

function getIframe(): HTMLIFrameElement {
if (typeof window === 'undefined') {
throw new Error(`Can't use browser JS runtime outside of a browser`);
}

const iframeId = 'toolpad-browser-runtime-iframe';
let iframe = document.getElementById(iframeId) as HTMLIFrameElement;

Expand Down Expand Up @@ -90,11 +94,8 @@ function createBrowserRuntime(): JsRuntime {
};
}

const browserRuntime = typeof window === 'undefined' ? null : createBrowserRuntime();
const browserRuntime = createBrowserRuntime();
export function getBrowserRuntime(): JsRuntime {
if (!browserRuntime) {
throw new Error(`Can't use browser JS runtime outside of a browser`);
}
return browserRuntime;
}

Expand Down
28 changes: 3 additions & 25 deletions packages/toolpad-studio/src/components/NoSsr.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
import * as React from 'react';

function subscribe() {
return () => {};
}

function getSnapshot() {
return false;
}

function getServerSnapshot() {
return true;
}

/**
* Returns true when serverside rendering, or when hydrating.
*/
export function useIsSsr(defer: boolean = false): boolean {
const isSsrInitialValue = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
const [isSsr, setIsSsr] = React.useState(defer ? true : isSsrInitialValue);
React.useEffect(() => setIsSsr(false), []);
return isSsr;
}
import useSsr from '@toolpad/utils/hooks/useSsr';

export interface NoSsrProps {
children?: React.ReactNode;
fallback?: React.ReactNode;
defer?: boolean;
}

/**
* Alternative version of MUI NoSsr that avoids state updates during layout effects.
*/
export default function NoSsr({ children, defer, fallback = null }: NoSsrProps) {
const isSsr = useIsSsr(defer);
export default function NoSsr({ children, fallback = null }: NoSsrProps) {
const isSsr = useSsr();

return <React.Fragment>{isSsr ? fallback : children}</React.Fragment>;
}
12 changes: 11 additions & 1 deletion packages/toolpad-studio/src/runtime/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ import { AuthContext } from './useAuth';
import productIconDark from '../../public/product-icon-dark.svg';
import productIconLight from '../../public/product-icon-light.svg';

function interopNextImg(img: any) {
if (typeof img.src === 'string') {
return img.src;
}
return img;
}

const productIconDarkSrc = interopNextImg(productIconDark);
const productIconLightSrc = interopNextImg(productIconLight);

const TOOLPAD_DISPLAY_MODE_URL_PARAM = 'toolpad-display';

// Url params that will be carried over to the next page
Expand Down Expand Up @@ -54,7 +64,7 @@ function AppPagesNavigation({

const theme = useTheme();

const productIcon = theme.palette.mode === 'dark' ? productIconDark : productIconLight;
const productIcon = theme.palette.mode === 'dark' ? productIconDarkSrc : productIconLightSrc;

return (
<Drawer
Expand Down
52 changes: 37 additions & 15 deletions packages/toolpad-studio/src/runtime/ToolpadApp.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import * as React from 'react';
import {
Stack,
Expand Down Expand Up @@ -67,6 +69,7 @@ import usePageTitle from '@toolpad/utils/hooks/usePageTitle';
import invariant from 'invariant';
import useEventCallback from '@mui/utils/useEventCallback';
import * as appDom from '@toolpad/studio-runtime/appDom';
import useSsr from '@toolpad/utils/hooks/useSsr';
import { RuntimeState } from './types';
import { getBindingType, getBindingValue } from './bindings';
import {
Expand Down Expand Up @@ -902,9 +905,15 @@ interface RenderedNodeContentProps {
node: appDom.PageNode | appDom.ElementNode;
childNodeGroups: appDom.NodeChildren<appDom.ElementNode>;
Component: ToolpadComponent<any>;
staticProps?: Record<string, any>;
}

function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeContentProps) {
function RenderedNodeContent({
node,
childNodeGroups,
Component,
staticProps,
}: RenderedNodeContentProps) {
const { setControlledBinding } = React.useContext(SetBindingContext) ?? {};
invariant(setControlledBinding, 'Node must be rendered in a RuntimeScoped context');

Expand Down Expand Up @@ -954,7 +963,10 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC
}

if (typeof hookResult[propName] === 'undefined' && argType) {
hookResult[propName] = getArgTypeDefaultValue(argType);
const defaultValue = getArgTypeDefaultValue(argType);
if (typeof defaultValue !== 'undefined') {
hookResult[propName] = getArgTypeDefaultValue(argType);
}
}
}

Expand Down Expand Up @@ -1079,13 +1091,14 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC

const props: Record<string, any> = React.useMemo(() => {
return {
...staticProps,
...boundProps,
...onChangeHandlers,
...eventHandlers,
...layoutElementProps,
...reactChildren,
};
}, [boundProps, eventHandlers, layoutElementProps, onChangeHandlers, reactChildren]);
}, [staticProps, boundProps, eventHandlers, layoutElementProps, onChangeHandlers, reactChildren]);

const previousProps = React.useRef<Record<string, any>>(props);
const [hasSetInitialBindings, setHasSetInitialBindings] = React.useState(false);
Expand Down Expand Up @@ -1207,24 +1220,19 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC
}

interface PageRootProps {
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none';
children?: React.ReactNode;
}

const PageRoot = React.forwardRef<HTMLDivElement, PageRootProps>(function PageRoot(
{ children, ...props }: PageRootProps,
{ children, maxWidth, ...props }: PageRootProps,
ref,
) {
const containerMaxWidth =
maxWidth === 'none' ? false : maxWidth ?? appDom.DEFAULT_CONTAINER_WIDTH;
return (
<Container ref={ref}>
<Stack
data-testid="page-root"
direction="column"
sx={{
my: 2,
gap: 1,
}}
{...props}
>
<Container ref={ref} maxWidth={containerMaxWidth}>
<Stack data-testid="page-root" direction="column" sx={{ my: 2, gap: 1 }} {...props}>
{children}
</Stack>
</Container>
Expand All @@ -1237,6 +1245,11 @@ const PageRootComponent = createComponent(PageRoot, {
type: 'element',
control: { type: 'slots' },
},
maxWidth: {
type: 'string',
enum: ['xs', 'sm', 'md', 'lg', 'xl', 'none'],
control: { type: 'ToggleButtons' },
},
},
});

Expand Down Expand Up @@ -1411,13 +1424,21 @@ function RenderedLowCodePage({ page }: RenderedLowCodePageProps) {

const applicationVm = useApplicationVm(onApplicationVmUpdate);

const pageProps = React.useMemo(
() => ({
maxWidth: page.attributes.maxWidth,
}),
[page.attributes.maxWidth],
);

return (
<ApplicationVmApiContext.Provider value={applicationVm}>
<RuntimeScoped id={'global'} parseBindingsResult={parseBindingsResult} onUpdate={onUpdate}>
<RenderedNodeContent
node={page}
childNodeGroups={{ children }}
Component={PageRootComponent}
staticProps={pageProps}
/>
{queries.map((node) => (
<FetchNode key={node.id} page={page} node={node} />
Expand Down Expand Up @@ -1709,7 +1730,8 @@ export function ToolpadAppRoutes(props: ToolpadAppProps) {
}

export default function ToolpadApp(props: ToolpadAppProps) {
return (
const isSsr = useSsr();
return isSsr ? null : (
<BrowserRouter basename={props.basename}>
<Routes>
<Route
Expand Down
4 changes: 2 additions & 2 deletions packages/toolpad-studio/src/server/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cors from 'cors';
import invariant from 'invariant';
import { errorFrom, serializeError, SerializedError } from '@toolpad/utils/errors';
import * as appDom from '@toolpad/studio-runtime/appDom';
import type { Methods, ServerDataSource, ToolpadProjectOptions } from '../types';
import type { Methods, ServerDataSource } from '../types';
import serverDataSources from '../toolpadDataSources/server';
import applyTransform from '../toolpadDataSources/applyTransform';
import { asyncHandler } from '../utils/express';
Expand All @@ -23,7 +23,7 @@ function withSerializedError<T extends { error?: unknown }>(
}

interface IToolpadProject {
options: ToolpadProjectOptions;
options: { dev: boolean };
getRoot(): string;
loadDom(): Promise<appDom.AppDom>;
saveDom(dom: appDom.AppDom): Promise<void>;
Expand Down
4 changes: 2 additions & 2 deletions packages/toolpad-studio/src/server/EnvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Emitter } from '@toolpad/utils/events';
import chalk from 'chalk';
import { truncate } from '@toolpad/utils/strings';
import { Awaitable } from '@toolpad/utils/types';
import { ProjectEvents, ToolpadProjectOptions } from '../types';
import { ProjectEvents } from '../types';

interface IToolpadProject {
options: ToolpadProjectOptions;
options: { dev: boolean };
events: Emitter<ProjectEvents>;
getRoot(): string;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/toolpad-studio/src/server/FunctionsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as url from 'node:url';
import type { GridRowId } from '@mui/x-data-grid';
import invariant from 'invariant';
import { Awaitable } from '@toolpad/utils/types';
import { ProjectEvents, ToolpadProjectOptions } from '../types';
import { ProjectEvents } from '../types';
import * as functionsRuntime from './functionsRuntime';
import type { ExtractTypesParams, IntrospectionResult } from './functionsTypesWorker';
import { format } from '../utils/prettier';
Expand Down Expand Up @@ -99,7 +99,7 @@ function formatError(esbuildError: esbuild.Message): Error {
}

interface IToolpadProject {
options: ToolpadProjectOptions;
options: { dev: boolean };
events: Emitter<ProjectEvents>;
getRoot(): string;
getOutputFolder(): string;
Expand Down
4 changes: 3 additions & 1 deletion packages/toolpad-studio/src/server/functionsRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ async function loadExports(filePath: string): Promise<Map<string, unknown>> {
const content = await fs.readFile(importFileUrl, 'utf-8');
const hash = crypto.createHash('md5').update(content).digest('hex');
importFileUrl.searchParams.set('hash', hash);
const exports = await import(importFileUrl.href);
// `webpackIgnore: true` is used to instruct webpack in Next.js to use the native import() function
// instead of trying to bundle the dynamic import() call
const exports = await import(/* webpackIgnore: true */ importFileUrl.href);
return new Map(Object.entries(exports));
}

Expand Down
Loading

0 comments on commit f839a69

Please sign in to comment.