diff --git a/packages/toolpad-app/src/appDom.ts b/packages/toolpad-app/src/appDom.ts index 2c255bfb585..debd88b56e8 100644 --- a/packages/toolpad-app/src/appDom.ts +++ b/packages/toolpad-app/src/appDom.ts @@ -76,7 +76,7 @@ export interface PageNode extends AppDomNodeBase { readonly type: 'page'; readonly attributes: { readonly title: ConstantAttrValue; - readonly urlQuery: ConstantAttrValue>; + readonly parameters?: ConstantAttrValue<[string, string][]>; }; } diff --git a/packages/toolpad-app/src/components/AppEditor/HierarchyExplorer/CreatePageNodeDialog.tsx b/packages/toolpad-app/src/components/AppEditor/HierarchyExplorer/CreatePageNodeDialog.tsx index e019c51ee30..dd4cdda2288 100644 --- a/packages/toolpad-app/src/components/AppEditor/HierarchyExplorer/CreatePageNodeDialog.tsx +++ b/packages/toolpad-app/src/components/AppEditor/HierarchyExplorer/CreatePageNodeDialog.tsx @@ -34,7 +34,6 @@ export default function CreatePageDialog({ appId, onClose, ...props }: CreatePag name, attributes: { title: appDom.createConst(name), - urlQuery: appDom.createConst({}), }, }); const appNode = appDom.getApp(dom); diff --git a/packages/toolpad-app/src/components/AppEditor/PageEditor/UrlQueryEditor.tsx b/packages/toolpad-app/src/components/AppEditor/PageEditor/UrlQueryEditor.tsx index 76e55dd75d5..5e2b6f6b856 100644 --- a/packages/toolpad-app/src/components/AppEditor/PageEditor/UrlQueryEditor.tsx +++ b/packages/toolpad-app/src/components/AppEditor/PageEditor/UrlQueryEditor.tsx @@ -1,10 +1,17 @@ -import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Typography, +} from '@mui/material'; import * as React from 'react'; import AddIcon from '@mui/icons-material/Add'; -import StringRecordEditor from '../../StringRecordEditor'; import * as appDom from '../../../appDom'; import { useDom, useDomApi } from '../../DomLoader'; import { NodeId } from '../../../types'; +import MapEntriesEditor from '../../MapEntriesEditor'; export interface UrlQueryEditorProps { pageNodeId: NodeId; @@ -18,38 +25,40 @@ export default function UrlQueryEditor({ pageNodeId }: UrlQueryEditorProps) { const [dialogOpen, setDialogOpen] = React.useState(false); - const [input, setInput] = React.useState(page.attributes.urlQuery.value || {}); - React.useEffect( - () => setInput(page.attributes.urlQuery.value || {}), - [page.attributes.urlQuery.value], - ); + const value = page.attributes.parameters?.value; + const [input, setInput] = React.useState(value); + React.useEffect(() => setInput(value), [value]); const handleSave = React.useCallback(() => { - domApi.setNodeNamespacedProp(page, 'attributes', 'urlQuery', appDom.createConst(input)); + domApi.setNodeNamespacedProp(page, 'attributes', 'parameters', appDom.createConst(input || [])); }, [domApi, page, input]); return ( setDialogOpen(false)}> - Edit URL query + Edit page parameters - + The parameters you define below will be made available in bindings under the{' '} + page.parameters global variable. You can set these parameters in the url + with query variables (?param=value). + + - diff --git a/packages/toolpad-app/src/components/StringRecordEditor.tsx b/packages/toolpad-app/src/components/StringRecordEditor.tsx deleted file mode 100644 index 38b726315c7..00000000000 --- a/packages/toolpad-app/src/components/StringRecordEditor.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { Box, TextField, IconButton, SxProps } from '@mui/material'; -import * as React from 'react'; -import DeleteIcon from '@mui/icons-material/Delete'; -import AddIcon from '@mui/icons-material/Add'; -import { omit } from '../utils/immutability'; -import { WithControlledProp } from '../utils/types'; -import { hasOwnProperty } from '../utils/collections'; - -export interface StringRecordEditorProps extends WithControlledProp> { - label?: string; - fieldLabel?: string; - valueLabel?: string; - autoFocus?: boolean; - sx?: SxProps; -} - -export default function StringRecordEditor({ - value, - onChange, - label, - fieldLabel = 'field', - valueLabel = 'value', - autoFocus = false, - sx, -}: StringRecordEditorProps) { - const [newFieldName, setNewFieldName] = React.useState(''); - const [newFieldValue, setNewFieldValue] = React.useState(''); - const fieldInputRef = React.useRef(null); - - const [editedField, setEditedField] = React.useState(null); - const [editedFieldNewName, setEditedFieldNewName] = React.useState(''); - - const isValidEditedFieldName: boolean = - !!editedFieldNewName && - (!hasOwnProperty(value, editedFieldNewName) || editedFieldNewName === editedField); - - const isValidNewFieldName: boolean = !hasOwnProperty(value, newFieldName); - const isValidNewFieldParams: boolean = !editedField && !!newFieldName && isValidNewFieldName; - - const handleFieldValueChange = React.useCallback( - (field: string) => (event: React.ChangeEvent) => { - onChange({ ...value, [field]: event.target.value }); - }, - [onChange, value], - ); - - const handleRemove = React.useCallback( - (field: string) => () => { - onChange(omit(value, field)); - }, - [onChange, value], - ); - - const handleSaveEditedFieldName = React.useCallback(() => { - if (!editedField || !isValidEditedFieldName) { - return false; - } - const oldValue = value[editedField]; - onChange({ ...omit(value, editedField), [editedFieldNewName]: oldValue }); - return true; - }, [editedField, isValidEditedFieldName, value, onChange, editedFieldNewName]); - - const handleSubmit = React.useCallback( - (event: React.FormEvent) => { - event.preventDefault(); - onChange({ ...value, [newFieldName]: newFieldValue }); - setNewFieldName(''); - setNewFieldValue(''); - fieldInputRef.current?.focus(); - }, - [newFieldName, newFieldValue, onChange, value], - ); - - const handleKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - const success = handleSaveEditedFieldName(); - if (success) { - setEditedField(null); - } - event.preventDefault(); - } - if (event.key === 'Escape') { - setEditedField(null); - event.preventDefault(); - } - }, - [handleSaveEditedFieldName], - ); - - const handleBlur = React.useCallback(() => { - handleSaveEditedFieldName(); - setEditedField(null); - }, [handleSaveEditedFieldName]); - - const handleFocus = React.useCallback( - (field: string) => () => { - setEditedField(field); - setEditedFieldNewName(field); - }, - [], - ); - - return ( - - {label ? {label}: : null} - {Object.entries(value).map(([field, fieldValue]) => ( - - {editedField === field ? ( - setEditedFieldNewName(event.target.value)} - error={!isValidEditedFieldName} - onBlur={handleBlur} - onKeyDown={handleKeyDown} - /> - ) : ( - - {field}: - - )} - - - - - - - ))} - -
- setNewFieldName(event.target.value)} - autoFocus={autoFocus} - error={!isValidNewFieldName} - /> - setNewFieldValue(event.target.value)} - /> - - - - -
- ); -} diff --git a/packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx b/packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx index dc63f598c53..1270571e27a 100644 --- a/packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx +++ b/packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx @@ -20,7 +20,6 @@ function renderPage(initPage: (dom: appDom.AppDom, page: appDom.PageNode) => app name: 'Page', attributes: { title: appDom.createConst(''), - urlQuery: appDom.createConst({}), }, }); dom = appDom.addNode(dom, page, root, 'pages'); diff --git a/packages/toolpad-app/src/runtime/ToolpadApp.tsx b/packages/toolpad-app/src/runtime/ToolpadApp.tsx index 290e3717f7e..94fd3c7f7b0 100644 --- a/packages/toolpad-app/src/runtime/ToolpadApp.tsx +++ b/packages/toolpad-app/src/runtime/ToolpadApp.tsx @@ -365,12 +365,13 @@ function parseBindings( } const urlParams = new URLSearchParams(location.search); - for (const [paramName, paramValue] of urlParams.entries()) { - const bindingId = `${page.id}.query.${paramName}`; - const scopePath = `page.query.${paramName}`; + const pageParameters = page.attributes.parameters?.value || []; + for (const [paramName, paramDefault] of pageParameters) { + const bindingId = `${page.id}.parameters.${paramName}`; + const scopePath = `page.parameters.${paramName}`; parsedBindingsMap.set(bindingId, { scopePath, - result: { value: paramValue }, + result: { value: urlParams.get(paramName) || paramDefault }, }); } diff --git a/packages/toolpad-app/src/server/data.ts b/packages/toolpad-app/src/server/data.ts index 7d7fd6f6996..922752710c8 100644 --- a/packages/toolpad-app/src/server/data.ts +++ b/packages/toolpad-app/src/server/data.ts @@ -169,7 +169,6 @@ function createDefaultDom(): appDom.AppDom { name: 'Page 1', attributes: { title: appDom.createConst('Page 1'), - urlQuery: appDom.createConst({}), }, });