Skip to content

Commit

Permalink
feat: add variable and expression support to serve props (#895)
Browse files Browse the repository at this point in the history
* feat: add variable and expression support to serve props

* chore: hide behind flag PP_EXPRESSIONS

* fix: don't use variable expansion

* fix: match variable struct exactly

* chore: remove flags option from cl
  • Loading branch information
Caele committed Aug 22, 2022
1 parent 0b8f9b5 commit 3b8dc2e
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 14 deletions.
2 changes: 1 addition & 1 deletion commands/serve/lib/webpack.build.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const cfg = ({ srcDir, distDir, dev = false, serveConfig = {} }) => {
eHub: [path.resolve(srcDir, 'eHub')],
fixtures: [path.resolve(__dirname, './fixtures.js')],
},
devtool: dev ? 'eval-cheap-module-source-map' : false,
devtool: dev ? 'source-map' : false,
output: {
path: distDir,
filename: '[name].js',
Expand Down
2 changes: 1 addition & 1 deletion commands/serve/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
],
"scripts": {
"build": "cross-env NODE_ENV=production DEFAULTS=true webpack --config ./lib/webpack.build.js",
"build:dev": "cross-env DEFAULTS=true webpack --config ./lib/webpack.build.js",
"build:dev": "cross-env NODE_ENV=development DEFAULTS=true webpack --config ./lib/webpack.build.js",
"lint": "eslint web",
"prepublishOnly": "rm -rf dist && yarn run build"
},
Expand Down
8 changes: 7 additions & 1 deletion commands/serve/web/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,13 @@ export default function App({ app, info }) {
padding: 0,
}}
>
<Properties sn={sn} viz={activeViz} isTemp={!objectListMode} storage={storage} />
<Properties
sn={sn}
viz={activeViz}
isTemp={!objectListMode}
storage={storage}
flags={info.flags}
/>
</Grid>
)}
</Grid>
Expand Down
69 changes: 62 additions & 7 deletions commands/serve/web/components/AutoComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
Accordion,
AccordionSummary,
AccordionDetails,
InputAdornment,
} from '@mui/material';

import { ExpandMore } from '@nebula.js/ui/icons';
import Variable from './property-panel/Variable';

const PREFIX = 'AutoComponents';

Expand Down Expand Up @@ -50,10 +52,19 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({
},
}));

const getType = (value) => {
const getType = (value, key) => {
if (Array.isArray(value)) {
return 'array';
}

if (enableExpressions && key === 'variable' && typeof value === 'object' && 'name' in value && 'value' in value) {
return 'variable';
}

if (enableExpressions && value && typeof value === 'object' && 'qStringExpression' in value) {
return 'expression';
}

if (typeof value === 'boolean') {
return 'boolean';
}
Expand Down Expand Up @@ -113,13 +124,41 @@ function Num({ property, value, target, changed }) {
return <TextField variant="standard" fullWidth onChange={handleChange} onBlur={onBlur} label={property} value={s} />;
}

function Obj({ property, value, changed }) {
function Expression({ property, value, target, changed }) {
const initial = value.qStringExpression && value.qStringExpression.qExpr ? value.qStringExpression.qExpr : '';
const [s, setS] = useState(initial);
const handleChange = (e) => {
setS(e.target.value);
};
const onBlur = () => {
if (s !== value) {
target[property].qStringExpression = { qExpr: `=${s}` };
changed();
}
};

return (
<TextField
variant="standard"
InputProps={{
startAdornment: <InputAdornment position="start">=</InputAdornment>,
}}
fullWidth
onChange={handleChange}
onBlur={onBlur}
label={property}
value={s}
/>
);
}

function Obj({ property, value, changed, app }) {
return (
<StyledAccordion square className={classes.root}>
<AccordionSummary expandIcon={<ExpandMore />} className={classes.summary}>
<Typography>{property}</Typography>
</AccordionSummary>
<AccordionDetails className={classes.details}>{generateComponents(value, changed)}</AccordionDetails>
<AccordionDetails className={classes.details}>{generateComponents(value, changed, app)}</AccordionDetails>
</StyledAccordion>
);
}
Expand All @@ -129,24 +168,33 @@ const registeredComponents = {
string: Str,
object: Obj,
number: Num,
expression: Expression,
variable: Variable,
};

const QRX = /^q[A-Z]/;

export default function generateComponents(properties, changed) {
let enableExpressions = false;

export default function generateComponents(properties, changed, app, flags) {
if (flags && flags.PP_EXPRESSIONS) {
enableExpressions = true;
}

const components = Object.keys(properties)
.map((key) => {
if (['visualization', 'version'].indexOf(key) !== -1) {
return false;
}
if (QRX.test(key)) {
// skip q properties for now
// skip q properties for now, but allow qStringExpression
return false;
}
const type = getType(properties[key]);
const type = getType(properties[key], key);
if (!registeredComponents[type]) {
return false;
}

return {
Component: registeredComponents[type],
property: key,
Expand All @@ -161,7 +209,14 @@ export default function generateComponents(properties, changed) {
<StyledGrid container direction="column" gap={0} alignItems="stretch">
{components.map((c) => (
<Grid item xs key={c.key} style={{ width: '100%' }}>
<c.Component key={c.key} property={c.property} value={c.value} target={properties} changed={changed} />
<c.Component
key={c.key}
app={app}
property={c.property}
value={c.value}
target={properties}
changed={changed}
/>
</Grid>
))}
</StyledGrid>
Expand Down
2 changes: 1 addition & 1 deletion commands/serve/web/components/FieldsPopover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export default function FieldsPopover({ alignTo, show, close, onSelected, type }
open={show}
onClose={close}
anchorEl={alignTo.current}
marginThreshold={theme.spacing(1)}
marginThreshold={16} // theme.spacing(1)
elevation={3}
anchorOrigin={{
vertical: 'bottom',
Expand Down
8 changes: 5 additions & 3 deletions commands/serve/web/components/Properties.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useState, useContext } from 'react';

import { Divider, Grid, Checkbox, FormControlLabel } from '@mui/material';

import usePropertiesById from '@nebula.js/nucleus/src/hooks/usePropertiesById';

import AppContext from '../contexts/AppContext';
import Data from './property-panel/Data';
import generateComponents from './AutoComponents';

export default function Properties({ viz, sn, isTemp, storage }) {
export default function Properties({ viz, sn, isTemp, storage, flags }) {
const [properties, setProperties] = usePropertiesById(viz.id);
const app = useContext(AppContext);

const [isReadCacheEnabled, setReadCacheEnabled] = useState(storage.get('readFromCache') !== false);

Expand Down Expand Up @@ -51,7 +53,7 @@ export default function Properties({ viz, sn, isTemp, storage }) {
</>
)}
<Data properties={properties} setProperties={setProperties} sn={sn} />
{generateComponents(properties, changed)}
{generateComponents(properties, changed, app, flags)}
</div>
);
}
73 changes: 73 additions & 0 deletions commands/serve/web/components/property-panel/Variable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint no-param-reassign:0 */
import React, { useState, useEffect } from 'react';

import { Select, FormControl, MenuItem } from '@mui/material';

let variableList = null;

async function getVariables(app) {
if (!variableList) {
variableList = await app.createSessionObject({
qInfo: {
qType: 'VariableList',
},
qVariableListDef: {
qType: 'variable',
qShowReserved: false,
qShowConfig: false,
},
});
}
const reply = await variableList.getLayout();

const list = reply.qVariableList.qItems.map((item) => ({
value: item.qName,
label: item.qName.length > 50 ? `${item.qName.slice(0, 50)}...` : item.qName,
}));

return list;
}
// variable, {name, value}, properties
export default function Variable({ property, value, target, changed, app }) {
const [s, setS] = useState(value.name);
const [l, setL] = useState([]);

useEffect(() => {
async function fetchData() {
const list = await getVariables(app);
setL(list);
}
fetchData();
}, []);

const handleChange = (e) => {
setS(e.target.value);
target[property].name = e.target.value;
target[property].value = { qStringExpression: { qExpr: `[${e.target.value}]` } };
changed();
};
return l.length === 0 ? (
<em>No variables in app</em>
) : (
<FormControl size="small" fullWidth>
<Select
onChange={handleChange}
displayEmpty
fullWidth
value={s}
renderValue={(selected) => {
if (selected.length === 0) {
return <em>Select a variable</em>;
}
return selected;
}}
>
{l.map((c) => (
<MenuItem key={c.value} value={c.value}>
{c.label}
</MenuItem>
))}
</Select>
</FormControl>
);
}

0 comments on commit 3b8dc2e

Please sign in to comment.