mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-25 06:24:19 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
2678dc66c6
37 changed files with 509 additions and 319 deletions
40
nest_frontend/components/base/Badge.js
Normal file
40
nest_frontend/components/base/Badge.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./Badge.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
|
import ButtonSmallX from "./ButtonSmallX"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small colored badge.
|
||||||
|
*
|
||||||
|
* @param icon - The icon that the badge should have on the left.
|
||||||
|
* @param color - The color that the badge should have.
|
||||||
|
* @param onClickDelete - The action to perform when the X button is clicked. If undefined, the X button won't be shown.
|
||||||
|
* @param children - The text of the badge.
|
||||||
|
* @param className - Additional class(es) to append to the badge.
|
||||||
|
* @param props - Additional props to pass to the badge.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export default function Badge({ icon, color, onClickDelete, children, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.Badge, Style[`Badge${color}`], className)} {...props}>
|
||||||
|
<div className={Style.Icon}>
|
||||||
|
<FontAwesomeIcon icon={icon}/>
|
||||||
|
</div>
|
||||||
|
<div className={Style.Text}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
onClickDelete ?
|
||||||
|
<div>
|
||||||
|
<ButtonSmallX
|
||||||
|
onClick={onClickDelete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
38
nest_frontend/components/base/Badge.module.css
Normal file
38
nest_frontend/components/base/Badge.module.css
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
.Badge {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
gap: 5px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 25px;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.BadgeRed {
|
||||||
|
background-color: var(--bg-red);
|
||||||
|
color: var(--fg-red)
|
||||||
|
}
|
||||||
|
|
||||||
|
.BadgeYellow {
|
||||||
|
background-color: var(--bg-yellow);
|
||||||
|
color: var(--fg-yellow)
|
||||||
|
}
|
||||||
|
|
||||||
|
.BadgeGrey {
|
||||||
|
background-color: var(--bg-grey);
|
||||||
|
color: var(--fg-grey)
|
||||||
|
}
|
||||||
|
|
||||||
|
.BadgeGreen {
|
||||||
|
background-color: var(--bg-green);
|
||||||
|
color: var(--fg-green)
|
||||||
|
}
|
||||||
|
|
||||||
|
.Text {
|
||||||
|
max-width: 350px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Icon {
|
||||||
|
width: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
33
nest_frontend/components/base/BoxMap.js
Normal file
33
nest_frontend/components/base/BoxMap.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./BoxMap.module.css"
|
||||||
|
import BoxFull from "./BoxFull"
|
||||||
|
import { MapContainer, TileLayer } from "react-leaflet"
|
||||||
|
|
||||||
|
|
||||||
|
export default function BoxMap({ header, setMap, startingPosition, startingZoom, button, children, additions, ...props }) {
|
||||||
|
return (
|
||||||
|
<BoxFull
|
||||||
|
header={header}
|
||||||
|
childrenClassName={Style.BoxMapContents}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MapContainer
|
||||||
|
center={startingPosition}
|
||||||
|
zoom={startingZoom}
|
||||||
|
className={Style.MapContainer}
|
||||||
|
whenCreated={setMap}
|
||||||
|
>
|
||||||
|
<TileLayer
|
||||||
|
attribution='(c) <a href="https://osm.org/copyright">OpenStreetMap contributors</a>'
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
/>
|
||||||
|
{additions}
|
||||||
|
<div className={"leaflet-top leaflet-right"}>
|
||||||
|
<div className={"leaflet-control"}>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MapContainer>
|
||||||
|
</BoxFull>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
.BoxConditionMapContents {
|
.BoxMapContents {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -9,6 +9,3 @@
|
||||||
height: 300px;
|
height: 300px;
|
||||||
border-radius: 0 0 25px 25px;
|
border-radius: 0 0 25px 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
|
||||||
}
|
|
|
@ -8,6 +8,8 @@ import make_icon from "../../utils/make_icon"
|
||||||
* A clickable button.
|
* A clickable button.
|
||||||
*
|
*
|
||||||
* @param children - The contents of the button.
|
* @param children - The contents of the button.
|
||||||
|
* @param disabled - Whether the button is disabled or not.
|
||||||
|
* @param onClick - Function to call when the button is clicked. Won't be called if the button is disabled.
|
||||||
* @param className - Additional class(es) that should be added to the button.
|
* @param className - Additional class(es) that should be added to the button.
|
||||||
* @param color - The color of the button. Either `Red`, `Grey`, `Green` or `Yellow`.
|
* @param color - The color of the button. Either `Red`, `Grey`, `Green` or `Yellow`.
|
||||||
* @param icon - The FontAwesome IconDefinition of the icon that should be rendered in the button.
|
* @param icon - The FontAwesome IconDefinition of the icon that should be rendered in the button.
|
||||||
|
@ -15,9 +17,15 @@ import make_icon from "../../utils/make_icon"
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export default function Button({ children, className, color, icon, ...props }) {
|
export default function Button({ children, disabled, onClick, className, color, icon, ...props }) {
|
||||||
return (
|
return (
|
||||||
<button type={"button"} className={classNames(Style.Button, Style[`Button${color}`], className)} {...props}>
|
<button
|
||||||
|
type={"button"}
|
||||||
|
className={classNames(Style.Button, Style[`Button${color}`], disabled ? null : "Clickable", className)}
|
||||||
|
onClick={disabled ? null : onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{children} {make_icon(icon, Style.Icon)}
|
{children} {make_icon(icon, Style.Icon)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,8 +45,6 @@
|
||||||
|
|
||||||
.Icon {
|
.Icon {
|
||||||
font-size: large;
|
font-size: large;
|
||||||
|
|
||||||
/* TODO: non so quanto sia una buona idea, ma funziona accettabilmente */
|
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ export default function ButtonSidebar({ icon, children, to, className, ...props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={to} className={Style.ButtonLink}>
|
<Link to={to} className={Style.ButtonLink}>
|
||||||
<div className={classNames(Style.ButtonSidebar, className)} {...props}>
|
<div className={classNames(Style.ButtonSidebar, "Clickable", className)} {...props}>
|
||||||
{make_icon(icon, Style.ButtonIcon)}
|
{make_icon(icon, Style.ButtonIcon)}
|
||||||
<div className={Style.ButtonText}>
|
<div className={Style.ButtonText}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -12,12 +12,15 @@
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ButtonSmallX:focus {
|
.ButtonSmallX:hover {
|
||||||
outline: none;
|
|
||||||
background-color: var(--bg-field-on);
|
background-color: var(--bg-field-on);
|
||||||
color: var(--fg-field-on);
|
color: var(--fg-field-on);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ButtonSmallX:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ButtonSmallX:focus-visible {
|
.ButtonSmallX:focus-visible {
|
||||||
outline: 2px dashed var(--outline);
|
outline: 2px dashed var(--outline);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import classNames from "classnames"
|
||||||
/**
|
/**
|
||||||
* A slider that allows to select a numeric value in a range.
|
* A slider that allows to select a numeric value in a range.
|
||||||
*
|
*
|
||||||
* @todo Custom styling only works on Firefox!
|
* Custom styling only works on Firefox!
|
||||||
*
|
*
|
||||||
* @param className - Additional class(es) to add to the element.
|
* @param className - Additional class(es) to add to the element.
|
||||||
* @param props - Additional props to pass to the element.
|
* @param props - Additional props to pass to the element.
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import React from "react"
|
|
||||||
import Style from "./Summary.module.css"
|
|
||||||
import classNames from "classnames"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A long line displaying the summary of a certain entity, such as a repository or an user.
|
|
||||||
*
|
|
||||||
* @param icon - The icon of the summary.
|
|
||||||
* @param title - The title of the summary.
|
|
||||||
* @param subtitle - The subtitle of the summary.
|
|
||||||
* @param onClick - A function to call when the summary is clicked.
|
|
||||||
* @param upperLabel - The label for the upper value.
|
|
||||||
* @param upperValue - The upper value.
|
|
||||||
* @param lowerLabel - The label for the lower value.
|
|
||||||
* @param lowerValue - The lower value.
|
|
||||||
* @param buttons - Buttons to add to the right of the summary.
|
|
||||||
* @param className - Additional class(es) to add to the summary.
|
|
||||||
* @param props - Additional props to pass to the summary.
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export default function Summary(
|
|
||||||
{ icon, title, subtitle, onClick, upperLabel, upperValue, lowerLabel, lowerValue, buttons, className, ...props },
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<div className={classNames(Style.Summary, className)} {...props}>
|
|
||||||
<div className={classNames(Style.Left, onClick ? Style.Clickable : null)} onClick={onClick}>
|
|
||||||
<div className={Style.IconContainer}>
|
|
||||||
<FontAwesomeIcon icon={icon}/>
|
|
||||||
</div>
|
|
||||||
<div className={Style.Title}>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
<div className={Style.Subtitle}>
|
|
||||||
{subtitle}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={Style.Middle}>
|
|
||||||
<div className={classNames(Style.Label, Style.Upper)}>
|
|
||||||
{upperLabel}
|
|
||||||
</div>
|
|
||||||
<div className={classNames(Style.Value, Style.Upper)}>
|
|
||||||
{upperValue}
|
|
||||||
</div>
|
|
||||||
<div className={classNames(Style.Label, Style.Lower)}>
|
|
||||||
{lowerLabel}
|
|
||||||
</div>
|
|
||||||
<div className={classNames(Style.Value, Style.Lower)}>
|
|
||||||
{lowerValue}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={Style.Right}>
|
|
||||||
{buttons}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
.Summary {
|
|
||||||
width: 100%;
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
margin: 10px 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Clickable:hover {
|
|
||||||
filter: brightness(110%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Left {
|
|
||||||
width: 280px;
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas:
|
|
||||||
"a b"
|
|
||||||
"a c"
|
|
||||||
"a d"
|
|
||||||
"a e";
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-template-rows: 5px 1fr 1fr 5px;
|
|
||||||
|
|
||||||
background-color: var(--bg-accent);
|
|
||||||
border-radius: 30px 0 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.IconContainer {
|
|
||||||
margin: 5px 15px 5px 5px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
background-color: var(--bg-light);
|
|
||||||
color: var(--fg-primary);
|
|
||||||
|
|
||||||
font-size: x-large;
|
|
||||||
|
|
||||||
grid-area: a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Title {
|
|
||||||
grid-area: c;
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Subtitle {
|
|
||||||
grid-area: d;
|
|
||||||
font-size: small;
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Middle {
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 60px;
|
|
||||||
padding: 5px 20px;
|
|
||||||
|
|
||||||
background-color: var(--bg-light);
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
grid-column-gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
font-size: small;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.Middle .Label {
|
|
||||||
grid-column: 1;
|
|
||||||
text-align: right;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Middle .Value {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Middle .Upper {
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Middle .Lower {
|
|
||||||
grid-row: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Right {
|
|
||||||
width: 280px;
|
|
||||||
height: 60px;
|
|
||||||
padding: 5px;
|
|
||||||
|
|
||||||
background-color: var(--bg-accent);
|
|
||||||
border-radius: 0 30px 30px 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
12
nest_frontend/components/base/summary/SummaryBase.js
Normal file
12
nest_frontend/components/base/summary/SummaryBase.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryBase.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryBase({ children, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.SummaryBase, className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
.SummaryBase {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
}
|
11
nest_frontend/components/base/summary/SummaryButton.js
Normal file
11
nest_frontend/components/base/summary/SummaryButton.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryButton.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import Button from "../Button"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryButton({ className, ...props }) {
|
||||||
|
return (
|
||||||
|
<Button className={classNames(Style.SummaryButton, className)} {...props}/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
.SummaryButton {
|
||||||
|
width: 90px;
|
||||||
|
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
23
nest_frontend/components/base/summary/SummaryLabels.js
Normal file
23
nest_frontend/components/base/summary/SummaryLabels.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryLabels.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryLabels({ children, upperLabel, upperValue, lowerLabel, lowerValue, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.SummaryLabels, className)} {...props}>
|
||||||
|
<div className={classNames(Style.Label, Style.Upper)}>
|
||||||
|
{upperLabel}
|
||||||
|
</div>
|
||||||
|
<div className={classNames(Style.Value, Style.Upper)}>
|
||||||
|
{upperValue}
|
||||||
|
</div>
|
||||||
|
<div className={classNames(Style.Label, Style.Lower)}>
|
||||||
|
{lowerLabel}
|
||||||
|
</div>
|
||||||
|
<div className={classNames(Style.Value, Style.Lower)}>
|
||||||
|
{lowerValue}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
.SummaryLabels {
|
||||||
|
flex-grow: 3;
|
||||||
|
|
||||||
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Label {
|
||||||
|
grid-column: 1;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Value {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Upper {
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lower {
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
25
nest_frontend/components/base/summary/SummaryLeft.js
Normal file
25
nest_frontend/components/base/summary/SummaryLeft.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryLeft.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryLeft({ icon, title, subtitle, className, onClick, ...props }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(Style.SummaryLeft, onClick ? "Clickable" : null, className)}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={Style.IconContainer}>
|
||||||
|
<FontAwesomeIcon icon={icon}/>
|
||||||
|
</div>
|
||||||
|
<div className={Style.Title}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div className={Style.Subtitle}>
|
||||||
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
46
nest_frontend/components/base/summary/SummaryLeft.module.css
Normal file
46
nest_frontend/components/base/summary/SummaryLeft.module.css
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
.SummaryLeft {
|
||||||
|
width: 275px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"a b"
|
||||||
|
"a c"
|
||||||
|
"a d"
|
||||||
|
"a e";
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-template-rows: 5px 1fr 1fr 5px;
|
||||||
|
justify-content: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
background-color: var(--bg-accent);
|
||||||
|
border-radius: 30px 0 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.IconContainer {
|
||||||
|
margin: 5px 15px 5px 5px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
color: var(--fg-primary);
|
||||||
|
|
||||||
|
font-size: x-large;
|
||||||
|
|
||||||
|
grid-area: a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Title {
|
||||||
|
grid-area: c;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Subtitle {
|
||||||
|
grid-area: d;
|
||||||
|
font-size: small;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
12
nest_frontend/components/base/summary/SummaryRight.js
Normal file
12
nest_frontend/components/base/summary/SummaryRight.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryRight.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryRight({ children, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.SummaryRight, className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
.SummaryRight {
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
background-color: var(--bg-accent);
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
}
|
12
nest_frontend/components/base/summary/SummaryText.js
Normal file
12
nest_frontend/components/base/summary/SummaryText.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./SummaryText.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
|
|
||||||
|
export default function SummaryText({ children, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.SummaryText, className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
import React, { useContext } from "react"
|
import React, { useContext } from "react"
|
||||||
import Style from "./ConditionBadge.module.css"
|
|
||||||
import classNames from "classnames"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import ButtonSmallX from "../base/ButtonSmallX"
|
|
||||||
import { faAt, faClock, faGlobe, faHashtag, faMapPin } from "@fortawesome/free-solid-svg-icons"
|
import { faAt, faClock, faGlobe, faHashtag, faMapPin } from "@fortawesome/free-solid-svg-icons"
|
||||||
import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
|
import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
|
||||||
|
import Badge from "../base/Badge"
|
||||||
|
|
||||||
|
|
||||||
const CONDITION_COLORS = {
|
const CONDITION_COLORS = {
|
||||||
|
@ -26,13 +23,13 @@ const CONDITION_ICONS = {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small colored badge representing a Condition for a filter.
|
* A {@link Badge} representing a Condition for a filter.
|
||||||
*
|
*
|
||||||
* @param condition - The Condition that this badge represents.
|
* @param condition - The Condition that this badge represents.
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export default function ConditionBadge({ ...condition }) {
|
export default function BadgeCondition({ ...condition }) {
|
||||||
const { id, type, content } = condition
|
const { id, type, content } = condition
|
||||||
const color = CONDITION_COLORS[type]
|
const color = CONDITION_COLORS[type]
|
||||||
const icon = CONDITION_ICONS[type]
|
const icon = CONDITION_ICONS[type]
|
||||||
|
@ -53,24 +50,16 @@ export default function ConditionBadge({ ...condition }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Badge
|
||||||
title={id ? `💠 Condition ID: ${id}` : "✨ New Condition"}
|
title={id ? `💠 Condition ID: ${id}` : "✨ New Condition"}
|
||||||
className={classNames(Style.ConditionBadge, Style[`ConditionBadge${color}`])}
|
color={color}
|
||||||
|
icon={icon}
|
||||||
|
onClickDelete={() => {
|
||||||
|
console.debug(`Removing Condition: `, condition)
|
||||||
|
removeRawCondition(condition)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className={Style.Icon}>
|
{displayedContent}
|
||||||
<FontAwesomeIcon icon={icon}/>
|
</Badge>
|
||||||
</div>
|
|
||||||
<div className={Style.Text}>
|
|
||||||
{displayedContent}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ButtonSmallX
|
|
||||||
onClick={() => {
|
|
||||||
console.debug(`Removing Condition: `, condition)
|
|
||||||
removeRawCondition(condition)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,19 +1,16 @@
|
||||||
import React, { useCallback, useContext, useEffect, useState } from "react"
|
import React, { useCallback, useContext, useEffect, useState } from "react"
|
||||||
import BoxFull from "../base/BoxFull"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { faMapPin, faPlus } from "@fortawesome/free-solid-svg-icons"
|
import { faMapPin, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||||
import Style from "./BoxConditionMap.module.css"
|
|
||||||
import ButtonIconOnly from "../base/ButtonIconOnly"
|
import ButtonIconOnly from "../base/ButtonIconOnly"
|
||||||
import { MapContainer, TileLayer } from "react-leaflet"
|
|
||||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||||
import Condition from "../../utils/Condition"
|
import Condition from "../../utils/Condition"
|
||||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||||
|
import BoxMap from "../base/BoxMap"
|
||||||
|
|
||||||
|
|
||||||
const STARTING_POSITION = { lat: 41.89309, lng: 12.48289 }
|
const STARTING_POSITION = { lat: 41.89309, lng: 12.48289 }
|
||||||
const STARTING_ZOOM = 3
|
const STARTING_ZOOM = 3
|
||||||
|
|
||||||
// FIXME: this only works correctly at the equator!
|
|
||||||
/**
|
/**
|
||||||
* https://wiki.openstreetmap.org/wiki/Zoom_levels
|
* https://wiki.openstreetmap.org/wiki/Zoom_levels
|
||||||
*/
|
*/
|
||||||
|
@ -99,7 +96,7 @@ export default function BoxConditionMap({ ...props }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoxFull
|
<BoxMap
|
||||||
header={
|
header={
|
||||||
<span>
|
<span>
|
||||||
{strings.searchBy}
|
{strings.searchBy}
|
||||||
|
@ -109,30 +106,17 @@ export default function BoxConditionMap({ ...props }) {
|
||||||
{strings.byZone}
|
{strings.byZone}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
childrenClassName={Style.BoxConditionMapContents}
|
startingPosition={STARTING_POSITION}
|
||||||
{...props}
|
startingZoom={STARTING_ZOOM}
|
||||||
>
|
setMap={setMap}
|
||||||
<MapContainer
|
button={
|
||||||
center={STARTING_POSITION}
|
<ButtonIconOnly
|
||||||
zoom={STARTING_ZOOM}
|
icon={faPlus}
|
||||||
className={Style.MapContainer}
|
color={"Green"}
|
||||||
whenCreated={setMap}
|
onClick={onButtonClick}
|
||||||
>
|
|
||||||
<TileLayer
|
|
||||||
attribution='(c) <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
|
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
||||||
/>
|
/>
|
||||||
<div className={"leaflet-top leaflet-right"}>
|
}
|
||||||
<div className={"leaflet-control"}>
|
{...props}
|
||||||
<ButtonIconOnly
|
/>
|
||||||
className={Style.Button}
|
|
||||||
icon={faPlus}
|
|
||||||
color={"Green"}
|
|
||||||
onClick={onButtonClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MapContainer>
|
|
||||||
</BoxFull>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { useContext } from "react"
|
import React, { useContext } from "react"
|
||||||
import BoxFull from "../base/BoxFull"
|
import BoxFull from "../base/BoxFull"
|
||||||
import ConditionBadge from "./ConditionBadge"
|
import BadgeCondition from "./BadgeCondition"
|
||||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A box which renders all conditions of the {@link RepositoryEditor} as {@link ConditionBadge}s.
|
* A box which renders all conditions of the {@link RepositoryEditor} as {@link BadgeCondition}s.
|
||||||
*
|
*
|
||||||
* @param props
|
* @param props
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
|
@ -16,7 +16,7 @@ export default function BoxConditions({ ...props }) {
|
||||||
const { conditions } = useRepositoryEditor()
|
const { conditions } = useRepositoryEditor()
|
||||||
const { strings } = useContext(ContextLanguage)
|
const { strings } = useContext(ContextLanguage)
|
||||||
|
|
||||||
const badges = conditions.map((cond, pos) => <ConditionBadge key={pos} {...cond}/>)
|
const badges = conditions.map((cond, pos) => <BadgeCondition key={pos} {...cond}/>)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoxFull header={strings.conditions} {...props}>
|
<BoxFull header={strings.conditions} {...props}>
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default function BoxRepositoriesActive({
|
||||||
deleteSelf={() => destroyRepository(repo["id"])}
|
deleteSelf={() => destroyRepository(repo["id"])}
|
||||||
canArchive={true}
|
canArchive={true}
|
||||||
canEdit={true}
|
canEdit={true}
|
||||||
canDelete={repo["owner"]["username"] === user["username"]}
|
canDelete={false}
|
||||||
running={running}
|
running={running}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|
|
@ -11,7 +11,6 @@ import ContextLanguage from "../../contexts/ContextLanguage"
|
||||||
/**
|
/**
|
||||||
* The sidebar of the website, with the logo, buttons to switch between the various pages and a notification counter.
|
* The sidebar of the website, with the logo, buttons to switch between the various pages and a notification counter.
|
||||||
*
|
*
|
||||||
* @todo The notification counter is still missing.
|
|
||||||
* @param className - Additional class(es) to be added to the outer container.
|
* @param className - Additional class(es) to be added to the outer container.
|
||||||
* @param props - Additional props to be passed to the outer container.
|
* @param props - Additional props to be passed to the outer container.
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import React, { useContext } from "react"
|
import React, { useContext } from "react"
|
||||||
import Button from "../base/Button"
|
|
||||||
import { faArchive, faFolder, faFolderOpen, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
import { faArchive, faFolder, faFolderOpen, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { useHistory } from "react-router"
|
import { useHistory } from "react-router"
|
||||||
import Summary from "../base/Summary"
|
|
||||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||||
|
import SummaryBase from "../base/summary/SummaryBase"
|
||||||
|
import SummaryLeft from "../base/summary/SummaryLeft"
|
||||||
|
import SummaryLabels from "../base/summary/SummaryLabels"
|
||||||
|
import SummaryButton from "../base/summary/SummaryButton"
|
||||||
|
import SummaryRight from "../base/summary/SummaryRight"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,51 +39,51 @@ export default function SummaryRepository(
|
||||||
history.push(`/repositories/${repo.id}/edit`)
|
history.push(`/repositories/${repo.id}/edit`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = <>
|
|
||||||
{canDelete ?
|
|
||||||
<Button
|
|
||||||
color={"Red"}
|
|
||||||
icon={faTrash}
|
|
||||||
onClick={deleteSelf}
|
|
||||||
disabled={running}
|
|
||||||
>
|
|
||||||
{strings.delete}
|
|
||||||
</Button>
|
|
||||||
: null}
|
|
||||||
{canEdit ?
|
|
||||||
<Button
|
|
||||||
color={"Yellow"}
|
|
||||||
icon={faPencilAlt}
|
|
||||||
onClick={onEditClick}
|
|
||||||
disabled={running}
|
|
||||||
>
|
|
||||||
{strings.edit}
|
|
||||||
</Button>
|
|
||||||
: null}
|
|
||||||
{canArchive ?
|
|
||||||
<Button
|
|
||||||
color={"Grey"}
|
|
||||||
icon={faArchive}
|
|
||||||
onClick={archiveSelf}
|
|
||||||
disabled={running}
|
|
||||||
>
|
|
||||||
{strings.archive}
|
|
||||||
</Button>
|
|
||||||
: null}
|
|
||||||
</>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<SummaryBase>
|
||||||
icon={repo.is_active ? faFolderOpen : faFolder}
|
<SummaryLeft
|
||||||
title={repo.name}
|
icon={repo.is_active ? faFolderOpen : faFolder}
|
||||||
subtitle={repo.owner ? repo.owner.username : null}
|
title={repo.name}
|
||||||
onClick={onRepoClick}
|
subtitle={repo.owner ? repo.owner.username : null}
|
||||||
upperLabel={strings.created}
|
onClick={onRepoClick}
|
||||||
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
/>
|
||||||
lowerLabel={strings.archived}
|
<SummaryLabels
|
||||||
lowerValue={repo.end ? new Date(repo.end).toLocaleString() : null}
|
upperLabel={strings.created}
|
||||||
buttons={buttons}
|
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
||||||
{...props}
|
lowerLabel={strings.archived}
|
||||||
/>
|
lowerValue={repo.end ? new Date(repo.end).toLocaleString() : null}
|
||||||
|
/>
|
||||||
|
{canDelete ?
|
||||||
|
<SummaryButton
|
||||||
|
color={"Red"}
|
||||||
|
icon={faTrash}
|
||||||
|
onClick={deleteSelf}
|
||||||
|
disabled={running}
|
||||||
|
>
|
||||||
|
{strings.delete}
|
||||||
|
</SummaryButton>
|
||||||
|
: null}
|
||||||
|
{canEdit ?
|
||||||
|
<SummaryButton
|
||||||
|
color={"Yellow"}
|
||||||
|
icon={faPencilAlt}
|
||||||
|
onClick={onEditClick}
|
||||||
|
disabled={running}
|
||||||
|
>
|
||||||
|
{strings.edit}
|
||||||
|
</SummaryButton>
|
||||||
|
: null}
|
||||||
|
{canArchive ?
|
||||||
|
<SummaryButton
|
||||||
|
color={"Grey"}
|
||||||
|
icon={faArchive}
|
||||||
|
onClick={archiveSelf}
|
||||||
|
disabled={running}
|
||||||
|
>
|
||||||
|
{strings.archive}
|
||||||
|
</SummaryButton>
|
||||||
|
: null}
|
||||||
|
<SummaryRight/>
|
||||||
|
</SummaryBase>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,41 +1,40 @@
|
||||||
import React, { useContext } from "react"
|
import React, { useContext } from "react"
|
||||||
import Summary from "../base/Summary"
|
|
||||||
import { faStar, faTrash, faUser } from "@fortawesome/free-solid-svg-icons"
|
import { faStar, faTrash, faUser } from "@fortawesome/free-solid-svg-icons"
|
||||||
import Button from "../base/Button"
|
|
||||||
import ContextUser from "../../contexts/ContextUser"
|
|
||||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||||
|
import SummaryBase from "../base/summary/SummaryBase"
|
||||||
|
import SummaryLeft from "../base/summary/SummaryLeft"
|
||||||
|
import SummaryLabels from "../base/summary/SummaryLabels"
|
||||||
|
import SummaryButton from "../base/summary/SummaryButton"
|
||||||
|
import SummaryRight from "../base/summary/SummaryRight"
|
||||||
|
|
||||||
|
|
||||||
export default function SummaryUser({ user, destroyUser, running, ...props }) {
|
export default function SummaryUser({ user, destroyUser, running, ...props }) {
|
||||||
const { user: loggedUser } = useContext(ContextUser)
|
|
||||||
const { strings } = useContext(ContextLanguage)
|
const { strings } = useContext(ContextLanguage)
|
||||||
|
|
||||||
const buttons = <>
|
|
||||||
{loggedUser.email !== user.email ?
|
|
||||||
<Button
|
|
||||||
color={"Red"}
|
|
||||||
icon={faTrash}
|
|
||||||
onClick={async event => {
|
|
||||||
event.stopPropagation()
|
|
||||||
// TODO: Errors are not caught here. Where should they be displayed?
|
|
||||||
await destroyUser(user["email"])
|
|
||||||
}}
|
|
||||||
disabled={running}
|
|
||||||
>
|
|
||||||
{strings.delete}
|
|
||||||
</Button>
|
|
||||||
: null}
|
|
||||||
</>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<SummaryBase>
|
||||||
icon={user.isAdmin ? faStar : faUser}
|
<SummaryLeft
|
||||||
title={user.username}
|
icon={user.isAdmin ? faStar : faUser}
|
||||||
subtitle={user.email}
|
title={user.username}
|
||||||
upperLabel={strings.type}
|
subtitle={user.email}
|
||||||
upperValue={user.isAdmin ? strings.admin : strings.user}
|
/>
|
||||||
buttons={buttons}
|
<SummaryLabels
|
||||||
{...props}
|
upperLabel={strings.type}
|
||||||
/>
|
upperValue={user.isAdmin ? strings.admin : strings.user}
|
||||||
|
/>
|
||||||
|
<SummaryButton
|
||||||
|
color={"Red"}
|
||||||
|
icon={faTrash}
|
||||||
|
onClick={async event => {
|
||||||
|
event.stopPropagation()
|
||||||
|
// TODO: Errors are not caught here. Where should they be displayed?
|
||||||
|
await destroyUser(user["email"])
|
||||||
|
}}
|
||||||
|
disabled={running}
|
||||||
|
>
|
||||||
|
{strings.delete}
|
||||||
|
</SummaryButton>
|
||||||
|
<SummaryRight/>
|
||||||
|
</SummaryBase>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
22
nest_frontend/components/providers/RepositoryViewer.js
Normal file
22
nest_frontend/components/providers/RepositoryViewer.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React from "react"
|
||||||
|
import Style from "./RepositoryViewer.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import ContextRepositoryViewer from "../../contexts/ContextRepositoryViewer"
|
||||||
|
|
||||||
|
|
||||||
|
export default function RepositoryViewer({
|
||||||
|
id,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ContextRepositoryViewer.Provider
|
||||||
|
value={{
|
||||||
|
id,
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={classNames(Style.RepositoryViewer, className)}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ContextRepositoryViewer.Provider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
.RepositoryViewer {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"b c d"
|
||||||
|
"b e e"
|
||||||
|
"b f f"
|
||||||
|
"b g g";
|
||||||
|
grid-template-columns: 400px 1fr 1fr;
|
||||||
|
grid-template-rows: auto auto 1fr auto;
|
||||||
|
|
||||||
|
grid-gap: 10px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
|
@ -2,6 +2,6 @@ import { createContext } from "react"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Document this.
|
* Context to quickly pass props to the children of {@link RepositoryEditor}.
|
||||||
*/
|
*/
|
||||||
export default createContext(null)
|
export default createContext(null)
|
||||||
|
|
7
nest_frontend/contexts/ContextRepositoryViewer.js
Normal file
7
nest_frontend/contexts/ContextRepositoryViewer.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { createContext } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context to quickly pass props to the children of {@link RepositoryViewer}.
|
||||||
|
*/
|
||||||
|
export default createContext(null)
|
|
@ -3,12 +3,12 @@ import ContextRepositoryEditor from "../contexts/ContextRepositoryEditor"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Document this.
|
* Hook to quickly use {@link ContextRepositoryEditor}.
|
||||||
*/
|
*/
|
||||||
export default function useRepositoryEditor() {
|
export default function useRepositoryEditor() {
|
||||||
const context = useContext(ContextRepositoryEditor)
|
const context = useContext(ContextRepositoryEditor)
|
||||||
if(!context) {
|
if(!context) {
|
||||||
throw new Error("Questo componente deve essere messo in un RepositoryEditor.")
|
throw new Error("This component must be placed inside a RepositoryEditor.")
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
14
nest_frontend/hooks/useRepositoryViewer.js
Normal file
14
nest_frontend/hooks/useRepositoryViewer.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { useContext } from "react"
|
||||||
|
import ContextRepositoryViewer from "../contexts/ContextRepositoryViewer"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to quickly use {@link ContextRepositoryViewer}.
|
||||||
|
*/
|
||||||
|
export default function useRepositoryViewer() {
|
||||||
|
const context = useContext(ContextRepositoryViewer)
|
||||||
|
if(!context) {
|
||||||
|
throw new Error("This component must be placed inside a RepositoryViewer.")
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
|
@ -83,3 +83,15 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
|
|
||||||
--outline: black;
|
--outline: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Clickable:hover {
|
||||||
|
filter: brightness(110%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Clickable:active {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
Loading…
Reference in a new issue