New Fullscreen component

This commit is contained in:
Florian Scholdei
2020-10-14 22:48:10 +02:00
parent a680b75f85
commit c366203d1f
9 changed files with 190 additions and 83 deletions

View File

@@ -0,0 +1,50 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import * as React from "react";
import { FC } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
type Props = {
onClick?: () => void;
};
const Button = styled.a`
width: 50px;
&:hover {
color: #33b2e8;
}
`;
const OpenInFullscreenButton: FC<Props> = ({ onClick }) => {
const [t] = useTranslation("repos");
return (
<Button title={t("diff.fullscreen.open")} className="button" onClick={onClick}>
<i className="fas fa-search-plus" />
</Button>
);
};
export default OpenInFullscreenButton;

View File

@@ -33,4 +33,5 @@ export { default as SubmitButton } from "./SubmitButton";
export { default as DownloadButton } from "./DownloadButton"; export { default as DownloadButton } from "./DownloadButton";
export { default as ButtonGroup } from "./ButtonGroup"; export { default as ButtonGroup } from "./ButtonGroup";
export { default as ButtonAddons } from "./ButtonAddons"; export { default as ButtonAddons } from "./ButtonAddons";
export { default as OpenInFullscreenButton } from "./OpenInFullscreenButton";
export { default as RemoveEntryOfTableButton } from "./RemoveEntryOfTableButton"; export { default as RemoveEntryOfTableButton } from "./RemoveEntryOfTableButton";

View File

@@ -0,0 +1,52 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import * as React from "react";
import { FC, ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Modal } from "./Modal";
import Button from "../buttons/Button";
import styled from "styled-components";
type Props = {
title: string;
closeFunction: () => void;
body: ReactNode;
active: boolean;
};
const FullSizedModal = styled(Modal)`
& .modal-card {
width: 98%;
max-height: 100vh;
}
`;
const FullscreenModal: FC<Props> = ({ title, closeFunction, body, active }) => {
const [t] = useTranslation("repos");
const footer = <Button label={t("diff.fullscreen.close")} action={closeFunction} color="grey" />;
return <FullSizedModal title={title} closeFunction={closeFunction} body={body} footer={footer} active={active} />;
};
export default FullscreenModal;

View File

@@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import * as React from "react"; import * as React from "react";
import {FC} from "react"; import { FC } from "react";
import classNames from "classnames"; import classNames from "classnames";
import usePortalRootElement from "../usePortalRootElement"; import usePortalRootElement from "../usePortalRootElement";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";

View File

@@ -26,3 +26,4 @@
export { default as ConfirmAlert, confirmAlert } from "./ConfirmAlert"; export { default as ConfirmAlert, confirmAlert } from "./ConfirmAlert";
export { default as Modal } from "./Modal"; export { default as Modal } from "./Modal";
export { default as FullscreenModal } from "./FullscreenModal";

View File

@@ -263,7 +263,11 @@
"expandLastBottomByLines": "Bis zu {{count}} weitere Zeilen laden", "expandLastBottomByLines": "Bis zu {{count}} weitere Zeilen laden",
"expandLastBottomComplete": "Alle verbleibenden Zeilen laden", "expandLastBottomComplete": "Alle verbleibenden Zeilen laden",
"expanding": "Zeilen werden geladen ...", "expanding": "Zeilen werden geladen ...",
"expansionFailed": "Fehler beim Laden der zusätzlichen Zeilen" "expansionFailed": "Fehler beim Laden der zusätzlichen Zeilen",
"fullscreen": {
"open": "In Vollbildansicht öffnen",
"close": "Schließen"
}
}, },
"fileUpload": { "fileUpload": {
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.", "clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",

View File

@@ -270,7 +270,11 @@
"expandLastBottomByLines": "load up to {{count}} more lines", "expandLastBottomByLines": "load up to {{count}} more lines",
"expandLastBottomComplete": "load all remaining lines", "expandLastBottomComplete": "load all remaining lines",
"expanding": "loading lines ...", "expanding": "loading lines ...",
"expansionFailed": "Error while loading additional lines" "expansionFailed": "Error while loading additional lines",
"fullscreen": {
"open": "Open in Fullscreen",
"close": "Close"
}
}, },
"fileUpload": { "fileUpload": {
"clickHere": "Click here to select your file", "clickHere": "Click here to select your file",

View File

@@ -21,87 +21,54 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React from "react"; import React, { FC, useEffect, useState } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { apiClient, ErrorNotification, Loading, SyntaxHighlighter } from "@scm-manager/ui-components"; import { apiClient, ErrorNotification, Loading, SyntaxHighlighter } from "@scm-manager/ui-components";
import { File, Link } from "@scm-manager/ui-types"; import { File, Link } from "@scm-manager/ui-types";
type Props = WithTranslation & { type Props = {
file: File; file: File;
language: string; language: string;
}; };
type State = { const SourcecodeViewer: FC<Props> = ({ file, language }) => {
content: string; const [content, setContent] = useState("");
error?: Error; const [error, setError] = useState<Error | undefined>(undefined);
loaded: boolean; const [loading, setLoading] = useState(true);
currentFileRevision: string; const [currentFileRevision, setCurrentFileRevision] = useState("");
};
class SourcecodeViewer extends React.Component<Props, State> { useEffect(() => {
constructor(props: Props) { function fetchContent() {
super(props); getContent((file._links.self as Link).href)
.then(content => {
setLoading(false);
setContent(content);
setCurrentFileRevision(file.revision);
})
.catch(error => {
setLoading(false);
setError(error);
});
}
this.state = {
content: "",
loaded: false,
currentFileRevision: ""
};
}
componentDidMount() {
this.fetchContentIfChanged();
}
componentDidUpdate() {
this.fetchContentIfChanged();
}
private fetchContentIfChanged() {
const { file } = this.props;
const { currentFileRevision } = this.state;
if (file.revision !== currentFileRevision) { if (file.revision !== currentFileRevision) {
this.fetchContent(); fetchContent();
} }
});
if (loading) {
return <Loading />;
} }
fetchContent = () => { if (error) {
const { file } = this.props; return <ErrorNotification error={error} />;
getContent((file._links.self as Link).href)
.then(content => {
this.setState({
content,
loaded: true,
currentFileRevision: file.revision
});
})
.catch(error => {
this.setState({
error,
loaded: true
});
});
};
render() {
const { content, error, loaded } = this.state;
const language = this.props.language;
if (error) {
return <ErrorNotification error={error} />;
}
if (!loaded) {
return <Loading />;
}
if (!content) {
return null;
}
return <SyntaxHighlighter language={getLanguage(language)} value={content} />;
} }
}
if (!content) {
return null;
}
return <SyntaxHighlighter language={getLanguage(language)} value={content} />;
};
export function getLanguage(language: string) { export function getLanguage(language: string) {
return language.toLowerCase(); return language.toLowerCase();
@@ -111,4 +78,4 @@ export function getContent(url: string) {
return apiClient.get(url).then(response => response.text()); return apiClient.get(url).then(response => response.text());
} }
export default withTranslation("repos")(SourcecodeViewer); export default SourcecodeViewer;

View File

@@ -28,7 +28,14 @@ import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { File, Repository } from "@scm-manager/ui-types"; import { File, Repository } from "@scm-manager/ui-types";
import { DateFromNow, ErrorNotification, FileSize, Icon } from "@scm-manager/ui-components"; import {
DateFromNow,
ErrorNotification,
FileSize,
Icon,
OpenInFullscreenButton,
FullscreenModal
} from "@scm-manager/ui-components";
import { getSources } from "../modules/sources"; import { getSources } from "../modules/sources";
import FileButtonAddons from "../components/content/FileButtonAddons"; import FileButtonAddons from "../components/content/FileButtonAddons";
import SourcesView from "./SourcesView"; import SourcesView from "./SourcesView";
@@ -48,6 +55,7 @@ type State = {
collapsed: boolean; collapsed: boolean;
selected: SourceViewSelection; selected: SourceViewSelection;
errorFromExtension?: Error; errorFromExtension?: Error;
showFullscreenModal: boolean;
}; };
const Header = styled.div` const Header = styled.div`
@@ -82,6 +90,12 @@ const LighterGreyBackgroundTable = styled.table`
background-color: #fbfbfb; background-color: #fbfbfb;
`; `;
const BorderLessDiv = styled.div`
margin: -1.25rem;
border: none;
box-shadow: none;
`;
export type SourceViewSelection = "source" | "history" | "annotations"; export type SourceViewSelection = "source" | "history" | "annotations";
class Content extends React.Component<Props, State> { class Content extends React.Component<Props, State> {
@@ -90,7 +104,8 @@ class Content extends React.Component<Props, State> {
this.state = { this.state = {
collapsed: true, collapsed: true,
selected: "source" selected: "source",
showFullscreenModal: false
}; };
} }
@@ -100,6 +115,18 @@ class Content extends React.Component<Props, State> {
})); }));
}; };
openModal = () => {
this.setState({
showFullscreenModal: true
});
};
closeModal = () => {
this.setState({
showFullscreenModal: false
});
};
handleExtensionError = (error: Error) => { handleExtensionError = (error: Error) => {
this.setState({ this.setState({
errorFromExtension: error errorFromExtension: error
@@ -129,6 +156,7 @@ class Content extends React.Component<Props, State> {
</div> </div>
<div className="buttons is-grouped"> <div className="buttons is-grouped">
{selector} {selector}
<OpenInFullscreenButton onClick={this.openModal} />
<ExtensionPoint <ExtensionPoint
name="repos.sources.content.actionbar" name="repos.sources.content.actionbar"
props={{ props={{
@@ -209,25 +237,19 @@ class Content extends React.Component<Props, State> {
render() { render() {
const { file, revision, repository, path, breadcrumb } = this.props; const { file, revision, repository, path, breadcrumb } = this.props;
const { selected, errorFromExtension } = this.state; const { selected, errorFromExtension, showFullscreenModal } = this.state;
const header = this.showHeader(); const header = this.showHeader();
let content; let content;
switch (selected) { switch (selected) {
case "source": case "source":
content = ( content = <SourcesView revision={revision} file={file} repository={repository} path={path} />;
<SourcesView revision={revision} file={file} repository={repository} path={path}/>
);
break; break;
case "history": case "history":
content = ( content = <HistoryView file={file} repository={repository} />;
<HistoryView file={file} repository={repository}/>
);
break; break;
case "annotations": case "annotations":
content = ( content = <AnnotateView file={file} repository={repository} />;
<AnnotateView file={file} repository={repository} />
);
} }
const moreInformation = this.showMoreInformation(); const moreInformation = this.showMoreInformation();
@@ -238,6 +260,12 @@ class Content extends React.Component<Props, State> {
<Header>{header}</Header> <Header>{header}</Header>
{moreInformation} {moreInformation}
{content} {content}
<FullscreenModal
title={file?.name}
closeFunction={() => this.closeModal()}
body={<BorderLessDiv className="panel">{content}</BorderLessDiv>}
active={showFullscreenModal}
/>
</div> </div>
{errorFromExtension && <ErrorNotification error={errorFromExtension} />} {errorFromExtension && <ErrorNotification error={errorFromExtension} />}
</div> </div>