From 4e449327d4a1f1355788a2a4e9b4e1f3cc1b897a Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Tue, 27 Sep 2022 11:25:50 -0700 Subject: [PATCH 01/18] prototype - seq number in messages --- adatest/_test_tree_browser.py | 77 +++-- client/package-lock.json | 321 ------------------ client/src/browser.jsx | 38 +-- ...tent-editable.jsx => content-editable.tsx} | 22 +- client/src/row.jsx | 3 +- client/src/web-socket-comm.js | 64 +++- 6 files changed, 140 insertions(+), 385 deletions(-) rename client/src/{content-editable.jsx => content-editable.tsx} (92%) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index a1bed78..cebf71a 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -289,10 +289,14 @@ def interface_event(self, msg): log.error("interface_event: missing event_id. msg dump: %s", msg) return event_id = msg["event_id"] + sequence_number = msg.get("sequence_number") # redraw the entire interface if event_id == "redraw": - self._refresh_interface() + self._refresh_interface(sequence_number) + + elif event_id == "refresh_tests": + self._refresh_tests(msg["test_ids"], sequence_number) # generate a new set of suggested tests/topics elif event_id == "generate_suggestions": @@ -322,7 +326,7 @@ def interface_event(self, msg): # log.debug(e) # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) # self._suggestions_error = True - self._refresh_interface() + # self._refresh_interface() # change the current topic elif event_id == "change_topic": @@ -337,13 +341,13 @@ def interface_event(self, msg): else: self.mode = "tests" - self._refresh_interface() + # self._refresh_interface() # clear the current set of suggestions elif event_id == "clear_suggestions": self._clear_suggestions() # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) - self._refresh_interface() + # self._refresh_interface() # add a new empty subtopic to the current topic elif event_id == "add_new_topic": @@ -357,7 +361,7 @@ def interface_event(self, msg): } self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface() + # self._refresh_interface() # add a new empty test to the current topic elif event_id == "add_new_test": @@ -377,7 +381,7 @@ def interface_event(self, msg): self.test_tree.loc[uuid.uuid4().hex] = row self._auto_save() - self._refresh_interface() + # self._refresh_interface() # change which scorer/model is used for sorting tests elif event_id == "set_first_model": @@ -394,7 +398,7 @@ def interface_event(self, msg): self.score_columns.insert(0, name) self._auto_save() - self._refresh_interface() + # self._refresh_interface() elif event_id == "change_generator": self.active_generator = msg["generator"] @@ -412,12 +416,12 @@ def interface_event(self, msg): self.test_tree.loc[id, 'label'] = "topic_marker" self.test_tree.loc[msg['topic_marker_id'], 'description'] = msg['description'] self._auto_save() - self._refresh_interface() + # self._refresh_interface() elif event_id == 'change_filter': print("change_filter") self.filter_text = msg['filter_text'] - self._refresh_interface() + # self._refresh_interface() # Move a test/topic to a new topic # Also used to rename @@ -436,7 +440,7 @@ def interface_event(self, msg): # Recompute any missing embeddings to handle any changes self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface() + # self._refresh_interface() elif event_id == "delete_test": log.debug("delete_test") @@ -452,18 +456,18 @@ def interface_event(self, msg): self.test_tree.drop(id, inplace=True) self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface() + # self._refresh_interface() # if we are just updating a single row in tests then we only recompute the scores elif event_id == "change_label" or event_id == "change_input" or event_id == "change_output": - sendback_data = {} + # sendback_data = {} test_id = msg["test_ids"][0] # convert template expansions into a standard value update - if msg.get("action", "") == "template_expand": - template_value = self.templatize(self.test_tree.loc[test_id, msg["value"]]) - msg = {msg["value"]: template_value} - sendback_data[msg["value"]] = template_value + # if msg.get("action", "") == "template_expand": + # template_value = self.templatize(self.test_tree.loc[test_id, msg["value"]]) + # msg = {msg["value"]: template_value} + # sendback_data[msg["value"]] = template_value # update the row and recompute scores metadata_fields = ["event_id", "test_ids"] @@ -480,22 +484,40 @@ def interface_event(self, msg): # self._compute_embeddings_and_scores(self.test_tree, overwrite_outputs=False) # send just the data that changed back to the frontend + # sendback_data["scores"] = {c: [[test_id, v] for v in ui_score_parts(self.test_tree.loc[test_id, c], self.test_tree.loc[test_id, "label"])] for c in self.score_columns} + # outputs = {c: [[test_id, json.loads(self.test_tree.loc[test_id].get(c[:-6] + " raw outputs", "{}"))]] for c in self.score_columns} + # sendback_data["raw_outputs"] = outputs + # if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back + # sendback_data["output"] = self.test_tree.loc[test_id, "output"] + # sendback_data["label"] = self.test_tree.loc[test_id, "label"] + # sendback_data["labeler"] = self.test_tree.loc[test_id, "labeler"] + # sendback_data.update(self.test_display_parts(self.test_tree.loc[test_id])) + # self.comm.send({test_id: sendback_data}) + # refresh_data = self._refresh_tests(test_id)[0] + # sendback_data.update(refresh_data) + # comm.send() + + self._auto_save() + + else: + log.error(f"Unable to parse the interface message: {msg}") + + def _refresh_tests(self, test_ids, sequence_number): + test_data_list = [] + for test_id in test_ids: + sendback_data = {} sendback_data["scores"] = {c: [[test_id, v] for v in ui_score_parts(self.test_tree.loc[test_id, c], self.test_tree.loc[test_id, "label"])] for c in self.score_columns} outputs = {c: [[test_id, json.loads(self.test_tree.loc[test_id].get(c[:-6] + " raw outputs", "{}"))]] for c in self.score_columns} sendback_data["raw_outputs"] = outputs - if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back - sendback_data["output"] = self.test_tree.loc[test_id, "output"] + # if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back + # sendback_data["output"] = self.test_tree.loc[test_id, "output"] sendback_data["label"] = self.test_tree.loc[test_id, "label"] sendback_data["labeler"] = self.test_tree.loc[test_id, "labeler"] sendback_data.update(self.test_display_parts(self.test_tree.loc[test_id])) - self.comm.send({test_id: sendback_data}) - - self._auto_save() - - else: - log.error(f"Unable to parse the interface message: {msg}") + test_data_list.append({test_id: sendback_data}) + self.send_response(test_data_list, sequence_number) - def _refresh_interface(self): + def _refresh_interface(self, sequence_number): """ Send our entire current state to the frontend interface. """ @@ -627,7 +649,10 @@ def sort_key(id): # "test_type_parts": test_type_parts, } - self.comm.send(data) + self.send_response(data, sequence_number) + + def send_response(self, data, sequence_number): + self.comm.send({"sequence_number": sequence_number, "data": data, "status": "ok"}) def _clear_suggestions(self): """ Clear the suggestions for the current topic. diff --git a/client/package-lock.json b/client/package-lock.json index df5a98f..5fb6fa3 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -22,7 +22,6 @@ "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", - "react-sortable-tree": "^2.8.0", "sanitize-html": "^2.3.3" }, "devDependencies": { @@ -1937,21 +1936,6 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, - "node_modules/@react-dnd/asap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz", - "integrity": "sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==" - }, - "node_modules/@react-dnd/invariant": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", - "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==" - }, - "node_modules/@react-dnd/shallowequal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", - "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" - }, "node_modules/@types/eslint": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", @@ -3082,16 +3066,6 @@ "node": ">=0.10.0" } }, - "node_modules/dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "dependencies": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -3385,59 +3359,6 @@ "node": ">=8" } }, - "node_modules/frontend-collective-react-dnd-scrollzone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.2.tgz", - "integrity": "sha512-me/D9PZJq9j/sjEjs/OPmm6V6nbaHbhgeQiwrWu0t35lhwAOKWc+QBzzKKcZQeboYTkgE8UvCD9el+5ANp+g5Q==", - "dependencies": { - "hoist-non-react-statics": "^3.1.0", - "lodash.throttle": "^4.0.1", - "prop-types": "^15.5.9", - "raf": "^3.2.0", - "react": "^16.3.0", - "react-display-name": "^0.2.0", - "react-dom": "^16.3.0" - }, - "peerDependencies": { - "react-dnd": "^7.3.0" - } - }, - "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3970,16 +3891,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4305,11 +4216,6 @@ "isarray": "0.0.1" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4464,14 +4370,6 @@ "node": ">=6" } }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -4516,34 +4414,6 @@ "react-dom": "^16.8.5 || ^17.0.0" } }, - "node_modules/react-display-name": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", - "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" - }, - "node_modules/react-dnd": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", - "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==", - "dependencies": { - "@react-dnd/shallowequal": "^2.0.0", - "@types/hoist-non-react-statics": "^3.3.1", - "dnd-core": "^11.1.3", - "hoist-non-react-statics": "^3.3.0" - }, - "peerDependencies": { - "react": ">= 16.9.0", - "react-dom": ">= 16.9.0" - } - }, - "node_modules/react-dnd-html5-backend": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz", - "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", - "dependencies": { - "dnd-core": "^11.1.3" - } - }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -4562,11 +4432,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "node_modules/react-redux": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", @@ -4628,25 +4493,6 @@ "react": ">=15" } }, - "node_modules/react-sortable-tree": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.8.0.tgz", - "integrity": "sha512-gTjwxRNt7z0FC76KeNTnGqx1qUSlV3N78mMPRushBpSUXzZYhiFNsWHUIruyPnaAbw4SA7LgpItV7VieAuwDpw==", - "dependencies": { - "frontend-collective-react-dnd-scrollzone": "^1.0.2", - "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.1", - "react-dnd": "^11.1.3", - "react-dnd-html5-backend": "^11.1.3", - "react-lifecycles-compat": "^3.0.4", - "react-virtualized": "^9.21.2" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dnd": "^7.3.0", - "react-dom": "^16.3.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -4662,23 +4508,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-virtualized": { - "version": "9.22.3", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz", - "integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "clsx": "^1.0.4", - "dom-helpers": "^5.1.3", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha", - "react-dom": "^15.3.0 || ^16.0.0-alpha" - } - }, "node_modules/rechoir": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", @@ -7035,21 +6864,6 @@ "react-is": "^16.8.0 || ^17.0.0" } }, - "@react-dnd/asap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz", - "integrity": "sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==" - }, - "@react-dnd/invariant": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", - "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==" - }, - "@react-dnd/shallowequal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", - "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" - }, "@types/eslint": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", @@ -8011,16 +7825,6 @@ "repeating": "^2.0.0" } }, - "dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "requires": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - }, "dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -8240,52 +8044,6 @@ "path-exists": "^4.0.0" } }, - "frontend-collective-react-dnd-scrollzone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.2.tgz", - "integrity": "sha512-me/D9PZJq9j/sjEjs/OPmm6V6nbaHbhgeQiwrWu0t35lhwAOKWc+QBzzKKcZQeboYTkgE8UvCD9el+5ANp+g5Q==", - "requires": { - "hoist-non-react-statics": "^3.1.0", - "lodash.throttle": "^4.0.1", - "prop-types": "^15.5.9", - "raf": "^3.2.0", - "react": "^16.3.0", - "react-display-name": "^0.2.0", - "react-dom": "^16.3.0" - }, - "dependencies": { - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -8707,16 +8465,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" - }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8956,11 +8704,6 @@ "isarray": "0.0.1" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -9067,14 +8810,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, "raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -9112,30 +8847,6 @@ "use-memo-one": "^1.1.1" } }, - "react-display-name": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", - "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" - }, - "react-dnd": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", - "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==", - "requires": { - "@react-dnd/shallowequal": "^2.0.0", - "@types/hoist-non-react-statics": "^3.3.1", - "dnd-core": "^11.1.3", - "hoist-non-react-statics": "^3.3.0" - } - }, - "react-dnd-html5-backend": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz", - "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", - "requires": { - "dnd-core": "^11.1.3" - } - }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -9151,11 +8862,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "react-redux": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", @@ -9200,20 +8906,6 @@ "tiny-warning": "^1.0.0" } }, - "react-sortable-tree": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.8.0.tgz", - "integrity": "sha512-gTjwxRNt7z0FC76KeNTnGqx1qUSlV3N78mMPRushBpSUXzZYhiFNsWHUIruyPnaAbw4SA7LgpItV7VieAuwDpw==", - "requires": { - "frontend-collective-react-dnd-scrollzone": "^1.0.2", - "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.1", - "react-dnd": "^11.1.3", - "react-dnd-html5-backend": "^11.1.3", - "react-lifecycles-compat": "^3.0.4", - "react-virtualized": "^9.21.2" - } - }, "react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -9225,19 +8917,6 @@ "prop-types": "^15.6.2" } }, - "react-virtualized": { - "version": "9.22.3", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz", - "integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==", - "requires": { - "@babel/runtime": "^7.7.2", - "clsx": "^1.0.4", - "dom-helpers": "^5.1.3", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-lifecycles-compat": "^3.0.4" - } - }, "rechoir": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", diff --git a/client/src/browser.jsx b/client/src/browser.jsx index 787e30f..0685af6 100644 --- a/client/src/browser.jsx +++ b/client/src/browser.jsx @@ -525,19 +525,19 @@ export default class Browser extends React.Component { this.setState(data); // TODO: this is just a hack for the Checklist baseline user study - defer(() => { - //console.log("this.state.suggestions", this.state.suggestions, this.suggestionsTemplateRow.state.value2); - // fill in the value of the template output if it is blank and we have a guess - if (this.state.suggestions && this.suggestionsTemplateRow && (this.suggestionsTemplateRow.state.value2 === null || this.suggestionsTemplateRow.state.value2 === "")) { + // defer(() => { + // //console.log("this.state.suggestions", this.state.suggestions, this.suggestionsTemplateRow.state.value2); + // // fill in the value of the template output if it is blank and we have a guess + // if (this.state.suggestions && this.suggestionsTemplateRow && (this.suggestionsTemplateRow.state.value2 === null || this.suggestionsTemplateRow.state.value2 === "")) { - const key = this.state.suggestions[this.state.suggestions.length - 1]; - //console.log("key", key, Object.keys(this.comm.data)) - if (key in this.comm.data) { - //console.log("DDFSSS") - this.suggestionsTemplateRow.setState({value2: this.comm.data[key].value2, comparator: this.comm.data[key].comparator}); - } - } - }); + // const key = this.state.suggestions[this.state.suggestions.length - 1]; + // //console.log("key", key, Object.keys(this.comm.data)) + // if (key in this.comm.data) { + // //console.log("DDFSSS") + // this.suggestionsTemplateRow.setState({value2: this.comm.data[key].value2, comparator: this.comm.data[key].comparator}); + // } + // } + // }); } clockFinished() { @@ -755,10 +755,10 @@ export default class Browser extends React.Component { this.comm.sendEvent(generateSuggestions({ value2_filter: this.state.value2Filter, value1_filter: this.state.value1Filter, comparator_filter: this.state.comparatorFilter, - suggestions_template_value1: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value1, - suggestions_template_comparator: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.comparator, - suggestions_template_value2: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value2, - checklist_mode: !!this.suggestionsTemplateRow + // suggestions_template_value1: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value1, + // suggestions_template_comparator: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.comparator, + // suggestions_template_value2: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value2, + checklist_mode: false //!!this.suggestionsTemplateRow })) } @@ -901,9 +901,9 @@ export default class Browser extends React.Component { goToTopic(topic) { console.log("goToTopic", topic); - if (this.suggestionsTemplateRow) { - this.suggestionsTemplateRow.setState({value2: null}); - } + // if (this.suggestionsTemplateRow) { + // this.suggestionsTemplateRow.setState({value2: null}); + // } this.comm.sendEvent(changeTopic(stripSlash(topic).replaceAll(" ", "%20"))) } diff --git a/client/src/content-editable.jsx b/client/src/content-editable.tsx similarity index 92% rename from client/src/content-editable.jsx rename to client/src/content-editable.tsx index 7a3baf0..7aca15f 100644 --- a/client/src/content-editable.jsx +++ b/client/src/content-editable.tsx @@ -1,16 +1,34 @@ +// @ts-nocheck + import React from 'react'; import autoBind from 'auto-bind'; import sanitizeHtml from 'sanitize-html'; import { defer } from 'lodash'; -export default class ContentEditable extends React.Component { +interface ContentEditableProps { + id: string; + text: string; // current text value + defaultText: string; // text to show when empty + editable: boolean; // if true, allow editing + finishOnReturn: boolean; // if true, call onFinish when return is pressed + onInput: Function; // called when text is changed + onFinish: Function; // called when editing is finished + onClick: Function; // called when the element is clicked +} + +export default class ContentEditable extends React.Component { + lastText; + divRef; + lastEditable; + skipBlurAction; + static defaultProps = { editable: true, defaultText: "", finishOnReturn: false }; - constructor(props) { + constructor(props: ContentEditableProps) { super(props); autoBind(this); this.lastText = null; diff --git a/client/src/row.jsx b/client/src/row.jsx index 5443de1..323a8c9 100644 --- a/client/src/row.jsx +++ b/client/src/row.jsx @@ -14,7 +14,6 @@ export default class Row extends React.Component { this.state = { editing: false, - topic: null, input: null, output: null, label: null, @@ -30,7 +29,7 @@ export default class Row extends React.Component { this.dataLoadActions = []; - this.props.comm.subscribe(this.props.id, this.dataLoaded); + // this.props.comm.subscribe(this.props.id, this.dataLoaded); window["row_"+this.props.id] = this; window.faTimes = faTimes; diff --git a/client/src/web-socket-comm.js b/client/src/web-socket-comm.js index 76e002f..4e665ac 100644 --- a/client/src/web-socket-comm.js +++ b/client/src/web-socket-comm.js @@ -8,10 +8,12 @@ export default class WebSocketComm { this.interfaceId = interfaceId; this.websocketServer = websocketServer; this.callbackMap = {}; - this.data = {}; + // this.data = {}; this.pendingData = {}; + this.pendingResponses = {}; this.onopen = onopen; this.reconnectDelay = 100; + this.seqNumber = 0; this.debouncedSendPendingData500 = debounce(this.sendPendingData, 500); this.debouncedSendPendingData1000 = debounce(this.sendPendingData, 1000); @@ -54,7 +56,7 @@ export default class WebSocketComm { for (const i in keys) { const k = keys[i]; this.pendingData[k] = data; - this.data[k] = Object.assign(this.data[k] || {}, data); // pretend it has already changed in our data cache + // this.data[k] = Object.assign(this.data[k] || {}, data); // pretend it has already changed in our data cache } } @@ -62,24 +64,30 @@ export default class WebSocketComm { let wsUri = (window.location.protocol=='https:' ? 'wss://' : 'ws://') + (this.websocketServer.startsWith("/") ? window.location.host : "") + this.websocketServer; this.wcomm = new WebSocket(wsUri); this.wcomm.onopen = this.onopen; - this.wcomm.onmessage = this.updateData; + this.wcomm.onmessage = this.handleResponse; this.wcomm.onerror = this.onError; this.wcomm.onclose = this.onClose; } - updateData(e) { - // console.log("updateData", e) + handleResponse(e) { let data = JSON5.parse(e.data); - console.log("updateData", data) - for (const k in data) { - // console.log("data[k]", data[k]) - this.data[k] = Object.assign(this.data[k] || {}, data[k]); - if (k in this.callbackMap) { - this.callbackMap[k](data[k]); - } + const keys = Object.keys(data); + if (keys.includes("sequence_number")) { + console.log(`received message#${data.sequence_number}`, data); } } + // updateData(e) { + // console.log("WEBSOCKET UPDATEDATA, received unexpected data", data) + // for (const k in data) { + // // console.log("data[k]", data[k]) + // this.data[k] = Object.assign(this.data[k] || {}, data[k]); + // if (k in this.callbackMap) { + // this.callbackMap[k](data[k]); + // } + // } + // } + onError(e) { console.log("Websocket error", e); } @@ -91,13 +99,39 @@ export default class WebSocketComm { } subscribe(key, callback) { - this.callbackMap[key] = callback; - defer(_ => this.callbackMap[key](this.data[key])); + console.log("WEBSOCKET SUBSCRIBE", key, callback); + // this.callbackMap[key] = callback; + // defer(_ => this.callbackMap[key](this.data[key])); + } + + getSeqNumber() { + return this.seqNumber++; } sendPendingData() { - console.log("sending", this.pendingData); + const seqNumber = this.getSeqNumber(); + this.pendingData["sequence_number"] = seqNumber; + console.log(`sending message#${seqNumber}`, this.pendingData); this.wcomm.send(JSON.stringify(this.pendingData)); this.pendingData = {}; + return this.waitForResponse(seqNumber); } + + waitForResponse(seqNumber) { + const timeout_ms = 30000; + this.pendingResponses[seqNumber] = "pending"; + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(`timeout waiting for response to message#${seqNumber}`); + }, timeout_ms); + const interval = setInterval(() => { + if (this.pendingResponses[seqNumber] !== "pending") { + clearTimeout(timeout); + clearInterval(interval); + resolve(this.pendingResponses[seqNumber]); + } + }, 10); + }); + } + } \ No newline at end of file From e83229b2e0c05130476d5b5f2011b2fccb4674dc Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Mon, 17 Oct 2022 22:38:34 -0700 Subject: [PATCH 02/18] Add types to Browser component --- client/src/{browser.jsx => browser.tsx} | 75 ++++++++++++++++++++++--- client/src/content-editable.tsx | 8 +-- 2 files changed, 70 insertions(+), 13 deletions(-) rename client/src/{browser.jsx => browser.tsx} (95%) diff --git a/client/src/browser.jsx b/client/src/browser.tsx similarity index 95% rename from client/src/browser.jsx rename to client/src/browser.tsx index 0685af6..5924c31 100644 --- a/client/src/browser.jsx +++ b/client/src/browser.tsx @@ -11,7 +11,64 @@ import BreadCrum from './bread-crum'; import TotalValue from './total-value'; import ContentEditable from './content-editable'; -export default class Browser extends React.Component { + +interface BrowserProps { + location: any; + interfaceId: any; + environment: string; + websocket_server: string; + history: any; + prefix: string; + startingTopic: string; + checklistMode: boolean; +} + +interface BrowserState { + topic: string; + suggestions: any[]; + tests: any[]; + selections: any; + user: string; + loading_suggestions: boolean; + max_suggestions: number; + suggestions_pos: number; + suggestionsDropHighlighted: number; + score_filter: number; + do_score_filter: boolean; + filter_text: string; + experiment_pos: number; + timerExpired: boolean; + experiment_locations: any[]; + experiment: boolean; + value2Filter: string; + test_types?: any[]; + test_type_parts?: any[]; + score_columns?: any[]; + test_tree_name?: any; + topic_description?: string; + read_only?: boolean; + topicFilter?: string; + value1Filter?: string; + comparatorFilter?: string; + disable_suggestions?: boolean; + mode_options?: any[]; + generator_options?: any[]; + active_generator?: string; + mode?: string; + suggestions_error?: string; + topic_marker_id?: string; +} + + +export default class Browser extends React.Component { + id: string; + rows: any; + comm: JupyterComm | WebSocketComm; + totalPassesObjects: any; + totalFailuresObjects: any; + divRef: HTMLDivElement; + suggestionsScrollWrapRef: HTMLDivElement; + constructor(props) { super(props); autoBind(this); @@ -56,7 +113,7 @@ export default class Browser extends React.Component { this.debouncedForceUpdate = debounce(this.debouncedForceUpdate, 100); - window.pair_chart = this; + // window.pair_chart = this; } debouncedForceUpdate() { @@ -154,7 +211,7 @@ export default class Browser extends React.Component { // let totalPasses = this.totalPassesObj = el} />; // let totalFailures = this.totalFailuresObj = el} />; - return (
this.divRef = el}> + return (
this.divRef = el}>
{/*  New Test */} @@ -172,7 +229,7 @@ export default class Browser extends React.Component {
-
+
{/* {this.state.score_columns && this.state.score_columns.slice().reverse().map(k => { return
{k != "model score" &&
this.clickModel(k, e)}>{k.replace(" score", "")}
} @@ -197,7 +254,7 @@ export default class Browser extends React.Component {
-
+
{!this.state.read_only &&
@@ -301,7 +358,7 @@ export default class Browser extends React.Component { Output
- Off-topic + Off-topic
Pass @@ -380,12 +437,12 @@ export default class Browser extends React.Component {
{this.state.score_columns && this.state.score_columns.map(k => { return -
+

{totalPasses[k]} {/* {this.state.tests.reduce((total, value) => total + this.rows[value].totalPasses["score"], 0)} */}
-
+

{totalFailures[k]}
@@ -412,7 +469,7 @@ export default class Browser extends React.Component { clickModel(modelName, e) { if (modelName !== this.state.score_columns[0]) { - this.sendEvent(setFirstModel(modelName)); + this.comm.sendEvent(setFirstModel(modelName)); } } diff --git a/client/src/content-editable.tsx b/client/src/content-editable.tsx index 7aca15f..b2ccca1 100644 --- a/client/src/content-editable.tsx +++ b/client/src/content-editable.tsx @@ -6,14 +6,14 @@ import sanitizeHtml from 'sanitize-html'; import { defer } from 'lodash'; interface ContentEditableProps { - id: string; + id?: string; text: string; // current text value defaultText: string; // text to show when empty editable: boolean; // if true, allow editing finishOnReturn: boolean; // if true, call onFinish when return is pressed - onInput: Function; // called when text is changed - onFinish: Function; // called when editing is finished - onClick: Function; // called when the element is clicked + onInput?: Function; // called when text is changed + onFinish?: Function; // called when editing is finished + onClick?: Function; // called when the element is clicked } export default class ContentEditable extends React.Component { From 1f1c864e56cb685421f9546043a4ab47c36da03f Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Wed, 19 Oct 2022 16:14:09 -0700 Subject: [PATCH 03/18] Add typescript to all frontend components --- client/package.json | 2 +- client/src/{adatest.jsx => adatest.tsx} | 18 ++- client/src/{bread-crum.jsx => bread-crum.tsx} | 16 ++- client/src/browser.tsx | 39 +++---- client/src/content-editable.tsx | 14 ++- .../{context-menu.jsx => context-menu.tsx} | 13 ++- client/src/{index.jsx => index.tsx} | 8 +- .../src/{jupyter-comm.js => jupyter-comm.ts} | 13 +++ client/src/{row.jsx => row.tsx} | 103 +++++++++++++++--- client/src/total-value.jsx | 41 ------- client/src/total-value.tsx | 48 ++++++++ client/src/types/index.d.ts | 13 +++ ...{web-socket-comm.js => web-socket-comm.ts} | 54 +++++---- client/webpack.config.js | 2 +- 14 files changed, 279 insertions(+), 105 deletions(-) rename client/src/{adatest.jsx => adatest.tsx} (77%) rename client/src/{bread-crum.jsx => bread-crum.tsx} (84%) rename client/src/{context-menu.jsx => context-menu.tsx} (81%) rename client/src/{index.jsx => index.tsx} (58%) rename client/src/{jupyter-comm.js => jupyter-comm.ts} (92%) rename client/src/{row.jsx => row.tsx} (93%) delete mode 100644 client/src/total-value.jsx create mode 100644 client/src/total-value.tsx create mode 100644 client/src/types/index.d.ts rename client/src/{web-socket-comm.js => web-socket-comm.ts} (72%) diff --git a/client/package.json b/client/package.json index f1e14ac..995ca1d 100644 --- a/client/package.json +++ b/client/package.json @@ -2,7 +2,7 @@ "name": "client", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "index.tsx", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "NODE_ENV=production webpack", diff --git a/client/src/adatest.jsx b/client/src/adatest.tsx similarity index 77% rename from client/src/adatest.jsx rename to client/src/adatest.tsx index 83e296e..7efa62b 100644 --- a/client/src/adatest.jsx +++ b/client/src/adatest.tsx @@ -9,7 +9,20 @@ import Browser from './browser' const BrowserWithRouter = withRouter(Browser); -export default class AdaTest extends React.Component { + +interface AdaTestProps { + interfaceId: string; + environment: string; + websocket_server: string; + startingTopic: string; + prefix: string; +} + +interface AdaTestState { + enabled: boolean; +} + +export default class AdaTest extends React.Component { constructor(props) { super(props); @@ -17,13 +30,14 @@ export default class AdaTest extends React.Component { this.state = { enabled: true }; window.adatest_root = this; } - render() { + render() { const Router = this.props.environment === "web" ? BrowserRouter : MemoryRouter; return (
+ { /* @ts-ignore: JSX element type 'Router' does not have any construct or call signatures */ } void; + topic: string; + onDrop: (testId: string, topic: string) => void; +} + +interface BreadCrumState { + dropHighlighted: number; +} + + +export default class BreadCrum extends React.Component { constructor(props) { super(props); autoBind(this); diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 5924c31..a27dfc4 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -64,8 +64,8 @@ export default class Browser extends React.Component id: string; rows: any; comm: JupyterComm | WebSocketComm; - totalPassesObjects: any; - totalFailuresObjects: any; + totalPassesObjects: {}; + totalFailuresObjects: {}; divRef: HTMLDivElement; suggestionsScrollWrapRef: HTMLDivElement; @@ -113,7 +113,8 @@ export default class Browser extends React.Component this.debouncedForceUpdate = debounce(this.debouncedForceUpdate, 100); - // window.pair_chart = this; + // @ts-ignore: Property does not exist on type 'Window' + window.pair_chart = this; } debouncedForceUpdate() { @@ -272,26 +273,26 @@ export default class Browser extends React.Component ref={(el) => this.rows[id] = el} topic={this.state.topic} isSuggestion={true} - topicFilter={this.state.topicFilter} + // topicFilter={this.state.topicFilter} value1Filter={this.state.value1Filter} comparatorFilter={this.state.comparatorFilter} value2Filter={this.state.value2Filter} - value1Default="New value" - value2Default="New value" - value2Edited={this.value2Edited} + // value1Default="New value" + // value2Default="New value" + // value2Edited={this.value2Edited} selected={this.state.selections[id]} soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onSelectToggle={this.toggleSelection} comm={this.comm} scoreFilter={this.state.do_score_filter && this.state.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 && this.state.score_filter} - selectWidth={maxSelectWidth} + // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} - inFillin={inFillin} + // inFillin={inFillin} scrollParent={this.suggestionsScrollWrapRef} giveUpSelection={this.removeSelection} scoreColumns={this.state.score_columns} - test_types={this.state.test_types} - test_type_parts={this.state.test_type_parts} + // test_types={this.state.test_types} + // test_type_parts={this.state.test_type_parts} user={this.state.user} outputColumnWidth={outputColumnWidth} /> @@ -379,11 +380,11 @@ export default class Browser extends React.Component ref={(el) => this.rows[id] = el} topic={this.state.topic} hideBorder={index == 0} - topicFilter={this.state.topicFilter} + // topicFilter={this.state.topicFilter} value1Filter={this.state.value1Filter} comparatorFilter={this.state.comparatorFilter} value2Filter={this.state.value2Filter} - value2Edited={this.value2Edited} + // value2Edited={this.value2Edited} selected={this.state.selections[id]} soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onOpen={this.setLocation} @@ -396,15 +397,15 @@ export default class Browser extends React.Component } }} comm={this.comm} - selectWidth={maxSelectWidth} + // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} - inFillin={inFillin} + // inFillin={inFillin} scrollParent={document.body} - generateTopicName={this.generateTopicName} - setSelected={this.setSelected} + // generateTopicName={this.generateTopicName} + // setSelected={this.setSelected} scoreColumns={this.state.score_columns} - test_types={this.state.test_types} - test_type_parts={this.state.test_type_parts} + // test_types={this.state.test_types} + // test_type_parts={this.state.test_type_parts} user={this.state.user} outputColumnWidth={outputColumnWidth} /> diff --git a/client/src/content-editable.tsx b/client/src/content-editable.tsx index b2ccca1..4a4766d 100644 --- a/client/src/content-editable.tsx +++ b/client/src/content-editable.tsx @@ -1,5 +1,3 @@ -// @ts-nocheck - import React from 'react'; import autoBind from 'auto-bind'; import sanitizeHtml from 'sanitize-html'; @@ -14,6 +12,7 @@ interface ContentEditableProps { onInput?: Function; // called when text is changed onFinish?: Function; // called when editing is finished onClick?: Function; // called when the element is clicked + onTemplateExpand?: () => void; } export default class ContentEditable extends React.Component { @@ -47,6 +46,7 @@ export default class ContentEditable extends React.Component
} @@ -198,7 +198,9 @@ export default class ContentEditable extends React.Component void; + onClose: () => void; +} + + +export default class ContextMenu extends React.Component { static defaultProps = { top: 0, left: 0, diff --git a/client/src/index.jsx b/client/src/index.tsx similarity index 58% rename from client/src/index.jsx rename to client/src/index.tsx index a69c483..d4f1a8a 100644 --- a/client/src/index.jsx +++ b/client/src/index.tsx @@ -4,6 +4,12 @@ import AdaTest from './adatest' ReactDOM.render( - , + , document.getElementById('adatest_container_1') ); \ No newline at end of file diff --git a/client/src/jupyter-comm.js b/client/src/jupyter-comm.ts similarity index 92% rename from client/src/jupyter-comm.js rename to client/src/jupyter-comm.ts index a03ed4d..ac6ac0f 100644 --- a/client/src/jupyter-comm.js +++ b/client/src/jupyter-comm.ts @@ -3,6 +3,14 @@ import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; export default class JupyterComm { + interfaceId: string; + callbackMap: { [key: string]: (data: any) => void }; + data: any; + pendingData: any; + jcomm: InnerJupyterComm; + debouncedSendPendingData500: () => void; + debouncedSendPendingData1000: () => void; + constructor(interfaceId, onopen) { autoBind(this); this.interfaceId = interfaceId; @@ -84,6 +92,9 @@ export default class JupyterComm { } class InnerJupyterComm { + jcomm: any; + callback: any; + constructor(target_name, callback, mode="open") { this._fire_callback = this._fire_callback.bind(this); this._register = this._register.bind(this) @@ -93,8 +104,10 @@ class InnerJupyterComm { // https://jupyter-notebook.readthedocs.io/en/stable/comms.html if (mode === "register") { + // @ts-ignore Jupyter.notebook.kernel.comm_manager.register_target(target_name, this._register); } else { + // @ts-ignore this.jcomm = Jupyter.notebook.kernel.comm_manager.new_comm(target_name); this.jcomm.on_msg(this._fire_callback); } diff --git a/client/src/row.jsx b/client/src/row.tsx similarity index 93% rename from client/src/row.jsx rename to client/src/row.tsx index 323a8c9..f168e49 100644 --- a/client/src/row.jsx +++ b/client/src/row.tsx @@ -6,8 +6,77 @@ import { defer } from 'lodash'; import { changeInput, changeLabel, changeOutput, deleteTest, moveTest } from './CommEvent'; import ContentEditable from './content-editable'; import ContextMenu from './context-menu'; +import JupyterComm from './jupyter-comm'; +import WebSocketComm from './web-socket-comm' + + +interface RowProps { + id: string; + soleSelected: boolean; + forceRelayout: () => void; + scoreColumns: any[]; + updateTotals?: (id: string, passes: number, failures: number) => void; + scrollParent: HTMLElement; + value1Filter: string; + value2Filter: string; + comparatorFilter: string; + scoreFilter?: number; + selected: boolean; + isSuggestion?: boolean; + comm: JupyterComm | WebSocketComm; + hideBorder?: boolean; + outputColumnWidth: string; + inputDefault?: string; + outputDefault?: string; + giveUpSelection?: (id: string) => void; + user: string; + topic: string; + onOpen?: (topic: string) => void; + onSelectToggle: (id: string, shiftKey: any, metaKey: any) => void; + onDrop?: (id: string, topic: string) => void; +} + +interface RowState { + type?: any; + scores: any[]; + label: string; + topic_name: string; + value1?: string; + comparator?: string; + value2?: string; + dropHighlighted: number; // used as a boolean + dragging: boolean; // used anywhere? + hovering: boolean; + plusHovering: boolean; + hidden?: boolean; + editing: boolean; + labeler: string; + display_parts: {}; + max_score_ind?: number; + contextTop?: number; + contextLeft?: number; + contextOpen?: boolean; + contextRows?: any[]; + description?: string; + previewValue1?: boolean; + previewValue2?: boolean; + prefix?: string; + input: string; + output: string; + maxImageHeight: number; + contextFocus?: string; +} + + +export default class Row extends React.Component { + dataLoadActions: any[]; + scrollToView: boolean; + divRef: HTMLDivElement; + topicNameEditable: ContentEditable; + inputEditable: ContentEditable; + outputEditable: ContentEditable; + mouseDownTarget: HTMLElement; -export default class Row extends React.Component { constructor(props) { super(props); autoBind(this); @@ -17,14 +86,15 @@ export default class Row extends React.Component { input: null, output: null, label: null, - labler: null, + labeler: null, topic_name: null, scores: null, dragging: false, dropHighlighted: 0, hovering: false, plusHovering: false, - maxImageHeight: 100 + maxImageHeight: 100, + display_parts: {}, }; this.dataLoadActions = []; @@ -63,7 +133,9 @@ export default class Row extends React.Component { // we need to force a relayout if the type changed since that impacts global alignments if (this.state.type !== nextState.type) { - if (this.props.forceRelayout) this.props.forceRelayout(); + if (this.props.forceRelayout) { + this.props.forceRelayout(); + } } } @@ -217,7 +289,7 @@ export default class Row extends React.Component { const display_parts = this.state.display_parts ? this.state.display_parts[this.state.max_score_ind] : {}; // console.log("overall_score[main_score]", overall_score[main_score], this.props.score_filter) - if (this.props.scoreFilter && overall_score[main_score] < this.props.scoreFilter && this.props.scoreFiler > -1000) { + if (this.props.scoreFilter && overall_score[main_score] < this.props.scoreFilter && this.props.scoreFilter > -1000) { //console.log("score filter ", this.state.value1, score, this.props.scoreFilter) return null; } @@ -232,7 +304,7 @@ export default class Row extends React.Component { return
this.divRef = el} - style={this.props.hideBorder ? {} : {borderTop: "1px solid rgb(216, 222, 228)"}} tabIndex="0" onKeyDown={this.keyDownHandler}> + style={this.props.hideBorder ? {} : {borderTop: "1px solid rgb(216, 222, 228)"}} tabIndex={0} onKeyDown={this.keyDownHandler}> {this.state.topic_name !== null && !this.props.isSuggestion && @@ -292,7 +364,7 @@ export default class Row extends React.Component {
-
+
@@ -676,7 +748,6 @@ export default class Row extends React.Component { } onDragStart(e) { - // don't initiate a drag from inside an editiable object if (this.mouseDownTarget.getAttribute("contenteditable") === "true") { e.preventDefault(); @@ -686,16 +757,16 @@ export default class Row extends React.Component { this.setState({dragging: true}); e.dataTransfer.setData("id", this.props.id); e.dataTransfer.setData("topic_name", this.state.topic_name); - if (this.props.onDragStart) { - this.props.onDragStart(e, this); - } + // if (this.props.onDragStart) { + // this.props.onDragStart(e, this); + // } } onDragEnd(e) { this.setState({dragging: false}); - if (this.props.onDragEnd) { - this.props.onDragEnd(e, this); - } + // if (this.props.onDragEnd) { + // this.props.onDragEnd(e, this); + // } } onDragOver(e) { @@ -779,10 +850,10 @@ function scrollParentToChild(parent, child) { if (!isViewable) { // Should we scroll using top or bottom? Find the smaller ABS adjustment if (parentScrolls) { - var scrollTop = childRect.top - parentRect.top; + var scrollTop: number = childRect.top - parentRect.top; var scrollBot = childRect.bottom - parentViewableArea.height - parentRect.top; } else { - var scrollTop = childRect.top; + var scrollTop: number = childRect.top; var scrollBot = childRect.bottom - parentViewableArea.height; } if (Math.abs(scrollTop) < Math.abs(scrollBot)) { diff --git a/client/src/total-value.jsx b/client/src/total-value.jsx deleted file mode 100644 index d3affba..0000000 --- a/client/src/total-value.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import autoBind from 'auto-bind'; -import { get, debounce } from 'lodash'; - -export default class TotalValue extends React.Component { - constructor(props) { - super(props); - autoBind(this); - - this.doStateUpdateDebounced = debounce(this.doStateUpdate, 100); - - this.pendingStateUpdate = {}; - - // our starting state - this.state = { - // note that all the ids will also be properties of the state - }; - } - - setSubtotal(id, subtotal) { - this.pendingStateUpdate[id] = subtotal; - this.doStateUpdateDebounced(); - } - - doStateUpdate() { - this.setState(this.pendingStateUpdate); - this.pendingStateUpdate = {}; - } - - render() { - // we just sum up the current active subtotals - let total = 0; - for (let i in this.props.activeIds) { - total += get(this.state, this.props.activeIds[i], 0); - } - - return - {total} - - } - } \ No newline at end of file diff --git a/client/src/total-value.tsx b/client/src/total-value.tsx new file mode 100644 index 0000000..a95d666 --- /dev/null +++ b/client/src/total-value.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import autoBind from 'auto-bind'; +import { get, debounce } from 'lodash'; + +interface TotalValueProps { + activeIds: string[]; +} + +export default class TotalValue extends React.Component { + doStateUpdateDebounced: () => void; + pendingStateUpdate: any; + + constructor(props) { + super(props); + autoBind(this); + + this.doStateUpdateDebounced = debounce(this.doStateUpdate, 100); + + this.pendingStateUpdate = {}; + + // our starting state + this.state = { + // note that all the ids will also be properties of the state + }; + } + + setSubtotal(id, subtotal) { + this.pendingStateUpdate[id] = subtotal; + this.doStateUpdateDebounced(); + } + + doStateUpdate() { + this.setState(this.pendingStateUpdate); + this.pendingStateUpdate = {}; + } + + render() { + // we just sum up the current active subtotals + let total = 0; + for (let i in this.props.activeIds) { + total += get(this.state, this.props.activeIds[i], 0); + } + + return + {total} + + } +} \ No newline at end of file diff --git a/client/src/types/index.d.ts b/client/src/types/index.d.ts new file mode 100644 index 0000000..7bff606 --- /dev/null +++ b/client/src/types/index.d.ts @@ -0,0 +1,13 @@ +export {}; + +declare global { + // Declare all the variables that we add to the global Window object + // Otherwise, TypeScript will complain that these variables are not defined + interface Window { + adatest_root: any; + AdaTestReact: any; + AdaTestReactDOM: any; + AdaTest: any; + faTimes: any; + } +} \ No newline at end of file diff --git a/client/src/web-socket-comm.js b/client/src/web-socket-comm.ts similarity index 72% rename from client/src/web-socket-comm.js rename to client/src/web-socket-comm.ts index 4e665ac..f7e87d5 100644 --- a/client/src/web-socket-comm.js +++ b/client/src/web-socket-comm.ts @@ -3,15 +3,30 @@ import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; export default class WebSocketComm { - constructor(interfaceId, websocketServer, onopen) { + interfaceId: string; + websocketServer: string; + callbackMap: { [key: string]: (data: any) => void }; + // local data cache + data: {}; + // data to send to the server + pendingData: {}; + pendingResponses: {}; + wcomm: WebSocket; + onOpen: typeof WebSocket.prototype.onopen; + reconnectDelay: number; + seqNumber: number; + debouncedSendPendingData500: () => void; + debouncedSendPendingData1000: () => void; + + constructor(interfaceId, websocketServer, onOpen) { autoBind(this); this.interfaceId = interfaceId; this.websocketServer = websocketServer; this.callbackMap = {}; - // this.data = {}; + this.data = {}; this.pendingData = {}; this.pendingResponses = {}; - this.onopen = onopen; + this.onOpen = onOpen; this.reconnectDelay = 100; this.seqNumber = 0; @@ -51,19 +66,19 @@ export default class WebSocketComm { } addPendingData(keys, data) { - // console.log("addPendingData", keys, data); + console.log("addPendingData", keys, data); if (!Array.isArray(keys)) keys = [keys]; for (const i in keys) { const k = keys[i]; this.pendingData[k] = data; - // this.data[k] = Object.assign(this.data[k] || {}, data); // pretend it has already changed in our data cache + this.data[k] = Object.assign(this.data[k] || {}, data); // pretend it has already changed in our data cache } } connect() { let wsUri = (window.location.protocol=='https:' ? 'wss://' : 'ws://') + (this.websocketServer.startsWith("/") ? window.location.host : "") + this.websocketServer; this.wcomm = new WebSocket(wsUri); - this.wcomm.onopen = this.onopen; + this.wcomm.onopen = this.onOpen; this.wcomm.onmessage = this.handleResponse; this.wcomm.onerror = this.onError; this.wcomm.onclose = this.onClose; @@ -74,19 +89,20 @@ export default class WebSocketComm { const keys = Object.keys(data); if (keys.includes("sequence_number")) { console.log(`received message#${data.sequence_number}`, data); + this.pendingResponses[data.sequence_number] = data; } } - // updateData(e) { - // console.log("WEBSOCKET UPDATEDATA, received unexpected data", data) - // for (const k in data) { - // // console.log("data[k]", data[k]) - // this.data[k] = Object.assign(this.data[k] || {}, data[k]); - // if (k in this.callbackMap) { - // this.callbackMap[k](data[k]); - // } - // } - // } + updateData(e) { + console.log("WEBSOCKET UPDATEDATA, received unexpected data", this.data) + for (const k in this.data) { + // console.log("data[k]", data[k]) + this.data[k] = Object.assign(this.data[k] || {}, this.data[k]); + if (k in this.callbackMap) { + this.callbackMap[k](this.data[k]); + } + } + } onError(e) { console.log("Websocket error", e); @@ -100,8 +116,8 @@ export default class WebSocketComm { subscribe(key, callback) { console.log("WEBSOCKET SUBSCRIBE", key, callback); - // this.callbackMap[key] = callback; - // defer(_ => this.callbackMap[key](this.data[key])); + this.callbackMap[key] = callback; + defer(_ => this.callbackMap[key](this.data[key])); } getSeqNumber() { @@ -130,7 +146,7 @@ export default class WebSocketComm { clearInterval(interval); resolve(this.pendingResponses[seqNumber]); } - }, 10); + }, 100); }); } diff --git a/client/webpack.config.js b/client/webpack.config.js index ba5d117..ddc5efa 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -3,7 +3,7 @@ const path = require('path'); const isDevelopment = process.env.NODE_ENV === 'dev'; module.exports = { - entry: path.resolve(__dirname, './src/adatest.jsx'), + entry: path.resolve(__dirname, './src/adatest.tsx'), devtool: isDevelopment ? 'eval-source-map' : false, module: { rules: [ From 5445ef97ee5d5af6e3e286a316ea78b7a1e700cd Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Wed, 19 Oct 2022 19:35:32 -0700 Subject: [PATCH 04/18] sequence_number send / response fixes --- adatest/_test_tree_browser.py | 77 +++++++++++++---------------------- client/src/web-socket-comm.ts | 8 ++-- 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index efa6db3..70a78e1 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -295,9 +295,6 @@ def interface_event(self, msg): if event_id == "redraw": self._refresh_interface(sequence_number) - elif event_id == "refresh_tests": - self._refresh_tests(msg["test_ids"], sequence_number) - # generate a new set of suggested tests/topics elif event_id == "generate_suggestions": self._clear_suggestions() @@ -326,7 +323,7 @@ def interface_event(self, msg): # log.debug(e) # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) # self._suggestions_error = True - # self._refresh_interface() + self._refresh_interface(sequence_number) # change the current topic elif event_id == "change_topic": @@ -341,13 +338,13 @@ def interface_event(self, msg): else: self.mode = "tests" - # self._refresh_interface() + self._refresh_interface(sequence_number) # clear the current set of suggestions elif event_id == "clear_suggestions": self._clear_suggestions() - # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) - # self._refresh_interface() + self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) + self._refresh_interface(sequence_number) # add a new empty subtopic to the current topic elif event_id == "add_new_topic": @@ -361,7 +358,7 @@ def interface_event(self, msg): } self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) # add a new empty test to the current topic elif event_id == "add_new_test": @@ -381,7 +378,7 @@ def interface_event(self, msg): self.test_tree.loc[uuid.uuid4().hex] = row self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) # change which scorer/model is used for sorting tests elif event_id == "set_first_model": @@ -398,14 +395,16 @@ def interface_event(self, msg): self.score_columns.insert(0, name) self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) elif event_id == "change_generator": self.active_generator = msg["generator"] self._active_generator_obj = self.generators[self.active_generator] + self.send_response(sequence_number) elif event_id == "change_mode": self.mode = msg["mode"] + self.send_response(sequence_number) elif event_id == 'change_description': id = msg['topic_marker_id'] @@ -416,12 +415,12 @@ def interface_event(self, msg): self.test_tree.loc[id, 'label'] = "topic_marker" self.test_tree.loc[msg['topic_marker_id'], 'description'] = msg['description'] self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) elif event_id == 'change_filter': print("change_filter") self.filter_text = msg['filter_text'] - # self._refresh_interface() + self._refresh_interface(sequence_number) # Move a test/topic to a new topic # Also used to rename @@ -440,7 +439,7 @@ def interface_event(self, msg): # Recompute any missing embeddings to handle any changes self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) elif event_id == "delete_test": log.debug("delete_test") @@ -456,18 +455,18 @@ def interface_event(self, msg): self.test_tree.drop(id, inplace=True) self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface() + self._refresh_interface(sequence_number) # if we are just updating a single row in tests then we only recompute the scores elif event_id == "change_label" or event_id == "change_input" or event_id == "change_output": - # sendback_data = {} + sendback_data = {} test_id = msg["test_ids"][0] # convert template expansions into a standard value update - # if msg.get("action", "") == "template_expand": - # template_value = self.templatize(self.test_tree.loc[test_id, msg["value"]]) - # msg = {msg["value"]: template_value} - # sendback_data[msg["value"]] = template_value + if msg.get("action", "") == "template_expand": + template_value = self.templatize(self.test_tree.loc[test_id, msg["value"]]) + msg = {msg["value"]: template_value} + sendback_data[msg["value"]] = template_value # update the row and recompute scores metadata_fields = ["event_id", "test_ids"] @@ -484,38 +483,20 @@ def interface_event(self, msg): # self._compute_embeddings_and_scores(self.test_tree, overwrite_outputs=False) # send just the data that changed back to the frontend - # sendback_data["scores"] = {c: [[test_id, v] for v in ui_score_parts(self.test_tree.loc[test_id, c], self.test_tree.loc[test_id, "label"])] for c in self.score_columns} - # outputs = {c: [[test_id, json.loads(self.test_tree.loc[test_id].get(c[:-6] + " raw outputs", "{}"))]] for c in self.score_columns} - # sendback_data["raw_outputs"] = outputs - # if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back - # sendback_data["output"] = self.test_tree.loc[test_id, "output"] - # sendback_data["label"] = self.test_tree.loc[test_id, "label"] - # sendback_data["labeler"] = self.test_tree.loc[test_id, "labeler"] - # sendback_data.update(self.test_display_parts(self.test_tree.loc[test_id])) - # self.comm.send({test_id: sendback_data}) - # refresh_data = self._refresh_tests(test_id)[0] - # sendback_data.update(refresh_data) - # comm.send() - - self._auto_save() - - else: - log.error(f"Unable to parse the interface message: {msg}") - - def _refresh_tests(self, test_ids, sequence_number): - test_data_list = [] - for test_id in test_ids: - sendback_data = {} sendback_data["scores"] = {c: [[test_id, v] for v in ui_score_parts(self.test_tree.loc[test_id, c], self.test_tree.loc[test_id, "label"])] for c in self.score_columns} outputs = {c: [[test_id, json.loads(self.test_tree.loc[test_id].get(c[:-6] + " raw outputs", "{}"))]] for c in self.score_columns} sendback_data["raw_outputs"] = outputs - # if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back - # sendback_data["output"] = self.test_tree.loc[test_id, "output"] + if "output" not in msg: # if the output was given to us the client is managing its current state so we shouldn't send it back + sendback_data["output"] = self.test_tree.loc[test_id, "output"] sendback_data["label"] = self.test_tree.loc[test_id, "label"] sendback_data["labeler"] = self.test_tree.loc[test_id, "labeler"] sendback_data.update(self.test_display_parts(self.test_tree.loc[test_id])) - test_data_list.append({test_id: sendback_data}) - self.send_response(test_data_list, sequence_number) + self.send_response(sequence_number, {test_id: sendback_data}) + + self._auto_save() + + else: + log.error(f"Unable to parse the interface message: {msg}") def _refresh_interface(self, sequence_number): """ Send our entire current state to the frontend interface. @@ -649,10 +630,10 @@ def sort_key(id): # "test_type_parts": test_type_parts, } - self.send_response(data, sequence_number) + self.send_response(sequence_number, data) - def send_response(self, data, sequence_number): - self.comm.send({"sequence_number": sequence_number, "data": data, "status": "ok"}) + def send_response(self, sequence_number: int, data: object = {}, status="ok"): + self.comm.send({"sequence_number": sequence_number, "data": data, "status": status}) def _clear_suggestions(self): """ Clear the suggestions for the current topic. diff --git a/client/src/web-socket-comm.ts b/client/src/web-socket-comm.ts index f7e87d5..c4df3ba 100644 --- a/client/src/web-socket-comm.ts +++ b/client/src/web-socket-comm.ts @@ -38,14 +38,14 @@ export default class WebSocketComm { send(keys, data) { this.addPendingData(keys, data); - this.sendPendingData(); + return this.sendPendingData(); } sendEvent(commEvent) { for (const k of Object.keys(commEvent)) { this.addPendingData(k, commEvent[k]); } - this.sendPendingData(); + return this.sendPendingData(); } debouncedSendEvent500(commEvent) { @@ -133,8 +133,8 @@ export default class WebSocketComm { return this.waitForResponse(seqNumber); } - waitForResponse(seqNumber) { - const timeout_ms = 30000; + waitForResponse(seqNumber): Promise { + const timeout_ms = 60000; this.pendingResponses[seqNumber] = "pending"; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { From 6ffffc3d1c345fb102d16d2f389f4afab1090ce7 Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Thu, 20 Oct 2022 17:02:57 -0700 Subject: [PATCH 05/18] React Query setup --- adatest/_test_tree_browser.py | 30 ++++++++++------ client/package-lock.json | 64 +++++++++++++++++++++++++++++++++++ client/package.json | 1 + client/src/jupyter-comm.ts | 2 ++ client/src/row.tsx | 20 ++++++++++- client/src/web-socket-comm.ts | 3 +- 6 files changed, 108 insertions(+), 12 deletions(-) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index 70a78e1..ecb73bf 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -301,6 +301,7 @@ def interface_event(self, msg): self.test_tree.retrain_topic_labeling_model(self.current_topic) self.test_tree.retrain_topic_membership_model(self.current_topic) self._generate_suggestions(filter=msg.get("filter", "")) + self.send_response(sequence_number) # if self._active_generator_obj is None: # self._suggestions_error = "No AdaTest generator has been set!" # else: @@ -323,7 +324,7 @@ def interface_event(self, msg): # log.debug(e) # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) # self._suggestions_error = True - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) # change the current topic elif event_id == "change_topic": @@ -337,14 +338,16 @@ def interface_event(self, msg): self.mode = "topics" else: self.mode = "tests" + self.send_response(sequence_number) - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) # clear the current set of suggestions elif event_id == "clear_suggestions": self._clear_suggestions() self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) # add a new empty subtopic to the current topic elif event_id == "add_new_topic": @@ -358,7 +361,8 @@ def interface_event(self, msg): } self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) # add a new empty test to the current topic elif event_id == "add_new_test": @@ -378,7 +382,8 @@ def interface_event(self, msg): self.test_tree.loc[uuid.uuid4().hex] = row self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) # change which scorer/model is used for sorting tests elif event_id == "set_first_model": @@ -395,7 +400,8 @@ def interface_event(self, msg): self.score_columns.insert(0, name) self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) elif event_id == "change_generator": self.active_generator = msg["generator"] @@ -415,12 +421,14 @@ def interface_event(self, msg): self.test_tree.loc[id, 'label'] = "topic_marker" self.test_tree.loc[msg['topic_marker_id'], 'description'] = msg['description'] self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) elif event_id == 'change_filter': print("change_filter") self.filter_text = msg['filter_text'] - self._refresh_interface(sequence_number) + self.send_response(sequence_number) + # self._refresh_interface(sequence_number) # Move a test/topic to a new topic # Also used to rename @@ -439,7 +447,8 @@ def interface_event(self, msg): # Recompute any missing embeddings to handle any changes self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) elif event_id == "delete_test": log.debug("delete_test") @@ -455,7 +464,8 @@ def interface_event(self, msg): self.test_tree.drop(id, inplace=True) self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - self._refresh_interface(sequence_number) + # self._refresh_interface(sequence_number) + self.send_response(sequence_number) # if we are just updating a single row in tests then we only recompute the scores elif event_id == "change_label" or event_id == "change_input" or event_id == "change_output": diff --git a/client/package-lock.json b/client/package-lock.json index 5fb6fa3..2321954 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,6 +15,7 @@ "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", + "@tanstack/react-query": "^4.12.0", "auto-bind": "^4.0.0", "json5": "^2.2.0", "lodash": "^4.17.21", @@ -1936,6 +1937,41 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, + "node_modules/@tanstack/query-core": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.12.0.tgz", + "integrity": "sha512-KEiFPNLMFByhNL2s6RBFL6Z5cNdwwQzFpW/II3GY+rEuQ343ZEoVyQ48zlUXXkEkbamQFIFg2onM8Pxf0Yo01A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.12.0.tgz", + "integrity": "sha512-prchV1q+CJ0ZVo8Rts2cOF3azDfQizZZySmH6XXsXRcPTbir0sgb9fp0vY/5l5ZkSYjTvWt/OL8WQhAhYMSvrA==", + "dependencies": { + "@tanstack/query-core": "4.12.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@types/eslint": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", @@ -5261,6 +5297,14 @@ "react": "^16.8.0 || ^17.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6864,6 +6908,20 @@ "react-is": "^16.8.0 || ^17.0.0" } }, + "@tanstack/query-core": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.12.0.tgz", + "integrity": "sha512-KEiFPNLMFByhNL2s6RBFL6Z5cNdwwQzFpW/II3GY+rEuQ343ZEoVyQ48zlUXXkEkbamQFIFg2onM8Pxf0Yo01A==" + }, + "@tanstack/react-query": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.12.0.tgz", + "integrity": "sha512-prchV1q+CJ0ZVo8Rts2cOF3azDfQizZZySmH6XXsXRcPTbir0sgb9fp0vY/5l5ZkSYjTvWt/OL8WQhAhYMSvrA==", + "requires": { + "@tanstack/query-core": "4.12.0", + "use-sync-external-store": "^1.2.0" + } + }, "@types/eslint": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", @@ -9460,6 +9518,12 @@ "integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==", "requires": {} }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index 995ca1d..0a8cb18 100644 --- a/client/package.json +++ b/client/package.json @@ -39,6 +39,7 @@ "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", + "@tanstack/react-query": "^4.12.0", "auto-bind": "^4.0.0", "json5": "^2.2.0", "lodash": "^4.17.21", diff --git a/client/src/jupyter-comm.ts b/client/src/jupyter-comm.ts index ac6ac0f..c174785 100644 --- a/client/src/jupyter-comm.ts +++ b/client/src/jupyter-comm.ts @@ -36,6 +36,8 @@ export default class JupyterComm { this.addPendingData(k, commEvent[k]); } this.sendPendingData(); + // TODO: Make promises work here too + return Promise.resolve(); } debouncedSendEvent500(commEvent) { diff --git a/client/src/row.tsx b/client/src/row.tsx index f168e49..313e0a4 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -3,12 +3,20 @@ import autoBind from 'auto-bind'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPlus, faCheck, faBan, faFolderMinus, faArrowRight, faTimes, faFolderPlus, faFolder} from '@fortawesome/free-solid-svg-icons' import { defer } from 'lodash'; -import { changeInput, changeLabel, changeOutput, deleteTest, moveTest } from './CommEvent'; +import { changeInput, changeLabel, changeOutput, deleteTest, moveTest, redraw } from './CommEvent'; import ContentEditable from './content-editable'; import ContextMenu from './context-menu'; import JupyterComm from './jupyter-comm'; import WebSocketComm from './web-socket-comm' +import { + useQuery, + useMutation, + useQueryClient, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query' + interface RowProps { id: string; @@ -821,6 +829,16 @@ export default class Row extends React.Component { } +// const queryClient = new QueryClient(); + +export function RowFunctional(props: RowProps) { + const queryClient = useQueryClient(); + const comm = props.comm; + const { isLoading, error, data, isFetching } = useQuery(['row'], () => comm.sendEvent(redraw()).then(data => data)); + return ; +} + + // https://stackoverflow.com/questions/45408920/plain-javascript-scrollintoview-inside-div function scrollParentToChild(parent, child) { console.log("scrollParentToChild", parent, child) diff --git a/client/src/web-socket-comm.ts b/client/src/web-socket-comm.ts index c4df3ba..d864c58 100644 --- a/client/src/web-socket-comm.ts +++ b/client/src/web-socket-comm.ts @@ -1,6 +1,7 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; +import { CommEvent } from './CommEvent'; export default class WebSocketComm { interfaceId: string; @@ -41,7 +42,7 @@ export default class WebSocketComm { return this.sendPendingData(); } - sendEvent(commEvent) { + sendEvent(commEvent: CommEvent) { for (const k of Object.keys(commEvent)) { this.addPendingData(k, commEvent[k]); } From 736d6ceba58c6aa8e22a983d9dc05da97620d533 Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Fri, 21 Oct 2022 16:28:23 -0700 Subject: [PATCH 06/18] Redux prototype - lots of testing needed --- client/package-lock.json | 254 +++++++++++++++++++-------- client/package.json | 3 +- client/src/TestTreeSlice.ts | 121 +++++++++++++ client/src/adatest.tsx | 26 +-- client/src/browser.tsx | 314 +++++++++++++++++++++------------- client/src/jupyter-comm.ts | 6 +- client/src/row.tsx | 10 -- client/src/store.ts | 13 ++ client/src/types/index.d.ts | 7 +- client/src/web-socket-comm.ts | 14 +- 10 files changed, 539 insertions(+), 229 deletions(-) create mode 100644 client/src/TestTreeSlice.ts create mode 100644 client/src/store.ts diff --git a/client/package-lock.json b/client/package-lock.json index 2321954..bd14b57 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,13 +15,14 @@ "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", - "@tanstack/react-query": "^4.12.0", + "@reduxjs/toolkit": "^1.8.6", "auto-bind": "^4.0.0", "json5": "^2.2.0", "lodash": "^4.17.21", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", + "react-redux": "^8.0.4", "react-router-dom": "^5.2.0", "sanitize-html": "^2.3.3" }, @@ -1533,11 +1534,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", + "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/runtime/node_modules/regenerator-runtime": { @@ -1937,37 +1941,25 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, - "node_modules/@tanstack/query-core": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.12.0.tgz", - "integrity": "sha512-KEiFPNLMFByhNL2s6RBFL6Z5cNdwwQzFpW/II3GY+rEuQ343ZEoVyQ48zlUXXkEkbamQFIFg2onM8Pxf0Yo01A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.12.0.tgz", - "integrity": "sha512-prchV1q+CJ0ZVo8Rts2cOF3azDfQizZZySmH6XXsXRcPTbir0sgb9fp0vY/5l5ZkSYjTvWt/OL8WQhAhYMSvrA==", + "node_modules/@reduxjs/toolkit": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.6.tgz", + "integrity": "sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==", "dependencies": { - "@tanstack/query-core": "4.12.0", - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-native": "*" + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" }, "peerDependenciesMeta": { - "react-dom": { + "react": { "optional": true }, - "react-native": { + "react-redux": { "optional": true } } @@ -2044,15 +2036,15 @@ "version": "18.0.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz", "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-redux": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", - "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", + "version": "7.1.24", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", + "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -2099,6 +2091,11 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -3592,6 +3589,15 @@ "postcss": "^8.1.0" } }, + "node_modules/immer": { + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz", + "integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -4450,6 +4456,35 @@ "react-dom": "^16.8.5 || ^17.0.0" } }, + "node_modules/react-beautiful-dnd/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-beautiful-dnd/node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -4469,29 +4504,48 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-redux": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", - "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", + "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", "dependencies": { "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" }, "peerDependencies": { - "react": "^16.8.3 || ^17" + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" }, "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, "react-dom": { "optional": true }, "react-native": { "optional": true + }, + "redux": { + "optional": true } } }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-router": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz", @@ -4557,13 +4611,21 @@ } }, "node_modules/redux": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", - "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", "dependencies": { "@babel/runtime": "^7.9.2" } }, + "node_modules/redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -4653,6 +4715,11 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz", + "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==" + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -6648,9 +6715,9 @@ } }, "@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", + "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", "requires": { "regenerator-runtime": "^0.13.4" }, @@ -6908,18 +6975,15 @@ "react-is": "^16.8.0 || ^17.0.0" } }, - "@tanstack/query-core": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.12.0.tgz", - "integrity": "sha512-KEiFPNLMFByhNL2s6RBFL6Z5cNdwwQzFpW/II3GY+rEuQ343ZEoVyQ48zlUXXkEkbamQFIFg2onM8Pxf0Yo01A==" - }, - "@tanstack/react-query": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.12.0.tgz", - "integrity": "sha512-prchV1q+CJ0ZVo8Rts2cOF3azDfQizZZySmH6XXsXRcPTbir0sgb9fp0vY/5l5ZkSYjTvWt/OL8WQhAhYMSvrA==", + "@reduxjs/toolkit": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.6.tgz", + "integrity": "sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==", "requires": { - "@tanstack/query-core": "4.12.0", - "use-sync-external-store": "^1.2.0" + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" } }, "@types/eslint": { @@ -7001,15 +7065,15 @@ "version": "18.0.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz", "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==", - "dev": true, + "devOptional": true, "requires": { "@types/react": "*" } }, "@types/react-redux": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", - "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", + "version": "7.1.24", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", + "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -7051,6 +7115,11 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -8248,6 +8317,11 @@ "dev": true, "requires": {} }, + "immer": { + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz", + "integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==" + }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -8903,6 +8977,26 @@ "react-redux": "^7.2.0", "redux": "^4.0.4", "use-memo-one": "^1.1.1" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + } + } } }, "react-dom": { @@ -8921,16 +9015,23 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-redux": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", - "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", + "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", "requires": { "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } } }, "react-router": { @@ -8985,13 +9086,19 @@ } }, "redux": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", - "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", "requires": { "@babel/runtime": "^7.9.2" } }, + "redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9068,6 +9175,11 @@ "is-finite": "^1.0.0" } }, + "reselect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz", + "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==" + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", diff --git a/client/package.json b/client/package.json index 0a8cb18..4d53427 100644 --- a/client/package.json +++ b/client/package.json @@ -39,13 +39,14 @@ "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", - "@tanstack/react-query": "^4.12.0", + "@reduxjs/toolkit": "^1.8.6", "auto-bind": "^4.0.0", "json5": "^2.2.0", "lodash": "^4.17.21", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", + "react-redux": "^8.0.4", "react-router-dom": "^5.2.0", "sanitize-html": "^2.3.3" } diff --git a/client/src/TestTreeSlice.ts b/client/src/TestTreeSlice.ts new file mode 100644 index 0000000..0d1e5e9 --- /dev/null +++ b/client/src/TestTreeSlice.ts @@ -0,0 +1,121 @@ +import { createSlice } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +// export interface CounterState { +// value: number +// } + +// data["browser"] = { +// "suggestions": suggestions_children, +// "tests": children, +// "user": self.user, +// "topic": self.current_topic, +// "topic_description": self.test_tree.loc[topic_marker_id]["description"] if topic_marker_id is not None else "", +// "topic_marker_id": topic_marker_id if topic_marker_id is not None else uuid.uuid4().hex, +// "score_filter": score_filter, +// "disable_suggestions": False, +// "read_only": False, +// "score_columns": self.score_columns, +// "suggestions_error": self._suggestions_error, +// "generator_options": [str(x) for x in self.generators.keys()] if isinstance(self.generators, dict) else [self.active_generator], +// "active_generator": self.active_generator, +// "mode": self.mode, +// "mode_options": self.mode_options, +// "test_tree_name": self.test_tree.name +// } + +// export interface TestTreeState { +// // topic: string; +// // suggestions: any[]; +// // tests: any[]; +// selections: any; +// // user: string; +// loading_suggestions: boolean; +// max_suggestions: number; +// suggestions_pos: number; +// suggestionsDropHighlighted: number; +// // score_filter: number; +// do_score_filter: boolean; +// // filter_text: string; +// experiment_pos: number; +// timerExpired: boolean; +// experiment_locations: any[]; +// experiment: boolean; +// value2Filter: string; +// test_types?: any[]; +// test_type_parts?: any[]; +// // score_columns?: any[]; +// // test_tree_name?: any; +// // topic_description?: string; +// // read_only?: boolean; +// topicFilter?: string; +// value1Filter?: string; +// comparatorFilter?: string; +// // disable_suggestions?: boolean; +// // mode_options?: any[]; +// // generator_options?: any[]; +// // active_generator?: string; +// // mode?: string; +// // suggestions_error?: string; +// // topic_marker_id?: string; +// } + +export interface TestTreeState { + topic: string; + suggestions: any[]; + tests: any[]; + user: string; + score_filter: number; + filter_text: string; + score_columns?: any[]; + test_tree_name?: any; + topic_description?: string; + read_only?: boolean; + disable_suggestions?: boolean; + mode_options?: any[]; + generator_options?: any[]; + active_generator?: string; + mode?: string; + suggestions_error?: string; + topic_marker_id?: string; +} + +const initialState: TestTreeState = { + topic: "/", + suggestions: [], + tests: [], + user: "anonymous", + score_filter: 0.3, + filter_text: "", +} + +export const testTreeSlice = createSlice({ + name: 'testTree', + initialState, + reducers: { + refresh: (state, action: PayloadAction) => { + state = {...action.payload} + }, + + updateGenerator: (state, action: PayloadAction) => { + state = {...state, active_generator: action.payload} + }, + + updateTopicDescription: (state, action: PayloadAction) => { + state = {...state, topic_description: action.payload} + }, + + updateFilterText: (state, action: PayloadAction) => { + state = {...state, filter_text: action.payload} + }, + + updateSuggestions: (state, action: PayloadAction) => { + state = {...state, suggestions: action.payload} + } + }, +}) + +// Action creators are generated for each case reducer function +export const { refresh, updateGenerator, updateTopicDescription, updateFilterText, updateSuggestions } = testTreeSlice.actions + +export default testTreeSlice.reducer \ No newline at end of file diff --git a/client/src/adatest.tsx b/client/src/adatest.tsx index 7efa62b..18a03c0 100644 --- a/client/src/adatest.tsx +++ b/client/src/adatest.tsx @@ -6,6 +6,8 @@ import { withRouter } from 'react-router-dom'; import { BrowserRouter } from "react-router-dom"; import { MemoryRouter } from 'react-router'; import Browser from './browser' +import { store } from './store' +import { Provider } from 'react-redux' const BrowserWithRouter = withRouter(Browser); @@ -35,18 +37,20 @@ export default class AdaTest extends React.Component const Router = this.props.environment === "web" ? BrowserRouter : MemoryRouter; return ( -
-
- { /* @ts-ignore: JSX element type 'Router' does not have any construct or call signatures */ } - - - + +
+
+ { /* @ts-ignore: JSX element type 'Router' does not have any construct or call signatures */ } + + + +
-
+ ); } } diff --git a/client/src/browser.tsx b/client/src/browser.tsx index a27dfc4..1329cd9 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -11,8 +11,16 @@ import BreadCrum from './bread-crum'; import TotalValue from './total-value'; import ContentEditable from './content-editable'; +import { TestTreeState, refresh, updateGenerator, updateTopicDescription, updateFilterText, updateSuggestions } from './TestTreeSlice'; +import { useSelector, useDispatch } from 'react-redux' +import { AppDispatch } from './store'; +import { Comm } from './types'; + interface BrowserProps { + testTree: TestTreeState; + comm: JupyterComm | WebSocketComm; + dispatch: AppDispatch; location: any; interfaceId: any; environment: string; @@ -24,18 +32,18 @@ interface BrowserProps { } interface BrowserState { - topic: string; - suggestions: any[]; - tests: any[]; + // topic: string; + // suggestions: any[]; + // tests: any[]; selections: any; - user: string; + // user: string; loading_suggestions: boolean; max_suggestions: number; suggestions_pos: number; suggestionsDropHighlighted: number; - score_filter: number; + // score_filter: number; do_score_filter: boolean; - filter_text: string; + // filter_text: string; experiment_pos: number; timerExpired: boolean; experiment_locations: any[]; @@ -43,27 +51,79 @@ interface BrowserState { value2Filter: string; test_types?: any[]; test_type_parts?: any[]; - score_columns?: any[]; - test_tree_name?: any; - topic_description?: string; - read_only?: boolean; + // score_columns?: any[]; + // test_tree_name?: any; + // topic_description?: string; + // read_only?: boolean; topicFilter?: string; value1Filter?: string; comparatorFilter?: string; - disable_suggestions?: boolean; - mode_options?: any[]; - generator_options?: any[]; - active_generator?: string; - mode?: string; - suggestions_error?: string; - topic_marker_id?: string; + // disable_suggestions?: boolean; + // mode_options?: any[]; + // generator_options?: any[]; + // active_generator?: string; + // mode?: string; + // suggestions_error?: string; + // topic_marker_id?: string; +} + +function useComm(env: string, interfaceId: any, websocket_server: string=null) { + console.log("pairs interfaceId", interfaceId) + if (env === "jupyter") { + return new JupyterComm(interfaceId); + } else if (env === "web") { + if (websocket_server != null) { + return new WebSocketComm(interfaceId, websocket_server); + } else { + console.error("websocket_server is null"); + throw new Error("websocket_server is null"); + } + } else { + console.error("Unknown environment:", env); + throw new Error(`Unknown environment: ${env}`); + } +} + +function refreshBrowser(comm: Comm, dispatch: AppDispatch) { + return comm.sendEvent(redraw()).then((data) => { + if (data["status"] === "ok") { + dispatch(refresh(data["data"])); + } else { + // TODO: handle error + } + }); +} + +// TestTreeBrowser function component wraps the legacy Browser +// class component so we can use hooks. It is responsible for setting +// up all the props for the Browser component. +export default function TestTreeBrowser(props: BrowserProps) { + const testTree = useSelector((state: TestTreeState) => state); + const dispatch = useDispatch(); + const comm = useComm(props.environment, props.interfaceId, props.websocket_server); + if (comm instanceof WebSocketComm) { + comm.connect(() => { + refreshBrowser(comm, dispatch); + // if we are in Jupyter we need to sync the URL in the MemoryRouter + // if (props.environment == "jupyter") { + // this.props.history.push(this.props.prefix + this.props.startingTopic); + // // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar + // } else if (props.location.pathname !== (props.prefix == "" ? "/" : props.prefix)) { + // defer(() => this.goToTopic(this.stripPrefix(this.props.location.pathname))); + // } + // props.history.listen(this.locationChanged); + }); + } + + return ( + + ) } -export default class Browser extends React.Component { +export class Browser extends React.Component { id: string; rows: any; - comm: JupyterComm | WebSocketComm; totalPassesObjects: {}; totalFailuresObjects: {}; divRef: HTMLDivElement; @@ -75,18 +135,12 @@ export default class Browser extends React.Component // our starting state this.state = { - topic: "/", - suggestions: [], - tests: [], selections: {}, - user: "anonymous", loading_suggestions: false, max_suggestions: 10, suggestions_pos: 0, suggestionsDropHighlighted: 0, - score_filter: 0.3, do_score_filter: true, - filter_text: "", experiment_pos: 0, timerExpired: false, experiment_locations: [], @@ -101,15 +155,6 @@ export default class Browser extends React.Component this.rows = {}; // connect to the jupyter backend - console.log("pairs this.props.interfaceId", this.props.interfaceId) - if (this.props.environment === "jupyter") { - this.comm = new JupyterComm(this.props.interfaceId, this.connectionOpen); - } else if (this.props.environment === "web") { - this.comm = new WebSocketComm(this.props.interfaceId, this.props.websocket_server, this.connectionOpen); - } else { - console.error("Unknown environment:", this.props.environment); - } - this.comm.subscribe(this.id, this.newData); this.debouncedForceUpdate = debounce(this.debouncedForceUpdate, 100); @@ -122,21 +167,6 @@ export default class Browser extends React.Component this.forceUpdate(); } - // gets called once we have a working websocket connection ready to go - connectionOpen() { - // if we are in Jupyter we need to sync the URL in the MemoryRouter - if (this.props.environment == "jupyter") { - this.props.history.push(this.props.prefix + this.props.startingTopic); - - // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar - } else if (this.props.location.pathname !== (this.props.prefix == "" ? "/" : this.props.prefix)) { - defer(() => this.goToTopic(this.stripPrefix(this.props.location.pathname))); - } - this.props.history.listen(this.locationChanged); - - defer(() => this.comm.sendEvent(redraw())); - } - stripPrefix(path) { if (path.startsWith(this.props.prefix)) { return path.slice(this.props.prefix.length); @@ -177,8 +207,8 @@ export default class Browser extends React.Component // ...this.state.tests.map(id => this.comm.data[id] ? this.comm.data[id].input.length : 1) // ); let maxOutputLength = Math.max( - ...this.state.suggestions.map(id => this.comm.data[id] && this.comm.data[id].output ? this.comm.data[id].output.length : 1), - ...this.state.tests.map(id => this.comm.data[id] && this.comm.data[id].output ? this.comm.data[id].output.length : 1) + ...this.props.testTree.suggestions.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1), + ...this.props.testTree.tests.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1) ); let outputColumnWidth = "45%"; if (maxOutputLength < 25) { @@ -189,7 +219,7 @@ export default class Browser extends React.Component let maxSelectWidth = 40; - const inFillin = this.state.topic.startsWith("/Fill-ins"); + const inFillin = this.props.testTree.topic.startsWith("/Fill-ins"); // console.log("location.pathname", location.pathname); @@ -197,18 +227,18 @@ export default class Browser extends React.Component let totalFailures = {}; this.totalPassesObjects = {}; this.totalFailuresObjects = {}; - if (this.state.score_columns) { - for (const k of this.state.score_columns) { + if (this.props.testTree.score_columns) { + for (const k of this.props.testTree.score_columns) { // console.log("k", k) - totalPasses[k] = {this.totalPassesObjects[k] = el}} />; - totalFailures[k] = {this.totalFailuresObjects[k] = el}} />; + totalPasses[k] = {this.totalPassesObjects[k] = el}} />; + totalFailures[k] = {this.totalFailuresObjects[k] = el}} />; // console.log("totalPasses", totalPasses) } } let topicPath = ""; // console.log("tests.render4", this.state.tests, stripSlash(this.stripPrefix(this.props.location.pathname)), this.state.topic); - let breadCrumbParts = stripSlash(this.stripPrefix(this.state.topic)).split("/"); + let breadCrumbParts = stripSlash(this.stripPrefix(this.props.testTree.topic)).split("/"); // let totalPasses = this.totalPassesObj = el} />; // let totalFailures = this.totalFailuresObj = el} />; @@ -224,7 +254,7 @@ export default class Browser extends React.Component
- +
@@ -242,7 +272,7 @@ export default class Browser extends React.Component // name = decodeURIComponent(name); const out = {index > 0 && / } - + if (index !== 0) topicPath += "/"; topicPath += name; @@ -253,14 +283,14 @@ export default class Browser extends React.Component
- +
- {!this.state.read_only &&
this.suggestionsScrollWrapRef = el}> - {this.state.suggestions + {this.props.testTree.suggestions //.slice(this.state.suggestions_pos, this.state.suggestions_pos + this.state.max_suggestions) // .filter(id => { // //console.log("Math.max(...this.comm.data[id].scores.map(x => x[1]))", Math.max(...this.comm.data[id].scores.map(x => x[1]))) @@ -271,7 +301,7 @@ export default class Browser extends React.Component this.rows[id] = el} - topic={this.state.topic} + topic={this.props.testTree.topic} isSuggestion={true} // topicFilter={this.state.topicFilter} value1Filter={this.state.value1Filter} @@ -283,17 +313,17 @@ export default class Browser extends React.Component selected={this.state.selections[id]} soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onSelectToggle={this.toggleSelection} - comm={this.comm} - scoreFilter={this.state.do_score_filter && this.state.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 && this.state.score_filter} + comm={this.props.comm} + scoreFilter={this.state.do_score_filter && this.props.testTree.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 && this.props.testTree.score_filter} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} // inFillin={inFillin} scrollParent={this.suggestionsScrollWrapRef} giveUpSelection={this.removeSelection} - scoreColumns={this.state.score_columns} + scoreColumns={this.props.testTree.score_columns} // test_types={this.state.test_types} // test_type_parts={this.state.test_type_parts} - user={this.state.user} + user={this.props.testTree.user} outputColumnWidth={outputColumnWidth} /> @@ -308,22 +338,22 @@ export default class Browser extends React.Component
- {this.state.suggestions.length > 1 && + {this.props.testTree.suggestions.length > 1 &&
} - {!this.state.disable_suggestions && + {!this.props.testTree.disable_suggestions &&
-   Suggest  e.stopPropagation()} value={this.props.testTree.mode} onChange={this.changeMode} style={{fontWeight: "bold", color: "#555555", marginTop: "1px"}}> + {(this.props.testTree.mode_options || []).map((mode_option) => { return })} - {this.state.generator_options && this.state.generator_options.length > 1 && - e.stopPropagation()} value={this.props.testTree.active_generator} onChange={this.changeGenerator} style={{position: "absolute", color: "rgb(170, 170, 170)", marginTop: "1px", right: "13px"}}> + {this.props.testTree.generator_options.map((generator_option) => { return })} @@ -335,9 +365,9 @@ export default class Browser extends React.Component */}
} - {this.state.suggestions_error && + {this.props.testTree.suggestions_error &&
- {this.state.suggestions_error} + {this.props.testTree.suggestions_error}
} {/* {this.state.loading_suggestions && this.state.tests.length < 5 && @@ -370,15 +400,15 @@ export default class Browser extends React.Component
- {this.state.tests.length == 0 &&
+ {this.props.testTree.tests.length == 0 &&
This topic is empty. Click the plus (+) button to add a test.
} - {this.state.tests.map((id, index) => { + {this.props.testTree.tests.map((id, index) => { return this.rows[id] = el} - topic={this.state.topic} + topic={this.props.testTree.topic} hideBorder={index == 0} // topicFilter={this.state.topicFilter} value1Filter={this.state.value1Filter} @@ -396,23 +426,23 @@ export default class Browser extends React.Component this.totalFailuresObjects[k].setSubtotal(id, failures); } }} - comm={this.comm} + comm={this.props.comm} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} // inFillin={inFillin} scrollParent={document.body} // generateTopicName={this.generateTopicName} // setSelected={this.setSelected} - scoreColumns={this.state.score_columns} + scoreColumns={this.props.testTree.score_columns} // test_types={this.state.test_types} // test_type_parts={this.state.test_type_parts} - user={this.state.user} + user={this.props.testTree.user} outputColumnWidth={outputColumnWidth} /> })}
- {this.state.score_columns && this.state.score_columns.length > 1 && + {this.props.testTree.score_columns && this.props.testTree.score_columns.length > 1 &&
Input @@ -426,7 +456,7 @@ export default class Browser extends React.Component
Label
- {this.state.score_columns.map(k => { + {this.props.testTree.score_columns.map(k => { return this.clickModel(k, e)}> {k.replace(" score", "")} @@ -436,7 +466,7 @@ export default class Browser extends React.Component
- {this.state.score_columns && this.state.score_columns.map(k => { + {this.props.testTree.score_columns && this.props.testTree.score_columns.map(k => { return

@@ -469,8 +499,8 @@ export default class Browser extends React.Component } clickModel(modelName, e) { - if (modelName !== this.state.score_columns[0]) { - this.comm.sendEvent(setFirstModel(modelName)); + if (modelName !== this.props.testTree.score_columns[0]) { + this.props.comm.sendEvent(setFirstModel(modelName)); } } @@ -491,7 +521,7 @@ export default class Browser extends React.Component console.log("value2Editedcccccc", id, old_value, new_value, keys) if (keys.length > 1 && this.state.selections[id]) { for (const k in this.state.selections) { - console.log("K", k, this.comm.data[k].value2, id) + console.log("K", k, this.props.testTree.tests[k].value2, id) if (k !== id && this.rows[k].state.value2 === old_value) { // console.log("setting new value", new_value) this.rows[k].setValue2(new_value); @@ -510,7 +540,7 @@ export default class Browser extends React.Component removeSelection(id) { console.log("removeSelection", id) let newId = undefined; - const ids = this.state.suggestions.concat(this.state.tests); + const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); for (let i = 0; i < ids.length; i++) { console.log(i, ids[i], id); if (ids[i] === id) { @@ -536,13 +566,22 @@ export default class Browser extends React.Component } changeGenerator(e) { - this.comm.sendEvent(changeGenerator(e.target.value)); - this.setState({active_generator: e.target.value}) + this.props.comm.sendEvent(changeGenerator(e.target.value)).then((data) => { + if (data["status"] === "ok") { + this.props.dispatch(updateGenerator(e.target.value)); + } else { + // TODO: error handling + } + }); + // this.setState({active_generator: e.target.value}) } changeMode(e) { - this.comm.sendEvent(changeMode(e.target.value)); - this.setState({mode: e.target.value}); + this.props.comm.sendEvent(changeMode(e.target.value)).then(() => { + + }) + refreshBrowser(this.props.comm, this.props.dispatch); + // this.setState({mode: e.target.value}); } setLocation(pathname) { @@ -635,12 +674,12 @@ export default class Browser extends React.Component console.log("keyCodeXX", e.keyCode); if (e.keyCode == 8 || e.keyCode == 46 || e.keyCode == passKey || e.keyCode == failKey || e.keyCode == offTopicKey) { // backspace and delete and labeling keys const keys = Object.keys(this.state.selections); - const ids = this.state.suggestions.concat(this.state.tests); + const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); if (keys.length > 0) { let in_suggestions = true; for (const i in keys) { - if (!this.state.suggestions.includes(keys[i])) { + if (!this.props.testTree.suggestions.includes(keys[i])) { in_suggestions = false; } } @@ -671,7 +710,12 @@ export default class Browser extends React.Component } } } else { - this.comm.sendEvent(deleteTest(keys)); + this.props.comm.sendEvent(deleteTest(keys)).then((data) => { + refreshBrowser(this.props.comm, this.props.dispatch); + if (data["status"] !== "ok") { + // TODO: Error handling + } + }); } // select the next test after the selected one when appropriate @@ -691,7 +735,7 @@ export default class Browser extends React.Component } } else if (e.keyCode == 38 || e.keyCode == 40) { const keys = Object.keys(this.state.selections); - const ids = this.state.suggestions.concat(this.state.tests); + const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); if (keys.length == 1) { const currId = keys[0]; let lastId = undefined; @@ -708,10 +752,10 @@ export default class Browser extends React.Component } console.log(" arrow!", lastId, currId); } else if (keys.length === 0) { - if (this.state.suggestions.length > 1) { - newId = this.state.suggestions[0]; + if (this.props.testTree.suggestions.length > 1) { + newId = this.props.testTree.suggestions[0]; } else { - newId = this.state.tests[0]; + newId = this.props.testTree.tests[0]; } } console.log(" arrow!", keys, newId); @@ -744,7 +788,7 @@ export default class Browser extends React.Component let new_topic_name = "New topic"; let suffix = ""; let count = 0; - while (this.state.tests.includes(this.state.topic + "/" + new_topic_name + suffix)) { + while (this.props.testTree.tests.includes(this.props.testTree.topic + "/" + new_topic_name + suffix)) { count += 1; suffix = " " + count; } @@ -756,13 +800,17 @@ export default class Browser extends React.Component addNewTopic(e) { e.preventDefault(); e.stopPropagation(); - this.comm.sendEvent(addTopic()) + this.props.comm.sendEvent(addTopic()).then(() => { + refreshBrowser(this.props.comm, this.props.dispatch); + }); } addNewTest(e) { e.preventDefault(); e.stopPropagation(); - this.comm.sendEvent(addTest()) + this.props.comm.sendEvent(addTest()).then(() => { + refreshBrowser(this.props.comm, this.props.dispatch); + }); } // inputTopicDescription(text) { @@ -771,15 +819,24 @@ export default class Browser extends React.Component finishTopicDescription(text) { console.log("finishTopicDescription", text) - - this.setState({topic_description: text}); - this.comm.sendEvent(finishTopicDescription(this.state.topic_marker_id, text)); + this.props.comm.sendEvent(finishTopicDescription(this.props.testTree.topic_marker_id, text)).then((data) => { + if (data["status"] === "ok") { + this.props.dispatch(updateTopicDescription(text)); + } else { + // TODO: Error handling + } + }); } inputFilterText(text) { console.log("inputFilterText", text) - this.setState({filter_text: text}); - this.comm.sendEvent(changeFilter(text)); + this.props.comm.sendEvent(changeFilter(text)).then((data) => { + if (data["status"] === "ok") { + this.props.dispatch(updateFilterText(text)); + } else { + // TODO: Error handling + } + }); } // inputSuggestionsTemplate(text) { @@ -805,27 +862,36 @@ export default class Browser extends React.Component console.log("refreshSuggestions"); if (this.state.loading_suggestions) return; for (let k in Object.keys(this.state.selections)) { - if (this.state.suggestions.includes(k)) { + if (this.props.testTree.suggestions.includes(k)) { delete this.state.selections[k]; } } - this.setState({suggestions: [], loading_suggestions: true, suggestions_pos: 0, do_score_filter: true}); - this.comm.sendEvent(generateSuggestions({ + this.props.dispatch(updateSuggestions([])); + this.setState({loading_suggestions: true, suggestions_pos: 0, do_score_filter: true}); + this.props.comm.sendEvent(generateSuggestions({ value2_filter: this.state.value2Filter, value1_filter: this.state.value1Filter, comparator_filter: this.state.comparatorFilter, // suggestions_template_value1: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value1, // suggestions_template_comparator: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.comparator, // suggestions_template_value2: this.suggestionsTemplateRow && this.suggestionsTemplateRow.state.value2, checklist_mode: false //!!this.suggestionsTemplateRow - })) + })).then((data) => { + if (data["status"] === "ok") { + refreshBrowser(this.props.comm, this.props.dispatch); + } else { + // TODO: Error handling + } + }) } clearSuggestions(e) { e.preventDefault(); e.stopPropagation(); console.log("clearSuggestions"); - this.setState({suggestions_pos: 0, suggestions: []}); - this.comm.sendEvent(clearSuggestions()); + this.props.comm.sendEvent(clearSuggestions()).then((data) => { + this.props.dispatch(updateSuggestions([])); + this.setState({suggestions_pos: 0}); + }); } pageSuggestions(e, direction) { @@ -856,8 +922,8 @@ export default class Browser extends React.Component let selections = {}; let selecting = false; console.log("first_selection_id", first_selection_id) - for (let i = 0; i < this.state.suggestions.length; ++i) { - const curr_id = this.state.suggestions[i]; + for (let i = 0; i < this.props.testTree.suggestions.length; ++i) { + const curr_id = this.props.testTree.suggestions[i]; if (curr_id === id) { if (selecting) { selections[curr_id] = true; @@ -878,8 +944,8 @@ export default class Browser extends React.Component selections[curr_id] = true; } } - for (let i = 0; i < this.state.tests.length; ++i) { - const curr_id = this.state.tests[i]; + for (let i = 0; i < this.props.testTree.tests.length; ++i) { + const curr_id = this.props.testTree.tests[i]; if (curr_id === id) { if (selecting) { selections[curr_id] = true; @@ -938,12 +1004,12 @@ export default class Browser extends React.Component const id = e.dataTransfer.getData("id"); const topic_name = e.dataTransfer.getData("topic_name"); console.log("onSuggestionsDrop", e, id); - if (this.state.suggestions.indexOf(id) !== -1) return; // dropping a suggestion into suggestions should do nothing + if (this.props.testTree.suggestions.indexOf(id) !== -1) return; // dropping a suggestion into suggestions should do nothing this.setState({suggestionsDropHighlighted: 0}); if (topic_name !== null && topic_name !== "null") { - this.onDrop(id, this.state.topic + "/__suggestions__" + "/" + topic_name); + this.onDrop(id, this.props.testTree.topic + "/__suggestions__" + "/" + topic_name); } else { - this.onDrop(id, this.state.topic + "/__suggestions__"); + this.onDrop(id, this.props.testTree.topic + "/__suggestions__"); } } @@ -962,7 +1028,9 @@ export default class Browser extends React.Component // if (this.suggestionsTemplateRow) { // this.suggestionsTemplateRow.setState({value2: null}); // } - this.comm.sendEvent(changeTopic(stripSlash(topic).replaceAll(" ", "%20"))) + this.props.comm.sendEvent(changeTopic(stripSlash(topic).replaceAll(" ", "%20"))).then(() => { + refreshBrowser(this.props.comm, this.props.dispatch); + }); } } @@ -976,4 +1044,4 @@ function red_blue_color(value, min, max) { function stripSlash(str) { return str.endsWith('/') ? str.slice(0, -1) : str; -} \ No newline at end of file +} diff --git a/client/src/jupyter-comm.ts b/client/src/jupyter-comm.ts index c174785..0c6aaf5 100644 --- a/client/src/jupyter-comm.ts +++ b/client/src/jupyter-comm.ts @@ -1,6 +1,7 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; +import { AppDispatch } from './store'; export default class JupyterComm { interfaceId: string; @@ -11,7 +12,7 @@ export default class JupyterComm { debouncedSendPendingData500: () => void; debouncedSendPendingData1000: () => void; - constructor(interfaceId, onopen) { + constructor(interfaceId) { autoBind(this); this.interfaceId = interfaceId; this.callbackMap = {}; @@ -21,9 +22,6 @@ export default class JupyterComm { this.debouncedSendPendingData500 = debounce(this.sendPendingData, 500); this.debouncedSendPendingData1000 = debounce(this.sendPendingData, 1000); - if (onopen) { - defer(onopen); - } } send(keys, data) { diff --git a/client/src/row.tsx b/client/src/row.tsx index 313e0a4..65e78cf 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -9,14 +9,6 @@ import ContextMenu from './context-menu'; import JupyterComm from './jupyter-comm'; import WebSocketComm from './web-socket-comm' -import { - useQuery, - useMutation, - useQueryClient, - QueryClient, - QueryClientProvider, -} from '@tanstack/react-query' - interface RowProps { id: string; @@ -832,9 +824,7 @@ export default class Row extends React.Component { // const queryClient = new QueryClient(); export function RowFunctional(props: RowProps) { - const queryClient = useQueryClient(); const comm = props.comm; - const { isLoading, error, data, isFetching } = useQuery(['row'], () => comm.sendEvent(redraw()).then(data => data)); return ; } diff --git a/client/src/store.ts b/client/src/store.ts new file mode 100644 index 0000000..4d547b5 --- /dev/null +++ b/client/src/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit' +import testTreeReducer from './TestTreeSlice' + +export const store = configureStore({ + reducer: { + testTree: testTreeReducer + }, +}) + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch \ No newline at end of file diff --git a/client/src/types/index.d.ts b/client/src/types/index.d.ts index 7bff606..449d134 100644 --- a/client/src/types/index.d.ts +++ b/client/src/types/index.d.ts @@ -1,3 +1,6 @@ +import JupyterComm from "src/jupyter-comm"; +import WebSocketComm from "src/web-socket-comm"; + export {}; declare global { @@ -10,4 +13,6 @@ declare global { AdaTest: any; faTimes: any; } -} \ No newline at end of file +} + +export type Comm = JupyterComm | WebSocketComm; \ No newline at end of file diff --git a/client/src/web-socket-comm.ts b/client/src/web-socket-comm.ts index d864c58..e6669e1 100644 --- a/client/src/web-socket-comm.ts +++ b/client/src/web-socket-comm.ts @@ -1,7 +1,9 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; -import { CommEvent } from './CommEvent'; +import { CommEvent, redraw } from './CommEvent'; +import { AppDispatch } from './store'; +import { refresh } from './TestTreeSlice'; export default class WebSocketComm { interfaceId: string; @@ -13,13 +15,12 @@ export default class WebSocketComm { pendingData: {}; pendingResponses: {}; wcomm: WebSocket; - onOpen: typeof WebSocket.prototype.onopen; reconnectDelay: number; seqNumber: number; debouncedSendPendingData500: () => void; debouncedSendPendingData1000: () => void; - constructor(interfaceId, websocketServer, onOpen) { + constructor(interfaceId, websocketServer) { autoBind(this); this.interfaceId = interfaceId; this.websocketServer = websocketServer; @@ -27,14 +28,11 @@ export default class WebSocketComm { this.data = {}; this.pendingData = {}; this.pendingResponses = {}; - this.onOpen = onOpen; this.reconnectDelay = 100; this.seqNumber = 0; this.debouncedSendPendingData500 = debounce(this.sendPendingData, 500); this.debouncedSendPendingData1000 = debounce(this.sendPendingData, 1000); - - this.connect(); } send(keys, data) { @@ -76,10 +74,10 @@ export default class WebSocketComm { } } - connect() { + connect(onOpen: any) { let wsUri = (window.location.protocol=='https:' ? 'wss://' : 'ws://') + (this.websocketServer.startsWith("/") ? window.location.host : "") + this.websocketServer; this.wcomm = new WebSocket(wsUri); - this.wcomm.onopen = this.onOpen; + this.wcomm.onopen = onOpen; this.wcomm.onmessage = this.handleResponse; this.wcomm.onerror = this.onError; this.wcomm.onclose = this.onClose; From 74680dae796ff80e999df43318ea233c920c058a Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Mon, 24 Oct 2022 17:09:59 -0700 Subject: [PATCH 07/18] Fix null values & references --- adatest/_test_tree_browser.py | 3 +- client/src/adatest.tsx | 15 ++-- client/src/browser.tsx | 81 ++++++++++++-------- client/src/content-editable.tsx | 8 +- client/src/row.tsx | 132 ++++++++++++++++---------------- client/tsconfig.json | 1 + 6 files changed, 132 insertions(+), 108 deletions(-) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index ecb73bf..813e835 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -635,7 +635,8 @@ def sort_key(id): "active_generator": self.active_generator, "mode": self.mode, "mode_options": self.mode_options, - "test_tree_name": self.test_tree.name + "test_tree_name": self.test_tree.name, + "filter_text": self.filter_text # "test_types": test_types, # "test_type_parts": test_type_parts, } diff --git a/client/src/adatest.tsx b/client/src/adatest.tsx index 18a03c0..2afcfd6 100644 --- a/client/src/adatest.tsx +++ b/client/src/adatest.tsx @@ -29,8 +29,10 @@ export default class AdaTest extends React.Component constructor(props) { super(props); console.log("interfaceId", this.props.interfaceId) - this.state = { enabled: true }; window.adatest_root = this; + this.state = { + enabled: true + }; } render() { @@ -39,13 +41,16 @@ export default class AdaTest extends React.Component return (
-
+
{ /* @ts-ignore: JSX element type 'Router' does not have any construct or call signatures */ }
diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 1329cd9..7795ef5 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -17,18 +17,23 @@ import { AppDispatch } from './store'; import { Comm } from './types'; -interface BrowserProps { - testTree: TestTreeState; - comm: JupyterComm | WebSocketComm; - dispatch: AppDispatch; - location: any; - interfaceId: any; +interface BrowserBaseProps { + interfaceId: string; environment: string; websocket_server: string; - history: any; prefix: string; startingTopic: string; checklistMode: boolean; + // React Router withRouter() props + match: any; + location: any; + history: any; +} + +interface BrowserProps extends BrowserBaseProps { + testTree: TestTreeState; + comm: JupyterComm | WebSocketComm; + dispatch: AppDispatch; } interface BrowserState { @@ -49,8 +54,8 @@ interface BrowserState { experiment_locations: any[]; experiment: boolean; value2Filter: string; - test_types?: any[]; - test_type_parts?: any[]; + test_types: any[]; + test_type_parts: any[]; // score_columns?: any[]; // test_tree_name?: any; // topic_description?: string; @@ -67,16 +72,16 @@ interface BrowserState { // topic_marker_id?: string; } -function useComm(env: string, interfaceId: any, websocket_server: string=null) { +function useComm(env: string, interfaceId: any, websocket_server: string="") { console.log("pairs interfaceId", interfaceId) if (env === "jupyter") { return new JupyterComm(interfaceId); } else if (env === "web") { - if (websocket_server != null) { + if (websocket_server !== "") { return new WebSocketComm(interfaceId, websocket_server); } else { - console.error("websocket_server is null"); - throw new Error("websocket_server is null"); + console.error("websocket_server is not set"); + throw new Error("websocket_server is not set"); } } else { console.error("Unknown environment:", env); @@ -97,7 +102,7 @@ function refreshBrowser(comm: Comm, dispatch: AppDispatch) { // TestTreeBrowser function component wraps the legacy Browser // class component so we can use hooks. It is responsible for setting // up all the props for the Browser component. -export default function TestTreeBrowser(props: BrowserProps) { +export default function TestTreeBrowser(props: BrowserBaseProps) { const testTree = useSelector((state: TestTreeState) => state); const dispatch = useDispatch(); const comm = useComm(props.environment, props.interfaceId, props.websocket_server); @@ -126,8 +131,8 @@ export class Browser extends React.Component { rows: any; totalPassesObjects: {}; totalFailuresObjects: {}; - divRef: HTMLDivElement; - suggestionsScrollWrapRef: HTMLDivElement; + divRef: HTMLDivElement | null; + suggestionsScrollWrapRef: HTMLDivElement | null; constructor(props) { super(props); @@ -144,6 +149,8 @@ export class Browser extends React.Component { experiment_pos: 0, timerExpired: false, experiment_locations: [], + test_types: [], + test_type_parts: [], experiment: false, value2Filter: "" }; @@ -283,7 +290,7 @@ export class Browser extends React.Component {
- +
@@ -314,7 +321,7 @@ export class Browser extends React.Component { soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onSelectToggle={this.toggleSelection} comm={this.props.comm} - scoreFilter={this.state.do_score_filter && this.props.testTree.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 && this.props.testTree.score_filter} + scoreFilter={this.state.do_score_filter && this.props.testTree.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 ? this.props.testTree.score_filter : undefined} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} // inFillin={inFillin} @@ -499,7 +506,7 @@ export class Browser extends React.Component { } clickModel(modelName, e) { - if (modelName !== this.props.testTree.score_columns[0]) { + if (this.props.testTree.score_columns && modelName !== this.props.testTree.score_columns[0]) { this.props.comm.sendEvent(setFirstModel(modelName)); } } @@ -511,7 +518,7 @@ export class Browser extends React.Component { div.innerText = text; document.body.appendChild(div); var width = div.offsetWidth; - div.parentNode.removeChild(div); + div?.parentNode?.removeChild(div); return width; } @@ -559,7 +566,7 @@ export class Browser extends React.Component { // change our selection to the new id if (newId !== undefined) { console.log(newId); - let selections = {}; + let selections: any = {}; selections[newId] = true; this.setState({selections: selections}); } @@ -722,13 +729,13 @@ export class Browser extends React.Component { if (in_suggestions || e.keyCode == 8 || e.keyCode == 46) { let lastId = undefined; for (const i in ids) { - if (this.state.selections[lastId] !== undefined && this.state.selections[ids[i]] === undefined) { + if (lastId && this.state.selections[lastId] !== undefined && this.state.selections[ids[i]] === undefined) { newId = ids[i]; break; } lastId = ids[i]; } - let selections = {}; + let selections: any = {}; if (newId !== undefined) selections[newId] = true; this.setState({selections: selections}); } @@ -768,7 +775,7 @@ export class Browser extends React.Component { // change our selection to the new id if (newId !== undefined) { - let selections = {};//clone(this.state.selections); + let selections: any = {};//clone(this.state.selections); selections[newId] = true; this.setState({selections: selections}); } @@ -819,13 +826,17 @@ export class Browser extends React.Component { finishTopicDescription(text) { console.log("finishTopicDescription", text) - this.props.comm.sendEvent(finishTopicDescription(this.props.testTree.topic_marker_id, text)).then((data) => { - if (data["status"] === "ok") { - this.props.dispatch(updateTopicDescription(text)); - } else { - // TODO: Error handling - } - }); + if (this.props.testTree.topic_marker_id != null) { + this.props.comm.sendEvent(finishTopicDescription(this.props.testTree.topic_marker_id, text)).then((data) => { + if (data["status"] === "ok") { + this.props.dispatch(updateTopicDescription(text)); + } else { + // TODO: Error handling + } + }); + } else { + console.log("finishTopicDescription: no topic marker id"); + } } inputFilterText(text) { @@ -917,8 +928,10 @@ export class Browser extends React.Component { this.setState({selections: selections}); } else if (shiftKey) { const keys = Object.keys(this.state.selections); - let first_selection_id = null; - if (keys.length > 0) first_selection_id = keys[0]; + let first_selection_id = ""; + if (keys.length > 0) { + first_selection_id = keys[0]; + } let selections = {}; let selecting = false; console.log("first_selection_id", first_selection_id) @@ -1020,7 +1033,7 @@ export class Browser extends React.Component { ids = Object.keys(this.state.selections); this.setState({selections: {}}); } else ids = id; - this.comm.sendEvent(moveTest(ids, topic)); + this.props.comm.sendEvent(moveTest(ids, topic)); } goToTopic(topic) { diff --git a/client/src/content-editable.tsx b/client/src/content-editable.tsx index 4a4766d..bdc2420 100644 --- a/client/src/content-editable.tsx +++ b/client/src/content-editable.tsx @@ -208,8 +208,8 @@ function selectElement(element){ var selection = window.getSelection(); var range = document.createRange(); range.selectNodeContents(element); - selection.removeAllRanges(); - selection.addRange(range); + selection?.removeAllRanges(); + selection?.addRange(range); } } @@ -220,8 +220,8 @@ function setCaret(el, pos) { range.setStart(el, pos) range.collapse(true) - sel.removeAllRanges() - sel.addRange(range) + sel?.removeAllRanges() + sel?.addRange(range) } // @ts-ignore diff --git a/client/src/row.tsx b/client/src/row.tsx index 65e78cf..6018cd7 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -14,12 +14,12 @@ interface RowProps { id: string; soleSelected: boolean; forceRelayout: () => void; - scoreColumns: any[]; + scoreColumns?: any[]; updateTotals?: (id: string, passes: number, failures: number) => void; - scrollParent: HTMLElement; - value1Filter: string; - value2Filter: string; - comparatorFilter: string; + scrollParent: HTMLElement | null; + value1Filter?: string; + value2Filter?: string; + comparatorFilter?: string; scoreFilter?: number; selected: boolean; isSuggestion?: boolean; @@ -28,9 +28,9 @@ interface RowProps { outputColumnWidth: string; inputDefault?: string; outputDefault?: string; - giveUpSelection?: (id: string) => void; user: string; topic: string; + giveUpSelection?: (id: string) => void; onOpen?: (topic: string) => void; onSelectToggle: (id: string, shiftKey: any, metaKey: any) => void; onDrop?: (id: string, topic: string) => void; @@ -38,12 +38,12 @@ interface RowProps { interface RowState { type?: any; - scores: any[]; - label: string; - topic_name: string; - value1?: string; - comparator?: string; - value2?: string; + scores?: any[] | null; + label?: string; + topic_name?: string; + value1: string; + comparator: string; + value2: string; dropHighlighted: number; // used as a boolean dragging: boolean; // used anywhere? hovering: boolean; @@ -52,7 +52,6 @@ interface RowState { editing: boolean; labeler: string; display_parts: {}; - max_score_ind?: number; contextTop?: number; contextLeft?: number; contextOpen?: boolean; @@ -71,10 +70,10 @@ interface RowState { export default class Row extends React.Component { dataLoadActions: any[]; scrollToView: boolean; - divRef: HTMLDivElement; - topicNameEditable: ContentEditable; - inputEditable: ContentEditable; - outputEditable: ContentEditable; + divRef: HTMLDivElement | null; + topicNameEditable: ContentEditable | null; + inputEditable: ContentEditable | null; + outputEditable: ContentEditable | null; mouseDownTarget: HTMLElement; constructor(props) { @@ -83,11 +82,9 @@ export default class Row extends React.Component { this.state = { editing: false, - input: null, - output: null, - label: null, - labeler: null, - topic_name: null, + input: "", + output: "", + labeler: "anonymous", scores: null, dragging: false, dropHighlighted: 0, @@ -95,6 +92,9 @@ export default class Row extends React.Component { plusHovering: false, maxImageHeight: 100, display_parts: {}, + value1: "", + value2: "", + comparator: "" }; this.dataLoadActions = []; @@ -174,16 +174,16 @@ export default class Row extends React.Component { render() { // console.log("---- render Row ----"); - if (this.state.label === null) return null; // only render if we have data + if (this.state.label == null) return null; // only render if we have data // console.log("real render Row"); const main_score = this.props.scoreColumns ? this.props.scoreColumns[0] : undefined; // console.log("rendering row", this.props) // apply the value1/value2/topic filters - if (this.state.topic_name === null) { + if (this.state.topic_name == null) { if (this.props.value1Filter && this.state.value1 !== "") { const re = RegExp(this.props.value1Filter); - if (!re.test(this.state.value1)) return null; + if (!re.test(this.state.value1 ?? "")) return null; } if (this.props.comparatorFilter) { const re = RegExp(this.props.comparatorFilter); @@ -286,7 +286,7 @@ export default class Row extends React.Component { var label_opacity = this.state.labeler === "imputed" ? 0.5 : 1; // get the display parts for the template instantiation with the highest score - const display_parts = this.state.display_parts ? this.state.display_parts[this.state.max_score_ind] : {}; + // const display_parts = this.state.display_parts ? this.state.display_parts[this.state.max_score_ind] : {}; // console.log("overall_score[main_score]", overall_score[main_score], this.props.score_filter) if (this.props.scoreFilter && overall_score[main_score] < this.props.scoreFilter && this.props.scoreFilter > -1000) { @@ -307,17 +307,17 @@ export default class Row extends React.Component { style={this.props.hideBorder ? {} : {borderTop: "1px solid rgb(216, 222, 228)"}} tabIndex={0} onKeyDown={this.keyDownHandler}> - {this.state.topic_name !== null && !this.props.isSuggestion && + {this.state.topic_name != null && !this.props.isSuggestion &&
} - {this.props.isSuggestion && this.state.topic_name !== null && + {this.props.isSuggestion && this.state.topic_name != null &&
} - {/* {this.state.topic_name === null && + {/* {this.state.topic_name == null && @@ -325,7 +325,7 @@ export default class Row extends React.Component { } */}
- {this.state.topic_name !== null ? + {this.state.topic_name != null ?
this.topicNameEditable = el} text={decodeURIComponent(this.state.topic_name)} onInput={this.inputTopicName} onFinish={this.finishTopicName} editable={this.state.editing} /> @@ -380,9 +380,9 @@ export default class Row extends React.Component { )}
{/*
- {this.state.topic_name === null && !isNaN(score) && score.toFixed(3).replace(/\.?0*$/, '')} + {this.state.topic_name == null && !isNaN(score) && score.toFixed(3).replace(/\.?0*$/, '')}
*/} - {/* {this.state.topic_name === null && + {/* {this.state.topic_name == null && {this.state.labeler === "imputed" && this.state.label === "pass" ? @@ -407,11 +407,11 @@ export default class Row extends React.Component { {this.props.scoreColumns && this.props.scoreColumns.map(k => { let total_pass = 0; - if (this.state.topic_name !== null) { + if (this.state.topic_name != null && this.state.scores != null) { total_pass = this.state.scores[k].reduce((total, value) => total + (value[1] <= 0), 0); } let total_fail = 0; - if (this.state.topic_name !== null) { + if (this.state.topic_name != null && this.state.scores != null) { total_fail = this.state.scores[k].reduce((total, value) => total + (value[1] > 0), 0); } @@ -436,7 +436,7 @@ export default class Row extends React.Component { } - {this.state.topic_name === null && + {this.state.topic_name == null && {/* {this.state.label == "pass" && @@ -464,10 +464,10 @@ export default class Row extends React.Component { } - {this.state.topic_name !== null && total_pass > 0 && + {this.state.topic_name != null && total_pass > 0 && {total_pass} } - {this.state.topic_name !== null && total_fail > 0 && + {this.state.topic_name != null && total_fail > 0 && {total_fail} } @@ -516,11 +516,15 @@ export default class Row extends React.Component { if (e.keyCode == 13) { console.log("return!", this.props.soleSelected, this.props.selected) if (this.props.soleSelected) { - if (this.state.topic_name !== null) { + if (this.state.topic_name != null) { this.onOpen(e); } else if (this.props.isSuggestion) { this.addToCurrentTopic(e); - this.doOnNextDataLoad(() => this.props.giveUpSelection(this.props.id)); + this.doOnNextDataLoad(() => { + if (this.props.giveUpSelection != null) { + this.props.giveUpSelection(this.props.id) + } + }); } } } @@ -560,18 +564,18 @@ export default class Row extends React.Component { this.setState({label: label}); } - onScoreOver(e, key) { - this.setState({ - previewValue1: this.props.comm.data[key].value1, - previewValue2: this.props.comm.data[key].value2 - }) - } - onScoreOut(e, key) { - this.setState({ - previewValue1: null, - previewValue2: null - }) - } + // onScoreOver(e, key) { + // this.setState({ + // previewValue1: this.props.comm.data[key].value1, + // previewValue2: this.props.comm.data[key].value2 + // }) + // } + // onScoreOut(e, key) { + // this.setState({ + // previewValue1: null, + // previewValue2: null + // }) + // } toggleEditRow(e) { e.preventDefault(); @@ -580,10 +584,10 @@ export default class Row extends React.Component { if (!this.state.editing) { this.setState({editing: true}); console.log("about to edit focus") - if (this.state.topic_name === null) { - defer(() => this.inputEditable.focus()); + if (this.state.topic_name == null) { + defer(() => this.inputEditable?.focus()); } else { - defer(() => this.topicNameEditable.focus()); + defer(() => this.topicNameEditable?.focus()); } } else { this.setState({editing: false}); @@ -603,7 +607,7 @@ export default class Row extends React.Component { e.stopPropagation(); const newName = this.props.generateTopicName(); const newTopic = this.props.topic + "/" + newName; - if (this.state.topic_name === null) { + if (this.state.topic_name == null) { this.props.comm.send(this.props.id, {"topic": newTopic }); } else { this.props.comm.send(this.props.id, {"topic": newTopic + "/" + this.state.topic_name}); @@ -639,7 +643,7 @@ export default class Row extends React.Component { e.preventDefault(); e.stopPropagation(); console.log("this.state.topic_name XXXXXXXXXXXX", this.state.topic_name)//, "Row.onOpen(", e, ")"); - if (this.state.topic_name !== null && this.props.onOpen) { + if (this.state.topic_name != null && this.props.onOpen) { this.props.onOpen(this.props.topic + "/" + this.state.topic_name); } } @@ -708,7 +712,7 @@ export default class Row extends React.Component { console.log("topic editing", this.state.editing) e.preventDefault(); e.stopPropagation(); - defer(() => this.topicNameEditable.focus()); + defer(() => this.topicNameEditable?.focus()); } } @@ -725,7 +729,7 @@ export default class Row extends React.Component { console.log("value1 editing", this.state.editing) e.preventDefault(); e.stopPropagation(); - defer(() => this.inputEditable.focus()); + defer(() => this.inputEditable?.focus()); } } @@ -743,7 +747,7 @@ export default class Row extends React.Component { this.setState({editing: true}); e.preventDefault(); e.stopPropagation(); - defer(() => this.outputEditable.focus()); + defer(() => this.outputEditable?.focus()); } } @@ -778,7 +782,7 @@ export default class Row extends React.Component { console.log("enter", e.target) e.preventDefault(); e.stopPropagation(); - if (this.state.topic_name !== null) { + if (this.state.topic_name != null) { this.setState({dropHighlighted: this.state.dropHighlighted + 1}); } } @@ -787,7 +791,7 @@ export default class Row extends React.Component { console.log("leave", e.target) e.preventDefault(); e.stopPropagation(); - if (this.state.topic_name !== null) { + if (this.state.topic_name != null) { this.setState({dropHighlighted: this.state.dropHighlighted - 1}); } } @@ -796,10 +800,10 @@ export default class Row extends React.Component { const id = e.dataTransfer.getData("id"); const topic_name = e.dataTransfer.getData("topic_name"); - if (this.state.topic_name !== null) { + if (this.state.topic_name != null) { this.setState({dropHighlighted: 0}); if (this.props.onDrop && id !== this.props.id) { - if (topic_name !== null && topic_name !== "null") { + if (topic_name != null && topic_name !== "null") { this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name + "/" + topic_name); } else { this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name); @@ -812,7 +816,7 @@ export default class Row extends React.Component { e.preventDefault(); e.stopPropagation(); console.log("addToCurrentTopic X", this.props.topic, this.state.topic_name); - if (this.state.topic_name !== null) { + if (this.state.topic_name != null) { this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic + "/" + this.state.topic_name)); } else { this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic)); diff --git a/client/tsconfig.json b/client/tsconfig.json index 1ccc427..4a30ba5 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -8,5 +8,6 @@ "target": "es6", "jsx": "react", "baseUrl": ".", + "strictNullChecks": true } } \ No newline at end of file From cc0567f3f4d5473a2e0f57e9449717c765ef69d6 Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Wed, 26 Oct 2022 00:01:23 -0700 Subject: [PATCH 08/18] refresh_interface refactored, app initial render working --- adatest/_test_tree_browser.py | 23 +++--- client/src/TestTreeSlice.ts | 18 ++--- client/src/browser.tsx | 127 +++++++++++++++++++--------------- client/src/row.tsx | 84 ++++++++++++++-------- client/src/web-socket-comm.ts | 2 - 5 files changed, 147 insertions(+), 107 deletions(-) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index 813e835..314cffe 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -512,9 +512,6 @@ def _refresh_interface(self, sequence_number): """ Send our entire current state to the frontend interface. """ - # get the children of the current topic - data = {} - def create_children(data, tests, topic): children = [] @@ -592,18 +589,21 @@ def sort_key(id): return sorted_children # get the children of the current topic - children = create_children(data, self.test_tree, self.current_topic) - suggestions_children = create_children(data, self.test_tree, self.current_topic + "/__suggestions__") + data = {} + children_data = {} + suggestions_children_data = {} + children_ids = create_children(children_data, self.test_tree, self.current_topic) + suggestions_children_ids = create_children(suggestions_children_data, self.test_tree, self.current_topic + "/__suggestions__") # TODO: This is a complete hack to hide lower scoring suggestions when we are likely already in the exploit phase # this is just for users who don't know when to stop scrolling down... # SML: I expect we can delete this at some point? if self.score_filter == "auto": - if len(children) < 10: + if len(children_ids) < 10: score_filter = -1e12 else: - children_scores = sorted([np.max([score_max(x[1]) for x in data[key]['scores'][self.score_columns[0]]]) for key in children]) - suggestions_children_scores = sorted([np.max([score_max(x[1]) for x in data[key]['scores'][self.score_columns[0]]]) for key in suggestions_children]) + children_scores = sorted([np.max([score_max(x[1]) for x in data[key]['scores'][self.score_columns[0]]]) for key in children_ids]) + suggestions_children_scores = sorted([np.max([score_max(x[1]) for x in data[key]['scores'][self.score_columns[0]]]) for key in suggestions_children_ids]) score_filter = children_scores[-5] - (children_scores[-1] - children_scores[-5]) * 0.2 if len(suggestions_children_scores) > 0: score_filter = min(score_filter, np.nanmax(suggestions_children_scores) - 1e-2) @@ -620,8 +620,8 @@ def sort_key(id): topic_marker_id = self._get_topic_marker_id(self.current_topic) # compile the global browser state for the frontend data["browser"] = { - "suggestions": suggestions_children, - "tests": children, + "suggestions": suggestions_children_data, + "tests": children_data, "user": self.user, "topic": self.current_topic, "topic_description": self.test_tree.loc[topic_marker_id]["description"] if topic_marker_id is not None else "", @@ -641,7 +641,8 @@ def sort_key(id): # "test_type_parts": test_type_parts, } - self.send_response(sequence_number, data) + # self.send_response(sequence_number, data) + self.send_response(sequence_number, data["browser"]) def send_response(self, sequence_number: int, data: object = {}, status="ok"): self.comm.send({"sequence_number": sequence_number, "data": data, "status": status}) diff --git a/client/src/TestTreeSlice.ts b/client/src/TestTreeSlice.ts index 0d1e5e9..4d164e0 100644 --- a/client/src/TestTreeSlice.ts +++ b/client/src/TestTreeSlice.ts @@ -62,8 +62,8 @@ import type { PayloadAction } from '@reduxjs/toolkit' export interface TestTreeState { topic: string; - suggestions: any[]; - tests: any[]; + suggestions: Object; + tests: Object; user: string; score_filter: number; filter_text: string; @@ -89,28 +89,30 @@ const initialState: TestTreeState = { filter_text: "", } +// See https://redux-toolkit.js.org/usage/immer-reducers#immer-usage-patterns for explanation +// of how to use Immer to write reducers export const testTreeSlice = createSlice({ name: 'testTree', initialState, reducers: { refresh: (state, action: PayloadAction) => { - state = {...action.payload} + return action.payload; }, updateGenerator: (state, action: PayloadAction) => { - state = {...state, active_generator: action.payload} + state.active_generator = action.payload; }, updateTopicDescription: (state, action: PayloadAction) => { - state = {...state, topic_description: action.payload} + state.topic_description = action.payload; }, updateFilterText: (state, action: PayloadAction) => { - state = {...state, filter_text: action.payload} + state.filter_text = action.payload; }, - updateSuggestions: (state, action: PayloadAction) => { - state = {...state, suggestions: action.payload} + updateSuggestions: (state, action: PayloadAction) => { + state.suggestions = [...action.payload]; } }, }) diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 7795ef5..0252b98 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import autoBind from 'auto-bind'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPlus, faFolderPlus, faCheck, faTimes, faChevronDown, faRedo, faFilter } from '@fortawesome/free-solid-svg-icons' @@ -13,7 +13,7 @@ import ContentEditable from './content-editable'; import { TestTreeState, refresh, updateGenerator, updateTopicDescription, updateFilterText, updateSuggestions } from './TestTreeSlice'; import { useSelector, useDispatch } from 'react-redux' -import { AppDispatch } from './store'; +import { AppDispatch, RootState } from './store'; import { Comm } from './types'; @@ -72,6 +72,16 @@ interface BrowserState { // topic_marker_id?: string; } +function refreshBrowser(comm: Comm, dispatch: AppDispatch) { + return comm.sendEvent(redraw()).then((data) => { + if (data["status"] === "ok") { + dispatch(refresh(data["data"])); + } else { + // TODO: handle error + } + }); +} + function useComm(env: string, interfaceId: any, websocket_server: string="") { console.log("pairs interfaceId", interfaceId) if (env === "jupyter") { @@ -89,40 +99,41 @@ function useComm(env: string, interfaceId: any, websocket_server: string="") { } } -function refreshBrowser(comm: Comm, dispatch: AppDispatch) { - return comm.sendEvent(redraw()).then((data) => { - if (data["status"] === "ok") { - dispatch(refresh(data["data"])); - } else { - // TODO: handle error - } - }); -} - // TestTreeBrowser function component wraps the legacy Browser // class component so we can use hooks. It is responsible for setting // up all the props for the Browser component. export default function TestTreeBrowser(props: BrowserBaseProps) { - const testTree = useSelector((state: TestTreeState) => state); + const testTree = useSelector((state: RootState) => state.testTree); const dispatch = useDispatch(); - const comm = useComm(props.environment, props.interfaceId, props.websocket_server); - if (comm instanceof WebSocketComm) { - comm.connect(() => { - refreshBrowser(comm, dispatch); - // if we are in Jupyter we need to sync the URL in the MemoryRouter - // if (props.environment == "jupyter") { - // this.props.history.push(this.props.prefix + this.props.startingTopic); - // // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar - // } else if (props.location.pathname !== (props.prefix == "" ? "/" : props.prefix)) { - // defer(() => this.goToTopic(this.stripPrefix(this.props.location.pathname))); - // } - // props.history.listen(this.locationChanged); - }); - } + const [comm, setComm] = useState(null); + useEffect(() => { + const c = useComm(props.environment, props.interfaceId, props.websocket_server); + setComm(c); + if (c instanceof WebSocketComm) { + c.connect(() => { + refreshBrowser(c, dispatch); + // if we are in Jupyter we need to sync the URL in the MemoryRouter + // if (props.environment == "jupyter") { + // this.props.history.push(this.props.prefix + this.props.startingTopic); + // // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar + // } else if (props.location.pathname !== (props.prefix == "" ? "/" : props.prefix)) { + // defer(() => this.goToTopic(this.stripPrefix(this.props.location.pathname))); + // } + // props.history.listen(this.locationChanged); + }); + + // Return cleanup function + return () => { + c.wcomm.close(); + } + } + }, []) - return ( - - ) + if (comm == null) { + return
Loading...
+ } else { + return + } } @@ -201,6 +212,8 @@ export class Browser extends React.Component { render() { console.log("----- render AdaTest browser -----", ) + let testKeys = Object.keys(this.props.testTree.tests); + let suggestionKeys = Object.keys(this.props.testTree.suggestions); // compute the width of the largest type selection option (used to size the type column) let selectWidths = {}; for (const i in this.state.test_types) { @@ -214,8 +227,8 @@ export class Browser extends React.Component { // ...this.state.tests.map(id => this.comm.data[id] ? this.comm.data[id].input.length : 1) // ); let maxOutputLength = Math.max( - ...this.props.testTree.suggestions.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1), - ...this.props.testTree.tests.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1) + ...suggestionKeys.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1), + ...testKeys.map(id => this.props.testTree.tests[id] && this.props.testTree.tests[id].output ? this.props.testTree.tests[id].output.length : 1) ); let outputColumnWidth = "45%"; if (maxOutputLength < 25) { @@ -237,8 +250,8 @@ export class Browser extends React.Component { if (this.props.testTree.score_columns) { for (const k of this.props.testTree.score_columns) { // console.log("k", k) - totalPasses[k] = {this.totalPassesObjects[k] = el}} />; - totalFailures[k] = {this.totalFailuresObjects[k] = el}} />; + totalPasses[k] = {this.totalPassesObjects[k] = el}} />; + totalFailures[k] = {this.totalFailuresObjects[k] = el}} />; // console.log("totalPasses", totalPasses) } } @@ -297,7 +310,7 @@ export class Browser extends React.Component { {!this.props.testTree.read_only &&
this.suggestionsScrollWrapRef = el}> - {this.props.testTree.suggestions + {suggestionKeys //.slice(this.state.suggestions_pos, this.state.suggestions_pos + this.state.max_suggestions) // .filter(id => { // //console.log("Math.max(...this.comm.data[id].scores.map(x => x[1]))", Math.max(...this.comm.data[id].scores.map(x => x[1]))) @@ -321,7 +334,7 @@ export class Browser extends React.Component { soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onSelectToggle={this.toggleSelection} comm={this.props.comm} - scoreFilter={this.state.do_score_filter && this.props.testTree.suggestions.length > this.state.max_suggestions && index > this.state.max_suggestions-4 ? this.props.testTree.score_filter : undefined} + scoreFilter={this.state.do_score_filter && suggestionKeys.length > this.state.max_suggestions && index > this.state.max_suggestions-4 ? this.props.testTree.score_filter : undefined} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} // inFillin={inFillin} @@ -345,7 +358,7 @@ export class Browser extends React.Component {
- {this.props.testTree.suggestions.length > 1 && + {suggestionKeys.length > 1 &&
@@ -407,10 +420,10 @@ export class Browser extends React.Component {
- {this.props.testTree.tests.length == 0 &&
+ {testKeys.length == 0 &&
This topic is empty. Click the plus (+) button to add a test.
} - {this.props.testTree.tests.map((id, index) => { + {testKeys.map((id, index) => { return { removeSelection(id) { console.log("removeSelection", id) - let newId = undefined; - const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); + let newId: string | null = null; + const ids = Object.keys(this.props.testTree.suggestions).concat(Object.keys(this.props.testTree.tests)); for (let i = 0; i < ids.length; i++) { console.log(i, ids[i], id); if (ids[i] === id) { @@ -564,7 +577,7 @@ export class Browser extends React.Component { } // change our selection to the new id - if (newId !== undefined) { + if (newId != null) { console.log(newId); let selections: any = {}; selections[newId] = true; @@ -674,19 +687,19 @@ export class Browser extends React.Component { } keyDownHandler(e) { - let newId = undefined; + let newId: string | null = null; const passKey = 86; const failKey = 66; const offTopicKey = 67; console.log("keyCodeXX", e.keyCode); if (e.keyCode == 8 || e.keyCode == 46 || e.keyCode == passKey || e.keyCode == failKey || e.keyCode == offTopicKey) { // backspace and delete and labeling keys const keys = Object.keys(this.state.selections); - const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); + const ids = Object.keys(this.props.testTree.suggestions).concat(Object.keys(this.props.testTree.tests)); if (keys.length > 0) { let in_suggestions = true; for (const i in keys) { - if (!this.props.testTree.suggestions.includes(keys[i])) { + if (!Object.keys(this.props.testTree.suggestions).includes(keys[i])) { in_suggestions = false; } } @@ -727,7 +740,7 @@ export class Browser extends React.Component { // select the next test after the selected one when appropriate if (in_suggestions || e.keyCode == 8 || e.keyCode == 46) { - let lastId = undefined; + let lastId: string | null = null; for (const i in ids) { if (lastId && this.state.selections[lastId] !== undefined && this.state.selections[ids[i]] === undefined) { newId = ids[i]; @@ -736,16 +749,18 @@ export class Browser extends React.Component { lastId = ids[i]; } let selections: any = {}; - if (newId !== undefined) selections[newId] = true; + if (newId != null) { + selections[newId] = true; + } this.setState({selections: selections}); } } } else if (e.keyCode == 38 || e.keyCode == 40) { const keys = Object.keys(this.state.selections); - const ids = this.props.testTree.suggestions.concat(this.props.testTree.tests); + const ids = Object.keys(this.props.testTree.suggestions).concat(Object.keys(this.props.testTree.tests)); if (keys.length == 1) { const currId = keys[0]; - let lastId = undefined; + let lastId: string | null = null; for (const i in ids) { if (e.keyCode == 38 && ids[i] === currId) { // up arrow newId = lastId; @@ -759,7 +774,7 @@ export class Browser extends React.Component { } console.log(" arrow!", lastId, currId); } else if (keys.length === 0) { - if (this.props.testTree.suggestions.length > 1) { + if (Object.keys(this.props.testTree.suggestions).length > 1) { newId = this.props.testTree.suggestions[0]; } else { newId = this.props.testTree.tests[0]; @@ -774,7 +789,7 @@ export class Browser extends React.Component { e.stopPropagation(); // change our selection to the new id - if (newId !== undefined) { + if (newId != null) { let selections: any = {};//clone(this.state.selections); selections[newId] = true; this.setState({selections: selections}); @@ -795,7 +810,7 @@ export class Browser extends React.Component { let new_topic_name = "New topic"; let suffix = ""; let count = 0; - while (this.props.testTree.tests.includes(this.props.testTree.topic + "/" + new_topic_name + suffix)) { + while (Object.keys(this.props.testTree.tests).includes(this.props.testTree.topic + "/" + new_topic_name + suffix)) { count += 1; suffix = " " + count; } @@ -873,7 +888,7 @@ export class Browser extends React.Component { console.log("refreshSuggestions"); if (this.state.loading_suggestions) return; for (let k in Object.keys(this.state.selections)) { - if (this.props.testTree.suggestions.includes(k)) { + if (Object.keys(this.props.testTree.suggestions).includes(k)) { delete this.state.selections[k]; } } @@ -935,7 +950,7 @@ export class Browser extends React.Component { let selections = {}; let selecting = false; console.log("first_selection_id", first_selection_id) - for (let i = 0; i < this.props.testTree.suggestions.length; ++i) { + for (let i = 0; i < Object.keys(this.props.testTree.suggestions).length; ++i) { const curr_id = this.props.testTree.suggestions[i]; if (curr_id === id) { if (selecting) { @@ -957,7 +972,7 @@ export class Browser extends React.Component { selections[curr_id] = true; } } - for (let i = 0; i < this.props.testTree.tests.length; ++i) { + for (let i = 0; i < Object.keys(this.props.testTree.tests).length; ++i) { const curr_id = this.props.testTree.tests[i]; if (curr_id === id) { if (selecting) { @@ -1017,7 +1032,7 @@ export class Browser extends React.Component { const id = e.dataTransfer.getData("id"); const topic_name = e.dataTransfer.getData("topic_name"); console.log("onSuggestionsDrop", e, id); - if (this.props.testTree.suggestions.indexOf(id) !== -1) return; // dropping a suggestion into suggestions should do nothing + if (Object.keys(this.props.testTree.suggestions).indexOf(id) !== -1) return; // dropping a suggestion into suggestions should do nothing this.setState({suggestionsDropHighlighted: 0}); if (topic_name !== null && topic_name !== "null") { this.onDrop(id, this.props.testTree.topic + "/__suggestions__" + "/" + topic_name); diff --git a/client/src/row.tsx b/client/src/row.tsx index 6018cd7..448d94f 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -8,9 +8,11 @@ import ContentEditable from './content-editable'; import ContextMenu from './context-menu'; import JupyterComm from './jupyter-comm'; import WebSocketComm from './web-socket-comm' +import { useSelector } from 'react-redux'; +import { RootState } from './store'; -interface RowProps { +interface RowBaseProps { id: string; soleSelected: boolean; forceRelayout: () => void; @@ -36,14 +38,17 @@ interface RowProps { onDrop?: (id: string, topic: string) => void; } +interface RowProps extends RowBaseProps { + // The test / topic / suggestion data + data: any; +} + interface RowState { type?: any; scores?: any[] | null; label?: string; topic_name?: string; - value1: string; comparator: string; - value2: string; dropHighlighted: number; // used as a boolean dragging: boolean; // used anywhere? hovering: boolean; @@ -51,6 +56,8 @@ interface RowState { hidden?: boolean; editing: boolean; labeler: string; + value1: string; + value2: string; display_parts: {}; contextTop?: number; contextLeft?: number; @@ -67,7 +74,31 @@ interface RowState { } -export default class Row extends React.Component { +const Row = React.forwardRef((props: RowBaseProps, ref: React.LegacyRef) => { + const testTree = useSelector((state: RootState) => state.testTree); + let data: any = null; + if (props.isSuggestion) { + if (Object.keys(testTree.suggestions).includes(props.id)) { + data = testTree.suggestions[props.id]; + } else { + console.error("Could not find suggestions in TestTree store for id", props.id); + return null; + } + } else { + if (Object.keys(testTree.tests).includes(props.id)) { + data = testTree.tests[props.id]; + } else { + console.error("Could not find tests in TestTree store for id", props.id); + return null; + } + } + + return +}); + +export default Row; + +export class RowInternal extends React.Component { dataLoadActions: any[]; scrollToView: boolean; divRef: HTMLDivElement | null; @@ -94,7 +125,8 @@ export default class Row extends React.Component { display_parts: {}, value1: "", value2: "", - comparator: "" + comparator: "", + ...props.data }; this.dataLoadActions = []; @@ -105,24 +137,24 @@ export default class Row extends React.Component { window.faTimes = faTimes; } - dataLoaded(state) { - if (state == undefined) return; - - if (this.dataLoadActions.length > 0) { - for (let i = 0; i < this.dataLoadActions.length; i++) { - this.dataLoadActions[i](); - } - this.dataLoadActions = []; - } - // console.log("state.topic_name", state.topic_name) - // we automatically start editing topics that are selected and have an imputed name - if (state.topic_name && (state.topic_name.startsWith("New topic") || state.value1 === "New test") && this.props.soleSelected) { - state["editing"] = true; - console.log("setting editing state to true!") - } + // dataLoaded(state) { + // if (state == undefined) return; + + // if (this.dataLoadActions.length > 0) { + // for (let i = 0; i < this.dataLoadActions.length; i++) { + // this.dataLoadActions[i](); + // } + // this.dataLoadActions = []; + // } + // // console.log("state.topic_name", state.topic_name) + // // we automatically start editing topics that are selected and have an imputed name + // if (state.topic_name && (state.topic_name.startsWith("New topic") || state.value1 === "New test") && this.props.soleSelected) { + // state["editing"] = true; + // console.log("setting editing state to true!") + // } - this.setState(state); - } + // this.setState(state); + // } UNSAFE_componentWillUpdate(nextProps, nextState) { @@ -825,14 +857,6 @@ export default class Row extends React.Component { } -// const queryClient = new QueryClient(); - -export function RowFunctional(props: RowProps) { - const comm = props.comm; - return ; -} - - // https://stackoverflow.com/questions/45408920/plain-javascript-scrollintoview-inside-div function scrollParentToChild(parent, child) { console.log("scrollParentToChild", parent, child) diff --git a/client/src/web-socket-comm.ts b/client/src/web-socket-comm.ts index e6669e1..63becac 100644 --- a/client/src/web-socket-comm.ts +++ b/client/src/web-socket-comm.ts @@ -2,8 +2,6 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; import { CommEvent, redraw } from './CommEvent'; -import { AppDispatch } from './store'; -import { refresh } from './TestTreeSlice'; export default class WebSocketComm { interfaceId: string; From c9b114d540ba95262e8b02447d9ac56ac43dcb1c Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Wed, 26 Oct 2022 02:34:21 -0700 Subject: [PATCH 09/18] Small fix for changeMode --- client/src/browser.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 0252b98..8d7c666 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -598,9 +598,8 @@ export class Browser extends React.Component { changeMode(e) { this.props.comm.sendEvent(changeMode(e.target.value)).then(() => { - + refreshBrowser(this.props.comm, this.props.dispatch); }) - refreshBrowser(this.props.comm, this.props.dispatch); // this.setState({mode: e.target.value}); } From a0f8f46e7aea57d2c4c7df1071fb50838d68a533 Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Fri, 28 Oct 2022 18:06:08 -0700 Subject: [PATCH 10/18] restructuring data and fixing regressions 1 --- adatest/_test_tree_browser.py | 4 ++ client/src/browser.tsx | 100 ++++++++++------------------------ client/src/jupyter-comm.ts | 38 ++++++------- client/src/row.tsx | 88 ++++++++++++++---------------- client/src/utils.ts | 53 ++++++++++++++++++ client/src/web-socket-comm.ts | 35 +++++------- 6 files changed, 160 insertions(+), 158 deletions(-) create mode 100644 client/src/utils.ts diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index 314cffe..ee7d318 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -594,6 +594,10 @@ def sort_key(id): suggestions_children_data = {} children_ids = create_children(children_data, self.test_tree, self.current_topic) suggestions_children_ids = create_children(suggestions_children_data, self.test_tree, self.current_topic + "/__suggestions__") + + # Filter data for frontend so we only return direct children data + children_data = {k: children_data[k] for k in children_ids} + suggestions_children_data = {k: suggestions_children_data[k] for k in suggestions_children_ids} # TODO: This is a complete hack to hide lower scoring suggestions when we are likely already in the exploit phase # this is just for users who don't know when to stop scrolling down... diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 8d7c666..579cb03 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -15,6 +15,7 @@ import { TestTreeState, refresh, updateGenerator, updateTopicDescription, update import { useSelector, useDispatch } from 'react-redux' import { AppDispatch, RootState } from './store'; import { Comm } from './types'; +import { goToTopic, refreshBrowser, stripPrefix, stripSlash, useComm } from './utils'; interface BrowserBaseProps { @@ -72,32 +73,6 @@ interface BrowserState { // topic_marker_id?: string; } -function refreshBrowser(comm: Comm, dispatch: AppDispatch) { - return comm.sendEvent(redraw()).then((data) => { - if (data["status"] === "ok") { - dispatch(refresh(data["data"])); - } else { - // TODO: handle error - } - }); -} - -function useComm(env: string, interfaceId: any, websocket_server: string="") { - console.log("pairs interfaceId", interfaceId) - if (env === "jupyter") { - return new JupyterComm(interfaceId); - } else if (env === "web") { - if (websocket_server !== "") { - return new WebSocketComm(interfaceId, websocket_server); - } else { - console.error("websocket_server is not set"); - throw new Error("websocket_server is not set"); - } - } else { - console.error("Unknown environment:", env); - throw new Error(`Unknown environment: ${env}`); - } -} // TestTreeBrowser function component wraps the legacy Browser // class component so we can use hooks. It is responsible for setting @@ -111,15 +86,16 @@ export default function TestTreeBrowser(props: BrowserBaseProps) { setComm(c); if (c instanceof WebSocketComm) { c.connect(() => { - refreshBrowser(c, dispatch); // if we are in Jupyter we need to sync the URL in the MemoryRouter - // if (props.environment == "jupyter") { - // this.props.history.push(this.props.prefix + this.props.startingTopic); - // // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar - // } else if (props.location.pathname !== (props.prefix == "" ? "/" : props.prefix)) { - // defer(() => this.goToTopic(this.stripPrefix(this.props.location.pathname))); - // } - // props.history.listen(this.locationChanged); + if (props.environment == "jupyter") { + props.history.push(this.props.prefix + this.props.startingTopic); + refreshBrowser(c, dispatch); + // if we don't have a starting topic then we are stand-alone and need to sync our state with the address bar + } else if (props.location.pathname !== (props.prefix == "" ? "/" : props.prefix)) { + goToTopic(stripPrefix(props.location.pathname, props.prefix), c).then(() => refreshBrowser(c, dispatch)); + } else { + refreshBrowser(c, dispatch); + } }); // Return cleanup function @@ -178,6 +154,8 @@ export class Browser extends React.Component { // @ts-ignore: Property does not exist on type 'Window' window.pair_chart = this; + + props.history.listen(this.locationChanged); } debouncedForceUpdate() { @@ -185,14 +163,6 @@ export class Browser extends React.Component { this.forceUpdate(); } - stripPrefix(path) { - if (path.startsWith(this.props.prefix)) { - return path.slice(this.props.prefix.length); - } else { - return path; - } - } - componentDidUpdate() { this.componentDidUpdateOrMount(false); } @@ -258,7 +228,7 @@ export class Browser extends React.Component { let topicPath = ""; // console.log("tests.render4", this.state.tests, stripSlash(this.stripPrefix(this.props.location.pathname)), this.state.topic); - let breadCrumbParts = stripSlash(this.stripPrefix(this.props.testTree.topic)).split("/"); + let breadCrumbParts = stripSlash(stripPrefix(this.props.testTree.topic, this.props.prefix)).split("/"); // let totalPasses = this.totalPassesObj = el} />; // let totalFailures = this.totalFailuresObj = el} />; @@ -334,6 +304,7 @@ export class Browser extends React.Component { soleSelected={this.state.selections[id] && Object.keys(this.state.selections).length == 1} onSelectToggle={this.toggleSelection} comm={this.props.comm} + dispatch={this.props.dispatch} scoreFilter={this.state.do_score_filter && suggestionKeys.length > this.state.max_suggestions && index > this.state.max_suggestions-4 ? this.props.testTree.score_filter : undefined} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} @@ -447,6 +418,7 @@ export class Browser extends React.Component { } }} comm={this.props.comm} + dispatch={this.props.dispatch} // selectWidth={maxSelectWidth} forceRelayout={this.debouncedForceUpdate} // inFillin={inFillin} @@ -520,7 +492,8 @@ export class Browser extends React.Component { clickModel(modelName, e) { if (this.props.testTree.score_columns && modelName !== this.props.testTree.score_columns[0]) { - this.props.comm.sendEvent(setFirstModel(modelName)); + this.props.comm.sendEvent(setFirstModel(modelName)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } } @@ -597,21 +570,23 @@ export class Browser extends React.Component { } changeMode(e) { - this.props.comm.sendEvent(changeMode(e.target.value)).then(() => { - refreshBrowser(this.props.comm, this.props.dispatch); - }) + this.props.comm.sendEvent(changeMode(e.target.value)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)) // this.setState({mode: e.target.value}); } setLocation(pathname) { console.log("setLocation", pathname); this.props.history.push(this.props.prefix + pathname); - this.setState({selections: {}}); + // this.setState({selections: {}}); } locationChanged(location, action) { console.log("locationChanged", location, action); - this.goToTopic(this.stripPrefix(location.pathname)); + this.setState({selections: {}}); + goToTopic(stripPrefix(location.pathname, this.props.prefix), this.props.comm).then(() => { + refreshBrowser(this.props.comm, this.props.dispatch); + }); } newData(data) { @@ -821,17 +796,15 @@ export class Browser extends React.Component { addNewTopic(e) { e.preventDefault(); e.stopPropagation(); - this.props.comm.sendEvent(addTopic()).then(() => { - refreshBrowser(this.props.comm, this.props.dispatch); - }); + this.props.comm.sendEvent(addTopic()) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } addNewTest(e) { e.preventDefault(); e.stopPropagation(); - this.props.comm.sendEvent(addTest()).then(() => { - refreshBrowser(this.props.comm, this.props.dispatch); - }); + this.props.comm.sendEvent(addTest()) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } // inputTopicDescription(text) { @@ -1047,17 +1020,8 @@ export class Browser extends React.Component { ids = Object.keys(this.state.selections); this.setState({selections: {}}); } else ids = id; - this.props.comm.sendEvent(moveTest(ids, topic)); - } - - goToTopic(topic) { - console.log("goToTopic", topic); - // if (this.suggestionsTemplateRow) { - // this.suggestionsTemplateRow.setState({value2: null}); - // } - this.props.comm.sendEvent(changeTopic(stripSlash(topic).replaceAll(" ", "%20"))).then(() => { - refreshBrowser(this.props.comm, this.props.dispatch); - }); + this.props.comm.sendEvent(moveTest(ids, topic)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } } @@ -1068,7 +1032,3 @@ const red_blue_100 = ["rgb(0.0, 199.0, 100.0)", "rgb(7.68, 199.88, 96.12)", "rgb function red_blue_color(value, min, max) { return red_blue_100[Math.floor(99.9999 * (value - min)/(max - min))] } - -function stripSlash(str) { - return str.endsWith('/') ? str.slice(0, -1) : str; -} diff --git a/client/src/jupyter-comm.ts b/client/src/jupyter-comm.ts index 0c6aaf5..af4632d 100644 --- a/client/src/jupyter-comm.ts +++ b/client/src/jupyter-comm.ts @@ -1,7 +1,6 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; -import { AppDispatch } from './store'; export default class JupyterComm { interfaceId: string; @@ -9,8 +8,6 @@ export default class JupyterComm { data: any; pendingData: any; jcomm: InnerJupyterComm; - debouncedSendPendingData500: () => void; - debouncedSendPendingData1000: () => void; constructor(interfaceId) { autoBind(this); @@ -19,9 +16,6 @@ export default class JupyterComm { this.data = {}; this.pendingData = {}; this.jcomm = new InnerJupyterComm('adatest_interface_target_'+this.interfaceId, this.updateData); - - this.debouncedSendPendingData500 = debounce(this.sendPendingData, 500); - this.debouncedSendPendingData1000 = debounce(this.sendPendingData, 1000); } send(keys, data) { @@ -38,22 +32,22 @@ export default class JupyterComm { return Promise.resolve(); } - debouncedSendEvent500(commEvent) { - for (const k of Object.keys(commEvent)) { - this.addPendingData(k, commEvent[k]); - } - this.debouncedSendPendingData500(); - } - - debouncedSend500(keys, data) { - this.addPendingData(keys, data); - this.debouncedSendPendingData500(); - } - - debouncedSend1000(keys, data) { - this.addPendingData(keys, data); - this.debouncedSendPendingData1000(); - } + // debouncedSendEvent500(commEvent) { + // for (const k of Object.keys(commEvent)) { + // this.addPendingData(k, commEvent[k]); + // } + // this.debouncedSendPendingData500(); + // } + + // debouncedSend500(keys, data) { + // this.addPendingData(keys, data); + // this.debouncedSendPendingData500(); + // } + + // debouncedSend1000(keys, data) { + // this.addPendingData(keys, data); + // this.debouncedSendPendingData1000(); + // } addPendingData(keys, data) { diff --git a/client/src/row.tsx b/client/src/row.tsx index 448d94f..df71c9b 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -2,14 +2,15 @@ import React from 'react'; import autoBind from 'auto-bind'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPlus, faCheck, faBan, faFolderMinus, faArrowRight, faTimes, faFolderPlus, faFolder} from '@fortawesome/free-solid-svg-icons' -import { defer } from 'lodash'; +import { defer, debounce } from 'lodash'; import { changeInput, changeLabel, changeOutput, deleteTest, moveTest, redraw } from './CommEvent'; import ContentEditable from './content-editable'; import ContextMenu from './context-menu'; import JupyterComm from './jupyter-comm'; import WebSocketComm from './web-socket-comm' import { useSelector } from 'react-redux'; -import { RootState } from './store'; +import { AppDispatch, RootState } from './store'; +import { refreshBrowser } from './utils'; interface RowBaseProps { @@ -26,6 +27,7 @@ interface RowBaseProps { selected: boolean; isSuggestion?: boolean; comm: JupyterComm | WebSocketComm; + dispatch: AppDispatch; hideBorder?: boolean; outputColumnWidth: string; inputDefault?: string; @@ -40,12 +42,11 @@ interface RowBaseProps { interface RowProps extends RowBaseProps { // The test / topic / suggestion data - data: any; + rowData: any; } interface RowState { type?: any; - scores?: any[] | null; label?: string; topic_name?: string; comparator: string; @@ -55,7 +56,6 @@ interface RowState { plusHovering: boolean; hidden?: boolean; editing: boolean; - labeler: string; value1: string; value2: string; display_parts: {}; @@ -67,8 +67,6 @@ interface RowState { previewValue1?: boolean; previewValue2?: boolean; prefix?: string; - input: string; - output: string; maxImageHeight: number; contextFocus?: string; } @@ -93,7 +91,7 @@ const Row = React.forwardRef((props: RowBaseProps, ref: React.LegacyRef + return }); export default Row; @@ -183,11 +181,11 @@ export class RowInternal extends React.Component { // update any listeners for score totals if (this.props.scoreColumns) { for (const k of this.props.scoreColumns) { - if (this.state.scores && this.props.updateTotals) { + if (this.props.rowData.scores && this.props.updateTotals) { // console.log("this.props.updateTotals", k, this.state.scores[k]) this.props.updateTotals(k, - this.state.scores[k].reduce((total, value) => total + (value[1] <= 0), 0), - this.state.scores[k].reduce((total, value) => total + (value[1] > 0), 0) + this.props.rowData.scores[k].reduce((total, value) => total + (value[1] <= 0), 0), + this.props.rowData.scores[k].reduce((total, value) => total + (value[1] > 0), 0) ); } } @@ -291,9 +289,9 @@ export class RowInternal extends React.Component { // const test_type_parts = this.props.test_type_parts[this.state.type]; let overall_score = {}; - if (this.state.scores) { - for (let k in this.state.scores) { - const arr = this.state.scores[k].filter(x => Number.isFinite(x[1])).map(x => x[1]) + if (this.props.rowData.scores) { + for (let k in this.props.rowData.scores) { + const arr = this.props.rowData.scores[k].filter(x => Number.isFinite(x[1])).map(x => x[1]) overall_score[k] = arr.reduce((a, b) => a + b, 0) / arr.length; } } else { @@ -315,7 +313,7 @@ export class RowInternal extends React.Component { // console.log("heresss65", this.state["value1_outputs"], Object.keys(tmp)); // var hack_output_name = Object.keys(tmp).reduce((a, b) => tmp[a] > tmp[b] ? a : b); // } - var label_opacity = this.state.labeler === "imputed" ? 0.5 : 1; + var label_opacity = this.props.rowData.labeler === "imputed" ? 0.5 : 1; // get the display parts for the template instantiation with the highest score // const display_parts = this.state.display_parts ? this.state.display_parts[this.state.max_score_ind] : {}; @@ -387,10 +385,10 @@ export class RowInternal extends React.Component {
{/* */} - {this.state.input.startsWith("__IMAGE=") ? - + {this.props.rowData.input.startsWith("__IMAGE=") ? + : - this.inputEditable = el} text={this.state.input} onInput={this.inputInput} onFinish={this.finishInput} editable={this.state.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> + this.inputEditable = el} text={this.props.rowData.input} onInput={this.inputInput} onFinish={this.finishInput} editable={this.state.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> } {/* */} @@ -403,7 +401,7 @@ export class RowInternal extends React.Component { - this.outputEditable = el} text={this.state.output} onInput={this.inputOutput} onFinish={_ => this.setState({editing: false})} editable={this.state.editing} defaultText={this.props.outputDefault} /> + this.outputEditable = el} text={this.props.rowData.output} onInput={this.inputOutput} onFinish={_ => this.setState({editing: false})} editable={this.state.editing} defaultText={this.props.outputDefault} /> @@ -439,12 +437,12 @@ export class RowInternal extends React.Component { {this.props.scoreColumns && this.props.scoreColumns.map(k => { let total_pass = 0; - if (this.state.topic_name != null && this.state.scores != null) { - total_pass = this.state.scores[k].reduce((total, value) => total + (value[1] <= 0), 0); + if (this.state.topic_name != null && this.props.rowData.scores != null) { + total_pass = this.props.rowData.scores[k].reduce((total, value) => total + (value[1] <= 0), 0); } let total_fail = 0; - if (this.state.topic_name != null && this.state.scores != null) { - total_fail = this.state.scores[k].reduce((total, value) => total + (value[1] > 0), 0); + if (this.state.topic_name != null && this.props.rowData.scores != null) { + total_fail = this.props.rowData.scores[k].reduce((total, value) => total + (value[1] > 0), 0); } let label_opacity = isNaN(overall_score[k]) ? 0.5 : 1; @@ -476,17 +474,17 @@ export class RowInternal extends React.Component { {this.state.label == "fail" && } */} - {this.state.labeler === "imputed" && this.state.label === "pass" ? + {this.props.rowData.labeler === "imputed" && this.state.label === "pass" ? : } - {this.state.labeler === "imputed" && this.state.label === "fail" ? + {this.props.rowData.labeler === "imputed" && this.state.label === "fail" ? : } - {this.state.labeler === "imputed" && this.state.label === "off_topic" ? + {this.props.rowData.labeler === "imputed" && this.state.label === "off_topic" ? : @@ -577,11 +575,7 @@ export class RowInternal extends React.Component { } labelAsOffTopic(e) { - this.props.comm.sendEvent(changeLabel(this.props.id, "off_topic", this.props.user)); - if (this.props.isSuggestion) { - this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic)); - } - this.setState({label: "off_topic"}); + this.setLabel("off_topic"); } labelAsPass(e) { @@ -589,10 +583,12 @@ export class RowInternal extends React.Component { } setLabel(label) { - this.props.comm.sendEvent(changeLabel(this.props.id, label, this.props.user)); - if (this.props.isSuggestion) { - this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic)); - } + this.props.comm.sendEvent(changeLabel(this.props.id, label, this.props.user)).then(async () => { + if (this.props.isSuggestion) { + await this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic)); + } + refreshBrowser(this.props.comm, this.props.dispatch); + }); this.setState({label: label}); } @@ -682,8 +678,9 @@ export class RowInternal extends React.Component { inputInput(text) { console.log("inputInput", text) - this.setState({input: text, scores: null}); - this.props.comm.debouncedSendEvent500(changeInput(this.props.id, text)); + // this.setState({input: text, scores: null}); + debounce(() => this.props.comm.sendEvent(changeInput(this.props.id, text)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); } finishInput(text) { @@ -699,8 +696,8 @@ export class RowInternal extends React.Component { // return; console.log("inputOutput", text); // text = text.trim(); // SML: causes the cursor to jump when editing because the text is updated - this.setState({output: text, scores: null}); - this.props.comm.debouncedSendEvent500(changeOutput(this.props.id, text)); + debounce(() => this.props.comm.sendEvent(changeOutput(this.props.id, text)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); // if (this.props.value2Edited) { // this.props.value2Edited(this.props.id, this.state.value2, text); @@ -719,7 +716,8 @@ export class RowInternal extends React.Component { this.setState({topic_name: text, editing: false}); let topic = this.props.topic; if (this.props.isSuggestion) topic += "/__suggestions__"; - this.props.comm.sendEvent(moveTest(this.props.id, topic + "/" + text)); + this.props.comm.sendEvent(moveTest(this.props.id, topic + "/" + text)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } clickRow(e) { @@ -835,7 +833,7 @@ export class RowInternal extends React.Component { if (this.state.topic_name != null) { this.setState({dropHighlighted: 0}); if (this.props.onDrop && id !== this.props.id) { - if (topic_name != null && topic_name !== "null") { + if (topic_name != null && topic_name !== "null" && topic_name !== "undefined") { this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name + "/" + topic_name); } else { this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name); @@ -848,11 +846,9 @@ export class RowInternal extends React.Component { e.preventDefault(); e.stopPropagation(); console.log("addToCurrentTopic X", this.props.topic, this.state.topic_name); - if (this.state.topic_name != null) { - this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic + "/" + this.state.topic_name)); - } else { - this.props.comm.sendEvent(moveTest(this.props.id, this.props.topic)); - } + let targetTopic = this.props.topic + (this.state.topic_name == null ? "" : "/" + this.state.topic_name); + this.props.comm.sendEvent(moveTest(this.props.id, targetTopic)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } } diff --git a/client/src/utils.ts b/client/src/utils.ts new file mode 100644 index 0000000..7bf5a2a --- /dev/null +++ b/client/src/utils.ts @@ -0,0 +1,53 @@ +import { changeTopic, redraw } from "./CommEvent"; +import JupyterComm from "./jupyter-comm"; +import { AppDispatch } from "./store"; +import { refresh } from "./TestTreeSlice"; +import { Comm } from "./types"; +import WebSocketComm from "./web-socket-comm"; + +export function refreshBrowser(comm: Comm, dispatch: AppDispatch) { + return comm.sendEvent(redraw()).then((data) => { + if (data["status"] === "ok") { + dispatch(refresh(data["data"])); + } else { + // TODO: handle error + } + }); +} + +export function goToTopic(topic: string, comm: Comm) { + console.log("goToTopic", topic); + // if (this.suggestionsTemplateRow) { + // this.suggestionsTemplateRow.setState({value2: null}); + // } + return comm.sendEvent(changeTopic(stripSlash(topic).replaceAll(" ", "%20"))); +} + +export function useComm(env: string, interfaceId: any, websocket_server: string="") { + console.log("pairs interfaceId", interfaceId) + if (env === "jupyter") { + return new JupyterComm(interfaceId); + } else if (env === "web") { + if (websocket_server !== "") { + return new WebSocketComm(interfaceId, websocket_server); + } else { + console.error("websocket_server is not set"); + throw new Error("websocket_server is not set"); + } + } else { + console.error("Unknown environment:", env); + throw new Error(`Unknown environment: ${env}`); + } +} + +export function stripPrefix(path: any, prefix: any) { + if (path.startsWith(prefix)) { + return path.slice(prefix.length); + } else { + return path; + } +} + +export function stripSlash(str) { + return str.endsWith('/') ? str.slice(0, -1) : str; +} \ No newline at end of file diff --git a/client/src/web-socket-comm.ts b/client/src/web-socket-comm.ts index 63becac..1d62b5f 100644 --- a/client/src/web-socket-comm.ts +++ b/client/src/web-socket-comm.ts @@ -1,7 +1,7 @@ import JSON5 from 'json5'; import autoBind from 'auto-bind'; import { defer, debounce } from 'lodash'; -import { CommEvent, redraw } from './CommEvent'; +import { CommEvent } from './CommEvent'; export default class WebSocketComm { interfaceId: string; @@ -15,8 +15,6 @@ export default class WebSocketComm { wcomm: WebSocket; reconnectDelay: number; seqNumber: number; - debouncedSendPendingData500: () => void; - debouncedSendPendingData1000: () => void; constructor(interfaceId, websocketServer) { autoBind(this); @@ -28,9 +26,6 @@ export default class WebSocketComm { this.pendingResponses = {}; this.reconnectDelay = 100; this.seqNumber = 0; - - this.debouncedSendPendingData500 = debounce(this.sendPendingData, 500); - this.debouncedSendPendingData1000 = debounce(this.sendPendingData, 1000); } send(keys, data) { @@ -45,22 +40,22 @@ export default class WebSocketComm { return this.sendPendingData(); } - debouncedSendEvent500(commEvent) { - for (const k of Object.keys(commEvent)) { - this.addPendingData(k, commEvent[k]); - } - this.debouncedSendPendingData500(); - } + // debouncedSendEvent500(commEvent) { + // for (const k of Object.keys(commEvent)) { + // this.addPendingData(k, commEvent[k]); + // } + // this.debouncedSendPendingData500(); + // } - debouncedSend500(keys, data) { - this.addPendingData(keys, data); - this.debouncedSendPendingData500(); - } + // debouncedSend500(keys, data) { + // this.addPendingData(keys, data); + // this.debouncedSendPendingData500(); + // } - debouncedSend1000(keys, data) { - this.addPendingData(keys, data); - this.debouncedSendPendingData1000(); - } + // debouncedSend1000(keys, data) { + // this.addPendingData(keys, data); + // this.debouncedSendPendingData1000(); + // } addPendingData(keys, data) { console.log("addPendingData", keys, data); From 71f57a5af9f171a9fbbe3b48b6b388c41202227d Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Mon, 31 Oct 2022 19:24:02 -0700 Subject: [PATCH 11/18] Bugfixes - filter, multiselect --- client/src/TestTreeSlice.ts | 6 +- client/src/browser.tsx | 10 +-- client/src/row.tsx | 143 ++++++++++++++++++------------------ 3 files changed, 77 insertions(+), 82 deletions(-) diff --git a/client/src/TestTreeSlice.ts b/client/src/TestTreeSlice.ts index 4d164e0..c47c34f 100644 --- a/client/src/TestTreeSlice.ts +++ b/client/src/TestTreeSlice.ts @@ -107,10 +107,6 @@ export const testTreeSlice = createSlice({ state.topic_description = action.payload; }, - updateFilterText: (state, action: PayloadAction) => { - state.filter_text = action.payload; - }, - updateSuggestions: (state, action: PayloadAction) => { state.suggestions = [...action.payload]; } @@ -118,6 +114,6 @@ export const testTreeSlice = createSlice({ }) // Action creators are generated for each case reducer function -export const { refresh, updateGenerator, updateTopicDescription, updateFilterText, updateSuggestions } = testTreeSlice.actions +export const { refresh, updateGenerator, updateTopicDescription, updateSuggestions } = testTreeSlice.actions export default testTreeSlice.reducer \ No newline at end of file diff --git a/client/src/browser.tsx b/client/src/browser.tsx index 579cb03..e148dcb 100644 --- a/client/src/browser.tsx +++ b/client/src/browser.tsx @@ -11,7 +11,7 @@ import BreadCrum from './bread-crum'; import TotalValue from './total-value'; import ContentEditable from './content-editable'; -import { TestTreeState, refresh, updateGenerator, updateTopicDescription, updateFilterText, updateSuggestions } from './TestTreeSlice'; +import { TestTreeState, refresh, updateGenerator, updateTopicDescription, updateSuggestions } from './TestTreeSlice'; import { useSelector, useDispatch } from 'react-redux' import { AppDispatch, RootState } from './store'; import { Comm } from './types'; @@ -830,7 +830,7 @@ export class Browser extends React.Component { console.log("inputFilterText", text) this.props.comm.sendEvent(changeFilter(text)).then((data) => { if (data["status"] === "ok") { - this.props.dispatch(updateFilterText(text)); + refreshBrowser(this.props.comm, this.props.dispatch); } else { // TODO: Error handling } @@ -922,8 +922,7 @@ export class Browser extends React.Component { let selections = {}; let selecting = false; console.log("first_selection_id", first_selection_id) - for (let i = 0; i < Object.keys(this.props.testTree.suggestions).length; ++i) { - const curr_id = this.props.testTree.suggestions[i]; + for (const curr_id in this.props.testTree.suggestions) { if (curr_id === id) { if (selecting) { selections[curr_id] = true; @@ -944,8 +943,7 @@ export class Browser extends React.Component { selections[curr_id] = true; } } - for (let i = 0; i < Object.keys(this.props.testTree.tests).length; ++i) { - const curr_id = this.props.testTree.tests[i]; + for (const curr_id in this.props.testTree.tests) { if (curr_id === id) { if (selecting) { selections[curr_id] = true; diff --git a/client/src/row.tsx b/client/src/row.tsx index df71c9b..bbff13b 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -144,9 +144,9 @@ export class RowInternal extends React.Component { // } // this.dataLoadActions = []; // } - // // console.log("state.topic_name", state.topic_name) + // // console.log("props.rowData.topic_name", props.rowData.topic_name) // // we automatically start editing topics that are selected and have an imputed name - // if (state.topic_name && (state.topic_name.startsWith("New topic") || state.value1 === "New test") && this.props.soleSelected) { + // if (props.rowData.topic_name && (props.rowData.topic_name.startsWith("New topic") || state.value1 === "New test") && this.props.soleSelected) { // state["editing"] = true; // console.log("setting editing state to true!") // } @@ -182,7 +182,7 @@ export class RowInternal extends React.Component { if (this.props.scoreColumns) { for (const k of this.props.scoreColumns) { if (this.props.rowData.scores && this.props.updateTotals) { - // console.log("this.props.updateTotals", k, this.state.scores[k]) + // console.log("this.props.updateTotals", k, this.props.rowData.scores[k]) this.props.updateTotals(k, this.props.rowData.scores[k].reduce((total, value) => total + (value[1] <= 0), 0), this.props.rowData.scores[k].reduce((total, value) => total + (value[1] > 0), 0) @@ -204,13 +204,13 @@ export class RowInternal extends React.Component { render() { // console.log("---- render Row ----"); - if (this.state.label == null) return null; // only render if we have data + if (this.props.rowData.label == null) return null; // only render if we have data // console.log("real render Row"); const main_score = this.props.scoreColumns ? this.props.scoreColumns[0] : undefined; // console.log("rendering row", this.props) // apply the value1/value2/topic filters - if (this.state.topic_name == null) { + if (this.props.rowData.topic_name == null) { if (this.props.value1Filter && this.state.value1 !== "") { const re = RegExp(this.props.value1Filter); if (!re.test(this.state.value1 ?? "")) return null; @@ -236,7 +236,7 @@ export class RowInternal extends React.Component { } else if (this.props.value2Filter) { const re = RegExp(this.props.value2Filter); // TODO: rename value2Filter to reflect it's global nature - if (!re.test(this.state.topic_name)) return null; + if (!re.test(this.props.rowData.topic_name)) return null; } // console.log("real render Row2"); @@ -284,7 +284,7 @@ export class RowInternal extends React.Component { let editRowClasses = "adatest-row-hide-button"; if (this.state.hovering) editRowClasses += " adatest-row-hide-hovering"; - if (this.state.editing) editRowClasses += " adatest-row-hide-hidden"; + if (this.props.rowData.editing) editRowClasses += " adatest-row-hide-hidden"; // const test_type_parts = this.props.test_type_parts[this.state.type]; @@ -337,17 +337,17 @@ export class RowInternal extends React.Component { style={this.props.hideBorder ? {} : {borderTop: "1px solid rgb(216, 222, 228)"}} tabIndex={0} onKeyDown={this.keyDownHandler}> - {this.state.topic_name != null && !this.props.isSuggestion && + {this.props.rowData.topic_name != null && !this.props.isSuggestion &&
} - {this.props.isSuggestion && this.state.topic_name != null && + {this.props.isSuggestion && this.props.rowData.topic_name != null &&
} - {/* {this.state.topic_name == null && + {/* {this.props.rowData.topic_name == null && @@ -355,11 +355,11 @@ export class RowInternal extends React.Component { } */}
- {this.state.topic_name != null ? + {this.props.rowData.topic_name != null ?
- this.topicNameEditable = el} text={decodeURIComponent(this.state.topic_name)} onInput={this.inputTopicName} onFinish={this.finishTopicName} editable={this.state.editing} /> - {this.state.description} + this.topicNameEditable = el} text={decodeURIComponent(this.props.rowData.topic_name)} onInput={this.inputTopicName} onFinish={this.finishTopicName} editable={this.props.rowData.editing} /> + {this.props.rowData.description}
@@ -388,7 +388,7 @@ export class RowInternal extends React.Component { {this.props.rowData.input.startsWith("__IMAGE=") ? : - this.inputEditable = el} text={this.props.rowData.input} onInput={this.inputInput} onFinish={this.finishInput} editable={this.state.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> + this.inputEditable = el} text={this.props.rowData.input} onInput={this.inputInput} onFinish={this.finishInput} editable={this.props.rowData.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> } {/* */} @@ -397,11 +397,11 @@ export class RowInternal extends React.Component {
-
+
- this.outputEditable = el} text={this.props.rowData.output} onInput={this.inputOutput} onFinish={_ => this.setState({editing: false})} editable={this.state.editing} defaultText={this.props.outputDefault} /> + this.outputEditable = el} text={this.props.rowData.output} onInput={this.inputOutput} onFinish={_ => this.setState({editing: false})} editable={this.props.rowData.editing} defaultText={this.props.outputDefault} /> @@ -410,22 +410,22 @@ export class RowInternal extends React.Component { )}
{/*
- {this.state.topic_name == null && !isNaN(score) && score.toFixed(3).replace(/\.?0*$/, '')} + {this.props.rowData.topic_name == null && !isNaN(score) && score.toFixed(3).replace(/\.?0*$/, '')}
*/} - {/* {this.state.topic_name == null && + {/* {this.props.rowData.topic_name == null && - {this.state.labeler === "imputed" && this.state.label === "pass" ? - + {this.props.rowData.labeler === "imputed" && this.props.rowData.label === "pass" ? + : - + } - {this.state.labeler === "imputed" && this.state.label === "fail" ? - + {this.props.rowData.labeler === "imputed" && this.props.rowData.label === "fail" ? + : - + } {this.props.isSuggestion ? - + : } @@ -437,11 +437,11 @@ export class RowInternal extends React.Component { {this.props.scoreColumns && this.props.scoreColumns.map(k => { let total_pass = 0; - if (this.state.topic_name != null && this.props.rowData.scores != null) { + if (this.props.rowData.topic_name != null && this.props.rowData.scores != null) { total_pass = this.props.rowData.scores[k].reduce((total, value) => total + (value[1] <= 0), 0); } let total_fail = 0; - if (this.state.topic_name != null && this.props.rowData.scores != null) { + if (this.props.rowData.topic_name != null && this.props.rowData.scores != null) { total_fail = this.props.rowData.scores[k].reduce((total, value) => total + (value[1] > 0), 0); } @@ -449,8 +449,8 @@ export class RowInternal extends React.Component { let scaled_score = scale_score(overall_score[k]); - // this.totalPasses[k] = Number.isFinite(overall_score[k]) ? this.state.scores[k].reduce((total, value) => total + (value[1] <= 0), 0) : NaN; - // this.totalFailures[k] = this.state.scores[k].reduce((total, value) => total + (value[1] > 0), 0); + // this.totalPasses[k] = Number.isFinite(overall_score[k]) ? this.props.rowData.scores[k].reduce((total, value) => total + (value[1] <= 0), 0) : NaN; + // this.totalFailures[k] = this.props.rowData.scores[k].reduce((total, value) => total + (value[1] > 0), 0); return
{/* {overall_score[k] > 0 ? */} (total_pass / (total_pass + total_fail)) @@ -466,38 +466,38 @@ export class RowInternal extends React.Component { } - {this.state.topic_name == null && + {this.props.rowData.topic_name == null && - {/* {this.state.label == "pass" && + {/* {this.props.rowData.label == "pass" && } - {this.state.label == "fail" && + {this.props.rowData.label == "fail" && } */} - {this.props.rowData.labeler === "imputed" && this.state.label === "pass" ? - + {this.props.rowData.labeler === "imputed" && this.props.rowData.label === "pass" ? + : - + } - {this.props.rowData.labeler === "imputed" && this.state.label === "fail" ? - + {this.props.rowData.labeler === "imputed" && this.props.rowData.label === "fail" ? + : - + } - {this.props.rowData.labeler === "imputed" && this.state.label === "off_topic" ? + {this.props.rowData.labeler === "imputed" && this.props.rowData.label === "off_topic" ? : - + } } - {this.state.topic_name != null && total_pass > 0 && + {this.props.rowData.topic_name != null && total_pass > 0 && {total_pass} } - {this.state.topic_name != null && total_fail > 0 && + {this.props.rowData.topic_name != null && total_fail > 0 && {total_fail} } @@ -546,7 +546,7 @@ export class RowInternal extends React.Component { if (e.keyCode == 13) { console.log("return!", this.props.soleSelected, this.props.selected) if (this.props.soleSelected) { - if (this.state.topic_name != null) { + if (this.props.rowData.topic_name != null) { this.onOpen(e); } else if (this.props.isSuggestion) { this.addToCurrentTopic(e); @@ -609,10 +609,10 @@ export class RowInternal extends React.Component { e.preventDefault(); e.stopPropagation(); - if (!this.state.editing) { + if (!this.props.rowData.editing) { this.setState({editing: true}); console.log("about to edit focus") - if (this.state.topic_name == null) { + if (this.props.rowData.topic_name == null) { defer(() => this.inputEditable?.focus()); } else { defer(() => this.topicNameEditable?.focus()); @@ -635,10 +635,10 @@ export class RowInternal extends React.Component { e.stopPropagation(); const newName = this.props.generateTopicName(); const newTopic = this.props.topic + "/" + newName; - if (this.state.topic_name == null) { + if (this.props.rowData.topic_name == null) { this.props.comm.send(this.props.id, {"topic": newTopic }); } else { - this.props.comm.send(this.props.id, {"topic": newTopic + "/" + this.state.topic_name}); + this.props.comm.send(this.props.id, {"topic": newTopic + "/" + this.props.rowData.topic_name}); } this.props.setSelected(newTopic); } */ @@ -670,9 +670,9 @@ export class RowInternal extends React.Component { onOpen(e) { e.preventDefault(); e.stopPropagation(); - console.log("this.state.topic_name XXXXXXXXXXXX", this.state.topic_name)//, "Row.onOpen(", e, ")"); - if (this.state.topic_name != null && this.props.onOpen) { - this.props.onOpen(this.props.topic + "/" + this.state.topic_name); + console.log("this.props.rowData.topic_name XXXXXXXXXXXX", this.props.rowData.topic_name)//, "Row.onOpen(", e, ")"); + if (this.props.rowData.topic_name != null && this.props.onOpen) { + this.props.onOpen(this.props.topic + "/" + this.props.rowData.topic_name); } } @@ -686,6 +686,8 @@ export class RowInternal extends React.Component { finishInput(text) { console.log("finishInput", text) this.setState({editing: false}); + this.props.comm.sendEvent(changeInput(this.props.id, text)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)) // if (text.includes("/")) { // this.setState({input: text, scores: null}); // this.props.comm.send(this.props.id, {input: text}); @@ -721,25 +723,24 @@ export class RowInternal extends React.Component { } clickRow(e) { - const modKey = e.metaKey || e.shiftKey; if (this.props.onSelectToggle) { e.preventDefault(); e.stopPropagation(); - this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey); + this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey || e.ctrlKey); } } clickTopicName(e) { console.log("clickTopicName"); - const modKey = e.metaKey || e.shiftKey; + const modKey = e.metaKey || e.shiftKey || e.ctrlKey; if (this.props.onSelectToggle) { e.preventDefault(); e.stopPropagation(); - this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey); + this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey || e.ctrlKey); } - if (!modKey && !this.state.editing) { + if (!modKey && !this.props.rowData.editing) { this.setState({editing: true}); - console.log("topic editing", this.state.editing) + console.log("topic editing", this.props.rowData.editing) e.preventDefault(); e.stopPropagation(); defer(() => this.topicNameEditable?.focus()); @@ -748,15 +749,15 @@ export class RowInternal extends React.Component { clickInput(e) { console.log("clickInput", e); - const modKey = e.metaKey || e.shiftKey; + const modKey = e.metaKey || e.shiftKey || e.ctrlKey; if (this.props.onSelectToggle) { e.preventDefault(); e.stopPropagation(); - this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey); + this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey || e.ctrlKey); } - if (!modKey && !this.state.editing) { + if (!modKey && !this.props.rowData.editing) { this.setState({editing: true}); - console.log("value1 editing", this.state.editing) + console.log("value1 editing", this.props.rowData.editing) e.preventDefault(); e.stopPropagation(); defer(() => this.inputEditable?.focus()); @@ -767,13 +768,13 @@ export class RowInternal extends React.Component { clickOutput(e) { console.log("clickOutput"); - const modKey = e.metaKey || e.shiftKey; + const modKey = e.metaKey || e.shiftKey || e.ctrlKey; if (this.props.onSelectToggle) { e.preventDefault(); e.stopPropagation(); - this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey); + this.props.onSelectToggle(this.props.id, e.shiftKey, e.metaKey || e.ctrlKey); } - if (!modKey && !this.state.editing) { + if (!modKey && !this.props.rowData.editing) { this.setState({editing: true}); e.preventDefault(); e.stopPropagation(); @@ -790,7 +791,7 @@ export class RowInternal extends React.Component { //console.log("drag start", e, this.mouseDownTarget.getAttribute("contenteditable") === "true") this.setState({dragging: true}); e.dataTransfer.setData("id", this.props.id); - e.dataTransfer.setData("topic_name", this.state.topic_name); + e.dataTransfer.setData("topic_name", this.props.rowData.topic_name); // if (this.props.onDragStart) { // this.props.onDragStart(e, this); // } @@ -812,7 +813,7 @@ export class RowInternal extends React.Component { console.log("enter", e.target) e.preventDefault(); e.stopPropagation(); - if (this.state.topic_name != null) { + if (this.props.rowData.topic_name != null) { this.setState({dropHighlighted: this.state.dropHighlighted + 1}); } } @@ -821,7 +822,7 @@ export class RowInternal extends React.Component { console.log("leave", e.target) e.preventDefault(); e.stopPropagation(); - if (this.state.topic_name != null) { + if (this.props.rowData.topic_name != null) { this.setState({dropHighlighted: this.state.dropHighlighted - 1}); } } @@ -830,13 +831,13 @@ export class RowInternal extends React.Component { const id = e.dataTransfer.getData("id"); const topic_name = e.dataTransfer.getData("topic_name"); - if (this.state.topic_name != null) { + if (this.props.rowData.topic_name != null) { this.setState({dropHighlighted: 0}); if (this.props.onDrop && id !== this.props.id) { if (topic_name != null && topic_name !== "null" && topic_name !== "undefined") { - this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name + "/" + topic_name); + this.props.onDrop(id, this.props.topic + "/" + this.props.rowData.topic_name + "/" + topic_name); } else { - this.props.onDrop(id, this.props.topic + "/" + this.state.topic_name); + this.props.onDrop(id, this.props.topic + "/" + this.props.rowData.topic_name); } } } @@ -845,8 +846,8 @@ export class RowInternal extends React.Component { addToCurrentTopic(e) { e.preventDefault(); e.stopPropagation(); - console.log("addToCurrentTopic X", this.props.topic, this.state.topic_name); - let targetTopic = this.props.topic + (this.state.topic_name == null ? "" : "/" + this.state.topic_name); + console.log("addToCurrentTopic X", this.props.topic, this.props.rowData.topic_name); + let targetTopic = this.props.topic + (this.props.rowData.topic_name == null ? "" : "/" + this.props.rowData.topic_name); this.props.comm.sendEvent(moveTest(this.props.id, targetTopic)) .then(() => refreshBrowser(this.props.comm, this.props.dispatch)); } From 9e017c54866df4c43d0676f968f56c2ab69b88ae Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Tue, 1 Nov 2022 16:12:01 -0700 Subject: [PATCH 12/18] Work in progress - make ContentEditable play nicely with new data flow --- client/src/content-editable.tsx | 51 +++++++++++++++++---------------- client/src/row.tsx | 48 +++++++++++++++++++------------ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/client/src/content-editable.tsx b/client/src/content-editable.tsx index bdc2420..269e97c 100644 --- a/client/src/content-editable.tsx +++ b/client/src/content-editable.tsx @@ -5,12 +5,12 @@ import { defer } from 'lodash'; interface ContentEditableProps { id?: string; - text: string; // current text value + text: string; // the starting text value before user makes edits defaultText: string; // text to show when empty editable: boolean; // if true, allow editing finishOnReturn: boolean; // if true, call onFinish when return is pressed - onInput?: Function; // called when text is changed - onFinish?: Function; // called when editing is finished + onInput?: (s: string) => string; // called when text is changed. Reacts to the input and returns the new text + onFinish?: (s: string) => void; // called when editing is finished onClick?: Function; // called when the element is clicked onTemplateExpand?: () => void; } @@ -30,7 +30,7 @@ export default class ContentEditable extends React.Component this.divRef = el} id={this.props.id} @@ -113,11 +113,11 @@ export default class ContentEditable extends React.Component 0 || this.divRef.textContent !== this.props.defaultText)) { - this.divRef.textContent = this.props.text; - } else { - if (mount) this.divRef.textContent = this.props.defaultText; - } - } - if (this.props.text && (this.props.text.startsWith("New topic") || this.props.text === "New test") && this.props.editable) { // hacky but works for now - // console.log("HACK!", this.props.text) - this.divRef.focus(); - selectElement(this.divRef); - // document.execCommand('selectAll', false, null); - } + // if (this.props.text !== this.divRef.textContent) { + // if (this.props.text !== undefined && this.props.text !== null && (this.props.text.length > 0 || this.divRef.textContent !== this.props.defaultText)) { + // this.divRef.textContent = this.props.text; + // } else { + // if (mount) this.divRef.textContent = this.props.defaultText; + // } + // } + // if (this.props.text && (this.props.text.startsWith("New topic") || this.props.text === "New test") && this.props.editable) { // hacky but works for now + // // console.log("HACK!", this.props.text) + // this.divRef.focus(); + // selectElement(this.divRef); + // // document.execCommand('selectAll', false, null); + // } } handleInput(e, finishing) { console.log("handleInput", finishing, this.divRef.textContent) - const text = this.divRef.textContent; + let text = this.divRef.textContent; if (this.props.onInput && text !== this.lastText) { - this.props.onInput(text); + text = this.props.onInput(text); this.lastText = text; + this.divRef.textContent = text; } if (finishing && this.props.onFinish) { diff --git a/client/src/row.tsx b/client/src/row.tsx index bbff13b..b6eac7c 100644 --- a/client/src/row.tsx +++ b/client/src/row.tsx @@ -388,7 +388,7 @@ export class RowInternal extends React.Component { {this.props.rowData.input.startsWith("__IMAGE=") ? : - this.inputEditable = el} text={this.props.rowData.input} onInput={this.inputInput} onFinish={this.finishInput} editable={this.props.rowData.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> + this.inputEditable = el} text={this.props.rowData.input} onFinish={this.finishInput} editable={this.props.rowData.editing} defaultText={this.props.inputDefault} onTemplateExpand={this.templateExpandValue1} /> } {/* */} @@ -401,7 +401,7 @@ export class RowInternal extends React.Component { - this.outputEditable = el} text={this.props.rowData.output} onInput={this.inputOutput} onFinish={_ => this.setState({editing: false})} editable={this.props.rowData.editing} defaultText={this.props.outputDefault} /> + this.outputEditable = el} text={this.props.rowData.output} onFinish={this.finishOutput} editable={this.props.rowData.editing} defaultText={this.props.outputDefault} /> @@ -676,12 +676,12 @@ export class RowInternal extends React.Component { } } - inputInput(text) { - console.log("inputInput", text) - // this.setState({input: text, scores: null}); - debounce(() => this.props.comm.sendEvent(changeInput(this.props.id, text)) - .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); - } + // inputInput(text) { + // console.log("inputInput", text) + // this.setState({input: text, scores: null}); + // debounce(() => this.props.comm.sendEvent(changeInput(this.props.id, text)) + // .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); + // } finishInput(text) { console.log("finishInput", text) @@ -694,22 +694,34 @@ export class RowInternal extends React.Component { // } } - inputOutput(text) { - // return; - console.log("inputOutput", text); - // text = text.trim(); // SML: causes the cursor to jump when editing because the text is updated - debounce(() => this.props.comm.sendEvent(changeOutput(this.props.id, text)) - .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); - - // if (this.props.value2Edited) { - // this.props.value2Edited(this.props.id, this.state.value2, text); + finishOutput(text) { + console.log("finishOutput", text) + this.setState({editing: false}); + this.props.comm.sendEvent(changeOutput(this.props.id, text)) + .then(() => refreshBrowser(this.props.comm, this.props.dispatch)) + // if (text.includes("/")) { + // this.setState({input: text, scores: null}); + // this.props.comm.send(this.props.id, {input: text}); // } - // this.setValue2(text); } + // inputOutput(text) { + // // return; + // console.log("inputOutput", text); + // // text = text.trim(); // SML: causes the cursor to jump when editing because the text is updated + // debounce(() => this.props.comm.sendEvent(changeOutput(this.props.id, text)) + // .then(() => refreshBrowser(this.props.comm, this.props.dispatch)), 500); + + // // if (this.props.value2Edited) { + // // this.props.value2Edited(this.props.id, this.state.value2, text); + // // } + // // this.setValue2(text); + // } + inputTopicName(text) { text = encodeURIComponent(text.replaceAll("\\", "").replaceAll("\n", "")); this.setState({topic_name: text}); + return text; } finishTopicName(text) { From 386ffdac0bd6094a3374c4951117c46e877b8e8d Mon Sep 17 00:00:00 2001 From: Nicholas King Date: Tue, 1 Nov 2022 16:59:57 -0700 Subject: [PATCH 13/18] Cleaning up commented code --- adatest/_test_tree_browser.py | 11 - adatest/_topic_model.py | 5 +- adatest/resources/main.js | 1836 ++++++++++++++++++++++++++++++++- client/src/TestTreeSlice.ts | 59 -- client/src/browser.tsx | 68 +- client/src/row.tsx | 21 - 6 files changed, 1840 insertions(+), 160 deletions(-) diff --git a/adatest/_test_tree_browser.py b/adatest/_test_tree_browser.py index ee7d318..00d8b2b 100644 --- a/adatest/_test_tree_browser.py +++ b/adatest/_test_tree_browser.py @@ -324,7 +324,6 @@ def interface_event(self, msg): # log.debug(e) # self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) # self._suggestions_error = True - # self._refresh_interface(sequence_number) # change the current topic elif event_id == "change_topic": @@ -339,14 +338,11 @@ def interface_event(self, msg): else: self.mode = "tests" self.send_response(sequence_number) - - # self._refresh_interface(sequence_number) # clear the current set of suggestions elif event_id == "clear_suggestions": self._clear_suggestions() self.suggestions = pd.DataFrame([], columns=self.test_tree.columns) - # self._refresh_interface(sequence_number) self.send_response(sequence_number) # add a new empty subtopic to the current topic @@ -361,7 +357,6 @@ def interface_event(self, msg): } self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) # add a new empty test to the current topic @@ -382,7 +377,6 @@ def interface_event(self, msg): self.test_tree.loc[uuid.uuid4().hex] = row self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) # change which scorer/model is used for sorting tests @@ -400,7 +394,6 @@ def interface_event(self, msg): self.score_columns.insert(0, name) self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) elif event_id == "change_generator": @@ -421,14 +414,12 @@ def interface_event(self, msg): self.test_tree.loc[id, 'label'] = "topic_marker" self.test_tree.loc[msg['topic_marker_id'], 'description'] = msg['description'] self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) elif event_id == 'change_filter': print("change_filter") self.filter_text = msg['filter_text'] self.send_response(sequence_number) - # self._refresh_interface(sequence_number) # Move a test/topic to a new topic # Also used to rename @@ -447,7 +438,6 @@ def interface_event(self, msg): # Recompute any missing embeddings to handle any changes self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) elif event_id == "delete_test": @@ -464,7 +454,6 @@ def interface_event(self, msg): self.test_tree.drop(id, inplace=True) self._compute_embeddings_and_scores(self.test_tree) self._auto_save() - # self._refresh_interface(sequence_number) self.send_response(sequence_number) # if we are just updating a single row in tests then we only recompute the scores diff --git a/adatest/_topic_model.py b/adatest/_topic_model.py index 752280d..505fe75 100644 --- a/adatest/_topic_model.py +++ b/adatest/_topic_model.py @@ -157,8 +157,9 @@ def __init__(self, topic, test_tree): else: # we are in a highly overparametrized situation, so we use a linear SVC to get "max-margin" based generalization - self.model = CVModel() - self.model.fit(embeddings, labels) + self.model = ConstantModel(1.0) + # self.model = CVModel() + # self.model.fit(embeddings, labels) def __call__(self, input): embeddings = adatest.embed([input])[0] diff --git a/adatest/resources/main.js b/adatest/resources/main.js index a37274f..893ddeb 100644 --- a/adatest/resources/main.js +++ b/adatest/resources/main.js @@ -1,2 +1,1834 @@ -/*! For license information please see main.js.LICENSE.txt */ -(()=>{var e={4543:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(5142),i=n.n(r)()((function(e){return e[1]}));i.push([e.id,'.adatest-button-container {\n display: block;\n width: 100%;\n padding: 3px;\n padding-top: 1px;\n padding-bottom: 1px;\n font-size: 13px;\n}\n.adatest-button-children {\n display: flex;\n vertical-align: top;\n width: 100%;\n}\n\n.adatest-button-box {\n padding: 2px;\n padding-left: 5px;\n padding-right: 5px;\n display: flex;\n cursor: pointer;\n text-align: center;\n width: 100%;\n margin-bottom: 3px;\n border: 1px solid #dddddd;\n}\n\n.adatest-explore-toggle {\n float: right;\n color: #666666;\n margin-top: 4px;\n margin-left: 4px;\n width: 30px;\n}\n\n.adatest-add-topic {\n color: #bbbbbb;\n margin-top: 4px;\n margin-left: 7px;\n margin-right: 7px;\n cursor: pointer;\n}\n.adatest-add-topic-wrapper {\n margin-top: 4px;\n margin-left: auto;\n margin-right: auto;\n}\n\n.adatest-button-label {\n flex: 1;\n display: inline-block;\n text-align: center;\n user-select: none;\n}\n\n.adatest-topic-label {\n width: 300px;\n border: 0px;\n border-bottom: 1px solid #999999;\n text-align: left;\n margin-top: 10px;\n margin-bottom: 10px;\n outline: 0px solid transparent;\n background: transparent;\n}\n.adatest-topic-label:focus {\n outline: 0px solid transparent;\n}\n\n.adatest-children-frame {\n padding: 0px;\n margin-top: 10px;\n /* padding-bottom: 10px; */\n border-radius: 7px 7px 7px 7px;\n border: 1px solid rgb(216, 222, 228);\n}\n\n.adatest-row-child {\n display: flex;\n padding: 0px;\n padding-left: 5px;\n padding-right: 0px;\n min-height: 30px;\n text-align: left;\n border-radius: 0px;/*10px 10px 10px 10px;*/\n outline: none;\n padding-top: 5px;\n padding-bottom: 5px;\n}\n.adatest-row-hidden {\n text-decoration: line-through;\n}\n\n.adatest-row-score-plot-box {\n float: right;\n height: 30px;\n flex: 0 0 150px;\n margin-right: 10px;\n padding-top: 0px;\n align-self: center;\n}\n\n.adatest-row-add-button {\n margin-top: 8px;\n flex: 0 0 20px;\n display: inline-block;\n margin-left: 10px;\n}\n\n.adatest-top-add-button {\n margin-top: 8px;\n flex: 0 0 20px;\n display: inline-block;\n margin-left: 10px;\n}\n\n[contenteditable] {\n -webkit-user-select: text;\n user-select: text;\n}\n\n/* [contenteditable]:focus {\n outline-width: 0px;\n} */\n\n.adatest-plain-select {\n margin-left: 4px;\n appearance: none;\n border: none;\n background: none;\n -webkit-appearance: none;\n font-size: 13px;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n margin: 0px;\n}\n.adatest-plain-select:focus {\n outline: 0px solid transparent;\n}\n\n.adatest-hover-opacity {\n opacity: 0.1;\n}\n.adatest-hover-opacity:hover {\n opacity: 0.6;\n}\n\n.adatest-scroll-wrap::-webkit-scrollbar {\n display: none;\n}\n\n.adatest-row-hide-button {\n opacity: 0;\n margin-top: 8px;\n /* line-height: 30px; */\n flex: 0 0 15px;\n display: inline-block;\n margin-left: 10px;\n}\n.adatest-row-hide-button:hover {\n opacity: 0.6\n}\n.adatest-row-hide-hovering {\n opacity: 0.1;\n}\n.adatest-row-hide-hidden {\n opacity: 0.6;\n}\n\n.adatest-row-score-text-box {\n float: right;\n text-align: right;\n flex: 0 0 50px;\n line-height: 30px;\n height: 30px;\n color: #999999;\n padding-right: 5px;\n font-size: 12px;\n overflow: hidden;\n box-sizing: border-box;\n}\n\n.adatest-main-table {\n margin-left: "auto";\n margin-right: "auto";\n background: #ffffff;\n width: 100%;\n}\n\n.adatest-main-table tbody tr:nth-child(odd) {\n background: #ffffff;\n}\n\n.adatest-main-table tbody tr:hover {\n background: #ffffff;\n}\n\n\n.adatest-output-text:focus {\n outline: 0px solid transparent;\n}\n\n.adatest-row-selected {\n background: #00000008;\n /* border-left: 3px solid #00000099; */\n /* padding-left: 2px; */\n}\n\n.adatest-select-width-calculation {\n position: absolute;\n visibility: hidden;\n height: auto;\n width: auto;\n white-space: nowrap;\n font-size: 13px;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n white-space: pre;\n}\n\n.adatest-row {\n display: flex;\n margin-top: 3px;\n}\n.adatest-row-input {\n flex: 1;\n display: flex;\n justify-content: right;\n align-items: center;\n}\n.adatest-row-editing {\n border-bottom: 0px dashed rgb(0, 130, 248);\n}\n.adatest-row-editing:focus {\n outline: 0px solid transparent;\n}\n.adatest-row-drop-highlighted {\n background: #dddddd;\n}\n.adatest-row-hover-highlighted {\n background: #f0f0f0;\n}\n.adatest-row-dragging {\n background: #dddddd;\n}\n\n\n.adatest-crum-selected {\n background: #dddddd;\n}\n\n.adatest-editable:focus {\n outline: 0px solid transparent;\n}\n\n.adatest-suggestions-box {\n position: relative;\n border-radius: 7px 7px 7px 7px;\n background: rgb(246, 248, 250);\n text-align: center;\n padding: 0px;\n padding-top: 10px;\n margin-top: 10px;\n padding-bottom: 0px;\n border: 1px solid rgb(216, 222, 228);\n}\n.adatest-suggestions-box-after {\n content: \'werwerwe\';\n width: 100%;\n height: 22px; \n position: absolute;\n bottom: 0px;\n background: linear-gradient(rgba(246, 248, 250, 0) 0px, rgb(246, 248, 250, 1) 18px);\n pointer-events: none;\n border-radius: 0px 0px 7px 7px;\n }\n.adatest-drop-highlighted {\n background: #dddddd;\n}\n\n.adatest-hover-gray:hover {\n background: #dddddd;\n}',""]);const o=i},5701:(e,t,n)=>{"use strict";var r,i=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),o=[];function a(e){for(var t=-1,n=0;n{"use strict";e.exports=(e,{include:t,exclude:n}={})=>{const r=e=>{const r=t=>"string"==typeof t?e===t:t.test(e);return t?t.some(r):!n||!n.some(r)};for(const[t,n]of(e=>{const t=new Set;do{for(const n of Reflect.ownKeys(e))t.add([e,n])}while((e=Reflect.getPrototypeOf(e))&&e!==Object.prototype);return t})(e.constructor.prototype)){if("constructor"===n||!r(n))continue;const i=Reflect.getOwnPropertyDescriptor(t,n);i&&"function"==typeof i.value&&(e[n]=e[n].bind(e))}return e}},5142:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var i={};if(r)for(var o=0;o{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===n}(e)}(e)},n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?u((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function i(e,t,n){return e.concat(t).map((function(e){return r(e,n)}))}function o(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return e.propertyIsEnumerable(t)})):[]}(e))}function a(e,t){try{return t in e}catch(e){return!1}}function u(e,n,s){(s=s||{}).arrayMerge=s.arrayMerge||i,s.isMergeableObject=s.isMergeableObject||t,s.cloneUnlessOtherwiseSpecified=r;var l=Array.isArray(n);return l===Array.isArray(e)?l?s.arrayMerge(e,n,s):function(e,t,n){var i={};return n.isMergeableObject(e)&&o(e).forEach((function(t){i[t]=r(e[t],n)})),o(t).forEach((function(o){(function(e,t){return a(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,o)||(a(e,o)&&n.isMergeableObject(t[o])?i[o]=function(e,t){if(!t.customMerge)return u;var n=t.customMerge(e);return"function"==typeof n?n:u}(o,n)(e[o],t[o],n):i[o]=r(t[o],n))})),i}(e,n,s):r(n,s)}u.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return u(e,n,t)}),{})};var s=u;e.exports=s},8752:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.attributeNames=t.elementNames=void 0,t.elementNames=new Map([["altglyph","altGlyph"],["altglyphdef","altGlyphDef"],["altglyphitem","altGlyphItem"],["animatecolor","animateColor"],["animatemotion","animateMotion"],["animatetransform","animateTransform"],["clippath","clipPath"],["feblend","feBlend"],["fecolormatrix","feColorMatrix"],["fecomponenttransfer","feComponentTransfer"],["fecomposite","feComposite"],["feconvolvematrix","feConvolveMatrix"],["fediffuselighting","feDiffuseLighting"],["fedisplacementmap","feDisplacementMap"],["fedistantlight","feDistantLight"],["fedropshadow","feDropShadow"],["feflood","feFlood"],["fefunca","feFuncA"],["fefuncb","feFuncB"],["fefuncg","feFuncG"],["fefuncr","feFuncR"],["fegaussianblur","feGaussianBlur"],["feimage","feImage"],["femerge","feMerge"],["femergenode","feMergeNode"],["femorphology","feMorphology"],["feoffset","feOffset"],["fepointlight","fePointLight"],["fespecularlighting","feSpecularLighting"],["fespotlight","feSpotLight"],["fetile","feTile"],["feturbulence","feTurbulence"],["foreignobject","foreignObject"],["glyphref","glyphRef"],["lineargradient","linearGradient"],["radialgradient","radialGradient"],["textpath","textPath"]]),t.attributeNames=new Map([["definitionurl","definitionURL"],["attributename","attributeName"],["attributetype","attributeType"],["basefrequency","baseFrequency"],["baseprofile","baseProfile"],["calcmode","calcMode"],["clippathunits","clipPathUnits"],["diffuseconstant","diffuseConstant"],["edgemode","edgeMode"],["filterunits","filterUnits"],["glyphref","glyphRef"],["gradienttransform","gradientTransform"],["gradientunits","gradientUnits"],["kernelmatrix","kernelMatrix"],["kernelunitlength","kernelUnitLength"],["keypoints","keyPoints"],["keysplines","keySplines"],["keytimes","keyTimes"],["lengthadjust","lengthAdjust"],["limitingconeangle","limitingConeAngle"],["markerheight","markerHeight"],["markerunits","markerUnits"],["markerwidth","markerWidth"],["maskcontentunits","maskContentUnits"],["maskunits","maskUnits"],["numoctaves","numOctaves"],["pathlength","pathLength"],["patterncontentunits","patternContentUnits"],["patterntransform","patternTransform"],["patternunits","patternUnits"],["pointsatx","pointsAtX"],["pointsaty","pointsAtY"],["pointsatz","pointsAtZ"],["preservealpha","preserveAlpha"],["preserveaspectratio","preserveAspectRatio"],["primitiveunits","primitiveUnits"],["refx","refX"],["refy","refY"],["repeatcount","repeatCount"],["repeatdur","repeatDur"],["requiredextensions","requiredExtensions"],["requiredfeatures","requiredFeatures"],["specularconstant","specularConstant"],["specularexponent","specularExponent"],["spreadmethod","spreadMethod"],["startoffset","startOffset"],["stddeviation","stdDeviation"],["stitchtiles","stitchTiles"],["surfacescale","surfaceScale"],["systemlanguage","systemLanguage"],["tablevalues","tableValues"],["targetx","targetX"],["targety","targetY"],["textlength","textLength"],["viewbox","viewBox"],["viewtarget","viewTarget"],["xchannelselector","xChannelSelector"],["ychannelselector","yChannelSelector"],["zoomandpan","zoomAndPan"]])},6380:function(e,t,n){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n";case u.Comment:return"\x3c!--"+e.data+"--\x3e";case u.CDATA:return function(e){return""}(e);case u.Script:case u.Style:case u.Tag:return function(e,t){var n;"foreign"===t.xmlMode&&(e.name=null!==(n=l.elementNames.get(e.name))&&void 0!==n?n:e.name,e.parent&&h.has(e.parent.name)&&(t=r(r({},t),{xmlMode:!1}))),!t.xmlMode&&g.has(e.name)&&(t=r(r({},t),{xmlMode:"foreign"}));var i="<"+e.name,o=function(e,t){if(e)return Object.keys(e).map((function(n){var r,i,o=null!==(r=e[n])&&void 0!==r?r:"";return"foreign"===t.xmlMode&&(n=null!==(i=l.attributeNames.get(n))&&void 0!==i?i:n),t.emptyAttrs||t.xmlMode||""!==o?n+'="'+(!1!==t.decodeEntities?s.encodeXML(o):o.replace(/"/g,"""))+'"':n})).join(" ")}(e.attribs,t);return o&&(i+=" "+o),0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&f.has(e.name))?(t.xmlMode||(i+=" "),i+="/>"):(i+=">",e.children.length>0&&(i+=p(e.children,t)),!t.xmlMode&&f.has(e.name)||(i+="")),i}(e,t);case u.Text:return function(e,t){var n=e.data||"";return!1===t.decodeEntities||!t.xmlMode&&e.parent&&c.has(e.parent.name)||(n=s.encodeXML(n)),n}(e,t)}}t.default=p;var h=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),g=new Set(["svg","math"])},9923:(e,t)=>{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.Doctype=t.CDATA=t.Tag=t.Style=t.Script=t.Comment=t.Directive=t.Text=t.Root=t.isTag=t.ElementType=void 0,function(e){e.Root="root",e.Text="text",e.Directive="directive",e.Comment="comment",e.Script="script",e.Style="style",e.Tag="tag",e.CDATA="cdata",e.Doctype="doctype"}(n=t.ElementType||(t.ElementType={})),t.isTag=function(e){return e.type===n.Tag||e.type===n.Script||e.type===n.Style},t.Root=n.Root,t.Text=n.Text,t.Directive=n.Directive,t.Comment=n.Comment,t.Script=n.Script,t.Style=n.Style,t.Tag=n.Tag,t.CDATA=n.CDATA,t.Doctype=n.Doctype},9628:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),t.DomHandler=void 0;var o=n(9923),a=n(4091);i(n(4091),t);var u=/\s+/g,s={normalizeWhitespace:!1,withStartIndices:!1,withEndIndices:!1},l=function(){function e(e,t,n){this.dom=[],this.root=new a.Document(this.dom),this.done=!1,this.tagStack=[this.root],this.lastNode=null,this.parser=null,"function"==typeof t&&(n=t,t=s),"object"==typeof e&&(t=e,e=void 0),this.callback=null!=e?e:null,this.options=null!=t?t:s,this.elementCB=null!=n?n:null}return e.prototype.onparserinit=function(e){this.parser=e},e.prototype.onreset=function(){var e;this.dom=[],this.root=new a.Document(this.dom),this.done=!1,this.tagStack=[this.root],this.lastNode=null,this.parser=null!==(e=this.parser)&&void 0!==e?e:null},e.prototype.onend=function(){this.done||(this.done=!0,this.parser=null,this.handleCallback(null))},e.prototype.onerror=function(e){this.handleCallback(e)},e.prototype.onclosetag=function(){this.lastNode=null;var e=this.tagStack.pop();this.options.withEndIndices&&(e.endIndex=this.parser.endIndex),this.elementCB&&this.elementCB(e)},e.prototype.onopentag=function(e,t){var n=this.options.xmlMode?o.ElementType.Tag:void 0,r=new a.Element(e,t,void 0,n);this.addNode(r),this.tagStack.push(r)},e.prototype.ontext=function(e){var t=this.options.normalizeWhitespace,n=this.lastNode;if(n&&n.type===o.ElementType.Text)t?n.data=(n.data+e).replace(u," "):n.data+=e;else{t&&(e=e.replace(u," "));var r=new a.Text(e);this.addNode(r),this.lastNode=r}},e.prototype.oncomment=function(e){if(this.lastNode&&this.lastNode.type===o.ElementType.Comment)this.lastNode.data+=e;else{var t=new a.Comment(e);this.addNode(t),this.lastNode=t}},e.prototype.oncommentend=function(){this.lastNode=null},e.prototype.oncdatastart=function(){var e=new a.Text(""),t=new a.NodeWithChildren(o.ElementType.CDATA,[e]);this.addNode(t),e.parent=t,this.lastNode=e},e.prototype.oncdataend=function(){this.lastNode=null},e.prototype.onprocessinginstruction=function(e,t){var n=new a.ProcessingInstruction(e,t);this.addNode(n)},e.prototype.handleCallback=function(e){if("function"==typeof this.callback)this.callback(e,this.dom);else if(e)throw e},e.prototype.addNode=function(e){var t=this.tagStack[this.tagStack.length-1],n=t.children[t.children.length-1];this.options.withStartIndices&&(e.startIndex=this.parser.startIndex),this.options.withEndIndices&&(e.endIndex=this.parser.endIndex),t.children.push(e),n&&(e.prev=n,n.next=e),e.parent=t,this.lastNode=null},e}();t.DomHandler=l,t.default=l},4091:function(e,t,n){"use strict";var r,i=this&&this.__extends||(r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),o=this&&this.__assign||function(){return o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0?this.children[this.children.length-1]:null},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"childNodes",{get:function(){return this.children},set:function(e){this.children=e},enumerable:!1,configurable:!0}),t}(s);t.NodeWithChildren=d;var h=function(e){function t(t){return e.call(this,a.ElementType.Root,t)||this}return i(t,e),t}(d);t.Document=h;var g=function(e){function t(t,n,r,i){void 0===r&&(r=[]),void 0===i&&(i="script"===t?a.ElementType.Script:"style"===t?a.ElementType.Style:a.ElementType.Tag);var o=e.call(this,i,r)||this;return o.name=t,o.attribs=n,o}return i(t,e),Object.defineProperty(t.prototype,"tagName",{get:function(){return this.name},set:function(e){this.name=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"attributes",{get:function(){var e=this;return Object.keys(this.attribs).map((function(t){var n,r;return{name:t,value:e.attribs[t],namespace:null===(n=e["x-attribsNamespace"])||void 0===n?void 0:n[t],prefix:null===(r=e["x-attribsPrefix"])||void 0===r?void 0:r[t]}}))},enumerable:!1,configurable:!0}),t}(d);function m(e){return a.isTag(e)}function v(e){return e.type===a.ElementType.CDATA}function y(e){return e.type===a.ElementType.Text}function b(e){return e.type===a.ElementType.Comment}function w(e){return e.type===a.ElementType.Directive}function D(e){return e.type===a.ElementType.Root}function x(e,t){var n;if(void 0===t&&(t=!1),y(e))n=new c(e.data);else if(b(e))n=new f(e.data);else if(m(e)){var r=t?E(e.children):[],i=new g(e.name,o({},e.attribs),r);r.forEach((function(e){return e.parent=i})),e["x-attribsNamespace"]&&(i["x-attribsNamespace"]=o({},e["x-attribsNamespace"])),e["x-attribsPrefix"]&&(i["x-attribsPrefix"]=o({},e["x-attribsPrefix"])),n=i}else if(v(e)){r=t?E(e.children):[];var u=new d(a.ElementType.CDATA,r);r.forEach((function(e){return e.parent=u})),n=u}else if(D(e)){r=t?E(e.children):[];var s=new h(r);r.forEach((function(e){return e.parent=s})),e["x-mode"]&&(s["x-mode"]=e["x-mode"]),n=s}else{if(!w(e))throw new Error("Not implemented yet: "+e.type);var l=new p(e.name,e.data);null!=e["x-name"]&&(l["x-name"]=e["x-name"],l["x-publicId"]=e["x-publicId"],l["x-systemId"]=e["x-systemId"]),n=l}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,n}function E(e){for(var t=e.map((function(e){return x(e,!0)})),n=1;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.uniqueSort=t.compareDocumentPosition=t.removeSubsets=void 0;var r=n(9628);function i(e,t){var n=[],i=[];if(e===t)return 0;for(var o=r.hasChildren(e)?e:e.parent;o;)n.unshift(o),o=o.parent;for(o=r.hasChildren(t)?t:t.parent;o;)i.unshift(o),o=o.parent;for(var a=Math.min(n.length,i.length),u=0;ul.indexOf(f)?s===t?20:4:s===e?10:2}t.removeSubsets=function(e){for(var t=e.length;--t>=0;){var n=e[t];if(t>0&&e.lastIndexOf(n,t-1)>=0)e.splice(t,1);else for(var r=n.parent;r;r=r.parent)if(e.includes(r)){e.splice(t,1);break}}return e},t.compareDocumentPosition=i,t.uniqueSort=function(e){return(e=e.filter((function(e,t,n){return!n.includes(e,t+1)}))).sort((function(e,t){var n=i(e,t);return 2&n?-1:4&n?1:0})),e}},1054:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),t.hasChildren=t.isDocument=t.isComment=t.isText=t.isCDATA=t.isTag=void 0,i(n(7310),t),i(n(2702),t),i(n(7545),t),i(n(4050),t),i(n(177),t),i(n(1907),t);var o=n(9628);Object.defineProperty(t,"isTag",{enumerable:!0,get:function(){return o.isTag}}),Object.defineProperty(t,"isCDATA",{enumerable:!0,get:function(){return o.isCDATA}}),Object.defineProperty(t,"isText",{enumerable:!0,get:function(){return o.isText}}),Object.defineProperty(t,"isComment",{enumerable:!0,get:function(){return o.isComment}}),Object.defineProperty(t,"isDocument",{enumerable:!0,get:function(){return o.isDocument}}),Object.defineProperty(t,"hasChildren",{enumerable:!0,get:function(){return o.hasChildren}})},177:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getElementsByTagType=t.getElementsByTagName=t.getElementById=t.getElements=t.testElement=void 0;var r=n(9628),i=n(4050),o={tag_name:function(e){return"function"==typeof e?function(t){return r.isTag(t)&&e(t.name)}:"*"===e?r.isTag:function(t){return r.isTag(t)&&t.name===e}},tag_type:function(e){return"function"==typeof e?function(t){return e(t.type)}:function(t){return t.type===e}},tag_contains:function(e){return"function"==typeof e?function(t){return r.isText(t)&&e(t.data)}:function(t){return r.isText(t)&&t.data===e}}};function a(e,t){return"function"==typeof t?function(n){return r.isTag(n)&&t(n.attribs[e])}:function(n){return r.isTag(n)&&n.attribs[e]===t}}function u(e,t){return function(n){return e(n)||t(n)}}function s(e){var t=Object.keys(e).map((function(t){var n=e[t];return t in o?o[t](n):a(t,n)}));return 0===t.length?null:t.reduce(u)}t.testElement=function(e,t){var n=s(e);return!n||n(t)},t.getElements=function(e,t,n,r){void 0===r&&(r=1/0);var o=s(e);return o?i.filter(o,t,n,r):[]},t.getElementById=function(e,t,n){return void 0===n&&(n=!0),Array.isArray(t)||(t=[t]),i.findOne(a("id",e),t,n)},t.getElementsByTagName=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),i.filter(o.tag_name(e),t,n,r)},t.getElementsByTagType=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),i.filter(o.tag_type(e),t,n,r)}},7545:(e,t)=>{"use strict";function n(e){if(e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent){var t=e.parent.children;t.splice(t.lastIndexOf(e),1)}}Object.defineProperty(t,"__esModule",{value:!0}),t.prepend=t.prependChild=t.append=t.appendChild=t.replaceElement=t.removeElement=void 0,t.removeElement=n,t.replaceElement=function(e,t){var n=t.prev=e.prev;n&&(n.next=t);var r=t.next=e.next;r&&(r.prev=t);var i=t.parent=e.parent;if(i){var o=i.children;o[o.lastIndexOf(e)]=t}},t.appendChild=function(e,t){if(n(t),t.next=null,t.parent=e,e.children.push(t)>1){var r=e.children[e.children.length-2];r.next=t,t.prev=r}else t.prev=null},t.append=function(e,t){n(t);var r=e.parent,i=e.next;if(t.next=i,t.prev=e,e.next=t,t.parent=r,i){if(i.prev=t,r){var o=r.children;o.splice(o.lastIndexOf(i),0,t)}}else r&&r.children.push(t)},t.prependChild=function(e,t){if(n(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)){var r=e.children[1];r.prev=t,t.next=r}else t.next=null},t.prepend=function(e,t){n(t);var r=e.parent;if(r){var i=r.children;i.splice(i.indexOf(e),0,t)}e.prev&&(e.prev.next=t),t.parent=r,t.prev=e.prev,t.next=e,e.prev=t}},4050:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.findAll=t.existsOne=t.findOne=t.findOneChild=t.find=t.filter=void 0;var r=n(9628);function i(e,t,n,o){for(var a=[],u=0,s=t;u0){var c=i(e,l.children,n,o);if(a.push.apply(a,c),(o-=c.length)<=0)break}}return a}t.filter=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),Array.isArray(t)||(t=[t]),i(e,t,n,r)},t.find=i,t.findOneChild=function(e,t){return t.find(e)},t.findOne=function e(t,n,i){void 0===i&&(i=!0);for(var o=null,a=0;a0&&(o=e(t,u.children)))}return o},t.existsOne=function e(t,n){return n.some((function(n){return r.isTag(n)&&(t(n)||n.children.length>0&&e(t,n.children))}))},t.findAll=function(e,t){for(var n,i,o=[],a=t.filter(r.isTag);i=a.shift();){var u=null===(n=i.children)||void 0===n?void 0:n.filter(r.isTag);u&&u.length>0&&a.unshift.apply(a,u),e(i)&&o.push(i)}return o}},7310:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getText=t.getInnerHTML=t.getOuterHTML=void 0;var i=n(9628),o=r(n(6380));function a(e,t){return o.default(e,t)}t.getOuterHTML=a,t.getInnerHTML=function(e,t){return i.hasChildren(e)?e.children.map((function(e){return a(e,t)})).join(""):""},t.getText=function e(t){return Array.isArray(t)?t.map(e).join(""):i.isTag(t)?"br"===t.name?"\n":e(t.children):i.isCDATA(t)?e(t.children):i.isText(t)?t.data:""}},2702:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.prevElementSibling=t.nextElementSibling=t.getName=t.hasAttrib=t.getAttributeValue=t.getSiblings=t.getParent=t.getChildren=void 0;var r=n(9628),i=[];function o(e){var t;return null!==(t=e.children)&&void 0!==t?t:i}function a(e){return e.parent||null}t.getChildren=o,t.getParent=a,t.getSiblings=function(e){var t=a(e);if(null!=t)return o(t);for(var n=[e],r=e.prev,i=e.next;null!=r;)n.unshift(r),r=r.prev;for(;null!=i;)n.push(i),i=i.next;return n},t.getAttributeValue=function(e,t){var n;return null===(n=e.attribs)||void 0===n?void 0:n[t]},t.hasAttrib=function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},t.getName=function(e){return e.name},t.nextElementSibling=function(e){for(var t=e.next;null!==t&&!r.isTag(t);)t=t.next;return t},t.prevElementSibling=function(e){for(var t=e.prev;null!==t&&!r.isTag(t);)t=t.prev;return t}},6136:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.decodeHTML=t.decodeHTMLStrict=t.decodeXML=void 0;var i=r(n(9323)),o=r(n(9591)),a=r(n(2586)),u=r(n(647)),s=/&(?:[a-zA-Z0-9]+|#[xX][\da-fA-F]+|#\d+);/g;function l(e){var t=f(e);return function(e){return String(e).replace(s,t)}}t.decodeXML=l(a.default),t.decodeHTMLStrict=l(i.default);var c=function(e,t){return e65535&&(e-=65536,t+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t+String.fromCharCode(e)};t.default=function(e){return e>=55296&&e<=57343||e>1114111?"�":(e in i.default&&(e=i.default[e]),o(e))}},7296:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.escapeUTF8=t.escape=t.encodeNonAsciiHTML=t.encodeHTML=t.encodeXML=void 0;var i=c(r(n(2586)).default),o=f(i);t.encodeXML=m(i);var a,u,s=c(r(n(9323)).default),l=f(s);function c(e){return Object.keys(e).sort().reduce((function(t,n){return t[e[n]]="&"+n+";",t}),{})}function f(e){for(var t=[],n=[],r=0,i=Object.keys(e);r1?d(e):e.charCodeAt(0)).toString(16).toUpperCase()+";"}var g=new RegExp(o.source+"|"+p.source,"g");function m(e){return function(t){return t.replace(g,(function(t){return e[t]||h(t)}))}}t.escape=function(e){return e.replace(g,h)},t.escapeUTF8=function(e){return e.replace(o,h)}},734:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXMLStrict=t.decodeHTML5Strict=t.decodeHTML4Strict=t.decodeHTML5=t.decodeHTML4=t.decodeHTMLStrict=t.decodeHTML=t.decodeXML=t.encodeHTML5=t.encodeHTML4=t.escapeUTF8=t.escape=t.encodeNonAsciiHTML=t.encodeHTML=t.encodeXML=t.encode=t.decodeStrict=t.decode=void 0;var r=n(6136),i=n(7296);t.decode=function(e,t){return(!t||t<=0?r.decodeXML:r.decodeHTML)(e)},t.decodeStrict=function(e,t){return(!t||t<=0?r.decodeXML:r.decodeHTMLStrict)(e)},t.encode=function(e,t){return(!t||t<=0?i.encodeXML:i.encodeHTML)(e)};var o=n(7296);Object.defineProperty(t,"encodeXML",{enumerable:!0,get:function(){return o.encodeXML}}),Object.defineProperty(t,"encodeHTML",{enumerable:!0,get:function(){return o.encodeHTML}}),Object.defineProperty(t,"encodeNonAsciiHTML",{enumerable:!0,get:function(){return o.encodeNonAsciiHTML}}),Object.defineProperty(t,"escape",{enumerable:!0,get:function(){return o.escape}}),Object.defineProperty(t,"escapeUTF8",{enumerable:!0,get:function(){return o.escapeUTF8}}),Object.defineProperty(t,"encodeHTML4",{enumerable:!0,get:function(){return o.encodeHTML}}),Object.defineProperty(t,"encodeHTML5",{enumerable:!0,get:function(){return o.encodeHTML}});var a=n(6136);Object.defineProperty(t,"decodeXML",{enumerable:!0,get:function(){return a.decodeXML}}),Object.defineProperty(t,"decodeHTML",{enumerable:!0,get:function(){return a.decodeHTML}}),Object.defineProperty(t,"decodeHTMLStrict",{enumerable:!0,get:function(){return a.decodeHTMLStrict}}),Object.defineProperty(t,"decodeHTML4",{enumerable:!0,get:function(){return a.decodeHTML}}),Object.defineProperty(t,"decodeHTML5",{enumerable:!0,get:function(){return a.decodeHTML}}),Object.defineProperty(t,"decodeHTML4Strict",{enumerable:!0,get:function(){return a.decodeHTMLStrict}}),Object.defineProperty(t,"decodeHTML5Strict",{enumerable:!0,get:function(){return a.decodeHTMLStrict}}),Object.defineProperty(t,"decodeXMLStrict",{enumerable:!0,get:function(){return a.decodeXML}})},6308:(e,t,n)=>{"use strict";var r=n(1498),i={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},a={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},u={};function s(e){return r.isMemo(e)?a:u[e.$$typeof]||i}u[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},u[r.Memo]=a;var l=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,d=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(h){var i=d(n);i&&i!==h&&e(t,i,r)}var a=c(n);f&&(a=a.concat(f(n)));for(var u=s(t),g=s(n),m=0;m0&&u[e].has(r=this.stack[this.stack.length-1]);)this.onclosetag(r);!this.options.xmlMode&&s.has(e)||(this.stack.push(e),l.has(e)?this.foreignContext.push(!0):c.has(e)&&this.foreignContext.push(!1)),null===(n=(t=this.cbs).onopentagname)||void 0===n||n.call(t,e),this.cbs.onopentag&&(this.attribs={})},e.prototype.onopentagend=function(){var e,t;this.updatePosition(1),this.attribs&&(null===(t=(e=this.cbs).onopentag)||void 0===t||t.call(e,this.tagname,this.attribs),this.attribs=null),!this.options.xmlMode&&this.cbs.onclosetag&&s.has(this.tagname)&&this.cbs.onclosetag(this.tagname),this.tagname=""},e.prototype.onclosetag=function(e){if(this.updatePosition(1),this.lowerCaseTagNames&&(e=e.toLowerCase()),(l.has(e)||c.has(e))&&this.foreignContext.pop(),!this.stack.length||!this.options.xmlMode&&s.has(e))this.options.xmlMode||"br"!==e&&"p"!==e||(this.onopentagname(e),this.closeCurrentTag());else{var t=this.stack.lastIndexOf(e);if(-1!==t)if(this.cbs.onclosetag)for(t=this.stack.length-t;t--;)this.cbs.onclosetag(this.stack.pop());else this.stack.length=t;else"p"!==e||this.options.xmlMode||(this.onopentagname(e),this.closeCurrentTag())}},e.prototype.onselfclosingtag=function(){this.options.xmlMode||this.options.recognizeSelfClosing||this.foreignContext[this.foreignContext.length-1]?this.closeCurrentTag():this.onopentagend()},e.prototype.closeCurrentTag=function(){var e,t,n=this.tagname;this.onopentagend(),this.stack[this.stack.length-1]===n&&(null===(t=(e=this.cbs).onclosetag)||void 0===t||t.call(e,n),this.stack.pop())},e.prototype.onattribname=function(e){this.lowerCaseAttributeNames&&(e=e.toLowerCase()),this.attribname=e},e.prototype.onattribdata=function(e){this.attribvalue+=e},e.prototype.onattribend=function(e){var t,n;null===(n=(t=this.cbs).onattribute)||void 0===n||n.call(t,this.attribname,this.attribvalue,e),this.attribs&&!Object.prototype.hasOwnProperty.call(this.attribs,this.attribname)&&(this.attribs[this.attribname]=this.attribvalue),this.attribname="",this.attribvalue=""},e.prototype.getInstructionName=function(e){var t=e.search(f),n=t<0?e:e.substr(0,t);return this.lowerCaseTagNames&&(n=n.toLowerCase()),n},e.prototype.ondeclaration=function(e){if(this.cbs.onprocessinginstruction){var t=this.getInstructionName(e);this.cbs.onprocessinginstruction("!"+t,"!"+e)}},e.prototype.onprocessinginstruction=function(e){if(this.cbs.onprocessinginstruction){var t=this.getInstructionName(e);this.cbs.onprocessinginstruction("?"+t,"?"+e)}},e.prototype.oncomment=function(e){var t,n,r,i;this.updatePosition(4),null===(n=(t=this.cbs).oncomment)||void 0===n||n.call(t,e),null===(i=(r=this.cbs).oncommentend)||void 0===i||i.call(r)},e.prototype.oncdata=function(e){var t,n,r,i,o,a;this.updatePosition(1),this.options.xmlMode||this.options.recognizeCDATA?(null===(n=(t=this.cbs).oncdatastart)||void 0===n||n.call(t),null===(i=(r=this.cbs).ontext)||void 0===i||i.call(r,e),null===(a=(o=this.cbs).oncdataend)||void 0===a||a.call(o)):this.oncomment("[CDATA["+e+"]]")},e.prototype.onerror=function(e){var t,n;null===(n=(t=this.cbs).onerror)||void 0===n||n.call(t,e)},e.prototype.onend=function(){var e,t;if(this.cbs.onclosetag)for(var n=this.stack.length;n>0;this.cbs.onclosetag(this.stack[--n]));null===(t=(e=this.cbs).onend)||void 0===t||t.call(e)},e.prototype.reset=function(){var e,t,n,r;null===(t=(e=this.cbs).onreset)||void 0===t||t.call(e),this.tokenizer.reset(),this.tagname="",this.attribname="",this.attribs=null,this.stack=[],null===(r=(n=this.cbs).onparserinit)||void 0===r||r.call(n,this)},e.prototype.parseComplete=function(e){this.reset(),this.end(e)},e.prototype.write=function(e){this.tokenizer.write(e)},e.prototype.end=function(e){this.tokenizer.end(e)},e.prototype.pause=function(){this.tokenizer.pause()},e.prototype.resume=function(){this.tokenizer.resume()},e.prototype.parseChunk=function(e){this.write(e)},e.prototype.done=function(e){this.end(e)},e}();t.Parser=p},9282:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var i=r(n(647)),o=r(n(9323)),a=r(n(9591)),u=r(n(2586));function s(e){return" "===e||"\n"===e||"\t"===e||"\f"===e||"\r"===e}function l(e){return e>="a"&&e<="z"||e>="A"&&e<="Z"}function c(e,t,n){var r=e.toLowerCase();return e===r?function(e,i){i===r?e._state=t:(e._state=n,e._index--)}:function(i,o){o===r||o===e?i._state=t:(i._state=n,i._index--)}}function f(e,t){var n=e.toLowerCase();return function(r,i){i===n||i===e?r._state=t:(r._state=3,r._index--)}}var p=c("C",24,16),d=c("D",25,16),h=c("A",26,16),g=c("T",27,16),m=c("A",28,16),v=f("R",35),y=f("I",36),b=f("P",37),w=f("T",38),D=c("R",40,1),x=c("I",41,1),E=c("P",42,1),_=c("T",43,1),C=f("Y",45),k=f("L",46),A=f("E",47),S=c("Y",49,1),F=c("L",50,1),O=c("E",51,1),T=f("I",54),P=f("T",55),B=f("L",56),N=f("E",57),R=c("I",58,1),j=c("T",59,1),L=c("L",60,1),M=c("E",61,1),I=c("#",63,64),z=c("X",66,65),U=function(){function e(e,t){var n;this._state=1,this.buffer="",this.sectionStart=0,this._index=0,this.bufferOffset=0,this.baseState=1,this.special=1,this.running=!0,this.ended=!1,this.cbs=t,this.xmlMode=!!(null==e?void 0:e.xmlMode),this.decodeEntities=null===(n=null==e?void 0:e.decodeEntities)||void 0===n||n}return e.prototype.reset=function(){this._state=1,this.buffer="",this.sectionStart=0,this._index=0,this.bufferOffset=0,this.baseState=1,this.special=1,this.running=!0,this.ended=!1},e.prototype.write=function(e){this.ended&&this.cbs.onerror(Error(".write() after done!")),this.buffer+=e,this.parse()},e.prototype.end=function(e){this.ended&&this.cbs.onerror(Error(".end() after done!")),e&&this.write(e),this.ended=!0,this.running&&this.finish()},e.prototype.pause=function(){this.running=!1},e.prototype.resume=function(){this.running=!0,this._indexthis.sectionStart&&this.cbs.ontext(this.getSection()),this._state=2,this.sectionStart=this._index):!this.decodeEntities||"&"!==e||1!==this.special&&4!==this.special||(this._index>this.sectionStart&&this.cbs.ontext(this.getSection()),this.baseState=1,this._state=62,this.sectionStart=this._index)},e.prototype.isTagStartChar=function(e){return l(e)||this.xmlMode&&!s(e)&&"/"!==e&&">"!==e},e.prototype.stateBeforeTagName=function(e){"/"===e?this._state=5:"<"===e?(this.cbs.ontext(this.getSection()),this.sectionStart=this._index):">"===e||1!==this.special||s(e)?this._state=1:"!"===e?(this._state=15,this.sectionStart=this._index+1):"?"===e?(this._state=17,this.sectionStart=this._index+1):this.isTagStartChar(e)?(this._state=this.xmlMode||"s"!==e&&"S"!==e?this.xmlMode||"t"!==e&&"T"!==e?3:52:32,this.sectionStart=this._index):this._state=1},e.prototype.stateInTagName=function(e){("/"===e||">"===e||s(e))&&(this.emitToken("onopentagname"),this._state=8,this._index--)},e.prototype.stateBeforeClosingTagName=function(e){s(e)||(">"===e?this._state=1:1!==this.special?4===this.special||"s"!==e&&"S"!==e?4!==this.special||"t"!==e&&"T"!==e?(this._state=1,this._index--):this._state=53:this._state=33:this.isTagStartChar(e)?(this._state=6,this.sectionStart=this._index):(this._state=20,this.sectionStart=this._index))},e.prototype.stateInClosingTagName=function(e){(">"===e||s(e))&&(this.emitToken("onclosetag"),this._state=7,this._index--)},e.prototype.stateAfterClosingTagName=function(e){">"===e&&(this._state=1,this.sectionStart=this._index+1)},e.prototype.stateBeforeAttributeName=function(e){">"===e?(this.cbs.onopentagend(),this._state=1,this.sectionStart=this._index+1):"/"===e?this._state=4:s(e)||(this._state=9,this.sectionStart=this._index)},e.prototype.stateInSelfClosingTag=function(e){">"===e?(this.cbs.onselfclosingtag(),this._state=1,this.sectionStart=this._index+1,this.special=1):s(e)||(this._state=8,this._index--)},e.prototype.stateInAttributeName=function(e){("="===e||"/"===e||">"===e||s(e))&&(this.cbs.onattribname(this.getSection()),this.sectionStart=-1,this._state=10,this._index--)},e.prototype.stateAfterAttributeName=function(e){"="===e?this._state=11:"/"===e||">"===e?(this.cbs.onattribend(void 0),this._state=8,this._index--):s(e)||(this.cbs.onattribend(void 0),this._state=9,this.sectionStart=this._index)},e.prototype.stateBeforeAttributeValue=function(e){'"'===e?(this._state=12,this.sectionStart=this._index+1):"'"===e?(this._state=13,this.sectionStart=this._index+1):s(e)||(this._state=14,this.sectionStart=this._index,this._index--)},e.prototype.handleInAttributeValue=function(e,t){e===t?(this.emitToken("onattribdata"),this.cbs.onattribend(t),this._state=8):this.decodeEntities&&"&"===e&&(this.emitToken("onattribdata"),this.baseState=this._state,this._state=62,this.sectionStart=this._index)},e.prototype.stateInAttributeValueDoubleQuotes=function(e){this.handleInAttributeValue(e,'"')},e.prototype.stateInAttributeValueSingleQuotes=function(e){this.handleInAttributeValue(e,"'")},e.prototype.stateInAttributeValueNoQuotes=function(e){s(e)||">"===e?(this.emitToken("onattribdata"),this.cbs.onattribend(null),this._state=8,this._index--):this.decodeEntities&&"&"===e&&(this.emitToken("onattribdata"),this.baseState=this._state,this._state=62,this.sectionStart=this._index)},e.prototype.stateBeforeDeclaration=function(e){this._state="["===e?23:"-"===e?18:16},e.prototype.stateInDeclaration=function(e){">"===e&&(this.cbs.ondeclaration(this.getSection()),this._state=1,this.sectionStart=this._index+1)},e.prototype.stateInProcessingInstruction=function(e){">"===e&&(this.cbs.onprocessinginstruction(this.getSection()),this._state=1,this.sectionStart=this._index+1)},e.prototype.stateBeforeComment=function(e){"-"===e?(this._state=19,this.sectionStart=this._index+1):this._state=16},e.prototype.stateInComment=function(e){"-"===e&&(this._state=21)},e.prototype.stateInSpecialComment=function(e){">"===e&&(this.cbs.oncomment(this.buffer.substring(this.sectionStart,this._index)),this._state=1,this.sectionStart=this._index+1)},e.prototype.stateAfterComment1=function(e){this._state="-"===e?22:19},e.prototype.stateAfterComment2=function(e){">"===e?(this.cbs.oncomment(this.buffer.substring(this.sectionStart,this._index-2)),this._state=1,this.sectionStart=this._index+1):"-"!==e&&(this._state=19)},e.prototype.stateBeforeCdata6=function(e){"["===e?(this._state=29,this.sectionStart=this._index+1):(this._state=16,this._index--)},e.prototype.stateInCdata=function(e){"]"===e&&(this._state=30)},e.prototype.stateAfterCdata1=function(e){this._state="]"===e?31:29},e.prototype.stateAfterCdata2=function(e){">"===e?(this.cbs.oncdata(this.buffer.substring(this.sectionStart,this._index-2)),this._state=1,this.sectionStart=this._index+1):"]"!==e&&(this._state=29)},e.prototype.stateBeforeSpecialS=function(e){"c"===e||"C"===e?this._state=34:"t"===e||"T"===e?this._state=44:(this._state=3,this._index--)},e.prototype.stateBeforeSpecialSEnd=function(e){2!==this.special||"c"!==e&&"C"!==e?3!==this.special||"t"!==e&&"T"!==e?this._state=1:this._state=48:this._state=39},e.prototype.stateBeforeSpecialLast=function(e,t){("/"===e||">"===e||s(e))&&(this.special=t),this._state=3,this._index--},e.prototype.stateAfterSpecialLast=function(e,t){">"===e||s(e)?(this.special=1,this._state=6,this.sectionStart=this._index-t,this._index--):this._state=1},e.prototype.parseFixedEntity=function(e){if(void 0===e&&(e=this.xmlMode?u.default:o.default),this.sectionStart+1=2;){var n=this.buffer.substr(e,t);if(Object.prototype.hasOwnProperty.call(a.default,n))return this.emitPartial(a.default[n]),void(this.sectionStart+=t+1);t--}},e.prototype.stateInNamedEntity=function(e){";"===e?(this.parseFixedEntity(),1===this.baseState&&this.sectionStart+1"9")&&!l(e)&&(this.xmlMode||this.sectionStart+1===this._index||(1!==this.baseState?"="!==e&&this.parseFixedEntity(a.default):this.parseLegacyEntity()),this._state=this.baseState,this._index--)},e.prototype.decodeNumericEntity=function(e,t,n){var r=this.sectionStart+e;if(r!==this._index){var o=this.buffer.substring(r,this._index),a=parseInt(o,t);this.emitPartial(i.default(a)),this.sectionStart=n?this._index+1:this._index}this._state=this.baseState},e.prototype.stateInNumericEntity=function(e){";"===e?this.decodeNumericEntity(2,10,!0):(e<"0"||e>"9")&&(this.xmlMode?this._state=this.baseState:this.decodeNumericEntity(2,10,!1),this._index--)},e.prototype.stateInHexEntity=function(e){";"===e?this.decodeNumericEntity(3,16,!0):(e<"a"||e>"f")&&(e<"A"||e>"F")&&(e<"0"||e>"9")&&(this.xmlMode?this._state=this.baseState:this.decodeNumericEntity(3,16,!1),this._index--)},e.prototype.cleanup=function(){this.sectionStart<0?(this.buffer="",this.bufferOffset+=this._index,this._index=0):this.running&&(1===this._state?(this.sectionStart!==this._index&&this.cbs.ontext(this.buffer.substr(this.sectionStart)),this.buffer="",this.bufferOffset+=this._index,this._index=0):this.sectionStart===this._index?(this.buffer="",this.bufferOffset+=this._index,this._index=0):(this.buffer=this.buffer.substr(this.sectionStart),this._index-=this.sectionStart,this.bufferOffset+=this.sectionStart),this.sectionStart=0)},e.prototype.parse=function(){for(;this._index{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},2417:function(e){e.exports=function(){"use strict";function e(e,t){return e(t={exports:{}},t.exports),t.exports}var t=e((function(e){var t=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=t)})),n=e((function(e){var t=e.exports={version:"2.6.5"};"number"==typeof __e&&(__e=t)})),r=(n.version,function(e){return"object"==typeof e?null!==e:"function"==typeof e}),i=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e},o=function(e){try{return!!e()}catch(e){return!0}},a=!o((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})),u=t.document,s=r(u)&&r(u.createElement),l=!a&&!o((function(){return 7!=Object.defineProperty((e="div",s?u.createElement(e):{}),"a",{get:function(){return 7}}).a;var e})),c=Object.defineProperty,f={f:a?Object.defineProperty:function(e,t,n){if(i(e),t=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}(t,!0),i(n),l)try{return c(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},p=a?function(e,t,n){return f.f(e,t,function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}(1,n))}:function(e,t,n){return e[t]=n,e},d={}.hasOwnProperty,h=function(e,t){return d.call(e,t)},g=0,m=Math.random(),v=e((function(e){var r="__core-js_shared__",i=t[r]||(t[r]={});(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:n.version,mode:"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})})),y=v("native-function-to-string",Function.toString),b=e((function(e){var r=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++g+m).toString(36))}("src"),i="toString",o=(""+y).split(i);n.inspectSource=function(e){return y.call(e)},(e.exports=function(e,n,i,a){var u="function"==typeof i;u&&(h(i,"name")||p(i,"name",n)),e[n]!==i&&(u&&(h(i,r)||p(i,r,e[n]?""+e[n]:o.join(String(n)))),e===t?e[n]=i:a?e[n]?e[n]=i:p(e,n,i):(delete e[n],p(e,n,i)))})(Function.prototype,i,(function(){return"function"==typeof this&&this[r]||y.call(this)}))})),w=function(e,t,n){if(function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!")}(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}},D=function(e,r,i){var o,a,u,s,l=e&D.F,c=e&D.G,f=e&D.S,d=e&D.P,h=e&D.B,g=c?t:f?t[r]||(t[r]={}):(t[r]||{}).prototype,m=c?n:n[r]||(n[r]={}),v=m.prototype||(m.prototype={});for(o in c&&(i=r),i)u=((a=!l&&g&&void 0!==g[o])?g:i)[o],s=h&&a?w(u,t):d&&"function"==typeof u?w(Function.call,u):u,g&&b(g,o,u,e&D.U),m[o]!=u&&p(m,o,s),d&&v[o]!=u&&(v[o]=u)};t.core=n,D.F=1,D.G=2,D.S=4,D.P=8,D.B=16,D.W=32,D.U=64,D.R=128;var x,E=D,_=Math.ceil,C=Math.floor,k=function(e){return isNaN(e=+e)?0:(e>0?C:_)(e)},A=(x=!1,function(e,t){var n,r,i=String(function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}(e)),o=k(t),a=i.length;return o<0||o>=a?x?"":void 0:(n=i.charCodeAt(o))<55296||n>56319||o+1===a||(r=i.charCodeAt(o+1))<56320||r>57343?x?i.charAt(o):n:x?i.slice(o,o+2):r-56320+(n-55296<<10)+65536});E(E.P,"String",{codePointAt:function(e){return A(this,e)}}),n.String.codePointAt;var S=Math.max,F=Math.min,O=function(e,t){return(e=k(e))<0?S(e+t,0):F(e,t)},T=String.fromCharCode,P=String.fromCodePoint;E(E.S+E.F*(!!P&&1!=P.length),"String",{fromCodePoint:function(e){for(var t,n=arguments,r=[],i=arguments.length,o=0;i>o;){if(t=+n[o++],O(t,1114111)!==t)throw RangeError(t+" is not a valid code point");r.push(t<65536?T(t):T(55296+((t-=65536)>>10),t%1024+56320))}return r.join("")}}),n.String.fromCodePoint;var B,N,R,j,L,M,I,z,U,q,H,V,W,$,G={Space_Separator:/[\u1680\u2000-\u200A\u202F\u205F\u3000]/,ID_Start:/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE83\uDE86-\uDE89\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/,ID_Continue:/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF9\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDE00-\uDE3E\uDE47\uDE50-\uDE83\uDE86-\uDE99\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/},X=function(e){return"string"==typeof e&&G.Space_Separator.test(e)},K=function(e){return"string"==typeof e&&(e>="a"&&e<="z"||e>="A"&&e<="Z"||"$"===e||"_"===e||G.ID_Start.test(e))},Q=function(e){return"string"==typeof e&&(e>="a"&&e<="z"||e>="A"&&e<="Z"||e>="0"&&e<="9"||"$"===e||"_"===e||"‌"===e||"‍"===e||G.ID_Continue.test(e))},Y=function(e){return"string"==typeof e&&/[0-9]/.test(e)},J=function(e){return"string"==typeof e&&/[0-9A-Fa-f]/.test(e)};function Z(e,t,n){var r=e[t];if(null!=r&&"object"==typeof r)for(var i in r){var o=Z(r,i,n);void 0===o?delete r[i]:r[i]=o}return n.call(e,t,r)}function ee(){for(q="default",H="",V=!1,W=1;;){$=te();var e=re[q]();if(e)return e}}function te(){if(B[j])return String.fromCodePoint(B.codePointAt(j))}function ne(){var e=te();return"\n"===e?(L++,M=0):e?M+=e.length:M++,e&&(j+=e.length),e}var re={default:function(){switch($){case"\t":case"\v":case"\f":case" ":case" ":case"\ufeff":case"\n":case"\r":case"\u2028":case"\u2029":return void ne();case"/":return ne(),void(q="comment");case void 0:return ne(),ie("eof")}if(!X($))return re[N]();ne()},comment:function(){switch($){case"*":return ne(),void(q="multiLineComment");case"/":return ne(),void(q="singleLineComment")}throw ce(ne())},multiLineComment:function(){switch($){case"*":return ne(),void(q="multiLineCommentAsterisk");case void 0:throw ce(ne())}ne()},multiLineCommentAsterisk:function(){switch($){case"*":return void ne();case"/":return ne(),void(q="default");case void 0:throw ce(ne())}ne(),q="multiLineComment"},singleLineComment:function(){switch($){case"\n":case"\r":case"\u2028":case"\u2029":return ne(),void(q="default");case void 0:return ne(),ie("eof")}ne()},value:function(){switch($){case"{":case"[":return ie("punctuator",ne());case"n":return ne(),oe("ull"),ie("null",null);case"t":return ne(),oe("rue"),ie("boolean",!0);case"f":return ne(),oe("alse"),ie("boolean",!1);case"-":case"+":return"-"===ne()&&(W=-1),void(q="sign");case".":return H=ne(),void(q="decimalPointLeading");case"0":return H=ne(),void(q="zero");case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return H=ne(),void(q="decimalInteger");case"I":return ne(),oe("nfinity"),ie("numeric",1/0);case"N":return ne(),oe("aN"),ie("numeric",NaN);case'"':case"'":return V='"'===ne(),H="",void(q="string")}throw ce(ne())},identifierNameStartEscape:function(){if("u"!==$)throw ce(ne());ne();var e=ae();switch(e){case"$":case"_":break;default:if(!K(e))throw pe()}H+=e,q="identifierName"},identifierName:function(){switch($){case"$":case"_":case"‌":case"‍":return void(H+=ne());case"\\":return ne(),void(q="identifierNameEscape")}if(!Q($))return ie("identifier",H);H+=ne()},identifierNameEscape:function(){if("u"!==$)throw ce(ne());ne();var e=ae();switch(e){case"$":case"_":case"‌":case"‍":break;default:if(!Q(e))throw pe()}H+=e,q="identifierName"},sign:function(){switch($){case".":return H=ne(),void(q="decimalPointLeading");case"0":return H=ne(),void(q="zero");case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return H=ne(),void(q="decimalInteger");case"I":return ne(),oe("nfinity"),ie("numeric",W*(1/0));case"N":return ne(),oe("aN"),ie("numeric",NaN)}throw ce(ne())},zero:function(){switch($){case".":return H+=ne(),void(q="decimalPoint");case"e":case"E":return H+=ne(),void(q="decimalExponent");case"x":case"X":return H+=ne(),void(q="hexadecimal")}return ie("numeric",0*W)},decimalInteger:function(){switch($){case".":return H+=ne(),void(q="decimalPoint");case"e":case"E":return H+=ne(),void(q="decimalExponent")}if(!Y($))return ie("numeric",W*Number(H));H+=ne()},decimalPointLeading:function(){if(Y($))return H+=ne(),void(q="decimalFraction");throw ce(ne())},decimalPoint:function(){switch($){case"e":case"E":return H+=ne(),void(q="decimalExponent")}return Y($)?(H+=ne(),void(q="decimalFraction")):ie("numeric",W*Number(H))},decimalFraction:function(){switch($){case"e":case"E":return H+=ne(),void(q="decimalExponent")}if(!Y($))return ie("numeric",W*Number(H));H+=ne()},decimalExponent:function(){switch($){case"+":case"-":return H+=ne(),void(q="decimalExponentSign")}if(Y($))return H+=ne(),void(q="decimalExponentInteger");throw ce(ne())},decimalExponentSign:function(){if(Y($))return H+=ne(),void(q="decimalExponentInteger");throw ce(ne())},decimalExponentInteger:function(){if(!Y($))return ie("numeric",W*Number(H));H+=ne()},hexadecimal:function(){if(J($))return H+=ne(),void(q="hexadecimalInteger");throw ce(ne())},hexadecimalInteger:function(){if(!J($))return ie("numeric",W*Number(H));H+=ne()},string:function(){switch($){case"\\":return ne(),void(H+=function(){switch(te()){case"b":return ne(),"\b";case"f":return ne(),"\f";case"n":return ne(),"\n";case"r":return ne(),"\r";case"t":return ne(),"\t";case"v":return ne(),"\v";case"0":if(ne(),Y(te()))throw ce(ne());return"\0";case"x":return ne(),function(){var e="",t=te();if(!J(t))throw ce(ne());if(e+=ne(),t=te(),!J(t))throw ce(ne());return e+=ne(),String.fromCodePoint(parseInt(e,16))}();case"u":return ne(),ae();case"\n":case"\u2028":case"\u2029":return ne(),"";case"\r":return ne(),"\n"===te()&&ne(),"";case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":case void 0:throw ce(ne())}return ne()}());case'"':return V?(ne(),ie("string",H)):void(H+=ne());case"'":return V?void(H+=ne()):(ne(),ie("string",H));case"\n":case"\r":throw ce(ne());case"\u2028":case"\u2029":!function(e){console.warn("JSON5: '"+de(e)+"' in strings is not valid ECMAScript; consider escaping")}($);break;case void 0:throw ce(ne())}H+=ne()},start:function(){switch($){case"{":case"[":return ie("punctuator",ne())}q="value"},beforePropertyName:function(){switch($){case"$":case"_":return H=ne(),void(q="identifierName");case"\\":return ne(),void(q="identifierNameStartEscape");case"}":return ie("punctuator",ne());case'"':case"'":return V='"'===ne(),void(q="string")}if(K($))return H+=ne(),void(q="identifierName");throw ce(ne())},afterPropertyName:function(){if(":"===$)return ie("punctuator",ne());throw ce(ne())},beforePropertyValue:function(){q="value"},afterPropertyValue:function(){switch($){case",":case"}":return ie("punctuator",ne())}throw ce(ne())},beforeArrayValue:function(){if("]"===$)return ie("punctuator",ne());q="value"},afterArrayValue:function(){switch($){case",":case"]":return ie("punctuator",ne())}throw ce(ne())},end:function(){throw ce(ne())}};function ie(e,t){return{type:e,value:t,line:L,column:M}}function oe(e){for(var t=0,n=e;t0;){var n=te();if(!J(n))throw ce(ne());e+=ne()}return String.fromCodePoint(parseInt(e,16))}var ue={start:function(){if("eof"===I.type)throw fe();se()},beforePropertyName:function(){switch(I.type){case"identifier":case"string":return z=I.value,void(N="afterPropertyName");case"punctuator":return void le();case"eof":throw fe()}},afterPropertyName:function(){if("eof"===I.type)throw fe();N="beforePropertyValue"},beforePropertyValue:function(){if("eof"===I.type)throw fe();se()},beforeArrayValue:function(){if("eof"===I.type)throw fe();"punctuator"!==I.type||"]"!==I.value?se():le()},afterPropertyValue:function(){if("eof"===I.type)throw fe();switch(I.value){case",":return void(N="beforePropertyName");case"}":le()}},afterArrayValue:function(){if("eof"===I.type)throw fe();switch(I.value){case",":return void(N="beforeArrayValue");case"]":le()}},end:function(){}};function se(){var e;switch(I.type){case"punctuator":switch(I.value){case"{":e={};break;case"[":e=[]}break;case"null":case"boolean":case"numeric":case"string":e=I.value}if(void 0===U)U=e;else{var t=R[R.length-1];Array.isArray(t)?t.push(e):t[z]=e}if(null!==e&&"object"==typeof e)R.push(e),N=Array.isArray(e)?"beforeArrayValue":"beforePropertyName";else{var n=R[R.length-1];N=null==n?"end":Array.isArray(n)?"afterArrayValue":"afterPropertyValue"}}function le(){R.pop();var e=R[R.length-1];N=null==e?"end":Array.isArray(e)?"afterArrayValue":"afterPropertyValue"}function ce(e){return he(void 0===e?"JSON5: invalid end of input at "+L+":"+M:"JSON5: invalid character '"+de(e)+"' at "+L+":"+M)}function fe(){return he("JSON5: invalid end of input at "+L+":"+M)}function pe(){return he("JSON5: invalid identifier character at "+L+":"+(M-=5))}function de(e){var t={"'":"\\'",'"':'\\"',"\\":"\\\\","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\v":"\\v","\0":"\\0","\u2028":"\\u2028","\u2029":"\\u2029"};if(t[e])return t[e];if(e<" "){var n=e.charCodeAt(0).toString(16);return"\\x"+("00"+n).substring(n.length)}return e}function he(e){var t=new SyntaxError(e);return t.lineNumber=L,t.columnNumber=M,t}return{parse:function(e,t){B=String(e),N="start",R=[],j=0,L=1,M=0,I=void 0,z=void 0,U=void 0;do{I=ee(),ue[N]()}while("eof"!==I.type);return"function"==typeof t?Z({"":U},"",t):U},stringify:function(e,t,n){var r,i,o,a=[],u="",s="";if(null==t||"object"!=typeof t||Array.isArray(t)||(n=t.space,o=t.quote,t=t.replacer),"function"==typeof t)i=t;else if(Array.isArray(t)){r=[];for(var l=0,c=t;l0&&(n=Math.min(10,Math.floor(n)),s=" ".substr(0,n)):"string"==typeof n&&(s=n.substr(0,10)),d("",{"":e});function d(e,t){var n=t[e];switch(null!=n&&("function"==typeof n.toJSON5?n=n.toJSON5(e):"function"==typeof n.toJSON&&(n=n.toJSON(e))),i&&(n=i.call(t,e,n)),n instanceof Number?n=Number(n):n instanceof String?n=String(n):n instanceof Boolean&&(n=n.valueOf()),n){case null:return"null";case!0:return"true";case!1:return"false"}return"string"==typeof n?h(n):"number"==typeof n?String(n):"object"==typeof n?Array.isArray(n)?function(e){if(a.indexOf(e)>=0)throw TypeError("Converting circular structure to JSON5");a.push(e);var t=u;u+=s;for(var n,r=[],i=0;i=0)throw TypeError("Converting circular structure to JSON5");a.push(e);var t=u;u+=s;for(var n,i,o=[],l=0,c=r||Object.keys(e);l{t.klona=function e(t){if("object"!=typeof t)return t;var n,r,i=Object.prototype.toString.call(t);if("[object Object]"===i){if(t.constructor!==Object&&"function"==typeof t.constructor)for(n in r=new t.constructor,t)r.hasOwnProperty(n)&&r[n]!==t[n]&&(r[n]=e(t[n]));else for(n in r={},t)"__proto__"===n?Object.defineProperty(r,n,{value:e(t[n]),configurable:!0,enumerable:!0,writable:!0}):r[n]=e(t[n]);return r}if("[object Array]"===i){for(n=t.length,r=Array(n);n--;)r[n]=e(t[n]);return r}return"[object Set]"===i?(r=new Set,t.forEach((function(t){r.add(e(t))})),r):"[object Map]"===i?(r=new Map,t.forEach((function(t,n){r.set(e(n),e(t))})),r):"[object Date]"===i?new Date(+t):"[object RegExp]"===i?((r=new RegExp(t.source,t.flags)).lastIndex=t.lastIndex,r):"[object DataView]"===i?new t.constructor(e(t.buffer)):"[object ArrayBuffer]"===i?t.slice(0):"Array]"===i.slice(-6)?new t.constructor(t):t}},8662:function(e,t,n){var r;e=n.nmd(e),function(){var i,o="Expected a function",a="__lodash_hash_undefined__",u="__lodash_placeholder__",s=32,l=128,c=1/0,f=9007199254740991,p=NaN,d=4294967295,h=[["ary",l],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",s],["partialRight",64],["rearg",256]],g="[object Arguments]",m="[object Array]",v="[object Boolean]",y="[object Date]",b="[object Error]",w="[object Function]",D="[object GeneratorFunction]",x="[object Map]",E="[object Number]",_="[object Object]",C="[object Promise]",k="[object RegExp]",A="[object Set]",S="[object String]",F="[object Symbol]",O="[object WeakMap]",T="[object ArrayBuffer]",P="[object DataView]",B="[object Float32Array]",N="[object Float64Array]",R="[object Int8Array]",j="[object Int16Array]",L="[object Int32Array]",M="[object Uint8Array]",I="[object Uint8ClampedArray]",z="[object Uint16Array]",U="[object Uint32Array]",q=/\b__p \+= '';/g,H=/\b(__p \+=) '' \+/g,V=/(__e\(.*?\)|\b__t\)) \+\n'';/g,W=/&(?:amp|lt|gt|quot|#39);/g,$=/[&<>"']/g,G=RegExp(W.source),X=RegExp($.source),K=/<%-([\s\S]+?)%>/g,Q=/<%([\s\S]+?)%>/g,Y=/<%=([\s\S]+?)%>/g,J=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Z=/^\w*$/,ee=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,te=/[\\^$.*+?()[\]{}|]/g,ne=RegExp(te.source),re=/^\s+/,ie=/\s/,oe=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,ae=/\{\n\/\* \[wrapped with (.+)\] \*/,ue=/,? & /,se=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,le=/[()=,{}\[\]\/\s]/,ce=/\\(\\)?/g,fe=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,pe=/\w*$/,de=/^[-+]0x[0-9a-f]+$/i,he=/^0b[01]+$/i,ge=/^\[object .+?Constructor\]$/,me=/^0o[0-7]+$/i,ve=/^(?:0|[1-9]\d*)$/,ye=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,be=/($^)/,we=/['\n\r\u2028\u2029\\]/g,De="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",xe="a-z\\xdf-\\xf6\\xf8-\\xff",Ee="A-Z\\xc0-\\xd6\\xd8-\\xde",_e="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Ce="["+_e+"]",ke="["+De+"]",Ae="\\d+",Se="["+xe+"]",Fe="[^\\ud800-\\udfff"+_e+Ae+"\\u2700-\\u27bf"+xe+Ee+"]",Oe="\\ud83c[\\udffb-\\udfff]",Te="[^\\ud800-\\udfff]",Pe="(?:\\ud83c[\\udde6-\\uddff]){2}",Be="[\\ud800-\\udbff][\\udc00-\\udfff]",Ne="["+Ee+"]",Re="(?:"+Se+"|"+Fe+")",je="(?:"+Ne+"|"+Fe+")",Le="(?:['’](?:d|ll|m|re|s|t|ve))?",Me="(?:['’](?:D|LL|M|RE|S|T|VE))?",Ie="(?:"+ke+"|"+Oe+")?",ze="[\\ufe0e\\ufe0f]?",Ue=ze+Ie+"(?:\\u200d(?:"+[Te,Pe,Be].join("|")+")"+ze+Ie+")*",qe="(?:"+["[\\u2700-\\u27bf]",Pe,Be].join("|")+")"+Ue,He="(?:"+[Te+ke+"?",ke,Pe,Be,"[\\ud800-\\udfff]"].join("|")+")",Ve=RegExp("['’]","g"),We=RegExp(ke,"g"),$e=RegExp(Oe+"(?="+Oe+")|"+He+Ue,"g"),Ge=RegExp([Ne+"?"+Se+"+"+Le+"(?="+[Ce,Ne,"$"].join("|")+")",je+"+"+Me+"(?="+[Ce,Ne+Re,"$"].join("|")+")",Ne+"?"+Re+"+"+Le,Ne+"+"+Me,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Ae,qe].join("|"),"g"),Xe=RegExp("[\\u200d\\ud800-\\udfff"+De+"\\ufe0e\\ufe0f]"),Ke=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Qe=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Ye=-1,Je={};Je[B]=Je[N]=Je[R]=Je[j]=Je[L]=Je[M]=Je[I]=Je[z]=Je[U]=!0,Je[g]=Je[m]=Je[T]=Je[v]=Je[P]=Je[y]=Je[b]=Je[w]=Je[x]=Je[E]=Je[_]=Je[k]=Je[A]=Je[S]=Je[O]=!1;var Ze={};Ze[g]=Ze[m]=Ze[T]=Ze[P]=Ze[v]=Ze[y]=Ze[B]=Ze[N]=Ze[R]=Ze[j]=Ze[L]=Ze[x]=Ze[E]=Ze[_]=Ze[k]=Ze[A]=Ze[S]=Ze[F]=Ze[M]=Ze[I]=Ze[z]=Ze[U]=!0,Ze[b]=Ze[w]=Ze[O]=!1;var et={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},tt=parseFloat,nt=parseInt,rt="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,it="object"==typeof self&&self&&self.Object===Object&&self,ot=rt||it||Function("return this")(),at=t&&!t.nodeType&&t,ut=at&&e&&!e.nodeType&&e,st=ut&&ut.exports===at,lt=st&&rt.process,ct=function(){try{return ut&&ut.require&&ut.require("util").types||lt&<.binding&<.binding("util")}catch(e){}}(),ft=ct&&ct.isArrayBuffer,pt=ct&&ct.isDate,dt=ct&&ct.isMap,ht=ct&&ct.isRegExp,gt=ct&&ct.isSet,mt=ct&&ct.isTypedArray;function vt(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function yt(e,t,n,r){for(var i=-1,o=null==e?0:e.length;++i-1}function _t(e,t,n){for(var r=-1,i=null==e?0:e.length;++r-1;);return n}function Gt(e,t){for(var n=e.length;n--&&Bt(t,e[n],0)>-1;);return n}function Xt(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}var Kt=Mt({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),Qt=Mt({"&":"&","<":"<",">":">",'"':""","'":"'"});function Yt(e){return"\\"+et[e]}function Jt(e){return Xe.test(e)}function Zt(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}function en(e,t){return function(n){return e(t(n))}}function tn(e,t){for(var n=-1,r=e.length,i=0,o=[];++n",""":'"',"'":"'"}),ln=function e(t){var n,r=(t=null==t?ot:ln.defaults(ot.Object(),t,ln.pick(ot,Qe))).Array,ie=t.Date,De=t.Error,xe=t.Function,Ee=t.Math,_e=t.Object,Ce=t.RegExp,ke=t.String,Ae=t.TypeError,Se=r.prototype,Fe=xe.prototype,Oe=_e.prototype,Te=t["__core-js_shared__"],Pe=Fe.toString,Be=Oe.hasOwnProperty,Ne=0,Re=(n=/[^.]+$/.exec(Te&&Te.keys&&Te.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",je=Oe.toString,Le=Pe.call(_e),Me=ot._,Ie=Ce("^"+Pe.call(Be).replace(te,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),ze=st?t.Buffer:i,Ue=t.Symbol,qe=t.Uint8Array,He=ze?ze.allocUnsafe:i,$e=en(_e.getPrototypeOf,_e),Xe=_e.create,et=Oe.propertyIsEnumerable,rt=Se.splice,it=Ue?Ue.isConcatSpreadable:i,at=Ue?Ue.iterator:i,ut=Ue?Ue.toStringTag:i,lt=function(){try{var e=lo(_e,"defineProperty");return e({},"",{}),e}catch(e){}}(),ct=t.clearTimeout!==ot.clearTimeout&&t.clearTimeout,Ot=ie&&ie.now!==ot.Date.now&&ie.now,Mt=t.setTimeout!==ot.setTimeout&&t.setTimeout,cn=Ee.ceil,fn=Ee.floor,pn=_e.getOwnPropertySymbols,dn=ze?ze.isBuffer:i,hn=t.isFinite,gn=Se.join,mn=en(_e.keys,_e),vn=Ee.max,yn=Ee.min,bn=ie.now,wn=t.parseInt,Dn=Ee.random,xn=Se.reverse,En=lo(t,"DataView"),_n=lo(t,"Map"),Cn=lo(t,"Promise"),kn=lo(t,"Set"),An=lo(t,"WeakMap"),Sn=lo(_e,"create"),Fn=An&&new An,On={},Tn=Io(En),Pn=Io(_n),Bn=Io(Cn),Nn=Io(kn),Rn=Io(An),jn=Ue?Ue.prototype:i,Ln=jn?jn.valueOf:i,Mn=jn?jn.toString:i;function In(e){if(nu(e)&&!Wa(e)&&!(e instanceof Hn)){if(e instanceof qn)return e;if(Be.call(e,"__wrapped__"))return zo(e)}return new qn(e)}var zn=function(){function e(){}return function(t){if(!tu(t))return{};if(Xe)return Xe(t);e.prototype=t;var n=new e;return e.prototype=i,n}}();function Un(){}function qn(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=i}function Hn(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=d,this.__views__=[]}function Vn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t=t?e:t)),e}function ur(e,t,n,r,o,a){var u,s=1&t,l=2&t,c=4&t;if(n&&(u=o?n(e,r,o,a):n(e)),u!==i)return u;if(!tu(e))return e;var f=Wa(e);if(f){if(u=function(e){var t=e.length,n=new e.constructor(t);return t&&"string"==typeof e[0]&&Be.call(e,"index")&&(n.index=e.index,n.input=e.input),n}(e),!s)return Ai(e,u)}else{var p=po(e),d=p==w||p==D;if(Ka(e))return Di(e,s);if(p==_||p==g||d&&!o){if(u=l||d?{}:go(e),!s)return l?function(e,t){return Si(e,fo(e),t)}(e,function(e,t){return e&&Si(t,Bu(t),e)}(u,e)):function(e,t){return Si(e,co(e),t)}(e,rr(u,e))}else{if(!Ze[p])return o?e:{};u=function(e,t,n){var r,i=e.constructor;switch(t){case T:return xi(e);case v:case y:return new i(+e);case P:return function(e,t){var n=t?xi(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}(e,n);case B:case N:case R:case j:case L:case M:case I:case z:case U:return Ei(e,n);case x:return new i;case E:case S:return new i(e);case k:return function(e){var t=new e.constructor(e.source,pe.exec(e));return t.lastIndex=e.lastIndex,t}(e);case A:return new i;case F:return r=e,Ln?_e(Ln.call(r)):{}}}(e,p,s)}}a||(a=new Xn);var h=a.get(e);if(h)return h;a.set(e,u),uu(e)?e.forEach((function(r){u.add(ur(r,t,n,r,e,a))})):ru(e)&&e.forEach((function(r,i){u.set(i,ur(r,t,n,i,e,a))}));var m=f?i:(c?l?no:to:l?Bu:Pu)(e);return bt(m||e,(function(r,i){m&&(r=e[i=r]),er(u,i,ur(r,t,n,i,e,a))})),u}function sr(e,t,n){var r=n.length;if(null==e)return!r;for(e=_e(e);r--;){var o=n[r],a=t[o],u=e[o];if(u===i&&!(o in e)||!a(u))return!1}return!0}function lr(e,t,n){if("function"!=typeof e)throw new Ae(o);return Oo((function(){e.apply(i,n)}),t)}function cr(e,t,n,r){var i=-1,o=Et,a=!0,u=e.length,s=[],l=t.length;if(!u)return s;n&&(t=Ct(t,Ht(n))),r?(o=_t,a=!1):t.length>=200&&(o=Wt,a=!1,t=new Gn(t));e:for(;++i-1},Wn.prototype.set=function(e,t){var n=this.__data__,r=tr(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},$n.prototype.clear=function(){this.size=0,this.__data__={hash:new Vn,map:new(_n||Wn),string:new Vn}},$n.prototype.delete=function(e){var t=uo(this,e).delete(e);return this.size-=t?1:0,t},$n.prototype.get=function(e){return uo(this,e).get(e)},$n.prototype.has=function(e){return uo(this,e).has(e)},$n.prototype.set=function(e,t){var n=uo(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Gn.prototype.add=Gn.prototype.push=function(e){return this.__data__.set(e,a),this},Gn.prototype.has=function(e){return this.__data__.has(e)},Xn.prototype.clear=function(){this.__data__=new Wn,this.size=0},Xn.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Xn.prototype.get=function(e){return this.__data__.get(e)},Xn.prototype.has=function(e){return this.__data__.has(e)},Xn.prototype.set=function(e,t){var n=this.__data__;if(n instanceof Wn){var r=n.__data__;if(!_n||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new $n(r)}return n.set(e,t),this.size=n.size,this};var fr=Ti(br),pr=Ti(wr,!0);function dr(e,t){var n=!0;return fr(e,(function(e,r,i){return n=!!t(e,r,i)})),n}function hr(e,t,n){for(var r=-1,o=e.length;++r0&&n(u)?t>1?mr(u,t-1,n,r,i):kt(i,u):r||(i[i.length]=u)}return i}var vr=Pi(),yr=Pi(!0);function br(e,t){return e&&vr(e,t,Pu)}function wr(e,t){return e&&yr(e,t,Pu)}function Dr(e,t){return xt(t,(function(t){return Ja(e[t])}))}function xr(e,t){for(var n=0,r=(t=vi(t,e)).length;null!=e&&nt}function kr(e,t){return null!=e&&Be.call(e,t)}function Ar(e,t){return null!=e&&t in _e(e)}function Sr(e,t,n){for(var o=n?_t:Et,a=e[0].length,u=e.length,s=u,l=r(u),c=1/0,f=[];s--;){var p=e[s];s&&t&&(p=Ct(p,Ht(t))),c=yn(p.length,c),l[s]=!n&&(t||a>=120&&p.length>=120)?new Gn(s&&p):i}p=e[0];var d=-1,h=l[0];e:for(;++d=u?s:s*("desc"==n[r]?-1:1)}return e.index-t.index}(e,t,n)}));r--;)e[r]=e[r].value;return e}(i)}function Hr(e,t,n){for(var r=-1,i=t.length,o={};++r-1;)u!==e&&rt.call(u,s,1),rt.call(e,s,1);return e}function Wr(e,t){for(var n=e?t.length:0,r=n-1;n--;){var i=t[n];if(n==r||i!==o){var o=i;vo(i)?rt.call(e,i,1):li(e,i)}}return e}function $r(e,t){return e+fn(Dn()*(t-e+1))}function Gr(e,t){var n="";if(!e||t<1||t>f)return n;do{t%2&&(n+=e),(t=fn(t/2))&&(e+=e)}while(t);return n}function Xr(e,t){return To(Co(e,t,is),e+"")}function Kr(e){return Qn(Uu(e))}function Qr(e,t){var n=Uu(e);return No(n,ar(t,0,n.length))}function Yr(e,t,n,r){if(!tu(e))return e;for(var o=-1,a=(t=vi(t,e)).length,u=a-1,s=e;null!=s&&++oo?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var a=r(o);++i>>1,a=e[o];null!==a&&!lu(a)&&(n?a<=t:a=200){var l=t?null:Gi(e);if(l)return nn(l);a=!1,i=Wt,s=new Gn}else s=t?[]:u;e:for(;++r=r?e:ti(e,t,n)}var wi=ct||function(e){return ot.clearTimeout(e)};function Di(e,t){if(t)return e.slice();var n=e.length,r=He?He(n):new e.constructor(n);return e.copy(r),r}function xi(e){var t=new e.constructor(e.byteLength);return new qe(t).set(new qe(e)),t}function Ei(e,t){var n=t?xi(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}function _i(e,t){if(e!==t){var n=e!==i,r=null===e,o=e==e,a=lu(e),u=t!==i,s=null===t,l=t==t,c=lu(t);if(!s&&!c&&!a&&e>t||a&&u&&l&&!s&&!c||r&&u&&l||!n&&l||!o)return 1;if(!r&&!a&&!c&&e1?n[o-1]:i,u=o>2?n[2]:i;for(a=e.length>3&&"function"==typeof a?(o--,a):i,u&&yo(n[0],n[1],u)&&(a=o<3?i:a,o=1),t=_e(t);++r-1?o[a?t[u]:u]:i}}function Li(e){return eo((function(t){var n=t.length,r=n,a=qn.prototype.thru;for(e&&t.reverse();r--;){var u=t[r];if("function"!=typeof u)throw new Ae(o);if(a&&!s&&"wrapper"==io(u))var s=new qn([],!0)}for(r=s?r:n;++r1&&b.reverse(),d&&fs))return!1;var c=a.get(e),f=a.get(t);if(c&&f)return c==t&&f==e;var p=-1,d=!0,h=2&n?new Gn:i;for(a.set(e,t),a.set(t,e);++p-1&&e%1==0&&e1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(oe,"{\n/* [wrapped with "+t+"] */\n")}(r,function(e,t){return bt(h,(function(n){var r="_."+n[0];t&n[1]&&!Et(e,r)&&e.push(r)})),e.sort()}(function(e){var t=e.match(ae);return t?t[1].split(ue):[]}(r),n)))}function Bo(e){var t=0,n=0;return function(){var r=bn(),o=16-(r-n);if(n=r,o>0){if(++t>=800)return arguments[0]}else t=0;return e.apply(i,arguments)}}function No(e,t){var n=-1,r=e.length,o=r-1;for(t=t===i?r:t;++n1?e[t-1]:i;return n="function"==typeof n?(e.pop(),n):i,aa(e,n)}));function da(e){var t=In(e);return t.__chain__=!0,t}function ha(e,t){return t(e)}var ga=eo((function(e){var t=e.length,n=t?e[0]:0,r=this.__wrapped__,o=function(t){return or(t,e)};return!(t>1||this.__actions__.length)&&r instanceof Hn&&vo(n)?((r=r.slice(n,+n+(t?1:0))).__actions__.push({func:ha,args:[o],thisArg:i}),new qn(r,this.__chain__).thru((function(e){return t&&!e.length&&e.push(i),e}))):this.thru(o)})),ma=Fi((function(e,t,n){Be.call(e,n)?++e[n]:ir(e,n,1)})),va=ji(Vo),ya=ji(Wo);function ba(e,t){return(Wa(e)?bt:fr)(e,ao(t,3))}function wa(e,t){return(Wa(e)?wt:pr)(e,ao(t,3))}var Da=Fi((function(e,t,n){Be.call(e,n)?e[n].push(t):ir(e,n,[t])})),xa=Xr((function(e,t,n){var i=-1,o="function"==typeof t,a=Ga(e)?r(e.length):[];return fr(e,(function(e){a[++i]=o?vt(t,e,n):Fr(e,t,n)})),a})),Ea=Fi((function(e,t,n){ir(e,n,t)}));function _a(e,t){return(Wa(e)?Ct:Lr)(e,ao(t,3))}var Ca=Fi((function(e,t,n){e[n?0:1].push(t)}),(function(){return[[],[]]})),ka=Xr((function(e,t){if(null==e)return[];var n=t.length;return n>1&&yo(e,t[0],t[1])?t=[]:n>2&&yo(t[0],t[1],t[2])&&(t=[t[0]]),qr(e,mr(t,1),[])})),Aa=Ot||function(){return ot.Date.now()};function Sa(e,t,n){return t=n?i:t,t=e&&null==t?e.length:t,Ki(e,l,i,i,i,i,t)}function Fa(e,t){var n;if("function"!=typeof t)throw new Ae(o);return e=gu(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=i),n}}var Oa=Xr((function(e,t,n){var r=1;if(n.length){var i=tn(n,oo(Oa));r|=s}return Ki(e,r,t,n,i)})),Ta=Xr((function(e,t,n){var r=3;if(n.length){var i=tn(n,oo(Ta));r|=s}return Ki(t,r,e,n,i)}));function Pa(e,t,n){var r,a,u,s,l,c,f=0,p=!1,d=!1,h=!0;if("function"!=typeof e)throw new Ae(o);function g(t){var n=r,o=a;return r=a=i,f=t,s=e.apply(o,n)}function m(e){return f=e,l=Oo(y,t),p?g(e):s}function v(e){var n=e-c;return c===i||n>=t||n<0||d&&e-f>=u}function y(){var e=Aa();if(v(e))return b(e);l=Oo(y,function(e){var n=t-(e-c);return d?yn(n,u-(e-f)):n}(e))}function b(e){return l=i,h&&r?g(e):(r=a=i,s)}function w(){var e=Aa(),n=v(e);if(r=arguments,a=this,c=e,n){if(l===i)return m(c);if(d)return wi(l),l=Oo(y,t),g(c)}return l===i&&(l=Oo(y,t)),s}return t=vu(t)||0,tu(n)&&(p=!!n.leading,u=(d="maxWait"in n)?vn(vu(n.maxWait)||0,t):u,h="trailing"in n?!!n.trailing:h),w.cancel=function(){l!==i&&wi(l),f=0,r=c=a=l=i},w.flush=function(){return l===i?s:b(Aa())},w}var Ba=Xr((function(e,t){return lr(e,1,t)})),Na=Xr((function(e,t,n){return lr(e,vu(t)||0,n)}));function Ra(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new Ae(o);var n=function(){var r=arguments,i=t?t.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var a=e.apply(this,r);return n.cache=o.set(i,a)||o,a};return n.cache=new(Ra.Cache||$n),n}function ja(e){if("function"!=typeof e)throw new Ae(o);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}Ra.Cache=$n;var La=yi((function(e,t){var n=(t=1==t.length&&Wa(t[0])?Ct(t[0],Ht(ao())):Ct(mr(t,1),Ht(ao()))).length;return Xr((function(r){for(var i=-1,o=yn(r.length,n);++i=t})),Va=Or(function(){return arguments}())?Or:function(e){return nu(e)&&Be.call(e,"callee")&&!et.call(e,"callee")},Wa=r.isArray,$a=ft?Ht(ft):function(e){return nu(e)&&_r(e)==T};function Ga(e){return null!=e&&eu(e.length)&&!Ja(e)}function Xa(e){return nu(e)&&Ga(e)}var Ka=dn||vs,Qa=pt?Ht(pt):function(e){return nu(e)&&_r(e)==y};function Ya(e){if(!nu(e))return!1;var t=_r(e);return t==b||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!ou(e)}function Ja(e){if(!tu(e))return!1;var t=_r(e);return t==w||t==D||"[object AsyncFunction]"==t||"[object Proxy]"==t}function Za(e){return"number"==typeof e&&e==gu(e)}function eu(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=f}function tu(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function nu(e){return null!=e&&"object"==typeof e}var ru=dt?Ht(dt):function(e){return nu(e)&&po(e)==x};function iu(e){return"number"==typeof e||nu(e)&&_r(e)==E}function ou(e){if(!nu(e)||_r(e)!=_)return!1;var t=$e(e);if(null===t)return!0;var n=Be.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&Pe.call(n)==Le}var au=ht?Ht(ht):function(e){return nu(e)&&_r(e)==k},uu=gt?Ht(gt):function(e){return nu(e)&&po(e)==A};function su(e){return"string"==typeof e||!Wa(e)&&nu(e)&&_r(e)==S}function lu(e){return"symbol"==typeof e||nu(e)&&_r(e)==F}var cu=mt?Ht(mt):function(e){return nu(e)&&eu(e.length)&&!!Je[_r(e)]},fu=Vi(jr),pu=Vi((function(e,t){return e<=t}));function du(e){if(!e)return[];if(Ga(e))return su(e)?an(e):Ai(e);if(at&&e[at])return function(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}(e[at]());var t=po(e);return(t==x?Zt:t==A?nn:Uu)(e)}function hu(e){return e?(e=vu(e))===c||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}function gu(e){var t=hu(e),n=t%1;return t==t?n?t-n:t:0}function mu(e){return e?ar(gu(e),0,d):0}function vu(e){if("number"==typeof e)return e;if(lu(e))return p;if(tu(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=tu(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=qt(e);var n=he.test(e);return n||me.test(e)?nt(e.slice(2),n?2:8):de.test(e)?p:+e}function yu(e){return Si(e,Bu(e))}function bu(e){return null==e?"":ui(e)}var wu=Oi((function(e,t){if(xo(t)||Ga(t))Si(t,Pu(t),e);else for(var n in t)Be.call(t,n)&&er(e,n,t[n])})),Du=Oi((function(e,t){Si(t,Bu(t),e)})),xu=Oi((function(e,t,n,r){Si(t,Bu(t),e,r)})),Eu=Oi((function(e,t,n,r){Si(t,Pu(t),e,r)})),_u=eo(or),Cu=Xr((function(e,t){e=_e(e);var n=-1,r=t.length,o=r>2?t[2]:i;for(o&&yo(t[0],t[1],o)&&(r=1);++n1),t})),Si(e,no(e),n),r&&(n=ur(n,7,Ji));for(var i=t.length;i--;)li(n,t[i]);return n})),Lu=eo((function(e,t){return null==e?{}:function(e,t){return Hr(e,t,(function(t,n){return Su(e,n)}))}(e,t)}));function Mu(e,t){if(null==e)return{};var n=Ct(no(e),(function(e){return[e]}));return t=ao(t),Hr(e,n,(function(e,n){return t(e,n[0])}))}var Iu=Xi(Pu),zu=Xi(Bu);function Uu(e){return null==e?[]:Vt(e,Pu(e))}var qu=Ni((function(e,t,n){return t=t.toLowerCase(),e+(n?Hu(t):t)}));function Hu(e){return Yu(bu(e).toLowerCase())}function Vu(e){return(e=bu(e))&&e.replace(ye,Kt).replace(We,"")}var Wu=Ni((function(e,t,n){return e+(n?"-":"")+t.toLowerCase()})),$u=Ni((function(e,t,n){return e+(n?" ":"")+t.toLowerCase()})),Gu=Bi("toLowerCase"),Xu=Ni((function(e,t,n){return e+(n?"_":"")+t.toLowerCase()})),Ku=Ni((function(e,t,n){return e+(n?" ":"")+Yu(t)})),Qu=Ni((function(e,t,n){return e+(n?" ":"")+t.toUpperCase()})),Yu=Bi("toUpperCase");function Ju(e,t,n){return e=bu(e),(t=n?i:t)===i?function(e){return Ke.test(e)}(e)?function(e){return e.match(Ge)||[]}(e):function(e){return e.match(se)||[]}(e):e.match(t)||[]}var Zu=Xr((function(e,t){try{return vt(e,i,t)}catch(e){return Ya(e)?e:new De(e)}})),es=eo((function(e,t){return bt(t,(function(t){t=Mo(t),ir(e,t,Oa(e[t],e))})),e}));function ts(e){return function(){return e}}var ns=Li(),rs=Li(!0);function is(e){return e}function os(e){return Nr("function"==typeof e?e:ur(e,1))}var as=Xr((function(e,t){return function(n){return Fr(n,e,t)}})),us=Xr((function(e,t){return function(n){return Fr(e,n,t)}}));function ss(e,t,n){var r=Pu(t),i=Dr(t,r);null!=n||tu(t)&&(i.length||!r.length)||(n=t,t=e,e=this,i=Dr(t,Pu(t)));var o=!(tu(n)&&"chain"in n&&!n.chain),a=Ja(e);return bt(i,(function(n){var r=t[n];e[n]=r,a&&(e.prototype[n]=function(){var t=this.__chain__;if(o||t){var n=e(this.__wrapped__),i=n.__actions__=Ai(this.__actions__);return i.push({func:r,args:arguments,thisArg:e}),n.__chain__=t,n}return r.apply(e,kt([this.value()],arguments))})})),e}function ls(){}var cs=Ui(Ct),fs=Ui(Dt),ps=Ui(Ft);function ds(e){return bo(e)?Lt(Mo(e)):function(e){return function(t){return xr(t,e)}}(e)}var hs=Hi(),gs=Hi(!0);function ms(){return[]}function vs(){return!1}var ys,bs=zi((function(e,t){return e+t}),0),ws=$i("ceil"),Ds=zi((function(e,t){return e/t}),1),xs=$i("floor"),Es=zi((function(e,t){return e*t}),1),_s=$i("round"),Cs=zi((function(e,t){return e-t}),0);return In.after=function(e,t){if("function"!=typeof t)throw new Ae(o);return e=gu(e),function(){if(--e<1)return t.apply(this,arguments)}},In.ary=Sa,In.assign=wu,In.assignIn=Du,In.assignInWith=xu,In.assignWith=Eu,In.at=_u,In.before=Fa,In.bind=Oa,In.bindAll=es,In.bindKey=Ta,In.castArray=function(){if(!arguments.length)return[];var e=arguments[0];return Wa(e)?e:[e]},In.chain=da,In.chunk=function(e,t,n){t=(n?yo(e,t,n):t===i)?1:vn(gu(t),0);var o=null==e?0:e.length;if(!o||t<1)return[];for(var a=0,u=0,s=r(cn(o/t));ao?0:o+n),(r=r===i||r>o?o:gu(r))<0&&(r+=o),r=n>r?0:mu(r);n>>0)?(e=bu(e))&&("string"==typeof t||null!=t&&!au(t))&&!(t=ui(t))&&Jt(e)?bi(an(e),0,n):e.split(t,n):[]},In.spread=function(e,t){if("function"!=typeof e)throw new Ae(o);return t=null==t?0:vn(gu(t),0),Xr((function(n){var r=n[t],i=bi(n,0,t);return r&&kt(i,r),vt(e,this,i)}))},In.tail=function(e){var t=null==e?0:e.length;return t?ti(e,1,t):[]},In.take=function(e,t,n){return e&&e.length?ti(e,0,(t=n||t===i?1:gu(t))<0?0:t):[]},In.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?ti(e,(t=r-(t=n||t===i?1:gu(t)))<0?0:t,r):[]},In.takeRightWhile=function(e,t){return e&&e.length?fi(e,ao(t,3),!1,!0):[]},In.takeWhile=function(e,t){return e&&e.length?fi(e,ao(t,3)):[]},In.tap=function(e,t){return t(e),e},In.throttle=function(e,t,n){var r=!0,i=!0;if("function"!=typeof e)throw new Ae(o);return tu(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),Pa(e,t,{leading:r,maxWait:t,trailing:i})},In.thru=ha,In.toArray=du,In.toPairs=Iu,In.toPairsIn=zu,In.toPath=function(e){return Wa(e)?Ct(e,Mo):lu(e)?[e]:Ai(Lo(bu(e)))},In.toPlainObject=yu,In.transform=function(e,t,n){var r=Wa(e),i=r||Ka(e)||cu(e);if(t=ao(t,4),null==n){var o=e&&e.constructor;n=i?r?new o:[]:tu(e)&&Ja(o)?zn($e(e)):{}}return(i?bt:br)(e,(function(e,r,i){return t(n,e,r,i)})),n},In.unary=function(e){return Sa(e,1)},In.union=na,In.unionBy=ra,In.unionWith=ia,In.uniq=function(e){return e&&e.length?si(e):[]},In.uniqBy=function(e,t){return e&&e.length?si(e,ao(t,2)):[]},In.uniqWith=function(e,t){return t="function"==typeof t?t:i,e&&e.length?si(e,i,t):[]},In.unset=function(e,t){return null==e||li(e,t)},In.unzip=oa,In.unzipWith=aa,In.update=function(e,t,n){return null==e?e:ci(e,t,mi(n))},In.updateWith=function(e,t,n,r){return r="function"==typeof r?r:i,null==e?e:ci(e,t,mi(n),r)},In.values=Uu,In.valuesIn=function(e){return null==e?[]:Vt(e,Bu(e))},In.without=ua,In.words=Ju,In.wrap=function(e,t){return Ma(mi(t),e)},In.xor=sa,In.xorBy=la,In.xorWith=ca,In.zip=fa,In.zipObject=function(e,t){return hi(e||[],t||[],er)},In.zipObjectDeep=function(e,t){return hi(e||[],t||[],Yr)},In.zipWith=pa,In.entries=Iu,In.entriesIn=zu,In.extend=Du,In.extendWith=xu,ss(In,In),In.add=bs,In.attempt=Zu,In.camelCase=qu,In.capitalize=Hu,In.ceil=ws,In.clamp=function(e,t,n){return n===i&&(n=t,t=i),n!==i&&(n=(n=vu(n))==n?n:0),t!==i&&(t=(t=vu(t))==t?t:0),ar(vu(e),t,n)},In.clone=function(e){return ur(e,4)},In.cloneDeep=function(e){return ur(e,5)},In.cloneDeepWith=function(e,t){return ur(e,5,t="function"==typeof t?t:i)},In.cloneWith=function(e,t){return ur(e,4,t="function"==typeof t?t:i)},In.conformsTo=function(e,t){return null==t||sr(e,t,Pu(t))},In.deburr=Vu,In.defaultTo=function(e,t){return null==e||e!=e?t:e},In.divide=Ds,In.endsWith=function(e,t,n){e=bu(e),t=ui(t);var r=e.length,o=n=n===i?r:ar(gu(n),0,r);return(n-=t.length)>=0&&e.slice(n,o)==t},In.eq=Ua,In.escape=function(e){return(e=bu(e))&&X.test(e)?e.replace($,Qt):e},In.escapeRegExp=function(e){return(e=bu(e))&&ne.test(e)?e.replace(te,"\\$&"):e},In.every=function(e,t,n){var r=Wa(e)?Dt:dr;return n&&yo(e,t,n)&&(t=i),r(e,ao(t,3))},In.find=va,In.findIndex=Vo,In.findKey=function(e,t){return Tt(e,ao(t,3),br)},In.findLast=ya,In.findLastIndex=Wo,In.findLastKey=function(e,t){return Tt(e,ao(t,3),wr)},In.floor=xs,In.forEach=ba,In.forEachRight=wa,In.forIn=function(e,t){return null==e?e:vr(e,ao(t,3),Bu)},In.forInRight=function(e,t){return null==e?e:yr(e,ao(t,3),Bu)},In.forOwn=function(e,t){return e&&br(e,ao(t,3))},In.forOwnRight=function(e,t){return e&&wr(e,ao(t,3))},In.get=Au,In.gt=qa,In.gte=Ha,In.has=function(e,t){return null!=e&&ho(e,t,kr)},In.hasIn=Su,In.head=Go,In.identity=is,In.includes=function(e,t,n,r){e=Ga(e)?e:Uu(e),n=n&&!r?gu(n):0;var i=e.length;return n<0&&(n=vn(i+n,0)),su(e)?n<=i&&e.indexOf(t,n)>-1:!!i&&Bt(e,t,n)>-1},In.indexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var i=null==n?0:gu(n);return i<0&&(i=vn(r+i,0)),Bt(e,t,i)},In.inRange=function(e,t,n){return t=hu(t),n===i?(n=t,t=0):n=hu(n),function(e,t,n){return e>=yn(t,n)&&e=-9007199254740991&&e<=f},In.isSet=uu,In.isString=su,In.isSymbol=lu,In.isTypedArray=cu,In.isUndefined=function(e){return e===i},In.isWeakMap=function(e){return nu(e)&&po(e)==O},In.isWeakSet=function(e){return nu(e)&&"[object WeakSet]"==_r(e)},In.join=function(e,t){return null==e?"":gn.call(e,t)},In.kebabCase=Wu,In.last=Yo,In.lastIndexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var o=r;return n!==i&&(o=(o=gu(n))<0?vn(r+o,0):yn(o,r-1)),t==t?function(e,t,n){for(var r=n+1;r--;)if(e[r]===t)return r;return r}(e,t,o):Pt(e,Rt,o,!0)},In.lowerCase=$u,In.lowerFirst=Gu,In.lt=fu,In.lte=pu,In.max=function(e){return e&&e.length?hr(e,is,Cr):i},In.maxBy=function(e,t){return e&&e.length?hr(e,ao(t,2),Cr):i},In.mean=function(e){return jt(e,is)},In.meanBy=function(e,t){return jt(e,ao(t,2))},In.min=function(e){return e&&e.length?hr(e,is,jr):i},In.minBy=function(e,t){return e&&e.length?hr(e,ao(t,2),jr):i},In.stubArray=ms,In.stubFalse=vs,In.stubObject=function(){return{}},In.stubString=function(){return""},In.stubTrue=function(){return!0},In.multiply=Es,In.nth=function(e,t){return e&&e.length?Ur(e,gu(t)):i},In.noConflict=function(){return ot._===this&&(ot._=Me),this},In.noop=ls,In.now=Aa,In.pad=function(e,t,n){e=bu(e);var r=(t=gu(t))?on(e):0;if(!t||r>=t)return e;var i=(t-r)/2;return qi(fn(i),n)+e+qi(cn(i),n)},In.padEnd=function(e,t,n){e=bu(e);var r=(t=gu(t))?on(e):0;return t&&rt){var r=e;e=t,t=r}if(n||e%1||t%1){var o=Dn();return yn(e+o*(t-e+tt("1e-"+((o+"").length-1))),t)}return $r(e,t)},In.reduce=function(e,t,n){var r=Wa(e)?At:It,i=arguments.length<3;return r(e,ao(t,4),n,i,fr)},In.reduceRight=function(e,t,n){var r=Wa(e)?St:It,i=arguments.length<3;return r(e,ao(t,4),n,i,pr)},In.repeat=function(e,t,n){return t=(n?yo(e,t,n):t===i)?1:gu(t),Gr(bu(e),t)},In.replace=function(){var e=arguments,t=bu(e[0]);return e.length<3?t:t.replace(e[1],e[2])},In.result=function(e,t,n){var r=-1,o=(t=vi(t,e)).length;for(o||(o=1,e=i);++rf)return[];var n=d,r=yn(e,d);t=ao(t),e-=d;for(var i=Ut(r,t);++n=a)return e;var s=n-on(r);if(s<1)return r;var l=u?bi(u,0,s).join(""):e.slice(0,s);if(o===i)return l+r;if(u&&(s+=l.length-s),au(o)){if(e.slice(s).search(o)){var c,f=l;for(o.global||(o=Ce(o.source,bu(pe.exec(o))+"g")),o.lastIndex=0;c=o.exec(f);)var p=c.index;l=l.slice(0,p===i?s:p)}}else if(e.indexOf(ui(o),s)!=s){var d=l.lastIndexOf(o);d>-1&&(l=l.slice(0,d))}return l+r},In.unescape=function(e){return(e=bu(e))&&G.test(e)?e.replace(W,sn):e},In.uniqueId=function(e){var t=++Ne;return bu(e)+t},In.upperCase=Qu,In.upperFirst=Yu,In.each=ba,In.eachRight=wa,In.first=Go,ss(In,(ys={},br(In,(function(e,t){Be.call(In.prototype,t)||(ys[t]=e)})),ys),{chain:!1}),In.VERSION="4.17.21",bt(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(e){In[e].placeholder=In})),bt(["drop","take"],(function(e,t){Hn.prototype[e]=function(n){n=n===i?1:vn(gu(n),0);var r=this.__filtered__&&!t?new Hn(this):this.clone();return r.__filtered__?r.__takeCount__=yn(n,r.__takeCount__):r.__views__.push({size:yn(n,d),type:e+(r.__dir__<0?"Right":"")}),r},Hn.prototype[e+"Right"]=function(t){return this.reverse()[e](t).reverse()}})),bt(["filter","map","takeWhile"],(function(e,t){var n=t+1,r=1==n||3==n;Hn.prototype[e]=function(e){var t=this.clone();return t.__iteratees__.push({iteratee:ao(e,3),type:n}),t.__filtered__=t.__filtered__||r,t}})),bt(["head","last"],(function(e,t){var n="take"+(t?"Right":"");Hn.prototype[e]=function(){return this[n](1).value()[0]}})),bt(["initial","tail"],(function(e,t){var n="drop"+(t?"":"Right");Hn.prototype[e]=function(){return this.__filtered__?new Hn(this):this[n](1)}})),Hn.prototype.compact=function(){return this.filter(is)},Hn.prototype.find=function(e){return this.filter(e).head()},Hn.prototype.findLast=function(e){return this.reverse().find(e)},Hn.prototype.invokeMap=Xr((function(e,t){return"function"==typeof e?new Hn(this):this.map((function(n){return Fr(n,e,t)}))})),Hn.prototype.reject=function(e){return this.filter(ja(ao(e)))},Hn.prototype.slice=function(e,t){e=gu(e);var n=this;return n.__filtered__&&(e>0||t<0)?new Hn(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),t!==i&&(n=(t=gu(t))<0?n.dropRight(-t):n.take(t-e)),n)},Hn.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()},Hn.prototype.toArray=function(){return this.take(d)},br(Hn.prototype,(function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),o=In[r?"take"+("last"==t?"Right":""):t],a=r||/^find/.test(t);o&&(In.prototype[t]=function(){var t=this.__wrapped__,u=r?[1]:arguments,s=t instanceof Hn,l=u[0],c=s||Wa(t),f=function(e){var t=o.apply(In,kt([e],u));return r&&p?t[0]:t};c&&n&&"function"==typeof l&&1!=l.length&&(s=c=!1);var p=this.__chain__,d=!!this.__actions__.length,h=a&&!p,g=s&&!d;if(!a&&c){t=g?t:new Hn(this);var m=e.apply(t,u);return m.__actions__.push({func:ha,args:[f],thisArg:i}),new qn(m,p)}return h&&g?e.apply(this,u):(m=this.thru(f),h?r?m.value()[0]:m.value():m)})})),bt(["pop","push","shift","sort","splice","unshift"],(function(e){var t=Se[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);In.prototype[e]=function(){var e=arguments;if(r&&!this.__chain__){var i=this.value();return t.apply(Wa(i)?i:[],e)}return this[n]((function(n){return t.apply(Wa(n)?n:[],e)}))}})),br(Hn.prototype,(function(e,t){var n=In[t];if(n){var r=n.name+"";Be.call(On,r)||(On[r]=[]),On[r].push({name:t,func:n})}})),On[Mi(i,2).name]=[{name:"wrapper",func:i}],Hn.prototype.clone=function(){var e=new Hn(this.__wrapped__);return e.__actions__=Ai(this.__actions__),e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=Ai(this.__iteratees__),e.__takeCount__=this.__takeCount__,e.__views__=Ai(this.__views__),e},Hn.prototype.reverse=function(){if(this.__filtered__){var e=new Hn(this);e.__dir__=-1,e.__filtered__=!0}else(e=this.clone()).__dir__*=-1;return e},Hn.prototype.value=function(){var e=this.__wrapped__.value(),t=this.__dir__,n=Wa(e),r=t<0,i=n?e.length:0,o=function(e,t,n){for(var r=-1,i=n.length;++r=this.__values__.length;return{done:e,value:e?i:this.__values__[this.__index__++]}},In.prototype.plant=function(e){for(var t,n=this;n instanceof Un;){var r=zo(n);r.__index__=0,r.__values__=i,t?o.__wrapped__=r:t=r;var o=r;n=n.__wrapped__}return o.__wrapped__=e,t},In.prototype.reverse=function(){var e=this.__wrapped__;if(e instanceof Hn){var t=e;return this.__actions__.length&&(t=new Hn(this)),(t=t.reverse()).__actions__.push({func:ha,args:[ta],thisArg:i}),new qn(t,this.__chain__)}return this.thru(ta)},In.prototype.toJSON=In.prototype.valueOf=In.prototype.value=function(){return pi(this.__wrapped__,this.__actions__)},In.prototype.first=In.prototype.head,at&&(In.prototype[at]=function(){return this}),In}();ot._=ln,(r=function(){return ln}.call(t,n,t,e))===i||(e.exports=r)}.call(this)},2739:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var a,u,s=i(e),l=1;l=s)return m;r=n(f),i=[],","===r.slice(-1)?(r=r.replace(p,""),y()):v()}function v(){for(n(l),o="",a="in descriptor";;){if(u=e.charAt(g),"in descriptor"===a)if(t(u))o&&(i.push(o),o="",a="after descriptor");else{if(","===u)return g+=1,o&&i.push(o),void y();if("("===u)o+=u,a="in parens";else{if(""===u)return o&&i.push(o),void y();o+=u}}else if("in parens"===a)if(")"===u)o+=u,a="in descriptor";else{if(""===u)return i.push(o),void y();o+=u}else if("after descriptor"===a)if(t(u));else{if(""===u)return void y();a="in descriptor",g-=1}g+=1}}function y(){var t,n,o,a,u,s,l,c,f,p=!1,g={};for(a=0;a{var r=n(9245);e.exports=function e(t,n,i){return r(n)||(i=n||i,n=[]),i=i||{},t instanceof RegExp?function(e,t){var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r{"use strict";let r=n(385);class i extends r{constructor(e){super(e),this.type="atrule"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}}e.exports=i,i.default=i,r.registerAtRule(i)},5859:(e,t,n)=>{"use strict";let r=n(7003);class i extends r{constructor(e){super(e),this.type="comment"}}e.exports=i,i.default=i},385:(e,t,n)=>{"use strict";let r,i,o,a=n(4509),{isClean:u}=n(5394),s=n(5859),l=n(7003);function c(e){return e.map((e=>(e.nodes&&(e.nodes=c(e.nodes)),delete e.source,e)))}function f(e){if(e[u]=!1,e.proxyOf.nodes)for(let t of e.proxyOf.nodes)f(t)}function p(e){"atrule"===e.type?Object.setPrototypeOf(e,o.prototype):"rule"===e.type?Object.setPrototypeOf(e,i.prototype):"decl"===e.type?Object.setPrototypeOf(e,a.prototype):"comment"===e.type&&Object.setPrototypeOf(e,s.prototype),e.nodes&&e.nodes.forEach((e=>{p(e)}))}class d extends l{push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}each(e){if(!this.proxyOf.nodes)return;let t,n,r=this.getIterator();for(;this.indexes[r]{let r;try{r=e(t,n)}catch(e){throw t.addToError(e)}return!1!==r&&t.walk&&(r=t.walk(e)),r}))}walkDecls(e,t){return t?e instanceof RegExp?this.walk(((n,r)=>{if("decl"===n.type&&e.test(n.prop))return t(n,r)})):this.walk(((n,r)=>{if("decl"===n.type&&n.prop===e)return t(n,r)})):(t=e,this.walk(((e,n)=>{if("decl"===e.type)return t(e,n)})))}walkRules(e,t){return t?e instanceof RegExp?this.walk(((n,r)=>{if("rule"===n.type&&e.test(n.selector))return t(n,r)})):this.walk(((n,r)=>{if("rule"===n.type&&n.selector===e)return t(n,r)})):(t=e,this.walk(((e,n)=>{if("rule"===e.type)return t(e,n)})))}walkAtRules(e,t){return t?e instanceof RegExp?this.walk(((n,r)=>{if("atrule"===n.type&&e.test(n.name))return t(n,r)})):this.walk(((n,r)=>{if("atrule"===n.type&&n.name===e)return t(n,r)})):(t=e,this.walk(((e,n)=>{if("atrule"===e.type)return t(e,n)})))}walkComments(e){return this.walk(((t,n)=>{if("comment"===t.type)return e(t,n)}))}append(...e){for(let t of e){let e=this.normalize(t,this.last);for(let t of e)this.proxyOf.nodes.push(t)}return this.markDirty(),this}prepend(...e){e=e.reverse();for(let t of e){let e=this.normalize(t,this.first,"prepend").reverse();for(let t of e)this.proxyOf.nodes.unshift(t);for(let t in this.indexes)this.indexes[t]=this.indexes[t]+e.length}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}insertBefore(e,t){let n,r=0===(e=this.index(e))&&"prepend",i=this.normalize(t,this.proxyOf.nodes[e],r).reverse();for(let t of i)this.proxyOf.nodes.splice(e,0,t);for(let t in this.indexes)n=this.indexes[t],e<=n&&(this.indexes[t]=n+i.length);return this.markDirty(),this}insertAfter(e,t){e=this.index(e);let n,r=this.normalize(t,this.proxyOf.nodes[e]).reverse();for(let t of r)this.proxyOf.nodes.splice(e+1,0,t);for(let t in this.indexes)n=this.indexes[t],e=e&&(this.indexes[n]=t-1);return this.markDirty(),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}replaceValues(e,t,n){return n||(n=t,t={}),this.walkDecls((r=>{t.props&&!t.props.includes(r.prop)||t.fast&&!r.value.includes(t.fast)||(r.value=r.value.replace(e,n))})),this.markDirty(),this}every(e){return this.nodes.every(e)}some(e){return this.nodes.some(e)}index(e){return"number"==typeof e?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}get first(){if(this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}normalize(e,t){if("string"==typeof e)e=c(r(e).nodes);else if(Array.isArray(e)){e=e.slice(0);for(let t of e)t.parent&&t.parent.removeChild(t,"ignore")}else if("root"===e.type){e=e.nodes.slice(0);for(let t of e)t.parent&&t.parent.removeChild(t,"ignore")}else if(e.type)e=[e];else if(e.prop){if(void 0===e.value)throw new Error("Value field is missed in node creation");"string"!=typeof e.value&&(e.value=String(e.value)),e=[new a(e)]}else if(e.selector)e=[new i(e)];else if(e.name)e=[new o(e)];else{if(!e.text)throw new Error("Unknown node type in node creation");e=[new s(e)]}return e.map((e=>("function"!=typeof e.markDirty&&p(e),(e=e.proxyOf).parent&&e.parent.removeChild(e),e[u]&&f(e),void 0===e.raws.before&&t&&void 0!==t.raws.before&&(e.raws.before=t.raws.before.replace(/\S/g,"")),e.parent=this,e)))}getProxyProcessor(){return{set:(e,t,n)=>(e[t]===n||(e[t]=n,"name"!==t&&"params"!==t&&"selector"!==t||e.markDirty()),!0),get:(e,t)=>"proxyOf"===t?e:e[t]?"each"===t||"string"==typeof t&&t.startsWith("walk")?(...n)=>e[t](...n.map((e=>"function"==typeof e?(t,n)=>e(t.toProxy(),n):e))):"every"===t||"some"===t?n=>e[t](((e,...t)=>n(e.toProxy(),...t))):"root"===t?()=>e.root().toProxy():"nodes"===t?e.nodes.map((e=>e.toProxy())):"first"===t||"last"===t?e[t].toProxy():e[t]:e[t]}}getIterator(){this.lastEach||(this.lastEach=0),this.indexes||(this.indexes={}),this.lastEach+=1;let e=this.lastEach;return this.indexes[e]=0,e}}d.registerParse=e=>{r=e},d.registerRule=e=>{i=e},d.registerAtRule=e=>{o=e},e.exports=d,d.default=d},7664:(e,t,n)=>{"use strict";let{red:r,bold:i,gray:o,options:a}=n(5574),u=n(2868);class s extends Error{constructor(e,t,n,r,i,o){super(e),this.name="CssSyntaxError",this.reason=e,i&&(this.file=i),r&&(this.source=r),o&&(this.plugin=o),void 0!==t&&void 0!==n&&(this.line=t,this.column=n),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,s)}setMessage(){this.message=this.plugin?this.plugin+": ":"",this.message+=this.file?this.file:"",void 0!==this.line&&(this.message+=":"+this.line+":"+this.column),this.message+=": "+this.reason}showSourceCode(e){if(!this.source)return"";let t=this.source;null==e&&(e=a.enabled),u&&e&&(t=u(t));let n,s,l=t.split(/\r?\n/),c=Math.max(this.line-3,0),f=Math.min(this.line+2,l.length),p=String(f).length;return e?(n=e=>i(r(e)),s=e=>o(e)):n=s=e=>e,l.slice(c,f).map(((e,t)=>{let r=c+1+t,i=" "+(" "+r).slice(-p)+" | ";if(r===this.line){let t=s(i.replace(/\d/g," "))+e.slice(0,this.column-1).replace(/[^\t]/g," ");return n(">")+s(i)+e+"\n "+t+n("^")}return" "+s(i)+e})).join("\n")}toString(){let e=this.showSourceCode();return e&&(e="\n\n"+e+"\n"),this.name+": "+this.message+e}}e.exports=s,s.default=s},4509:(e,t,n)=>{"use strict";let r=n(7003);class i extends r{constructor(e){e&&void 0!==e.value&&"string"!=typeof e.value&&(e={...e,value:String(e.value)}),super(e),this.type="decl"}get variable(){return this.prop.startsWith("--")||"$"===this.prop[0]}}e.exports=i,i.default=i},6595:(e,t,n)=>{"use strict";let r=n(4509),i=n(6086),o=n(5859),a=n(602),u=n(2766),s=n(1410),l=n(6900);function c(e,t){if(Array.isArray(e))return e.map((e=>c(e)));let{inputs:n,...f}=e;if(n){t=[];for(let e of n){let n={...e,__proto__:u.prototype};n.map&&(n.map={...n.map,__proto__:i.prototype}),t.push(n)}}if(f.nodes&&(f.nodes=e.nodes.map((e=>c(e,t)))),f.source){let{inputId:e,...n}=f.source;f.source=n,null!=e&&(f.source.input=t[e])}if("root"===f.type)return new s(f);if("decl"===f.type)return new r(f);if("rule"===f.type)return new l(f);if("comment"===f.type)return new o(f);if("atrule"===f.type)return new a(f);throw new Error("Unknown node type: "+e.type)}e.exports=c,c.default=c},2766:(e,t,n)=>{"use strict";let{fileURLToPath:r,pathToFileURL:i}=n(7414),{resolve:o,isAbsolute:a}=n(9830),{SourceMapConsumer:u,SourceMapGenerator:s}=n(4061),{nanoid:l}=n(2961),c=n(2868),f=n(7664),p=n(6086),d=Symbol("fromOffset cache"),h=Boolean(u&&s),g=Boolean(o&&a);class m{constructor(e,t={}){if(null==e||"object"==typeof e&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),"\ufeff"===this.css[0]||"￾"===this.css[0]?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!g||/^\w+:\/\//.test(t.from)||a(t.from)?this.file=t.from:this.file=o(t.from)),g&&h){let e=new p(this.css,t);if(e.text){this.map=e;let t=e.consumer().file;!this.file&&t&&(this.file=this.mapResolve(t))}}this.file||(this.id=""),this.map&&(this.map.file=this.from)}fromOffset(e){let t,n;if(this[d])n=this[d];else{let e=this.css.split("\n");n=new Array(e.length);let t=0;for(let r=0,i=e.length;r=t)r=n.length-1;else{let t,i=n.length-2;for(;r>1),e=n[t+1])){r=t;break}r=t+1}}return{line:r+1,col:e-n[r]+1}}error(e,t,n,r={}){let o;if(!n){let e=this.fromOffset(t);t=e.line,n=e.col}let a=this.origin(t,n);return o=a?new f(e,a.line,a.column,a.source,a.file,r.plugin):new f(e,t,n,this.css,this.file,r.plugin),o.input={line:t,column:n,source:this.css},this.file&&(i&&(o.input.url=i(this.file).toString()),o.input.file=this.file),o}origin(e,t){if(!this.map)return!1;let n,o=this.map.consumer(),u=o.originalPositionFor({line:e,column:t});if(!u.source)return!1;n=a(u.source)?i(u.source):new URL(u.source,this.map.consumer().sourceRoot||i(this.map.mapFile));let s={url:n.toString(),line:u.line,column:u.column};if("file:"===n.protocol){if(!r)throw new Error("file: protocol is not available in this PostCSS build");s.file=r(n)}let l=o.sourceContentFor(u.source);return l&&(s.source=l),s}mapResolve(e){return/^\w+:\/\//.test(e)?e:o(this.map.consumer().sourceRoot||this.map.root||".",e)}get from(){return this.file||this.id}toJSON(){let e={};for(let t of["hasBOM","css","file","id"])null!=this[t]&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}}e.exports=m,m.default=m,c&&c.registerInput&&c.registerInput(m)},6445:(e,t,n)=>{"use strict";let r=n(7837),{isClean:i}=n(5394),o=n(5821),a=(n(134),n(7049)),u=n(6883),s=n(1410);const l={root:"Root",atrule:"AtRule",rule:"Rule",decl:"Declaration",comment:"Comment"},c={postcssPlugin:!0,prepare:!0,Once:!0,Root:!0,Declaration:!0,Rule:!0,AtRule:!0,Comment:!0,DeclarationExit:!0,RuleExit:!0,AtRuleExit:!0,CommentExit:!0,RootExit:!0,OnceExit:!0},f={postcssPlugin:!0,prepare:!0,Once:!0};function p(e){return"object"==typeof e&&"function"==typeof e.then}function d(e){let t=!1,n=l[e.type];return"decl"===e.type?t=e.prop.toLowerCase():"atrule"===e.type&&(t=e.name.toLowerCase()),t&&e.append?[n,n+"-"+t,0,n+"Exit",n+"Exit-"+t]:t?[n,n+"-"+t,n+"Exit",n+"Exit-"+t]:e.append?[n,0,n+"Exit"]:[n,n+"Exit"]}function h(e){let t;return t="root"===e.type?["Root",0,"RootExit"]:d(e),{node:e,events:t,eventIndex:0,visitors:[],visitorIndex:0,iterator:0}}function g(e){return e[i]=!1,e.nodes&&e.nodes.forEach((e=>g(e))),e}let m={};class v{constructor(e,t,n){let r;if(this.stringified=!1,this.processed=!1,"object"==typeof t&&null!==t&&"root"===t.type)r=g(t);else if(t instanceof v||t instanceof a)r=g(t.root),t.map&&(void 0===n.map&&(n.map={}),n.map.inline||(n.map.inline=!1),n.map.prev=t.map);else{let e=u;n.syntax&&(e=n.syntax.parse),n.parser&&(e=n.parser),e.parse&&(e=e.parse);try{r=e(t,n)}catch(e){this.processed=!0,this.error=e}}this.result=new a(e,r,n),this.helpers={...m,result:this.result,postcss:m},this.plugins=this.processor.plugins.map((e=>"object"==typeof e&&e.prepare?{...e,...e.prepare(this.result)}:e))}get[Symbol.toStringTag](){return"LazyResult"}get processor(){return this.result.processor}get opts(){return this.result.opts}get css(){return this.stringify().css}get content(){return this.stringify().content}get map(){return this.stringify().map}get root(){return this.sync().root}get messages(){return this.sync().messages}warnings(){return this.sync().warnings()}toString(){return this.css}then(e,t){return this.async().then(e,t)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins)if(p(this.runOnRoot(e)))throw this.getAsyncError();if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[i];)e[i]=!0,this.walkSync(e);this.listeners.OnceExit&&this.visitSync(this.listeners.OnceExit,e)}return this.result}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=o;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let n=new r(t,this.result.root,this.result.opts).generate();return this.result.css=n[0],this.result.map=n[1],this.result}walkSync(e){e[i]=!0;let t=d(e);for(let n of t)if(0===n)e.nodes&&e.each((e=>{e[i]||this.walkSync(e)}));else{let t=this.listeners[n];if(t&&this.visitSync(t,e.toProxy()))return}}visitSync(e,t){for(let[n,r]of e){let e;this.result.lastPlugin=n;try{e=r(t,this.helpers)}catch(e){throw this.handleError(e,t.proxyOf)}if("root"!==t.type&&!t.parent)return!0;if(p(e))throw this.getAsyncError()}}runOnRoot(e){this.result.lastPlugin=e;try{if("object"==typeof e&&e.Once)return e.Once(this.result.root,this.helpers);if("function"==typeof e)return e(this.result.root,this.result)}catch(e){throw this.handleError(e)}}getAsyncError(){throw new Error("Use process(css).then(cb) to work with async plugins")}handleError(e,t){let n=this.result.lastPlugin;try{t&&t.addToError(e),this.error=e,"CssSyntaxError"!==e.name||e.plugin?n.postcssVersion:(e.plugin=n.postcssPlugin,e.setMessage())}catch(e){console&&console.error&&console.error(e)}return e}async runAsync(){this.plugin=0;for(let e=0;e0;){let e=this.visitTick(t);if(p(e))try{await e}catch(e){let n=t[t.length-1].node;throw this.handleError(e,n)}}}if(this.listeners.OnceExit)for(let[t,n]of this.listeners.OnceExit){this.result.lastPlugin=t;try{await n(e,this.helpers)}catch(e){throw this.handleError(e)}}}return this.processed=!0,this.stringify()}prepareVisitors(){this.listeners={};let e=(e,t,n)=>{this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push([e,n])};for(let t of this.plugins)if("object"==typeof t)for(let n in t){if(!c[n]&&/^[A-Z]/.test(n))throw new Error(`Unknown event ${n} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!f[n])if("object"==typeof t[n])for(let r in t[n])e(t,"*"===r?n:n+"-"+r.toLowerCase(),t[n][r]);else"function"==typeof t[n]&&e(t,n,t[n])}this.hasListener=Object.keys(this.listeners).length>0}visitTick(e){let t=e[e.length-1],{node:n,visitors:r}=t;if("root"!==n.type&&!n.parent)return void e.pop();if(r.length>0&&t.visitorIndex{m=e},e.exports=v,v.default=v,s.registerLazyResult(v)},5159:e=>{"use strict";let t={split(e,t,n){let r=[],i="",o=!1,a=0,u=!1,s=!1;for(let n of e)s?s=!1:"\\"===n?s=!0:u?n===u&&(u=!1):'"'===n||"'"===n?u=n:"("===n?a+=1:")"===n?a>0&&(a-=1):0===a&&t.includes(n)&&(o=!0),o?(""!==i&&r.push(i.trim()),i="",o=!1):i+=n;return(n||""!==i)&&r.push(i.trim()),r},space:e=>t.split(e,[" ","\n","\t"]),comma:e=>t.split(e,[","],!0)};e.exports=t,t.default=t},7837:(e,t,n)=>{"use strict";let{dirname:r,resolve:i,relative:o,sep:a}=n(9830),{pathToFileURL:u}=n(7414),{SourceMapConsumer:s,SourceMapGenerator:l}=n(4061),c=Boolean(s&&l),f=Boolean(r&&i&&o&&a);e.exports=class{constructor(e,t,n){this.stringify=e,this.mapOpts=n.map||{},this.root=t,this.opts=n}isMap(){return void 0!==this.opts.map?!!this.opts.map:this.previous().length>0}previous(){return this.previousMaps||(this.previousMaps=[],this.root.walk((e=>{if(e.source&&e.source.input.map){let t=e.source.input.map;this.previousMaps.includes(t)||this.previousMaps.push(t)}}))),this.previousMaps}isInline(){if(void 0!==this.mapOpts.inline)return this.mapOpts.inline;let e=this.mapOpts.annotation;return(void 0===e||!0===e)&&(!this.previous().length||this.previous().some((e=>e.inline)))}isSourcesContent(){return void 0!==this.mapOpts.sourcesContent?this.mapOpts.sourcesContent:!this.previous().length||this.previous().some((e=>e.withContent()))}clearAnnotation(){if(!1===this.mapOpts.annotation)return;let e;for(let t=this.root.nodes.length-1;t>=0;t--)e=this.root.nodes[t],"comment"===e.type&&0===e.text.indexOf("# sourceMappingURL=")&&this.root.removeChild(t)}setSourcesContent(){let e={};this.root.walk((t=>{if(t.source){let n=t.source.input.from;n&&!e[n]&&(e[n]=!0,this.map.setSourceContent(this.toUrl(this.path(n)),t.source.input.css))}}))}applyPrevMaps(){for(let e of this.previous()){let t,n=this.toUrl(this.path(e.file)),i=e.root||r(e.file);!1===this.mapOpts.sourcesContent?(t=new s(e.text),t.sourcesContent&&(t.sourcesContent=t.sourcesContent.map((()=>null)))):t=e.consumer(),this.map.applySourceMap(t,n,this.toUrl(this.path(i)))}}isAnnotation(){return!!this.isInline()||(void 0!==this.mapOpts.annotation?this.mapOpts.annotation:!this.previous().length||this.previous().some((e=>e.annotation)))}toBase64(e){return Buffer?Buffer.from(e).toString("base64"):window.btoa(unescape(encodeURIComponent(e)))}addAnnotation(){let e;e=this.isInline()?"data:application/json;base64,"+this.toBase64(this.map.toString()):"string"==typeof this.mapOpts.annotation?this.mapOpts.annotation:"function"==typeof this.mapOpts.annotation?this.mapOpts.annotation(this.opts.to,this.root):this.outputFile()+".map";let t="\n";this.css.includes("\r\n")&&(t="\r\n"),this.css+=t+"/*# sourceMappingURL="+e+" */"}outputFile(){return this.opts.to?this.path(this.opts.to):this.opts.from?this.path(this.opts.from):"to.css"}generateMap(){return this.generateString(),this.isSourcesContent()&&this.setSourcesContent(),this.previous().length>0&&this.applyPrevMaps(),this.isAnnotation()&&this.addAnnotation(),this.isInline()?[this.css]:[this.css,this.map]}path(e){if(0===e.indexOf("<"))return e;if(/^\w+:\/\//.test(e))return e;if(this.mapOpts.absolute)return e;let t=this.opts.to?r(this.opts.to):".";return"string"==typeof this.mapOpts.annotation&&(t=r(i(t,this.mapOpts.annotation))),o(t,e)}toUrl(e){return"\\"===a&&(e=e.replace(/\\/g,"/")),encodeURI(e).replace(/[#?]/g,encodeURIComponent)}sourcePath(e){if(this.mapOpts.from)return this.toUrl(this.mapOpts.from);if(this.mapOpts.absolute){if(u)return u(e.source.input.from).toString();throw new Error("`map.absolute` option is not available in this PostCSS build")}return this.toUrl(this.path(e.source.input.from))}generateString(){this.css="",this.map=new l({file:this.outputFile()});let e,t,n=1,r=1,i="",o={source:"",generated:{line:0,column:0},original:{line:0,column:0}};this.stringify(this.root,((a,u,s)=>{if(this.css+=a,u&&"end"!==s&&(o.generated.line=n,o.generated.column=r-1,u.source&&u.source.start?(o.source=this.sourcePath(u),o.original.line=u.source.start.line,o.original.column=u.source.start.column-1,this.map.addMapping(o)):(o.source=i,o.original.line=1,o.original.column=0,this.map.addMapping(o))),e=a.match(/\n/g),e?(n+=e.length,t=a.lastIndexOf("\n"),r=a.length-t):r+=a.length,u&&"start"!==s){let e=u.parent||{raws:{}};("decl"!==u.type||u!==e.last||e.raws.semicolon)&&(u.source&&u.source.end?(o.source=this.sourcePath(u),o.original.line=u.source.end.line,o.original.column=u.source.end.column-1,o.generated.line=n,o.generated.column=r-2,this.map.addMapping(o)):(o.source=i,o.original.line=1,o.original.column=0,o.generated.line=n,o.generated.column=r-1,this.map.addMapping(o)))}}))}generate(){if(this.clearAnnotation(),f&&c&&this.isMap())return this.generateMap();let e="";return this.stringify(this.root,(t=>{e+=t})),[e]}}},7003:(e,t,n)=>{"use strict";let r=n(7664),i=n(679),{isClean:o}=n(5394),a=n(5821);function u(e,t){let n=new e.constructor;for(let r in e){if(!Object.prototype.hasOwnProperty.call(e,r))continue;if("proxyCache"===r)continue;let i=e[r],o=typeof i;"parent"===r&&"object"===o?t&&(n[r]=t):"source"===r?n[r]=i:Array.isArray(i)?n[r]=i.map((e=>u(e,n))):("object"===o&&null!==i&&(i=u(i)),n[r]=i)}return n}class s{constructor(e={}){this.raws={},this[o]=!1;for(let t in e)if("nodes"===t){this.nodes=[];for(let n of e[t])"function"==typeof n.clone?this.append(n.clone()):this.append(n)}else this[t]=e[t]}error(e,t={}){if(this.source){let n=this.positionBy(t);return this.source.input.error(e,n.line,n.column,t)}return new r(e)}warn(e,t,n){let r={node:this};for(let e in n)r[e]=n[e];return e.warn(t,r)}remove(){return this.parent&&this.parent.removeChild(this),this.parent=void 0,this}toString(e=a){e.stringify&&(e=e.stringify);let t="";return e(this,(e=>{t+=e})),t}clone(e={}){let t=u(this);for(let n in e)t[n]=e[n];return t}cloneBefore(e={}){let t=this.clone(e);return this.parent.insertBefore(this,t),t}cloneAfter(e={}){let t=this.clone(e);return this.parent.insertAfter(this,t),t}replaceWith(...e){if(this.parent){let t=this,n=!1;for(let r of e)r===this?n=!0:n?(this.parent.insertAfter(t,r),t=r):this.parent.insertBefore(t,r);n||this.remove()}return this}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}prev(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e-1]}before(e){return this.parent.insertBefore(this,e),this}after(e){return this.parent.insertAfter(this,e),this}root(){let e=this;for(;e.parent;)e=e.parent;return e}raw(e,t){return(new i).raw(this,e,t)}cleanRaws(e){delete this.raws.before,delete this.raws.after,e||delete this.raws.between}toJSON(e,t){let n={},r=null==t;t=t||new Map;let i=0;for(let e in this){if(!Object.prototype.hasOwnProperty.call(this,e))continue;if("parent"===e||"proxyCache"===e)continue;let r=this[e];if(Array.isArray(r))n[e]=r.map((e=>"object"==typeof e&&e.toJSON?e.toJSON(null,t):e));else if("object"==typeof r&&r.toJSON)n[e]=r.toJSON(null,t);else if("source"===e){let o=t.get(r.input);null==o&&(o=i,t.set(r.input,i),i++),n[e]={inputId:o,start:r.start,end:r.end}}else n[e]=r}return r&&(n.inputs=[...t.keys()].map((e=>e.toJSON()))),n}positionInside(e){let t=this.toString(),n=this.source.start.column,r=this.source.start.line;for(let i=0;i(e[t]===n||(e[t]=n,"prop"!==t&&"value"!==t&&"name"!==t&&"params"!==t&&"important"!==t&&"text"!==t||e.markDirty()),!0),get:(e,t)=>"proxyOf"===t?e:"root"===t?()=>e.root().toProxy():e[t]}}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}addToError(e){if(e.postcssNode=this,e.stack&&this.source&&/\n\s{4}at /.test(e.stack)){let t=this.source;e.stack=e.stack.replace(/\n\s{4}at /,`$&${t.input.from}:${t.start.line}:${t.start.column}$&`)}return e}markDirty(){if(this[o]){this[o]=!1;let e=this;for(;e=e.parent;)e[o]=!1}}get proxyOf(){return this}}e.exports=s,s.default=s},6883:(e,t,n)=>{"use strict";let r=n(385),i=n(2439),o=n(2766);function a(e,t){let n=new o(e,t),r=new i(n);try{r.parse()}catch(e){throw e}return r.root}e.exports=a,a.default=a,r.registerParse(a)},2439:(e,t,n)=>{"use strict";let r=n(4509),i=n(1951),o=n(5859),a=n(602),u=n(1410),s=n(6900);e.exports=class{constructor(e){this.input=e,this.root=new u,this.current=this.root,this.spaces="",this.semicolon=!1,this.customProperty=!1,this.createTokenizer(),this.root.source={input:e,start:{offset:0,line:1,column:1}}}createTokenizer(){this.tokenizer=i(this.input)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case"space":this.spaces+=e[1];break;case";":this.freeSemicolon(e);break;case"}":this.end(e);break;case"comment":this.comment(e);break;case"at-word":this.atrule(e);break;case"{":this.emptyRule(e);break;default:this.other(e)}this.endFile()}comment(e){let t=new o;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]);let n=e[1].slice(2,-2);if(/^\s*$/.test(n))t.text="",t.raws.left=n,t.raws.right="";else{let e=n.match(/^(\s*)([^]*\S)(\s*)$/);t.text=e[2],t.raws.left=e[1],t.raws.right=e[3]}}emptyRule(e){let t=new s;this.init(t,e[2]),t.selector="",t.raws.between="",this.current=t}other(e){let t=!1,n=null,r=!1,i=null,o=[],a=e[1].startsWith("--"),u=[],s=e;for(;s;){if(n=s[0],u.push(s),"("===n||"["===n)i||(i=s),o.push("("===n?")":"]");else if(a&&r&&"{"===n)i||(i=s),o.push("}");else if(0===o.length){if(";"===n){if(r)return void this.decl(u,a);break}if("{"===n)return void this.rule(u);if("}"===n){this.tokenizer.back(u.pop()),t=!0;break}":"===n&&(r=!0)}else n===o[o.length-1]&&(o.pop(),0===o.length&&(i=null));s=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),o.length>0&&this.unclosedBracket(i),t&&r){for(;u.length&&(s=u[u.length-1][0],"space"===s||"comment"===s);)this.tokenizer.back(u.pop());this.decl(u,a)}else this.unknownWord(u)}rule(e){e.pop();let t=new s;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,"selector",e),this.current=t}decl(e,t){let n=new r;this.init(n,e[0][2]);let i,o=e[e.length-1];for(";"===o[0]&&(this.semicolon=!0,e.pop()),n.source.end=this.getPosition(o[3]||o[2]);"word"!==e[0][0];)1===e.length&&this.unknownWord(e),n.raws.before+=e.shift()[1];for(n.source.start=this.getPosition(e[0][2]),n.prop="";e.length;){let t=e[0][0];if(":"===t||"space"===t||"comment"===t)break;n.prop+=e.shift()[1]}for(n.raws.between="";e.length;){if(i=e.shift(),":"===i[0]){n.raws.between+=i[1];break}"word"===i[0]&&/\w/.test(i[1])&&this.unknownWord([i]),n.raws.between+=i[1]}"_"!==n.prop[0]&&"*"!==n.prop[0]||(n.raws.before+=n.prop[0],n.prop=n.prop.slice(1));let a=this.spacesAndCommentsFromStart(e);this.precheckMissedSemicolon(e);for(let t=e.length-1;t>=0;t--){if(i=e[t],"!important"===i[1].toLowerCase()){n.important=!0;let r=this.stringFrom(e,t);r=this.spacesFromEnd(e)+r," !important"!==r&&(n.raws.important=r);break}if("important"===i[1].toLowerCase()){let r=e.slice(0),i="";for(let e=t;e>0;e--){let t=r[e][0];if(0===i.trim().indexOf("!")&&"space"!==t)break;i=r.pop()[1]+i}0===i.trim().indexOf("!")&&(n.important=!0,n.raws.important=i,e=r)}if("space"!==i[0]&&"comment"!==i[0])break}let u=e.some((e=>"space"!==e[0]&&"comment"!==e[0]));this.raw(n,"value",e),u?n.raws.between+=a:n.value=a+n.value,n.value.includes(":")&&!t&&this.checkMissedSemicolon(e)}atrule(e){let t,n,r,i=new a;i.name=e[1].slice(1),""===i.name&&this.unnamedAtrule(i,e),this.init(i,e[2]);let o=!1,u=!1,s=[],l=[];for(;!this.tokenizer.endOfFile();){if(t=(e=this.tokenizer.nextToken())[0],"("===t||"["===t?l.push("("===t?")":"]"):"{"===t&&l.length>0?l.push("}"):t===l[l.length-1]&&l.pop(),0===l.length){if(";"===t){i.source.end=this.getPosition(e[2]),this.semicolon=!0;break}if("{"===t){u=!0;break}if("}"===t){if(s.length>0){for(r=s.length-1,n=s[r];n&&"space"===n[0];)n=s[--r];n&&(i.source.end=this.getPosition(n[3]||n[2]))}this.end(e);break}s.push(e)}else s.push(e);if(this.tokenizer.endOfFile()){o=!0;break}}i.raws.between=this.spacesAndCommentsFromEnd(s),s.length?(i.raws.afterName=this.spacesAndCommentsFromStart(s),this.raw(i,"params",s),o&&(e=s[s.length-1],i.source.end=this.getPosition(e[3]||e[2]),this.spaces=i.raws.between,i.raws.between="")):(i.raws.afterName="",i.params=""),u&&(i.nodes=[],this.current=i)}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.spaces="",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||"")+this.spaces}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let e=this.current.nodes[this.current.nodes.length-1];e&&"rule"===e.type&&!e.raws.ownSemicolon&&(e.raws.ownSemicolon=this.spaces,this.spaces="")}}getPosition(e){let t=this.input.fromOffset(e);return{offset:e,line:t.line,column:t.col}}init(e,t){this.current.push(e),e.source={start:this.getPosition(t),input:this.input},e.raws.before=this.spaces,this.spaces="","comment"!==e.type&&(this.semicolon=!1)}raw(e,t,n){let r,i,o,a,u=n.length,s="",l=!0,c=/^([#.|])?(\w)+/i;for(let t=0;te+t[1]),"");e.raws[t]={value:s,raw:r}}e[t]=s}spacesAndCommentsFromEnd(e){let t,n="";for(;e.length&&(t=e[e.length-1][0],"space"===t||"comment"===t);)n=e.pop()[1]+n;return n}spacesAndCommentsFromStart(e){let t,n="";for(;e.length&&(t=e[0][0],"space"===t||"comment"===t);)n+=e.shift()[1];return n}spacesFromEnd(e){let t,n="";for(;e.length&&(t=e[e.length-1][0],"space"===t);)n=e.pop()[1]+n;return n}stringFrom(e,t){let n="";for(let r=t;r=0&&(n=e[i],"space"===n[0]||(r+=1,2!==r));i--);throw this.input.error("Missed semicolon",n[2])}}},5173:(e,t,n)=>{"use strict";let r=n(7664),i=n(4509),o=n(6445),a=n(385),u=n(1087),s=n(5821),l=n(6595),c=n(4856),f=n(5859),p=n(602),d=n(7049),h=n(2766),g=n(6883),m=n(5159),v=n(6900),y=n(1410),b=n(7003);function w(...e){return 1===e.length&&Array.isArray(e[0])&&(e=e[0]),new u(e)}w.plugin=function(e,t){function n(...n){let r=t(...n);return r.postcssPlugin=e,r.postcssVersion=(new u).version,r}let r;return console&&console.warn&&(console.warn(e+": postcss.plugin was deprecated. Migration guide:\nhttps://evilmartians.com/chronicles/postcss-8-plugin-migration"),process.env.LANG&&process.env.LANG.startsWith("cn")&&console.warn(e+": 里面 postcss.plugin 被弃用. 迁移指南:\nhttps://www.w3ctech.com/topic/2226")),Object.defineProperty(n,"postcss",{get:()=>(r||(r=n()),r)}),n.process=function(e,t,r){return w([n(r)]).process(e,t)},n},w.stringify=s,w.parse=g,w.fromJSON=l,w.list=m,w.comment=e=>new f(e),w.atRule=e=>new p(e),w.decl=e=>new i(e),w.rule=e=>new v(e),w.root=e=>new y(e),w.CssSyntaxError=r,w.Declaration=i,w.Container=a,w.Comment=f,w.Warning=c,w.AtRule=p,w.Result=d,w.Input=h,w.Rule=v,w.Root=y,w.Node=b,o.registerPostcss(w),e.exports=w,w.default=w},6086:(e,t,n)=>{"use strict";let{existsSync:r,readFileSync:i}=n(4777),{dirname:o,join:a}=n(9830),{SourceMapConsumer:u,SourceMapGenerator:s}=n(4061);class l{constructor(e,t){if(!1===t.map)return;this.loadAnnotation(e),this.inline=this.startWith(this.annotation,"data:");let n=t.map?t.map.prev:void 0,r=this.loadMap(t.from,n);!this.mapFile&&t.from&&(this.mapFile=t.from),this.mapFile&&(this.root=o(this.mapFile)),r&&(this.text=r)}consumer(){return this.consumerCache||(this.consumerCache=new u(this.text)),this.consumerCache}withContent(){return!!(this.consumer().sourcesContent&&this.consumer().sourcesContent.length>0)}startWith(e,t){return!!e&&e.substr(0,t.length)===t}getAnnotationURL(e){return e.match(/\/\*\s*# sourceMappingURL=((?:(?!sourceMappingURL=).)*)\*\//)[1].trim()}loadAnnotation(e){let t=e.match(/\/\*\s*# sourceMappingURL=(?:(?!sourceMappingURL=).)*\*\//gm);if(t&&t.length>0){let e=t[t.length-1];e&&(this.annotation=this.getAnnotationURL(e))}}decodeInline(e){if(/^data:application\/json;charset=utf-?8,/.test(e)||/^data:application\/json,/.test(e))return decodeURIComponent(e.substr(RegExp.lastMatch.length));if(/^data:application\/json;charset=utf-?8;base64,/.test(e)||/^data:application\/json;base64,/.test(e))return t=e.substr(RegExp.lastMatch.length),Buffer?Buffer.from(t,"base64").toString():window.atob(t);var t;let n=e.match(/data:application\/json;([^,]+),/)[1];throw new Error("Unsupported source map encoding "+n)}loadFile(e){if(this.root=o(e),r(e))return this.mapFile=e,i(e,"utf-8").toString().trim()}loadMap(e,t){if(!1===t)return!1;if(t){if("string"==typeof t)return t;if("function"!=typeof t){if(t instanceof u)return s.fromSourceMap(t).toString();if(t instanceof s)return t.toString();if(this.isMap(t))return JSON.stringify(t);throw new Error("Unsupported previous source map format: "+t.toString())}{let n=t(e);if(n){let e=this.loadFile(n);if(!e)throw new Error("Unable to load previous source map: "+n.toString());return e}}}else{if(this.inline)return this.decodeInline(this.annotation);if(this.annotation){let t=this.annotation;return e&&(t=a(o(e),t)),this.loadFile(t)}}}isMap(e){return"object"==typeof e&&("string"==typeof e.mappings||"string"==typeof e._mappings||Array.isArray(e.sections))}}e.exports=l,l.default=l},1087:(e,t,n)=>{"use strict";let r=n(6445),i=n(1410);class o{constructor(e=[]){this.version="8.2.15",this.plugins=this.normalize(e)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}process(e,t={}){return 0!==this.plugins.length||t.parser!==t.stringifier||t.hideNothingWarning,new r(this,e,t)}normalize(e){let t=[];for(let n of e)if(!0===n.postcss?n=n():n.postcss&&(n=n.postcss),"object"==typeof n&&Array.isArray(n.plugins))t=t.concat(n.plugins);else if("object"==typeof n&&n.postcssPlugin)t.push(n);else if("function"==typeof n)t.push(n);else if("object"!=typeof n||!n.parse&&!n.stringify)throw new Error(n+" is not a PostCSS plugin");return t}}e.exports=o,o.default=o,i.registerProcessor(o)},7049:(e,t,n)=>{"use strict";let r=n(4856);class i{constructor(e,t,n){this.processor=e,this.messages=[],this.root=t,this.opts=n,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let n=new r(e,t);return this.messages.push(n),n}warnings(){return this.messages.filter((e=>"warning"===e.type))}get content(){return this.css}}e.exports=i,i.default=i},1410:(e,t,n)=>{"use strict";let r,i,o=n(385);class a extends o{constructor(e){super(e),this.type="root",this.nodes||(this.nodes=[])}removeChild(e,t){let n=this.index(e);return!t&&0===n&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[n].raws.before),super.removeChild(e)}normalize(e,t,n){let r=super.normalize(e);if(t)if("prepend"===n)this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let e of r)e.raws.before=t.raws.before;return r}toResult(e={}){return new r(new i,this,e).stringify()}}a.registerLazyResult=e=>{r=e},a.registerProcessor=e=>{i=e},e.exports=a,a.default=a},6900:(e,t,n)=>{"use strict";let r=n(385),i=n(5159);class o extends r{constructor(e){super(e),this.type="rule",this.nodes||(this.nodes=[])}get selectors(){return i.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\s*/):null,n=t?t[0]:","+this.raw("between","beforeOpen");this.selector=e.join(n)}}e.exports=o,o.default=o,r.registerRule(o)},679:e=>{"use strict";const t={colon:": ",indent:" ",beforeDecl:"\n",beforeRule:"\n",beforeOpen:" ",beforeClose:"\n",beforeComment:"\n",after:"\n",emptyBody:"",commentLeft:" ",commentRight:" ",semicolon:!1};e.exports=class{constructor(e){this.builder=e}stringify(e,t){if(!this[e.type])throw new Error("Unknown AST node type "+e.type+". Maybe you need to change PostCSS stringifier.");this[e.type](e,t)}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}comment(e){let t=this.raw(e,"left","commentLeft"),n=this.raw(e,"right","commentRight");this.builder("/*"+t+e.text+n+"*/",e)}decl(e,t){let n=this.raw(e,"between","colon"),r=e.prop+n+this.rawValue(e,"value");e.important&&(r+=e.raws.important||" !important"),t&&(r+=";"),this.builder(r,e)}rule(e){this.block(e,this.rawValue(e,"selector")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,"end")}atrule(e,t){let n="@"+e.name,r=e.params?this.rawValue(e,"params"):"";if(void 0!==e.raws.afterName?n+=e.raws.afterName:r&&(n+=" "),e.nodes)this.block(e,n+r);else{let i=(e.raws.between||"")+(t?";":"");this.builder(n+r+i,e)}}body(e){let t=e.nodes.length-1;for(;t>0&&"comment"===e.nodes[t].type;)t-=1;let n=this.raw(e,"semicolon");for(let r=0;r{if(i=e.raws[n],void 0!==i)return!1}))}var u;return void 0===i&&(i=t[r]),a.rawCache[r]=i,i}rawSemicolon(e){let t;return e.walk((e=>{if(e.nodes&&e.nodes.length&&"decl"===e.last.type&&(t=e.raws.semicolon,void 0!==t))return!1})),t}rawEmptyBody(e){let t;return e.walk((e=>{if(e.nodes&&0===e.nodes.length&&(t=e.raws.after,void 0!==t))return!1})),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk((n=>{let r=n.parent;if(r&&r!==e&&r.parent&&r.parent===e&&void 0!==n.raws.before){let e=n.raws.before.split("\n");return t=e[e.length-1],t=t.replace(/\S/g,""),!1}})),t}rawBeforeComment(e,t){let n;return e.walkComments((e=>{if(void 0!==e.raws.before)return n=e.raws.before,n.includes("\n")&&(n=n.replace(/[^\n]+$/,"")),!1})),void 0===n?n=this.raw(t,null,"beforeDecl"):n&&(n=n.replace(/\S/g,"")),n}rawBeforeDecl(e,t){let n;return e.walkDecls((e=>{if(void 0!==e.raws.before)return n=e.raws.before,n.includes("\n")&&(n=n.replace(/[^\n]+$/,"")),!1})),void 0===n?n=this.raw(t,null,"beforeRule"):n&&(n=n.replace(/\S/g,"")),n}rawBeforeRule(e){let t;return e.walk((n=>{if(n.nodes&&(n.parent!==e||e.first!==n)&&void 0!==n.raws.before)return t=n.raws.before,t.includes("\n")&&(t=t.replace(/[^\n]+$/,"")),!1})),t&&(t=t.replace(/\S/g,"")),t}rawBeforeClose(e){let t;return e.walk((e=>{if(e.nodes&&e.nodes.length>0&&void 0!==e.raws.after)return t=e.raws.after,t.includes("\n")&&(t=t.replace(/[^\n]+$/,"")),!1})),t&&(t=t.replace(/\S/g,"")),t}rawBeforeOpen(e){let t;return e.walk((e=>{if("decl"!==e.type&&(t=e.raws.between,void 0!==t))return!1})),t}rawColon(e){let t;return e.walkDecls((e=>{if(void 0!==e.raws.between)return t=e.raws.between.replace(/[^\s:]/g,""),!1})),t}beforeAfter(e,t){let n;n="decl"===e.type?this.raw(e,null,"beforeDecl"):"comment"===e.type?this.raw(e,null,"beforeComment"):"before"===t?this.raw(e,null,"beforeRule"):this.raw(e,null,"beforeClose");let r=e.parent,i=0;for(;r&&"root"!==r.type;)i+=1,r=r.parent;if(n.includes("\n")){let t=this.raw(e,null,"indent");if(t.length)for(let e=0;e{"use strict";let r=n(679);function i(e,t){new r(t).stringify(e)}e.exports=i,i.default=i},5394:e=>{"use strict";e.exports.isClean=Symbol("isClean")},1951:e=>{"use strict";const t="'".charCodeAt(0),n='"'.charCodeAt(0),r="\\".charCodeAt(0),i="/".charCodeAt(0),o="\n".charCodeAt(0),a=" ".charCodeAt(0),u="\f".charCodeAt(0),s="\t".charCodeAt(0),l="\r".charCodeAt(0),c="[".charCodeAt(0),f="]".charCodeAt(0),p="(".charCodeAt(0),d=")".charCodeAt(0),h="{".charCodeAt(0),g="}".charCodeAt(0),m=";".charCodeAt(0),v="*".charCodeAt(0),y=":".charCodeAt(0),b="@".charCodeAt(0),w=/[\t\n\f\r "#'()/;[\\\]{}]/g,D=/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g,x=/.[\n"'(/\\]/,E=/[\da-f]/i;e.exports=function(e,_={}){let C,k,A,S,F,O,T,P,B,N,R=e.css.valueOf(),j=_.ignoreErrors,L=R.length,M=0,I=[],z=[];function U(t){throw e.error("Unclosed "+t,M)}return{back:function(e){z.push(e)},nextToken:function(e){if(z.length)return z.pop();if(M>=L)return;let _=!!e&&e.ignoreUnclosed;switch(C=R.charCodeAt(M),C){case o:case a:case s:case l:case u:k=M;do{k+=1,C=R.charCodeAt(k)}while(C===a||C===o||C===s||C===l||C===u);N=["space",R.slice(M,k)],M=k-1;break;case c:case f:case h:case g:case y:case m:case d:{let e=String.fromCharCode(C);N=[e,e,M];break}case p:if(P=I.length?I.pop()[1]:"",B=R.charCodeAt(M+1),"url"===P&&B!==t&&B!==n&&B!==a&&B!==o&&B!==s&&B!==u&&B!==l){k=M;do{if(O=!1,k=R.indexOf(")",k+1),-1===k){if(j||_){k=M;break}U("bracket")}for(T=k;R.charCodeAt(T-1)===r;)T-=1,O=!O}while(O);N=["brackets",R.slice(M,k+1),M,k],M=k}else k=R.indexOf(")",M+1),S=R.slice(M,k+1),-1===k||x.test(S)?N=["(","(",M]:(N=["brackets",S,M,k],M=k);break;case t:case n:A=C===t?"'":'"',k=M;do{if(O=!1,k=R.indexOf(A,k+1),-1===k){if(j||_){k=M+1;break}U("string")}for(T=k;R.charCodeAt(T-1)===r;)T-=1,O=!O}while(O);N=["string",R.slice(M,k+1),M,k],M=k;break;case b:w.lastIndex=M+1,w.test(R),k=0===w.lastIndex?R.length-1:w.lastIndex-2,N=["at-word",R.slice(M,k+1),M,k],M=k;break;case r:for(k=M,F=!0;R.charCodeAt(k+1)===r;)k+=1,F=!F;if(C=R.charCodeAt(k+1),F&&C!==i&&C!==a&&C!==o&&C!==s&&C!==l&&C!==u&&(k+=1,E.test(R.charAt(k)))){for(;E.test(R.charAt(k+1));)k+=1;R.charCodeAt(k+1)===a&&(k+=1)}N=["word",R.slice(M,k+1),M,k],M=k;break;default:C===i&&R.charCodeAt(M+1)===v?(k=R.indexOf("*/",M+2)+1,0===k&&(j||_?k=R.length:U("comment")),N=["comment",R.slice(M,k+1),M,k],M=k):(D.lastIndex=M+1,D.test(R),k=0===D.lastIndex?R.length-1:D.lastIndex-2,N=["word",R.slice(M,k+1),M,k],I.push(N),M=k)}return M++,N},endOfFile:function(){return 0===z.length&&M>=L},position:function(){return M}}}},134:e=>{"use strict";let t={};e.exports=function(e){t[e]||(t[e]=!0,"undefined"!=typeof console&&console.warn&&console.warn(e))}},4856:e=>{"use strict";class t{constructor(e,t={}){if(this.type="warning",this.text=e,t.node&&t.node.source){let e=t.node.positionBy(t);this.line=e.line,this.column=e.column}for(let e in t)this[e]=t[e]}toString(){return this.node?this.node.error(this.text,{plugin:this.plugin,index:this.index,word:this.word}).message:this.plugin?this.plugin+": "+this.text:this.text}}e.exports=t,t.default=t},9189:(e,t,n)=>{"use strict";var r=n(1837);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,a){if(a!==r){var u=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},3437:(e,t,n)=>{e.exports=n(9189)()},1837:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},9704:(e,t,n)=>{"use strict";var r=n(1742),i=n(2739),o=n(2378);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n