diff --git a/packages/entity/package.json b/packages/entity/package.json index 20a4ad5..ba12d81 100644 --- a/packages/entity/package.json +++ b/packages/entity/package.json @@ -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": { diff --git a/packages/entity/src/resource-controllers/entity/traits/index.jsx b/packages/entity/src/resource-controllers/entity/traits/index.jsx index d043f48..09f8a1f 100644 --- a/packages/entity/src/resource-controllers/entity/traits/index.jsx +++ b/packages/entity/src/resource-controllers/entity/traits/index.jsx @@ -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}) => ( ); @@ -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) => ( {type}   - + )); 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 = ({
- + + { + setIsSelecting(false); + }} + > + { + setIsSelecting(false); + patch({ + path: join(path, type), + value: {}, + }); + }} + types={suggestible} + /> + {tabs} diff --git a/packages/entity/src/resource-controllers/entity/traits/suggest.jsx b/packages/entity/src/resource-controllers/entity/traits/suggest.jsx new file mode 100644 index 0000000..683c717 --- /dev/null +++ b/packages/entity/src/resource-controllers/entity/traits/suggest.jsx @@ -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 ( +
+ { + setSuggestions(calculateSuggestions(value)); + }} + onSuggestionsClearRequested={() => { + setSuggestions([]); + }} + getSuggestionValue={({type}) => type} + renderSuggestion={({type}) => ( +
{type}
+ )} + inputProps={{ + onChange: (event, {newValue}) => { + const {target: {value}} = event; + if (!value) { + onChange(event, newValue); + } + else { + setTyped(event.target.value); + } + }, + ref, + value: typed, + }} + /> +
+ ); +}; + +Suggest.propTypes = { + onChange: PropTypes.func.isRequired, + types: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + +export default Suggest; diff --git a/packages/entity/yarn.lock b/packages/entity/yarn.lock index e23282f..1b33121 100644 --- a/packages/entity/yarn.lock +++ b/packages/entity/yarn.lock @@ -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"