diff --git a/frontend/src/packages/dashboard/api/index.ts b/frontend/src/packages/dashboard/api/index.ts index 34298ec1..a0ee72f8 100644 --- a/frontend/src/packages/dashboard/api/index.ts +++ b/frontend/src/packages/dashboard/api/index.ts @@ -6,124 +6,124 @@ const DEFAULT_OWNER_GROUP = '1'; // Given a file ID (if no ID is provided root is assumed), returns // a FileFormat of that file from the backend export async function getFolder(id?: string) { - const ending = id === undefined ? '' : `?EntityID=${id}`; - const folder_resp = await fetch(`/api/filesystem/info${ending}`); - - if (!folder_resp.ok) { - const message = `An error has occured: ${folder_resp.status}`; - throw new Error(message); - } - const folder_json = await folder_resp.json(); - return toFileOrFolder(folder_json.Response); + const ending = id === undefined ? '' : `?EntityID=${id}`; + const folder_resp = await fetch(`/api/filesystem/info${ending}`); + + if (!folder_resp.ok) { + const message = `An error has occured: ${folder_resp.status}`; + throw new Error(message); + } + const folder_json = await folder_resp.json(); + return toFileOrFolder(folder_json.Response); } // Given a file ID, sets the `contents` state variable to the children // of that file export async function updateContents(id: string) { - // const id = getCurrentID(); - const children_resp = await fetch(`/api/filesystem/info?EntityID=${id}`); - - if (!children_resp.ok) { - const message = `An error has occured: ${children_resp.status}`; - throw new Error(message); - } - - const children_json = await children_resp.json(); - const children = children_json.Response.Children.map( - (child: JSONFileFormat) => { - return toFileOrFolder(child); + // const id = getCurrentID(); + const children_resp = await fetch(`/api/filesystem/info?EntityID=${id}`); + + if (!children_resp.ok) { + const message = `An error has occured: ${children_resp.status}`; + throw new Error(message); } - ); - return children; + const children_json = await children_resp.json(); + const children = children_json.Response.Children.map( + (child: JSONFileFormat) => { + return toFileOrFolder(child); + } + ); + + return children; } export const newFile = async ( - name: string, - parentID: string + name: string, + parentID: string ): Promise => { - // This isn't attached to the parent folder yet, - // TODO: patch once auth is finished - const create_resp = await fetch('/api/filesystem/create', { - method: 'POST', - body: new URLSearchParams({ - LogicalName: name, - Parent: parentID.toString(), - OwnerGroup: DEFAULT_OWNER_GROUP, - IsDocument: 'true', - }), - }); - - if (!create_resp.ok) { - const message = `An error has occured: ${create_resp.status}`; - throw new Error(message); - } - const response = await create_resp.json(); - - console.log(response); - console.log(JSON.stringify(response)); - return response.Response.NewID; + // This isn't attached to the parent folder yet, + // TODO: patch once auth is finished + const create_resp = await fetch('/api/filesystem/create', { + method: 'POST', + body: new URLSearchParams({ + LogicalName: name, + Parent: parentID.toString(), + OwnerGroup: DEFAULT_OWNER_GROUP, + IsDocument: 'true', + }), + }); + + if (!create_resp.ok) { + const message = `An error has occured: ${create_resp.status}`; + throw new Error(message); + } + const response = await create_resp.json(); + + console.log(response); + console.log(JSON.stringify(response)); + return response.Response.NewID; }; export const newFolder = async ( - name: string, - parentID: string + name: string, + parentID: string ): Promise => { - // TODO: patch once auth is finished - const create_resp = await fetch('/api/filesystem/create', { - method: 'POST', - body: new URLSearchParams({ - LogicalName: name, - Parent: parentID.toString(), - OwnerGroup: DEFAULT_OWNER_GROUP, - IsDocument: 'false', - }), - }); - - if (!create_resp.ok) { - const message = `An error has occured: ${create_resp.status}`; - throw new Error(message); - } - const response = await create_resp.json(); - return response.Response.NewID; + // TODO: patch once auth is finished + const create_resp = await fetch('/api/filesystem/create', { + method: 'POST', + body: new URLSearchParams({ + LogicalName: name, + Parent: parentID.toString(), + OwnerGroup: DEFAULT_OWNER_GROUP, + IsDocument: 'false', + }), + }); + + if (!create_resp.ok) { + const message = `An error has occured: ${create_resp.status}`; + throw new Error(message); + } + const response = await create_resp.json(); + return response.Response.NewID; }; export const renameFileEntity = async ( - fileEntityID: string, - fileEntityNewName: string + fileEntityID: string, + fileEntityNewName: string ): Promise => { - const rename_resp = await fetch('/api/filesystem/rename', { - method: 'POST', - body: new URLSearchParams({ - EntityID: fileEntityID, - NewName: fileEntityNewName, - }), - }); - - if (!rename_resp.ok) { - const message = `An error has occured: ${rename_resp.status}`; - throw new Error(message); - } - const response = await rename_resp.json(); - - console.log(response); - return; + const rename_resp = await fetch('/api/filesystem/rename', { + method: 'POST', + body: new URLSearchParams({ + EntityID: fileEntityID, + NewName: fileEntityNewName, + }), + }); + + if (!rename_resp.ok) { + const message = `An error has occured: ${rename_resp.status}`; + throw new Error(message); + } + const response = await rename_resp.json(); + + console.log(response); + return; }; export const deleteFileEntity = async (fileEntityID: string): Promise => { - const delete_resp = await fetch('/api/filesystem/delete', { - method: 'POST', - body: new URLSearchParams({ - EntityID: fileEntityID, - }), - }); - - if (!delete_resp.ok) { - const message = `An error has occured: ${delete_resp.status}`; - throw new Error(message); - } - const response = await delete_resp.json(); - - console.log(response); - return; + const delete_resp = await fetch('/api/filesystem/delete', { + method: 'POST', + body: new URLSearchParams({ + EntityID: fileEntityID, + }), + }); + + if (!delete_resp.ok) { + const message = `An error has occured: ${delete_resp.status}`; + throw new Error(message); + } + const response = await delete_resp.json(); + + console.log(response); + return; }; diff --git a/frontend/src/packages/dashboard/components/ConfirmationModal/ConfirmationWindow.tsx b/frontend/src/packages/dashboard/components/ConfirmationModal/ConfirmationWindow.tsx index 854ef0be..26c75816 100644 --- a/frontend/src/packages/dashboard/components/ConfirmationModal/ConfirmationWindow.tsx +++ b/frontend/src/packages/dashboard/components/ConfirmationModal/ConfirmationWindow.tsx @@ -6,141 +6,139 @@ import { useDispatch } from 'react-redux'; // local imports import Button from '../../../../cse-ui-kit/buttons/Button'; import { - addItemAction, - AddPayloadType, - deleteFileEntityAction, - DeletePayloadType, + addItemAction, + AddPayloadType, + deleteFileEntityAction, + DeletePayloadType, } from 'src/packages/dashboard/state/folders/actions'; import { getFolderState } from '../../api/helpers'; type Props = { + open: boolean; + modalState: { open: boolean; selectedFile: string; type: string }; + setModalState: (flag: { open: boolean; - modalState: { open: boolean; selectedFile: string; type: string }; - setModalState: (flag: { - open: boolean; - selectedFile: string; - type: string; - }) => void; + selectedFile: string; + type: string; + }) => void; }; const Container = styled.div` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); - width: 500px; - height: 200px; - background: white; - border-radius: 20px; + width: 500px; + height: 200px; + background: white; + border-radius: 20px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - grid-gap: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + grid-gap: 20px; `; export default function ConfirmationWindow({ - open, - modalState, - setModalState, + open, + modalState, + setModalState, }: Props) { - const dispatch = useDispatch(); - const [inputValue, setInputValue] = useState(''); - const folderState = getFolderState(); + const dispatch = useDispatch(); + const [inputValue, setInputValue] = useState(''); + const folderState = getFolderState(); - useEffect(() => { - setInputValue(''); - }, [modalState]); + useEffect(() => { + setInputValue(''); + }, [modalState]); - const handleSubmit = () => { - switch (modalState.type) { - case 'folder': { - const folderPayload: AddPayloadType = { - name: inputValue, - type: 'Folder', - parentId: folderState.parentFolder, - }; - dispatch(addItemAction(folderPayload)); - break; - } - case 'file': { - const filePayload: AddPayloadType = { - name: inputValue, - type: 'File', - parentId: folderState.parentFolder, - }; - dispatch(addItemAction(filePayload)); - break; - } - case 'delete': { - const folderPayload: DeletePayloadType = { - id: modalState.selectedFile, - }; - dispatch(deleteFileEntityAction(folderPayload)); - break; - } - } + const handleSubmit = () => { + switch (modalState.type) { + case 'folder': { + const folderPayload: AddPayloadType = { + name: inputValue, + type: 'Folder', + parentId: folderState.parentFolder, + }; + dispatch(addItemAction(folderPayload)); + break; + } + case 'file': { + const filePayload: AddPayloadType = { + name: inputValue, + type: 'File', + parentId: folderState.parentFolder, + }; + dispatch(addItemAction(filePayload)); + break; + } + case 'delete': { + const folderPayload: DeletePayloadType = { + id: modalState.selectedFile, + }; + dispatch(deleteFileEntityAction(folderPayload)); + break; + } + } - setModalState({ - open: false, - selectedFile: '', - type: '', - }); - }; + setModalState({ + open: false, + selectedFile: '', + type: '', + }); + }; - const handleChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setInputValue(value); - }; + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setInputValue(value); + }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - handleSubmit(); - } - }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSubmit(); + } + }; - return ( - { - setModalState({ - open: false, - selectedFile: '', - type: '', - }); - }} - > - {modalState.type !== 'delete' ? ( - - - Choose your {modalState.type} name - - - - - - - ) : ( - - - Are you sure you want to delete? - - - - - - )} - - ); + return ( + { + setModalState({ + open: false, + selectedFile: '', + type: '', + }); + }} + > + {modalState.type !== 'delete' ? ( + + + Choose your {modalState.type} name + + + + + + + ) : ( + + Are you sure you want to delete? + + + + + )} + + ); } diff --git a/frontend/src/packages/dashboard/components/FileRenderer/FileContainer.tsx b/frontend/src/packages/dashboard/components/FileRenderer/FileContainer.tsx index 38fe094e..5ec36fd0 100644 --- a/frontend/src/packages/dashboard/components/FileRenderer/FileContainer.tsx +++ b/frontend/src/packages/dashboard/components/FileRenderer/FileContainer.tsx @@ -4,16 +4,18 @@ // Wraps the contents of a file stored on the CMS into its own // functional component, with hovering capabilities -import React from "react"; -import styled, { css } from "styled-components"; -import { useNavigate } from "react-router-dom"; -import Renamable from "./Renamable"; +import React, { useRef } from 'react'; +import styled, { css } from 'styled-components'; +import { useNavigate } from 'react-router-dom'; +import Renamable from './Renamable'; +import { useDispatch } from 'react-redux'; type Props = { name: string; id: string; selectedFile: string | null; setSelectedFile: (id: string) => void; + handleDeleteKeyDown: (event: React.KeyboardEvent) => void; }; type styledProps = { @@ -34,13 +36,24 @@ const IconContainer = styled.div` cursor: pointer; border: ${(props) => - props.active ? "3px solid red" : "3px solid var(--background-color)"}; + props.active ? '3px solid red' : '3px solid var(--background-color)'}; `; -function FileContainer({ name, id, selectedFile, setSelectedFile }: Props) { +function FileContainer({ + name, + id, + selectedFile, + setSelectedFile, + handleDeleteKeyDown, +}: Props) { + const fileEntityRef = useRef(null); + const handleClick = () => { console.log(id); setSelectedFile(id); + if (fileEntityRef && fileEntityRef.current) { + fileEntityRef.current.focus(); + } }; const navigate = useNavigate(); @@ -48,23 +61,28 @@ function FileContainer({ name, id, selectedFile, setSelectedFile }: Props) { console.log(id); setSelectedFile(id); if (selectedFile !== null) { - navigate("/editor/" + selectedFile, { + navigate('/editor/' + selectedFile, { replace: false, state: { - filename: name - } - }), [navigate]; + filename: name, + }, + }), + [navigate]; } }; return (
setSelectedFile('')} + onKeyDown={handleDeleteKeyDown} > void; + handleDeleteKeyDown: (event: React.KeyboardEvent) => void; } const IconContainer = styled.div` @@ -18,6 +19,7 @@ const IconContainer = styled.div` align-items: center; margin: 20px; text-align: center; + cursor: pointer; `; interface HighlightProps { @@ -42,12 +44,18 @@ export default function FolderContainer({ id, selectedFile, setSelectedFile, + handleDeleteKeyDown, }: Props) { const dispatch = useDispatch(); + const fileEntityRef = useRef(null); + const handleClick = () => { console.log(id); setSelectedFile(id); + if (fileEntityRef && fileEntityRef.current) { + fileEntityRef.current.focus(); + } }; const handleDoubleClick = () => { @@ -57,15 +65,23 @@ export default function FolderContainer({ return ( - - {/* */} +
setSelectedFile('')} + onKeyDown={handleDeleteKeyDown} + > + +
+ + {/* */}
); diff --git a/frontend/src/packages/dashboard/components/FileRenderer/Renderer.tsx b/frontend/src/packages/dashboard/components/FileRenderer/Renderer.tsx index 546c1e9e..c9b98067 100644 --- a/frontend/src/packages/dashboard/components/FileRenderer/Renderer.tsx +++ b/frontend/src/packages/dashboard/components/FileRenderer/Renderer.tsx @@ -1,9 +1,13 @@ import React from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { RootState } from 'src/redux-state/reducers'; import { folderSelectors } from '../../state/folders/index'; import FileContainer from './FileContainer'; import FolderContainer from './FolderContainer'; +import { + DeletePayloadType, + deleteFileEntityAction, +} from '../../state/folders/actions'; type Props = { selectedFile: string | null; @@ -17,6 +21,17 @@ export default function Renderer({ selectedFile, setSelectedFile }: Props) { const folderItems = folders.items; + const dispatch = useDispatch(); + + const handleDeleteKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Delete' && selectedFile !== null) { + const folderPayload: DeletePayloadType = { + id: selectedFile, + }; + dispatch(deleteFileEntityAction(folderPayload)); + } + }; + const renderItems = () => folderItems.map((item, index) => { switch (item.type) { @@ -28,6 +43,7 @@ export default function Renderer({ selectedFile, setSelectedFile }: Props) { name={item.name} selectedFile={selectedFile} setSelectedFile={setSelectedFile} + handleDeleteKeyDown={handleDeleteKeyDown} /> ); case 'File': @@ -38,6 +54,7 @@ export default function Renderer({ selectedFile, setSelectedFile }: Props) { name={item.name} selectedFile={selectedFile} setSelectedFile={setSelectedFile} + handleDeleteKeyDown={handleDeleteKeyDown} /> ); default: diff --git a/frontend/src/packages/dashboard/components/SideBar/SideBar.tsx b/frontend/src/packages/dashboard/components/SideBar/SideBar.tsx index 76c4d404..12e0a1af 100644 --- a/frontend/src/packages/dashboard/components/SideBar/SideBar.tsx +++ b/frontend/src/packages/dashboard/components/SideBar/SideBar.tsx @@ -1,132 +1,130 @@ -import styled from "styled-components"; -import Button from "@mui/material/Button"; -import { useNavigate } from "react-router-dom"; -import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import MenuIcon from "@mui/icons-material/Menu"; +import styled from 'styled-components'; +import Button from '@mui/material/Button'; +import { useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import MenuIcon from '@mui/icons-material/Menu'; import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; -import { useSelector } from "react-redux"; -import { RootState } from "src/redux-state/reducers"; -import { folderSelectors } from "../../state/folders"; -import { getFolderState } from "../../api/helpers"; +import { useSelector } from 'react-redux'; +import { RootState } from 'src/redux-state/reducers'; +import { folderSelectors } from '../../state/folders'; +import { getFolderState } from '../../api/helpers'; import React from 'react'; const Container = styled.div` - position: relative; - width: 250px; - background: #c9bff2; - height: 100vh; - transition: left 0.3s ease-in-out; - margin-right: 25px; + position: relative; + width: 250px; + background: #c9bff2; + height: 100vh; + transition: left 0.3s ease-in-out; + margin-right: 25px; `; const IconWrapper = styled.div` - z-index: 999; - position: absolute; - top: 50%; - transform: translate(0, -50%); + z-index: 999; + position: absolute; + top: 50%; + transform: translate(0, -50%); - right: -45px; - width: 30px; - height: 30px; - cursor: pointer; + right: -45px; + width: 30px; + height: 30px; + cursor: pointer; - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; `; const Circle = styled.div` - position: absolute; - border: 1px solid black; - border-radius: 999px; - width: 40px; - height: 40px; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + position: absolute; + border: 1px solid black; + border-radius: 999px; + width: 40px; + height: 40px; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); `; const SidebarTitle = styled.div` - font-size: xx-large; - margin: 2rem; - font-weight: bold; + font-size: xx-large; + margin: 2rem; + font-weight: bold; `; const ButtonFlex = styled.div` - display: flex; - flex-direction: column; - align-items: center; - grid-gap: 80px; + display: flex; + flex-direction: column; + align-items: center; + grid-gap: 80px; `; const ButtonGroup = styled.div` - display: flex; - flex-direction: column; - align-items: center; - grid-gap: 30px; + display: flex; + flex-direction: column; + align-items: center; + grid-gap: 30px; `; const ButtonIcon = styled.div` - margin: 0 1rem 0; - display: flex; - align-items: center; + margin: 0 1rem 0; + display: flex; + align-items: center; `; const ButtonText = styled.p` - flex-grow: 2; - text-align: left; - margin: 0.2rem 0; + flex-grow: 2; + text-align: left; + margin: 0.2rem 0; `; interface SideBarButtonProps { - bgcolor: string; + bgcolor: string; } const SidebarButton = styled(Button)` - && { - width: 160px; - variant: contained; - background-color: ${(props) => props.bgcolor}; - color: white; - border-radius: 20px; - text-transform: none; - color: black; - display: flex; - &:hover { - transform: scale(1.04); - background-color: darkgrey; - } - &:active { - transform: scale(0.96); - background-color: darkgrey; - } + && { + width: 160px; + variant: contained; + background-color: ${(props) => props.bgcolor}; + color: white; + border-radius: 20px; + text-transform: none; + color: black; + display: flex; + &:hover { + transform: scale(1.04); + background-color: darkgrey; + } + &:active { + transform: scale(0.96); + background-color: darkgrey; } + } `; type Props = { - setModalState: (state: { - open: boolean; - selectedFile: string; - type: string; - }) => void; - selectedFile: string | null; - isOpen: boolean; - setOpen: (state: boolean) => void; + setModalState: (state: { + open: boolean; + selectedFile: string; + type: string; + }) => void; + selectedFile: string | null; + isOpen: boolean; + setOpen: (state: boolean) => void; }; // Wrapper component ${props => props.color} export default function SideBar({ - setModalState, - selectedFile, - isOpen, - setOpen, + setModalState, + selectedFile, + isOpen, + setOpen, }: Props) { - - const handleNewFile = () => { setModalState({ open: true, - type: "file", + type: 'file', selectedFile: '', }); // sets modal to be open }; @@ -134,7 +132,7 @@ export default function SideBar({ const handleNewFolder = () => { setModalState({ open: true, - type: "folder", + type: 'folder', selectedFile: '', }); }; @@ -142,21 +140,24 @@ export default function SideBar({ // temporary delete file entity button const handleDeleteFile = () => { if (selectedFile !== null) { - setModalState({ open: true, selectedFile, type: 'delete' }); + setModalState({ open: true, selectedFile, type: 'delete' }); } }; const navigate = useNavigate(); const handleEdit = () => { if (selectedFile !== null) { - const fileItem = getFolderState().items.find((item) => item.id == selectedFile); - const filename = fileItem != null ? fileItem.name : ""; - navigate("/editor/" + selectedFile, { + const fileItem = getFolderState().items.find( + (item) => item.id == selectedFile + ); + const filename = fileItem != null ? fileItem.name : ''; + navigate('/editor/' + selectedFile, { replace: false, state: { - filename: filename - } - }), [navigate]; + filename: filename, + }, + }), + [navigate]; } }; @@ -166,7 +167,7 @@ export default function SideBar({ }; return ( - + setOpen(!isOpen)}> {isOpen ? : } @@ -182,42 +183,42 @@ export default function SideBar({ }); // sets modal to be open }; - const handleNewFolder = () => { - setModalState({ - open: true, - selectedFile: '', - type: 'folder', - }); - }; + const handleNewFolder = () => { + setModalState({ + open: true, + selectedFile: '', + type: 'folder', + }); + }; - // temporary delete file entity button - const handleDeleteFile = () => { - if (selectedFile !== null) { - setModalState({ open: true, selectedFile, type: 'delete' }); - } - }; + // temporary delete file entity button + const handleDeleteFile = () => { + if (selectedFile !== null) { + setModalState({ open: true, selectedFile, type: 'delete' }); + } + }; - const navigate = useNavigate(); - const handleEdit = () => { - if (selectedFile !== null) { - navigate('/editor/' + selectedFile, { replace: false }), [navigate]; - } - }; + const navigate = useNavigate(); + const handleEdit = () => { + if (selectedFile !== null) { + navigate('/editor/' + selectedFile, { replace: false }), [navigate]; + } + }; - // TODO - const handleRecycle = () => { - return; - }; + // TODO + const handleRecycle = () => { + return; + }; - return ( - - setOpen(!isOpen)}> - - {isOpen ? : } - - Welcome \name\ - - {/* + return ( + + setOpen(!isOpen)}> + + {isOpen ? : } + + Welcome \name\ + + {/* Blog @@ -225,53 +226,53 @@ export default function SideBar({ Core pages */} - - - - - - New page - - - - - - New folder - - - - - - Delete Folder/File - - - - - - - - Edit - - {/* + + + + + + New page + + + + + + New folder + + + + + + Delete Folder/File + + + + + + + + Edit + + {/* Feature Recycle */} - - - - ); + + + + ); }