From 5f59210c85aa39c3c1b8d43f29d907587bdb29b0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 4 Aug 2019 14:58:31 -0700 Subject: [PATCH] Use easy-peasy to store file state data --- package.json | 2 + .../scripts/api/server/files/loadDirectory.ts | 3 ++ .../scripts/components/elements/Field.tsx | 4 +- .../server/files/FileDropdownMenu.tsx | 9 +++- .../server/files/FileManagerContainer.tsx | 20 ++++--- .../components/server/files/FileObjectRow.tsx | 7 ++- .../server/files/RenameFileModal.tsx | 23 +++++--- resources/scripts/helpers.ts | 7 --- resources/scripts/state/server/files.ts | 54 +++++++++++++++++++ resources/scripts/state/server/index.ts | 7 ++- yarn.lock | 8 ++- 11 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 resources/scripts/state/server/files.ts diff --git a/package.json b/package.json index ae732bc31..704d612b6 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "sockette": "^2.0.6", "styled-components": "^4.3.2", "use-react-router": "^1.0.7", + "uuid": "^3.3.2", "ws-wrapper": "^2.0.0", "xterm": "^3.14.4", "xterm-addon-attach": "^0.1.0", @@ -50,6 +51,7 @@ "@types/react-router-dom": "^4.3.3", "@types/react-transition-group": "^2.9.2", "@types/styled-components": "^4.1.18", + "@types/uuid": "^3.4.5", "@types/webpack-env": "^1.13.6", "@types/yup": "^0.26.17", "@typescript-eslint/eslint-plugin": "^1.10.1", diff --git a/resources/scripts/api/server/files/loadDirectory.ts b/resources/scripts/api/server/files/loadDirectory.ts index 64e504e0c..7c73c58a9 100644 --- a/resources/scripts/api/server/files/loadDirectory.ts +++ b/resources/scripts/api/server/files/loadDirectory.ts @@ -1,6 +1,8 @@ import http from '@/api/http'; +import v4 from 'uuid/v4'; export interface FileObject { + uuid: string; name: string; mode: string; size: number; @@ -18,6 +20,7 @@ export default (uuid: string, directory?: string): Promise => { params: { directory }, }) .then(response => resolve((response.data.data || []).map((item: any): FileObject => ({ + uuid: v4(), name: item.attributes.name, mode: item.attributes.mode, size: Number(item.attributes.size), diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index f1820343e..c9557c0f0 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -8,10 +8,11 @@ interface Props { name: string; label?: string; description?: string; + autoFocus?: boolean; validate?: (value: any) => undefined | string | Promise; } -export default ({ id, type, name, label, description, validate }: Props) => ( +export default ({ id, type, name, label, description, autoFocus, validate }: Props) => ( { ({ field, form: { errors, touched } }: FieldProps) => ( @@ -23,6 +24,7 @@ export default ({ id, type, name, label, description, validate }: Props) => ( id={id} type={type} {...field} + autoFocus={autoFocus} className={classNames('input-dark', { error: touched[field.name] && errors[field.name], })} diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index acaa14673..1ab8877e3 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -1,7 +1,6 @@ import React, { createRef, useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEllipsisH } from '@fortawesome/free-solid-svg-icons/faEllipsisH'; -import { FileObject } from '@/api/server/files/loadDirectory'; import { CSSTransition } from 'react-transition-group'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; @@ -9,15 +8,21 @@ import { faFileDownload } from '@fortawesome/free-solid-svg-icons/faFileDownload import { faCopy } from '@fortawesome/free-solid-svg-icons/faCopy'; import { faLevelUpAlt } from '@fortawesome/free-solid-svg-icons/faLevelUpAlt'; import RenameFileModal from '@/components/server/files/RenameFileModal'; +import { ServerContext } from '@/state/server'; type ModalType = 'rename' | 'move'; -export default ({ file }: { file: FileObject }) => { +export default ({ uuid }: { uuid: string }) => { const menu = createRef(); const [ visible, setVisible ] = useState(false); const [ modal, setModal ] = useState(null); const [ posX, setPosX ] = useState(0); + const file = ServerContext.useStoreState(state => state.files.contents.find(file => file.uuid === uuid)); + if (!file) { + return null; + } + const windowListener = (e: MouseEvent) => { if (e.button === 2 || !visible || !menu.current) { return; diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index de0d1d215..5799788e7 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; import { ServerContext } from '@/state/server'; -import loadDirectory, { FileObject } from '@/api/server/files/loadDirectory'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; @@ -9,22 +8,21 @@ import { CSSTransition } from 'react-transition-group'; import { Link } from 'react-router-dom'; import Spinner from '@/components/elements/Spinner'; import FileObjectRow from '@/components/server/files/FileObjectRow'; -import { getDirectoryFromHash } from '@/helpers'; export default () => { const [ loading, setLoading ] = useState(true); - const [ files, setFiles ] = useState([]); - const server = ServerContext.useStoreState(state => state.server.data!); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const { contents: files } = ServerContext.useStoreState(state => state.files); + const getDirectoryContents = ServerContext.useStoreActions(actions => actions.files.getDirectoryContents); + + const urlDirectory = window.location.hash.replace(/^#(\/)+/, '/'); const load = () => { setLoading(true); clearFlashes(); - loadDirectory(server.uuid, getDirectoryFromHash()) - .then(files => { - setFiles(files); - setLoading(false); - }) + + getDirectoryContents(urlDirectory) + .then(() => setLoading(false)) .catch(error => { if (error.response && error.response.status === 404) { window.location.hash = '#/'; @@ -36,7 +34,7 @@ export default () => { }); }; - const breadcrumbs = (): { name: string; path?: string }[] => getDirectoryFromHash().split('/') + const breadcrumbs = (): { name: string; path?: string }[] => urlDirectory.split('/') .filter(directory => !!directory) .map((directory, index, dirs) => { if (index === dirs.length - 1) { @@ -86,7 +84,7 @@ export default () => {
{ files.map(file => ( - + )) }
diff --git a/resources/scripts/components/server/files/FileObjectRow.tsx b/resources/scripts/components/server/files/FileObjectRow.tsx index a23a6c96a..32df60df9 100644 --- a/resources/scripts/components/server/files/FileObjectRow.tsx +++ b/resources/scripts/components/server/files/FileObjectRow.tsx @@ -9,8 +9,11 @@ import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; import React from 'react'; import { FileObject } from '@/api/server/files/loadDirectory'; import FileDropdownMenu from '@/components/server/files/FileDropdownMenu'; +import { ServerContext } from '@/state/server'; + +export default ({ file }: { file: FileObject }) => { + const directory = ServerContext.useStoreState(state => state.files.directory); -export default ({ file, directory }: { file: FileObject; directory: string }) => { return ( distanceInWordsToNow(file.modifiedAt, { includeSeconds: true }) } - + ); }; diff --git a/resources/scripts/components/server/files/RenameFileModal.tsx b/resources/scripts/components/server/files/RenameFileModal.tsx index 8e5575991..5b97d0e4e 100644 --- a/resources/scripts/components/server/files/RenameFileModal.tsx +++ b/resources/scripts/components/server/files/RenameFileModal.tsx @@ -1,26 +1,32 @@ import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikActions } from 'formik'; -import { FileObject } from '@/api/server/files/loadDirectory'; import Field from '@/components/elements/Field'; -import { getDirectoryFromHash } from '@/helpers'; import { join } from 'path'; import renameFile from '@/api/server/files/renameFile'; import { ServerContext } from '@/state/server'; +import { FileObject } from '@/api/server/files/loadDirectory'; interface FormikValues { name: string; } -export default ({ file, ...props }: RequiredModalProps & { file: FileObject }) => { - const server = ServerContext.useStoreState(state => state.server.data!); +type Props = RequiredModalProps & { file: FileObject }; + +export default ({ file, ...props }: Props) => { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); + const pushFile = ServerContext.useStoreActions(actions => actions.files.pushFile); const submit = (values: FormikValues, { setSubmitting }: FormikActions) => { - const renameFrom = join(getDirectoryFromHash(), file.name); - const renameTo = join(getDirectoryFromHash(), values.name); + const renameFrom = join(directory, file.name); + const renameTo = join(directory, values.name); - renameFile(server.uuid, { renameFrom, renameTo }) - .then(() => props.onDismissed()) + renameFile(uuid, { renameFrom, renameTo }) + .then(() => { + pushFile({ ...file, name: values.name }); + props.onDismissed(); + }) .catch(error => { setSubmitting(false); console.error(error); @@ -41,6 +47,7 @@ export default ({ file, ...props }: RequiredModalProps & { file: FileObject }) = name={'name'} label={'File Name'} description={'Enter the new name of this file or folder.'} + autoFocus={true} />