mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 14:05:44 +01:00
merge repository heads
This commit is contained in:
@@ -101,7 +101,9 @@
|
||||
"length": "Größe",
|
||||
"lastModified": "Zuletzt bearbeitet",
|
||||
"description": "Beschreibung",
|
||||
"branch": "Branch"
|
||||
"branch": "Branch",
|
||||
"notYetComputed": "Noch nicht berechnet; Der Wert wird in Kürze aktualisiert",
|
||||
"computationAborted": "Die Berechnung dauert zu lange und wurde abgebrochen"
|
||||
},
|
||||
"content": {
|
||||
"historyButton": "History",
|
||||
|
||||
@@ -101,7 +101,9 @@
|
||||
"length": "Length",
|
||||
"lastModified": "Last modified",
|
||||
"description": "Description",
|
||||
"branch": "Branch"
|
||||
"branch": "Branch",
|
||||
"notYetComputed": "Not yet computed, will be updated in a short while",
|
||||
"computationAborted": "The computation took too long and was aborted"
|
||||
},
|
||||
"content": {
|
||||
"historyButton": "History",
|
||||
|
||||
@@ -7,7 +7,7 @@ import styled from "styled-components";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import { Repository, File } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components";
|
||||
import { getFetchSourcesFailure, isFetchSourcesPending, getSources } from "../modules/sources";
|
||||
import { getFetchSourcesFailure, isFetchSourcesPending, getSources, fetchSources } from "../modules/sources";
|
||||
import FileTreeLeaf from "./FileTreeLeaf";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
@@ -19,10 +19,16 @@ type Props = WithTranslation & {
|
||||
path: string;
|
||||
baseUrl: string;
|
||||
|
||||
updateSources: () => void;
|
||||
|
||||
// context props
|
||||
match: any;
|
||||
};
|
||||
|
||||
type State = {
|
||||
stoppableUpdateHandler?: number;
|
||||
};
|
||||
|
||||
const FixedWidthTh = styled.th`
|
||||
width: 16px;
|
||||
`;
|
||||
@@ -39,7 +45,28 @@ export function findParent(path: string) {
|
||||
return "";
|
||||
}
|
||||
|
||||
class FileTree extends React.Component<Props> {
|
||||
class FileTree extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.state.stoppableUpdateHandler) {
|
||||
clearTimeout(this.state.stoppableUpdateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error, loading, tree } = this.props;
|
||||
|
||||
@@ -106,7 +133,7 @@ class FileTree extends React.Component<Props> {
|
||||
<FixedWidthTh />
|
||||
<th>{t("sources.file-tree.name")}</th>
|
||||
<th className="is-hidden-mobile">{t("sources.file-tree.length")}</th>
|
||||
<th className="is-hidden-mobile">{t("sources.file-tree.lastModified")}</th>
|
||||
<th className="is-hidden-mobile">{t("sources.file-tree.commitDate")}</th>
|
||||
<th className="is-hidden-touch">{t("sources.file-tree.description")}</th>
|
||||
{binder.hasExtension("repos.sources.tree.row.right") && <th className="is-hidden-mobile" />}
|
||||
</tr>
|
||||
@@ -123,6 +150,14 @@ class FileTree extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: Props) => {
|
||||
const { repository, revision, path } = ownProps;
|
||||
|
||||
const updateSources = () => dispatch(fetchSources(repository, revision, path, false));
|
||||
|
||||
return { updateSources };
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
const { repository, revision, path } = ownProps;
|
||||
|
||||
@@ -141,5 +176,8 @@ const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps)
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
)(withTranslation("repos")(FileTree));
|
||||
|
||||
@@ -4,10 +4,12 @@ import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { File } from "@scm-manager/ui-types";
|
||||
import { DateFromNow, FileSize } from "@scm-manager/ui-components";
|
||||
import { DateFromNow, FileSize, Tooltip } from "@scm-manager/ui-components";
|
||||
import FileIcon from "./FileIcon";
|
||||
import { Icon } from "@scm-manager/ui-components/src";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
type Props = WithTranslation & {
|
||||
file: File;
|
||||
baseUrl: string;
|
||||
};
|
||||
@@ -35,7 +37,7 @@ export function createLink(base: string, file: File) {
|
||||
return link;
|
||||
}
|
||||
|
||||
export default class FileTreeLeaf extends React.Component<Props> {
|
||||
class FileTreeLeaf extends React.Component<Props> {
|
||||
createLink = (file: File) => {
|
||||
return createLink(this.props.baseUrl, file);
|
||||
};
|
||||
@@ -62,20 +64,42 @@ export default class FileTreeLeaf extends React.Component<Props> {
|
||||
return <Link to={this.createLink(file)}>{file.name}</Link>;
|
||||
};
|
||||
|
||||
contentIfPresent = (file: File, attribute: string, content: (file: File) => any) => {
|
||||
const { t } = this.props;
|
||||
if (file.hasOwnProperty(attribute)) {
|
||||
return content(file);
|
||||
} else if (file.computationAborted) {
|
||||
return (
|
||||
<Tooltip location="top" message={t("sources.file-tree.computationAborted")}>
|
||||
<Icon name={"question-circle"} />
|
||||
</Tooltip>
|
||||
);
|
||||
} else if (file.partialResult) {
|
||||
return (
|
||||
<Tooltip location="top" message={t("sources.file-tree.notYetComputed")}>
|
||||
<Icon name={"hourglass"} />
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return content(file);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { file } = this.props;
|
||||
|
||||
const fileSize = file.directory ? "" : <FileSize bytes={file.length} />;
|
||||
const renderFileSize = (file: File) => <FileSize bytes={file.length} />;
|
||||
const renderCommitDate = (file: File) => <DateFromNow date={file.commitDate} />;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.createFileIcon(file)}</td>
|
||||
<MinWidthTd className="is-word-break">{this.createFileName(file)}</MinWidthTd>
|
||||
<NoWrapTd className="is-hidden-mobile">{fileSize}</NoWrapTd>
|
||||
<td className="is-hidden-mobile">
|
||||
<DateFromNow date={file.lastModified} />
|
||||
</td>
|
||||
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>{file.description}</MinWidthTd>
|
||||
<NoWrapTd className="is-hidden-mobile">{file.directory ? "" : this.contentIfPresent(file, "length", renderFileSize)}</NoWrapTd>
|
||||
<td className="is-hidden-mobile">{this.contentIfPresent(file, "commitDate", renderCommitDate)}</td>
|
||||
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>
|
||||
{this.contentIfPresent(file, "description", file => file.description)}
|
||||
</MinWidthTd>
|
||||
{binder.hasExtension("repos.sources.tree.row.right") && (
|
||||
<td className="is-hidden-mobile">
|
||||
{!file.directory && (
|
||||
@@ -93,3 +117,5 @@ export default class FileTreeLeaf extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("repos")(FileTreeLeaf);
|
||||
|
||||
@@ -115,7 +115,7 @@ class Content extends React.Component<Props, State> {
|
||||
showMoreInformation() {
|
||||
const collapsed = this.state.collapsed;
|
||||
const { file, revision, t, repository } = this.props;
|
||||
const date = <DateFromNow date={file.lastModified} />;
|
||||
const date = <DateFromNow date={file.commitDate} />;
|
||||
const description = file.description ? (
|
||||
<p>
|
||||
{file.description.split("\n").map((item, key) => {
|
||||
|
||||
@@ -49,10 +49,8 @@ const collection = {
|
||||
name: "src",
|
||||
path: "src",
|
||||
directory: true,
|
||||
description: "",
|
||||
length: 176,
|
||||
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
|
||||
lastModified: "",
|
||||
subRepository: undefined,
|
||||
_links: {
|
||||
self: {
|
||||
@@ -71,7 +69,7 @@ const collection = {
|
||||
description: "bump version",
|
||||
length: 780,
|
||||
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
|
||||
lastModified: "2017-07-31T11:17:19Z",
|
||||
commitDate: "2017-07-31T11:17:19Z",
|
||||
subRepository: undefined,
|
||||
_links: {
|
||||
self: {
|
||||
@@ -127,7 +125,7 @@ describe("sources fetch", () => {
|
||||
{
|
||||
type: FETCH_SOURCES_SUCCESS,
|
||||
itemId: "scm/core/_/",
|
||||
payload: collection
|
||||
payload: { updatePending: false, sources: collection }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -148,7 +146,7 @@ describe("sources fetch", () => {
|
||||
{
|
||||
type: FETCH_SOURCES_SUCCESS,
|
||||
itemId: "scm/core/abc/src",
|
||||
payload: collection
|
||||
payload: { updatePending: false, sources: collection }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -182,14 +180,14 @@ describe("reducer tests", () => {
|
||||
|
||||
it("should store the collection, without revision and path", () => {
|
||||
const expectedState = {
|
||||
"scm/core/_/": collection
|
||||
"scm/core/_/": { updatePending: false, sources: collection }
|
||||
};
|
||||
expect(reducer({}, fetchSourcesSuccess(repository, "", "", collection))).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it("should store the collection, with revision and path", () => {
|
||||
const expectedState = {
|
||||
"scm/core/abc/src/main": collection
|
||||
"scm/core/abc/src/main": { updatePending: false, sources: collection }
|
||||
};
|
||||
expect(reducer({}, fetchSourcesSuccess(repository, "abc", "src/main", collection))).toEqual(expectedState);
|
||||
});
|
||||
@@ -200,7 +198,7 @@ describe("selector tests", () => {
|
||||
const state = {
|
||||
sources: {
|
||||
"scm/core/abc/src/main/package.json": {
|
||||
noDirectory
|
||||
sources: {noDirectory}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -223,7 +221,9 @@ describe("selector tests", () => {
|
||||
it("should return the source collection without revision and path", () => {
|
||||
const state = {
|
||||
sources: {
|
||||
"scm/core/_/": collection
|
||||
"scm/core/_/": {
|
||||
sources: collection
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(getSources(state, repository, "", "")).toBe(collection);
|
||||
@@ -232,7 +232,9 @@ describe("selector tests", () => {
|
||||
it("should return the source collection with revision and path", () => {
|
||||
const state = {
|
||||
sources: {
|
||||
"scm/core/abc/src/main": collection
|
||||
"scm/core/abc/src/main": {
|
||||
sources: collection
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(getSources(state, repository, "abc", "src/main")).toBe(collection);
|
||||
|
||||
@@ -9,13 +9,25 @@ export const FETCH_SOURCES_PENDING = `${FETCH_SOURCES}_${types.PENDING_SUFFIX}`;
|
||||
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) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(fetchSourcesPending(repository, revision, path));
|
||||
export function fetchSources(repository: Repository, revision: string, path: string, initialLoad = true) {
|
||||
return function(dispatch: any, getState: () => any) {
|
||||
const state = getState();
|
||||
if (
|
||||
isFetchSourcesPending(state, repository, revision, path) ||
|
||||
isUpdateSourcePending(state, repository, revision, path)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (initialLoad) {
|
||||
dispatch(fetchSourcesPending(repository, revision, path));
|
||||
} else {
|
||||
dispatch(updateSourcesPending(repository, revision, path, getSources(state, repository, revision, path)));
|
||||
}
|
||||
return apiClient
|
||||
.get(createUrl(repository, revision, path))
|
||||
.then(response => response.json())
|
||||
.then(sources => {
|
||||
.then((sources: File) => {
|
||||
dispatch(fetchSourcesSuccess(repository, revision, path, sources));
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -42,10 +54,23 @@ export function fetchSourcesPending(repository: Repository, revision: string, pa
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSourcesPending(
|
||||
repository: Repository,
|
||||
revision: string,
|
||||
path: string,
|
||||
currentSources: any
|
||||
): Action {
|
||||
return {
|
||||
type: "UPDATE_PENDING",
|
||||
payload: { updatePending: true, sources: currentSources },
|
||||
itemId: createItemId(repository, revision, path)
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchSourcesSuccess(repository: Repository, revision: string, path: string, sources: File) {
|
||||
return {
|
||||
type: FETCH_SOURCES_SUCCESS,
|
||||
payload: sources,
|
||||
payload: { updatePending: false, sources },
|
||||
itemId: createItemId(repository, revision, path)
|
||||
};
|
||||
}
|
||||
@@ -72,7 +97,7 @@ export default function reducer(
|
||||
type: "UNKNOWN"
|
||||
}
|
||||
): any {
|
||||
if (action.itemId && action.type === FETCH_SOURCES_SUCCESS) {
|
||||
if (action.itemId && (action.type === FETCH_SOURCES_SUCCESS || action.type === "UPDATE_PENDING")) {
|
||||
return {
|
||||
...state,
|
||||
[action.itemId]: action.payload
|
||||
@@ -99,13 +124,17 @@ export function getSources(
|
||||
path: string
|
||||
): File | null | undefined {
|
||||
if (state.sources) {
|
||||
return state.sources[createItemId(repository, revision, path)];
|
||||
return state.sources[createItemId(repository, revision, path)]?.sources;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isFetchSourcesPending(state: any, repository: Repository, revision: string, path: string): boolean {
|
||||
return isPending(state, FETCH_SOURCES, createItemId(repository, revision, path));
|
||||
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 getFetchSourcesFailure(
|
||||
|
||||
Reference in New Issue
Block a user