feat: first step toward mutability

This commit is contained in:
cha0s 2020-06-21 19:46:15 -05:00
parent 2de1665426
commit d2e26d3727
6 changed files with 60 additions and 28 deletions

View File

@ -29,7 +29,7 @@ const EntityComponent = (props) => {
<div className="entity">
<div className="document-pane" />
<div className="settings-pane">
<Traits context={entity.context} traits={traits} />
<Traits context={entity.context} target={json.uuid} traits={traits} />
</div>
</div>
);

View File

@ -46,7 +46,15 @@ export const selectEntityByUri = createSelector(
const slice = createSlice({
name: 'entities',
initialState: adapter.getInitialState({uris: {}}),
reducers: {},
reducers: {
setTraitProperty: (state, {payload: {target, trait, type, key, value}}) => {
const {traits} = state.entities[target];
traits[trait] = traits[trait] || {};
traits[trait][type] = traits[trait][type] || {};
traits[trait][type][key] = value;
state.entities[target].traits = traits;
},
},
extraReducers: {
[fetchEntity.fulfilled]: (state, action) => {
adapter.upsertMany(state, action.payload.entities);
@ -56,4 +64,8 @@ const slice = createSlice({
},
});
export const {
setTraitProperty,
} = slice.actions;
export default slice.reducer;

View File

@ -6,6 +6,7 @@ import {produce} from 'immer';
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
// import ReactMarkdown from 'react-markdown';
import {useDispatch} from 'react-redux';
import {
Tab,
Tabs,
@ -17,6 +18,8 @@ import {deregisterHooks, registerHooks} from 'scwp';
import Value from '~/client/value';
import {setTraitProperty} from './state';
const SCROLL_MAG = 80;
const decorate = compose(
@ -44,7 +47,7 @@ const ensureTraitComponents = () => {
}
};
const makeTabSelector = (context, type) => createSelector(
const makeTabSelector = (context, dispatch, target, type) => createSelector(
(_) => _,
(trait) => {
ensureTraitComponents();
@ -55,7 +58,7 @@ const makeTabSelector = (context, type) => createSelector(
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
const paramsDescription = Trait?.describeParams() || {};
const stateDescription = Trait?.describeState() || {};
const renderComponents = (description, values) => (
const renderComponents = (traitHalf, description, values) => (
// eslint-disable-next-line no-shadow
Object.values(mapObject(description, (description, key) => {
const {label, options} = description;
@ -68,6 +71,13 @@ const makeTabSelector = (context, type) => createSelector(
<div className="invisible-separator" />
<Value.Component
context={context}
onChange={(value) => dispatch(setTraitProperty({
target,
trait: type,
type: traitHalf,
key,
value,
}))}
options={options}
value={values[key]}
/>
@ -79,55 +89,56 @@ const makeTabSelector = (context, type) => createSelector(
? <TraitComponent params={params} state={state} />
: (
<form className="trait-component-builtin">
{renderComponents(paramsDescription, params)}
{renderComponents(stateDescription, state)}
{renderComponents('params', paramsDescription, params)}
{renderComponents('state', stateDescription, state)}
</form>
);
},
);
const Traits = (props) => {
const {context, traits} = props;
const [tabSelectorMap, setTabSelectorMap] = useState({});
const {context, target, traits} = props;
const dispatch = useDispatch();
const [traitForms, setTraitForms] = useState({});
const [mts, setMts] = useState();
useEffect(() => {
if (!mts) {
setMts(() => (type) => makeTabSelector(context, dispatch, target, type));
}
}, [mts, setMts]);
useEffect(() => {
if (!mts) {
return;
}
const entries = Object.entries(traits);
setTabSelectorMap(produce(tabSelectorMap, (draft) => {
setTraitForms(produce(traitForms, (draft) => {
for (let i = 0; i < entries.length; i++) {
const [type] = entries[i];
if (!draft[type]) {
// eslint-disable-next-line no-param-reassign
draft[type] = new WeakMap();
}
if (!draft[type].has(traits)) {
draft[type].set(traits, makeTabSelector(context, type));
}
draft[type] = mts(type);
}
}));
const M = {
autoreg$accept: (type, M2) => {
if ('trait' === type) {
const {default: Trait} = M2;
setTabSelectorMap(produce(tabSelectorMap, (draft) => {
const traitType = Trait.type();
if (!draft[traitType]) {
// eslint-disable-next-line no-param-reassign
draft[traitType] = new WeakMap();
}
draft[traitType].set(traits, makeTabSelector(traitType));
setTraitForms(produce(traitForms, (draft) => {
draft[Trait.type()] = mts(Trait.type());
}));
}
},
};
registerHooks(M, __filename);
return () => deregisterHooks(M);
}, [context, tabSelectorMap, traits]);
}, [mts, traits]);
const [tabIndex, setTabIndex] = useState(0);
let listRef;
const entries = Object.entries(traits);
const tabs = [];
for (let i = 0; i < entries.length; i++) {
const [type, trait] = entries[i];
tabs.push([type, tabSelectorMap[type]?.get(traits)(trait)]);
if (type in traitForms) {
tabs.push([type, traitForms[type](trait)]);
}
}
return (
<div className="traits">

View File

@ -14,6 +14,7 @@ const decorate = compose(
const Literal = ({
context,
onChange,
value,
}) => {
const typeRenderers = useTypeRenderers();
@ -36,7 +37,7 @@ const Literal = ({
.map((tierOption) => <option key={tierOption}>{tierOption}</option>)
}
</select>
{Component ? <Component value={value.value} /> : null}
{Component ? <Component onChange={onChange} value={value.value} /> : null}
</div>
);
};

View File

@ -4,6 +4,7 @@ import React from 'react';
import propTypes from './prop-types';
const String = ({
onChange,
options,
value,
}) => (
@ -17,7 +18,13 @@ const String = ({
))}
</select>
)
: <input readOnly type="text" value={value} />
: (
<input
onChange={(event) => onChange(event.target.value)}
type="text"
value={value}
/>
)
}
</span>
);

View File

@ -14,6 +14,7 @@ const decorate = compose(
const Value = ({
context,
onChange,
options,
value,
}) => {
@ -25,7 +26,7 @@ const Value = ({
<span className="value">
{
Component
? <Component context={context} options={options} value={value} />
? <Component context={context} onChange={onChange} options={options} value={value} />
: <div className="unrenderable" title={`No renderer for '${type}' found!`}>?</div>
}
</span>