mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
New Fullscreen component
This commit is contained in:
50
scm-ui/ui-components/src/buttons/OpenInFullscreenButton.tsx
Normal file
50
scm-ui/ui-components/src/buttons/OpenInFullscreenButton.tsx
Normal 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;
|
||||||
@@ -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";
|
||||||
|
|||||||
52
scm-ui/ui-components/src/modals/FullscreenModal.tsx
Normal file
52
scm-ui/ui-components/src/modals/FullscreenModal.tsx
Normal 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;
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user