feat: steps++

This commit is contained in:
cha0s 2020-06-19 15:31:26 -05:00
parent 2c9fe3a1a7
commit 35e2a36e58
18 changed files with 442 additions and 133 deletions

View File

@ -1,4 +1,5 @@
import {compose} from '@avocado/core';
import {Entity} from '@avocado/entity';
import contempo from 'contempo';
import React from 'react';
@ -8,19 +9,21 @@ const decorate = compose(
contempo(require('./entity.raw.scss')),
);
const kitty = require('~/../fixtures/kitty.entity.json');
const json = require('~/../fixtures/kitty.entity.json');
const Entity = () => {
const entity = kitty;
const {traits} = entity;
const EntityComponent = (props) => {
const {context} = props;
const entity = new Entity(json);
context.add('entity', entity);
const {traits} = json;
return (
<div className="entity">
<div className="document-pane" />
<div className="settings-pane">
<Traits traits={traits} />
<Traits context={context} traits={traits} />
</div>
</div>
);
};
export default decorate(Entity);
export default decorate(EntityComponent);

View File

@ -1,10 +1,13 @@
import {Context} from '@avocado/behavior';
import {hot} from 'react-hot-loader';
import React from 'react';
import Entity from './entity';
const context = new Context();
const Persea = () => (
<Entity />
<Entity context={context} />
);
export default hot(module)(Persea);

View File

@ -53,7 +53,7 @@ const ensureTypeRenderers = () => {
}
};
const makeTabSelector = (type) => createSelector(
const makeTabSelector = (context, type) => createSelector(
(_) => _,
(trait) => {
ensureTypeRenderers();
@ -79,6 +79,7 @@ const makeTabSelector = (type) => createSelector(
</span>
<div className="invisible-separator" />
<Component
context={context}
key={key}
label={label}
name={key}
@ -102,7 +103,7 @@ const makeTabSelector = (type) => createSelector(
);
const Traits = (props) => {
const {traits} = props;
const {context, traits} = props;
const [tabSelectorMap, setTabSelectorMap] = useState({});
useEffect(() => {
const entries = Object.entries(traits);
@ -114,7 +115,7 @@ const Traits = (props) => {
draft[type] = new WeakMap();
}
if (!draft[type].has(traits)) {
draft[type].set(traits, makeTabSelector(type));
draft[type].set(traits, makeTabSelector(context, type));
}
}
}));

View File

@ -1,28 +1,19 @@
.args {
margin-left: 2em;
}
.arg {
margin-top: 0.125em;
> :first-child::after {
color: #999999;
content: ', ';
}
}
.bracket, .paren {
color: #999999;
}
.object {
background-color: #333333;
border: 1px solid black;
padding: 0.25em;
:scope {
margin-top: 0.5em;
}
li {
margin-bottom: 0.5em;
border: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: none;
padding: 1em;
&:last-child {
margin-bottom: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
&:nth-of-type(2n) {
background-color: rgba(0, 0, 0, 0.025);
}
&:nth-of-type(2n+1) {
background-color: rgba(255, 255, 255, 0.025);
}
}

View File

@ -12,6 +12,7 @@ const decorate = compose(
);
const Actions = ({
context,
name,
label,
options,
@ -22,7 +23,15 @@ const Actions = ({
{
value.traversals.length > 0 && (
value.traversals.map(
(traversal) => <li><Value.component value={traversal} /></li>,
(traversal) => (
<li>
<Value.component
context={context}
type="void|ticking-promise"
value={traversal}
/>
</li>
),
)
)
}

View File

@ -8,7 +8,10 @@ const Bool = ({
label,
value,
}) => (
<input name={name} type="checkbox" checked={value} />
<select value={value}>
<option>false</option>
<option>true</option>
</select>
);
Bool.propTypes = {

View File

@ -1,11 +1,16 @@
.condition {
display: flex;
justify-content: flex-end;
align-items: center;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
}
.operator {
margin: 0 0.5em;
margin: 0.25em 0;
text-align: center;
text-align-last: center;
}
.operand .steps {
display: flex;
}

View File

@ -4,29 +4,11 @@ import PropTypes from 'prop-types';
import React from 'react';
import propertyPropTypes from './property-prop-types';
import Value from './value.type-renderer';
const renderValue = (value) => {
switch (value.type) {
case 'traversal': {
const {steps} = value;
return (
<span>
{steps.length > 0 && (
steps.slice(1).reduce((r, step) => `${r}.${step.key}`, steps[0].key)
)}
</span>
);
}
case 'literal':
return <input value={value.value} />;
default:
return null;
}
};
const renderOperand = (operand) => (
const renderOperand = (context, operand, type) => (
<span className="operand">
{renderValue(operand)}
<Value.component context={context} type={type} value={operand} />
</span>
);
@ -44,6 +26,7 @@ const decorate = compose(
);
const Condition = ({
context,
name,
label,
options,
@ -59,6 +42,7 @@ const Condition = ({
case '>=':
case '<':
case '<=':
const type = -1 !== ['is', 'isnt'].indexOf(value.operator) ? 'any' : 'number';
return value.operands.slice(1).reduce(
(r, operand) => (
<>
@ -66,10 +50,10 @@ const Condition = ({
<select className="operator" value={value.operator}>
{binaryOps.map(([k, v]) => <option value={k}>{v}</option>)}
</select>
{renderOperand(operand)}
{renderOperand(context, operand, type)}
</>
),
renderOperand(value.operands[0]),
renderOperand(context, value.operands[0], type),
);
default:
return undefined;

View File

@ -0,0 +1,49 @@
import {compose} from '@avocado/core';
import classnames from 'classnames';
import contempo from 'contempo';
import PropTypes from 'prop-types';
import React from 'react';
import propertyPropTypes from './property-prop-types';
import Actions from './actions.type-renderer';
const decorate = compose(
contempo(require('./routines.raw.scss')),
);
const Routines = ({
context,
name,
label,
options,
value,
}) => {
const entries = Object.entries(value.routines);
return (
<div className="routines">
<ol>
{
entries.length > 0 && (
entries.map(
([name, routine]) => (
<li className="routine">
<Actions.component context={context} value={routine.routine} />
</li>
),
)
)
}
</ol>
</div>
);
};
Routines.propTypes = {
...propertyPropTypes,
value: PropTypes.shape({}).isRequired,
};
export default {
type: 'routines',
component: decorate(Routines),
};

View File

@ -0,0 +1,35 @@
import {flatten} from '@avocado/core';
import {typeFits, typeFromV} from './typing';
export const variableStepsList = (key, variable, description, type) => {
const steps = [];
if (typeFits(type, description.type)) {
steps.push({type: 'key', key});
if ('function' === typeof variable) {
steps.push({type: 'invoke', args: []});
}
}
if (!variable || !variable.contextDescription) {
return steps;
}
const {children} = variable.contextDescription();
const sublists = Object.entries(children)
.map(([key, description]) => (
flatten(variableStepsList(key, variable[key], description, type))
))
.map((childLists) => (
childLists.length > 0 ? [{type: 'key', key}].concat(childLists) : []
))
.filter((lists) => lists.length > 0)
return sublists.length > 0 ? sublists : [steps];
};
export const contextStepsList = (context, type) => (
flatten(
Object.entries(context.all())
.map(([key, variable]) => (
variableStepsList(key, variable, {type: typeFromV(variable)}, type)
))
)
);

View File

@ -0,0 +1,95 @@
import {compose} from '@avocado/core';
import contempo from 'contempo';
import React, {useState} from 'react';
import {contextStepsList} from './steps-lists';
import {typeFits, typeFromSteps, typeFromV} from './typing';
import Value from './value.type-renderer';
const decorate = compose(
contempo(require('./steps.raw.scss')),
);
const Steps = (props) => {
const {context, steps, type, value} = props;
let stepsList;
if ('undefined' === typeof value) {
stepsList = contextStepsList(context, type);
}
else {
stepsList = contextStepsList(
context,
value.steps ? typeFromSteps(context, value.steps) : typeFromV(value.value),
);
}
return (
<div className="steps">
{steps.map((step, i) => {
switch (step.type) {
case 'key':
const tierOptions = Object.keys(stepsList.reduce((r, optionSteps) => {
if (!optionSteps[i] || !optionSteps[i].key) {
return r;
}
for (let j = 0; j < i; ++j) {
if (steps[j].key !== optionSteps[j].key) {
return r;
}
}
return {...r, [optionSteps[i].key]: true};
}, {}));
if (0 === i) {
const index = tierOptions.indexOf('context');
if (-1 !== index) {
tierOptions.splice(index, 1);
}
tierOptions.push('Literal');
}
return (
<span className="key">
<select value={steps[i].key}>
{
tierOptions
.sort((l, r) => l.toLowerCase() < r.toLowerCase() ? -1 : 1)
.map((tierOption) => <option>{tierOption}</option>)
}
</select>
</span>
);
case 'invoke':
return (
<div className="invoke">
<span className="paren open">(</span>
{step.args.map((arg) => (
<div className="arg">
<Value.component
context={context}
type={arg.steps ? typeFromSteps(context, arg.steps) : typeFromV(arg.value)}
value={arg}
/>
</div>
))}
<span className="paren close">)</span>
</div>
);
default:
return null;
}
})}
{
value && (
<span className="assign">
<span className="op">=</span>
<Value.component
context={context}
type={typeFromSteps(context, steps)}
value={value}
/>
</span>
)
}
</div>
);
};
export default decorate(Steps);

View File

@ -0,0 +1,53 @@
:scope {
display: flex;
flex-wrap: wrap;
}
.key + .key {
select {
border-left: none;
}
}
.args {
margin-left: 2em;
}
.arg {
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
margin-left: 2em;
padding: 0.5em;
&:not(:last-of-type) {
border-bottom: none;
}
&:nth-of-type(2n) {
background-color: rgba(0, 0, 0, 0.025);
}
&:nth-of-type(2n+1) {
background-color: rgba(255, 255, 255, 0.025);
}
}
.arg .steps {
display: flex;
}
.bracket, .paren {
color: #999999;
}
.invoke {
// border-left: 1px dotted rgba(255, 255, 255, 0.1);
padding-left: 1em;
}
.assign {
display: flex;
align-items: center;
}
.op {
align-self: flex-start;
margin: 0.5em 1em;
}

View File

@ -0,0 +1,61 @@
export function typeFromV(v) {
if ('undefined' === typeof v) {
return 'undefined';
}
if (null === v) {
return 'null';
}
if ('number' === typeof v) {
return 'number';
}
if ('string' === typeof v) {
return 'string';
}
if ('boolean' === typeof v) {
return 'bool';
}
if (v.length && 2 === v.length && v instanceof Array) {
return 'vector';
}
if (!v.contextDescription) {
return 'object';
}
const {type} = v.contextDescription();
return type;
}
export function typeFromSteps(context, steps) {
if (!steps || 0 === steps.length) {
return 'undefined';
}
const first = context.map.get(steps[0].key);
const [, type] = steps.slice(1).reduce(
([v, type], step, i) => {
if (i === steps.length - 2) {
if (v.contextDescription) {
const {children} = v.contextDescription();
return [undefined, step.key ? children[step.key].type : type];
}
else {
return [undefined, type];
}
}
else {
if (step.key) {
const {children} = v.contextDescription();
return [v[step.key], children[step.key].type];
}
else {
return [v, type];
}
}
step
},
[first, typeFromV(first)],
);
return type;
}
export function typeFits(reference, candidate) {
return 'any' === reference || -1 !== (reference.split('|') || []).indexOf(candidate);
}

View File

@ -0,0 +1,22 @@
.fn {
text-align-last: right;
}
:scope {
display: flex;
align-items: center;
}
.object {
background-color: #333333;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 0.25em;
}
.object, input {
// border-left: none;
}
select {
margin-right: 0.25em;
}

View File

@ -4,67 +4,46 @@ import contempo from 'contempo';
import PropTypes from 'prop-types';
import React from 'react';
import Bool from './bool.type-renderer';
import propertyPropTypes from './property-prop-types';
import Steps from './steps';
import {contextStepsList} from './steps-lists';
import {typeFits, typeFromSteps, typeFromV} from './typing';
const reduceStep = (r, step) => {
switch (step.type) {
case 'key':
return `${r}.${step.key}`;
case 'invoke':
return (
<span className="step">
<span className="fn"><input value={r} /></span>
<span className="paren open">(</span>
<div className="args">
{
step.args.length > 0 && (
step.args.slice(1).map(renderValue).reduce(
(r2, element, i) => (
<>
{r2}
<div className={classnames('arg', step.args[i + 1].type)}>
{element}
</div>
</>
),
<div className={classnames('arg', step.args[0].type)}>
{renderValue(step.args[0])}
</div>,
)
)
}
</div>
<span className="paren close">)</span>
</span>
);
default:
return undefined;
}
};
const renderValue = (value) => {
const renderValue = (context, type, value) => {
switch (value.type) {
case 'traversal': {
const {steps} = value;
return (
<span>
{steps.length > 0 && (
steps.slice(1).reduce(
reduceStep,
steps[0].key,
)
)}
</span>
);
return <Steps context={context} steps={value.steps} type={type} value={value.value} />;
}
case 'literal':
if (
null === value.value
|| 'string' === typeof value.value
|| 'number' === typeof value.value
) {
return <span className="wrapper"><input value={value.value} /></span>;
// const type = typeFromV(value.value);
const stepsList = contextStepsList(context, type);
const tierOptions = Object.keys(stepsList.reduce((r, optionSteps) => {
if (!optionSteps[0] || !optionSteps[0].key) {
return r;
}
return {...r, [optionSteps[0].key]: true};
}, {}));
tierOptions.splice(tierOptions.indexOf('context'), 1);
tierOptions.push('Literal');
return (
<div className="literal">
<select value="Literal">
{
tierOptions
.sort((l, r) => l.toLowerCase() < r.toLowerCase() ? -1 : 1)
.map((tierOption) => <option>{tierOption}</option>)
}
</select>
{
(() => {
switch (type) {
case 'bool':
return <Bool.component value={value.value} />;
case 'number':
case 'string':
return <span className="wrapper"><input value={value.value} /></span>;
case 'object':
return (
<div className="object">
<span className="bracket open">{'{'}</span>
@ -80,6 +59,13 @@ const renderValue = (value) => {
default:
return null;
}
})()
}
</div>
);
default:
return null;
}
}
const decorate = compose(
@ -87,11 +73,13 @@ const decorate = compose(
);
const Value = ({
context,
name,
label,
options,
type,
value,
}) => renderValue(value);
}) => renderValue(context, type || 'any', value);
Value.propTypes = {
...propertyPropTypes,

View File

@ -72,7 +72,7 @@ code {
label {
align-items: left;
background-color: #272727;
background-color: rgba(255, 255, 255, 0.025);
color: #ffffff;
display: flex;
flex-direction: column;
@ -93,12 +93,16 @@ label {
}
label:nth-of-type(2n+1) {
background-color: #1d1d1d
background-color: rgba(0, 0, 0, 0.025);
}
[contenteditable] {
cursor: text;
}
input {
background: #333;
border: 1px solid black;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 0.75em;
padding: 0.5em;
@ -106,7 +110,7 @@ input {
fieldset {
background-color: #151515;
border: 1px solid #000000;
border: 1px solid rgba(255, 255, 255, 0.1);
display: inline-block;
margin: 0 0 1em 0;
padding: 0.5em;
@ -121,16 +125,19 @@ button, input[type="checkbox"], input[type="checkbox"] + label {
select {
background: #222222;
border: 1px solid #000000;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
cursor: pointer;
font-size: 0.75em;
line-height: 1em;
margin: 0.25em 0;
-moz-appearance: none;
-webkit-appearance: none;
padding: 0.5em;
}
*:focus {
position: relative;
box-shadow: 0 0 2px 0 #d68030ff;
outline: none;
z-index: 1;
}