From 4bc3d16aa958e8d3232b6cca255d8e1a31079b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Feb 2020 18:37:09 +0100 Subject: [PATCH] WIP --- scm-ui/ui-types/src/Sources.ts | 1 + .../src/repos/sources/components/FileTree.tsx | 161 +++++++++++------- .../src/repos/sources/modules/sources.ts | 91 +++++++--- 3 files changed, 165 insertions(+), 88 deletions(-) diff --git a/scm-ui/ui-types/src/Sources.ts b/scm-ui/ui-types/src/Sources.ts index dce6947622..99e3bde7ac 100644 --- a/scm-ui/ui-types/src/Sources.ts +++ b/scm-ui/ui-types/src/Sources.ts @@ -18,6 +18,7 @@ export type File = { subRepository?: SubRepository; // TODO partialResult: boolean; computationAborted: boolean; + truncated: boolean; _links: Links; _embedded: { children: File[] | null | undefined; diff --git a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx index 0c5568d09c..55a9abdf18 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx @@ -7,28 +7,40 @@ import styled from "styled-components"; import { binder } from "@scm-manager/ui-extensions"; import { File, Repository } from "@scm-manager/ui-types"; import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components"; -import { fetchSources, getFetchSourcesFailure, getSources, isFetchSourcesPending } from "../modules/sources"; +import { + fetchSources, + getFetchSourcesFailure, + getHunkCount, + getSources, + isFetchSourcesPending, isUpdateSourcePending +} from "../modules/sources"; import FileTreeLeaf from "./FileTreeLeaf"; -import queryString from "query-string"; +import Button from "@scm-manager/ui-components/src/buttons/Button"; -type Props = WithTranslation & { +type Hunk = { + tree: File; loading: boolean; error: Error; - tree: File; + updateSources: (hunk: number) => void; +}; + +type Props = WithTranslation & { repository: Repository; revision: string; path: string; baseUrl: string; location: any; + hunks: Hunk[]; - updateSources: () => void; + // dispatch props + fetchSources: (repository: Repository, revision: string, path: string, hunk: number) => void; // context props match: any; }; type State = { - stoppableUpdateHandler?: number; + stoppableUpdateHandler: number[]; }; const FixedWidthTh = styled.th` @@ -50,44 +62,57 @@ export function findParent(path: string) { class FileTree extends React.Component { constructor(props: Props) { super(props); - this.state = {}; + this.state = { stoppableUpdateHandler: [] }; } componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { if (prevState.stoppableUpdateHandler === this.state.stoppableUpdateHandler) { - const { tree, updateSources } = this.props; - if (tree?._embedded?.children && tree._embedded.children.find(c => c.partialResult)) { - const stoppableUpdateHandler = setTimeout(updateSources, 3000); - this.setState({ stoppableUpdateHandler: stoppableUpdateHandler }); - } + const { hunks } = this.props; + hunks?.forEach((hunk, index) => { + if (hunk.tree?._embedded?.children && hunk.tree._embedded.children.find(c => c.partialResult)) { + const stoppableUpdateHandler = setTimeout(hunk.updateSources, 3000); + this.setState(prevState => { + return { + stoppableUpdateHandler: [...prevState.stoppableUpdateHandler, stoppableUpdateHandler] + }; + }); + } + }); } } componentWillUnmount(): void { - if (this.state.stoppableUpdateHandler) { - clearTimeout(this.state.stoppableUpdateHandler); - } + this.state.stoppableUpdateHandler.forEach(handler => clearTimeout(handler)); } + loadMore = () => { + // console.log("smth"); + }; + render() { - const { error, loading, tree } = this.props; + const { hunks, t } = this.props; - if (error) { - return ; - } - - if (loading) { - return ; - } - if (!tree) { + if (!hunks || hunks.length === 0) { return null; } - return
{this.renderSourcesTable()}
; + if (hunks.some(hunk => hunk.error)) { + return hunk.error)[0]} />; + } + + const lastHunk = hunks[hunks.length - 1]; + + return ( +
+ {this.renderSourcesTable()} + {lastHunk.loading && } + {lastHunk.tree?.truncated &&
+ ); } renderSourcesTable() { - const { tree, revision, path, baseUrl, t, location } = this.props; + const { hunks, revision, path, baseUrl, t } = this.props; const files = []; @@ -115,46 +140,41 @@ class FileTree extends React.Component { } }; - if (tree._embedded && tree._embedded.children) { - const children = [...tree._embedded.children].sort(compareFiles); - files.push(...children); - } + hunks + .filter(hunk => !hunk.loading) + .forEach(hunk => { + if (hunk.tree?._embedded && hunk.tree._embedded.children) { + const children = [...hunk.tree._embedded.children]; + files.push(...children); + } + }); if (files && files.length > 0) { - let baseUrlWithRevision = baseUrl; if (revision) { baseUrlWithRevision += "/" + encodeURIComponent(revision); } else { - baseUrlWithRevision += "/" + encodeURIComponent(tree.revision); - } - - const offset = queryString.parse(location.search).offset; - if (offset) { - baseUrlWithRevision += "?offset=" + offset; + baseUrlWithRevision += "/" + encodeURIComponent(hunks[0].tree.revision); } return ( - <> - - - - - - - - - {binder.hasExtension("repos.sources.tree.row.right") && - - - {files.map((file: any) => ( - - ))} - -
{t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.commitDate")}{t("sources.file-tree.description")}} -
- {tree.truncated &&

TRUNCATED

} - + + + + + + + + + {binder.hasExtension("repos.sources.tree.row.right") && + + + {files.map((file: any) => ( + + ))} + +
{t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.commitDate")}{t("sources.file-tree.description")}} +
); } return {t("sources.noSources")}; @@ -164,24 +184,37 @@ class FileTree extends React.Component { const mapDispatchToProps = (dispatch: any, ownProps: Props) => { const { repository, revision, path } = ownProps; - const updateSources = () => dispatch(fetchSources(repository, revision, path, false)); - - return { updateSources }; + return { + updateSources: (hunk: number) => dispatch(fetchSources(repository, revision, path, false, hunk)), + fetchSources: (repository: Repository, revision: string, path: string, hunk: number) => { + dispatch(fetchSources(repository, revision, path, true, hunk)); + } + }; }; const mapStateToProps = (state: any, ownProps: Props) => { const { repository, revision, path } = ownProps; - const loading = isFetchSourcesPending(state, repository, revision, path); - const error = getFetchSourcesFailure(state, repository, revision, path); - const tree = getSources(state, repository, revision, path); + const loading = isFetchSourcesPending(state, repository, revision, path, 0); + const error = getFetchSourcesFailure(state, repository, revision, path, 0); + const hunkCount = getHunkCount(state, repository, revision, path); + const hunks = []; + for (let i = 0; i < hunkCount; ++i) { + console.log(`getting data for hunk ${i}`); + const tree = getSources(state, repository, revision, path, i); + const loading = isFetchSourcesPending(state, repository, revision, path, i); + hunks.push({ + tree, + loading + }); + } return { revision, path, loading, error, - tree + hunks }; }; diff --git a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts index 2c7413f7ea..51c5eeb0e1 100644 --- a/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts +++ b/scm-ui/ui-webapp/src/repos/sources/modules/sources.ts @@ -10,34 +10,36 @@ export const FETCH_UPDATES_PENDING = `${FETCH_SOURCES}_UPDATE_PENDING`; export const FETCH_SOURCES_SUCCESS = `${FETCH_SOURCES}_${types.SUCCESS_SUFFIX}`; export const FETCH_SOURCES_FAILURE = `${FETCH_SOURCES}_${types.FAILURE_SUFFIX}`; -export function fetchSources(repository: Repository, revision: string, path: string, initialLoad = true) { +export function fetchSources(repository: Repository, revision: string, path: string, initialLoad = true, hunk = 0) { return function(dispatch: any, getState: () => any) { const state = getState(); if ( - isFetchSourcesPending(state, repository, revision, path) || - isUpdateSourcePending(state, repository, revision, path) + isFetchSourcesPending(state, repository, revision, path, hunk) || + isUpdateSourcePending(state, repository, revision, path, hunk) ) { return; } if (initialLoad) { - dispatch(fetchSourcesPending(repository, revision, path)); + dispatch(fetchSourcesPending(repository, revision, path, hunk)); } else { - dispatch(updateSourcesPending(repository, revision, path, getSources(state, repository, revision, path))); + dispatch( + updateSourcesPending(repository, revision, path, hunk, getSources(state, repository, revision, path, hunk)) + ); } return apiClient - .get(createUrl(repository, revision, path)) + .get(createUrl(repository, revision, path, hunk)) .then(response => response.json()) .then((sources: File) => { - dispatch(fetchSourcesSuccess(repository, revision, path, sources)); + dispatch(fetchSourcesSuccess(repository, revision, path, hunk, sources)); }) .catch(err => { - dispatch(fetchSourcesFailure(repository, revision, path, err)); + dispatch(fetchSourcesFailure(repository, revision, path, hunk, err)); }); }; } -function createUrl(repository: Repository, revision: string, path: string) { +function createUrl(repository: Repository, revision: string, path: string, hunk: number) { const base = (repository._links.sources as Link).href; if (!revision && !path) { return base; @@ -45,13 +47,14 @@ function createUrl(repository: Repository, revision: string, path: string) { // TODO handle trailing slash const pathDefined = path ? path : ""; - return `${base}${encodeURIComponent(revision)}/${pathDefined}`; + return `${base}${encodeURIComponent(revision)}/${pathDefined}?hunk=${hunk}`; } -export function fetchSourcesPending(repository: Repository, revision: string, path: string): Action { +export function fetchSourcesPending(repository: Repository, revision: string, path: string, hunk: number): Action { return { type: FETCH_SOURCES_PENDING, - itemId: createItemId(repository, revision, path) + itemId: createItemId(repository, revision, path), + payload: { hunk, pending: true, sources: {} } }; } @@ -59,24 +62,37 @@ export function updateSourcesPending( repository: Repository, revision: string, path: string, + hunk: number, currentSources: any ): Action { return { type: FETCH_UPDATES_PENDING, - payload: { updatePending: true, sources: currentSources }, + payload: { hunk, updatePending: true, sources: currentSources }, itemId: createItemId(repository, revision, path) }; } -export function fetchSourcesSuccess(repository: Repository, revision: string, path: string, sources: File) { +export function fetchSourcesSuccess( + repository: Repository, + revision: string, + path: string, + hunk: number, + sources: File +) { return { type: FETCH_SOURCES_SUCCESS, - payload: { updatePending: false, sources }, + payload: { hunk, pending: false, updatePending: false, sources }, itemId: createItemId(repository, revision, path) }; } -export function fetchSourcesFailure(repository: Repository, revision: string, path: string, error: Error): Action { +export function fetchSourcesFailure( + repository: Repository, + revision: string, + path: string, + hunk: number, + error: Error +): Action { return { type: FETCH_SOURCES_FAILURE, payload: error, @@ -99,9 +115,14 @@ export default function reducer( } ): any { if (action.itemId && (action.type === FETCH_SOURCES_SUCCESS || action.type === FETCH_UPDATES_PENDING)) { + console.log("adding payload to " + action.itemId + "/" + action.payload.hunk); return { ...state, - [action.itemId]: action.payload + [action.itemId + "/hunkCount"]: action.payload.hunk + 1, + [action.itemId + "/" + action.payload.hunk]: { + sources: action.payload.sources, + loading: false + } }; } return state; @@ -110,7 +131,7 @@ export default function reducer( // selectors export function isDirectory(state: any, repository: Repository, revision: string, path: string): boolean { - const currentFile = getSources(state, repository, revision, path); + const currentFile = getSources(state, repository, revision, path, 0); if (currentFile && !currentFile.directory) { return false; } else { @@ -118,31 +139,53 @@ export function isDirectory(state: any, repository: Repository, revision: string } } +export function getHunkCount(state: any, repository: Repository, revision: string | undefined, path: string): number { + if (state.sources) { + const count = state.sources[createItemId(repository, revision, path) + "/hunkCount"]; + return count ? count : 0; + } + return 0; +} + export function getSources( state: any, repository: Repository, revision: string | undefined, - path: string + path: string, + hunk: number ): File | null | undefined { if (state.sources) { - return state.sources[createItemId(repository, revision, path)]?.sources; + return state.sources[createItemId(repository, revision, path) + "/" + hunk]?.sources; } return null; } -export function isFetchSourcesPending(state: any, repository: Repository, revision: string, path: string): boolean { +export function isFetchSourcesPending( + state: any, + repository: Repository, + revision: string, + path: string, + hunk: number +): boolean { return state && isPending(state, FETCH_SOURCES, createItemId(repository, revision, path)); } -function isUpdateSourcePending(state: any, repository: Repository, revision: string, path: string): boolean { - return state?.sources && state.sources[createItemId(repository, revision, path)]?.updatePending; +export function isUpdateSourcePending( + state: any, + repository: Repository, + revision: string, + path: string, + hunk: number +): boolean { + return state?.sources && state.sources[createItemId(repository, revision, path) + "/" + hunk]?.updatePending; } export function getFetchSourcesFailure( state: any, repository: Repository, revision: string, - path: string + path: string, + hunk: number ): Error | null | undefined { return getFailure(state, FETCH_SOURCES, createItemId(repository, revision, path)); }