mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-22 04:54:18 +00:00
Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
b65af5615c
39 changed files with 800 additions and 392 deletions
|
@ -71,6 +71,11 @@ export default {
|
|||
alerts: "Allarmi",
|
||||
alertTitle: "I tuoi allarmi",
|
||||
alertCreate: "Crea un allarme",
|
||||
alertName: "Nome allarme", // TODO: tradurre
|
||||
createAlert: "Crea allarme", // TODO: tradurre
|
||||
alertLimit: "Limite", // TODO: tradurre e migliorare?
|
||||
alertWindow: "Finestra (in ore)", // TODO: tradurre
|
||||
|
||||
notImplemented: "🚧 Non implementato.",
|
||||
|
||||
settings: "Impostazioni",
|
||||
|
@ -141,7 +146,8 @@ export default {
|
|||
errorViewNotAllowed: "Errore: Non è permesso effettuare la richiesta.",
|
||||
errorServerNotConfigured: "Errore: Non è stato configurato nessun server.",
|
||||
errorDecodeError: "Errore: Non è stato possibile deserializzare i dati ricevuti dal backend.",
|
||||
errorSerializationError: "Errore: Non è stato possibile serializzare i dati da inviare al backend."
|
||||
errorSerializationError: "Errore: Non è stato possibile serializzare i dati da inviare al backend.",
|
||||
errorPageNotFound: "Errore: Pagina non trovata.", // TODO: Tradurre
|
||||
},
|
||||
// 🇬🇧
|
||||
en: {
|
||||
|
|
|
@ -9,15 +9,25 @@ import PageRepositoryEdit from "./routes/PageRepositoryEdit"
|
|||
import PageUsers from "./routes/PageUsers"
|
||||
import PageRepositoryAnalyze from "./routes/PageRepositoryAnalyze"
|
||||
import PageShare from "./routes/PageShare"
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"
|
||||
import makeIcon from "./utils/makeIcon"
|
||||
import useStrings from "./hooks/useStrings"
|
||||
import Alert from "./components/base/Alert"
|
||||
import PageRepositoryAlertsCreate from "./routes/PageRepositoryAlertsCreate"
|
||||
|
||||
|
||||
export default function PageSwitcher({ ...props }) {
|
||||
const strings = useStrings()
|
||||
|
||||
return (
|
||||
<Switch {...props}>
|
||||
<Route path={"/repositories/create"} exact={true}>
|
||||
<PageRepositoryCreate/>
|
||||
</Route>
|
||||
<Route path={"/repositories/:id/alerts"} exact={true}>
|
||||
<Route path={"/repositories/:id/alerts/create"} exact={true}>
|
||||
<PageRepositoryAlertsCreate/>
|
||||
</Route>
|
||||
<Route path={"/repositories/:id/alerts/"} exact={true}>
|
||||
<PageRepositoryAlerts/>
|
||||
</Route>
|
||||
<Route path={"/repositories/:id/share"} exact={true}>
|
||||
|
@ -38,9 +48,12 @@ export default function PageSwitcher({ ...props }) {
|
|||
<Route path={"/settings"} exact={true}>
|
||||
<PageSettings/>
|
||||
</Route>
|
||||
<Route path={"/"}>
|
||||
<Route path={"/"} exact={true}>
|
||||
<PageLogin/>
|
||||
</Route>
|
||||
<Route>
|
||||
<Alert color={"Red"}>{makeIcon(faQuestionCircle)} {strings.errorPageNotFound}</Alert>
|
||||
</Route>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
|
19
nest_frontend/components/base/ButtonHeader.js
Normal file
19
nest_frontend/components/base/ButtonHeader.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from "react"
|
||||
import Style from "./ButtonHeader.module.css"
|
||||
import classNames from "classnames"
|
||||
import Button from "./Button"
|
||||
|
||||
|
||||
/**
|
||||
* A {@link Button} without `boxShadow` and with `flexGrow`, to be used in {@link PageWithHeader}.
|
||||
*
|
||||
* @param className - Additional class(es) to add to the button.
|
||||
* @param props - Additional props to pass to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function ButtonHeader({ className, ...props }) {
|
||||
return (
|
||||
<Button className={classNames(Style.ButtonHeader, className)} {...props}/>
|
||||
)
|
||||
}
|
4
nest_frontend/components/base/ButtonHeader.module.css
Normal file
4
nest_frontend/components/base/ButtonHeader.module.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.ButtonHeader {
|
||||
box-shadow: none;
|
||||
flex-grow: 1;
|
||||
}
|
12
nest_frontend/components/base/layout/BodyFlex.js
Normal file
12
nest_frontend/components/base/layout/BodyFlex.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
import Style from "./BodyFlex.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function BodyFlex({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.BodyFlex, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
.PageLogin {
|
||||
.BodyFlex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
17
nest_frontend/components/base/layout/BodyHorizontalHalves.js
Normal file
17
nest_frontend/components/base/layout/BodyHorizontalHalves.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from "react"
|
||||
import Style from "./BodyHorizontalHalves.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function BodyHorizontalHalves({ upper, lower, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.BodyHorizontalHalves, className)} {...props}>
|
||||
<div className={Style.Upper}>
|
||||
{upper}
|
||||
</div>
|
||||
<div className={Style.Lower}>
|
||||
{lower}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
.BodyHorizontalHalves {
|
||||
display: grid;
|
||||
grid-template-areas: "upper" "lower";
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.Upper {
|
||||
grid-area: upper;
|
||||
}
|
||||
|
||||
.Upper > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Lower {
|
||||
grid-area: lower;
|
||||
}
|
||||
|
||||
.Lower > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react"
|
||||
import Style from "./BodyHorizontalUpperGrow.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function BodyHorizontalUpperGrow({ upper, lower, error, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.BodyHorizontalUpperGrow, className)} {...props}>
|
||||
<div className={Style.Upper}>
|
||||
{upper}
|
||||
</div>
|
||||
<div className={Style.Lower}>
|
||||
{lower}
|
||||
</div>
|
||||
<div className={Style.Error}>
|
||||
{error}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
.BodyHorizontalUpperGrow {
|
||||
display: grid;
|
||||
grid-template-areas: "upper" "lower" "error";
|
||||
grid-template-rows: 1fr auto auto;
|
||||
}
|
||||
|
||||
.Upper {
|
||||
grid-area: upper;
|
||||
}
|
||||
|
||||
.Upper > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Lower {
|
||||
grid-area: lower;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.Lower > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Error {
|
||||
grid-area: error;
|
||||
margin-top: 10px;
|
||||
}
|
22
nest_frontend/components/base/layout/PageWithHeader.js
Normal file
22
nest_frontend/components/base/layout/PageWithHeader.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React from "react"
|
||||
import Style from "./PageWithHeader.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function PageWithHeader({ header, buttons, children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.PageWithHeader, className)} {...props}>
|
||||
<div className={Style.Header}>
|
||||
{header}
|
||||
</div>
|
||||
{buttons ?
|
||||
<div className={Style.Buttons}>
|
||||
{buttons}
|
||||
</div>
|
||||
: null}
|
||||
<div className={Style.Body}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
.PageWithHeader {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"header button"
|
||||
"body body";
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: header;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.Buttons {
|
||||
grid-area: button;
|
||||
margin-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.Buttons > * {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.Body {
|
||||
grid-area: body;
|
||||
}
|
||||
|
||||
.Body > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext } from "react"
|
||||
import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
|
||||
import ContextRepositoryEditor from "../../contexts/ContextConditionEditor"
|
||||
import Badge from "../base/Badge"
|
||||
|
||||
|
||||
|
|
109
nest_frontend/components/interactive/BoxAlertCreate.js
Normal file
109
nest_frontend/components/interactive/BoxAlertCreate.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import React from "react"
|
||||
import BoxFull from "../base/BoxFull"
|
||||
import FormLabelled from "../base/FormLabelled"
|
||||
import FormLabel from "../base/formparts/FormLabel"
|
||||
import InputWithIcon from "../base/InputWithIcon"
|
||||
import {
|
||||
faBackward, faBell,
|
||||
faFolder,
|
||||
faPencilAlt,
|
||||
faPlus, faStopwatch,
|
||||
faThermometerThreeQuarters,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
import Radio from "../base/Radio"
|
||||
import Button from "../base/Button"
|
||||
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
|
||||
import FormAlert from "../base/formparts/FormAlert"
|
||||
import { useHistory } from "react-router"
|
||||
import useStrings from "../../hooks/useStrings"
|
||||
|
||||
|
||||
export default function BoxAlertCreate(
|
||||
{
|
||||
name,
|
||||
setName,
|
||||
evaluationMode,
|
||||
setEvaluationMode,
|
||||
limit,
|
||||
setLimit,
|
||||
windowSize,
|
||||
setWindowSize,
|
||||
running,
|
||||
error,
|
||||
save,
|
||||
...props
|
||||
}) {
|
||||
|
||||
const strings = useStrings()
|
||||
|
||||
return (
|
||||
<BoxFull header={strings.createRepo} {...props}>
|
||||
<FormLabelled
|
||||
onSubmit={e => {
|
||||
e.preventDefault()
|
||||
save()
|
||||
}}
|
||||
>
|
||||
<FormLabel htmlFor={"alert-name"} text={strings.alertName}>
|
||||
<InputWithIcon
|
||||
id={"alert-name"}
|
||||
icon={faBell}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor={"filter-mode"} text={strings.request}>
|
||||
<label>
|
||||
<Radio
|
||||
name={"filter-mode"}
|
||||
onChange={() => setEvaluationMode(0)}
|
||||
checked={evaluationMode === 0}
|
||||
/>
|
||||
{strings.filterOR}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<Radio
|
||||
name={"filter-mode"}
|
||||
onChange={() => setEvaluationMode(1)}
|
||||
checked={evaluationMode === 1}
|
||||
/>
|
||||
{strings.filterAND}
|
||||
</label>
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor={"alert-limit"} text={strings.alertLimit}>
|
||||
<InputWithIcon
|
||||
id={"alert-limit"}
|
||||
type={"number"}
|
||||
icon={faThermometerThreeQuarters}
|
||||
value={limit}
|
||||
onChange={e => setLimit(e.target.limit)}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormLabel htmlFor={"alert-window"} text={strings.alertWindow}>
|
||||
<InputWithIcon
|
||||
id={"alert-window"}
|
||||
type={"number"}
|
||||
icon={faStopwatch}
|
||||
value={windowSize}
|
||||
onChange={e => setWindowSize(e.target.limit)}
|
||||
/>
|
||||
</FormLabel>
|
||||
{error ?
|
||||
<FormAlert color={"Red"}>
|
||||
{strings[error.data.code]}
|
||||
</FormAlert>
|
||||
: null}
|
||||
<Button
|
||||
style={{ "gridColumn": "1 / 3" }}
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={save}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.createAlert}
|
||||
</Button>
|
||||
</FormLabelled>
|
||||
</BoxFull>
|
||||
)
|
||||
}
|
|
@ -15,24 +15,33 @@ import useStrings from "../../hooks/useStrings"
|
|||
/**
|
||||
* A {@link BoxFull} allowing the user to save the changes made in the current {@link RepositoryEditor}.
|
||||
*
|
||||
* @param id - The id of the repository.
|
||||
* @param name - The current name of the repository.
|
||||
* @param setName - Function to change the name of the repository.
|
||||
* @param evaluationMode - The current evaluation mode of the repository.
|
||||
* @param setEvaluationMode - Function to change the current evaluation mode of the repository.
|
||||
* @param running - If a request is running, disabling the buttons.
|
||||
* @param error - If a request error occoured, the error.
|
||||
* @param revert - Function to cancel the changes made to the repository.
|
||||
* @param save - Function to apply the changes made to the repository.
|
||||
* @param props - Additional props to pass to the box.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function BoxRepositoryCreate({ running, ...props }) {
|
||||
const {
|
||||
export default function BoxRepositoryCreate(
|
||||
{
|
||||
id,
|
||||
evaluationMode,
|
||||
setEvaluationMode,
|
||||
name,
|
||||
setName,
|
||||
save,
|
||||
revert,
|
||||
evaluationMode,
|
||||
setEvaluationMode,
|
||||
running,
|
||||
error,
|
||||
} = useRepositoryEditor()
|
||||
revert,
|
||||
save,
|
||||
...props
|
||||
}) {
|
||||
|
||||
const history = useHistory()
|
||||
const strings = useStrings()
|
||||
|
||||
return (
|
||||
|
|
145
nest_frontend/components/providers/AlertEditor.js
Normal file
145
nest_frontend/components/providers/AlertEditor.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
import React, { useCallback, useState } from "react"
|
||||
import ContextConditionEditor from "../../contexts/ContextConditionEditor"
|
||||
import useArrayState from "../../hooks/useArrayState"
|
||||
import Style from "./RepositoryEditor.module.css"
|
||||
import BoxConditionLocation from "../interactive/BoxConditionLocation"
|
||||
import BoxConditionHashtag from "../interactive/BoxConditionHashtag"
|
||||
import BoxConditionUser from "../interactive/BoxConditionUser"
|
||||
import BoxConditionDatetime from "../interactive/BoxConditionDatetime"
|
||||
import BoxConditions from "../interactive/BoxConditions"
|
||||
import classNames from "classnames"
|
||||
import { Condition } from "../../objects/Condition"
|
||||
import useBackendViewset from "../../hooks/useBackendViewset"
|
||||
import { Redirect, useParams } from "react-router"
|
||||
import BoxAlertCreate from "../interactive/BoxAlertCreate"
|
||||
|
||||
|
||||
export default function AlertEditor({className}) {
|
||||
/** The connected repository id. */
|
||||
const {id: repoId} = useParams()
|
||||
|
||||
/** The alert name. */
|
||||
const [_name, setName] = useState("")
|
||||
|
||||
/** The alert limit. */
|
||||
const [limit, setLimit] = useState(10)
|
||||
|
||||
/** The window size. */
|
||||
const [windowSize, setWindowSize] = useState(24)
|
||||
|
||||
/** The conditions of the data gathering. */
|
||||
const {
|
||||
value: rawConditions,
|
||||
setValue: setRawConditions,
|
||||
appendValue: appendRawCondition,
|
||||
removeValue: removeRawCondition,
|
||||
spliceValue: spliceRawCondition,
|
||||
} = useArrayState([])
|
||||
const _conditions = rawConditions.map(cond => Condition.fromRaw(cond))
|
||||
|
||||
/** The operator the conditions should be evaluated with. */
|
||||
const [_evaluationMode, setEvaluationMode] = useState(0)
|
||||
|
||||
/** The backend viewset to use to create / edit the repository. */
|
||||
const {running, error, createResource} = useBackendViewset(
|
||||
`/api/v1/repositories/${repoId}/alerts/`,
|
||||
"name",
|
||||
{
|
||||
list: false,
|
||||
create: true,
|
||||
retrieve: false,
|
||||
edit: false,
|
||||
destroy: false,
|
||||
command: false,
|
||||
action: false,
|
||||
}
|
||||
)
|
||||
|
||||
/** 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(
|
||||
async () => {
|
||||
const body = {
|
||||
"repository_id": repoId,
|
||||
"name": _name,
|
||||
"window_size": windowSize,
|
||||
"limit": limit,
|
||||
"evaluation_mode": _evaluationMode,
|
||||
"conditions": _conditions,
|
||||
}
|
||||
|
||||
console.info("Creating new alert with body: ", body)
|
||||
await createResource(body)
|
||||
setSwitchPage(true)
|
||||
},
|
||||
[repoId, createResource, _conditions, _evaluationMode, _name, limit],
|
||||
)
|
||||
|
||||
/**
|
||||
* Try to add a new condition, logging a message to the console if something goes wrong.
|
||||
*
|
||||
* @type {(function(): void)|*}
|
||||
*/
|
||||
const addCondition = useCallback(
|
||||
(newCond) => {
|
||||
|
||||
// Check for duplicates
|
||||
let duplicate = null
|
||||
for(const oldCond of _conditions) {
|
||||
if(newCond.type === oldCond.type && newCond.content === oldCond.content) {
|
||||
duplicate = oldCond
|
||||
break
|
||||
}
|
||||
}
|
||||
if(duplicate) {
|
||||
console.debug("Cannot add ", newCond, ": ", duplicate, " already exists.")
|
||||
return
|
||||
}
|
||||
|
||||
console.debug("Adding ", newCond, " to the repository conditions")
|
||||
appendRawCondition(newCond)
|
||||
},
|
||||
[_conditions, appendRawCondition],
|
||||
)
|
||||
|
||||
// Hack to switch page on success
|
||||
if(!error && switchPage) {
|
||||
return <Redirect to={`/repositories/${repoId}/alerts/`}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextConditionEditor.Provider
|
||||
value={{
|
||||
conditions: _conditions, addCondition, appendRawCondition, removeRawCondition, spliceRawCondition,
|
||||
}}
|
||||
>
|
||||
<div className={classNames(Style.RepositoryEditor, className)}>
|
||||
<BoxConditionLocation className={Style.SearchByZone}/>
|
||||
<BoxConditionHashtag className={Style.SearchByHashtags}/>
|
||||
<BoxConditionUser className={Style.SearchByUser}/>
|
||||
<BoxConditionDatetime className={Style.SearchByTimePeriod}/>
|
||||
<BoxConditions className={Style.Conditions}/>
|
||||
<BoxAlertCreate
|
||||
className={Style.CreateDialog}
|
||||
name={_name}
|
||||
setName={setName}
|
||||
evaluationMode={_evaluationMode}
|
||||
setEvaluationMode={setEvaluationMode}
|
||||
limit={limit}
|
||||
setLimit={setLimit}
|
||||
windowSize={windowSize}
|
||||
setWindowSize={setWindowSize}
|
||||
running={running}
|
||||
error={error}
|
||||
save={save}
|
||||
/>
|
||||
</div>
|
||||
</ContextConditionEditor.Provider>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useContext, useState } from "react"
|
||||
import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
|
||||
import ContextConditionEditor from "../../contexts/ContextConditionEditor"
|
||||
import useArrayState from "../../hooks/useArrayState"
|
||||
import Style from "./RepositoryEditor.module.css"
|
||||
import BoxConditionLocation from "../interactive/BoxConditionLocation"
|
||||
|
@ -142,14 +142,9 @@ export default function RepositoryEditor({
|
|||
}
|
||||
|
||||
return (
|
||||
<ContextRepositoryEditor.Provider
|
||||
<ContextConditionEditor.Provider
|
||||
value={{
|
||||
id,
|
||||
name: _name, setName,
|
||||
conditions: _conditions, addCondition, appendRawCondition, removeRawCondition, spliceRawCondition,
|
||||
evaluationMode: _evaluationMode, setEvaluationMode,
|
||||
error, running,
|
||||
revert, save,
|
||||
}}
|
||||
>
|
||||
<div className={classNames(Style.RepositoryEditor, className)}>
|
||||
|
@ -158,8 +153,19 @@ export default function RepositoryEditor({
|
|||
<BoxConditionUser className={Style.SearchByUser}/>
|
||||
<BoxConditionDatetime className={Style.SearchByTimePeriod}/>
|
||||
<BoxConditions className={Style.Conditions}/>
|
||||
<BoxRepositoryCreate running={running} className={Style.CreateDialog}/>
|
||||
<BoxRepositoryCreate
|
||||
className={Style.CreateDialog}
|
||||
id={id}
|
||||
name={_name}
|
||||
setName={setName}
|
||||
evaluationMode={_evaluationMode}
|
||||
setEvaluationMode={setEvaluationMode}
|
||||
running={running}
|
||||
error={error}
|
||||
revert={revert}
|
||||
save={save}
|
||||
/>
|
||||
</div>
|
||||
</ContextRepositoryEditor.Provider>
|
||||
</ContextConditionEditor.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useContext, useMemo, useState } from "react"
|
||||
import Style from "./RepositoryViewer.module.css"
|
||||
import classNames from "classnames"
|
||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||
import useBackendResource from "../../hooks/useBackendResource"
|
||||
import useBackendViewset from "../../hooks/useBackendViewset"
|
||||
|
@ -9,7 +8,7 @@ import countTweetWords from "../../utils/countTweetWords"
|
|||
import BoxHeader from "../base/BoxHeader"
|
||||
import Loading from "../base/Loading"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faFolder, faFolderOpen, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||
import { faFolder, faFolderOpen, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||
import BoxRepositoryTweets from "../interactive/BoxRepositoryTweets"
|
||||
import PickerVisualization from "../interactive/PickerVisualization"
|
||||
import BoxVisualizationWordcloud from "../interactive/BoxVisualizationWordcloud"
|
||||
|
@ -29,6 +28,9 @@ import BoxFilterDatetime from "../interactive/BoxFilterDatetime"
|
|||
import BoxFilterHasPlace from "../interactive/BoxFilterHasPlace"
|
||||
import BoxFilterHasImage from "../interactive/BoxFilterHasImage"
|
||||
import BoxFilterIsNotRetweet from "../interactive/BoxFilterIsNotRetweet"
|
||||
import PageWithHeader from "../base/layout/PageWithHeader"
|
||||
import ButtonHeader from "../base/ButtonHeader"
|
||||
import AlertError from "../interactive/AlertError"
|
||||
|
||||
|
||||
export default function RepositoryViewer({ id, className, ...props }) {
|
||||
|
@ -49,7 +51,11 @@ export default function RepositoryViewer({ id, className, ...props }) {
|
|||
const mapViewHook = useMapAreaState()
|
||||
|
||||
// Repository
|
||||
const repositoryBr = useBackendResource(
|
||||
const {
|
||||
resource: repository,
|
||||
error: repositoryError,
|
||||
firstLoad: repositoryFirstLoad,
|
||||
} = useBackendResource(
|
||||
`/api/v1/repositories/${id}`,
|
||||
{
|
||||
retrieve: true,
|
||||
|
@ -58,11 +64,13 @@ export default function RepositoryViewer({ id, className, ...props }) {
|
|||
action: false,
|
||||
},
|
||||
)
|
||||
const repository = repositoryBr.error ? null : repositoryBr.resource
|
||||
|
||||
|
||||
// Tweets
|
||||
const rawTweetsBv = useBackendViewset(
|
||||
const {
|
||||
resources: tweets,
|
||||
error: tweetsError,
|
||||
firstLoad: tweetsFirstLoad,
|
||||
} = useBackendViewset(
|
||||
`/api/v1/repositories/${id}/tweets/`,
|
||||
"snowflake",
|
||||
{
|
||||
|
@ -75,44 +83,50 @@ export default function RepositoryViewer({ id, className, ...props }) {
|
|||
action: false,
|
||||
},
|
||||
)
|
||||
const rawTweets = rawTweetsBv.resources && rawTweetsBv.error ? [] : rawTweetsBv.resources
|
||||
|
||||
|
||||
// Filtering
|
||||
let tweets = rawTweets
|
||||
const filteredTweets = useMemo(
|
||||
() => {
|
||||
let tempTweets = tweets
|
||||
for(const filter of filters) {
|
||||
tweets = tweets.filter(tweet => filter.exec(tweet))
|
||||
tempTweets = tempTweets.filter(tweet => filter.exec(tweet))
|
||||
}
|
||||
|
||||
return tempTweets
|
||||
},
|
||||
[tweets, filters]
|
||||
)
|
||||
|
||||
// Words
|
||||
const words = useMemo(
|
||||
() => objectToWordcloudFormat(countTweetWords(tweets)),
|
||||
[tweets],
|
||||
() => objectToWordcloudFormat(countTweetWords(filteredTweets)),
|
||||
[filteredTweets],
|
||||
)
|
||||
|
||||
// Page header
|
||||
const header = useMemo(
|
||||
() => (
|
||||
<BoxHeader>
|
||||
<FontAwesomeIcon icon={repository?.is_active ? faFolderOpen : faFolder}/> {repository?.name}
|
||||
</BoxHeader>
|
||||
),
|
||||
[repository?.is_active, repository?.name]
|
||||
)
|
||||
|
||||
let contents
|
||||
if(!repositoryBr.firstLoad || !rawTweetsBv.firstLoad) {
|
||||
contents = <>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<Loading/>
|
||||
</BoxHeader>
|
||||
</>
|
||||
// Page contents
|
||||
const contents = useMemo(
|
||||
() => {
|
||||
if(!repositoryFirstLoad || !tweetsFirstLoad) {
|
||||
return <Loading/>
|
||||
}
|
||||
else if(repository === null) {
|
||||
contents = <>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<FontAwesomeIcon icon={faTrash}/> <i>{strings.repoDeleted}</i>
|
||||
</BoxHeader>
|
||||
</>
|
||||
else if(repositoryError) {
|
||||
return <AlertError error={repositoryError}/>
|
||||
}
|
||||
else if(tweetsError) {
|
||||
return <AlertError error={tweetsError}/>
|
||||
}
|
||||
else {
|
||||
contents = <>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<FontAwesomeIcon icon={repository.is_active ? faFolderOpen : faFolder}/> {repository.name}
|
||||
</BoxHeader>
|
||||
|
||||
return (
|
||||
<div className={Style.RepositoryViewer}>
|
||||
<BoxRepositoryTweets className={Style.Tweets}/>
|
||||
<PickerVisualization className={Style.VisualizationPicker}/>
|
||||
{visualizationTab === "wordcloud" ? <BoxVisualizationWordcloud className={Style.Visualization}/> : null}
|
||||
|
@ -130,8 +144,12 @@ export default function RepositoryViewer({ id, className, ...props }) {
|
|||
{filterTab === "time" ? <BoxFilterDatetime className={Style.AddFilter}/> : null}
|
||||
{filterTab === "place" ? <BoxFilterHasPlace className={Style.AddFilter}/> : null}
|
||||
{filterTab === "location" ? <BoxFilterLocation className={Style.AddFilter}/> : null}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
[repositoryFirstLoad, tweetsFirstLoad, visualizationTab, filterTab]
|
||||
)
|
||||
|
||||
return (
|
||||
<ContextRepositoryViewer.Provider
|
||||
|
@ -145,20 +163,16 @@ export default function RepositoryViewer({ id, className, ...props }) {
|
|||
appendFilter,
|
||||
spliceFilter,
|
||||
removeFilter,
|
||||
repositoryBr,
|
||||
repository,
|
||||
rawTweetsBv,
|
||||
rawTweets,
|
||||
tweets,
|
||||
rawTweets: tweets,
|
||||
tweets: filteredTweets,
|
||||
words,
|
||||
mapViewHook,
|
||||
}}
|
||||
>
|
||||
|
||||
<div className={classNames(Style.RepositoryViewer, className)} {...props}>
|
||||
<PageWithHeader header={header}>
|
||||
{contents}
|
||||
</div>
|
||||
|
||||
</PageWithHeader>
|
||||
</ContextRepositoryViewer.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"h h"
|
||||
"a b"
|
||||
"a c"
|
||||
"d e"
|
||||
|
@ -10,16 +9,12 @@
|
|||
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto auto 1fr auto auto;
|
||||
grid-template-rows: auto 1fr auto auto;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: h;
|
||||
}
|
||||
|
||||
.Tweets {
|
||||
grid-area: a;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createContext } from "react"
|
|||
|
||||
|
||||
/**
|
||||
* React Context representing containing all variables of a {@link RepositoryEditor}.
|
||||
* React Context representing a list of {@link Condition}s as provided by {@link useArrayState}.
|
||||
*
|
||||
* It is `null` outside a RepositoryEditor.
|
||||
*/
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from "react"
|
||||
import ContextRepositoryEditor from "../contexts/ContextRepositoryEditor"
|
||||
import ContextRepositoryEditor from "../contexts/ContextConditionEditor"
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageLogin.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxSetServer from "../components/interactive/BoxSetServer"
|
||||
import BoxLogin from "../components/interactive/BoxLogin"
|
||||
import ContextUser from "../contexts/ContextUser"
|
||||
|
@ -9,9 +7,11 @@ import BoxHeader from "../components/base/BoxHeader"
|
|||
import useStrings from "../hooks/useStrings"
|
||||
import BoxFull from "../components/base/BoxFull"
|
||||
import SelectLanguage from "../components/interactive/SelectLanguage"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import BodyFlex from "../components/base/layout/BodyFlex"
|
||||
|
||||
|
||||
export default function PageLogin({ className, ...props }) {
|
||||
export default function PageLogin() {
|
||||
const {user} = useContext(ContextUser)
|
||||
const strings = useStrings()
|
||||
|
||||
|
@ -20,15 +20,20 @@ export default function PageLogin({ className, ...props }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageLogin, className)} {...props}>
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{strings.welcomeToNest}
|
||||
</BoxHeader>
|
||||
}
|
||||
>
|
||||
<BodyFlex>
|
||||
<BoxSetServer/>
|
||||
<BoxLogin/>
|
||||
<BoxFull header={strings.changeLang}>
|
||||
<SelectLanguage/>
|
||||
</BoxFull>
|
||||
</div>
|
||||
</BodyFlex>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useCallback, useContext } from "react"
|
||||
import Style from "./PageRepositoriesList.module.css"
|
||||
import classNames from "classnames"
|
||||
import useBackendViewset from "../hooks/useBackendViewset"
|
||||
import BoxRepositories from "../components/interactive/BoxRepositories"
|
||||
import { useHistory } from "react-router"
|
||||
|
@ -8,10 +6,12 @@ import ContextLanguage from "../contexts/ContextLanguage"
|
|||
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"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import ButtonHeader from "../components/base/ButtonHeader"
|
||||
import BodyHorizontalHalves from "../components/base/layout/BodyHorizontalHalves"
|
||||
|
||||
|
||||
export default function PageRepositoriesList({ children, className, ...props }) {
|
||||
export default function PageRepositoriesList() {
|
||||
const bv = useBackendViewset("/api/v1/repositories/", "id")
|
||||
const history = useHistory()
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
@ -27,33 +27,38 @@ export default function PageRepositoriesList({ children, className, ...props })
|
|||
)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
<FontAwesomeIcon icon={faHome}/> {strings.dashboard}
|
||||
</BoxHeader>
|
||||
<div className={Style.Buttons}>
|
||||
<Button
|
||||
}
|
||||
buttons={
|
||||
<ButtonHeader
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={() => history.push("/repositories/create")}
|
||||
>
|
||||
{strings.createRepo}
|
||||
</Button>
|
||||
</div>
|
||||
</ButtonHeader>
|
||||
}
|
||||
>
|
||||
<BodyHorizontalHalves
|
||||
upper={
|
||||
<BoxRepositories
|
||||
className={Style.ActiveRepositories}
|
||||
header={strings.menuActive}
|
||||
loading={!bv.firstLoad}
|
||||
running={bv.running}
|
||||
repositories={bv.resources.filter(r => r.is_active)}
|
||||
view={pk => history.push(`/repositories/${pk}`)}
|
||||
share={pk => history.push(`/repositories/${pk}/share`)}
|
||||
alerts={pk => history.push(`/repositories/${pk}/alerts`)}
|
||||
alerts={pk => history.push(`/repositories/${pk}/alerts/`)}
|
||||
archive={archive}
|
||||
edit={pk => history.push(`/repositories/${pk}/edit`)}
|
||||
/>
|
||||
}
|
||||
lower={
|
||||
<BoxRepositories
|
||||
className={Style.ArchivedRepositories}
|
||||
header={strings.menuArchived}
|
||||
loading={!bv.firstLoad}
|
||||
running={bv.running}
|
||||
|
@ -61,6 +66,8 @@ export default function PageRepositoriesList({ children, className, ...props })
|
|||
view={pk => history.push(`/repositories/${pk}`)}
|
||||
destroy={bv.destroyResource}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
.PageRepositories {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"h x"
|
||||
"a a"
|
||||
"b b";
|
||||
grid-template-columns: 4fr 1fr;
|
||||
grid-template-rows: auto 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 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 {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.ArchivedRepositories {
|
||||
grid-area: b;
|
||||
}
|
|
@ -1,27 +1,39 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageRepositoryAlerts.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxFull from "../components/base/BoxFull"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import { useParams } from "react-router"
|
||||
import { useHistory, useParams } from "react-router"
|
||||
import { faBell, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import ButtonHeader from "../components/base/ButtonHeader"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
|
||||
|
||||
export default function PageRepositoryAlerts({ children, className, ...props }) {
|
||||
export default function PageRepositoryAlerts() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
const { id } = useParams()
|
||||
const history = useHistory()
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageAlerts, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
{strings.alerts}
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faBell)} {strings.alerts}
|
||||
</BoxHeader>
|
||||
<BoxFull header={strings.alertTitle} className={Style.YourAlerts}>
|
||||
}
|
||||
buttons={
|
||||
<ButtonHeader
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={() => history.push(`/repositories/${id}/alerts/create`)}
|
||||
>
|
||||
{strings.alertCreate}
|
||||
</ButtonHeader>
|
||||
}
|
||||
>
|
||||
<BoxFull header={strings.alertTitle}>
|
||||
{strings.notImplemented}
|
||||
</BoxFull>
|
||||
<BoxFull header={strings.alertCreate} className={Style.CreateAlert}>
|
||||
{strings.notImplemented}
|
||||
</BoxFull>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
.PageAlerts {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"a"
|
||||
"b"
|
||||
"c";
|
||||
grid-template-rows: auto 1fr 1fr;
|
||||
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.YourAlerts {
|
||||
grid-area: b;
|
||||
}
|
||||
|
||||
.CreateAlert {
|
||||
grid-area: c;
|
||||
}
|
25
nest_frontend/routes/PageRepositoryAlertsCreate.js
Normal file
25
nest_frontend/routes/PageRepositoryAlertsCreate.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { useContext } from "react"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import { useParams } from "react-router"
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
import AlertEditor from "../components/providers/AlertEditor"
|
||||
|
||||
|
||||
export default function PageRepositoryAlertsCreate() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
return (
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faPlus)} {strings.alertCreate}
|
||||
</BoxHeader>
|
||||
}
|
||||
>
|
||||
<AlertEditor/>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@ import { useParams } from "react-router"
|
|||
import RepositoryViewer from "../components/providers/RepositoryViewer"
|
||||
|
||||
|
||||
export default function PageRepositoryAnalyze({ className, ...props }) {
|
||||
export default function PageRepositoryAnalyze({...props }) {
|
||||
const { id } = useParams()
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageRepositoryCreate.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import RepositoryEditor from "../components/providers/RepositoryEditor"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
export default function PageRepositoryCreate({ children, className, ...props }) {
|
||||
export default function PageRepositoryCreate() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageHome, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
{strings.dashboardTitle}
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faPlus)} {strings.dashboardTitle}
|
||||
</BoxHeader>
|
||||
<RepositoryEditor className={Style.RepositoryEditor}/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RepositoryEditor/>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
.PageHome {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"a"
|
||||
"b";
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.RepositoryEditor {
|
||||
grid-area: b;
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageRepositoryCreate.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import RepositoryEditor from "../components/providers/RepositoryEditor"
|
||||
import useBackendImmediately from "../hooks/useBackendImmediately"
|
||||
|
@ -8,9 +6,12 @@ import ContextUser from "../contexts/ContextUser"
|
|||
import renderContents from "../utils/renderContents"
|
||||
import { useParams } from "react-router"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
export default function PageRepositoryEdit({ className, ...props }) {
|
||||
export default function PageRepositoryEdit() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
const { id } = useParams()
|
||||
|
@ -18,18 +19,18 @@ export default function PageRepositoryEdit({ className, ...props }) {
|
|||
const repositoryRequest = useBackendImmediately(fetchDataAuth, "GET", `/api/v1/repositories/${id}`)
|
||||
const contents = renderContents(
|
||||
repositoryRequest,
|
||||
data => {
|
||||
console.debug("Data: ", data)
|
||||
return <RepositoryEditor className={Style.RepositoryEditor} {...data}/>
|
||||
},
|
||||
data => <RepositoryEditor {...data}/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageHome, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
{strings.repoEdit}
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faPencilAlt)} {strings.repoEdit}
|
||||
</BoxHeader>
|
||||
}
|
||||
>
|
||||
{contents}
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageSettings.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxFull from "../components/base/BoxFull"
|
||||
import SelectTheme from "../components/interactive/SelectTheme"
|
||||
import BoxLoggedIn from "../components/interactive/BoxLoggedIn"
|
||||
import SelectLanguage from "../components/interactive/SelectLanguage"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import { faCog } from "@fortawesome/free-solid-svg-icons"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
import BodyFlex from "../components/base/layout/BodyFlex"
|
||||
|
||||
|
||||
export default function PageSettings({ children, className, ...props }) {
|
||||
export default function PageSettings() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageSettings, className)} {...props}>
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faCog)} {strings.settings}
|
||||
</BoxHeader>
|
||||
}
|
||||
>
|
||||
<BodyFlex>
|
||||
<BoxLoggedIn/>
|
||||
<BoxFull header={strings.changeLang}>
|
||||
<SelectLanguage/>
|
||||
|
@ -20,6 +30,7 @@ export default function PageSettings({ children, className, ...props }) {
|
|||
<BoxFull header={strings.switchTheme}>
|
||||
<SelectTheme/>
|
||||
</BoxFull>
|
||||
</div>
|
||||
</BodyFlex>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
.PageSettings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
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"
|
||||
import ContextUser from "../contexts/ContextUser"
|
||||
import Alert from "../components/base/Alert"
|
||||
import useStrings from "../hooks/useStrings"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import BodyHorizontalHalves from "../components/base/layout/BodyHorizontalHalves"
|
||||
import AlertError from "../components/interactive/AlertError"
|
||||
|
||||
|
||||
export default function PageShare({ className, ...props }) {
|
||||
export default function PageShare() {
|
||||
const strings = useStrings()
|
||||
const { user: loggedUser } = useContext(ContextUser)
|
||||
const { id } = useParams()
|
||||
|
@ -71,30 +70,35 @@ export default function PageShare({ className, ...props }) {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageShare, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{strings.repoShare}
|
||||
</BoxHeader>
|
||||
}
|
||||
>
|
||||
<BodyHorizontalHalves
|
||||
upper={
|
||||
<BoxUserList
|
||||
className={Style.UserList}
|
||||
users={users.filter(user => user["email"] !== loggedUser["email"] && !authorizations.map(a => a.email).includes(user.email))}
|
||||
shareWithUser={shareWith}
|
||||
header={strings.availableUsers}
|
||||
running={usersBvRunning && authBvRunning}
|
||||
running={usersBvRunning || authBvRunning}
|
||||
/>
|
||||
}
|
||||
lower={<>
|
||||
<BoxUserList
|
||||
className={Style.SharingWith}
|
||||
users={users.filter(user => user["email"] === loggedUser["email"] || authorizations.map(a => a.email).includes(user.email))}
|
||||
unshareWithUser={unshareWith}
|
||||
header={strings.sharingWith}
|
||||
running={usersBvRunning && authBvRunning}
|
||||
running={usersBvRunning || authBvRunning}
|
||||
/>
|
||||
{authBvError ?
|
||||
<Alert color={"Red"} className={Style.Error}>{strings[authBvError?.data?.code ?? "errorUnknownError"]}</Alert>
|
||||
: null}
|
||||
{usersBvError ?
|
||||
<Alert color={"Red"} className={Style.Error}>{strings[usersBvError?.data?.code ?? "errorUnknownError"]}</Alert>
|
||||
: null}
|
||||
</div>
|
||||
</>}
|
||||
error={<>
|
||||
{authBvError ? <AlertError error={authBvError}/> : null}
|
||||
{usersBvError ? <AlertError error={usersBvError}/> : null}
|
||||
</>}
|
||||
/>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
.PageShare {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
"d";
|
||||
grid-template-rows: auto 1fr 1fr auto;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.Header {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.UserList {
|
||||
grid-area: b;
|
||||
}
|
||||
|
||||
.SharingWith {
|
||||
grid-area: c;
|
||||
}
|
||||
|
||||
.Error {
|
||||
grid-area: d;
|
||||
}
|
|
@ -1,29 +1,45 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./PageUsers.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import BoxUserCreate from "../components/interactive/BoxUserCreate"
|
||||
import useBackendViewset from "../hooks/useBackendViewset"
|
||||
import BoxUserList from "../components/interactive/BoxUserList"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import Alert from "../components/base/Alert"
|
||||
import PageWithHeader from "../components/base/layout/PageWithHeader"
|
||||
import { faUserCog } from "@fortawesome/free-solid-svg-icons"
|
||||
import makeIcon from "../utils/makeIcon"
|
||||
import AlertError from "../components/interactive/AlertError"
|
||||
import BodyHorizontalUpperGrow from "../components/base/layout/BodyHorizontalUpperGrow"
|
||||
|
||||
|
||||
export default function PageUsers({ children, className, ...props }) {
|
||||
export default function PageUsers() {
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
const bv = useBackendViewset("/api/v1/users/", "email")
|
||||
const {createResource, running, resources, destroyResource, error} = useBackendViewset("/api/v1/users/", "email")
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageUsers, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
{strings.manageUsers}
|
||||
<PageWithHeader
|
||||
header={
|
||||
<BoxHeader>
|
||||
{makeIcon(faUserCog)} {strings.manageUsers}
|
||||
</BoxHeader>
|
||||
<BoxUserCreate className={Style.CreateUser} createUser={bv.createResource} running={bv.running}/>
|
||||
<BoxUserList className={Style.UserList} users={bv.resources} destroyUser={bv.destroyResource} running={bv.running}/>
|
||||
{bv.error ?
|
||||
<Alert className={Style.Error} color={"red"}>{strings[bv.error?.data?.code ?? "errorUnknownError"]}</Alert>
|
||||
: null}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<BodyHorizontalUpperGrow
|
||||
upper={
|
||||
<BoxUserList
|
||||
users={resources}
|
||||
destroyUser={destroyResource}
|
||||
running={running}
|
||||
/>
|
||||
}
|
||||
lower={
|
||||
<BoxUserCreate
|
||||
createUser={createResource}
|
||||
running={running}
|
||||
/>
|
||||
}
|
||||
error={error ? <AlertError error={error}/> : null}
|
||||
/>
|
||||
</PageWithHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
.PageUsers {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
"e";
|
||||
grid-template-rows: auto 1fr auto auto;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.UserList {
|
||||
grid-area: b;
|
||||
}
|
||||
|
||||
.CreateUser {
|
||||
grid-area: c;
|
||||
}
|
||||
|
||||
.Error {
|
||||
grid-area: e;
|
||||
}
|
|
@ -4,7 +4,7 @@ import sw from "stopword"
|
|||
const stopwords = [...sw.it, ...sw.en, "rt"]
|
||||
|
||||
|
||||
export default function countTweetWords(tweets = {}) {
|
||||
export default function countTweetWords(tweets = []) {
|
||||
let words = {}
|
||||
for(const tweet of tweets) {
|
||||
if(!tweet.content) {
|
||||
|
|
|
@ -4,6 +4,9 @@ import Alert from "../components/base/Alert"
|
|||
import Starting from "../components/base/Starting"
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export default function renderContents(requestHookResults, renderFunction) {
|
||||
const { data, error, loading } = requestHookResults
|
||||
|
||||
|
|
Loading…
Reference in a new issue