feat: first step toward mutability
This commit is contained in:
parent
2de1665426
commit
d2e26d3727
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user