diff --git a/client/constants.js b/client/constants.js
index fa6010aed1..a50478804b 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -1,6 +1,5 @@
// 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';
@@ -27,10 +26,7 @@ export const RENAME_PROJECT = 'RENAME_PROJECT';
export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';
-export const NEW_PROJECT = 'NEW_PROJECT';
-export const RESET_PROJECT = 'RESET_PROJECT';
-export const SET_PROJECT = 'SET_PROJECT';
export const SET_PROJECTS = 'SET_PROJECTS';
export const SET_COLLECTIONS = 'SET_COLLECTIONS';
@@ -43,11 +39,8 @@ 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';
@@ -57,9 +50,6 @@ export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
export const TOGGLE_FORCE_DESKTOP = 'TOGGLE_FORCE_DESKTOP';
-export const UPDATE_FILE_NAME = 'UPDATE_FILE_NAME';
-export const DELETE_FILE = 'DELETE_FILE';
-
export const SET_AUTOSAVE = 'SET_AUTOSAVE';
export const SET_LINEWRAP = 'SET_LINEWRAP';
export const SET_LINT_WARNING = 'SET_LINT_WARNING';
@@ -74,8 +64,6 @@ 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';
diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js
index 84b76b6f41..321d125e70 100644
--- a/client/modules/IDE/actions/files.js
+++ b/client/modules/IDE/actions/files.js
@@ -10,6 +10,28 @@ import {
} from './ide';
import { setProjectSavedTime } from './project';
import { createError } from './ide';
+import {
+ updateFileContent,
+ setBlobURL,
+ newProject,
+ setProject,
+ resetProject,
+ createFile,
+ showFolderChildren,
+ hideFolderChildren,
+ DeleteFile
+} from '../reducers/files';
+
+export {
+ updateFileContent,
+ setBlobURL,
+ newProject,
+ setProject,
+ resetProject,
+ createFile,
+ showFolderChildren,
+ hideFolderChildren
+};
export function appendToFilename(filename, string) {
const dotIndex = filename.lastIndexOf('.');
@@ -37,22 +59,6 @@ export function createUniqueName(name, parentId, files) {
return testName;
}
-export function updateFileContent(id, content) {
- return {
- type: ActionTypes.UPDATE_FILE_CONTENT,
- id,
- content
- };
-}
-
-export function createFile(file, parentId) {
- return {
- type: ActionTypes.CREATE_FILE,
- ...file,
- parentId
- };
-}
-
export function submitFile(formProps, files, parentId, projectId) {
if (projectId) {
const postParams = {
@@ -83,6 +89,17 @@ export function submitFile(formProps, files, parentId, projectId) {
});
}
+export function updateFileName(id, name) {
+ return (dispatch) => {
+ dispatch(setUnsavedChanges(true));
+ dispatch({
+ type: ActionTypes.UPDATE_FILE_NAME,
+ id,
+ name
+ });
+ };
+}
+
export function handleCreateFile(formProps, setSelected = true) {
return (dispatch, getState) => {
const state = getState();
@@ -129,15 +146,17 @@ export function submitFolder(formProps, files, parentId, projectId) {
}
const id = objectID().toHexString();
const file = {
- type: ActionTypes.CREATE_FILE,
name: createUniqueName(formProps.name, parentId, files),
id,
_id: id,
content: '',
// TODO pass parent id from File Tree
fileType: 'folder',
+ parentId,
children: []
};
+
+ // Dispatch local folder creation
return Promise.resolve({
file
});
@@ -153,7 +172,7 @@ export function handleCreateFolder(formProps) {
submitFolder(formProps, files, parentId, projectId)
.then((response) => {
const { file, updatedAt } = response;
- dispatch(createFile(file, parentId));
+ dispatch(createFile({ ...file, parentId }));
if (updatedAt) dispatch(setProjectSavedTime(updatedAt));
dispatch(closeNewFolderModal());
dispatch(setUnsavedChanges(true));
@@ -168,17 +187,6 @@ export function handleCreateFolder(formProps) {
};
}
-export function updateFileName(id, name) {
- return (dispatch) => {
- dispatch(setUnsavedChanges(true));
- dispatch({
- type: ActionTypes.UPDATE_FILE_NAME,
- id,
- name
- });
- };
-}
-
export function deleteFile(id, parentId) {
return (dispatch, getState) => {
const state = getState();
@@ -192,11 +200,7 @@ export function deleteFile(id, parentId) {
.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig)
.then((response) => {
dispatch(setProjectSavedTime(response.data.project.updatedAt));
- dispatch({
- type: ActionTypes.DELETE_FILE,
- id,
- parentId
- });
+ dispatch(DeleteFile(id, parentId));
})
.catch((error) => {
const { response } = error;
@@ -206,37 +210,11 @@ export function deleteFile(id, parentId) {
});
});
} else {
- dispatch({
- type: ActionTypes.DELETE_FILE,
- id,
- parentId
- });
+ dispatch(DeleteFile(id, parentId));
}
};
}
-export function showFolderChildren(id) {
- return {
- type: ActionTypes.SHOW_FOLDER_CHILDREN,
- id
- };
-}
-
-export function hideFolderChildren(id) {
- return {
- type: ActionTypes.HIDE_FOLDER_CHILDREN,
- id
- };
-}
-
-export function setBlobUrl(file, blobURL) {
- return {
- type: ActionTypes.SET_BLOB_URL,
- id: file.id,
- blobURL
- };
-}
-
export function getBlobUrl(file) {
if (file.blobUrl) {
blobUtil.revokeObjectURL(file.blobUrl);
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 80a43443bc..9e55b37ca4 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -1,7 +1,9 @@
import * as ActionTypes from '../../../constants';
import { clearConsole } from './console';
import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher';
+import { setSelectedFile } from '../reducers/files';
+export { setSelectedFile };
export function startVisualSketch() {
return {
type: ActionTypes.START_SKETCH
@@ -45,13 +47,6 @@ export function stopAccessibleOutput() {
};
}
-export function setSelectedFile(fileId) {
- return {
- type: ActionTypes.SET_SELECTED_FILE,
- selectedFile: fileId
- };
-}
-
export function resetSelectedFile(previousId) {
return (dispatch, getState) => {
const state = getState();
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 1d8943336b..cdc5b977b7 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -14,20 +14,14 @@ import {
setPreviousPath
} from './ide';
import { clearState, saveState } from '../../../persistState';
+import { setProject } from './files';
+
+export { setProject };
const ROOT_URL = getConfig('API_URL');
const S3_BUCKET_URL_BASE = getConfig('S3_BUCKET_URL_BASE');
const S3_BUCKET = getConfig('S3_BUCKET');
-export function setProject(project) {
- return {
- type: ActionTypes.SET_PROJECT,
- project,
- files: project.files,
- owner: project.user
- };
-}
-
export function setProjectName(name) {
return {
type: ActionTypes.SET_PROJECT_NAME,
diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx
index 3c84d31196..264793b9ca 100644
--- a/client/modules/IDE/components/FileNode.jsx
+++ b/client/modules/IDE/components/FileNode.jsx
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import classNames from 'classnames';
import React, { useState, useRef } from 'react';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import * as IDEActions from '../actions/ide';
@@ -62,40 +62,35 @@ FileName.propTypes = {
name: PropTypes.string.isRequired
};
-const FileNode = ({
- id,
- parentId,
- children,
- name,
- fileType,
- isSelectedFile,
- isFolderClosed,
- setSelectedFile,
- deleteFile,
- updateFileName,
- resetSelectedFile,
- newFile,
- newFolder,
- showFolderChildren,
- hideFolderChildren,
- canEdit,
- openUploadFileModal,
- authenticated,
- onClickFile
-}) => {
+const FileNode = ({ id, canEdit, onClickFile }) => {
+ const dispatch = useDispatch();
+ const { t } = useTranslation();
+
+ const fileNode =
+ useSelector((state) => state.files.find((file) => file.id === id)) || {};
+ const authenticated = useSelector((state) => state.user.authenticated);
+
+ const {
+ name = '',
+ parentId = null,
+ children = [],
+ fileType = 'file',
+ isSelectedFile = false,
+ isFolderClosed = false
+ } = fileNode;
+
const [isOptionsOpen, setIsOptionsOpen] = useState(false);
const [isEditingName, setIsEditingName] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [updatedName, setUpdatedName] = useState(name);
- const { t } = useTranslation();
const fileNameInput = useRef(null);
const fileOptionsRef = useRef(null);
const handleFileClick = (event) => {
event.stopPropagation();
if (name !== 'root' && !isDeleting) {
- setSelectedFile(id);
+ dispatch(IDEActions.setSelectedFile(id));
}
if (onClickFile) {
onClickFile();
@@ -122,17 +117,17 @@ const FileNode = ({
};
const handleClickAddFile = () => {
- newFile(id);
+ dispatch(IDEActions.newFile(id));
setTimeout(() => hideFileOptions(), 0);
};
const handleClickAddFolder = () => {
- newFolder(id);
+ dispatch(IDEActions.newFolder(id));
setTimeout(() => hideFileOptions(), 0);
};
const handleClickUploadFile = () => {
- openUploadFileModal(id);
+ dispatch(IDEActions.openUploadFileModal(id));
setTimeout(hideFileOptions, 0);
};
@@ -141,8 +136,8 @@ const FileNode = ({
if (window.confirm(prompt)) {
setIsDeleting(true);
- resetSelectedFile(id);
- setTimeout(() => deleteFile(id, parentId), 100);
+ dispatch(IDEActions.resetSelectedFile(id));
+ setTimeout(() => dispatch(FileActions.deleteFile(id, parentId), 100));
}
};
@@ -158,7 +153,7 @@ const FileNode = ({
const saveUpdatedFileName = () => {
if (updatedName !== name) {
- updateFileName(id, updatedName);
+ dispatch(FileActions.updateFileName(id, updatedName));
}
};
@@ -243,7 +238,7 @@ const FileNode = ({
@@ -354,7 +348,7 @@ const FileNode = ({
{children.map((childId) => (
-
- file.id === ownProps.id) || {
- name: 'test',
- fileType: 'file'
- };
- return Object.assign({}, fileNode, {
- authenticated: state.user.authenticated
- });
-}
-
-const mapDispatchToProps = { ...FileActions, ...IDEActions };
-
-const ConnectedFileNode = connect(
- mapStateToProps,
- mapDispatchToProps
-)(FileNode);
-
-export { FileNode };
-export default ConnectedFileNode;
+export default FileNode;
diff --git a/client/modules/IDE/components/FileNode.unit.test.jsx b/client/modules/IDE/components/FileNode.unit.test.jsx
index 8676a817d8..4f75d9a272 100644
--- a/client/modules/IDE/components/FileNode.unit.test.jsx
+++ b/client/modules/IDE/components/FileNode.unit.test.jsx
@@ -1,4 +1,8 @@
import React from 'react';
+import { Provider } from 'react-redux';
+import configureStore from 'redux-mock-store';
+import { useDispatch } from 'react-redux';
+import * as FileActions from '../actions/files';
import {
fireEvent,
@@ -7,9 +11,27 @@ import {
waitFor,
within
} from '../../../test-utils';
-import { FileNode } from './FileNode';
+import FileNode from './FileNode';
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: jest.fn()
+}));
+
+jest.mock('../actions/files', () => ({
+ updateFileName: jest.fn()
+}));
+
+const mockStore = configureStore([]);
describe('', () => {
+ const mockDispatch = jest.fn();
+
+ beforeEach(() => {
+ useDispatch.mockReturnValue(mockDispatch);
+ jest.clearAllMocks();
+ });
+
const changeName = (newFileName) => {
const renameButton = screen.getByText(/Rename/i);
fireEvent.click(renameButton);
@@ -24,62 +46,64 @@ describe('', () => {
await waitFor(() => within(name).queryByText(expectedName));
};
- const renderFileNode = (fileType, extraProps = {}) => {
- const props = {
- ...extraProps,
- id: '0',
- name: fileType === 'folder' ? 'afolder' : 'test.jsx',
- fileType,
- canEdit: true,
- children: [],
- authenticated: false,
- setSelectedFile: jest.fn(),
- deleteFile: jest.fn(),
- updateFileName: jest.fn(),
- resetSelectedFile: jest.fn(),
- newFile: jest.fn(),
- newFolder: jest.fn(),
- showFolderChildren: jest.fn(),
- hideFolderChildren: jest.fn(),
- openUploadFileModal: jest.fn(),
- setProjectName: jest.fn()
+ const renderFileNode = (fileType, extraState = {}) => {
+ const initialState = {
+ files: [
+ {
+ id: '0',
+ name: fileType === 'folder' ? 'afolder' : 'test.jsx',
+ fileType,
+ parentId: 'root',
+ children: [],
+ isSelectedFile: false,
+ isFolderClosed: false
+ }
+ ],
+ user: { authenticated: false },
+ ...extraState
};
- render();
+ const store = mockStore(initialState);
- return props;
+ render(
+
+
+
+ );
+
+ return { store };
};
describe('fileType: file', () => {
it('cannot change to an empty name', async () => {
- const props = renderFileNode('file');
+ renderFileNode('file');
changeName('');
- await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
- await expectFileNameToBe(props.name);
+ await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled());
+ await expectFileNameToBe('test.jsx');
});
it('can change to a valid filename', async () => {
const newName = 'newname.jsx';
- const props = renderFileNode('file');
+ renderFileNode('file');
changeName(newName);
await waitFor(() =>
- expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName)
+ expect(FileActions.updateFileName).toHaveBeenCalledWith('0', newName)
);
await expectFileNameToBe(newName);
});
it('must have an extension', async () => {
const newName = 'newname';
- const props = renderFileNode('file');
+ renderFileNode('file');
changeName(newName);
- await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
- await expectFileNameToBe(props.name);
+ await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled());
+ await expectFileNameToBe('test.jsx');
});
it('can change to a different extension', async () => {
@@ -87,58 +111,58 @@ describe('', () => {
window.confirm = mockConfirm;
const newName = 'newname.gif';
- const props = renderFileNode('file');
+ renderFileNode('file');
changeName(newName);
expect(mockConfirm).toHaveBeenCalled();
await waitFor(() =>
- expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName)
+ expect(FileActions.updateFileName).toHaveBeenCalledWith('0', newName)
);
- await expectFileNameToBe(props.name);
+ await expectFileNameToBe(newName);
});
it('cannot be just an extension', async () => {
const newName = '.jsx';
- const props = renderFileNode('file');
+ renderFileNode('file');
changeName(newName);
- await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
- await expectFileNameToBe(props.name);
+ await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled());
+ await expectFileNameToBe('test.jsx');
});
});
describe('fileType: folder', () => {
it('cannot change to an empty name', async () => {
- const props = renderFileNode('folder');
+ renderFileNode('folder');
changeName('');
- await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
- await expectFileNameToBe(props.name);
+ await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled());
+ await expectFileNameToBe('afolder');
});
it('can change to another name', async () => {
const newName = 'foldername';
- const props = renderFileNode('folder');
+ renderFileNode('folder');
changeName(newName);
await waitFor(() =>
- expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName)
+ expect(FileActions.updateFileName).toHaveBeenCalledWith('0', newName)
);
await expectFileNameToBe(newName);
});
it('cannot have a file extension', async () => {
const newName = 'foldername.jsx';
- const props = renderFileNode('folder');
+ renderFileNode('folder');
changeName(newName);
- await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
- await expectFileNameToBe(props.name);
+ await waitFor(() => expect(mockDispatch).not.toHaveBeenCalled());
+ await expectFileNameToBe('afolder');
});
});
});
diff --git a/client/modules/IDE/reducers/files.js b/client/modules/IDE/reducers/files.js
index de1b9b1aa7..a464f165c0 100644
--- a/client/modules/IDE/reducers/files.js
+++ b/client/modules/IDE/reducers/files.js
@@ -1,5 +1,5 @@
-import objectID from 'bson-objectid';
-import * as ActionTypes from '../../../constants';
+import { createSlice } from '@reduxjs/toolkit';
+import { nanoid } from '@reduxjs/toolkit';
import {
defaultSketch,
defaultCSS,
@@ -7,10 +7,11 @@ import {
} from '../../../../server/domain-objects/createDefaultFiles';
export const initialState = () => {
- const a = objectID().toHexString();
- const b = objectID().toHexString();
- const c = objectID().toHexString();
- const r = objectID().toHexString();
+ const r = nanoid();
+ const a = nanoid();
+ const b = nanoid();
+ const c = nanoid();
+
return [
{
name: 'root',
@@ -51,18 +52,14 @@ export const initialState = () => {
];
};
-function getAllDescendantIds(state, nodeId) {
- return state
- .find((file) => file.id === nodeId)
- .children.reduce(
- (acc, childId) => [
- ...acc,
- childId,
- ...getAllDescendantIds(state, childId)
- ],
- []
- );
-}
+export const getAllDescendantIds = (state, nodeId) => {
+ const node = state.find((file) => file.id === nodeId);
+ if (!node || !node.children.length) return [];
+ return node.children.flatMap((childId) => [
+ childId,
+ ...getAllDescendantIds(state, childId)
+ ]);
+};
function deleteChild(state, parentId, id) {
const newState = state.map((file) => {
@@ -91,188 +88,181 @@ function deleteMany(state, ids) {
return newState;
}
-function sortedChildrenId(state, children) {
- const childrenArray = state.filter((file) => children.includes(file.id));
- childrenArray.sort((a, b) => (a.name > b.name ? 1 : -1));
- return childrenArray.map((child) => child.id);
+export function sortedChildrenId(state, children) {
+ return children
+ .map((id) => state.find((file) => file.id === id))
+ .filter(Boolean) // Remove undefined values
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map((file) => file.id);
}
-function updateParent(state, action) {
- return state.map((file) => {
- if (file.id === action.parentId) {
- const newFile = Object.assign({}, file);
- newFile.children = [...newFile.children, action.id];
- return newFile;
- }
- return file;
- });
+export function updateParent(state, action) {
+ return state.map((file) =>
+ file.id === action.parentId
+ ? { ...file, children: [...file.children, action.id] }
+ : file
+ );
}
-function renameFile(state, action) {
- return state.map((file) => {
- if (file.id !== action.id) {
- return file;
- }
- return Object.assign({}, file, { name: action.name });
- });
+export function renameFile(state, action) {
+ return state.map((file) =>
+ file.id === action.id ? { ...file, name: action.name } : file
+ );
}
-function setFilePath(files, fileId, path) {
+export function setFilePath(files, fileId, path) {
const file = files.find((f) => f.id === fileId);
+ if (!file) return;
+
file.filePath = path;
- // const newPath = `${path}${path.length > 0 ? '/' : ''}${file.name}`;
- const newPath = `${path}/${file.name}`;
- if (file.children.length === 0) return;
- file.children.forEach((childFileId) => {
- setFilePath(files, childFileId, newPath);
+ const newPath = path ? `${path}/${file.name}` : file.name;
+
+ file.children.forEach((childId) => {
+ setFilePath(files, childId, newPath);
});
}
-function setFilePaths(files) {
+export const setFilePaths = (files) => {
const updatedFiles = [...files];
- const rootPath = '';
- const rootFile = files.find((f) => f.name === 'root');
- rootFile.children.forEach((fileId) => {
- setFilePath(updatedFiles, fileId, rootPath);
- });
- return updatedFiles;
-}
+ const rootFile = updatedFiles.find((f) => f.name === 'root');
-const files = (state, action) => {
- if (state === undefined) {
- state = initialState(); // eslint-disable-line
+ if (rootFile) {
+ rootFile.children.forEach((fileId) =>
+ setFilePath(updatedFiles, fileId, '')
+ );
}
- switch (action.type) {
- case ActionTypes.UPDATE_FILE_CONTENT:
- return state.map((file) => {
- if (file.id !== action.id) {
- return file;
- }
- return Object.assign({}, file, { content: action.content });
- });
- case ActionTypes.SET_BLOB_URL:
- return state.map((file) => {
- if (file.id !== action.id) {
- return file;
- }
- return Object.assign({}, file, { blobURL: action.blobURL });
- });
- case ActionTypes.NEW_PROJECT: {
- const newFiles = action.files.map((file) => {
- const corrospondingObj = state.find((obj) => obj.id === file.id);
- if (corrospondingObj && corrospondingObj.fileType === 'folder') {
- const isFolderClosed = corrospondingObj.isFolderClosed || false;
- return { ...file, isFolderClosed };
- }
- return file;
- });
- return setFilePaths(newFiles);
- }
- case ActionTypes.SET_PROJECT: {
- const newFiles = action.files.map((file) => {
- const corrospondingObj = state.find((obj) => obj.id === file.id);
- if (corrospondingObj && corrospondingObj.fileType === 'folder') {
- const isFolderClosed = corrospondingObj.isFolderClosed || false;
- return { ...file, isFolderClosed };
+ return updatedFiles;
+};
+
+const filesSlice = createSlice({
+ name: 'files',
+ initialState: initialState(),
+ reducers: {
+ updateFileContent: (state, action) => {
+ const file = state.find((x) => x.id === action.payload.id);
+ if (file) file.content = action.payload.content;
+ },
+ setBlobURL: (state, action) => {
+ const file = state.find((x) => x.id === action.payload.id);
+ if (file) file.blobURL = action.payload.blobURL;
+ },
+ newProject(state, action) {
+ return setFilePaths(
+ action.payload.files.map((file) => {
+ const existingFile = state.find((obj) => obj.id === file.id);
+ return existingFile?.fileType === 'folder'
+ ? { ...file, isFolderClosed: existingFile.isFolderClosed || false }
+ : file;
+ })
+ );
+ },
+ setProject(state, action) {
+ if (!action.payload.files) return state;
+ action.payload.files.forEach((file) => {
+ const correspondingObj = state.find((obj) => obj.id === file.id);
+
+ if (correspondingObj && correspondingObj.fileType === 'folder') {
+ file.isFolderClosed = correspondingObj.isFolderClosed || false;
}
- return file;
});
- return setFilePaths(newFiles);
- }
- case ActionTypes.RESET_PROJECT:
- return initialState();
- case ActionTypes.CREATE_FILE: {
- const parentFile = state.find((file) => file.id === action.parentId);
- // const filePath =
- // parentFile.name === 'root'
- // ? ''
- // : `${parentFile.filePath}${parentFile.filePath.length > 0 ? '/' : ''}
- // ${parentFile.name}`;
+
+ return setFilePaths(action.payload.files);
+ },
+ resetProject: () => initialState,
+ createFile: (state, action) => {
+ const {
+ name,
+ id,
+ _id,
+ content,
+ url,
+ children,
+ fileType,
+ parentId
+ } = action.payload;
+
+ // Find parent file
+ const parentFile = state.files.find((file) => file.id === parentId);
+
+ // Construct file path
const filePath =
parentFile.name === 'root'
? ''
: `${parentFile.filePath}/${parentFile.name}`;
- const newState = [
- ...updateParent(state, action),
- {
- name: action.name,
- id: action.id,
- _id: action._id,
- content: action.content,
- url: action.url,
- children: action.children,
- fileType: action.fileType || 'file',
- filePath
- }
- ];
- return newState.map((file) => {
- if (file.id === action.parentId) {
- file.children = sortedChildrenId(newState, file.children);
- }
- return file;
- });
- }
- case ActionTypes.UPDATE_FILE_NAME: {
- const newState = renameFile(state, action);
- const updatedFile = newState.find((file) => file.id === action.id);
- // const childPath = `${updatedFile.filePath}
- // ${updatedFile.filePath.length > 0 ? '/' : ''}${updatedFile.name}`;
- const childPath = `${updatedFile.filePath}/${updatedFile.name}`;
- updatedFile.children.forEach((childId) => {
- setFilePath(newState, action.id, childPath);
- });
- return newState.map((file) => {
- if (file.children.includes(action.id)) {
- file.children = sortedChildrenId(newState, file.children);
- }
- return file;
- });
- }
- case ActionTypes.DELETE_FILE: {
+ // Create new file object
+ const newFile = {
+ name,
+ id,
+ _id,
+ content: content || '',
+ url: url || '',
+ children: children || [],
+ fileType: fileType || 'file',
+ filePath
+ };
+
+ // Add new file to state
+ state.files.push(newFile);
+
+ // Update parent's children list if applicable
+ if (parentFile) {
+ parentFile.children.push(newFile.id);
+ parentFile.children = sortedChildrenId(
+ state.files,
+ parentFile.children
+ );
+ }
+
+ return newFile; // Returning this does not affect Redux state but might be useful for debugging
+ },
+ updateFileName(state, action) {
+ const file = state.find((x) => x.id === action.payload.id);
+ if (file) {
+ const childPath = `${file.filePath}/${action.payload.name}`;
+ file.name = action.payload.name;
+ file.children.forEach((childId) => {
+ setFilePath(state, action.payload.id, childPath);
+ });
+ }
+ },
+ DeleteFile(state, action) {
const newState = deleteMany(state, [
- action.id,
- ...getAllDescendantIds(state, action.id)
+ action.payload.id,
+ ...getAllDescendantIds(state, action.payload.id)
]);
return deleteChild(newState, action.parentId, action.id);
- // const newState = state.map((file) => {
- // if (file.id === action.parentId) {
- // const newChildren = file.children.filter(child => child !== action.id);
- // return { ...file, children: newChildren };
- // }
- // return file;
- // });
- // return newState.filter(file => file.id !== action.id);
- }
- case ActionTypes.SET_SELECTED_FILE:
- return state.map((file) => {
- if (file.id === action.selectedFile) {
- return Object.assign({}, file, { isSelectedFile: true });
- }
- return Object.assign({}, file, { isSelectedFile: false });
- });
- case ActionTypes.SHOW_FOLDER_CHILDREN:
- return state.map((file) => {
- if (file.id === action.id) {
- return Object.assign({}, file, { isFolderClosed: false });
- }
- return file;
- });
- case ActionTypes.HIDE_FOLDER_CHILDREN:
- return state.map((file) => {
- if (file.id === action.id) {
- return Object.assign({}, file, { isFolderClosed: true });
- }
- return file;
- });
- default:
- return state.map((file) => {
- file.children = sortedChildrenId(state, file.children);
- return file;
+ },
+ setSelectedFile: (state, action) => {
+ state.forEach((file) => {
+ file.isSelectedFile = file.id === action.payload.selectedFile;
});
+ },
+ showFolderChildren: (state, action) => {
+ const file = state.find((x) => x.id === action.payload.id);
+ if (file) file.isFolderClosed = false;
+ },
+ hideFolderChildren(state, action) {
+ const file = state.find((x) => x.id === action.payload.id);
+ if (file) file.isFolderClosed = true;
+ }
}
-};
+});
+
+export const {
+ updateFileContent,
+ setBlobURL,
+ newProject,
+ setProject,
+ resetProject,
+ createFile,
+ updateFileName,
+ DeleteFile,
+ setSelectedFile,
+ showFolderChildren,
+ hideFolderChildren
+} = filesSlice.actions;
export const getHTMLFile = (state) =>
state.filter((file) => file.name.match(/.*\.html$/i))[0];
@@ -282,4 +272,4 @@ export const getCSSFiles = (state) =>
state.filter((file) => file.name.match(/.*\.css$/i));
export const getLinkedFiles = (state) => state.filter((file) => file.url);
-export default files;
+export default filesSlice.reducer;