mirror of
https://github.com/pds-nest/nest.git
synced 2025-02-16 12:43:58 +00:00
🔧 New dashboard layout
This commit is contained in:
parent
ab919f351b
commit
a506a07588
14 changed files with 249 additions and 87 deletions
|
@ -112,6 +112,8 @@ export default {
|
||||||
postUniq: "Totale utenti che hanno postato",
|
postUniq: "Totale utenti che hanno postato",
|
||||||
postPop: "Utente più attivo",
|
postPop: "Utente più attivo",
|
||||||
filters: "Filtri",
|
filters: "Filtri",
|
||||||
|
|
||||||
|
errorMissingFields: "Errore: Uno o più campi richiesti non sono stati compilati."
|
||||||
},
|
},
|
||||||
// 🇬🇧
|
// 🇬🇧
|
||||||
en: {
|
en: {
|
||||||
|
|
|
@ -14,6 +14,9 @@ import PageShare from "./routes/PageShare"
|
||||||
export default function PageSwitcher({ ...props }) {
|
export default function PageSwitcher({ ...props }) {
|
||||||
return (
|
return (
|
||||||
<Switch {...props}>
|
<Switch {...props}>
|
||||||
|
<Route path={"/repositories/create"} exact={true}>
|
||||||
|
<PageRepositoryCreate/>
|
||||||
|
</Route>
|
||||||
<Route path={"/repositories/:id/alerts"} exact={true}>
|
<Route path={"/repositories/:id/alerts"} exact={true}>
|
||||||
<PageRepositoryAlerts/>
|
<PageRepositoryAlerts/>
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -35,9 +38,6 @@ export default function PageSwitcher({ ...props }) {
|
||||||
<Route path={"/settings"} exact={true}>
|
<Route path={"/settings"} exact={true}>
|
||||||
<PageSettings/>
|
<PageSettings/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={"/dashboard"} exact={true}>
|
|
||||||
<PageRepositoryCreate/>
|
|
||||||
</Route>
|
|
||||||
<Route path={"/"}>
|
<Route path={"/"}>
|
||||||
<PageLogin/>
|
<PageLogin/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -17,7 +17,6 @@ import ContextUser from "../../contexts/ContextUser"
|
||||||
* @param destroy - Function with a single "id" parameter to call when the delete repository button is clicked.
|
* @param destroy - Function with a single "id" parameter to call when the delete repository button is clicked.
|
||||||
* @param firstLoad - If the repositories are loading and a loading message should be displayed.
|
* @param firstLoad - If the repositories are loading and a loading message should be displayed.
|
||||||
* @param running - If an action is running on the viewset.
|
* @param running - If an action is running on the viewset.
|
||||||
* @param className - Additional class(es) to append to the box.
|
|
||||||
* @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
|
||||||
|
@ -33,7 +32,6 @@ export default function BoxRepositories(
|
||||||
destroy,
|
destroy,
|
||||||
loading,
|
loading,
|
||||||
running,
|
running,
|
||||||
className,
|
|
||||||
...props
|
...props
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
|
|
@ -73,7 +73,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
{error ?
|
{error ?
|
||||||
<FormAlert color={"Red"}>
|
<FormAlert color={"Red"}>
|
||||||
{error.toString()}
|
{strings[error.data.code]}
|
||||||
</FormAlert>
|
</FormAlert>
|
||||||
: null}
|
: null}
|
||||||
{id ?
|
{id ?
|
||||||
|
@ -91,7 +91,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
||||||
style={{ "gridColumn": "2" }}
|
style={{ "gridColumn": "2" }}
|
||||||
icon={faPencilAlt}
|
icon={faPencilAlt}
|
||||||
color={"Green"}
|
color={"Green"}
|
||||||
onClick={_ => goToOnSuccess(save, history, "/repositories")()}
|
onClick={save}
|
||||||
disabled={running}
|
disabled={running}
|
||||||
>
|
>
|
||||||
{strings.save}
|
{strings.save}
|
||||||
|
@ -102,7 +102,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
||||||
style={{ "gridColumn": "1 / 3" }}
|
style={{ "gridColumn": "1 / 3" }}
|
||||||
icon={faPlus}
|
icon={faPlus}
|
||||||
color={"Green"}
|
color={"Green"}
|
||||||
onClick={_ => goToOnSuccess(save, history, "/repositories")()}
|
onClick={save}
|
||||||
disabled={running}
|
disabled={running}
|
||||||
>
|
>
|
||||||
{strings.createRepo}
|
{strings.createRepo}
|
||||||
|
|
|
@ -11,6 +11,10 @@ import BoxRepositoryCreate from "../interactive/BoxRepositoryCreate"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import ContextUser from "../../contexts/ContextUser"
|
import ContextUser from "../../contexts/ContextUser"
|
||||||
import useBackend from "../../hooks/useBackend"
|
import useBackend from "../../hooks/useBackend"
|
||||||
|
import { Condition } from "../../objects/Condition"
|
||||||
|
import useBackendResource from "../../hooks/useBackendResource"
|
||||||
|
import useBackendViewset from "../../hooks/useBackendViewset"
|
||||||
|
import { Redirect } from "react-router"
|
||||||
|
|
||||||
|
|
||||||
export default function RepositoryEditor({
|
export default function RepositoryEditor({
|
||||||
|
@ -23,74 +27,84 @@ export default function RepositoryEditor({
|
||||||
evaluation_mode: evaluationMode,
|
evaluation_mode: evaluationMode,
|
||||||
className,
|
className,
|
||||||
}) {
|
}) {
|
||||||
|
/** The currently logged in user. */
|
||||||
|
const { user } = useContext(ContextUser)
|
||||||
|
|
||||||
/** The repository name. */
|
/** The repository name. */
|
||||||
const [_name, setName] = useState(name ?? "")
|
const [_name, setName] = useState(name ?? "")
|
||||||
|
|
||||||
/** The repository state (active / archived). */
|
|
||||||
const [_isActive, setActive] = useState(isActive ?? true)
|
|
||||||
|
|
||||||
/** The start date of the data gathering. */
|
|
||||||
const [_start, setStart] = useState(start ?? new Date().toISOString())
|
|
||||||
|
|
||||||
/** The end date of the data gathering. */
|
|
||||||
const [_end, setEnd] = useState(end ?? new Date().toISOString())
|
|
||||||
|
|
||||||
/** The conditions of the data gathering. */
|
/** The conditions of the data gathering. */
|
||||||
const {
|
const {
|
||||||
value: _conditions,
|
value: rawConditions,
|
||||||
setValue: setRawConditions,
|
setValue: setRawConditions,
|
||||||
appendValue: appendRawCondition,
|
appendValue: appendRawCondition,
|
||||||
removeValue: removeRawCondition,
|
removeValue: removeRawCondition,
|
||||||
spliceValue: spliceRawCondition,
|
spliceValue: spliceRawCondition,
|
||||||
} = useArrayState(conditions)
|
} = useArrayState(conditions)
|
||||||
|
const _conditions = rawConditions.map(cond => Condition.fromRaw(cond))
|
||||||
|
|
||||||
/** The operator the conditions should be evaluated with. */
|
/** The operator the conditions should be evaluated with. */
|
||||||
const [_evaluationMode, setEvaluationMode] = useState(evaluationMode ?? 0)
|
const [_evaluationMode, setEvaluationMode] = useState(evaluationMode ?? 0)
|
||||||
|
|
||||||
const { user, fetchDataAuth } = useContext(ContextUser)
|
/** The backend viewset to use to create / edit the repository. */
|
||||||
|
const {running, error, createResource, editResource} = useBackendViewset(
|
||||||
const method = id ? "PUT" : "POST"
|
`/api/v1/repositories/`,
|
||||||
const path = id ? `/api/v1/repositories/${id}` : `/api/v1/repositories/`
|
"id",
|
||||||
const body = useMemo(
|
{
|
||||||
() => {
|
list: false,
|
||||||
return {
|
create: true,
|
||||||
"conditions": _conditions,
|
retrieve: false,
|
||||||
"end": null,
|
edit: true,
|
||||||
"evaluation_mode": _evaluationMode,
|
destroy: false,
|
||||||
"id": id,
|
command: false,
|
||||||
"is_active": true,
|
action: false,
|
||||||
"name": _name,
|
}
|
||||||
"owner": user,
|
|
||||||
"start": null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[_conditions, _evaluationMode, id, _name, user],
|
|
||||||
)
|
)
|
||||||
const { error, loading, fetchNow } = useBackend(fetchDataAuth, method, path, body)
|
|
||||||
|
|
||||||
|
/** If `true`, switches to the repository page on the next render. */
|
||||||
|
const [switchPage, setSwitchPage] = useState(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current changes, creating or editing it as needed.
|
||||||
|
*
|
||||||
|
* @type {(function(): Promise<void>)|*}
|
||||||
|
*/
|
||||||
const save = useCallback(
|
const save = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
|
const body = {
|
||||||
|
"id": id,
|
||||||
|
"name": _name,
|
||||||
|
"start": null,
|
||||||
|
"is_active": true,
|
||||||
|
"end": null,
|
||||||
|
"owner": user,
|
||||||
|
"spectators": null,
|
||||||
|
"evaluation_mode": _evaluationMode,
|
||||||
|
"conditions": _conditions,
|
||||||
|
}
|
||||||
|
|
||||||
if(!id) {
|
if(!id) {
|
||||||
console.info("Creando una nuova repository avente come corpo: ", body)
|
console.info("Creating new repository with body: ", body)
|
||||||
|
await createResource(body)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.info("Modificando la repository ", id, " con corpo: ", body)
|
console.info("Editing repository ", id, " with body: ", body)
|
||||||
|
await editResource(id, body)
|
||||||
}
|
}
|
||||||
await fetchNow()
|
setSwitchPage(true)
|
||||||
},
|
},
|
||||||
[id, body, fetchNow],
|
[id, createResource, editResource, _conditions, _evaluationMode, _name, user],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the changes made so far to the repository.
|
* Cancel the changes made so far to the repository.
|
||||||
|
*
|
||||||
|
* @type {(function(): void)|*}
|
||||||
*/
|
*/
|
||||||
const revert = useCallback(
|
const revert = useCallback(
|
||||||
() => {
|
() => {
|
||||||
setName(name)
|
setName(name)
|
||||||
setActive(isActive)
|
|
||||||
setStart(start)
|
|
||||||
setEnd(end)
|
|
||||||
setRawConditions(conditions)
|
setRawConditions(conditions)
|
||||||
setEvaluationMode(evaluationMode)
|
setEvaluationMode(evaluationMode)
|
||||||
},
|
},
|
||||||
|
@ -99,14 +113,11 @@ export default function RepositoryEditor({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to add a new condition, logging a message to the console if something goes wrong.
|
* Try to add a new condition, logging a message to the console if something goes wrong.
|
||||||
|
*
|
||||||
|
* @type {(function(): void)|*}
|
||||||
*/
|
*/
|
||||||
const addCondition = useCallback(
|
const addCondition = useCallback(
|
||||||
(newCond) => {
|
(newCond) => {
|
||||||
// Check for content
|
|
||||||
if(!newCond.content) {
|
|
||||||
console.debug("Impossibile aggiungere ", newCond, ": l'oggetto è vuoto.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicates
|
// Check for duplicates
|
||||||
let duplicate = null
|
let duplicate = null
|
||||||
|
@ -117,27 +128,29 @@ export default function RepositoryEditor({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(duplicate) {
|
if(duplicate) {
|
||||||
console.debug("Impossibile aggiungere ", newCond, ": ", duplicate, " è già esistente.")
|
console.debug("Cannot add ", newCond, ": ", duplicate, " already exists.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("Aggiungendo ", newCond, " alle condizioni del repository")
|
console.debug("Adding ", newCond, " to the repository conditions")
|
||||||
appendRawCondition(newCond)
|
appendRawCondition(newCond)
|
||||||
},
|
},
|
||||||
[_conditions, appendRawCondition],
|
[_conditions, appendRawCondition],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Hack to switch page on success
|
||||||
|
if(!error && switchPage) {
|
||||||
|
return <Redirect to={"/repositories"}/>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextRepositoryEditor.Provider
|
<ContextRepositoryEditor.Provider
|
||||||
value={{
|
value={{
|
||||||
id,
|
id,
|
||||||
name: _name, setName,
|
name: _name, setName,
|
||||||
isActive: _isActive, setActive,
|
|
||||||
start: _start, setStart,
|
|
||||||
end: _end, setEnd,
|
|
||||||
conditions: _conditions, addCondition, appendRawCondition, removeRawCondition, spliceRawCondition,
|
conditions: _conditions, addCondition, appendRawCondition, removeRawCondition, spliceRawCondition,
|
||||||
evaluationMode: _evaluationMode, setEvaluationMode,
|
evaluationMode: _evaluationMode, setEvaluationMode,
|
||||||
error, loading,
|
error, running,
|
||||||
revert, save,
|
revert, save,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -147,7 +160,7 @@ export default function RepositoryEditor({
|
||||||
<BoxConditionUser className={Style.SearchByUser}/>
|
<BoxConditionUser className={Style.SearchByUser}/>
|
||||||
<BoxConditionDatetime className={Style.SearchByTimePeriod}/>
|
<BoxConditionDatetime className={Style.SearchByTimePeriod}/>
|
||||||
<BoxConditions className={Style.Conditions}/>
|
<BoxConditions className={Style.Conditions}/>
|
||||||
<BoxRepositoryCreate running={loading} className={Style.CreateDialog}/>
|
<BoxRepositoryCreate running={running} className={Style.CreateDialog}/>
|
||||||
</div>
|
</div>
|
||||||
</ContextRepositoryEditor.Provider>
|
</ContextRepositoryEditor.Provider>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useContext, useState } from "react"
|
import { useCallback, useContext, useState } from "react"
|
||||||
import ContextServer from "../contexts/ContextServer"
|
import ContextServer from "../contexts/ContextServer"
|
||||||
import ContextUser from "../contexts/ContextUser"
|
import ContextUser from "../contexts/ContextUser"
|
||||||
|
import { ServerNotConfiguredError, FetchAlreadyRunningError, DecodeError, ResultError } from "../objects/Errors"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
import useBackendRequest from "./useBackendRequest"
|
import useBackendRequest from "./useBackendRequest"
|
||||||
|
import { ViewNotAllowedError } from "../objects/Errors"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,9 +73,10 @@ export default function useBackendResource(
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
setError(e)
|
setError(e)
|
||||||
throw e
|
return
|
||||||
}
|
}
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
setResource(refreshedResource)
|
setResource(refreshedResource)
|
||||||
return refreshedResource
|
return refreshedResource
|
||||||
},
|
},
|
||||||
|
@ -89,9 +91,10 @@ export default function useBackendResource(
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
setError(e)
|
setError(e)
|
||||||
throw e
|
return
|
||||||
}
|
}
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
setResource(editedResource)
|
setResource(editedResource)
|
||||||
return editedResource
|
return editedResource
|
||||||
},
|
},
|
||||||
|
@ -105,9 +108,10 @@ export default function useBackendResource(
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
setError(e)
|
setError(e)
|
||||||
throw e
|
return
|
||||||
}
|
}
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
setResource(null)
|
setResource(null)
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
import useBackendRequest from "./useBackendRequest"
|
import useBackendRequest from "./useBackendRequest"
|
||||||
|
import { ViewNotAllowedError } from "../objects/Errors"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,60 +99,97 @@ export default function useBackendViewset(resourcesPath, pkName,
|
||||||
|
|
||||||
const listResources = useCallback(
|
const listResources = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
|
let res
|
||||||
try {
|
try {
|
||||||
setResources(await apiList())
|
res = await apiList()
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
setError(e)
|
setError(e)
|
||||||
throw e
|
return
|
||||||
}
|
}
|
||||||
setError(null)
|
setError(null)
|
||||||
return {}
|
setResources(res)
|
||||||
},
|
},
|
||||||
[apiList],
|
[apiList],
|
||||||
)
|
)
|
||||||
|
|
||||||
const retrieveResource = useCallback(
|
const retrieveResource = useCallback(
|
||||||
async (pk) => {
|
async (pk) => {
|
||||||
const refreshedResource = await apiRetrieve(pk)
|
let res
|
||||||
setResources(res => res.map(resource => {
|
try {
|
||||||
|
res = await apiRetrieve(pk)
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
setError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
setResources(r => r.map(resource => {
|
||||||
if(resource[pkName] === pk) {
|
if(resource[pkName] === pk) {
|
||||||
return refreshedResource
|
return res
|
||||||
}
|
}
|
||||||
return resource
|
return resource
|
||||||
}))
|
}))
|
||||||
return refreshedResource
|
|
||||||
|
return res
|
||||||
},
|
},
|
||||||
[apiRetrieve, pkName],
|
[apiRetrieve, pkName],
|
||||||
)
|
)
|
||||||
|
|
||||||
const createResource = useCallback(
|
const createResource = useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
const newResource = await apiCreate(data)
|
let res
|
||||||
setResources(res => [...res, newResource])
|
try {
|
||||||
return newResource
|
res = await apiCreate(data)
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
setError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
setResources(r => [...r, res])
|
||||||
|
return res
|
||||||
},
|
},
|
||||||
[apiCreate],
|
[apiCreate],
|
||||||
)
|
)
|
||||||
|
|
||||||
const editResource = useCallback(
|
const editResource = useCallback(
|
||||||
async (pk, data) => {
|
async (pk, data) => {
|
||||||
const editedResource = await apiEdit(pk, data)
|
let res
|
||||||
setResources(res => res.map(resource => {
|
try {
|
||||||
|
res = await apiEdit(pk, data)
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
setError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
setResources(r => r.map(resource => {
|
||||||
if(resource[pkName] === pk) {
|
if(resource[pkName] === pk) {
|
||||||
return editedResource
|
return res
|
||||||
}
|
}
|
||||||
return resource
|
return resource
|
||||||
}))
|
}))
|
||||||
return editedResource
|
return res
|
||||||
},
|
},
|
||||||
[apiEdit, pkName],
|
[apiEdit, pkName],
|
||||||
)
|
)
|
||||||
|
|
||||||
const destroyResource = useCallback(
|
const destroyResource = useCallback(
|
||||||
async (pk) => {
|
async (pk) => {
|
||||||
await apiDestroy(pk)
|
try {
|
||||||
setResources(res => res.filter(resource => resource[pkName] !== pk))
|
await apiDestroy(pk)
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
setError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
setResources(r => r.filter(resource => resource[pkName] !== pk))
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
[apiDestroy, pkName],
|
[apiDestroy, pkName],
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {
|
||||||
faQuestionCircle,
|
faQuestionCircle,
|
||||||
IconDefinition,
|
IconDefinition,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import TimeRay from "./TimeRay"
|
||||||
|
import MapArea from "./MapArea"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +26,16 @@ export class Condition {
|
||||||
this.id = id
|
this.id = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
console.debug("Trying to serialize condition: ", data)
|
||||||
|
|
||||||
|
if(data.type === 0) return ConditionHashtag.fromRaw(data)
|
||||||
|
else if(data.type === 2) return ConditionTime.fromRaw(data)
|
||||||
|
else if(data.type === 3) return ConditionLocation.fromRaw(data)
|
||||||
|
else if(data.type === 5) return ConditionUser.fromRaw(data)
|
||||||
|
else return new Condition(data.type, data.content, data.id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the condition as an object readable by the backend.
|
* Get the condition as an object readable by the backend.
|
||||||
*
|
*
|
||||||
|
@ -61,6 +73,10 @@ export class ConditionHashtag extends Condition {
|
||||||
super(0, hashtag, id)
|
super(0, hashtag, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
return new ConditionHashtag(data.content, data.id)
|
||||||
|
}
|
||||||
|
|
||||||
display() {
|
display() {
|
||||||
return {
|
return {
|
||||||
color: "Grey",
|
color: "Grey",
|
||||||
|
@ -80,6 +96,10 @@ export class ConditionUser extends Condition {
|
||||||
super(5, user, id)
|
super(5, user, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
return new ConditionUser(data.content, data.id)
|
||||||
|
}
|
||||||
|
|
||||||
display() {
|
display() {
|
||||||
return {
|
return {
|
||||||
color: "Green",
|
color: "Green",
|
||||||
|
@ -102,6 +122,10 @@ export class ConditionTime extends Condition {
|
||||||
this.timeRay = timeRay
|
this.timeRay = timeRay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
return new ConditionTime(TimeRay.fromRaw(data.content), data.id)
|
||||||
|
}
|
||||||
|
|
||||||
display() {
|
display() {
|
||||||
return {
|
return {
|
||||||
color: "Yellow",
|
color: "Yellow",
|
||||||
|
@ -124,6 +148,10 @@ export class ConditionLocation extends Condition {
|
||||||
this.mapArea = mapArea
|
this.mapArea = mapArea
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
return new ConditionLocation(MapArea.fromRaw(data.content), data.id)
|
||||||
|
}
|
||||||
|
|
||||||
display() {
|
display() {
|
||||||
return {
|
return {
|
||||||
color: "Red",
|
color: "Red",
|
||||||
|
@ -133,3 +161,4 @@ export class ConditionLocation extends Condition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Error thrown when a function is not implemented in the current class/instance.
|
* Error thrown when a function is not implemented in the current class/instance.
|
||||||
*/
|
*/
|
||||||
class NotImplementedError {
|
export class NotImplementedError {
|
||||||
name
|
name
|
||||||
|
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
|
@ -13,7 +13,7 @@ class NotImplementedError {
|
||||||
/**
|
/**
|
||||||
* An error in the N.E.S.T. frontend-backend communication.
|
* An error in the N.E.S.T. frontend-backend communication.
|
||||||
*/
|
*/
|
||||||
class BackendCommunicationError {
|
export class BackendCommunicationError {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class BackendCommunicationError {
|
||||||
/**
|
/**
|
||||||
* Error thrown when trying to access a backend view which doesn't exist or isn't allowed in the used hook.
|
* Error thrown when trying to access a backend view which doesn't exist or isn't allowed in the used hook.
|
||||||
*/
|
*/
|
||||||
class ViewNotAllowedError extends BackendCommunicationError {
|
export class ViewNotAllowedError extends BackendCommunicationError {
|
||||||
view
|
view
|
||||||
|
|
||||||
constructor(view) {
|
constructor(view) {
|
||||||
|
@ -35,7 +35,7 @@ class ViewNotAllowedError extends BackendCommunicationError {
|
||||||
/**
|
/**
|
||||||
* Error thrown when trying to access a backend view when outside a {@link ContextServer}.
|
* Error thrown when trying to access a backend view when outside a {@link ContextServer}.
|
||||||
*/
|
*/
|
||||||
class ServerNotConfiguredError extends BackendCommunicationError {
|
export class ServerNotConfiguredError extends BackendCommunicationError {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class ServerNotConfiguredError extends BackendCommunicationError {
|
||||||
*
|
*
|
||||||
* This is not allowed due to potential race conditions.
|
* This is not allowed due to potential race conditions.
|
||||||
*/
|
*/
|
||||||
class FetchAlreadyRunningError extends BackendCommunicationError {
|
export class FetchAlreadyRunningError extends BackendCommunicationError {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class FetchAlreadyRunningError extends BackendCommunicationError {
|
||||||
/**
|
/**
|
||||||
* Abstract class for {@link DecodeError} and {@link ResultError}.
|
* Abstract class for {@link DecodeError} and {@link ResultError}.
|
||||||
*/
|
*/
|
||||||
class FetchError extends BackendCommunicationError {
|
export class FetchError extends BackendCommunicationError {
|
||||||
status
|
status
|
||||||
statusText
|
statusText
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class FetchError extends BackendCommunicationError {
|
||||||
/**
|
/**
|
||||||
* Error thrown when the frontend can't parse the data received from the backend.
|
* Error thrown when the frontend can't parse the data received from the backend.
|
||||||
*/
|
*/
|
||||||
class DecodeError extends FetchError {
|
export class DecodeError extends FetchError {
|
||||||
error
|
error
|
||||||
|
|
||||||
constructor(status, statusText, error) {
|
constructor(status, statusText, error) {
|
||||||
|
@ -83,7 +83,7 @@ class DecodeError extends FetchError {
|
||||||
/**
|
/**
|
||||||
* Error thrown when the backend returns a falsy `"result"` value.
|
* Error thrown when the backend returns a falsy `"result"` value.
|
||||||
*/
|
*/
|
||||||
class ResultError extends FetchError {
|
export class ResultError extends FetchError {
|
||||||
status
|
status
|
||||||
statusText
|
statusText
|
||||||
data
|
data
|
||||||
|
@ -102,3 +102,12 @@ class ResultError extends FetchError {
|
||||||
return this.data.code
|
return this.data.code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class SerializationError {
|
||||||
|
invalidString
|
||||||
|
|
||||||
|
constructor(invalidString) {
|
||||||
|
this.invalidString = invalidString
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import { getDistance } from "geolib"
|
import { getDistance } from "geolib"
|
||||||
import osmZoomLevels from "../utils/osmZoomLevels"
|
import osmZoomLevels from "../utils/osmZoomLevels"
|
||||||
|
import Coordinates from "./Coordinates"
|
||||||
|
import { SerializationError } from "./Errors"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,6 +34,21 @@ export default class MapArea {
|
||||||
return new MapArea(osmZoomLevels[zoom], center)
|
return new MapArea(osmZoomLevels[zoom], center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static rawRegex = /^< (?<radius>[0-9.]+) (?<lat>[0-9.]+) (?<lng>[0-9.]+)$/
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
const match = this.rawRegex.exec(data)
|
||||||
|
|
||||||
|
if(!match) throw new SerializationError(data)
|
||||||
|
|
||||||
|
const radius = Number(match.groups.radius)
|
||||||
|
const lat = Number(match.groups.lat)
|
||||||
|
const lng = Number(match.groups.lng)
|
||||||
|
const center = new Coordinates(lat, lng)
|
||||||
|
|
||||||
|
return new MapArea(radius, center)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { SerializationError } from "./Errors"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An half-line of time, defined by a `date` and a boolean `isBefore` indicating if the time before or after the
|
* An half-line of time, defined by a `date` and a boolean `isBefore` indicating if the time before or after the
|
||||||
* specified date should be selected.
|
* specified date should be selected.
|
||||||
|
@ -15,6 +18,18 @@ export default class TimeRay {
|
||||||
this.date = date
|
this.date = date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static rawRegex = /^(?<isBefore>[><]) (?<date>.+)$/
|
||||||
|
|
||||||
|
static fromRaw(data) {
|
||||||
|
const match = this.rawRegex.exec(data)
|
||||||
|
|
||||||
|
if(!match) throw new SerializationError(data)
|
||||||
|
|
||||||
|
const isBefore = match.groups.isBefore === "<"
|
||||||
|
const date = new Date(match.groups.date)
|
||||||
|
return new TimeRay(isBefore, date)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,7 +5,10 @@ import useBackendViewset from "../hooks/useBackendViewset"
|
||||||
import BoxRepositories from "../components/interactive/BoxRepositories"
|
import BoxRepositories from "../components/interactive/BoxRepositories"
|
||||||
import { useHistory } from "react-router"
|
import { useHistory } from "react-router"
|
||||||
import ContextLanguage from "../contexts/ContextLanguage"
|
import ContextLanguage from "../contexts/ContextLanguage"
|
||||||
import ContextUser from "../contexts/ContextUser"
|
import BoxHeader from "../components/base/BoxHeader"
|
||||||
|
import { faHome, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import Button from "../components/base/Button"
|
||||||
|
|
||||||
|
|
||||||
export default function PageRepositoriesList({ children, className, ...props }) {
|
export default function PageRepositoriesList({ children, className, ...props }) {
|
||||||
|
@ -25,7 +28,20 @@ export default function PageRepositoriesList({ children, className, ...props })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
||||||
|
<BoxHeader className={Style.Header}>
|
||||||
|
<FontAwesomeIcon icon={faHome}/> {strings.dashboard}
|
||||||
|
</BoxHeader>
|
||||||
|
<div className={Style.Buttons}>
|
||||||
|
<Button
|
||||||
|
icon={faPlus}
|
||||||
|
color={"Green"}
|
||||||
|
onClick={() => history.push("/repositories/create")}
|
||||||
|
>
|
||||||
|
{strings.createRepo}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<BoxRepositories
|
<BoxRepositories
|
||||||
|
className={Style.ActiveRepositories}
|
||||||
header={strings.menuActive}
|
header={strings.menuActive}
|
||||||
loading={!bv.firstLoad}
|
loading={!bv.firstLoad}
|
||||||
running={bv.running}
|
running={bv.running}
|
||||||
|
@ -37,6 +53,7 @@ export default function PageRepositoriesList({ children, className, ...props })
|
||||||
edit={pk => history.push(`/repositories/${pk}/edit`)}
|
edit={pk => history.push(`/repositories/${pk}/edit`)}
|
||||||
/>
|
/>
|
||||||
<BoxRepositories
|
<BoxRepositories
|
||||||
|
className={Style.ArchivedRepositories}
|
||||||
header={strings.menuArchived}
|
header={strings.menuArchived}
|
||||||
loading={!bv.firstLoad}
|
loading={!bv.firstLoad}
|
||||||
running={bv.running}
|
running={bv.running}
|
||||||
|
|
|
@ -2,15 +2,34 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"a"
|
"h x"
|
||||||
"b";
|
"a a"
|
||||||
|
"b b";
|
||||||
|
grid-template-columns: 4fr 1fr;
|
||||||
|
grid-template-rows: auto 1fr 1fr;
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Header {
|
||||||
|
grid-area: h;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Buttons {
|
||||||
|
grid-area: x;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Buttons > * {
|
||||||
|
box-shadow: none;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.ActiveRepositories {
|
.ActiveRepositories {
|
||||||
grid-area: a;
|
grid-area: a;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue