Merge pull request 'fix/article-interaction-buttons' (#155) from fix/article-interaction-buttons into develop

Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/155
This commit is contained in:
Daniel Weissmall 2022-10-18 10:43:30 +00:00
commit 35900bec5d
10 changed files with 230 additions and 50 deletions

65
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@reduxjs/toolkit": "^1.8.3", "@reduxjs/toolkit": "^1.8.3",
"@types/node": "^16.11.47", "@types/node": "^16.11.47",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@uiw/react-md-editor": "^3.18.1", "@uiw/react-md-editor": "^3.18.1",
"axios": "^0.27.2", "axios": "^0.27.2",
@ -25,6 +26,7 @@
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hotkeys": "^2.0.0", "react-hotkeys": "^2.0.0",
"react-i18next": "^11.18.3", "react-i18next": "^11.18.3",
@ -11479,6 +11481,14 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-copy-to-clipboard": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
"integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "18.0.6", "version": "18.0.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
@ -15021,6 +15031,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/copy-to-clipboard": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
"dependencies": {
"toggle-selection": "^1.0.6"
}
},
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.24.1", "version": "3.24.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
@ -30025,6 +30043,18 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^15.3.0 || 16 || 17 || 18"
}
},
"node_modules/react-dev-utils": { "node_modules/react-dev-utils": {
"version": "12.0.1", "version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@ -37574,6 +37604,11 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -48448,6 +48483,14 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-copy-to-clipboard": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
"integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": { "@types/react-dom": {
"version": "18.0.6", "version": "18.0.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
@ -51233,6 +51276,14 @@
"integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==",
"dev": true "dev": true
}, },
"copy-to-clipboard": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
"requires": {
"toggle-selection": "^1.0.6"
}
},
"core-js": { "core-js": {
"version": "3.24.1", "version": "3.24.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
@ -62246,6 +62297,15 @@
"whatwg-fetch": "^3.6.2" "whatwg-fetch": "^3.6.2"
} }
}, },
"react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"requires": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
}
},
"react-dev-utils": { "react-dev-utils": {
"version": "12.0.1", "version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@ -67797,6 +67857,11 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"toidentifier": { "toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

@ -7,6 +7,7 @@
"@reduxjs/toolkit": "^1.8.3", "@reduxjs/toolkit": "^1.8.3",
"@types/node": "^16.11.47", "@types/node": "^16.11.47",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@uiw/react-md-editor": "^3.18.1", "@uiw/react-md-editor": "^3.18.1",
"axios": "^0.27.2", "axios": "^0.27.2",
@ -20,6 +21,7 @@
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hotkeys": "^2.0.0", "react-hotkeys": "^2.0.0",
"react-i18next": "^11.18.3", "react-i18next": "^11.18.3",

View File

@ -62,7 +62,15 @@
}, },
"articlePage": { "articlePage": {
"abstract": "Введение", "abstract": "Введение",
"keywords": "Ключевые слова" "keywords": "Ключевые слова",
"interactionButtons":{
"abstract": "Развернуть",
"readFile": "Читать",
"download" : "Скачать",
"share" : "Поделиться",
"cite" : "Цитировать",
"copied": "Скопировано"
}
}, },
"navbar": { "navbar": {
"createNew": "Создать статью", "createNew": "Создать статью",
@ -124,5 +132,4 @@
"totalResults":"Всего найдено", "totalResults":"Всего найдено",
"nothingFound": "Ничего не найдено" "nothingFound": "Ничего не найдено"
} }
} }

View File

@ -13,14 +13,10 @@ import {
import classNames from "classnames"; import classNames from "classnames";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import Link from "components/typography/Link"; import Link from "components/typography/Link";
import { useTranslation } from "react-i18next";
import { ShareButton } from "./ArticleShareButton";
const interactionButtonsStore = [ const interactionButtonsStore = [
{
icon: <SVGFiletext />,
title: "Read file",
buttonEmphasis: "high",
iconClassName: "h-6 fill-white stroke-white",
},
{ {
icon: <SVGDownload />, icon: <SVGDownload />,
title: "Download", title: "Download",
@ -47,18 +43,22 @@ type ArticleButtonProps = {
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
emphasis?: "high" | "low"; emphasis?: "high" | "low";
articleID?: string, articleID?: string;
} & Omit<React.ComponentPropsWithoutRef<"button">, "">; } & Omit<React.ComponentPropsWithoutRef<"button">, "">;
export function ArticleInteractionButtons({ export function ArticleInteractionButtons({
isAbstractOpen = false, isAbstractOpen = false,
children, children,
openAbstract = () => { }, openAbstract = () => {},
className, className,
articleID, articleID,
emphasis = "high", //to change displaying of component emphasis = "high", //to change displaying of component
...props ...props
}: ArticleButtonProps) { }: ArticleButtonProps) {
const [t, i18next] = useTranslation("translation", {
keyPrefix: "articlePage.interactionButtons",
});
/* ----------------------------- Abstract Button ---------------------------- */
const abstractButton = ( const abstractButton = (
<Button <Button
emphasis="medium" emphasis="medium"
@ -66,45 +66,66 @@ export function ArticleInteractionButtons({
onClick={openAbstract} onClick={openAbstract}
> >
<Typography fontWeightVariant="bold" className="pr-2"> <Typography fontWeightVariant="bold" className="pr-2">
Abstract {t("abstract")}
</Typography> </Typography>
<Button.Icon> <Button.Icon>
{!isAbstractOpen ? <SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" /> : <SVGArrowUp className="w-4 fill-blue-700 stroke-blue-700" />} {!isAbstractOpen ? (
<SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" />
) : (
<SVGArrowUp className="w-4 fill-blue-700 stroke-blue-700" />
)}
</Button.Icon> </Button.Icon>
</Button> </Button>
); );
const fileInteractionButtons = interactionButtonsStore.map((button) => {
return ( /* ---------------------------- Read file button ---------------------------- */
button.title === 'Read file' ? const readFileButton = (
<Link <Link
to={`/article/content/${articleID}`}> to={`/article/content/${articleID}`}
<Button className="rounded active:outline focus:outline focus:outline-blue-400/10 outline-8 active:outline-blue-400/10"
emphasis={button.buttonEmphasis === "high" ? "high" : "low"} >
className="h-max px-2 mr-2" <div
> className="bg-blue-600
<Button.Icon> hover:bg-blue-500
{React.cloneElement(button.icon, { className: button.iconClassName })} active:bg-blue-700
</Button.Icon> focus:shadow-lg shadow-blue-500
{emphasis === "high" ? <Typography>{button.title}</Typography> : null} p-2 rounded
</Button> flex flex-row space-x-2
</Link> "
: >
<Button <SVGFiletext className="w-6 fill-white stroke-white" />
emphasis={button.buttonEmphasis === "high" ? "high" : "low"} {emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-white">{t("readFile")}</Typography>}
className="h-max px-2 mr-2" </div>
> </Link>
<Button.Icon> );
{React.cloneElement(button.icon, { className: button.iconClassName })} /* ----------------------------- Download button ---------------------------- */
</Button.Icon> const downLoadButton = (
{emphasis === "high" ? <Typography>{button.title}</Typography> : null} <Button emphasis="low" className="px-2 space-x-2">
</Button> <Button.Icon>
); <SVGDownload className="w-6 fill-gray-900 stroke-gray-900" />
}); </Button.Icon>
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("download")}</Typography>}
</Button>
);
/* ------------------------------- Cite button ------------------------------ */
const citeButton = (
<Button emphasis="low" className="px-2 space-x-2">
<Button.Icon>
<SVGCite className="w-6 fill-gray-900 stroke-gray-900" />
</Button.Icon>
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("cite")}</Typography>}
</Button>
);
return ( return (
<div className="flex flex-row"> <div className="flex flex-row space-x-2">
{emphasis === "low" && !children ? abstractButton : null} {emphasis != "high" && abstractButton}
{children ? children : fileInteractionButtons} {readFileButton}
{downLoadButton}
{citeButton}
<ShareButton emphasis={emphasis} linktoCopy={`/article/content/${articleID}`} />
</div> </div>
); );
} }

View File

@ -0,0 +1,87 @@
import { BASE_URL } from "core/httpClient";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "../../../Button/Button";
import { SVGCopy, SVGShare, SVGXmark } from "../../../icons";
import Typography from "../../../typography/Typography";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Popover } from "@headlessui/react";
type Props = {
emphasis?: "high" | "low";
linktoCopy?: string;
};
export function ShareButton({ emphasis, linktoCopy }: Props) {
const [t, i18next] = useTranslation("translation", {
keyPrefix: "articlePage.interactionButtons",
});
const [copied, setCopied] = useState(false);
const copyValue =
BASE_URL != undefined && linktoCopy != undefined
? BASE_URL + linktoCopy
: t("searchResults.nothingFound");
function onCopy() {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1500);
};
const handleFocus = (event: any) => event.target.select();
return (
<Popover className="relative">
<Popover.Button>
<Button emphasis="low" className="px-2 space-x-2">
<Button.Icon>
<SVGShare className="w-6 fill-gray-900 stroke-gray-900" />
</Button.Icon>
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("share")}</Typography>}
</Button>
</Popover.Button>
<Popover.Panel className="absolute z-10 -right-3/4 mt-3 pl-2 bg-white border-2 border-gray-300 shadow-lg rounded">
<div className="flex flex-row">
<input
className="focus:outline-none active:outline-none p-1 mr-1"
type="text"
readOnly
value={copyValue}
onFocus={handleFocus}
/>
<div className="relative">
<CopyToClipboard text={copyValue} onCopy={onCopy}>
<Button
emphasis="low"
className="items-center border-l-2 rounded-none "
>
<Button.Icon>
<SVGCopy className="w-5 fill-gray-900 stroke-gray-900" />
</Button.Icon>
</Button>
</CopyToClipboard>
{copied && (
<div className="absolute z-11 -bottom-full bg-gray-900 text-white px-2 py-1 rounded before:content[''] before:absolute before:bg-gray-900 before:rotate-45 before:w-3 before:h-3 before:-top-1 before:left-4 before:block">
{t("copied")}
</div>
)}
</div>
<Popover.Button>
<Button
emphasis="low"
className="items-center border-l-2 rounded-none "
>
<Button.Icon>
<SVGXmark className="w-5 fill-gray-900 stroke-gray-900" />
</Button.Icon>
</Button>
</Popover.Button>
</div>
</Popover.Panel>
</Popover>
);
}

View File

@ -47,10 +47,11 @@ export const ArticleSearchResult = ({ searchItem }: Props) => {
{searchItem.tags} {searchItem.tags}
</Article.Keywords> </Article.Keywords>
<Article.InteractionButtons <Article.InteractionButtons
className="py-2 " className="py-2"
emphasis="low" emphasis="low"
openAbstract={open} openAbstract={open}
isAbstractOpen={openAbstract} isAbstractOpen={openAbstract}
articleID={searchItem.id}
/> />
<Article.Description <Article.Description
emphasis="low" emphasis="low"

View File

@ -57,5 +57,5 @@ export default function Link({
{children} {children}
</a> </a>
); );
return <div>{link}</div>; return link;
} }

View File

@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL; export const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL;
export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL ?? ""; export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL ?? "";
const instance = axios.create({ const instance = axios.create({

View File

@ -3,9 +3,6 @@ import BaseLayout from "components/BaseLayout";
import { SearchSection } from "components/SearchSection"; import { SearchSection } from "components/SearchSection";
import { ColumnLayout } from "components/layouts/ThreeColumn/ColumnLayout"; import { ColumnLayout } from "components/layouts/ThreeColumn/ColumnLayout";
import { SearchResultSection } from "components/SearchResultsSection"; import { SearchResultSection } from "components/SearchResultsSection";
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel";
import { Loader } from "components/Loader/Loader";
import Fiter from "components/Filters/Filter"; import Fiter from "components/Filters/Filter";
export const SearchResultsPage = () => { export const SearchResultsPage = () => {
@ -14,7 +11,7 @@ export const SearchResultsPage = () => {
<SearchSection /> <SearchSection />
<ColumnLayout> <ColumnLayout>
<ColumnLayout.Left> <ColumnLayout.Left>
<div className="h-98 w-[300px]"></div> <div className="h-98 w-[300px] border rounded my-5"><Fiter /></div>
</ColumnLayout.Left> </ColumnLayout.Left>
<ColumnLayout.Main> <ColumnLayout.Main>
<SearchResultSection /> <SearchResultSection />

View File

@ -10,7 +10,7 @@ const searchEndpoint = "/papers/search";
async function search(request: string): Promise<SearchResults> { async function search(request: string): Promise<SearchResults> {
try { try {
const response = await integratorApiClient.get<SearchResultsDTO>( const response = await integratorApiClient.get<SearchResultsDTO>(
// searchEndpoint + `?query=` + request + `&limit=10&offset=0` // searchEndpoint + `?query=` + request
"https://run.mocky.io/v3/ea705665-2479-4039-8b81-412e011fc145" "https://run.mocky.io/v3/ea705665-2479-4039-8b81-412e011fc145"
); );
const dto = response.data; const dto = response.data;