refactor: Variant

This commit is contained in:
cha0s 2021-01-30 14:14:05 -06:00
parent a296b57c02
commit 9ecf2d082b
8 changed files with 162 additions and 124 deletions

View File

@ -1,7 +1,11 @@
import {join, relative} from 'path'; import {join, relative} from 'path';
import {isInvocation, isKey} from '@avocado/behavior'; import {compile, isInvocation, isKey} from '@avocado/behavior';
import {PropTypes, React} from '@latus/react'; import {
PropTypes,
React,
useLatus,
} from '@latus/react';
import {useJsonPatcher} from '@persea/json'; import {useJsonPatcher} from '@persea/json';
import classnames from 'classnames'; import classnames from 'classnames';
@ -10,18 +14,18 @@ import Key from './expression/key';
const Expression = ({ const Expression = ({
context, context,
expression, value,
path, path,
type, type,
}) => { }) => {
const latus = useLatus();
const patch = useJsonPatcher(); const patch = useJsonPatcher();
let i = 0; let i = 0;
const {ops} = expression; const {ops} = value;
const Renderables = []; const Renderables = [];
let opsCount = 0; let opsCount = 0;
let walk = context;
// eslint-disable-next-line react/destructuring-assignment // eslint-disable-next-line react/destructuring-assignment
let description = context.constructor.descriptionFor(walk); let description = context.constructor.descriptionFor(context);
const onChange = (event, value, localPath) => { const onChange = (event, value, localPath) => {
const patches = []; const patches = [];
const j = parseInt(relative(path, localPath).split('/')[1], 10); const j = parseInt(relative(path, localPath).split('/')[1], 10);
@ -100,8 +104,8 @@ const Expression = ({
onChange(event, value, localPath); onChange(event, value, localPath);
} }
}} }}
op={op}
path={opPath} path={opPath}
value={op.key}
/>, />,
); );
opsCount += 1; opsCount += 1;
@ -136,15 +140,10 @@ const Expression = ({
opsCount += 1; opsCount += 1;
} }
if (isKey(op)) { if (isKey(op)) {
if (context === walk) { const current = compile({type: 'expression', ops: ops.slice(0, i + 1)}, latus)(context);
[walk] = context.get(op.key);
}
else {
walk = walk[op.key];
}
description = { description = {
...context.constructor.descriptionFor(walk), ...context.constructor.descriptionFor(current),
...description.children[op.key], ...description?.children?.[op.key] || {},
}; };
if (description.args && (!nextOp || !isInvocation(nextOp))) { if (description.args && (!nextOp || !isInvocation(nextOp))) {
Renderables.push( Renderables.push(
@ -154,17 +153,16 @@ const Expression = ({
onClick={((description, opPath) => () => { onClick={((description, opPath) => () => {
const parts = opPath.split('/'); const parts = opPath.split('/');
parts.push(parseInt(parts.pop(), 10) + 1); parts.push(parseInt(parts.pop(), 10) + 1);
const invocation = {
type: 'invoke',
args: description.args.map(() => ({
type: 'literal',
value: null,
})),
};
patch({ patch({
op: 'add', op: 'add',
path: parts.join('/'), path: parts.join('/'),
value: invocation, value: {
type: 'invoke',
args: description.args.map(() => ({
type: 'literal',
value: null,
})),
},
}); });
})(description, opPath)} })(description, opPath)}
type="button" type="button"
@ -218,7 +216,7 @@ Expression.propTypes = {
describe: PropTypes.func, describe: PropTypes.func,
get: PropTypes.func, get: PropTypes.func,
}).isRequired, }).isRequired,
expression: PropTypes.shape({ value: PropTypes.shape({
ops: PropTypes.arrayOf( ops: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
key: PropTypes.string, key: PropTypes.string,

View File

@ -1,22 +1,33 @@
import {join} from 'path'; import {join} from 'path';
import {PropTypes, React} from '@latus/react'; import {PropTypes, React} from '@latus/react';
import {useJsonPatcher} from '@persea/json';
import Literal from '../literal'; import Variant from '../variant';
import Key from './key';
const Invocation = ({ const Invocation = ({
context, context,
description, description,
Expression,
onChange, onChange,
op, op,
path, path,
}) => { }) => {
// eslint-disable-next-line react/destructuring-assignment // eslint-disable-next-line react/destructuring-assignment
const contextDescription = context.describe(); const argComponent = (arg, i) => {
const patch = useJsonPatcher(); const argPath = join(path, 'args', i.toString());
const options = description?.args?.[i].options;
const type = description?.args?.[i].type || 'undefined';
return (
<Variant
context={context}
onChange={onChange}
op={op}
options={options}
path={argPath}
type={type}
value={arg}
/>
);
};
return ( return (
<div className="invocation"> <div className="invocation">
( (
@ -26,55 +37,7 @@ const Invocation = ({
key={join(path, 'args', i.toString())} key={join(path, 'args', i.toString())}
className="invocation__arg" className="invocation__arg"
> >
{ {argComponent(arg, i)}
'literal' === arg.type
? (
<>
<Key
childrenDescription={{
'<literal>': {
label: '',
type: arg.type,
},
...contextDescription.children,
}}
onChange={(event, value) => {
patch({
op: 'replace',
path: join(path, 'args', i.toString()),
value: {
type: 'expression',
ops: [
{
type: 'key',
key: value,
},
],
},
});
}}
op={op}
path={path}
/>
<Literal
onChange={onChange}
options={description.args[i].options}
path={join(path, 'args', i.toString(), 'value')}
type={description.args[i].type}
value={arg}
/>
</>
)
: (
<Expression
context={context}
expression={arg}
onChange={onChange}
path={join(path, 'args', i.toString())}
type={description.args[i].type}
/>
)
}
{i < op.args.length - 1 && <span className="invocation__arg-sep">, </span>} {i < op.args.length - 1 && <span className="invocation__arg-sep">, </span>}
</div> </div>
)) ))
@ -85,9 +48,7 @@ const Invocation = ({
}; };
Invocation.propTypes = { Invocation.propTypes = {
context: PropTypes.shape({ context: PropTypes.shape({}).isRequired,
describe: PropTypes.func,
}).isRequired,
description: PropTypes.shape({ description: PropTypes.shape({
args: PropTypes.arrayOf( args: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
@ -96,7 +57,6 @@ Invocation.propTypes = {
}), }),
), ),
}).isRequired, }).isRequired,
Expression: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
op: PropTypes.shape({ op: PropTypes.shape({
args: PropTypes.arrayOf(PropTypes.any), args: PropTypes.arrayOf(PropTypes.any),

View File

@ -5,8 +5,8 @@ import {PropTypes, React} from '@latus/react';
const Key = ({ const Key = ({
childrenDescription, childrenDescription,
onChange, onChange,
op,
path, path,
value,
}) => { }) => {
const options = Object.entries(childrenDescription) const options = Object.entries(childrenDescription)
.map(([key]) => ( .map(([key]) => (
@ -18,7 +18,7 @@ const Key = ({
onChange={(event) => { onChange={(event) => {
onChange(event, event.target.value, join(path, 'key')); onChange(event, event.target.value, join(path, 'key'));
}} }}
value={op.key} value={value}
> >
{options} {options}
</select> </select>
@ -29,10 +29,8 @@ const Key = ({
Key.propTypes = { Key.propTypes = {
childrenDescription: PropTypes.shape({}).isRequired, childrenDescription: PropTypes.shape({}).isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
op: PropTypes.shape({
key: PropTypes.string,
}).isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
}; };
export default Key; export default Key;

View File

@ -6,7 +6,7 @@ import Expression from './expression';
const Expressions = ({ const Expressions = ({
context, context,
expressions: {expressions}, value: {expressions},
path, path,
}) => ( }) => (
<div className="expressions"> <div className="expressions">
@ -14,7 +14,7 @@ const Expressions = ({
expressions.map((expression, i) => ( expressions.map((expression, i) => (
<Expression <Expression
context={context} context={context}
expression={expression} value={expression}
key={join(path, 'expressions', i.toString())} key={join(path, 'expressions', i.toString())}
path={join(path, 'expressions', i.toString())} path={join(path, 'expressions', i.toString())}
type="void" type="void"
@ -26,7 +26,7 @@ const Expressions = ({
Expressions.propTypes = { Expressions.propTypes = {
context: PropTypes.shape({}).isRequired, context: PropTypes.shape({}).isRequired,
expressions: PropTypes.shape({ value: PropTypes.shape({
type: PropTypes.string, type: PropTypes.string,
expressions: PropTypes.arrayOf( expressions: PropTypes.arrayOf(
PropTypes.shape({}), PropTypes.shape({}),

View File

@ -1,17 +1,29 @@
import {join} from 'path';
import {PropTypes, React} from '@latus/react'; import {PropTypes, React} from '@latus/react';
import { import {
Number, Number,
} from '@persea/core'; } from '@persea/core';
import {JsonComponent} from '@persea/json'; import {
JsonComponent,
useJsonPatcher,
} from '@persea/json';
import Key from './expression/key';
const Literal = ({ const Literal = ({
context,
onChange, onChange,
path, path,
type, type,
op,
options, options,
value: {value}, value,
}) => { }) => {
const valueComponent = (path, type, value) => { // eslint-disable-next-line react/destructuring-assignment
const contextDescription = context.describe();
const patch = useJsonPatcher();
const valueComponent = (path, type, {value}) => {
switch (type) { switch (type) {
case 'number': case 'number':
return ( return (
@ -19,7 +31,7 @@ const Literal = ({
onChange={(event, value) => { onChange={(event, value) => {
onChange(event, value, path); onChange(event, value, path);
}} }}
value={value} value={null === value ? 0 : value}
/> />
); );
case 'string': case 'string':
@ -29,7 +41,7 @@ const Literal = ({
onChange={(event) => { onChange={(event) => {
onChange(event, value, path); onChange(event, value, path);
}} }}
value={value || ''} value={null === value ? '' : value}
/> />
); );
case 'object': case 'object':
@ -38,7 +50,7 @@ const Literal = ({
onChange={(event, value) => { onChange={(event, value) => {
onChange(event, value, path); onChange(event, value, path);
}} }}
json={value} json={null === value ? {} : value}
/> />
); );
default: default:
@ -46,32 +58,58 @@ const Literal = ({
} }
}; };
return ( return (
<div className="literal"> <>
{ <Key
options.length > 0 childrenDescription={{
? ( '<literal>': {
<select label: '',
onChange={(event) => { type: value.type,
onChange(event, value, path); },
}} ...contextDescription.children,
value={options.findIndex(({value: optionValue}) => value === optionValue)} }}
> onChange={(event, value) => {
{ patch({
Object.entries(options) op: 'replace',
.map(([key, {label}]) => ( path,
<option value: {
key={label} type: 'expression',
value={key} ops: [
> {
{label} type: 'key',
</option> key: value,
)) },
} ],
</select> },
) });
: valueComponent(path, type, value) }}
} op={op}
</div> path={path}
/>
<div className="literal">
{
options.length > 0
? (
<select
onChange={onChange}
value={options.findIndex(({value: optionValue}) => value.value === optionValue)}
>
{
Object.entries(options)
.map(([key, {label}]) => (
<option
key={label}
value={key}
>
{label}
</option>
))
}
</select>
)
: valueComponent(join(path, 'value'), type, value)
}
</div>
</>
); );
}; };
@ -80,8 +118,14 @@ Literal.defaultProps = {
}; };
Literal.propTypes = { Literal.propTypes = {
context: PropTypes.shape({
describe: PropTypes.func,
}).isRequired,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
op: PropTypes.shape({
key: PropTypes.string,
}).isRequired,
options: PropTypes.arrayOf(PropTypes.any), options: PropTypes.arrayOf(PropTypes.any),
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
// eslint-disable-next-line react/forbid-prop-types // eslint-disable-next-line react/forbid-prop-types

View File

@ -0,0 +1,30 @@
import {
PropTypes,
React,
useLatus,
} from '@latus/react';
const Variant = (props) => {
const {value} = props;
const {type} = value;
const latus = useLatus();
const BehaviorControllers = latus.get('%behavior-controllers');
const Controller = BehaviorControllers[type];
return (
<Controller
value={value}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
};
Variant.propTypes = {
onChange: PropTypes.func.isRequired,
path: PropTypes.string.isRequired,
value: PropTypes.shape({
type: PropTypes.string,
}).isRequired,
};
export default Variant;

View File

@ -2,6 +2,9 @@ import {basename, extname} from 'path';
import {camelCase} from '@latus/core'; import {camelCase} from '@latus/core';
import Expression from './behavior-components/expression';
import Expressions from './behavior-components/expressions';
import Literal from './behavior-components/literal';
import EntityResourceController from './resource-controllers/entity'; import EntityResourceController from './resource-controllers/entity';
export {EntityResourceController}; export {EntityResourceController};
@ -11,6 +14,11 @@ export default {
'@latus/core/starting': async (latus) => { '@latus/core/starting': async (latus) => {
const TraitRenderers = latus.invokeReduce('@persea/entity/trait-components'); const TraitRenderers = latus.invokeReduce('@persea/entity/trait-components');
latus.set('%trait-components', TraitRenderers); latus.set('%trait-components', TraitRenderers);
latus.set('%behavior-controllers', {
expression: Expression,
expressions: Expressions,
literal: Literal,
});
}, },
'@persea/core/resource-controllers': () => [ '@persea/core/resource-controllers': () => [
EntityResourceController, EntityResourceController,

View File

@ -14,7 +14,7 @@ const Alive = ({
Death actions Death actions
<Expressions <Expressions
context={entity.context} context={entity.context}
expressions={json.params.deathActions} value={json.params.deathActions}
path={join(path, 'params/deathActions')} path={join(path, 'params/deathActions')}
/> />
</label> </label>