fix: Virtualize move document listbox (#1650)
* fix: Virtualize move document listbox closes #1645
This commit is contained in:
parent
32d3053002
commit
7bdcba46b8
|
@ -14,6 +14,7 @@ type Props = {
|
||||||
document?: ?Document,
|
document?: ?Document,
|
||||||
collection: ?Collection,
|
collection: ?Collection,
|
||||||
onSuccess?: () => void,
|
onSuccess?: () => void,
|
||||||
|
style?: Object,
|
||||||
ref?: (?React.ElementRef<"div">) => void,
|
ref?: (?React.ElementRef<"div">) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,13 +35,20 @@ class PathToDocument extends React.Component<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { result, collection, document, ref } = this.props;
|
const { result, collection, document, ref, style } = this.props;
|
||||||
const Component = document ? ResultWrapperLink : ResultWrapper;
|
const Component = document ? ResultWrapperLink : ResultWrapper;
|
||||||
|
|
||||||
if (!result) return <div />;
|
if (!result) return <div />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component ref={ref} onClick={this.handleClick} href="" selectable>
|
<Component
|
||||||
|
ref={ref}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
href=""
|
||||||
|
style={style}
|
||||||
|
role="option"
|
||||||
|
selectable
|
||||||
|
>
|
||||||
{collection && <CollectionIcon collection={collection} />}
|
{collection && <CollectionIcon collection={collection} />}
|
||||||
|
|
||||||
{result.path
|
{result.path
|
||||||
|
@ -72,21 +80,27 @@ const StyledGoToIcon = styled(GoToIcon)`
|
||||||
const ResultWrapper = styled.div`
|
const ResultWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
margin-left: -4px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
color: ${(props) => props.theme.text};
|
color: ${(props) => props.theme.text};
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ResultWrapperLink = styled(ResultWrapper.withComponent("a"))`
|
const ResultWrapperLink = styled(ResultWrapper.withComponent("a"))`
|
||||||
margin: 0 -8px;
|
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
|
|
||||||
${DocumentTitle} {
|
${DocumentTitle} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
|
|
||||||
import { Search } from "js-search";
|
import { Search } from "js-search";
|
||||||
import { last } from "lodash";
|
import { last } from "lodash";
|
||||||
import { observable, computed } from "mobx";
|
import { observable, computed } from "mobx";
|
||||||
import { observer, inject } from "mobx-react";
|
import { observer, inject } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import AutoSizer from "react-virtualized-auto-sizer";
|
||||||
|
import { FixedSizeList as List } from "react-window";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import CollectionsStore, { type DocumentPath } from "stores/CollectionsStore";
|
import CollectionsStore, { type DocumentPath } from "stores/CollectionsStore";
|
||||||
import DocumentsStore from "stores/DocumentsStore";
|
import DocumentsStore from "stores/DocumentsStore";
|
||||||
import UiStore from "stores/UiStore";
|
import UiStore from "stores/UiStore";
|
||||||
|
@ -28,7 +27,6 @@ type Props = {|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class DocumentMove extends React.Component<Props> {
|
class DocumentMove extends React.Component<Props> {
|
||||||
firstDocument: ?PathToDocument;
|
|
||||||
@observable searchTerm: ?string;
|
@observable searchTerm: ?string;
|
||||||
@observable isSaving: boolean;
|
@observable isSaving: boolean;
|
||||||
|
|
||||||
|
@ -87,17 +85,6 @@ class DocumentMove extends React.Component<Props> {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (ev) => {
|
|
||||||
// Down
|
|
||||||
if (ev.which === 40) {
|
|
||||||
ev.preventDefault();
|
|
||||||
if (this.firstDocument) {
|
|
||||||
const element = ReactDOM.findDOMNode(this.firstDocument);
|
|
||||||
if (element instanceof HTMLElement) element.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSuccess = () => {
|
handleSuccess = () => {
|
||||||
this.props.ui.showToast("Document moved");
|
this.props.ui.showToast("Document moved");
|
||||||
this.props.onRequestClose();
|
this.props.onRequestClose();
|
||||||
|
@ -107,10 +94,6 @@ class DocumentMove extends React.Component<Props> {
|
||||||
this.searchTerm = ev.target.value;
|
this.searchTerm = ev.target.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
setFirstDocumentRef = (ref) => {
|
|
||||||
this.firstDocument = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderPathToCurrentDocument() {
|
renderPathToCurrentDocument() {
|
||||||
const { collections, document } = this.props;
|
const { collections, document } = this.props;
|
||||||
const result = collections.getPathForDocument(document.id);
|
const result = collections.getPathForDocument(document.id);
|
||||||
|
@ -125,8 +108,24 @@ class DocumentMove extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row = ({ index, data, style }) => {
|
||||||
|
const result = data[index];
|
||||||
|
const { document, collections } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PathToDocument
|
||||||
|
result={result}
|
||||||
|
document={document}
|
||||||
|
collection={collections.get(result.collectionId)}
|
||||||
|
onSuccess={this.handleSuccess}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { document, collections, onRequestClose } = this.props;
|
const { document, collections, onRequestClose } = this.props;
|
||||||
|
const data = this.results;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen onRequestClose={onRequestClose} title="Move document">
|
<Modal isOpen onRequestClose={onRequestClose} title="Move document">
|
||||||
|
@ -145,33 +144,29 @@ class DocumentMove extends React.Component<Props> {
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search collections & documents…"
|
placeholder="Search collections & documents…"
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
onChange={this.handleFilter}
|
onChange={this.handleFilter}
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</InputWrapper>
|
</InputWrapper>
|
||||||
|
|
||||||
<Results>
|
<Results>
|
||||||
<Flex column>
|
<AutoSizer>
|
||||||
<StyledArrowKeyNavigation
|
{({ width, height }) => (
|
||||||
mode={ArrowKeyNavigation.mode.VERTICAL}
|
<Flex role="listbox" column>
|
||||||
defaultActiveChildIndex={0}
|
<List
|
||||||
>
|
key={data.length}
|
||||||
{this.results.map((result, index) => (
|
width={width}
|
||||||
<PathToDocument
|
height={height}
|
||||||
key={result.id}
|
itemData={data}
|
||||||
result={result}
|
itemCount={data.length}
|
||||||
document={document}
|
itemSize={40}
|
||||||
collection={collections.get(result.collectionId)}
|
itemKey={(index, data) => data[index].id}
|
||||||
ref={(ref) =>
|
>
|
||||||
index === 0 && this.setFirstDocumentRef(ref)
|
{this.row}
|
||||||
}
|
</List>
|
||||||
onSuccess={this.handleSuccess}
|
</Flex>
|
||||||
/>
|
)}
|
||||||
))}
|
</AutoSizer>
|
||||||
</StyledArrowKeyNavigation>
|
|
||||||
</Flex>
|
|
||||||
</Results>
|
</Results>
|
||||||
</NewLocation>
|
</NewLocation>
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -202,25 +197,18 @@ const Input = styled("input")`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NewLocation = styled(Outline)`
|
const NewLocation = styled(Outline)`
|
||||||
flex-direction: column;
|
display: block;
|
||||||
|
flex: initial;
|
||||||
|
height: 40vh;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Results = styled(Flex)`
|
const Results = styled.div`
|
||||||
display: block;
|
padding: 8px 0;
|
||||||
width: 100%;
|
height: calc(100% - 46px);
|
||||||
max-height: 40vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 8px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Section = styled(Flex)`
|
const Section = styled(Flex)`
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default inject("documents", "collections", "ui")(DocumentMove);
|
export default inject("documents", "collections", "ui")(DocumentMove);
|
||||||
|
|
|
@ -141,7 +141,9 @@
|
||||||
"react-modal": "^3.1.2",
|
"react-modal": "^3.1.2",
|
||||||
"react-portal": "^4.0.0",
|
"react-portal": "^4.0.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
|
"react-virtualized-auto-sizer": "^1.0.2",
|
||||||
"react-waypoint": "^9.0.2",
|
"react-waypoint": "^9.0.2",
|
||||||
|
"react-window": "^1.8.6",
|
||||||
"rich-markdown-editor": "^11.0.6",
|
"rich-markdown-editor": "^11.0.6",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"sequelize": "^6.3.4",
|
"sequelize": "^6.3.4",
|
||||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -962,10 +962,10 @@
|
||||||
core-js-pure "^3.0.0"
|
core-js-pure "^3.0.0"
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||||
version "7.11.2"
|
version "7.12.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||||
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
|
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
@ -7719,6 +7719,11 @@ mem@^4.0.0:
|
||||||
mimic-fn "^2.0.0"
|
mimic-fn "^2.0.0"
|
||||||
p-is-promise "^2.0.0"
|
p-is-promise "^2.0.0"
|
||||||
|
|
||||||
|
"memoize-one@>=3.1.1 <6":
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||||
|
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||||
|
|
||||||
memoizee@^0.4.14:
|
memoizee@^0.4.14:
|
||||||
version "0.4.14"
|
version "0.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
||||||
|
@ -9470,6 +9475,11 @@ react-side-effect@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shallowequal "^1.0.1"
|
shallowequal "^1.0.1"
|
||||||
|
|
||||||
|
react-virtualized-auto-sizer@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
|
||||||
|
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
|
||||||
|
|
||||||
react-waypoint@^9.0.2:
|
react-waypoint@^9.0.2:
|
||||||
version "9.0.2"
|
version "9.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-9.0.2.tgz#d65fb0fe6ff5c1b832a1d01b1462a661fb921e45"
|
resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-9.0.2.tgz#d65fb0fe6ff5c1b832a1d01b1462a661fb921e45"
|
||||||
|
@ -9479,6 +9489,14 @@ react-waypoint@^9.0.2:
|
||||||
prop-types "^15.0.0"
|
prop-types "^15.0.0"
|
||||||
react-is "^16.6.3"
|
react-is "^16.6.3"
|
||||||
|
|
||||||
|
react-window@^1.8.6:
|
||||||
|
version "1.8.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112"
|
||||||
|
integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
memoize-one ">=3.1.1 <6"
|
||||||
|
|
||||||
react@^16.8.6:
|
react@^16.8.6:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||||
|
|
Reference in New Issue