1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-11-22 04:54:18 +00:00

Add initial support for sharing a repository

This commit is contained in:
Steffo 2021-05-23 17:35:38 +02:00
parent 6048de040f
commit 1928435f6f
Signed by: steffo
GPG key ID: 6965406171929D01
8 changed files with 157 additions and 29 deletions

View file

@ -11,12 +11,6 @@
cursor: pointer;
}
.Button[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.Button:focus-visible {
outline: 4px solid var(--outline);
}

View file

@ -4,11 +4,18 @@ import classNames from "classnames"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
export default function SummaryLeft({ icon, title, subtitle, className, onClick, ...props }) {
export default function SummaryLeft({ icon, title, subtitle, className, onClick, disabled, ...props }) {
const _onClick = disabled ? null : onClick
return (
<div
className={classNames(Style.SummaryLeft, onClick ? "Clickable" : null, className)}
onClick={onClick}
className={classNames(
Style.SummaryLeft,
onClick ? "Clickable" : null,
(onClick && disabled) ? "Disabled" : null,
className
)}
onClick={_onClick}
{...props}
>
<div className={Style.IconContainer}>

View file

@ -9,13 +9,15 @@ import ContextLanguage from "../../contexts/ContextLanguage"
* A {@link BoxFullScrollable} rendering an array of users as {@link SummaryUser}s.
*
* @param users - Array of users to render.
* @param shareWithUser - Async function to share a repository with an user, to be passed to {@link SummaryUser}.
* @param unshareWithUser - Async function to unshare a repository with an user, to be passed to {@link SummaryUser}.
* @param destroyUser - Async function to destroy an user, to be passed to {@link SummaryUser}.
* @param running - Whether another request is currently running.
* @param props - Additional props to pass to the box.
* @returns {JSX.Element}
* @constructor
*/
export default function BoxUserList({ users, destroyUser, running, ...props }) {
export default function BoxUserList({ users, shareWithUser, unshareWithUser, destroyUser, running, ...props }) {
const { strings } = useContext(ContextLanguage)
let contents
@ -23,8 +25,17 @@ export default function BoxUserList({ users, destroyUser, running, ...props }) {
contents = <Loading/>
}
else {
contents = users.map(user =>
<SummaryUser key={user["email"]} destroyUser={destroyUser} running={running} user={user}/>)
contents = users.map(user => (
<SummaryUser
key={user["email"]}
shareWithUser={shareWithUser}
unshareWithUser={unshareWithUser}
destroyUser={destroyUser}
running={running}
user={user}
/>
)
)
}
return (

View file

@ -35,6 +35,7 @@ export default function SummaryRepository(
title={repo.name}
subtitle={repo.owner ? repo.owner.username : null}
onClick={view}
disabled={running}
/>
<SummaryLabels

View file

@ -1,24 +1,27 @@
import React, { useContext } from "react"
import { faStar, faTrash, faUser } from "@fortawesome/free-solid-svg-icons"
import { faShare, faStar, faTrash, faUser } from "@fortawesome/free-solid-svg-icons"
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"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
/**
* A {@link SummaryBase} representing a N.E.S.T. user.
*
* @param user - The user to represent.
* @param shareWithUser - Async function to share a repository with an user, to be passed to {@link SummaryUser}.
* @param unshareWithUser - Async function to unshare a repository with an user, to be passed to {@link SummaryUser}.
* @param destroyUser - Async function <string> to destroy an user from the frontend.
* @param running - Whether another request is already running.
* @param props - Additional props to pass to the summary.
* @returns {JSX.Element}
* @constructor
*/
export default function SummaryUser({ user, destroyUser, running, ...props }) {
export default function SummaryUser({ user, shareWithUser, unshareWithUser, destroyUser, running, ...props }) {
const { strings } = useContext(ContextLanguage)
return (
@ -27,23 +30,52 @@ export default function SummaryUser({ user, destroyUser, running, ...props }) {
icon={user.isAdmin ? faStar : faUser}
title={user.username}
subtitle={user.email}
disabled={running}
/>
<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>
{shareWithUser ?
<SummaryButton
color={"Green"}
icon={faShare}
onClick={async event => {
event.stopPropagation()
await shareWithUser(user)
}}
disabled={running}
>
{strings.share}
</SummaryButton>
: null}
{unshareWithUser ?
<SummaryButton
color={"Red"}
icon={<FontAwesomeIcon icon={faShare} flip={"horizontal"}/>}
onClick={async event => {
event.stopPropagation()
await unshareWithUser(user)
}}
disabled={running}
>
{strings.unshare}
</SummaryButton>
: null}
{destroyUser ?
<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>
: null}
<SummaryRight/>
</SummaryBase>
)

View file

@ -24,8 +24,9 @@ h1, h2, h3, h4, h5, h6 {
font-family: var(--font-title);
}
*[disabled] {
cursor: not-allowed;
*[disabled], .Disabled {
opacity: 0.5;
cursor: not-allowed !important;
}
a {

View file

@ -1,18 +1,76 @@
import React, { useContext } from "react"
import React, { useCallback, useContext } from "react"
import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader"
import ContextLanguage from "../contexts/ContextLanguage"
import Style from "./PageShare.module.css"
import BoxUserList from "../components/interactive/BoxUserList"
import useBackendViewset from "../hooks/useBackendViewset"
import { useParams } from "react-router"
export default function PageShare({ className, ...props }) {
const { strings } = useContext(ContextLanguage)
const { id } = useParams()
const {resources: users} = useBackendViewset(
"/api/v1/users/",
"email",
{
list: true,
create: false,
retrieve: false,
edit: false,
destroy: false,
command: false,
action: false,
}
)
const {resources: authorizations, createResource: createAuthorization, destroyResource: destroyAuthorization} = useBackendViewset(
`/api/v1/repositories/${id}/authorizations/`,
"email",
{
list: true,
create: true,
retrieve: false,
edit: false,
destroy: true,
command: false,
action: false,
}
)
const shareWith = useCallback(
user => {
console.info("Authorizing ", user, " ...")
createAuthorization({rid: id, email: user.email})
},
[createAuthorization, id]
)
const unshareWith = useCallback(
user => {
console.info("Deauthorizing ", user, " ...")
destroyAuthorization(user.email)
},
[destroyAuthorization, id]
)
return (
<div className={classNames(Style.PageShare, className)} {...props}>
<BoxHeader className={Style.Header}>
{strings.repoShare}
</BoxHeader>
<BoxUserList
className={Style.UserList}
users={users.filter(user => !authorizations.map(a => a.email).includes(user.email))}
shareWithUser={shareWith}
header={strings.availableUsers}
/>
<BoxUserList
className={Style.SharingWith}
users={users.filter(user => authorizations.map(a => a.email).includes(user.email))}
unshareWithUser={unshareWith}
header={strings.sharingWith}
/>
</div>
)
}

View file

@ -1,3 +1,27 @@
.PageShare {
display: grid;
grid-template-areas:
"a"
"b"
"c";
grid-template-rows: auto 1fr 1fr;
grid-template-columns: 1fr;
grid-gap: 10px;
width: 100%;
height: 100%;
}
.Header {
grid-area: a;
}
.UserList {
grid-area: b;
}
.SharingWith {
grid-area: c;
}