From a1b92f5587b54f4f9d2a41a35c978bb9fa5fde5d Mon Sep 17 00:00:00 2001 From: controlol Date: Mon, 1 Nov 2021 23:46:03 +0100 Subject: [PATCH] Closes #19 only render visable gridrows Doing this greatly improves render speed and bypasses the webbrowsers grid limitation of 1000 files With a padding for a better user experience --- src/components/fileBrowser.jsx | 95 ++++++++++++++++++++++-------- src/components/fileBrowserMenu.jsx | 1 - 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/components/fileBrowser.jsx b/src/components/fileBrowser.jsx index 4cebe1f..37a8fab 100644 --- a/src/components/fileBrowser.jsx +++ b/src/components/fileBrowser.jsx @@ -40,6 +40,15 @@ import { Button } from '../styled.js' import assert from 'assert' import LineLoader from './LineLoader.jsx' +const ROW_HEIGHT = 20 +const ROW_GAP = 8 +const DATA_PADDING = 3 +const DEBOUNCE_THRESHOLD = 100 + +const OptimisticRow = styled.div` + grid-column: 1 / span 3; +` + const FBContainer = styled.div` display: flex; flex-direction: column; @@ -213,13 +222,16 @@ class FileBrowser extends Component { renderMenu: false, cursorX: 0, cursorY: 0, - clicked: "" + clicked: "", + from: 0, + to: 100 } this.backListener = undefined this.handleInputChange = this.handleInputChange.bind(this) this.searchTimeout = undefined + this.scrollTimeout = undefined } componentDidMount = () => { @@ -241,7 +253,7 @@ class FileBrowser extends Component { this.props.currentPath.split("/").length < this.state.prevPath.split("/").length || this.props.currentPath === "/" ) direction = -1 - this.setState({ prevPath: this.props.currentPath, transitionFiles: 1 * direction }) + this.setState({ prevPath: this.props.currentPath, transitionFiles: 1 * direction, from: 0, to: 100 }) delay(5).then(() => this.setState({ transitionFiles: 2 * direction })) delay(300).then(() => this.setState({ transitionFiles: 3 * direction, files: this.props.files })) @@ -399,17 +411,9 @@ class FileBrowser extends Component { if (isNew) files = this.props.files else files = this.state.files + files = this.getOrderedItems(files).slice(this.state.from, this.state.to) + return files - // apply search filter - .filter(v => v.Name?.toLowerCase().indexOf(this.state.filter) !== -1) - // sort by name - .sort((a,b) => this.state.orderBy === "name" ? this.state.orderAscending ? a.Name.localeCompare(b.Name, 'nl', { sensitivity: 'base' }) : b.Name.localeCompare(a.Name, 'nl', { sensitivity: 'base' }) : 0) - // // sort by modified date - // .sort((a,b) => this.state.orderBy === "modified" ? this.state.orderAscending ? a.modified - b.modified : b.modified - a.modified : 0) - // sort by size - .sort((a,b) => this.state.orderBy === "size" ? this.state.orderAscending ? a.Size - b.Size : b.Size - a.Size : 0) - // // sort folders to top - .sort((a,b) => (b.IsDir ? 1 : 0) - (a.IsDir ? 1 : 0)) .map(v => ( { this.renderImage(v.MimeType, v.Name) } @@ -425,6 +429,34 @@ class FileBrowser extends Component { )) } + getOrderedItems = files => { + return files + // apply search filter + .filter(v => v.Name?.toLowerCase().indexOf(this.state.filter) !== -1) + // sort by name + .sort((a,b) => this.state.orderBy === "name" ? this.state.orderAscending ? a.Name.localeCompare(b.Name, 'nl', { sensitivity: 'base' }) : b.Name.localeCompare(a.Name, 'nl', { sensitivity: 'base' }) : 0) + // // sort by modified date + // .sort((a,b) => this.state.orderBy === "modified" ? this.state.orderAscending ? a.modified - b.modified : b.modified - a.modified : 0) + // sort by size + .sort((a,b) => this.state.orderBy === "size" ? this.state.orderAscending ? a.Size - b.Size : b.Size - a.Size : 0) + // // sort folders to top + .sort((a,b) => (b.IsDir ? 1 : 0) - (a.IsDir ? 1 : 0)) + } + + handleDebounce = (scrollTop, clientHeight) => { + const maxVisibleRows = Math.ceil(clientHeight / (ROW_HEIGHT + ROW_GAP)) + const from = Math.max(0, Math.floor(scrollTop / (ROW_HEIGHT + ROW_GAP)) - maxVisibleRows * DATA_PADDING) + const to = Math.min(this.state.files.length, from + maxVisibleRows * (DATA_PADDING * 2 + 1)) + console.log({from, to}) + this.setState({from, to}) + } + + handleGridScroll = e => { + clearTimeout(this.scrollTimeout) + const { scrollTop, clientHeight } = e.target + this.scrollTimeout = setTimeout(this.handleDebounce, DEBOUNCE_THRESHOLD, scrollTop, clientHeight) + } + // render the path the user is currently at renderPath = () => path.join(...this.props.currentPath.split("/")) @@ -434,7 +466,8 @@ class FileBrowser extends Component { ) render() { - const { transitionFiles } = this.state + const { transitionFiles, from, to, orderBy, orderAscending, files } = this.state + const rows = Math.max(this.props.files.length, files.length) return ( @@ -465,11 +498,11 @@ class FileBrowser extends Component { this.updateOrder("name")} style={{ position: "relative", cursor: "pointer" }}> filename { - this.state.orderBy === "name" && - {this.state.orderAscending this.updateOrder("modified")} style={{ position: "relative", cursor: "pointer" }}> modified { - this.state.orderBy === "modified" && - {this.state.orderAscending this.updateOrder("size")} style={{ position: "relative", cursor: "pointer" }}> size { - this.state.orderBy === "size" && - {this.state.orderAscending - + { transitionFiles !== 0 && -2 && transitionFiles < 0 ? "translateX(-100%)" : undefined }}> + { + from > 0 && + + } { this.renderFiles(true) } + { + to < rows && + + } } @@ -537,7 +578,15 @@ class FileBrowser extends Component { transitionFiles === 3 || transitionFiles === -3 ? "none" : undefined }}> + { + from > 0 && + + } { this.renderFiles() } + { + to < rows && + + } diff --git a/src/components/fileBrowserMenu.jsx b/src/components/fileBrowserMenu.jsx index ecbeff1..4b9d54c 100644 --- a/src/components/fileBrowserMenu.jsx +++ b/src/components/fileBrowserMenu.jsx @@ -69,7 +69,6 @@ class FileBrowserMenu extends Component { .then(response => { if (typeof response.data.list !== "object") return reject(new Error("Invalid response")) - if (response.data.list.length > 100) response.data.list.length = 100 let { files } = this.state files[brIndex] = response.data.list loading[brIndex] = false