mirror of
https://github.com/pds-nest/nest.git
synced 2025-02-18 05:33:58 +00:00
Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
b5af4283ce
9 changed files with 172 additions and 32 deletions
|
@ -7,6 +7,7 @@ import PageSettings from "./routes/PageSettings"
|
||||||
import PageSandbox from "./routes/PageSandbox"
|
import PageSandbox from "./routes/PageSandbox"
|
||||||
import PageDashboard from "./routes/PageDashboard"
|
import PageDashboard from "./routes/PageDashboard"
|
||||||
import PageRoot from "./routes/PageRoot"
|
import PageRoot from "./routes/PageRoot"
|
||||||
|
import PageEdit from "./routes/PageEdit"
|
||||||
|
|
||||||
|
|
||||||
export default function PageSwitcher({ ...props }) {
|
export default function PageSwitcher({ ...props }) {
|
||||||
|
@ -15,6 +16,9 @@ export default function PageSwitcher({ ...props }) {
|
||||||
<Route path={"/login"} exact={true}>
|
<Route path={"/login"} exact={true}>
|
||||||
<PageLogin/>
|
<PageLogin/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path={"/repositories/:id/edit"} exact={true}>
|
||||||
|
<PageEdit/>
|
||||||
|
</Route>
|
||||||
<Route path={"/repositories"} exact={true}>
|
<Route path={"/repositories"} exact={true}>
|
||||||
<PageRepositories/>
|
<PageRepositories/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -18,18 +18,15 @@ import useDataImmediately from "../../hooks/useDataImmediately"
|
||||||
*/
|
*/
|
||||||
export default function BoxRepositoriesActive({ ...props }) {
|
export default function BoxRepositoriesActive({ ...props }) {
|
||||||
const {user, fetchDataAuth} = useContext(ContextUser)
|
const {user, fetchDataAuth} = useContext(ContextUser)
|
||||||
const {data, started, loading, error} = useDataImmediately(fetchDataAuth, "GET", "/api/v1/repositories/", {
|
const {data, error} = useDataImmediately(fetchDataAuth, "GET", "/api/v1/repositories/", {
|
||||||
"onlyActive": true,
|
"onlyActive": true,
|
||||||
})
|
})
|
||||||
|
|
||||||
let contents;
|
let contents;
|
||||||
if(!started || loading) {
|
if(error) {
|
||||||
contents = <Loading/>
|
|
||||||
}
|
|
||||||
else if(error) {
|
|
||||||
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
||||||
}
|
}
|
||||||
else {
|
else if(data) {
|
||||||
let repositories = [...data["owner"], ...data["spectator"]]
|
let repositories = [...data["owner"], ...data["spectator"]]
|
||||||
if(repositories.length > 0) {
|
if(repositories.length > 0) {
|
||||||
contents = repositories.map(repo => (
|
contents = repositories.map(repo => (
|
||||||
|
@ -46,6 +43,9 @@ export default function BoxRepositoriesActive({ ...props }) {
|
||||||
contents = <i>There's nothing here.</i>
|
contents = <i>There's nothing here.</i>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
contents = <Loading/>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoxFull header={"Your active repositories"} {...props}>
|
<BoxFull header={"Your active repositories"} {...props}>
|
||||||
|
|
|
@ -17,18 +17,15 @@ import useDataImmediately from "../../hooks/useDataImmediately"
|
||||||
*/
|
*/
|
||||||
export default function BoxRepositoriesArchived({ ...props }) {
|
export default function BoxRepositoriesArchived({ ...props }) {
|
||||||
const {user, fetchDataAuth} = useContext(ContextUser)
|
const {user, fetchDataAuth} = useContext(ContextUser)
|
||||||
const {data, started, loading, error} = useDataImmediately(fetchDataAuth, "GET", "/api/v1/repositories/", {
|
const {data, error} = useDataImmediately(fetchDataAuth, "GET", "/api/v1/repositories/", {
|
||||||
"onlyDead": true,
|
"onlyDead": true,
|
||||||
})
|
})
|
||||||
|
|
||||||
let contents;
|
let contents;
|
||||||
if(!started || loading) {
|
if(error) {
|
||||||
contents = <Loading/>
|
|
||||||
}
|
|
||||||
else if(error) {
|
|
||||||
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
||||||
}
|
}
|
||||||
else {
|
else if(data) {
|
||||||
let repositories = [...data["owner"], ...data["spectator"]]
|
let repositories = [...data["owner"], ...data["spectator"]]
|
||||||
if(repositories.length > 0) {
|
if(repositories.length > 0) {
|
||||||
contents = repositories.map(repo => (
|
contents = repositories.map(repo => (
|
||||||
|
@ -45,6 +42,9 @@ export default function BoxRepositoriesArchived({ ...props }) {
|
||||||
contents = <i>There's nothing here.</i>
|
contents = <i>There's nothing here.</i>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
contents = <Loading/>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoxFull header={"Your archived repositories"} {...props}>
|
<BoxFull header={"Your archived repositories"} {...props}>
|
||||||
|
|
|
@ -3,15 +3,23 @@ import BoxFull from "../base/BoxFull"
|
||||||
import FormLabelled from "../base/FormLabelled"
|
import FormLabelled from "../base/FormLabelled"
|
||||||
import FormLabel from "../base/formparts/FormLabel"
|
import FormLabel from "../base/formparts/FormLabel"
|
||||||
import InputWithIcon from "../base/InputWithIcon"
|
import InputWithIcon from "../base/InputWithIcon"
|
||||||
import { faFolder, faPlus } from "@fortawesome/free-solid-svg-icons"
|
import { faFolder, faPencilAlt, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||||
import Radio from "../base/Radio"
|
import Radio from "../base/Radio"
|
||||||
import Button from "../base/Button"
|
import Button from "../base/Button"
|
||||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||||
import FormAlert from "../base/formparts/FormAlert"
|
import FormAlert from "../base/formparts/FormAlert"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link BoxFull} allowing the user to save the changes made in the current {@link RepositoryEditor}.
|
||||||
|
*
|
||||||
|
* @param props
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
export default function BoxRepositoryCreate({ ...props }) {
|
export default function BoxRepositoryCreate({ ...props }) {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
evaluationMode,
|
evaluationMode,
|
||||||
setEvaluationMode,
|
setEvaluationMode,
|
||||||
name,
|
name,
|
||||||
|
@ -35,7 +43,7 @@ export default function BoxRepositoryCreate({ ...props }) {
|
||||||
<label>
|
<label>
|
||||||
<Radio
|
<Radio
|
||||||
name={"filter-mode"}
|
name={"filter-mode"}
|
||||||
onClick={() => setEvaluationMode(0)}
|
onChange={() => setEvaluationMode(0)}
|
||||||
checked={evaluationMode === 0}
|
checked={evaluationMode === 0}
|
||||||
/>
|
/>
|
||||||
At least one filter
|
At least one filter
|
||||||
|
@ -44,7 +52,7 @@ export default function BoxRepositoryCreate({ ...props }) {
|
||||||
<label>
|
<label>
|
||||||
<Radio
|
<Radio
|
||||||
name={"filter-mode"}
|
name={"filter-mode"}
|
||||||
onClick={() => setEvaluationMode(1)}
|
onChange={() => setEvaluationMode(1)}
|
||||||
checked={evaluationMode === 1}
|
checked={evaluationMode === 1}
|
||||||
/>
|
/>
|
||||||
Every filter
|
Every filter
|
||||||
|
@ -55,9 +63,28 @@ export default function BoxRepositoryCreate({ ...props }) {
|
||||||
{error.toString()}
|
{error.toString()}
|
||||||
</FormAlert>
|
</FormAlert>
|
||||||
: null}
|
: null}
|
||||||
<Button style={{"gridColumn": "1 / 3"}} icon={faPlus} color={"Green"} onClick={e => save()}>
|
{id ?
|
||||||
|
<Button
|
||||||
|
style={{"gridColumn": "1 / 3"}}
|
||||||
|
icon={faPencilAlt}
|
||||||
|
color={"Green"}
|
||||||
|
goTo={"/repositories"}
|
||||||
|
onClick={e => save()}
|
||||||
|
>
|
||||||
|
Edit repository
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
<Button
|
||||||
|
style={{"gridColumn": "1 / 3"}}
|
||||||
|
icon={faPlus}
|
||||||
|
color={"Green"}
|
||||||
|
goTo={"/repositories"}
|
||||||
|
onClick={e => save()}
|
||||||
|
>
|
||||||
Create repository
|
Create repository
|
||||||
</Button>
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
</FormLabelled>
|
</FormLabelled>
|
||||||
</BoxFull>
|
</BoxFull>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import React from "react"
|
import React, { useContext } from "react"
|
||||||
import Style from "./RepositorySummaryBase.module.css"
|
import Style from "./RepositorySummaryBase.module.css"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
import Button from "../base/Button"
|
import Button from "../base/Button"
|
||||||
import { faArchive, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
import { faArchive, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import { useHistory } from "react-router"
|
||||||
|
import useData from "../../hooks/useData"
|
||||||
|
import ContextUser from "../../contexts/ContextUser"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A long line representing a repository in a list.
|
* A long line representing a repository in a list.
|
||||||
*
|
*
|
||||||
|
* @param id - The id of the repository.
|
||||||
|
* @param owner - The owner of the repository.
|
||||||
* @param icon - The FontAwesome IconDefinition that represents the repository.
|
* @param icon - The FontAwesome IconDefinition that represents the repository.
|
||||||
* @param name - The title of the repository.
|
* @param name - The title of the repository.
|
||||||
* @todo What goes in the details field?
|
|
||||||
* @param details - Whatever should be rendered in the details field.
|
|
||||||
* @param start - The start date of the repository.
|
* @param start - The start date of the repository.
|
||||||
* @param end - The end date of the repository.
|
* @param end - The end date of the repository.
|
||||||
* @param isActive - Whether the repository is active or not.
|
* @param isActive - Whether the repository is active or not.
|
||||||
|
@ -25,8 +28,14 @@ import { faArchive, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-ico
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export default function RepositorySummaryBase(
|
export default function RepositorySummaryBase(
|
||||||
{ icon, name, details, start, end, isActive, canDelete, canEdit, canArchive, className, ...props }
|
{ id, owner, icon, name, start, end, isActive, canDelete, canEdit, canArchive, className, ...props }
|
||||||
) {
|
) {
|
||||||
|
const {fetchDataAuth} = useContext(ContextUser)
|
||||||
|
const {history} = useHistory()
|
||||||
|
const {fetchNow: archiveThis} = useData(fetchDataAuth, "PATCH", `/api/v1/repositories/${id}`, {"close": true})
|
||||||
|
const {fetchNow: unarchiveThis} = useData(fetchDataAuth, "PATCH", `/api/v1/repositories/${id}`, {"open": true})
|
||||||
|
const {fetchNow: deletThis} = useData(fetchDataAuth, "DELETE", `/api/v1/repositories/${id}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.RepositorySummary, className)} {...props}>
|
<div className={classNames(Style.RepositorySummary, className)} {...props}>
|
||||||
<div className={Style.Left}>
|
<div className={Style.Left}>
|
||||||
|
@ -36,22 +45,45 @@ export default function RepositorySummaryBase(
|
||||||
<div className={Style.Title}>
|
<div className={Style.Title}>
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div className={Style.StartDate}>
|
<div className={Style.Author}>
|
||||||
{start}
|
{owner["username"]}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={Style.Middle}>
|
<div className={Style.Middle}>
|
||||||
{details}
|
<div className={Style.StartDate}>
|
||||||
|
Start: {start}
|
||||||
|
</div>
|
||||||
|
<div className={Style.EndDate}>
|
||||||
|
End: {end}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={Style.Right}>
|
<div className={Style.Right}>
|
||||||
{canDelete ?
|
{canDelete ?
|
||||||
<Button color={"Red"} icon={faTrash}>Delete</Button>
|
<Button
|
||||||
|
color={"Red"}
|
||||||
|
icon={faTrash}
|
||||||
|
onClick={deletThis}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
: null}
|
: null}
|
||||||
{canEdit ?
|
{canEdit ?
|
||||||
<Button color={"Yellow"} icon={faPencilAlt}>Edit</Button>
|
<Button
|
||||||
|
color={"Yellow"}
|
||||||
|
icon={faPencilAlt}
|
||||||
|
onClick={() => history.push(`/repositories/${id}/edit`)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
: null}
|
: null}
|
||||||
{canArchive ?
|
{canArchive ?
|
||||||
<Button color={"Grey"} icon={faArchive}>{isActive ? "Archive" : "Unarchive"}</Button>
|
<Button
|
||||||
|
color={"Grey"}
|
||||||
|
icon={faArchive}
|
||||||
|
onClick={isActive ? archiveThis : unarchiveThis}
|
||||||
|
>
|
||||||
|
{isActive ? "Archive" : "Unarchive"}
|
||||||
|
</Button>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.StartDate {
|
.Author {
|
||||||
grid-area: d;
|
grid-area: d;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
|
||||||
|
@ -59,10 +59,23 @@
|
||||||
|
|
||||||
.Middle {
|
.Middle {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
|
||||||
background-color: var(--bg-light);
|
background-color: var(--bg-light);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.StartDate {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.EndDate {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Right {
|
.Right {
|
||||||
|
|
|
@ -20,16 +20,19 @@ export default function useData(fetchData, method, path, body, init) {
|
||||||
*/
|
*/
|
||||||
const load = useCallback(
|
const load = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
console.debug(`Trying to ${method} ${path}...`)
|
console.debug(`Loading ${method} ${path}...`)
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
|
console.debug(`Fetching ${method} ${path}...`)
|
||||||
try {
|
try {
|
||||||
const _data = await fetchData(method, path, body, init)
|
const _data = await fetchData(method, path, body, init)
|
||||||
|
console.debug(`Displaying data of ${method} ${path}: `, _data)
|
||||||
setData(_data)
|
setData(_data)
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.debug(`Displaying error of ${method} ${path}: `, e)
|
||||||
setError(e)
|
setError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
console.debug(`Stopping loading of ${method} ${path}...`)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
38
code/frontend/src/routes/PageEdit.js
Normal file
38
code/frontend/src/routes/PageEdit.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useContext } from "react"
|
||||||
|
import Style from "./PageDashboard.module.css"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import BoxHeader from "../components/base/BoxHeader"
|
||||||
|
import RepositoryEditor from "../components/providers/RepositoryEditor"
|
||||||
|
import useDataImmediately from "../hooks/useDataImmediately"
|
||||||
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
import BoxAlert from "../components/base/BoxAlert"
|
||||||
|
import RepositorySummaryBase from "../components/interactive/RepositorySummaryBase"
|
||||||
|
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import Loading from "../components/base/Loading"
|
||||||
|
import BoxFull from "../components/base/BoxFull"
|
||||||
|
|
||||||
|
|
||||||
|
export default function PageEdit({ id, className, ...props }) {
|
||||||
|
const {fetchDataAuth} = useContext(ContextUser)
|
||||||
|
const {data, error} = useDataImmediately(fetchDataAuth, "GET", `/api/v1/repositories/${id}`)
|
||||||
|
|
||||||
|
let contents;
|
||||||
|
if(error) {
|
||||||
|
contents = <BoxAlert color={"Red"}>{error.toString()}</BoxAlert>
|
||||||
|
}
|
||||||
|
else if(data) {
|
||||||
|
contents = <RepositoryEditor className={Style.RepositoryEditor} {...data}/>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contents = <Loading/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(Style.PageHome, className)} {...props}>
|
||||||
|
<BoxHeader className={Style.Header}>
|
||||||
|
Edit repository
|
||||||
|
</BoxHeader>
|
||||||
|
{contents}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
23
code/frontend/src/utils/goToOnSuccess.js
Normal file
23
code/frontend/src/utils/goToOnSuccess.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Decorator which adds a redirect on success to an event handler.
|
||||||
|
*
|
||||||
|
* @param func - The function to decorate.
|
||||||
|
* @param history - The history to push the destination to.
|
||||||
|
* @param destination - The path of the destination.
|
||||||
|
* @returns {(function(): void)|*}
|
||||||
|
*/
|
||||||
|
export default function goToOnSuccess(func, history, destination) {
|
||||||
|
return ([...args]) => {
|
||||||
|
let success = false
|
||||||
|
try {
|
||||||
|
func(...args)
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
if(success) {
|
||||||
|
history.push(destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue