mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-22 04:54:18 +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;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
|
@ -9,6 +9,3 @@
|
|||
height: 300px;
|
||||
border-radius: 0 0 25px 25px;
|
||||
}
|
||||
|
||||
.Button {
|
||||
}
|
|
@ -8,6 +8,8 @@ import make_icon from "../../utils/make_icon"
|
|||
* A clickable 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 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.
|
||||
|
@ -15,9 +17,15 @@ import make_icon from "../../utils/make_icon"
|
|||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Button({ children, className, color, icon, ...props }) {
|
||||
export default function Button({ children, disabled, onClick, className, color, icon, ...props }) {
|
||||
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)}
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -45,8 +45,6 @@
|
|||
|
||||
.Icon {
|
||||
font-size: large;
|
||||
|
||||
/* TODO: non so quanto sia una buona idea, ma funziona accettabilmente */
|
||||
line-height: 0;
|
||||
vertical-align: sub;
|
||||
}
|
|
@ -30,7 +30,7 @@ export default function ButtonSidebar({ icon, children, to, className, ...props
|
|||
|
||||
return (
|
||||
<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)}
|
||||
<div className={Style.ButtonText}>
|
||||
{children}
|
||||
|
|
|
@ -12,12 +12,15 @@
|
|||
top: -1px;
|
||||
}
|
||||
|
||||
.ButtonSmallX:focus {
|
||||
outline: none;
|
||||
.ButtonSmallX:hover {
|
||||
background-color: var(--bg-field-on);
|
||||
color: var(--fg-field-on);
|
||||
}
|
||||
|
||||
.ButtonSmallX:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ButtonSmallX:focus-visible {
|
||||
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.
|
||||
*
|
||||
* @todo Custom styling only works on Firefox!
|
||||
* Custom styling only works on Firefox!
|
||||
*
|
||||
* @param className - Additional class(es) to add 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 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 ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
|
||||
import Badge from "../base/Badge"
|
||||
|
||||
|
||||
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.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function ConditionBadge({ ...condition }) {
|
||||
export default function BadgeCondition({ ...condition }) {
|
||||
const { id, type, content } = condition
|
||||
const color = CONDITION_COLORS[type]
|
||||
const icon = CONDITION_ICONS[type]
|
||||
|
@ -53,24 +50,16 @@ export default function ConditionBadge({ ...condition }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<Badge
|
||||
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}>
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</div>
|
||||
<div className={Style.Text}>
|
||||
{displayedContent}
|
||||
</div>
|
||||
<div>
|
||||
<ButtonSmallX
|
||||
onClick={() => {
|
||||
console.debug(`Removing Condition: `, condition)
|
||||
removeRawCondition(condition)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{displayedContent}
|
||||
</Badge>
|
||||
)
|
||||
}
|
|
@ -1,19 +1,16 @@
|
|||
import React, { useCallback, useContext, useEffect, useState } from "react"
|
||||
import BoxFull from "../base/BoxFull"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faMapPin, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
import Style from "./BoxConditionMap.module.css"
|
||||
import ButtonIconOnly from "../base/ButtonIconOnly"
|
||||
import { MapContainer, TileLayer } from "react-leaflet"
|
||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||
import Condition from "../../utils/Condition"
|
||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||
import BoxMap from "../base/BoxMap"
|
||||
|
||||
|
||||
const STARTING_POSITION = { lat: 41.89309, lng: 12.48289 }
|
||||
const STARTING_ZOOM = 3
|
||||
|
||||
// FIXME: this only works correctly at the equator!
|
||||
/**
|
||||
* https://wiki.openstreetmap.org/wiki/Zoom_levels
|
||||
*/
|
||||
|
@ -99,7 +96,7 @@ export default function BoxConditionMap({ ...props }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<BoxFull
|
||||
<BoxMap
|
||||
header={
|
||||
<span>
|
||||
{strings.searchBy}
|
||||
|
@ -109,30 +106,17 @@ export default function BoxConditionMap({ ...props }) {
|
|||
{strings.byZone}
|
||||
</span>
|
||||
}
|
||||
childrenClassName={Style.BoxConditionMapContents}
|
||||
{...props}
|
||||
>
|
||||
<MapContainer
|
||||
center={STARTING_POSITION}
|
||||
zoom={STARTING_ZOOM}
|
||||
className={Style.MapContainer}
|
||||
whenCreated={setMap}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='(c) <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
startingPosition={STARTING_POSITION}
|
||||
startingZoom={STARTING_ZOOM}
|
||||
setMap={setMap}
|
||||
button={
|
||||
<ButtonIconOnly
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
<div className={"leaflet-top leaflet-right"}>
|
||||
<div className={"leaflet-control"}>
|
||||
<ButtonIconOnly
|
||||
className={Style.Button}
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MapContainer>
|
||||
</BoxFull>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useContext } from "react"
|
||||
import BoxFull from "../base/BoxFull"
|
||||
import ConditionBadge from "./ConditionBadge"
|
||||
import BadgeCondition from "./BadgeCondition"
|
||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||
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
|
||||
* @returns {JSX.Element}
|
||||
|
@ -16,7 +16,7 @@ export default function BoxConditions({ ...props }) {
|
|||
const { conditions } = useRepositoryEditor()
|
||||
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 (
|
||||
<BoxFull header={strings.conditions} {...props}>
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function BoxRepositoriesActive({
|
|||
deleteSelf={() => destroyRepository(repo["id"])}
|
||||
canArchive={true}
|
||||
canEdit={true}
|
||||
canDelete={repo["owner"]["username"] === user["username"]}
|
||||
canDelete={false}
|
||||
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.
|
||||
*
|
||||
* @todo The notification counter is still missing.
|
||||
* @param className - Additional class(es) to be added to the outer container.
|
||||
* @param props - Additional props to be passed to the outer container.
|
||||
* @returns {JSX.Element}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React, { useContext } from "react"
|
||||
import Button from "../base/Button"
|
||||
import { faArchive, faFolder, faFolderOpen, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||
import { useHistory } from "react-router"
|
||||
import Summary from "../base/Summary"
|
||||
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`)
|
||||
}
|
||||
|
||||
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 (
|
||||
<Summary
|
||||
icon={repo.is_active ? faFolderOpen : faFolder}
|
||||
title={repo.name}
|
||||
subtitle={repo.owner ? repo.owner.username : null}
|
||||
onClick={onRepoClick}
|
||||
upperLabel={strings.created}
|
||||
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
||||
lowerLabel={strings.archived}
|
||||
lowerValue={repo.end ? new Date(repo.end).toLocaleString() : null}
|
||||
buttons={buttons}
|
||||
{...props}
|
||||
/>
|
||||
<SummaryBase>
|
||||
<SummaryLeft
|
||||
icon={repo.is_active ? faFolderOpen : faFolder}
|
||||
title={repo.name}
|
||||
subtitle={repo.owner ? repo.owner.username : null}
|
||||
onClick={onRepoClick}
|
||||
/>
|
||||
<SummaryLabels
|
||||
upperLabel={strings.created}
|
||||
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
||||
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 Summary from "../base/Summary"
|
||||
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 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 }) {
|
||||
const { user: loggedUser } = useContext(ContextUser)
|
||||
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 (
|
||||
<Summary
|
||||
icon={user.isAdmin ? faStar : faUser}
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
upperLabel={strings.type}
|
||||
upperValue={user.isAdmin ? strings.admin : strings.user}
|
||||
buttons={buttons}
|
||||
{...props}
|
||||
/>
|
||||
<SummaryBase>
|
||||
<SummaryLeft
|
||||
icon={user.isAdmin ? faStar : faUser}
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
/>
|
||||
<SummaryLabels
|
||||
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)
|
||||
|
|
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() {
|
||||
const context = useContext(ContextRepositoryEditor)
|
||||
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
|
||||
}
|
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;
|
||||
}
|
||||
|
||||
.Clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Clickable:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.Clickable:active {
|
||||
filter: brightness(125%);
|
||||
}
|
Loading…
Reference in a new issue