refactor: proper memo
This commit is contained in:
parent
622f32015f
commit
597de89826
|
@ -19,7 +19,7 @@ const EntityComponent = (props) => {
|
|||
if (!json) {
|
||||
dispatch(fetchEntity(uri));
|
||||
}
|
||||
}, [json, uri]);
|
||||
}, [dispatch, json, uri]);
|
||||
if (!json) {
|
||||
return null;
|
||||
}
|
||||
|
|
87
src/client/resources/entity/trait-pane.jsx
Normal file
87
src/client/resources/entity/trait-pane.jsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
import {mapObject} from '@avocado/core';
|
||||
import {lookupTrait} from '@avocado/entity';
|
||||
import {all as allTraitComponents} from '@avocado/entity/trait/trait-components.scwp';
|
||||
import React, {useMemo} from 'react';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import Value from '~/client/value';
|
||||
|
||||
import {setTraitProperty} from './state';
|
||||
|
||||
let TraitComponents;
|
||||
const ensureTraitComponents = () => {
|
||||
if (!TraitComponents) {
|
||||
TraitComponents = Object.values(allTraitComponents()).reduce((r, M) => {
|
||||
const {default: TraitComponent} = M;
|
||||
return {...r, [TraitComponent.type]: TraitComponent};
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
const makePane = (context, dispatch, target, type) => (trait) => {
|
||||
ensureTraitComponents();
|
||||
const {params: paramsRaw, state: stateRaw} = trait;
|
||||
const {[type]: TraitComponent} = TraitComponents;
|
||||
const Trait = lookupTrait(type);
|
||||
const params = Trait ? {...Trait.defaultParams(), ...paramsRaw} : paramsRaw;
|
||||
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
|
||||
if (TraitComponent) {
|
||||
return <TraitComponent params={params} state={state} />;
|
||||
}
|
||||
const paramsDescription = Trait?.describeParams() || {};
|
||||
const stateDescription = Trait?.describeState() || {};
|
||||
const renderComponents = (traitHalf, description, values) => (
|
||||
// eslint-disable-next-line no-shadow
|
||||
Object.values(mapObject(description, (description, key) => {
|
||||
const {label, options} = description;
|
||||
return (
|
||||
<label key={key}>
|
||||
<span className="text">
|
||||
{label}
|
||||
:
|
||||
</span>
|
||||
<div className="invisible-separator" />
|
||||
<Value.Component
|
||||
context={context}
|
||||
onChange={(value) => dispatch(setTraitProperty({
|
||||
target,
|
||||
trait: type,
|
||||
type: traitHalf,
|
||||
key,
|
||||
value,
|
||||
}))}
|
||||
options={options}
|
||||
value={values[key]}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}))
|
||||
);
|
||||
return (
|
||||
<form className="trait-component-builtin">
|
||||
{renderComponents('params', paramsDescription, params)}
|
||||
{renderComponents('state', stateDescription, state)}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const TraitPane = (props) => {
|
||||
const {
|
||||
context,
|
||||
target,
|
||||
trait,
|
||||
type,
|
||||
} = props;
|
||||
const dispatch = useDispatch();
|
||||
// TODO: dependency on context is too noisy
|
||||
const renderTraitPane = useMemo(
|
||||
() => makePane(context, dispatch, target, type),
|
||||
[context, dispatch, target, type],
|
||||
);
|
||||
return useMemo(
|
||||
() => renderTraitPane(trait),
|
||||
[renderTraitPane, trait],
|
||||
);
|
||||
};
|
||||
|
||||
export default TraitPane;
|
14
src/client/resources/entity/trait-tab.jsx
Normal file
14
src/client/resources/entity/trait-tab.jsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
const TraitTab = (props) => {
|
||||
const {type} = props;
|
||||
return (
|
||||
<span className="wrapper">
|
||||
<span className="icon" />
|
||||
{type}
|
||||
<span className="close">✕</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraitTab;
|
|
@ -1,24 +1,16 @@
|
|||
import {compose, mapObject} from '@avocado/core';
|
||||
import {all as allTraitComponents} from '@avocado/entity/trait/trait-components.scwp';
|
||||
import {lookupTrait} from '@avocado/entity';
|
||||
import {compose} from '@avocado/core';
|
||||
import contempo from 'contempo';
|
||||
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 React, {useState} from 'react';
|
||||
import {
|
||||
Tab,
|
||||
Tabs,
|
||||
TabList,
|
||||
TabPanel,
|
||||
} from 'react-tabs';
|
||||
import {createSelector} from '@reduxjs/toolkit';
|
||||
import {deregisterHooks, registerHooks} from 'scwp';
|
||||
|
||||
import Value from '~/client/value';
|
||||
|
||||
import {setTraitProperty} from './state';
|
||||
import TraitTab from './trait-tab';
|
||||
import TraitPane from './trait-pane';
|
||||
|
||||
const SCROLL_MAG = 80;
|
||||
|
||||
|
@ -37,115 +29,18 @@ const decorate = compose(
|
|||
// )
|
||||
// );
|
||||
|
||||
let TraitComponents;
|
||||
const ensureTraitComponents = () => {
|
||||
if (!TraitComponents) {
|
||||
TraitComponents = Object.values(allTraitComponents()).reduce((r, M) => {
|
||||
const {default: TraitComponent} = M;
|
||||
return {...r, [TraitComponent.type]: TraitComponent};
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
const makeTabSelector = (context, dispatch, target, type) => createSelector(
|
||||
(_) => _,
|
||||
(trait) => {
|
||||
ensureTraitComponents();
|
||||
const {params: paramsRaw, state: stateRaw} = trait;
|
||||
const {[type]: TraitComponent} = TraitComponents;
|
||||
const Trait = lookupTrait(type);
|
||||
const params = Trait ? {...Trait.defaultParams(), ...paramsRaw} : paramsRaw;
|
||||
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
|
||||
const paramsDescription = Trait?.describeParams() || {};
|
||||
const stateDescription = Trait?.describeState() || {};
|
||||
const renderComponents = (traitHalf, description, values) => (
|
||||
// eslint-disable-next-line no-shadow
|
||||
Object.values(mapObject(description, (description, key) => {
|
||||
const {label, options} = description;
|
||||
return (
|
||||
<label key={key}>
|
||||
<span className="text">
|
||||
{label}
|
||||
:
|
||||
</span>
|
||||
<div className="invisible-separator" />
|
||||
<Value.Component
|
||||
context={context}
|
||||
onChange={(value) => dispatch(setTraitProperty({
|
||||
target,
|
||||
trait: type,
|
||||
type: traitHalf,
|
||||
key,
|
||||
value,
|
||||
}))}
|
||||
options={options}
|
||||
value={values[key]}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}))
|
||||
);
|
||||
return TraitComponent
|
||||
? <TraitComponent params={params} state={state} />
|
||||
: (
|
||||
<form className="trait-component-builtin">
|
||||
{renderComponents('params', paramsDescription, params)}
|
||||
{renderComponents('state', stateDescription, state)}
|
||||
</form>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const Traits = (props) => {
|
||||
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);
|
||||
setTraitForms(produce(traitForms, (draft) => {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [type] = entries[i];
|
||||
draft[type] = mts(type);
|
||||
}
|
||||
}));
|
||||
const M = {
|
||||
autoreg$accept: (type, M2) => {
|
||||
if ('trait' === type) {
|
||||
const {default: Trait} = M2;
|
||||
setTraitForms(produce(traitForms, (draft) => {
|
||||
draft[Trait.type()] = mts(Trait.type());
|
||||
}));
|
||||
}
|
||||
},
|
||||
};
|
||||
registerHooks(M, __filename);
|
||||
return () => deregisterHooks(M);
|
||||
}, [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];
|
||||
if (type in traitForms) {
|
||||
tabs.push([type, traitForms[type](trait)]);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="traits">
|
||||
<Tabs
|
||||
domRef={(node) => {
|
||||
listRef = node && node.querySelector('ul');
|
||||
}}
|
||||
forceRenderTabPanel
|
||||
selectedIndex={tabIndex}
|
||||
onSelect={(index) => setTabIndex(index)}
|
||||
>
|
||||
|
@ -157,21 +52,21 @@ const Traits = (props) => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
{tabs.map(([type]) => (
|
||||
{entries.map(([type]) => (
|
||||
<Tab key={type}>
|
||||
<span className="wrapper">
|
||||
<span className="icon" />
|
||||
{type}
|
||||
<span className="close">✕</span>
|
||||
</span>
|
||||
<TraitTab key={type} type={type} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
{tabs.map(([type, content]) => (
|
||||
<TabPanel
|
||||
{entries.map(([type, trait]) => (
|
||||
<TabPanel key={type}>
|
||||
<TraitPane
|
||||
context={context}
|
||||
key={type}
|
||||
>
|
||||
{content}
|
||||
target={target}
|
||||
trait={trait}
|
||||
type={type}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
@extend %tabs;
|
||||
}
|
||||
|
||||
.react-tabs__tab-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.react-tabs__tab-panel--selected {
|
||||
display: block;
|
||||
height: calc(100vh - 2.7em);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user