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="entity">
<div className="document-pane" /> <div className="document-pane" />
<div className="settings-pane"> <div className="settings-pane">
<Traits context={entity.context} traits={traits} /> <Traits context={entity.context} target={json.uuid} traits={traits} />
</div> </div>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

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