feat: add/remove traits
This commit is contained in:
parent
a94b63f27f
commit
e20d831d05
|
@ -30,6 +30,8 @@
|
||||||
"@persea/core": "^1.0.0",
|
"@persea/core": "^1.0.0",
|
||||||
"@persea/json": "^1.0.0",
|
"@persea/json": "^1.0.0",
|
||||||
"debug": "4.3.1",
|
"debug": "4.3.1",
|
||||||
|
"lodash.difference": "^4.5.0",
|
||||||
|
"react-autosuggest": "^10.1.0",
|
||||||
"react-tabs": "^3.1.2"
|
"react-tabs": "^3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -5,8 +5,12 @@ import {
|
||||||
PropTypes,
|
PropTypes,
|
||||||
React,
|
React,
|
||||||
useLatus,
|
useLatus,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
} from '@latus/react';
|
} from '@latus/react';
|
||||||
import {JsonResourceController} from '@persea/json';
|
import {Modal} from '@persea/core';
|
||||||
|
import {JsonResourceController, useJsonPatcher} from '@persea/json';
|
||||||
|
import difference from 'lodash.difference';
|
||||||
import {
|
import {
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
@ -14,6 +18,8 @@ import {
|
||||||
TabPanel,
|
TabPanel,
|
||||||
} from 'react-tabs';
|
} from 'react-tabs';
|
||||||
|
|
||||||
|
import Suggest from './suggest';
|
||||||
|
|
||||||
const NoTraitRenderer = ({path, json}) => (
|
const NoTraitRenderer = ({path, json}) => (
|
||||||
<JsonResourceController.Component path={path} resource={json} />
|
<JsonResourceController.Component path={path} resource={json} />
|
||||||
);
|
);
|
||||||
|
@ -27,16 +33,36 @@ const Traits = ({
|
||||||
json,
|
json,
|
||||||
path,
|
path,
|
||||||
}) => {
|
}) => {
|
||||||
|
const patch = useJsonPatcher();
|
||||||
|
const buttonRef = useRef();
|
||||||
const latus = useLatus();
|
const latus = useLatus();
|
||||||
const types = Object.keys(json);
|
const [isSelecting, setIsSelecting] = useState(false);
|
||||||
|
const types = Object.keys(json).sort((l, r) => (l < r ? -1 : 1));
|
||||||
const tabs = types.map((type) => (
|
const tabs = types.map((type) => (
|
||||||
<Tab key={type}>
|
<Tab key={type}>
|
||||||
{type}
|
{type}
|
||||||
|
|
||||||
<button type="button" className="traits__tab-close">✕</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="traits__tab-close"
|
||||||
|
onClick={() => {
|
||||||
|
patch({
|
||||||
|
op: 'remove',
|
||||||
|
path: join(path, type),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
</Tab>
|
</Tab>
|
||||||
));
|
));
|
||||||
const Traits = latus.get('%traits');
|
const Traits = latus.get('%traits');
|
||||||
|
const suggestible = difference(
|
||||||
|
Object.entries(Traits)
|
||||||
|
.filter(([key]) => !Number.isInteger(parseInt(key, 10)))
|
||||||
|
.map(([, {type}]) => type),
|
||||||
|
types,
|
||||||
|
);
|
||||||
const TraitRenderers = latus.get('%trait-renderers');
|
const TraitRenderers = latus.get('%trait-renderers');
|
||||||
const tabPanels = types.map((type) => {
|
const tabPanels = types.map((type) => {
|
||||||
const Trait = Traits[type] || BaseTrait;
|
const Trait = Traits[type] || BaseTrait;
|
||||||
|
@ -60,7 +86,48 @@ const Traits = ({
|
||||||
<div className="traits">
|
<div className="traits">
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<div className="traits__tab-bar">
|
<div className="traits__tab-bar">
|
||||||
<button className="traits__add-trait" type="button">+</button>
|
<button
|
||||||
|
className="traits__add-trait"
|
||||||
|
onClick={() => {
|
||||||
|
if (suggestible.length > 0) {
|
||||||
|
setIsSelecting(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={buttonRef}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
<Modal
|
||||||
|
style={{
|
||||||
|
content: {
|
||||||
|
top: buttonRef.current
|
||||||
|
? `${buttonRef.current.getBoundingClientRect().top}px`
|
||||||
|
: 0,
|
||||||
|
left: buttonRef.current
|
||||||
|
? `${buttonRef.current.getBoundingClientRect().left}px`
|
||||||
|
: 0,
|
||||||
|
bottom: 'auto',
|
||||||
|
padding: '0.5em',
|
||||||
|
right: 'auto',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
isOpen={isSelecting}
|
||||||
|
onRequestClose={() => {
|
||||||
|
setIsSelecting(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Suggest
|
||||||
|
onChange={(event, type) => {
|
||||||
|
setIsSelecting(false);
|
||||||
|
patch({
|
||||||
|
path: join(path, type),
|
||||||
|
value: {},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
types={suggestible}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs}
|
{tabs}
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
PropTypes,
|
||||||
|
React,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from '@latus/react';
|
||||||
|
import Autosuggest from 'react-autosuggest';
|
||||||
|
|
||||||
|
const Suggest = ({
|
||||||
|
onChange,
|
||||||
|
types,
|
||||||
|
}) => {
|
||||||
|
const ref = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.focus();
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
const [suggestions, setSuggestions] = useState([]);
|
||||||
|
const [typed, setTyped] = useState('');
|
||||||
|
const calculateSuggestions = (value) => {
|
||||||
|
const inputValue = value.trim().toLowerCase();
|
||||||
|
return '' === inputValue
|
||||||
|
? types
|
||||||
|
: types
|
||||||
|
.filter((type) => type.toLowerCase().includes(inputValue))
|
||||||
|
.map((type) => ({type}));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="traits-suggestor">
|
||||||
|
<Autosuggest
|
||||||
|
suggestions={suggestions}
|
||||||
|
onSuggestionsFetchRequested={({value}) => {
|
||||||
|
setSuggestions(calculateSuggestions(value));
|
||||||
|
}}
|
||||||
|
onSuggestionsClearRequested={() => {
|
||||||
|
setSuggestions([]);
|
||||||
|
}}
|
||||||
|
getSuggestionValue={({type}) => type}
|
||||||
|
renderSuggestion={({type}) => (
|
||||||
|
<div>{type}</div>
|
||||||
|
)}
|
||||||
|
inputProps={{
|
||||||
|
onChange: (event, {newValue}) => {
|
||||||
|
const {target: {value}} = event;
|
||||||
|
if (!value) {
|
||||||
|
onChange(event, newValue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTyped(event.target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
value: typed,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Suggest.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
types: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Suggest;
|
|
@ -3586,6 +3586,11 @@ es-to-primitive@^1.2.1:
|
||||||
is-date-object "^1.0.1"
|
is-date-object "^1.0.1"
|
||||||
is-symbol "^1.0.2"
|
is-symbol "^1.0.2"
|
||||||
|
|
||||||
|
es6-promise@^4.2.8:
|
||||||
|
version "4.2.8"
|
||||||
|
resolved "http://npm.cha0sdev/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||||
|
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
||||||
|
|
||||||
es6-templates@^0.2.3:
|
es6-templates@^0.2.3:
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "http://npm.cha0sdev/es6-templates/-/es6-templates-0.2.3.tgz#5cb9ac9fb1ded6eb1239342b81d792bbb4078ee4"
|
resolved "http://npm.cha0sdev/es6-templates/-/es6-templates-0.2.3.tgz#5cb9ac9fb1ded6eb1239342b81d792bbb4078ee4"
|
||||||
|
@ -5600,6 +5605,11 @@ lodash.clonedeep@^4.5.0:
|
||||||
resolved "http://npm.cha0sdev/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "http://npm.cha0sdev/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||||
|
|
||||||
|
lodash.difference@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "http://npm.cha0sdev/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
|
||||||
|
integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
|
||||||
|
|
||||||
lodash.flatten@^4.4.0:
|
lodash.flatten@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "http://npm.cha0sdev/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
resolved "http://npm.cha0sdev/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
|
@ -6308,6 +6318,11 @@ oauth-sign@~0.9.0:
|
||||||
resolved "http://npm.cha0sdev/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
resolved "http://npm.cha0sdev/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||||
|
|
||||||
|
object-assign@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "http://npm.cha0sdev/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
|
||||||
|
integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=
|
||||||
|
|
||||||
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "http://npm.cha0sdev/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "http://npm.cha0sdev/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
@ -7114,6 +7129,17 @@ raw-body@2.4.0:
|
||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
react-autosuggest@^10.1.0:
|
||||||
|
version "10.1.0"
|
||||||
|
resolved "http://npm.cha0sdev/react-autosuggest/-/react-autosuggest-10.1.0.tgz#4d25b8acc78bb518eb70189bb96bcd777dc71ffb"
|
||||||
|
integrity sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==
|
||||||
|
dependencies:
|
||||||
|
es6-promise "^4.2.8"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-themeable "^1.1.0"
|
||||||
|
section-iterator "^2.0.0"
|
||||||
|
shallow-equal "^1.2.1"
|
||||||
|
|
||||||
react-dom@^17.0.1:
|
react-dom@^17.0.1:
|
||||||
version "17.0.1"
|
version "17.0.1"
|
||||||
resolved "http://npm.cha0sdev/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
|
resolved "http://npm.cha0sdev/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
|
||||||
|
@ -7188,6 +7214,13 @@ react-tabs@^3.1.2:
|
||||||
clsx "^1.1.0"
|
clsx "^1.1.0"
|
||||||
prop-types "^15.5.0"
|
prop-types "^15.5.0"
|
||||||
|
|
||||||
|
react-themeable@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "http://npm.cha0sdev/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
|
||||||
|
integrity sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=
|
||||||
|
dependencies:
|
||||||
|
object-assign "^3.0.0"
|
||||||
|
|
||||||
react-virtualized-auto-sizer@^1.0.2:
|
react-virtualized-auto-sizer@^1.0.2:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "http://npm.cha0sdev/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz#2b83adb5ecdee8dcb33784e96ee0074eb5369665"
|
resolved "http://npm.cha0sdev/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz#2b83adb5ecdee8dcb33784e96ee0074eb5369665"
|
||||||
|
@ -7652,6 +7685,11 @@ scss-tokenizer@^0.2.3:
|
||||||
js-base64 "^2.1.8"
|
js-base64 "^2.1.8"
|
||||||
source-map "^0.4.2"
|
source-map "^0.4.2"
|
||||||
|
|
||||||
|
section-iterator@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "http://npm.cha0sdev/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
|
||||||
|
integrity sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=
|
||||||
|
|
||||||
select-hose@^2.0.0:
|
select-hose@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "http://npm.cha0sdev/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
resolved "http://npm.cha0sdev/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||||
|
@ -7785,6 +7823,11 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
shallow-equal@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "http://npm.cha0sdev/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
|
||||||
|
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
|
||||||
|
|
||||||
shallowequal@^1.1.0:
|
shallowequal@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "http://npm.cha0sdev/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
resolved "http://npm.cha0sdev/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user