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; cursor: pointer;
} }
.Button[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.Button:focus-visible { .Button:focus-visible {
outline: 4px solid var(--outline); outline: 4px solid var(--outline);
} }

View file

@ -4,11 +4,18 @@ import classNames from "classnames"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" 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 ( return (
<div <div
className={classNames(Style.SummaryLeft, onClick ? "Clickable" : null, className)} className={classNames(
onClick={onClick} Style.SummaryLeft,
onClick ? "Clickable" : null,
(onClick && disabled) ? "Disabled" : null,
className
)}
onClick={_onClick}
{...props} {...props}
> >
<div className={Style.IconContainer}> <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. * A {@link BoxFullScrollable} rendering an array of users as {@link SummaryUser}s.
* *
* @param users - Array of users to render. * @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 destroyUser - Async function to destroy an user, to be passed to {@link SummaryUser}.
* @param running - Whether another request is currently running. * @param running - Whether another request is currently running.
* @param props - Additional props to pass to the box. * @param props - Additional props to pass to the box.
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
export default function BoxUserList({ users, destroyUser, running, ...props }) { export default function BoxUserList({ users, shareWithUser, unshareWithUser, destroyUser, running, ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
let contents let contents
@ -23,8 +25,17 @@ export default function BoxUserList({ users, destroyUser, running, ...props }) {
contents = <Loading/> contents = <Loading/>
} }
else { else {
contents = users.map(user => contents = users.map(user => (
<SummaryUser key={user["email"]} destroyUser={destroyUser} running={running} user={user}/>) <SummaryUser
key={user["email"]}
shareWithUser={shareWithUser}
unshareWithUser={unshareWithUser}
destroyUser={destroyUser}
running={running}
user={user}
/>
)
)
} }
return ( return (

View file

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

View file

@ -1,24 +1,27 @@
import React, { useContext } from "react" 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 ContextLanguage from "../../contexts/ContextLanguage"
import SummaryBase from "../base/summary/SummaryBase" import SummaryBase from "../base/summary/SummaryBase"
import SummaryLeft from "../base/summary/SummaryLeft" import SummaryLeft from "../base/summary/SummaryLeft"
import SummaryLabels from "../base/summary/SummaryLabels" import SummaryLabels from "../base/summary/SummaryLabels"
import SummaryButton from "../base/summary/SummaryButton" import SummaryButton from "../base/summary/SummaryButton"
import SummaryRight from "../base/summary/SummaryRight" import SummaryRight from "../base/summary/SummaryRight"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
/** /**
* A {@link SummaryBase} representing a N.E.S.T. user. * A {@link SummaryBase} representing a N.E.S.T. user.
* *
* @param user - The user to represent. * @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 destroyUser - Async function <string> to destroy an user from the frontend.
* @param running - Whether another request is already running. * @param running - Whether another request is already running.
* @param props - Additional props to pass to the summary. * @param props - Additional props to pass to the summary.
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
export default function SummaryUser({ user, destroyUser, running, ...props }) { export default function SummaryUser({ user, shareWithUser, unshareWithUser, destroyUser, running, ...props }) {
const { strings } = useContext(ContextLanguage) const { strings } = useContext(ContextLanguage)
return ( return (
@ -27,11 +30,39 @@ export default function SummaryUser({ user, destroyUser, running, ...props }) {
icon={user.isAdmin ? faStar : faUser} icon={user.isAdmin ? faStar : faUser}
title={user.username} title={user.username}
subtitle={user.email} subtitle={user.email}
disabled={running}
/> />
<SummaryLabels <SummaryLabels
upperLabel={strings.type} upperLabel={strings.type}
upperValue={user.isAdmin ? strings.admin : strings.user} upperValue={user.isAdmin ? strings.admin : strings.user}
/> />
{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 <SummaryButton
color={"Red"} color={"Red"}
icon={faTrash} icon={faTrash}
@ -44,6 +75,7 @@ export default function SummaryUser({ user, destroyUser, running, ...props }) {
> >
{strings.delete} {strings.delete}
</SummaryButton> </SummaryButton>
: null}
<SummaryRight/> <SummaryRight/>
</SummaryBase> </SummaryBase>
) )

View file

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

View file

@ -1,18 +1,76 @@
import React, { useContext } from "react" import React, { useCallback, useContext } from "react"
import classNames from "classnames" import classNames from "classnames"
import BoxHeader from "../components/base/BoxHeader" import BoxHeader from "../components/base/BoxHeader"
import ContextLanguage from "../contexts/ContextLanguage" import ContextLanguage from "../contexts/ContextLanguage"
import Style from "./PageShare.module.css" 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 }) { export default function PageShare({ className, ...props }) {
const { strings } = useContext(ContextLanguage) 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 ( return (
<div className={classNames(Style.PageShare, className)} {...props}> <div className={classNames(Style.PageShare, className)} {...props}>
<BoxHeader className={Style.Header}> <BoxHeader className={Style.Header}>
{strings.repoShare} {strings.repoShare}
</BoxHeader> </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> </div>
) )
} }

View file

@ -1,3 +1,27 @@
.PageShare { .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;
} }