Skip to content

Commit

Permalink
[docs] Replace react-inspector with custom TreeView implementa… (#17662)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Nov 12, 2019
1 parent 0f5a304 commit 2df006e
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 36 deletions.
1 change: 0 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@
"react-draggable": "^4.0.3",
"react-final-form": "^6.3.0",
"react-frame-component": "^4.1.1",
"react-inspector": "^3.0.2",
"react-number-format": "^4.0.8",
"react-redux": "^7.1.1",
"react-router": "^5.0.0",
Expand Down
234 changes: 214 additions & 20 deletions docs/src/pages/customization/default-theme/DefaultTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,185 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import url from 'url';
import Inspector from 'react-inspector';
import { withStyles, createMuiTheme, useTheme } from '@material-ui/core/styles';
import ExpandIcon from '@material-ui/icons/ExpandMore';
import CollapseIcon from '@material-ui/icons/ChevronRight';
import TreeView from '@material-ui/lab/TreeView';
import TreeItem from '@material-ui/lab/TreeItem';
import clsx from 'clsx';
import { makeStyles, withStyles, createMuiTheme, lighten } from '@material-ui/core/styles';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';

/**
* @param {unknown} value
*/
function useType(value) {
if (Array.isArray(value)) {
return 'array';
}
if (value === null) {
return 'null';
}

return typeof value;
}

/**
*
* @param {unknown} value
* @param {ReturnType<typeof useType>} type
*/
function useLabel(value, type) {
switch (type) {
case 'array':
return `Array(${value.length})`;
case 'null':
return 'null';
case 'undefined':
return 'undefined';
case 'function':
return `f ${value.name}()`;
case 'object':
return 'Object';
case 'string':
return `"${value}"`;
case 'symbol':
return `Symbol(${String(value)})`;
case 'bigint':
case 'boolean':
case 'number':
default:
return String(value);
}
}

function useTokenType(type) {
switch (type) {
case 'object':
case 'array':
return 'comment';
default:
return type;
}
}

function ObjectEntryLabel({ objectKey, objectValue }) {
const type = useType(objectValue);
const label = useLabel(objectValue, type);
const tokenType = useTokenType(type);

return (
<React.Fragment>
{objectKey}: <span className={clsx('token', tokenType)}>{label}</span>
</React.Fragment>
);
}
ObjectEntryLabel.propTypes = { objectKey: PropTypes.any, objectValue: PropTypes.any };

const useObjectEntryStyles = makeStyles({
treeItem: {
'&:focus > $treeItemContent': {
backgroundColor: lighten('#333', 0.08),
outline: `2px dashed ${lighten('#333', 0.3)}`,
},
},
treeItemContent: {
'&:hover': {
backgroundColor: lighten('#333', 0.08),
},
},
});

function ObjectEntry(props) {
const { nodeId, objectKey, objectValue } = props;

const keyPrefix = nodeId;

let children = null;
if (
(objectValue !== null && typeof objectValue === 'object') ||
typeof objectValue === 'function'
) {
children =
Object.keys(objectValue).length === 0
? undefined
: Object.keys(objectValue).map(key => {
return (
<ObjectEntry
key={key}
nodeId={`${keyPrefix}.${key}`}
objectKey={key}
objectValue={objectValue[key]}
/>
);
});
}

const classes = useObjectEntryStyles();

return (
<TreeItem
classes={{ root: classes.treeItem, content: classes.treeItemContent }}
nodeId={nodeId}
label={<ObjectEntryLabel objectKey={objectKey} objectValue={objectValue} />}
>
{children}
</TreeItem>
);
}
ObjectEntry.propTypes = {
nodeId: PropTypes.string.isRequired,
objectKey: PropTypes.any.isRequired,
objectValue: PropTypes.any,
};

function Inspector(props) {
const { data, expandPaths } = props;

const keyPrefix = '$ROOT';
const defaultExpanded = React.useMemo(() => {
return Array.isArray(expandPaths)
? expandPaths.map(expandPath => `${keyPrefix}.${expandPath}`)
: [];
}, [keyPrefix, expandPaths]);
// for default* to take effect we need to remount
const key = React.useMemo(() => defaultExpanded.join(''), [defaultExpanded]);

return (
<TreeView
key={key}
defaultCollapseIcon={<ExpandIcon />}
defaultEndIcon={<div style={{ width: 24 }} />}
defaultExpanded={defaultExpanded}
defaultExpandIcon={<CollapseIcon />}
>
{Object.keys(data).map(objectKey => {
return (
<ObjectEntry
key={objectKey}
nodeId={`${keyPrefix}.${objectKey}`}
objectKey={objectKey}
objectValue={data[objectKey]}
/>
);
})}
</TreeView>
);
}

Inspector.propTypes = {
data: PropTypes.any,
expandPaths: PropTypes.arrayOf(PropTypes.string),
};

const styles = theme => ({
root: {
backgroundColor: '#333',
borderRadius: 4,
color: '#fff',
display: 'block',
padding: theme.spacing(2),
paddingTop: 0,
// Match <Inspector /> default theme.
backgroundColor: theme.palette.type === 'light' ? theme.palette.common.white : '#242424',
minHeight: theme.spacing(40),
width: '100%',
},
Expand All @@ -21,9 +189,32 @@ const styles = theme => ({
},
});

function computeNodeIds(object, prefix) {
if ((object !== null && typeof object === 'object') || typeof object === 'function') {
const ids = [];
Object.keys(object).forEach(key => {
ids.push(`${prefix}${key}`, ...computeNodeIds(object[key], `${prefix}${key}.`));
});

return ids;
}
return [];
}

function useNodeIdsLazy(object) {
const [allNodeIds, setAllNodeIds] = React.useState([]);
// technically we want to compute them lazily until we need them (expand all)
// yielding is good enough. technically we want to schedule the computation
// with low pri and upgrade the priority later
React.useEffect(() => {
setAllNodeIds(computeNodeIds(object, ''));
}, [object]);

return allNodeIds;
}

function DefaultTheme(props) {
const { classes } = props;
const docsTheme = useTheme();
const [checked, setChecked] = React.useState(false);
const [expandPaths, setExpandPaths] = React.useState(null);
const t = useSelector(state => state.options.t);
Expand All @@ -45,12 +236,16 @@ function DefaultTheme(props) {
);
}, []);

const theme = createMuiTheme({
palette: {
type: docsTheme.palette.type,
},
direction: docsTheme.direction,
});
const data = React.useMemo(createMuiTheme, []);

const allNodeIds = useNodeIdsLazy(data);
React.useDebugValue(allNodeIds);
React.useEffect(() => {
if (checked) {
// in case during the event handler allNodeIds wasn't computed yet
setExpandPaths(allNodeIds);
}
}, [checked, allNodeIds]);

return (
<div className={classes.root}>
Expand All @@ -59,20 +254,19 @@ function DefaultTheme(props) {
control={
<Switch
checked={checked}
onChange={(event, value) => {
setChecked(value);
onChange={(event, newChecked) => {
setChecked(newChecked);
if (newChecked) {
setExpandPaths(allNodeIds);
} else {
setExpandPaths([]);
}
}}
/>
}
label={t('expandAll')}
/>
<Inspector
theme={theme.palette.type === 'light' ? 'chromeLight' : 'chromeDark'}
data={theme}
expandPaths={expandPaths}
expandLevel={checked ? 100 : 1}
key={`${checked}-${theme.palette.type}`} // Remount
/>
<Inspector data={data} expandPaths={expandPaths} expandLevel={checked ? 100 : 1} />
</div>
);
}
Expand Down
16 changes: 1 addition & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8186,11 +8186,6 @@ is-directory@^0.3.1:
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=

is-dom@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.0.9.tgz#483832d52972073de12b9fe3f60320870da8370d"
integrity sha1-SDgy1SlyBz3hK5/j9gMghw2oNw0=

is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
Expand Down Expand Up @@ -11798,7 +11793,7 @@ [email protected]:
loose-envify "^1.3.1"
object-assign "^4.1.1"

prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
Expand Down Expand Up @@ -12159,15 +12154,6 @@ react-input-autosize@^2.2.2:
dependencies:
prop-types "^15.5.8"

react-inspector@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-3.0.2.tgz#c530a06101f562475537e47df428e1d7aff16ed8"
integrity sha512-PSR8xDoGFN8R3LKmq1NT+hBBwhxjd9Qwz8yKY+5NXY/CHpxXHm01CVabxzI7zFwFav/M3JoC/Z0Ro2kSX6Ef2Q==
dependencies:
babel-runtime "^6.26.0"
is-dom "^1.0.9"
prop-types "^15.6.1"

[email protected]:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
Expand Down

0 comments on commit 2df006e

Please sign in to comment.