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"