diff --git a/app/controllers/api/v1/reaction_deck_controller.rb b/app/controllers/api/v1/reaction_deck_controller.rb
index cac2a9fb00..a65cd33e7f 100644
--- a/app/controllers/api/v1/reaction_deck_controller.rb
+++ b/app/controllers/api/v1/reaction_deck_controller.rb
@@ -18,6 +18,35 @@ class Api::V1::ReactionDeckController < Api::BaseController
end
def create
+ deck = []
+
+ (deck_params['emojis'] || []).each do |shortcode|
+ shortcode = shortcode.delete(':')
+ custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: nil)
+
+ emoji_data = {}
+
+ if custom_emoji
+ emoji_data['name'] = custom_emoji.shortcode
+ emoji_data['url'] = full_asset_url(custom_emoji.image.url)
+ emoji_data['static_url'] = full_asset_url(custom_emoji.image.url(:static))
+ emoji_data['width'] = custom_emoji.image_width
+ emoji_data['height'] = custom_emoji.image_height
+ emoji_data['custom_emoji_id'] = custom_emoji.id
+ else
+ emoji_data['name'] = shortcode
+ end
+
+ deck << emoji_data
+ end
+
+ current_user.settings['reaction_deck'] = deck.to_json
+ current_user.save!
+
+ render json: remove_metas(deck)
+ end
+
+ def legacy_create
deck = @deck
(deck_params['emojis'] || []).each do |data|
@@ -86,6 +115,7 @@ class Api::V1::ReactionDeckController < Api::BaseController
deck.tap do |d|
d.each do |item|
item.delete('custom_emoji_id')
+ # item.delete('id') if item.key?('id')
end
end
end
diff --git a/app/javascript/mastodon/actions/reaction_deck.js b/app/javascript/mastodon/actions/reaction_deck.js
index 5b8be5d62f..c1a1b0146e 100644
--- a/app/javascript/mastodon/actions/reaction_deck.js
+++ b/app/javascript/mastodon/actions/reaction_deck.js
@@ -47,11 +47,11 @@ export function fetchReactionDeckFail(error) {
};
}
-export function updateReactionDeck(id, emoji) {
+export function updateReactionDeck(emojis) {
return (dispatch, getState) => {
dispatch(updateReactionDeckRequest());
- api(getState).post('/api/v1/reaction_deck', { emojis: [{ id, emoji: emoji.native || emoji.id }] }).then(response => {
+ api(getState).post('/api/v1/reaction_deck', { emojis }).then(response => {
dispatch(updateReactionDeckSuccess(response.data));
}).catch(error => {
dispatch(updateReactionDeckFail(error));
diff --git a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx b/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
index dadcb1982e..d221ef857c 100644
--- a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
+++ b/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
@@ -2,12 +2,10 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
-import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
-import { updateReactionDeck, removeReactionDeck } from 'mastodon/actions/reaction_deck';
import Button from 'mastodon/components/button';
import EmojiPickerDropdownContainer from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
import emojify from 'mastodon/features/emoji/emoji';
@@ -17,19 +15,10 @@ const messages = defineMessages({
remove: { id: 'reaction_deck.remove', defaultMessage: 'Remove' },
});
-const MapStateToProps = (state, { emojiId, emojiMap }) => ({
- emoji: (state.get('reaction_deck', ImmutableList()).toArray().find(em => em.get('id') === emojiId) || ImmutableMap({ emoji: { shortcode: '' } })).get('name'),
- emojiMap,
-});
-
-const mapDispatchToProps = (dispatch, { emojiId }) => ({
- onChange: (emoji) => dispatch(updateReactionDeck(emojiId, emoji)),
- onRemove: () => dispatch(removeReactionDeck(emojiId)),
-});
-
class ReactionEmoji extends ImmutablePureComponent {
static propTypes = {
+ index: PropTypes.number,
emoji: PropTypes.string,
emojiMap: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@@ -40,8 +29,16 @@ class ReactionEmoji extends ImmutablePureComponent {
emoji: '',
};
+ handleChange = (emoji) => {
+ this.props.onChange(this.props.index, emoji);
+ };
+
+ handleRemove = () => {
+ this.props.onRemove(this.props.index);
+ };
+
render () {
- const { intl, emojiMap, emoji, onChange, onRemove } = this.props;
+ const { intl, emojiMap, emoji } = this.props;
let content = null;
@@ -69,13 +66,13 @@ class ReactionEmoji extends ImmutablePureComponent {
@@ -84,4 +81,4 @@ class ReactionEmoji extends ImmutablePureComponent {
}
-export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(ReactionEmoji));
+export default connect(injectIntl(ReactionEmoji));
diff --git a/app/javascript/mastodon/features/reaction_deck/index.jsx b/app/javascript/mastodon/features/reaction_deck/index.jsx
index a457272da5..feb309b804 100644
--- a/app/javascript/mastodon/features/reaction_deck/index.jsx
+++ b/app/javascript/mastodon/features/reaction_deck/index.jsx
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
+import { useEffect, useState } from "react";
import { defineMessages, injectIntl } from 'react-intl';
@@ -10,15 +11,36 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
+import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
+
+import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
+import Button from 'mastodon/components/button';
import ColumnHeader from 'mastodon/components/column_header';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import Column from 'mastodon/features/ui/components/column';
+
import ReactionEmoji from './components/reaction_emoji';
-const DECK_SIZE = 16;
+// https://medium.com/@wbern/getting-react-18s-strict-mode-to-work-with-react-beautiful-dnd-47bc909348e4
+/* eslint react/prop-types: 0 */
+const StrictModeDroppable = ({ children, ...props }) => {
+ const [enabled, setEnabled] = useState(false);
+ useEffect(() => {
+ const animation = requestAnimationFrame(() => setEnabled(true));
+ return () => {
+ cancelAnimationFrame(animation);
+ setEnabled(false);
+ };
+ }, []);
+ if (!enabled) {
+ return null;
+ }
+ return {children};
+};
+/* eslint react/prop-types: 0 */
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
@@ -33,6 +55,10 @@ const mapStateToProps = (state, props) => ({
emojiMap: customEmojiMap(state),
});
+const mapDispatchToProps = (dispatch) => ({
+ onChange: (emojis) => dispatch(updateReactionDeck(emojis)),
+});
+
class ReactionDeck extends ImmutablePureComponent {
static propTypes = {
@@ -42,8 +68,40 @@ class ReactionDeck extends ImmutablePureComponent {
emojiMap: ImmutablePropTypes.map,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
};
+ deckToArray = () => {
+ const { deck } = this.props;
+
+ return deck.map((item) => item.get('name')).toArray();
+ };
+
+ handleReorder = (result) => {
+ const newDeck = this.deckToArray();
+ const deleted = newDeck.splice(result.source.index, 1);
+ newDeck.splice(result.destination.index, 0, deleted[0]);
+ this.props.onChange(newDeck);
+ };
+
+ handleChange = (index, emoji) => {
+ const newDeck = this.deckToArray();
+ newDeck[index] = emoji.native || emoji.id.replace(':', '');
+ this.props.onChange(newDeck);
+ };
+
+ handleRemove = (index) => {
+ const newDeck = this.deckToArray();
+ newDeck.splice(index, 1);
+ this.props.onChange(newDeck);
+ };
+
+ handleAdd = () => {
+ const newDeck = this.deckToArray();
+ newDeck.push('👍');
+ this.props.onChange(newDeck);
+ }
+
render () {
const { intl, deck, emojiMap, multiColumn } = this.props;
@@ -69,9 +127,32 @@ class ReactionDeck extends ImmutablePureComponent {
scrollKey='reaction_deck'
bindToDocument={!multiColumn}
>
- {[...Array(DECK_SIZE).keys()].map(emojiId =>
-
- )}
+
+
+ {(provided) => (
+
+ {deck.map((emoji, index) => (
+
+ {(provided2) => (
+
+
+
+ )}
+
+ ))}
+ {provided.placeholder}
+
+
+
+ )}
+
+
@@ -83,4 +164,4 @@ class ReactionDeck extends ImmutablePureComponent {
}
-export default connect(mapStateToProps)(injectIntl(ReactionDeck));
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ReactionDeck));
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index eff67cb837..7b2154aed4 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -86,7 +86,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
},
reaction_deck: {
- max_emojis: 16,
+ max_emojis: 32_767,
},
reactions: {
diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb
index 059ce3a4d5..9c4ae9e3be 100644
--- a/app/serializers/rest/v1/instance_serializer.rb
+++ b/app/serializers/rest/v1/instance_serializer.rb
@@ -92,7 +92,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
},
reaction_deck: {
- max_emojis: 16,
+ max_emojis: 32_767,
},
reactions: {
diff --git a/package.json b/package.json
index 13720c56b4..d96e9b189b 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"prop-types": "^15.8.1",
"punycode": "^2.3.0",
"react": "^18.2.0",
+ "react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-hotkeys": "^1.1.4",
diff --git a/yarn.lock b/yarn.lock
index ae89f81d37..773070a0a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1062,7 +1062,7 @@
dependencies:
regenerator-runtime "^0.12.0"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
@@ -2006,7 +2006,7 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
-"@types/hoist-non-react-statics@^3.3.1":
+"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@@ -2222,6 +2222,16 @@
dependencies:
react-overlays "*"
+"@types/react-redux@^7.1.20":
+ version "7.1.25"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
+ integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-router-dom@^5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
@@ -4222,6 +4232,13 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-declaration-sorter@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec"
@@ -7940,6 +7957,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
@@ -9445,6 +9467,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
raf@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -9482,6 +9509,19 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
+react-beautiful-dnd@^13.1.1:
+ version "13.1.1"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
+ integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
+
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -9568,7 +9608,7 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^17.0.1:
+react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -9621,6 +9661,18 @@ react-redux-loading-bar@^5.0.4:
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
+react-redux@^7.2.0:
+ version "7.2.9"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
+ 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"
+
react-redux@^8.0.4:
version "8.0.5"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
@@ -9871,7 +9923,7 @@ redux-thunk@^2.4.2:
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
-redux@^4.0.0, redux@^4.2.1:
+redux@^4.0.0, redux@^4.0.4, redux@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
@@ -11281,6 +11333,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+tiny-invariant@^1.0.6:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
+ integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
+
tiny-queue@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046"
@@ -11685,6 +11742,11 @@ use-latest@^1.2.1:
dependencies:
use-isomorphic-layout-effect "^1.1.1"
+use-memo-one@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
+ integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
+
use-sync-external-store@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"