Skip to content

Commit 7bd5cd0

Browse files
authored
Merge pull request #390 from Omnicpie/add-param-typings
Add ability to specify param types
2 parents 359a6c0 + d7820bb commit 7bd5cd0

File tree

2 files changed

+116
-21
lines changed

2 files changed

+116
-21
lines changed

.changeset/nice-kangaroos-greet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/diagnostics-app': minor
3+
---
4+
5+
Support specifying client parameter types

tools/diagnostics-app/src/app/views/client-params.tsx

Lines changed: 111 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,156 @@
11
import { NavigationPage } from '@/components/navigation/NavigationPage';
22
import { getParams, setParams as setParamsGlobal } from '@/library/powersync/ConnectionManager';
3-
import { Box, Button, Grid, IconButton, styled, TextField } from '@mui/material';
3+
import {
4+
Box,
5+
Button,
6+
Grid,
7+
IconButton,
8+
styled,
9+
TextField,
10+
Select,
11+
MenuItem,
12+
InputLabel,
13+
FormControl
14+
} from '@mui/material';
415
import { FormEvent, useState } from 'react';
516
import DeleteIcon from '@mui/icons-material/Delete';
617
import AddIcon from '@mui/icons-material/Add';
718

8-
const jsonToObjectArray = (json: Object) => {
19+
const typeForValue = (value: unknown) => {
20+
//when using typeof arrays are "object" so we have to have this specific case
21+
if (Array.isArray(value)) return 'array';
22+
return typeof value;
23+
};
24+
25+
const jsonToObjectArray = (json: Object): ParameterEntry[] => {
926
const entrySet = Object.entries(json);
10-
return entrySet.map(([key, value]) => ({
11-
key,
12-
value
13-
}));
27+
return entrySet.map(([key, value]) => {
28+
const type = typeForValue(value) as ParameterType;
29+
return {
30+
key,
31+
// Only arrays and objects need special cases here since JS will take care of the rest.
32+
value: type === 'array' || type === 'object' ? JSON.stringify(value) : String(value),
33+
type
34+
};
35+
});
36+
};
37+
38+
type ParameterType = 'string' | 'number' | 'boolean' | 'array' | 'object';
39+
40+
interface ParameterEntry {
41+
key: string;
42+
type: ParameterType;
43+
value: string;
44+
error?: string;
45+
}
46+
47+
// A simple set of mappers for converting a string to the correct value for saving
48+
const CONVERTERS = {
49+
string: (v: string) => v,
50+
number: (v: string) => Number(v),
51+
boolean: (v: string) => v === 'true',
52+
array: (v: string) => JSON.parse(v),
53+
object: (v: string) => JSON.parse(v)
1454
};
1555

1656
function ClientParamsPage() {
1757
const [params, setParams] = useState(jsonToObjectArray(getParams()));
1858

59+
const convertValueForSave = (t: ParameterType, stringValue: string) => CONVERTERS[t](stringValue);
60+
1961
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
2062
e.preventDefault();
2163
e.stopPropagation();
2264

23-
const newParams = params.reduce((curr, item) => ({ ...curr, [`${item.key}`]: item.value }), {});
24-
setParamsGlobal(newParams);
65+
try {
66+
const newParams = params.reduce<Record<string, any>>(
67+
(curr: any, item: { key: string; type: string; value: string }) => ({
68+
...curr,
69+
[`${item.key}`]: convertValueForSave(item.type as ParameterType, item.value)
70+
}),
71+
{}
72+
);
73+
setParamsGlobal(newParams);
74+
} catch (e) {}
75+
};
76+
77+
const validate = (val: ParameterEntry) => {
78+
if (val.type == 'object' || val.type == 'array') {
79+
try {
80+
JSON.parse(val.value);
81+
return val;
82+
} catch (e: any) {
83+
return {
84+
...val,
85+
error: e.message
86+
};
87+
}
88+
} else {
89+
return val;
90+
}
2591
};
2692

27-
const replace = (idx: number, val: any) => setParams((a) => a.map((entity, i) => (i === idx ? val : entity)));
93+
const replace = (idx: number, val: ParameterEntry) => {
94+
setParams((a: any[]) => a.map((entity, i) => (i === idx ? validate(val) : entity)));
95+
};
2896

2997
const removeIdx = (idx: number) =>
30-
setParams((a) => a.map((entity, i) => i !== idx && entity).filter((entity) => entity !== false));
98+
setParams((a: any[]) => a.map((entity, i) => i !== idx && entity).filter((entity) => entity !== false));
3199

32100
const addRow = () => {
33-
setParams((a) => [...a, { key: '', value: '' }]);
101+
setParams((a: any[]) => [...a, { key: '', value: '', type: 'string' }]);
34102
};
35103

36-
const changeValue = (idx: number, value: string, currKey: string) => {
37-
replace(idx, { key: currKey, value });
104+
const changeValue = (idx: number, value: string, currKey: string, type: ParameterType) => {
105+
replace(idx, { key: currKey, value, type });
38106
};
39107

40-
const changeKey = (idx: number, key: string, currValue: unknown) => {
41-
replace(idx, { key, value: currValue });
108+
const changeKey = (idx: number, key: string, currValue: string, type: ParameterType) => {
109+
replace(idx, { key, value: currValue, type });
110+
};
111+
112+
const changeType = (idx: number, key: string, value: string, newType: ParameterType) => {
113+
replace(idx, { key, value, type: newType });
42114
};
43115

44116
return (
45117
<NavigationPage title="Client Parameters">
46118
<S.MainContainer>
47119
<form onSubmit={onSubmit}>
48-
{params.map(({ key, value }, idx) => (
49-
<S.CenteredGrid container>
120+
{params.map(({ key, value, type, error }, idx: number) => (
121+
<S.CenteredGrid container key={idx}>
50122
<S.CenteredGrid item xs={12} md={10}>
51123
<TextField
52124
label="Key"
53125
value={key}
54126
sx={{ margin: '10px' }}
55-
onChange={(v) => changeKey(idx, v.target.value, value)}
127+
onChange={(v: { target: { value: string } }) => changeKey(idx, v.target.value, value, type)}
56128
/>
129+
{/* TODO: Potentially add an explanation here about how users should write values for a given piece of text? */}
57130
<TextField
58131
label="Value"
59132
value={value}
60133
sx={{ margin: '10px' }}
61-
onChange={(v) => changeValue(idx, v.target.value, key)}
134+
error={!!error}
135+
title={error}
136+
onChange={(v: { target: { value: string } }) => changeValue(idx, v.target.value, key, type)}
62137
/>
63-
138+
<FormControl sx={{ margin: '10px', width: '125px', minWidth: '95px' }}>
139+
<InputLabel id="demo-simple-select-label">Type</InputLabel>
140+
<Select
141+
labelId="demo-simple-select-label"
142+
value={type}
143+
label="Type"
144+
onChange={(v: { target: { value: string } }) =>
145+
changeType(idx, key, value, v.target.value as ParameterType)
146+
}>
147+
<MenuItem value={'string'}>String</MenuItem>
148+
<MenuItem value={'number'}>Number</MenuItem>
149+
<MenuItem value={'array'}>Array</MenuItem>
150+
<MenuItem value={'object'}>Object</MenuItem>
151+
<MenuItem value={'boolean'}>Boolean</MenuItem>
152+
</Select>
153+
</FormControl>
64154
<IconButton sx={{ margin: '10px' }} color="error" onClick={() => removeIdx(idx)}>
65155
<DeleteIcon />
66156
</IconButton>
@@ -73,7 +163,7 @@ function ClientParamsPage() {
73163
</IconButton>
74164
</S.CenteredGrid>
75165
<Button type="submit" sx={{ margin: '10px' }} variant="contained">
76-
Submit
166+
Save
77167
</Button>
78168
</form>
79169
</S.MainContainer>

0 commit comments

Comments
 (0)