Skip to content

Commit

Permalink
Merge pull request #68 from kleros/feat/file-attachment-display
Browse files Browse the repository at this point in the history
Feat/file attachment display
  • Loading branch information
kemuru authored Jun 25, 2024
2 parents 9a56ba0 + 5473436 commit 60495b2
Show file tree
Hide file tree
Showing 16 changed files with 774 additions and 14 deletions.
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@cyntler/react-doc-viewer": "^1.16.3",
"@filebase/client": "^0.0.5",
"@kleros/ui-components-library": "^2.12.0",
"@middy/core": "^5.3.5",
Expand Down
2 changes: 2 additions & 0 deletions web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Layout from "layout/index";
import NewTransaction from "./pages/NewTransaction";
import MyTransactions from "./pages/MyTransactions";
import { NewTransactionProvider } from "./context/NewTransactionContext";
import AttachmentDisplay from "./pages/AttachmentDisplay";

const App: React.FC = () => {
return (
Expand All @@ -28,6 +29,7 @@ const App: React.FC = () => {
<Route index element={<Navigate to="new-transaction" replace />} />
<Route path="new-transaction/*" element={<NewTransaction />} />
<Route path="transactions/*" element={<MyTransactions />} />
<Route path="attachment/*" element={<AttachmentDisplay />} />
<Route path="*" element={<h1>404 not found</h1>} />
</Route>
</SentryRoutes>
Expand Down
10 changes: 10 additions & 0 deletions web/src/assets/svgs/icons/arrow-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions web/src/assets/svgs/icons/new-tab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions web/src/assets/svgs/icons/paperclip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions web/src/components/FileViewer/Viewers/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import styled from "styled-components";

import { type DocRenderer } from "@cyntler/react-doc-viewer";
import ReactMarkdown from "react-markdown";

const Container = styled.div`
padding: 16px;
`;

const StyledMarkdown = styled(ReactMarkdown)`
background-color: ${({ theme }) => theme.whiteBackground};
a {
font-size: 16px;
}
code {
color: ${({ theme }) => theme.secondaryText};
}
`;

const MarkdownRenderer: DocRenderer = ({ mainState: { currentDocument } }) => {
if (!currentDocument) return null;
const base64String = (currentDocument.fileData as string).split(",")[1];

// Decode the base64 string
const decodedData = atob(base64String);

return (
<Container id="md-renderer">
<StyledMarkdown>{decodedData}</StyledMarkdown>
</Container>
);
};

MarkdownRenderer.fileTypes = ["md", "text/plain"];
MarkdownRenderer.weight = 1;

export default MarkdownRenderer;
53 changes: 53 additions & 0 deletions web/src/components/FileViewer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import styled from "styled-components";

import DocViewer, { DocViewerRenderers } from "@cyntler/react-doc-viewer";

import "@cyntler/react-doc-viewer/dist/index.css";

import MarkdownRenderer from "./Viewers/MarkdownViewer";
import { customScrollbar } from "styles/customScrollbar";

const Wrapper = styled.div`
background-color: ${({ theme }) => theme.whiteBackground};
border-radius: 3px;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.06);
max-height: 1050px;
overflow: scroll;
${customScrollbar}
`;

const StyledDocViewer = styled(DocViewer)`
background-color: ${({ theme }) => theme.whiteBackground} !important;
`;

/**
* @description this viewer supports loading multiple files, it can load urls, local files, etc
* @param url The url of the file to be displayed
* @returns renders the file
*/
const FileViewer: React.FC<{ url: string }> = ({ url }) => {
const docs = [{ uri: url }];
return (
<Wrapper className="file-viewer-wrapper">
<StyledDocViewer
documents={docs}
pluginRenderers={[...DocViewerRenderers, MarkdownRenderer]}
config={{
header: {
disableHeader: true,
disableFileName: true,
},
pdfZoom: {
defaultZoom: 0.8,
zoomJump: 0.1,
},
pdfVerticalScrollByDefault: true, // false as default
}}
/>
</Wrapper>
);
};

export default FileViewer;
49 changes: 49 additions & 0 deletions web/src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from "react";
import styled, { type CSSProperties, keyframes } from "styled-components";

import KlerosIcon from "svgs/icons/kleros.svg";

type Width = CSSProperties["width"];
type Height = CSSProperties["height"];

const breathing = keyframes`
0% {
transform: scale(1);
}
50% {
transform: scale(1.3);
}
100% {
transform: scale(1);
}
`;

const StyledKlerosIcon = styled(KlerosIcon)`
path {
fill: ${({ theme }) => theme.klerosUIComponentsStroke};
}
animation: ${breathing} 2s ease-out infinite normal;
`;

const Container = styled.div<{ width?: Width; height?: Height }>`
width: ${({ width }) => width ?? "100%"};
height: ${({ height }) => height ?? "100%"};
`;

interface ILoader {
width?: Width;
height?: Height;
className?: string;
}

const Loader: React.FC<ILoader> = ({ width, height, className }) => {
return (
<Container {...{ width, height, className }}>
<StyledKlerosIcon />
</Container>
);
};

export default Loader;
10 changes: 6 additions & 4 deletions web/src/components/PreviewCard/Terms/AttachedFile.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { responsiveSize } from "styles/responsiveSize";
import AttachmentIcon from "svgs/icons/attachment.svg";
import { responsiveSize } from "styles/responsiveSize";
import { getIpfsUrl } from "utils/getIpfsUrl";

const StyledA = styled.a`
const StyledA = styled(Link)`
display: flex;
gap: ${responsiveSize(5, 6)};
> svg {
width: 16px;
fill: ${({ theme }) => theme.primaryBlue};
Expand All @@ -18,10 +20,10 @@ interface IAttachedFile {
}

const AttachedFile: React.FC<IAttachedFile> = ({ extraDescriptionUri }) => {
const href = extraDescriptionUri && getIpfsUrl(extraDescriptionUri);
const uri = extraDescriptionUri && getIpfsUrl(extraDescriptionUri);

return extraDescriptionUri ? (
<StyledA href={href} target="_blank" rel="noreferrer">
<StyledA to={`/attachment/?url=${uri}`}>
<AttachmentIcon />
View Attached File
</StyledA>
Expand Down
4 changes: 2 additions & 2 deletions web/src/layout/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import DesktopHeader from "./DesktopHeader";

const Container = styled.div`
position: sticky;
z-index: 1;
z-index: 10;
top: 0;
width: 100%;
height: 64px;
Expand Down Expand Up @@ -33,4 +33,4 @@ const Header: React.FC = () => {
);
};

export default Header;
export default Header;
73 changes: 73 additions & 0 deletions web/src/pages/AttachmentDisplay/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import styled from "styled-components";

import { useNavigate } from "react-router-dom";

import { Button } from "@kleros/ui-components-library";

import Arrow from "svgs/icons/arrow-left.svg";
import PaperClip from "svgs/icons/paperclip.svg";

import { responsiveSize } from "styles/responsiveSize";

const Container = styled.div`
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 38px;
`;

const TitleContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
`;

const Title = styled.h1`
margin: 0px;
font-size: ${responsiveSize(16, 24)};
`;

const StyledPaperClip = styled(PaperClip)`
width: ${responsiveSize(16, 24)};
height: ${responsiveSize(16, 24)};
path {
fill: ${({ theme }) => theme.primaryPurple};
}
`;

const StyledButton = styled(Button)`
background-color: transparent;
padding: 0;
.button-text {
color: ${({ theme }) => theme.primaryBlue};
font-weight: 400;
}
.button-svg {
path {
fill: ${({ theme }) => theme.primaryBlue};
}
}
:focus,
:hover {
background-color: transparent;
}
`;

const Header: React.FC = () => {
const navigate = useNavigate();

return (
<Container>
<TitleContainer>
<StyledPaperClip />
<Title>Attachment File</Title>{" "}
</TitleContainer>
<StyledButton text="Return" Icon={Arrow} onClick={() => navigate(-1)} />
</Container>
);
};

export default Header;
Loading

0 comments on commit 60495b2

Please sign in to comment.