diff --git a/nest_frontend/LocalizationStrings.js b/nest_frontend/LocalizationStrings.js
index b254c31..335e0a9 100644
--- a/nest_frontend/LocalizationStrings.js
+++ b/nest_frontend/LocalizationStrings.js
@@ -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: {
diff --git a/nest_frontend/PageSwitcher.js b/nest_frontend/PageSwitcher.js
index 28d5177..41e3cb3 100644
--- a/nest_frontend/PageSwitcher.js
+++ b/nest_frontend/PageSwitcher.js
@@ -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 (
-
+
+
+
+
@@ -38,9 +48,12 @@ export default function PageSwitcher({ ...props }) {
-
+
+
+ {makeIcon(faQuestionCircle)} {strings.errorPageNotFound}
+
)
}
diff --git a/nest_frontend/components/base/ButtonHeader.js b/nest_frontend/components/base/ButtonHeader.js
new file mode 100644
index 0000000..a3e8b50
--- /dev/null
+++ b/nest_frontend/components/base/ButtonHeader.js
@@ -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 (
+
+ )
+}
diff --git a/nest_frontend/components/base/ButtonHeader.module.css b/nest_frontend/components/base/ButtonHeader.module.css
new file mode 100644
index 0000000..5196751
--- /dev/null
+++ b/nest_frontend/components/base/ButtonHeader.module.css
@@ -0,0 +1,4 @@
+.ButtonHeader {
+ box-shadow: none;
+ flex-grow: 1;
+}
diff --git a/nest_frontend/components/base/layout/BodyFlex.js b/nest_frontend/components/base/layout/BodyFlex.js
new file mode 100644
index 0000000..e0a6fbf
--- /dev/null
+++ b/nest_frontend/components/base/layout/BodyFlex.js
@@ -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 (
+
+ {children}
+
+ )
+}
diff --git a/nest_frontend/routes/PageLogin.module.css b/nest_frontend/components/base/layout/BodyFlex.module.css
similarity index 56%
rename from nest_frontend/routes/PageLogin.module.css
rename to nest_frontend/components/base/layout/BodyFlex.module.css
index 917046f..0ca0f93 100644
--- a/nest_frontend/routes/PageLogin.module.css
+++ b/nest_frontend/components/base/layout/BodyFlex.module.css
@@ -1,9 +1,6 @@
-.PageLogin {
+.BodyFlex {
display: flex;
flex-direction: column;
-
+ align-items: stretch;
gap: 10px;
-
- width: 100%;
- height: 100%;
}
diff --git a/nest_frontend/components/base/layout/BodyHorizontalHalves.js b/nest_frontend/components/base/layout/BodyHorizontalHalves.js
new file mode 100644
index 0000000..1e71fe3
--- /dev/null
+++ b/nest_frontend/components/base/layout/BodyHorizontalHalves.js
@@ -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 (
+
+
+ {upper}
+
+
+ {lower}
+
+
+ )
+}
diff --git a/nest_frontend/components/base/layout/BodyHorizontalHalves.module.css b/nest_frontend/components/base/layout/BodyHorizontalHalves.module.css
new file mode 100644
index 0000000..3892f03
--- /dev/null
+++ b/nest_frontend/components/base/layout/BodyHorizontalHalves.module.css
@@ -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%;
+}
\ No newline at end of file
diff --git a/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.js b/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.js
new file mode 100644
index 0000000..2d3338f
--- /dev/null
+++ b/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.js
@@ -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 (
+
+
+ {upper}
+
+
+ {lower}
+
+
+ {error}
+
+
+ )
+}
diff --git a/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.module.css b/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.module.css
new file mode 100644
index 0000000..2a1728d
--- /dev/null
+++ b/nest_frontend/components/base/layout/BodyHorizontalUpperGrow.module.css
@@ -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;
+}
\ No newline at end of file
diff --git a/nest_frontend/components/base/layout/PageWithHeader.js b/nest_frontend/components/base/layout/PageWithHeader.js
new file mode 100644
index 0000000..e2a35a8
--- /dev/null
+++ b/nest_frontend/components/base/layout/PageWithHeader.js
@@ -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 (
+
+
+ {header}
+
+ {buttons ?
+
+ {buttons}
+
+ : null}
+
+ {children}
+
+
+ )
+}
diff --git a/nest_frontend/components/base/layout/PageWithHeader.module.css b/nest_frontend/components/base/layout/PageWithHeader.module.css
new file mode 100644
index 0000000..7331b9e
--- /dev/null
+++ b/nest_frontend/components/base/layout/PageWithHeader.module.css
@@ -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%;
+}
\ No newline at end of file
diff --git a/nest_frontend/components/interactive/BadgeCondition.js b/nest_frontend/components/interactive/BadgeCondition.js
index dc30660..f450a00 100644
--- a/nest_frontend/components/interactive/BadgeCondition.js
+++ b/nest_frontend/components/interactive/BadgeCondition.js
@@ -1,5 +1,5 @@
import React, { useContext } from "react"
-import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
+import ContextRepositoryEditor from "../../contexts/ContextConditionEditor"
import Badge from "../base/Badge"
diff --git a/nest_frontend/components/interactive/BoxAlertCreate.js b/nest_frontend/components/interactive/BoxAlertCreate.js
new file mode 100644
index 0000000..e97a7f2
--- /dev/null
+++ b/nest_frontend/components/interactive/BoxAlertCreate.js
@@ -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 (
+
+ {
+ e.preventDefault()
+ save()
+ }}
+ >
+
+ setName(e.target.value)}
+ />
+
+
+
+
+
+
+
+ setLimit(e.target.limit)}
+ />
+
+
+ setWindowSize(e.target.limit)}
+ />
+
+ {error ?
+
+ {strings[error.data.code]}
+
+ : null}
+
+
+
+ )
+}
diff --git a/nest_frontend/components/interactive/BoxRepositoryCreate.js b/nest_frontend/components/interactive/BoxRepositoryCreate.js
index 40182a3..80c0a85 100644
--- a/nest_frontend/components/interactive/BoxRepositoryCreate.js
+++ b/nest_frontend/components/interactive/BoxRepositoryCreate.js
@@ -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 (
diff --git a/nest_frontend/components/providers/AlertEditor.js b/nest_frontend/components/providers/AlertEditor.js
new file mode 100644
index 0000000..ab365a2
--- /dev/null
+++ b/nest_frontend/components/providers/AlertEditor.js
@@ -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)|*}
+ */
+ 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
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/nest_frontend/components/providers/RepositoryEditor.js b/nest_frontend/components/providers/RepositoryEditor.js
index 7c2fa94..c8cb367 100644
--- a/nest_frontend/components/providers/RepositoryEditor.js
+++ b/nest_frontend/components/providers/RepositoryEditor.js
@@ -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 (
-
@@ -158,8 +153,19 @@ export default function RepositoryEditor({
-
+
-
+
)
}
diff --git a/nest_frontend/components/providers/RepositoryViewer.js b/nest_frontend/components/providers/RepositoryViewer.js
index 5e402ea..94c73de 100644
--- a/nest_frontend/components/providers/RepositoryViewer.js
+++ b/nest_frontend/components/providers/RepositoryViewer.js
@@ -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,63 +83,73 @@ export default function RepositoryViewer({ id, className, ...props }) {
action: false,
},
)
- const rawTweets = rawTweetsBv.resources && rawTweetsBv.error ? [] : rawTweetsBv.resources
-
// Filtering
- let tweets = rawTweets
- for(const filter of filters) {
- tweets = tweets.filter(tweet => filter.exec(tweet))
- }
-
+ const filteredTweets = useMemo(
+ () => {
+ let tempTweets = tweets
+ for(const filter of filters) {
+ tempTweets = tempTweets.filter(tweet => filter.exec(tweet))
+ }
+ return tempTweets
+ },
+ [tweets, filters]
+ )
// Words
const words = useMemo(
- () => objectToWordcloudFormat(countTweetWords(tweets)),
- [tweets],
+ () => objectToWordcloudFormat(countTweetWords(filteredTweets)),
+ [filteredTweets],
)
-
- let contents
- if(!repositoryBr.firstLoad || !rawTweetsBv.firstLoad) {
- contents = <>
-
-
-
- >
- }
- else if(repository === null) {
- contents = <>
-
- {strings.repoDeleted}
-
- >
- }
- else {
- contents = <>
-
- {repository.name}
+ // Page header
+ const header = useMemo(
+ () => (
+
+ {repository?.name}
+ ),
+ [repository?.is_active, repository?.name]
+ )
-
-
- {visualizationTab === "wordcloud" ? : null}
- {visualizationTab === "chart" ? : null}
- {visualizationTab === "map" ? : null}
- {visualizationTab === "stats" ? : null}
+ // Page contents
+ const contents = useMemo(
+ () => {
+ if(!repositoryFirstLoad || !tweetsFirstLoad) {
+ return
+ }
+ else if(repositoryError) {
+ return
+ }
+ else if(tweetsError) {
+ return
+ }
+ else {
+ return (
+
+
+
+ {visualizationTab === "wordcloud" ? : null}
+ {visualizationTab === "chart" ? : null}
+ {visualizationTab === "map" ? : null}
+ {visualizationTab === "stats" ? : null}
-
-
- {filterTab === "contains" ? : null}
- {filterTab === "hashtag" ? : null}
- {filterTab === "user" ? : null}
- {filterTab === "retweet" ? : null}
- {filterTab === "image" ? : null}
- {filterTab === "time" ? : null}
- {filterTab === "place" ? : null}
- {filterTab === "location" ? : null}
- >
- }
+
+
+ {filterTab === "contains" ? : null}
+ {filterTab === "hashtag" ? : null}
+ {filterTab === "user" ? : null}
+ {filterTab === "retweet" ? : null}
+ {filterTab === "image" ? : null}
+ {filterTab === "time" ? : null}
+ {filterTab === "place" ? : null}
+ {filterTab === "location" ? : null}
+
+ )
+ }
+ },
+ [repositoryFirstLoad, tweetsFirstLoad, visualizationTab, filterTab]
+ )
return (
-
-
-
+
)
}
diff --git a/nest_frontend/components/providers/RepositoryViewer.module.css b/nest_frontend/components/providers/RepositoryViewer.module.css
index c678bc5..403db53 100644
--- a/nest_frontend/components/providers/RepositoryViewer.module.css
+++ b/nest_frontend/components/providers/RepositoryViewer.module.css
@@ -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;
}
diff --git a/nest_frontend/contexts/ContextRepositoryEditor.js b/nest_frontend/contexts/ContextConditionEditor.js
similarity index 57%
rename from nest_frontend/contexts/ContextRepositoryEditor.js
rename to nest_frontend/contexts/ContextConditionEditor.js
index 3364c6b..a68a4c0 100644
--- a/nest_frontend/contexts/ContextRepositoryEditor.js
+++ b/nest_frontend/contexts/ContextConditionEditor.js
@@ -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.
*/
diff --git a/nest_frontend/hooks/useRepositoryEditor.js b/nest_frontend/hooks/useRepositoryEditor.js
index 143f665..dedfce2 100644
--- a/nest_frontend/hooks/useRepositoryEditor.js
+++ b/nest_frontend/hooks/useRepositoryEditor.js
@@ -1,5 +1,5 @@
import { useContext } from "react"
-import ContextRepositoryEditor from "../contexts/ContextRepositoryEditor"
+import ContextRepositoryEditor from "../contexts/ContextConditionEditor"
/**
diff --git a/nest_frontend/routes/PageLogin.js b/nest_frontend/routes/PageLogin.js
index fcea5fe..f8786b9 100644
--- a/nest_frontend/routes/PageLogin.js
+++ b/nest_frontend/routes/PageLogin.js
@@ -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 (
-
-
- {strings.welcomeToNest}
-
-
-
-
-
-
-
+
+ {strings.welcomeToNest}
+
+ }
+ >
+
+
+
+
+
+
+
+
)
}
diff --git a/nest_frontend/routes/PageRepositoriesList.js b/nest_frontend/routes/PageRepositoriesList.js
index 24c7ed8..840fde2 100644
--- a/nest_frontend/routes/PageRepositoriesList.js
+++ b/nest_frontend/routes/PageRepositoriesList.js
@@ -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,40 +27,47 @@ export default function PageRepositoriesList({ children, className, ...props })
)
return (
-
-
- {strings.dashboard}
-
-
-
-
-
r.is_active)}
- view={pk => history.push(`/repositories/${pk}`)}
- share={pk => history.push(`/repositories/${pk}/share`)}
- alerts={pk => history.push(`/repositories/${pk}/alerts`)}
- archive={archive}
- edit={pk => history.push(`/repositories/${pk}/edit`)}
+
+ }
+ >
+ r.is_active)}
+ view={pk => history.push(`/repositories/${pk}`)}
+ share={pk => history.push(`/repositories/${pk}/share`)}
+ alerts={pk => history.push(`/repositories/${pk}/alerts/`)}
+ archive={archive}
+ edit={pk => history.push(`/repositories/${pk}/edit`)}
+ />
+ }
+ lower={
+ !r.is_active)}
+ view={pk => history.push(`/repositories/${pk}`)}
+ destroy={bv.destroyResource}
+ />
+ }
/>
- !r.is_active)}
- view={pk => history.push(`/repositories/${pk}`)}
- destroy={bv.destroyResource}
- />
-
+
)
}
diff --git a/nest_frontend/routes/PageRepositoriesList.module.css b/nest_frontend/routes/PageRepositoriesList.module.css
deleted file mode 100644
index 7feeae5..0000000
--- a/nest_frontend/routes/PageRepositoriesList.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/nest_frontend/routes/PageRepositoryAlerts.js b/nest_frontend/routes/PageRepositoryAlerts.js
index dc8eaa5..02a2a1f 100644
--- a/nest_frontend/routes/PageRepositoryAlerts.js
+++ b/nest_frontend/routes/PageRepositoryAlerts.js
@@ -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 (
-
-
- {strings.alerts}
-
-
+
+ {makeIcon(faBell)} {strings.alerts}
+
+ }
+ buttons={
+ history.push(`/repositories/${id}/alerts/create`)}
+ >
+ {strings.alertCreate}
+
+ }
+ >
+
{strings.notImplemented}
-
- {strings.notImplemented}
-
-
+
)
}
diff --git a/nest_frontend/routes/PageRepositoryAlerts.module.css b/nest_frontend/routes/PageRepositoryAlerts.module.css
deleted file mode 100644
index f1dd8d9..0000000
--- a/nest_frontend/routes/PageRepositoryAlerts.module.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/nest_frontend/routes/PageRepositoryAlertsCreate.js b/nest_frontend/routes/PageRepositoryAlertsCreate.js
new file mode 100644
index 0000000..6ecb44e
--- /dev/null
+++ b/nest_frontend/routes/PageRepositoryAlertsCreate.js
@@ -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 (
+
+ {makeIcon(faPlus)} {strings.alertCreate}
+
+ }
+ >
+
+
+ )
+}
diff --git a/nest_frontend/routes/PageRepositoryAnalyze.js b/nest_frontend/routes/PageRepositoryAnalyze.js
index f71d530..cfa67ed 100644
--- a/nest_frontend/routes/PageRepositoryAnalyze.js
+++ b/nest_frontend/routes/PageRepositoryAnalyze.js
@@ -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 (
diff --git a/nest_frontend/routes/PageRepositoryCreate.js b/nest_frontend/routes/PageRepositoryCreate.js
index b846d06..f8892f1 100644
--- a/nest_frontend/routes/PageRepositoryCreate.js
+++ b/nest_frontend/routes/PageRepositoryCreate.js
@@ -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 (
-
-
- {strings.dashboardTitle}
-
-
-
+
+ {makeIcon(faPlus)} {strings.dashboardTitle}
+
+ }
+ >
+
+
)
}
diff --git a/nest_frontend/routes/PageRepositoryCreate.module.css b/nest_frontend/routes/PageRepositoryCreate.module.css
deleted file mode 100644
index f062f37..0000000
--- a/nest_frontend/routes/PageRepositoryCreate.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/nest_frontend/routes/PageRepositoryEdit.js b/nest_frontend/routes/PageRepositoryEdit.js
index 6dfed22..8619f68 100644
--- a/nest_frontend/routes/PageRepositoryEdit.js
+++ b/nest_frontend/routes/PageRepositoryEdit.js
@@ -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
- },
+ data =>
)
return (
-
-
- {strings.repoEdit}
-
+
+ {makeIcon(faPencilAlt)} {strings.repoEdit}
+
+ }
+ >
{contents}
-
+
)
}
diff --git a/nest_frontend/routes/PageSettings.js b/nest_frontend/routes/PageSettings.js
index a1f132c..6ee0a4f 100644
--- a/nest_frontend/routes/PageSettings.js
+++ b/nest_frontend/routes/PageSettings.js
@@ -1,25 +1,36 @@
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 (
-
-
-
-
-
-
-
-
-
+
+ {makeIcon(faCog)} {strings.settings}
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/nest_frontend/routes/PageSettings.module.css b/nest_frontend/routes/PageSettings.module.css
deleted file mode 100644
index 1f6bff8..0000000
--- a/nest_frontend/routes/PageSettings.module.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.PageSettings {
- display: flex;
- flex-direction: column;
-
- grid-gap: 10px;
-
- width: 100%;
- height: 100%;
-}
diff --git a/nest_frontend/routes/PageShare.js b/nest_frontend/routes/PageShare.js
index 2067461..d075052 100644
--- a/nest_frontend/routes/PageShare.js
+++ b/nest_frontend/routes/PageShare.js
@@ -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 (
-
-
- {strings.repoShare}
-
-
user["email"] !== loggedUser["email"] && !authorizations.map(a => a.email).includes(user.email))}
- shareWithUser={shareWith}
- header={strings.availableUsers}
- running={usersBvRunning && authBvRunning}
+
+ {strings.repoShare}
+
+ }
+ >
+ user["email"] !== loggedUser["email"] && !authorizations.map(a => a.email).includes(user.email))}
+ shareWithUser={shareWith}
+ header={strings.availableUsers}
+ running={usersBvRunning || authBvRunning}
+ />
+ }
+ lower={<>
+ user["email"] === loggedUser["email"] || authorizations.map(a => a.email).includes(user.email))}
+ unshareWithUser={unshareWith}
+ header={strings.sharingWith}
+ running={usersBvRunning || authBvRunning}
+ />
+ >}
+ error={<>
+ {authBvError ? : null}
+ {usersBvError ? : null}
+ >}
/>
- user["email"] === loggedUser["email"] || authorizations.map(a => a.email).includes(user.email))}
- unshareWithUser={unshareWith}
- header={strings.sharingWith}
- running={usersBvRunning && authBvRunning}
- />
- {authBvError ?
- {strings[authBvError?.data?.code ?? "errorUnknownError"]}
- : null}
- {usersBvError ?
- {strings[usersBvError?.data?.code ?? "errorUnknownError"]}
- : null}
-
+
)
}
diff --git a/nest_frontend/routes/PageShare.module.css b/nest_frontend/routes/PageShare.module.css
deleted file mode 100644
index 474f9e1..0000000
--- a/nest_frontend/routes/PageShare.module.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/nest_frontend/routes/PageUsers.js b/nest_frontend/routes/PageUsers.js
index e216cef..c897734 100644
--- a/nest_frontend/routes/PageUsers.js
+++ b/nest_frontend/routes/PageUsers.js
@@ -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 (
-
-
- {strings.manageUsers}
-
-
-
- {bv.error ?
- {strings[bv.error?.data?.code ?? "errorUnknownError"]}
- : null}
-
+
+ {makeIcon(faUserCog)} {strings.manageUsers}
+
+ }
+ >
+
+ }
+ lower={
+
+ }
+ error={error ? : null}
+ />
+
)
}
diff --git a/nest_frontend/routes/PageUsers.module.css b/nest_frontend/routes/PageUsers.module.css
deleted file mode 100644
index 6731e46..0000000
--- a/nest_frontend/routes/PageUsers.module.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/nest_frontend/utils/countTweetWords.js b/nest_frontend/utils/countTweetWords.js
index 172f501..551237c 100644
--- a/nest_frontend/utils/countTweetWords.js
+++ b/nest_frontend/utils/countTweetWords.js
@@ -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) {
diff --git a/nest_frontend/utils/renderContents.js b/nest_frontend/utils/renderContents.js
index f1ea761..9f8eee5 100644
--- a/nest_frontend/utils/renderContents.js
+++ b/nest_frontend/utils/renderContents.js
@@ -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