From 548db7150d300df57e83934583e8e08c6b60e4b2 Mon Sep 17 00:00:00 2001
From: Vincent <vincent@loki.network>
Date: Fri, 28 Feb 2020 09:49:03 +1100
Subject: [PATCH] Start message selection reactification

---
 js/background.js                              |   7 +-
 package.json                                  |   2 +
 preload.js                                    |  48 +++---
 ts/components/conversation/Message.tsx        |   2 +-
 .../session/LeftPaneMessageSection.tsx        |   2 +-
 .../conversation/SessionCompositionBox.tsx    | 149 +++++++++++-------
 .../conversation/SessionConversation.tsx      |  20 ++-
 .../session/conversation/SessionRecording.tsx |  15 +-
 .../session/settings/SessionSettings.tsx      |  44 ++++--
 ts/global.d.ts                                |   1 +
 ts/state/ducks/search.ts                      |  30 ----
 yarn.lock                                     |  20 +++
 12 files changed, 202 insertions(+), 138 deletions(-)

diff --git a/js/background.js b/js/background.js
index 859909b61..d8aba5f6e 100644
--- a/js/background.js
+++ b/js/background.js
@@ -1056,8 +1056,11 @@
     };
 
     window.toggleMediaPermissions = () => {
-      const mediaPermissions = window.getMediaPermissions();
-      window.setMediaPermissions(!mediaPermissions);
+      // eslint-disable-next-line more/no-then
+      window.getMediaPermissions().then(value => {
+        window.setMediaPermissions(!value);
+      });
+
     };
 
     // attempts a connection to an open group server
diff --git a/package.json b/package.json
index 2db553450..78019ed96 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
     "@sindresorhus/is": "0.8.0",
     "@types/dompurify": "^2.0.0",
     "@types/rc-slider": "^8.6.5",
+    "@types/react-mic": "^12.4.1",
     "backbone": "1.3.3",
     "blob-util": "1.3.0",
     "blueimp-canvas-to-blob": "3.14.0",
@@ -114,6 +115,7 @@
     "react-autosize-textarea": "^7.0.0",
     "react-contextmenu": "2.11.0",
     "react-dom": "16.8.3",
+    "react-mic": "^12.4.1",
     "react-portal": "^4.2.0",
     "react-qr-svg": "^2.2.1",
     "react-redux": "6.0.1",
diff --git a/preload.js b/preload.js
index 40c512c44..6083dcc04 100644
--- a/preload.js
+++ b/preload.js
@@ -205,29 +205,6 @@ ipc.on('remove-dark-overlay', () => {
   }
 });
 
-window.getSettingValue = (settingID, comparisonValue = null) => {
-  // Comparison value allows you to pull boolean values from any type.
-  // Eg. window.getSettingValue('theme', 'light')
-  // returns 'false' when the value is 'dark'.
-
-  if (settingID === 'media-permissions') {
-    let permissionValue;
-    // eslint-disable-next-line more/no-then
-    window.getMediaPermissions().then(value => {
-      permissionValue = value;
-    });
-
-    return permissionValue;
-  }
-
-  const settingVal = window.storage.get(settingID);
-  return comparisonValue ? !!settingVal === comparisonValue : settingVal;
-};
-
-window.setSettingValue = (settingID, value) => {
-  window.storage.put(settingID, value);
-};
-
 installGetter('device-name', 'getDeviceName');
 
 installGetter('theme-setting', 'getThemeSetting');
@@ -260,6 +237,7 @@ installSetter('spell-check', 'setSpellCheck');
 installGetter('media-permissions', 'getMediaPermissions');
 installGetter('media-permissions', 'setMediaPermissions');
 
+
 window.getMediaPermissions = () =>
   new Promise((resolve, reject) => {
     ipc.once('get-success-media-permissions', (_event, error, value) => {
@@ -272,6 +250,30 @@ window.getMediaPermissions = () =>
     ipc.send('get-media-permissions');
   });
 
+window.setMediaPermissions = value => {
+  ipc.send('set-media-permissions', value);
+};
+
+window.getSettingValue = (settingID, comparisonValue = null) => {
+  // Comparison value allows you to pull boolean values from any type.
+  // Eg. window.getSettingValue('theme', 'light')
+  // returns 'false' when the value is 'dark'.
+
+  if (settingID === 'media-permissions') {
+    // This must be done asynchronously. Call
+    // await window.getMediaPermissions();
+    return null;
+  }
+
+  const settingVal = window.storage.get(settingID);
+  return comparisonValue ? !!settingVal === comparisonValue : settingVal;
+};
+
+
+window.setSettingValue = (settingID, value) => {
+  window.storage.put(settingID, value);
+};
+
 installGetter('is-primary', 'isPrimary');
 installGetter('sync-request', 'getSyncRequest');
 installGetter('sync-time', 'getLastSyncTime');
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 95d9b150d..d11fbc47f 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -107,7 +107,7 @@ export interface Props {
   onClickAttachment?: (attachment: AttachmentType) => void;
   onClickLinkPreview?: (url: string) => void;
   onCopyText?: () => void;
-  onSelectMessage: () => void;
+  onSelectMessage: (messageId: string) => void;
   onSelectMessageUnchecked: () => void;
   onReply?: () => void;
   onRetrySend?: () => void;
diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx
index c68c604bd..8fea380f9 100644
--- a/ts/components/session/LeftPaneMessageSection.tsx
+++ b/ts/components/session/LeftPaneMessageSection.tsx
@@ -209,7 +209,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
     );
   }
 
-  public renderConversations() {
+  public renderConversations() {\
     return (
       <div className="module-conversations-list-content">
         {this.state.shouldRenderMessageOnboarding ? (
diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx
index a5ae64bcd..a1d12f2bc 100644
--- a/ts/components/session/conversation/SessionCompositionBox.tsx
+++ b/ts/components/session/conversation/SessionCompositionBox.tsx
@@ -16,6 +16,7 @@ interface Props {
 interface State {
   message: string;
   isRecording: boolean;
+  mediaSetting: boolean | null;
   showEmojiPanel: boolean;
 }
 
@@ -28,19 +29,30 @@ export class SessionCompositionBox extends React.Component<Props, State> {
 
     this.state = {
       message: '',
-      isRecording: true,
+      isRecording: false,
+      mediaSetting: null,
       showEmojiPanel: false,
     };
 
     this.textarea = React.createRef();
     this.fileInput = React.createRef();
 
-    this.onKeyDown = this.onKeyDown.bind(this);
+    this.toggleEmojiPanel = this.toggleEmojiPanel.bind(this);
 
+    this.renderRecordingView = this.renderRecordingView.bind(this);
+    this.renderCompositionView = this.renderCompositionView.bind(this);
+
+    this.onKeyDown = this.onKeyDown.bind(this);
+    this.onStartRecording = this.onStartRecording.bind(this);
+    this.onStopRecording = this.onStopRecording.bind(this);
+    this.onSendMessage = this.onSendMessage.bind(this);
     this.onChooseAttachment = this.onChooseAttachment.bind(this);
-    this.toggleEmojiPanel = this.toggleEmojiPanel.bind(this);
     
-    this.onSendMessage = this.onSendMessage.bind(this);
+  }
+
+  public async componentWillMount() {
+    const mediaSetting = await window.getMediaPermissions();
+    this.setState({mediaSetting});
   }
 
   public componentWillReceiveProps(){
@@ -48,66 +60,15 @@ export class SessionCompositionBox extends React.Component<Props, State> {
   }
 
   render() {
-    const { placeholder } = this.props;
-    const { showEmojiPanel } = this.state;
+    const { isRecording } = this.state;
 
     return (
       <div className="composition-container">
-        { this.state.isRecording ? (
-          <SessionRecording
-            onStoppedRecording={this.props.onStoppedRecording}
-          />
+        { isRecording ? (
+          <>{this.renderRecordingView()}</>
         ) : (
-          <>
-            <SessionIconButton
-              iconType={SessionIconType.CirclePlus}
-              iconSize={SessionIconSize.Large}
-              onClick={this.onChooseAttachment}
-            />
-
-            <input
-              className="hidden"
-              multiple={true}
-              ref={this.fileInput}
-              type='file'
-            />
-            
-            <SessionIconButton
-              iconType={SessionIconType.Microphone}
-              iconSize={SessionIconSize.Huge}
-              onClick={this.onStartRecording}
-            />
-
-            <div className="send-message-input">
-              <TextareaAutosize
-                rows={1}
-                maxRows={3}
-                ref={this.textarea}
-                placeholder={placeholder}
-                maxLength={window.CONSTANTS.MAX_MESSAGE_BODY_LENGTH}
-                onKeyDown={this.onKeyDown}
-              />
-            </div>
-
-            <SessionIconButton
-              iconType={SessionIconType.Emoji}
-              iconSize={SessionIconSize.Large}
-              onClick={this.toggleEmojiPanel}
-            />
-            <div className="send-message-button">
-              <SessionIconButton
-                iconType={SessionIconType.Send}
-                iconSize={SessionIconSize.Large}
-                iconColor={'#FFFFFF'}
-                iconRotation={90}
-                onClick={this.onSendMessage}
-              />
-            </div>
-
-            {showEmojiPanel && <SessionEmojiPanel />}
-          </>
+          <>{this.renderCompositionView()}</>
         )}
-
       </div>
     );
   }
@@ -118,7 +79,72 @@ export class SessionCompositionBox extends React.Component<Props, State> {
     });
   }
   
+  private renderRecordingView() {
+    return (
+      <SessionRecording
+        onStoppedRecording={this.props.onStoppedRecording}
+      />
+      );
+  }
+
+  private renderCompositionView() {
+    const { placeholder } = this.props;
+    const { showEmojiPanel } = this.state;
+
+    return (
+      <>
+        <SessionIconButton
+          iconType={SessionIconType.CirclePlus}
+          iconSize={SessionIconSize.Large}
+          onClick={this.onChooseAttachment}
+        />
+
+        <input
+          className="hidden"
+          multiple={true}
+          ref={this.fileInput}
+          type='file'
+        />
+        
+        { this.state.mediaSetting && (
+          <SessionIconButton
+            iconType={SessionIconType.Microphone}
+            iconSize={SessionIconSize.Huge}
+            onClick={this.onStartRecording}
+          />
+        )}
+
+        <div className="send-message-input">
+          <TextareaAutosize
+            rows={1}
+            maxRows={3}
+            ref={this.textarea}
+            placeholder={placeholder}
+            maxLength={window.CONSTANTS.MAX_MESSAGE_BODY_LENGTH}
+            onKeyDown={this.onKeyDown}
+          />
+        </div>
+
+        <SessionIconButton
+          iconType={SessionIconType.Emoji}
+          iconSize={SessionIconSize.Large}
+          onClick={this.toggleEmojiPanel}
+        />
+        <div className="send-message-button">
+          <SessionIconButton
+            iconType={SessionIconType.Send}
+            iconSize={SessionIconSize.Large}
+            iconColor={'#FFFFFF'}
+            iconRotation={90}
+            onClick={this.onSendMessage}
+          />
+        </div>
 
+        {showEmojiPanel && <SessionEmojiPanel />}
+      </>
+    );
+  }
+  
   private onChooseAttachment() {
     this.fileInput.current?.click();
   }
@@ -143,7 +169,6 @@ export class SessionCompositionBox extends React.Component<Props, State> {
       console.log(`[vince][msg] Message:`, messagePlaintext);
       console.log(`[vince][msg] Attachments:`, attachments);
 
-
     if (false){
       this.props.sendMessage();
     }
@@ -151,6 +176,9 @@ export class SessionCompositionBox extends React.Component<Props, State> {
 
   private onStartRecording(){
     // Do stuff for component, then run callback to SessionConversation
+    this.setState({
+      isRecording: true,
+    });
 
     this.props.onStartedRecording();
   }
@@ -161,4 +189,5 @@ export class SessionCompositionBox extends React.Component<Props, State> {
     this.props.onStoppedRecording();
   }
 
+
 }
diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx
index b338de989..09302c513 100644
--- a/ts/components/session/conversation/SessionConversation.tsx
+++ b/ts/components/session/conversation/SessionConversation.tsx
@@ -23,6 +23,7 @@ interface State {
   conversationKey: string;
   unreadCount: number;
   messages: Array<any>;
+  selectedMessages: Array<string>;
   isScrolledToBottom: boolean;
   doneInitialScroll: boolean;
   messageFetchTimestamp: number;
@@ -45,6 +46,7 @@ export class SessionConversation extends React.Component<any, State> {
       conversationKey,
       unreadCount,
       messages: [],
+      selectedMessages: [],
       isScrolledToBottom: !unreadCount,
       doneInitialScroll: false,
       messageFetchTimestamp: 0,
@@ -99,7 +101,7 @@ export class SessionConversation extends React.Component<any, State> {
   render() {
     console.log(`[vince][info] Props`, this.props);
 
-    const { messages, conversationKey, doneInitialScroll } = this.state;
+    const { messages, conversationKey, doneInitialScroll, isRecording } = this.state;
     const loading = !doneInitialScroll || messages.length === 0;
 
     const conversation = this.props.conversations.conversationLookup[conversationKey];
@@ -129,7 +131,9 @@ export class SessionConversation extends React.Component<any, State> {
           </div>
 
           <SessionScrollButton display={true} onClick={this.scrollToBottom}/>
-          <div className="messages-wrapper--blocking-overlay"></div>
+          { isRecording && (
+            <div className="messages-wrapper--blocking-overlay"></div>
+          )}
         </div>
         
         { !isRss && (
@@ -262,7 +266,7 @@ export class SessionConversation extends React.Component<any, State> {
         onDownload = {messageProps?.onDownload}
         onReply = {messageProps?.onReply}
         onRetrySend = {messageProps?.onRetrySend}
-        onSelectMessage = {messageProps?.onSelectMessage}
+        onSelectMessage = {messageId => this.onSelectMessage(messageId)}
         onSelectMessageUnchecked = {messageProps?.onSelectMessageUnchecked}
         onShowDetail = {messageProps?.onShowDetail}
         onShowUserDetails = {messageProps?.onShowUserDetails}
@@ -399,7 +403,7 @@ export class SessionConversation extends React.Component<any, State> {
     const { messages, unreadCount } = this.state;
 
     const message = messages[(messages.length - 1) - unreadCount];
-    this.scrollToMessage(message.id);
+    message.id && this.scrollToMessage(message.id);
   }
 
   public scrollToMessage(messageId: string) {
@@ -535,6 +539,14 @@ export class SessionConversation extends React.Component<any, State> {
     };
   };
 
+  public onSelectMessage(messageId: string) {
+    const selectedMessages = !this.state.selectedMessages.includes(messageId)
+      ? [...this.state.selectedMessages, messageId] : [];
+    
+    selectedMessages && this.setState({ selectedMessages });
+    console.log(`[vince] SelectedMessages: `, selectedMessages);
+  }
+
   public getGroupSettingsProps() {
     const {conversationKey} = this.state;
     const conversation = window.getConversationByKey[conversationKey];
diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx
index 4d1226eea..e55b190aa 100644
--- a/ts/components/session/conversation/SessionRecording.tsx
+++ b/ts/components/session/conversation/SessionRecording.tsx
@@ -1,5 +1,7 @@
 import React from 'react';
 
+import {ReactMic} from 'react-mic';
+
 import {  SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
 import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton';
 
@@ -25,7 +27,6 @@ export class SessionRecording extends React.Component<Props, State> {
       isPaused: false,
       actionHover: false,
     };
-
     
     this.handleHoverActions = this.handleHoverActions.bind(this);
     this.handleUnhoverActions = this.handleUnhoverActions.bind(this);
@@ -39,6 +40,8 @@ export class SessionRecording extends React.Component<Props, State> {
 
   public componentWillReceiveProps(){
     console.log(`[vince][mic] Here are my composition props: `, this.props);
+
+    console.log(`[vince][mic] Permissions: `, navigator.getUserMedia({ audio: true }, () => null, error => alert(error)));
   }
 
   render() {
@@ -78,6 +81,15 @@ export class SessionRecording extends React.Component<Props, State> {
             )}
         </div>
 
+        {/* <ReactMic
+            record={this.state.isRecording}
+            visualSetting={'frequencyBars'}
+            className='session-recording--visualisation'
+            onStop={() => null}
+            onData= {(data: any) => console.log(`[vince][mic] Data:`, data)}
+            strokeColor={'#00F480'}
+        /> */}
+
 
         <div className="send-message-button">
           <SessionIconButton
@@ -108,7 +120,6 @@ export class SessionRecording extends React.Component<Props, State> {
         });
     }
 
-    navigator.getUserMedia();
   }
 
   private handleUnhoverActions() {
diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx
index 692a0efbd..6dd3e9e52 100644
--- a/ts/components/session/settings/SessionSettings.tsx
+++ b/ts/components/session/settings/SessionSettings.tsx
@@ -32,6 +32,7 @@ export interface SettingsViewProps {
 interface State {
   hasPassword: boolean | null;
   pwdLockError: string | null;
+  mediaSetting: boolean | null;
   shouldLockSettings: boolean | null;
   linkedPubKeys: Array<any>;
 }
@@ -41,6 +42,7 @@ interface LocalSettingType {
   description: string | undefined;
   comparisonValue: string | undefined;
   id: any;
+  value?: any;
   content: any | undefined;
   hidden: any;
   title: string;
@@ -59,6 +61,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
     this.state = {
       hasPassword: null,
       pwdLockError: null,
+      mediaSetting: null,
       shouldLockSettings: true,
       linkedPubKeys: new Array(),
     };
@@ -74,6 +77,11 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
     window.addEventListener('keyup', this.onKeyUp);
   }
 
+  public async componentWillMount() {
+    const mediaSetting = await window.getMediaPermissions();
+    this.setState({mediaSetting});
+  }
+
   public componentDidMount() {
     setTimeout(() => $('#password-lock-input').focus(), 100);
 
@@ -90,7 +98,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
   }
 
   /* tslint:disable-next-line:max-func-body-length */
-  public renderSettingInCategory(): JSX.Element {
+  public renderSettingInCategory() {
     const { category } = this.props;
 
     let settings: Array<LocalSettingType>;
@@ -115,9 +123,15 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
             const description = setting.description || '';
 
             const comparisonValue = setting.comparisonValue || null;
-            const value =
+            
+            let value;
+            if (setting.id === 'media-permissions'){
+              value = this.state.mediaSetting;
+            } else {
+              value =
               window.getSettingValue(setting.id, comparisonValue) ||
               (setting.content && setting.content.defaultValue);
+            }
 
             const sliderFn =
               setting.type === SessionSettingType.Slider
@@ -416,19 +430,6 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
         },
         confirmationDialogParams: undefined,
       },
-      {
-        id: 'media-permissions',
-        title: window.i18n('mediaPermissionsTitle'),
-        description: window.i18n('mediaPermissionsDescription'),
-        hidden: false,
-        type: SessionSettingType.Toggle,
-        category: SessionSettingCategory.Permissions,
-        setFn: window.toggleMediaPermissions,
-        content: undefined,
-        comparisonValue: undefined,
-        onClick: undefined,
-        confirmationDialogParams: undefined,
-      },
       {
         id: 'message-ttl',
         title: window.i18n('messageTTL'),
@@ -444,6 +445,19 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
         },
         confirmationDialogParams: undefined,
       },
+      {
+        id: 'media-permissions',
+        title: window.i18n('mediaPermissionsTitle'),
+        description: window.i18n('mediaPermissionsDescription'),
+        hidden: false,
+        type: SessionSettingType.Toggle,
+        category: SessionSettingCategory.Privacy,
+        setFn: window.toggleMediaPermissions,
+        content: undefined,
+        comparisonValue: undefined,
+        onClick: undefined,
+        confirmationDialogParams: undefined,
+      },
       {
         id: 'read-receipt-setting',
         title: window.i18n('readReceiptSettingTitle'),
diff --git a/ts/global.d.ts b/ts/global.d.ts
index 1b0aec703..d9d1a117e 100644
--- a/ts/global.d.ts
+++ b/ts/global.d.ts
@@ -11,6 +11,7 @@ interface Window {
   clearLocalData: any;
 
   getAccountManager: any;
+  getMediaPermissions: any;
   getConversations: any;
   getConversationByKey: any;
   getMessagesByKey: any;
diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts
index ac3ee43b8..e74da0fb0 100644
--- a/ts/state/ducks/search.ts
+++ b/ts/state/ducks/search.ts
@@ -157,36 +157,6 @@ const getMessageProps = (messages: Array<MessageType>) => {
   });
 };
 
-async function doGetMessages(
-  query: string,
-  options: SearchOptions
-): Promise<SearchResultsPayloadType> {
-  const { regionCode } = options;
-
-  const [discussions, messages] = await Promise.all([
-    queryConversationsAndContacts(query, options),
-    queryMessages(query),
-  ]);
-  const { conversations, contacts } = discussions;
-  const filteredMessages = messages.filter(message => message !== undefined);
-
-  let messageSet = [];
-  if (filteredMessages && !filteredMessages.length) {
-    messageSet = filteredMessages.map(message => {
-      const model = getMessageModel(message);
-      return model.propsForMessage;
-    });
-  }
-
-  return {
-    query,
-    normalizedPhoneNumber: normalize(query, { regionCode }),
-    conversations,
-    contacts,
-    messages: messageSet,
-  };
-}
-
 async function queryMessages(query: string) {
   try {
     const normalized = cleanSearchTerm(query);
diff --git a/yarn.lock b/yarn.lock
index 7b1872633..d57583ff2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -333,6 +333,13 @@
   dependencies:
     "@types/react" "*"
 
+"@types/react-mic@^12.4.1":
+  version "12.4.1"
+  resolved "https://registry.yarnpkg.com/@types/react-mic/-/react-mic-12.4.1.tgz#7c261988c3be918b108df642a14101873b42846b"
+  integrity sha512-vlrXGS80ZEKI5d+cwWACaBSEFInBiNPl34BlVS3D0B6rTr6g3RhBCoR6gHZfO8VWP27wx7uVDcyUabTs17f8fg==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-portal@^4.0.2":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@types/react-portal/-/react-portal-4.0.2.tgz#57a7f4c8ad48097c5a2d0cbbd09187831b91afdf"
@@ -8458,6 +8465,11 @@ react-error-overlay@^4.0.1:
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89"
   integrity sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==
 
+react-ga@^2.2.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c"
+  integrity sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA==
+
 react-group@^1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/react-group/-/react-group-1.0.6.tgz#8dd7c00c3b35d05ce164021458bb07d580e3001a"
@@ -8487,6 +8499,14 @@ react-lifecycles-compat@^3.0.4:
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
+react-mic@^12.4.1:
+  version "12.4.1"
+  resolved "https://registry.yarnpkg.com/react-mic/-/react-mic-12.4.1.tgz#6476a321ccd3babc61ebb12319b0fc0db4ca30c3"
+  integrity sha512-l580F9Mv6NRslSQj+80azx2rr/5OjWISEsjXx2GXBXcLvnT9vGeSYHzFVwDhGBjo/cj25AzXDg+rSp4Bz/qS2w==
+  dependencies:
+    prop-types "^15.5.10"
+    react-ga "^2.2.0"
+
 react-portal@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.0.tgz#5400831cdb0ae64dccb8128121cf076089ab1afd"