diff --git a/products/ASC.Files/Client/package.json b/products/ASC.Files/Client/package.json index c05e5b98bf..6c38c6f8ea 100644 --- a/products/ASC.Files/Client/package.json +++ b/products/ASC.Files/Client/package.json @@ -38,7 +38,8 @@ }, "dependencies": { "copy-to-clipboard": "^3.3.1", - "file-saver": "^2.0.5" + "file-saver": "^2.0.5", + "react-hotkeys-hook": "^3.4.4" }, "devDependencies": { "@babel/core": "^7.15.5", diff --git a/products/ASC.Files/Client/src/HOCs/withHotkeys.js b/products/ASC.Files/Client/src/HOCs/withHotkeys.js new file mode 100644 index 0000000000..29ed886568 --- /dev/null +++ b/products/ASC.Files/Client/src/HOCs/withHotkeys.js @@ -0,0 +1,88 @@ +import React from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { observer, inject } from "mobx-react"; + +const withHotkeys = (Component) => { + const WithHotkeys = (props) => { + const { selection, setSelection, firstFile, nextFile, prevFile } = props; + + //Select item + useHotkeys( + "x", + () => (selection.length ? setSelection([]) : setSelection([firstFile])), + [selection] + ); + + //Select bottom element + // TODO: tile view + useHotkeys( + "j, DOWN", + () => { + if (!selection.length) setSelection([firstFile]); + else if (nextFile) setSelection([nextFile]); + }, + [nextFile, selection, firstFile] + ); + + //Select upper item + // TODO: tile view + useHotkeys( + "k, UP", + () => { + if (!selection.length) setSelection([firstFile]); + else if (prevFile) setSelection([prevFile]); + }, + [prevFile, selection, firstFile] + ); + + //Select item on the left + useHotkeys( + "h, LEFT", + () => { + if (!selection.length) setSelection([firstFile]); + else if (prevFile) setSelection([prevFile]); + }, + [prevFile, selection, firstFile] + ); + + //Select item on the right + useHotkeys( + "l, RIGHT", + () => { + if (!selection.length) setSelection([firstFile]); + else if (nextFile) setSelection([nextFile]); + }, + [nextFile, selection, firstFile] + ); + + return ; + }; + + return inject(({ filesStore }) => { + const { selection, setSelection, filesList } = filesStore; + + const indexOfCurrentFile = + filesList && + selection.length && + filesList.findIndex((f) => f.id === selection[selection.length - 1].id); + + const isValidIndex = + indexOfCurrentFile !== null && indexOfCurrentFile !== -1; + + const nextIndex = isValidIndex ? indexOfCurrentFile + 1 : null; + const prevIndex = isValidIndex ? indexOfCurrentFile - 1 : null; + + const nextFile = nextIndex !== null ? filesList[nextIndex] : null; + const prevFile = prevIndex !== null ? filesList[prevIndex] : null; + + return { + selection, + setSelection, + firstFile: filesList[0], + nextFile, + prevFile, + }; + })(observer(WithHotkeys)); +}; + +export default withHotkeys; diff --git a/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js b/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js index 0f1e66ceb0..41560abe4c 100644 --- a/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js +++ b/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js @@ -9,6 +9,7 @@ import EmptyContainer from "../../../../components/EmptyContainer"; import withLoader from "../../../../HOCs/withLoader"; import TableView from "./TableView/TableContainer"; import { Consumer } from "@appserver/components/utils/context"; +import withHotkeys from "../../../../HOCs/withHotkeys"; let currentDroppable = null; @@ -246,7 +247,7 @@ export default inject( )( withRouter( withTranslation(["Home", "Common", "Translations"])( - withLoader(observer(SectionBodyContent))() + withLoader(withHotkeys(observer(SectionBodyContent)))() ) ) ); diff --git a/products/ASC.Files/Client/src/store/FilesActionsStore.js b/products/ASC.Files/Client/src/store/FilesActionsStore.js index 406dd354d1..4978f3dad6 100644 --- a/products/ASC.Files/Client/src/store/FilesActionsStore.js +++ b/products/ASC.Files/Client/src/store/FilesActionsStore.js @@ -336,8 +336,13 @@ class FilesActionStore { } }; - onSelectItem = ({ id, isFolder }) => { - const { setBufferSelection, selected, setSelected } = this.filesStore; + onSelectItem = ({ id, isFolder }, isBuffer = false) => { + const { + setBufferSelection, + selected, + setSelected, + setSelection, + } = this.filesStore; /* selected === "close" && */ setSelected("none"); if (!id) return; @@ -348,7 +353,7 @@ class FilesActionStore { if (item) { item.isFolder = isFolder; - setBufferSelection(item); + isBuffer ? setBufferSelection(item) : setSelection([item]); } }; diff --git a/yarn.lock b/yarn.lock index 1f17cbc4bd..6ee9969b66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10924,6 +10924,11 @@ hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" +hotkeys-js@3.8.7: + version "3.8.7" + resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.8.7.tgz#c16cab978b53d7242f860ca3932e976b92399981" + integrity sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg== + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -16551,6 +16556,13 @@ react-helmet-async@^1.0.2, react-helmet-async@^1.0.7: react-fast-compare "^3.2.0" shallowequal "^1.1.0" +react-hotkeys-hook@^3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz#52ba5d8ef5e47cc2e776c70a9036d518e0993d51" + integrity sha512-vaORq07rWgmuF3owWRhgFV/3VL8/l2q9lz0WyVEddJnWTtKW+AOgU5YgYKuwN6h6h7bCcLG3MFsJIjCrM/5DvQ== + dependencies: + hotkeys-js "3.8.7" + react-i18next@^11.12.0: version "11.14.3" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.14.3.tgz#b44b5c4d1aadac5211be011827a2830be60f2522"