feat: add/remove traits

This commit is contained in:
cha0s 2021-01-27 19:31:45 -06:00
parent a94b63f27f
commit e20d831d05
4 changed files with 183 additions and 4 deletions

View File

@ -30,6 +30,8 @@
"@persea/core": "^1.0.0",
"@persea/json": "^1.0.0",
"debug": "4.3.1",
"lodash.difference": "^4.5.0",
"react-autosuggest": "^10.1.0",
"react-tabs": "^3.1.2"
},
"devDependencies": {

View File

@ -5,8 +5,12 @@ import {
PropTypes,
React,
useLatus,
useRef,
useState,
} 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 {
Tab,
Tabs,
@ -14,6 +18,8 @@ import {
TabPanel,
} from 'react-tabs';
import Suggest from './suggest';
const NoTraitRenderer = ({path, json}) => (
<JsonResourceController.Component path={path} resource={json} />
);
@ -27,16 +33,36 @@ const Traits = ({
json,
path,
}) => {
const patch = useJsonPatcher();
const buttonRef = useRef();
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) => (
<Tab key={type}>
{type}
&nbsp;
<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>
));
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 tabPanels = types.map((type) => {
const Trait = Traits[type] || BaseTrait;
@ -60,7 +86,48 @@ const Traits = ({
<div className="traits">
<Tabs>
<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>
{tabs}
</TabList>

View File

@ -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;

View File

@ -3586,6 +3586,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
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:
version "0.2.3"
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"
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:
version "4.4.0"
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"
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:
version "4.1.1"
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"
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:
version "17.0.1"
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"
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:
version "1.0.4"
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"
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:
version "2.0.0"
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"
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:
version "1.1.0"
resolved "http://npm.cha0sdev/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"