refactor: proper memo
This commit is contained in:
parent
622f32015f
commit
597de89826
|
@ -19,7 +19,7 @@ const EntityComponent = (props) => {
|
||||||
if (!json) {
|
if (!json) {
|
||||||
dispatch(fetchEntity(uri));
|
dispatch(fetchEntity(uri));
|
||||||
}
|
}
|
||||||
}, [json, uri]);
|
}, [dispatch, json, uri]);
|
||||||
if (!json) {
|
if (!json) {
|
||||||
return null;
|
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 {compose} from '@avocado/core';
|
||||||
import {all as allTraitComponents} from '@avocado/entity/trait/trait-components.scwp';
|
|
||||||
import {lookupTrait} from '@avocado/entity';
|
|
||||||
import contempo from 'contempo';
|
import contempo from 'contempo';
|
||||||
import {produce} from 'immer';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
// import ReactMarkdown from 'react-markdown';
|
|
||||||
import {useDispatch} from 'react-redux';
|
|
||||||
import {
|
import {
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
} from 'react-tabs';
|
} from 'react-tabs';
|
||||||
import {createSelector} from '@reduxjs/toolkit';
|
|
||||||
import {deregisterHooks, registerHooks} from 'scwp';
|
|
||||||
|
|
||||||
import Value from '~/client/value';
|
import TraitTab from './trait-tab';
|
||||||
|
import TraitPane from './trait-pane';
|
||||||
import {setTraitProperty} from './state';
|
|
||||||
|
|
||||||
const SCROLL_MAG = 80;
|
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 Traits = (props) => {
|
||||||
const {context, target, 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);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
let listRef;
|
let listRef;
|
||||||
const entries = Object.entries(traits);
|
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 (
|
return (
|
||||||
<div className="traits">
|
<div className="traits">
|
||||||
<Tabs
|
<Tabs
|
||||||
domRef={(node) => {
|
domRef={(node) => {
|
||||||
listRef = node && node.querySelector('ul');
|
listRef = node && node.querySelector('ul');
|
||||||
}}
|
}}
|
||||||
|
forceRenderTabPanel
|
||||||
selectedIndex={tabIndex}
|
selectedIndex={tabIndex}
|
||||||
onSelect={(index) => setTabIndex(index)}
|
onSelect={(index) => setTabIndex(index)}
|
||||||
>
|
>
|
||||||
|
@ -157,21 +52,21 @@ const Traits = (props) => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tabs.map(([type]) => (
|
{entries.map(([type]) => (
|
||||||
<Tab key={type}>
|
<Tab key={type}>
|
||||||
<span className="wrapper">
|
<TraitTab key={type} type={type} />
|
||||||
<span className="icon" />
|
|
||||||
{type}
|
|
||||||
<span className="close">✕</span>
|
|
||||||
</span>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
{tabs.map(([type, content]) => (
|
{entries.map(([type, trait]) => (
|
||||||
<TabPanel
|
<TabPanel key={type}>
|
||||||
key={type}
|
<TraitPane
|
||||||
>
|
context={context}
|
||||||
{content}
|
key={type}
|
||||||
|
target={target}
|
||||||
|
trait={trait}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
@extend %tabs;
|
@extend %tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.react-tabs__tab-panel--selected {
|
.react-tabs__tab-panel--selected {
|
||||||
|
display: block;
|
||||||
height: calc(100vh - 2.7em);
|
height: calc(100vh - 2.7em);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user