From fd686f2a6b77f03e78779e3bdc1610bc8bace88b Mon Sep 17 00:00:00 2001
From: Linda Paiste <lindapaiste@gmail.com>
Date: Mon, 24 Apr 2023 12:00:03 -0500
Subject: [PATCH 1/3] Refactor IDE reducer and actions using Redux Toolkit.

---
 client/constants.js                |  45 +----
 client/modules/IDE/actions/ide.js  | 274 ++++++-----------------------
 client/modules/IDE/reducers/ide.js | 234 +++++++++++++-----------
 3 files changed, 186 insertions(+), 367 deletions(-)

diff --git a/client/constants.js b/client/constants.js
index 2678e6950b..113778c52d 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -1,16 +1,7 @@
 // TODO Organize this file by reducer type, to break this apart into
 // multiple files
 export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
-export const TOGGLE_SKETCH = 'TOGGLE_SKETCH';
 
-export const START_SKETCH = 'START_SKETCH';
-export const STOP_SKETCH = 'STOP_SKETCH';
-
-export const START_ACCESSIBLE_OUTPUT = 'START_ACCESSIBLE_OUTPUT';
-export const STOP_ACCESSIBLE_OUTPUT = 'STOP_ACCESSIBLE_OUTPUT';
-
-export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
-export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
 export const SET_FONT_SIZE = 'SET_FONT_SIZE';
 export const SET_LINE_NUMBERS = 'SET_LINE_NUMBERS';
 
@@ -47,18 +38,11 @@ export const EDIT_COLLECTION = 'EDIT_COLLECTION';
 export const DELETE_PROJECT = 'DELETE_PROJECT';
 
 export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
-export const SHOW_MODAL = 'SHOW_MODAL';
-export const HIDE_MODAL = 'HIDE_MODAL';
 export const CREATE_FILE = 'CREATE_FILE';
 export const SET_BLOB_URL = 'SET_BLOB_URL';
 
-export const EXPAND_SIDEBAR = 'EXPAND_SIDEBAR';
-export const COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR';
-
 export const CONSOLE_EVENT = 'CONSOLE_EVENT';
 export const CLEAR_CONSOLE = 'CLEAR_CONSOLE';
-export const EXPAND_CONSOLE = 'EXPAND_CONSOLE';
-export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
 
 export const UPDATE_LINT_MESSAGE = 'UPDATE_LINT_MESSAGE';
 export const CLEAR_LINT_MESSAGE = 'CLEAR_LINT_MESSAGE';
@@ -76,34 +60,16 @@ export const SET_GRID_OUTPUT = 'SET_GRID_OUTPUT';
 export const SET_SOUND_OUTPUT = 'SET_SOUND_OUTPUT';
 export const SET_AUTOCLOSE_BRACKETS_QUOTES = 'SET_AUTOCLOSE_BRACKETS_QUOTES';
 
-export const OPEN_PROJECT_OPTIONS = 'OPEN_PROJECT_OPTIONS';
-export const CLOSE_PROJECT_OPTIONS = 'CLOSE_PROJECT_OPTIONS';
-export const SHOW_NEW_FOLDER_MODAL = 'SHOW_NEW_FOLDER_MODAL';
-export const CLOSE_NEW_FOLDER_MODAL = 'CLOSE_NEW_FOLDER_MODAL';
 export const SHOW_FOLDER_CHILDREN = 'SHOW_FOLDER_CHILDREN';
 export const HIDE_FOLDER_CHILDREN = 'HIDE_FOLDER_CHILDREN';
-export const OPEN_UPLOAD_FILE_MODAL = 'OPEN_UPLOAD_FILE_MODAL';
-export const CLOSE_UPLOAD_FILE_MODAL = 'CLOSE_UPLOAD_FILE_MODAL';
-
-export const SHOW_SHARE_MODAL = 'SHOW_SHARE_MODAL';
-export const CLOSE_SHARE_MODAL = 'CLOSE_SHARE_MODAL';
-export const SHOW_EDITOR_OPTIONS = 'SHOW_EDITOR_OPTIONS';
-export const CLOSE_EDITOR_OPTIONS = 'CLOSE_EDITOR_OPTIONS';
-export const SHOW_KEYBOARD_SHORTCUT_MODAL = 'SHOW_KEYBOARD_SHORTCUT_MODAL';
-export const CLOSE_KEYBOARD_SHORTCUT_MODAL = 'CLOSE_KEYBOARD_SHORTCUT_MODAL';
+
 export const SHOW_TOAST = 'SHOW_TOAST';
 export const HIDE_TOAST = 'HIDE_TOAST';
 export const SET_TOAST_TEXT = 'SET_TOAST_TEXT';
 export const SET_THEME = 'SET_THEME';
 export const SET_LANGUAGE = 'SET_LANGUAGE';
 
-export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
 export const SET_AUTOREFRESH = 'SET_AUTOREFRESH';
-export const START_SKETCH_REFRESH = 'START_SKETCH_REFRESH';
-export const END_SKETCH_REFRESH = 'END_SKETCH_REFRESH';
-
-export const DETECT_INFINITE_LOOPS = 'DETECT_INFINITE_LOOPS';
-export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS';
 
 export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE';
 export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET';
@@ -117,20 +83,11 @@ export const EMAIL_VERIFICATION_INVALID = 'EMAIL_VERIFICATION_INVALID';
 // eventually, handle errors more specifically and better
 export const ERROR = 'ERROR';
 
-export const JUST_OPENED_PROJECT = 'JUST_OPENED_PROJECT';
-export const RESET_JUST_OPENED_PROJECT = 'RESET_JUST_OPENED_PROJECT';
-
 export const SET_PROJECT_SAVED_TIME = 'SET_PROJECT_SAVED_TIME';
-export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';
-export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
-export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
-export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';
 
 export const PERSIST_STATE = 'PERSIST_STATE';
 export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';
 
-export const HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
-export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
 export const SET_ASSETS = 'SET_ASSETS';
 export const DELETE_ASSET = 'DELETE_ASSET';
 
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 80a43443bc..8dc6ef6685 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -2,49 +2,48 @@ import * as ActionTypes from '../../../constants';
 import { clearConsole } from './console';
 import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher';
 
-export function startVisualSketch() {
-  return {
-    type: ActionTypes.START_SKETCH
-  };
-}
-
-export function stopVisualSketch() {
-  return {
-    type: ActionTypes.STOP_SKETCH
-  };
-}
-
-export function startRefreshSketch() {
-  return {
-    type: ActionTypes.START_SKETCH_REFRESH
-  };
-}
-
-export function startSketchAndRefresh() {
-  return (dispatch) => {
-    dispatch(startVisualSketch());
-    dispatch(startRefreshSketch());
-  };
-}
-
-export function endSketchRefresh() {
-  return {
-    type: ActionTypes.END_SKETCH_REFRESH
-  };
-}
-
-export function startAccessibleOutput() {
-  return {
-    type: ActionTypes.START_ACCESSIBLE_OUTPUT
-  };
-}
-
-export function stopAccessibleOutput() {
-  return {
-    type: ActionTypes.STOP_ACCESSIBLE_OUTPUT
-  };
-}
-
+import { ideActions } from '../reducers/ide';
+
+// TODO: refactor actions which are only used internally by other actions.
+const {
+  startVisualSketch,
+  stopVisualSketch,
+  startRefreshSketch,
+  startAccessibleOutput,
+  stopAccessibleOutput,
+  showShareModal: showShareModalInternal
+} = ideActions;
+
+export const {
+  openUploadFileModal,
+  closeUploadFileModal,
+  hideRuntimeErrorWarning,
+  showRuntimeErrorWarning,
+  setUnsavedChanges,
+  endSketchRefresh, // TODO: export is not actually needed
+  newFile,
+  closeNewFileModal,
+  newFolder,
+  closeNewFolderModal,
+  expandSidebar,
+  collapseSidebar,
+  expandConsole,
+  collapseConsole,
+  openPreferences,
+  closePreferences,
+  openProjectOptions,
+  closeProjectOptions,
+  closeShareModal,
+  showKeyboardShortcutModal,
+  closeKeyboardShortcutModal,
+  showErrorModal,
+  hideErrorModal,
+  setPreviousPath,
+  justOpenedProject,
+  resetJustOpenedProject
+} = ideActions;
+
+// TODO: move to /files
 export function setSelectedFile(fileId) {
   return {
     type: ActionTypes.SET_SELECTED_FILE,
@@ -52,199 +51,27 @@ export function setSelectedFile(fileId) {
   };
 }
 
+// TODO: move to /files
 export function resetSelectedFile(previousId) {
   return (dispatch, getState) => {
     const state = getState();
     const newId = state.files.find(
       (file) => file.name !== 'root' && file.id !== previousId
     ).id;
-    dispatch({
-      type: ActionTypes.SET_SELECTED_FILE,
-      selectedFile: newId
-    });
-  };
-}
-
-export function newFile(parentId) {
-  return {
-    type: ActionTypes.SHOW_MODAL,
-    parentId
-  };
-}
-
-export function closeNewFileModal() {
-  return {
-    type: ActionTypes.HIDE_MODAL
-  };
-}
-
-export function openUploadFileModal(parentId) {
-  return {
-    type: ActionTypes.OPEN_UPLOAD_FILE_MODAL,
-    parentId
-  };
-}
-
-export function closeUploadFileModal() {
-  return {
-    type: ActionTypes.CLOSE_UPLOAD_FILE_MODAL
-  };
-}
-
-export function expandSidebar() {
-  return {
-    type: ActionTypes.EXPAND_SIDEBAR
-  };
-}
-
-export function collapseSidebar() {
-  return {
-    type: ActionTypes.COLLAPSE_SIDEBAR
-  };
-}
-
-export function expandConsole() {
-  return {
-    type: ActionTypes.EXPAND_CONSOLE
-  };
-}
-
-export function collapseConsole() {
-  return {
-    type: ActionTypes.COLLAPSE_CONSOLE
-  };
-}
-
-export function openPreferences() {
-  return {
-    type: ActionTypes.OPEN_PREFERENCES
-  };
-}
-
-export function closePreferences() {
-  return {
-    type: ActionTypes.CLOSE_PREFERENCES
-  };
-}
-
-export function openProjectOptions() {
-  return {
-    type: ActionTypes.OPEN_PROJECT_OPTIONS
-  };
-}
-
-export function closeProjectOptions() {
-  return {
-    type: ActionTypes.CLOSE_PROJECT_OPTIONS
-  };
-}
-
-export function newFolder(parentId) {
-  return {
-    type: ActionTypes.SHOW_NEW_FOLDER_MODAL,
-    parentId
-  };
-}
-
-export function closeNewFolderModal() {
-  return {
-    type: ActionTypes.CLOSE_NEW_FOLDER_MODAL
+    dispatch(setSelectedFile(newId));
   };
 }
 
 export function showShareModal(projectId, projectName, ownerUsername) {
   return (dispatch, getState) => {
     const { project, user } = getState();
-    dispatch({
-      type: ActionTypes.SHOW_SHARE_MODAL,
-      payload: {
+    dispatch(
+      showShareModalInternal({
         shareModalProjectId: projectId || project.id,
         shareModalProjectName: projectName || project.name,
         shareModalProjectUsername: ownerUsername || user.username
-      }
-    });
-  };
-}
-
-export function closeShareModal() {
-  return {
-    type: ActionTypes.CLOSE_SHARE_MODAL
-  };
-}
-
-export function showKeyboardShortcutModal() {
-  return {
-    type: ActionTypes.SHOW_KEYBOARD_SHORTCUT_MODAL
-  };
-}
-
-export function closeKeyboardShortcutModal() {
-  return {
-    type: ActionTypes.CLOSE_KEYBOARD_SHORTCUT_MODAL
-  };
-}
-
-export function setUnsavedChanges(value) {
-  return {
-    type: ActionTypes.SET_UNSAVED_CHANGES,
-    value
-  };
-}
-
-export function detectInfiniteLoops(message) {
-  return {
-    type: ActionTypes.DETECT_INFINITE_LOOPS,
-    message
-  };
-}
-
-export function resetInfiniteLoops() {
-  return {
-    type: ActionTypes.RESET_INFINITE_LOOPS
-  };
-}
-
-export function justOpenedProject() {
-  return {
-    type: ActionTypes.JUST_OPENED_PROJECT
-  };
-}
-
-export function resetJustOpenedProject() {
-  return {
-    type: ActionTypes.RESET_JUST_OPENED_PROJECT
-  };
-}
-
-export function setPreviousPath(path) {
-  return {
-    type: ActionTypes.SET_PREVIOUS_PATH,
-    path
-  };
-}
-
-export function showErrorModal(modalType) {
-  return {
-    type: ActionTypes.SHOW_ERROR_MODAL,
-    modalType
-  };
-}
-
-export function hideErrorModal() {
-  return {
-    type: ActionTypes.HIDE_ERROR_MODAL
-  };
-}
-
-export function hideRuntimeErrorWarning() {
-  return {
-    type: ActionTypes.HIDE_RUNTIME_ERROR_WARNING
-  };
-}
-
-export function showRuntimeErrorWarning() {
-  return {
-    type: ActionTypes.SHOW_RUNTIME_ERROR_WARNING
+      })
+    );
   };
 }
 
@@ -269,11 +96,13 @@ export function startSketch() {
   };
 }
 
+// TODO: does this need to call dispatchMessage like in startSketch? Should it call startSketch internally?
 export function startAccessibleSketch() {
   return (dispatch) => {
     dispatch(clearConsole());
     dispatch(startAccessibleOutput());
-    dispatch(startSketchAndRefresh());
+    dispatch(startVisualSketch());
+    dispatch(startRefreshSketch());
   };
 }
 
@@ -287,6 +116,7 @@ export function stopSketch() {
   };
 }
 
+// TODO: move to /files
 export function createError(error) {
   return {
     type: ActionTypes.ERROR,
diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index 8d45b8b50a..53bdbdec03 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -1,8 +1,10 @@
-import * as ActionTypes from '../../../constants';
+import { createSlice } from '@reduxjs/toolkit';
 
 const initialState = {
   isPlaying: false,
+  // TODO: this doesn't do anything.
   isAccessibleOutputPlaying: false,
+  // TODO: rename ambiguous property
   modalIsVisible: false,
   sidebarIsExpanded: false,
   consoleIsExpanded: true,
@@ -10,12 +12,14 @@ const initialState = {
   projectOptionsVisible: false,
   newFolderModalVisible: false,
   uploadFileModalVisible: false,
+  // TODO: nested properties instead of all at top-level
   shareModalVisible: false,
   shareModalProjectId: 'abcd',
   shareModalProjectName: 'My Cute Sketch',
   shareModalProjectUsername: 'p5_user',
   keyboardShortcutVisible: false,
   unsavedChanges: false,
+  // TODO: remove dead code, see: PR #849 and issue #698
   infiniteLoop: false,
   previewIsRefreshing: false,
   infiniteLoopMessage: '',
@@ -26,105 +30,133 @@ const initialState = {
   parentId: undefined
 };
 
-const ide = (state = initialState, action) => {
-  switch (action.type) {
-    case ActionTypes.START_SKETCH:
-      return Object.assign({}, state, { isPlaying: true });
-    case ActionTypes.STOP_SKETCH:
-      return Object.assign({}, state, { isPlaying: false });
-    case ActionTypes.START_ACCESSIBLE_OUTPUT:
-      return Object.assign({}, state, { isAccessibleOutputPlaying: true });
-    case ActionTypes.STOP_ACCESSIBLE_OUTPUT:
-      return Object.assign({}, state, { isAccessibleOutputPlaying: false });
-    case ActionTypes.CONSOLE_EVENT:
-      return Object.assign({}, state, { consoleEvent: action.event });
-    case ActionTypes.SHOW_MODAL:
-      return Object.assign({}, state, {
-        modalIsVisible: true,
-        parentId: action.parentId,
-        newFolderModalVisible: false
-      });
-    case ActionTypes.HIDE_MODAL:
-      return Object.assign({}, state, { modalIsVisible: false });
-    case ActionTypes.COLLAPSE_SIDEBAR:
-      return Object.assign({}, state, { sidebarIsExpanded: false });
-    case ActionTypes.EXPAND_SIDEBAR:
-      return Object.assign({}, state, { sidebarIsExpanded: true });
-    case ActionTypes.COLLAPSE_CONSOLE:
-      return Object.assign({}, state, { consoleIsExpanded: false });
-    case ActionTypes.EXPAND_CONSOLE:
-      return Object.assign({}, state, { consoleIsExpanded: true });
-    case ActionTypes.OPEN_PREFERENCES:
-      return Object.assign({}, state, { preferencesIsVisible: true });
-    case ActionTypes.CLOSE_PREFERENCES:
-      return Object.assign({}, state, { preferencesIsVisible: false });
-    case ActionTypes.RESET_PROJECT:
-      return initialState;
-    case ActionTypes.OPEN_PROJECT_OPTIONS:
-      return Object.assign({}, state, { projectOptionsVisible: true });
-    case ActionTypes.CLOSE_PROJECT_OPTIONS:
-      return Object.assign({}, state, { projectOptionsVisible: false });
-    case ActionTypes.SHOW_NEW_FOLDER_MODAL:
-      return Object.assign({}, state, {
-        newFolderModalVisible: true,
-        parentId: action.parentId,
-        modalIsVisible: false
-      });
-    case ActionTypes.CLOSE_NEW_FOLDER_MODAL:
-      return Object.assign({}, state, { newFolderModalVisible: false });
-    case ActionTypes.SHOW_SHARE_MODAL:
-      return Object.assign({}, state, {
-        shareModalVisible: true,
-        shareModalProjectId: action.payload.shareModalProjectId,
-        shareModalProjectName: action.payload.shareModalProjectName,
-        shareModalProjectUsername: action.payload.shareModalProjectUsername
-      });
-    case ActionTypes.CLOSE_SHARE_MODAL:
-      return Object.assign({}, state, { shareModalVisible: false });
-    case ActionTypes.SHOW_KEYBOARD_SHORTCUT_MODAL:
-      return Object.assign({}, state, { keyboardShortcutVisible: true });
-    case ActionTypes.CLOSE_KEYBOARD_SHORTCUT_MODAL:
-      return Object.assign({}, state, { keyboardShortcutVisible: false });
-    case ActionTypes.SET_UNSAVED_CHANGES:
-      return Object.assign({}, state, { unsavedChanges: action.value });
-    case ActionTypes.DETECT_INFINITE_LOOPS:
-      return Object.assign({}, state, {
-        infiniteLoop: true,
-        infiniteLoopMessage: action.message
-      });
-    case ActionTypes.RESET_INFINITE_LOOPS:
-      return Object.assign({}, state, {
-        infiniteLoop: false,
-        infiniteLoopMessage: ''
-      });
-    case ActionTypes.START_SKETCH_REFRESH:
-      return Object.assign({}, state, { previewIsRefreshing: true });
-    case ActionTypes.END_SKETCH_REFRESH:
-      return Object.assign({}, state, { previewIsRefreshing: false });
-    case ActionTypes.JUST_OPENED_PROJECT:
-      return Object.assign({}, state, { justOpenedProject: true });
-    case ActionTypes.RESET_JUST_OPENED_PROJECT:
-      return Object.assign({}, state, { justOpenedProject: false });
-    case ActionTypes.SET_PREVIOUS_PATH:
-      return Object.assign({}, state, { previousPath: action.path });
-    case ActionTypes.SHOW_ERROR_MODAL:
-      return Object.assign({}, state, { errorType: action.modalType });
-    case ActionTypes.HIDE_ERROR_MODAL:
-      return Object.assign({}, state, { errorType: undefined });
-    case ActionTypes.HIDE_RUNTIME_ERROR_WARNING:
-      return Object.assign({}, state, { runtimeErrorWarningVisible: false });
-    case ActionTypes.SHOW_RUNTIME_ERROR_WARNING:
-      return Object.assign({}, state, { runtimeErrorWarningVisible: true });
-    case ActionTypes.OPEN_UPLOAD_FILE_MODAL:
-      return Object.assign({}, state, {
-        uploadFileModalVisible: true,
-        parentId: action.parentId
-      });
-    case ActionTypes.CLOSE_UPLOAD_FILE_MODAL:
-      return Object.assign({}, state, { uploadFileModalVisible: false });
-    default:
-      return state;
+const ideSlice = createSlice({
+  name: 'ide',
+  initialState,
+  reducers: {
+    startVisualSketch: (state) => {
+      state.isPlaying = true;
+    },
+    stopVisualSketch: (state) => {
+      state.isPlaying = false;
+    },
+    startAccessibleOutput: (state) => {
+      state.isAccessibleOutputPlaying = true;
+    },
+    stopAccessibleOutput: (state) => {
+      state.isAccessibleOutputPlaying = false;
+    },
+    consoleEvent: (state, action) => {
+      state.consoleEvent = action.payload;
+    },
+    collapseSidebar: (state) => {
+      state.sidebarIsExpanded = false;
+    },
+    expandSidebar: (state) => {
+      state.sidebarIsExpanded = true;
+    },
+    collapseConsole: (state) => {
+      state.consoleIsExpanded = false;
+    },
+    expandConsole: (state) => {
+      state.consoleIsExpanded = true;
+    },
+    openPreferences: (state) => {
+      state.preferencesIsVisible = true;
+    },
+    closePreferences: (state) => {
+      state.preferencesIsVisible = false;
+    },
+    openProjectOptions: (state) => {
+      state.projectOptionsVisible = true;
+    },
+    closeProjectOptions: (state) => {
+      state.projectOptionsVisible = false;
+    },
+    resetProject: () => initialState,
+    // TODO: rename to openNewFileModal or showNewFileModal
+    newFile: (state, action) => {
+      state.modalIsVisible = true;
+      // TODO: nested properties
+      state.parentId = action.payload;
+      state.newFolderModalVisible = false;
+    },
+    closeNewFileModal: (state) => {
+      state.modalIsVisible = false;
+    },
+    // TODO: rename to openNewFolderModal or showNewFolderModal
+    newFolder: (state, action) => {
+      state.newFolderModalVisible = true;
+      state.parentId = action.payload;
+      state.modalIsVisible = false;
+    },
+    closeNewFolderModal: (state) => {
+      state.newFolderModalVisible = false;
+    },
+    openUploadFileModal: (state, action) => {
+      state.uploadFileModalVisible = true;
+      state.parentId = action.payload;
+    },
+    closeUploadFileModal: (state) => {
+      state.uploadFileModalVisible = false;
+    },
+    showShareModal: (state, action) => {
+      state.shareModalVisible = true;
+      state.shareModalProjectId = action.payload.shareModalProjectId;
+      state.shareModalProjectName = action.payload.shareModalProjectName;
+      state.shareModalProjectUsername =
+        action.payload.shareModalProjectUsername;
+    },
+    closeShareModal: (state) => {
+      state.shareModalVisible = false;
+    },
+    showKeyboardShortcutModal: (state) => {
+      state.keyboardShortcutVisible = true;
+    },
+    closeKeyboardShortcutModal: (state) => {
+      state.keyboardShortcutVisible = false;
+    },
+    showErrorModal: (state, action) => {
+      state.errorType = action.payload;
+    },
+    hideErrorModal: (state) => {
+      state.errorType = undefined;
+    },
+    setUnsavedChanges: (state, action) => {
+      state.unsavedChanges = action.payload;
+    },
+    detectInfiniteLoops: (state, action) => {
+      state.infiniteLoop = true;
+      state.infiniteLoopMessage = action.payload;
+    },
+    resetInfiniteLoops: (state) => {
+      state.infiniteLoop = false;
+      state.infiniteLoopMessage = '';
+    },
+    startRefreshSketch: (state) => {
+      state.previewIsRefreshing = true;
+    },
+    endSketchRefresh: (state) => {
+      state.previewIsRefreshing = false;
+    },
+    justOpenedProject: (state) => {
+      state.justOpenedProject = true;
+    },
+    resetJustOpenedProject: (state) => {
+      state.justOpenedProject = false;
+    },
+    setPreviousPath: (state, action) => {
+      state.previousPath = action.payload;
+    },
+    showRuntimeErrorWarning: (state) => {
+      state.runtimeErrorWarningVisible = true;
+    },
+    hideRuntimeErrorWarning: (state) => {
+      state.runtimeErrorWarningVisible = false;
+    }
   }
-};
+});
+
+export const ideActions = ideSlice.actions;
 
-export default ide;
+export default ideSlice.reducer;

From ca09600c8cd786a13a8ae61d5479233a7b6667b1 Mon Sep 17 00:00:00 2001
From: Linda Paiste <lindapaiste@gmail.com>
Date: Sat, 6 May 2023 21:17:30 -0500
Subject: [PATCH 2/3] Update `createSelector` import.

---
 client/modules/IDE/selectors/project.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/modules/IDE/selectors/project.js b/client/modules/IDE/selectors/project.js
index 26197f3b00..cdf7d61826 100644
--- a/client/modules/IDE/selectors/project.js
+++ b/client/modules/IDE/selectors/project.js
@@ -1,4 +1,4 @@
-import { createSelector } from 'reselect';
+import { createSelector } from '@reduxjs/toolkit';
 
 export const selectProjectOwner = (state) => state.project.owner;
 export const selectProjectId = (state) => state.project.id;

From 00017453752c2854b451accada7520cad5414d59 Mon Sep 17 00:00:00 2001
From: Linda Paiste <lindapaiste@gmail.com>
Date: Sat, 23 Sep 2023 14:07:22 -0500
Subject: [PATCH 3/3] Fix handling of actions which are used in multiple
 reducers.

---
 client/modules/IDE/reducers/ide.js | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index 53bdbdec03..7ef97741b8 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -1,4 +1,5 @@
 import { createSlice } from '@reduxjs/toolkit';
+import * as ActionTypes from '../../../constants';
 
 const initialState = {
   isPlaying: false,
@@ -21,6 +22,7 @@ const initialState = {
   unsavedChanges: false,
   // TODO: remove dead code, see: PR #849 and issue #698
   infiniteLoop: false,
+  // TODO: this doesn't do anything.
   previewIsRefreshing: false,
   infiniteLoopMessage: '',
   justOpenedProject: false,
@@ -46,9 +48,6 @@ const ideSlice = createSlice({
     stopAccessibleOutput: (state) => {
       state.isAccessibleOutputPlaying = false;
     },
-    consoleEvent: (state, action) => {
-      state.consoleEvent = action.payload;
-    },
     collapseSidebar: (state) => {
       state.sidebarIsExpanded = false;
     },
@@ -73,7 +72,6 @@ const ideSlice = createSlice({
     closeProjectOptions: (state) => {
       state.projectOptionsVisible = false;
     },
-    resetProject: () => initialState,
     // TODO: rename to openNewFileModal or showNewFileModal
     newFile: (state, action) => {
       state.modalIsVisible = true;
@@ -154,7 +152,20 @@ const ideSlice = createSlice({
     hideRuntimeErrorWarning: (state) => {
       state.runtimeErrorWarningVisible = false;
     }
-  }
+  },
+  // Respond to actions which are primarily "owned" by another reducer
+  extraReducers: (builder) =>
+    builder
+      .addMatcher(
+        (action) => action.type === ActionTypes.CONSOLE_EVENT,
+        (state, action) => {
+          state.consoleEvent = action.event;
+        }
+      )
+      .addMatcher(
+        (action) => action.type === ActionTypes.RESET_PROJECT,
+        () => initialState
+      )
 });
 
 export const ideActions = ideSlice.actions;