diff --git a/nest_frontend/components/base/BoxWordcloud.js b/nest_frontend/components/base/BoxWordcloud.js
index a74cf3d..8bad4f1 100644
--- a/nest_frontend/components/base/BoxWordcloud.js
+++ b/nest_frontend/components/base/BoxWordcloud.js
@@ -1,33 +1,46 @@
-import React from "react"
+import React, { useMemo } from "react"
import BoxFull from "../base/BoxFull"
import ReactWordcloud from "@steffo/nest-react-wordcloud"
import Style from "./BoxWordcloud.module.css"
/**
- * A Box which displays a wordcloud.
+ * A {@link BoxFull} which displays a wordcloud.
*
* @param words - A list of word objects, made of a string "text" and a number "value"
+ * @param options - Additional options to pass to {@link ReactWordcloud}.
* @param props - Additional props to pass to the box.
* @returns {JSX.Element}
* @constructor
*/
-export default function BoxWordcloud({ words, ...props }) {
+export default function BoxWordcloud({ words, callbacks = {}, ...props }) {
+ const wordcloud = useMemo(
+ () => (
+
+ ),
+ [words]
+ )
+
return (
-
+ {wordcloud}
)
diff --git a/nest_frontend/components/interactive/BadgeFilter.js b/nest_frontend/components/interactive/BadgeFilter.js
index dbfaf90..d4123f7 100644
--- a/nest_frontend/components/interactive/BadgeFilter.js
+++ b/nest_frontend/components/interactive/BadgeFilter.js
@@ -19,7 +19,7 @@ export default function BadgeFilter({ filter }) {
removeFilter(filter)}
>
{filter.text()}
diff --git a/nest_frontend/components/interactive/BoxFilterContains.js b/nest_frontend/components/interactive/BoxFilterContains.js
new file mode 100644
index 0000000..b36f4ee
--- /dev/null
+++ b/nest_frontend/components/interactive/BoxFilterContains.js
@@ -0,0 +1,28 @@
+import React, { useContext, useState } from "react"
+import BoxFull from "../base/BoxFull"
+import useRepositoryViewer from "../../hooks/useRepositoryViewer"
+import useStrings from "../../hooks/useStrings"
+import { ContainsFilter } from "../../utils/Filter"
+import FormInlineText from "./FormInlineText"
+import { faFont } from "@fortawesome/free-solid-svg-icons"
+
+
+export default function BoxFilterContains({ ...props }) {
+ const strings = useStrings()
+ const { appendFilter } = useRepositoryViewer()
+
+ const submit = value => {
+ appendFilter(new ContainsFilter(false, value))
+ }
+
+ // TODO: add this string
+ return (
+
+
+
+ )
+}
diff --git a/nest_frontend/components/interactive/BoxFilterHashtag.js b/nest_frontend/components/interactive/BoxFilterHashtag.js
new file mode 100644
index 0000000..4096492
--- /dev/null
+++ b/nest_frontend/components/interactive/BoxFilterHashtag.js
@@ -0,0 +1,43 @@
+import React, { useContext, useState } from "react"
+import BoxFull from "../base/BoxFull"
+import FormInline from "../base/FormInline"
+import InputWithIcon from "../base/InputWithIcon"
+import Style from "./BoxConditionUser.module.css"
+import { faAt, faFilter, faFont, faHashtag } from "@fortawesome/free-solid-svg-icons"
+import ButtonIconOnly from "../base/ButtonIconOnly"
+import useRepositoryViewer from "../../hooks/useRepositoryViewer"
+import useStrings from "../../hooks/useStrings"
+import { ContainsFilter, HashtagFilter, UserFilter } from "../../utils/Filter"
+import Condition from "../../utils/Condition"
+import FormInlineText from "./FormInlineText"
+
+
+const INVALID_HASHTAG_CHARACTERS = /([^a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73\u2b740-\u2b81\u2f800-\u2fa1])/g
+
+
+export default function BoxFilterHashtag({ ...props }) {
+ // TODO: Translate this
+ // TODO: and also use a better string maybe
+ const strings = useStrings()
+
+ const { appendFilter } = useRepositoryViewer()
+
+ const validate = value => {
+ return value.replace(INVALID_HASHTAG_CHARACTERS, "")
+ }
+
+ const submit = value => {
+ appendFilter(new HashtagFilter(false, value))
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/nest_frontend/components/interactive/BoxFilterUser.js b/nest_frontend/components/interactive/BoxFilterUser.js
new file mode 100644
index 0000000..37d24ea
--- /dev/null
+++ b/nest_frontend/components/interactive/BoxFilterUser.js
@@ -0,0 +1,40 @@
+import React, { useContext, useState } from "react"
+import BoxFull from "../base/BoxFull"
+import FormInline from "../base/FormInline"
+import InputWithIcon from "../base/InputWithIcon"
+import Style from "./BoxConditionUser.module.css"
+import { faAt, faFilter, faFont } from "@fortawesome/free-solid-svg-icons"
+import ButtonIconOnly from "../base/ButtonIconOnly"
+import useRepositoryViewer from "../../hooks/useRepositoryViewer"
+import useStrings from "../../hooks/useStrings"
+import { ContainsFilter, UserFilter } from "../../utils/Filter"
+import Condition from "../../utils/Condition"
+import FormInlineText from "./FormInlineText"
+
+
+export default function BoxFilterUser({ ...props }) {
+ // TODO: Translate this
+ // TODO: and also use a better string maybe
+ const strings = useStrings()
+
+ const { appendFilter } = useRepositoryViewer()
+
+ const validate = value => {
+ return value.replace(/[^a-zA-Z0-9]/g, "")
+ }
+
+ const submit = value => {
+ appendFilter(new UserFilter(false, value))
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/nest_frontend/components/interactive/BoxVisualizationWordcloud.js b/nest_frontend/components/interactive/BoxVisualizationWordcloud.js
index 0c44d2d..543274e 100644
--- a/nest_frontend/components/interactive/BoxVisualizationWordcloud.js
+++ b/nest_frontend/components/interactive/BoxVisualizationWordcloud.js
@@ -4,11 +4,12 @@ import ContextLanguage from "../../contexts/ContextLanguage"
import BoxFull from "../base/BoxFull"
import Empty from "./Empty"
import ContextRepositoryViewer from "../../contexts/ContextRepositoryViewer"
+import { ContainsFilter } from "../../utils/Filter"
export default function BoxVisualizationWordcloud({ ...props }) {
const { strings } = useContext(ContextLanguage)
- const {words} = useContext(ContextRepositoryViewer)
+ const {words, appendFilter} = useContext(ContextRepositoryViewer)
if(words.length === 0) {
return (
@@ -18,7 +19,16 @@ export default function BoxVisualizationWordcloud({ ...props }) {
)
}
+ const onWordClick = word => {
+ appendFilter(new ContainsFilter(false, word.text))
+ }
+
return (
-
+
)
}
diff --git a/nest_frontend/components/interactive/FormInlineText.js b/nest_frontend/components/interactive/FormInlineText.js
new file mode 100644
index 0000000..660e4aa
--- /dev/null
+++ b/nest_frontend/components/interactive/FormInlineText.js
@@ -0,0 +1,39 @@
+import React, { useState } from "react"
+import FormInline from "../base/FormInline"
+import InputWithIcon from "../base/InputWithIcon"
+import { faPlus } from "@fortawesome/free-solid-svg-icons"
+import ButtonIconOnly from "../base/ButtonIconOnly"
+import Style from "./FormInlineText.module.css"
+
+
+export default function FormInlineText({ textIcon, placeholder, buttonIcon = faPlus, buttonColor = "Green", validate = value => value, submit, ...props }) {
+ const [value, setValue] = useState("")
+
+ const _onSubmit = event => {
+ event.preventDefault()
+ submit(value)
+ setValue("")
+ }
+
+ const _onChange = event => {
+ setValue(validate(event.target.value))
+ }
+
+ return (
+
+
+
+
+ )
+}
diff --git a/nest_frontend/components/interactive/FormInlineText.module.css b/nest_frontend/components/interactive/FormInlineText.module.css
new file mode 100644
index 0000000..f8b3933
--- /dev/null
+++ b/nest_frontend/components/interactive/FormInlineText.module.css
@@ -0,0 +1,7 @@
+.Input {
+ flex-shrink: 1;
+}
+
+.Button {
+
+}
\ No newline at end of file
diff --git a/nest_frontend/components/interactive/PickerFilter.js b/nest_frontend/components/interactive/PickerFilter.js
index 899166c..4416abf 100644
--- a/nest_frontend/components/interactive/PickerFilter.js
+++ b/nest_frontend/components/interactive/PickerFilter.js
@@ -16,6 +16,12 @@ export default function PickerFilter({ ...props }) {
name={"contains"}
icon={faFont}
/>
+
- {filterTab === "contains" ? "Contains" : null}
- {filterTab === "user" ? "User" : null}
+ {filterTab === "contains" ? : null}
+ {filterTab === "hashtag" ? : null}
+ {filterTab === "user" ? : null}
{filterTab === "time" ? "Time" : null}
{filterTab === "location" ? "Location" : null}
>
diff --git a/nest_frontend/components/providers/RepositoryViewer.module.css b/nest_frontend/components/providers/RepositoryViewer.module.css
index 47ebd4b..c678bc5 100644
--- a/nest_frontend/components/providers/RepositoryViewer.module.css
+++ b/nest_frontend/components/providers/RepositoryViewer.module.css
@@ -10,7 +10,7 @@
grid-gap: 10px;
grid-template-columns: 1fr 1fr;
- grid-template-rows: auto auto 3fr auto 1fr;
+ grid-template-rows: auto auto 1fr auto auto;
width: 100%;
height: 100%;
diff --git a/nest_frontend/contexts/ContextLanguage.js b/nest_frontend/contexts/ContextLanguage.js
index 8831fff..a48fb29 100644
--- a/nest_frontend/contexts/ContextLanguage.js
+++ b/nest_frontend/contexts/ContextLanguage.js
@@ -1,4 +1,5 @@
import { createContext } from "react"
+import LocalizationStrings from "../LocalizationStrings"
/**
@@ -6,9 +7,11 @@ import { createContext } from "react"
* - `lang`: a string corresponding to the ISO 639-1 code of the current language
* - `setLang`: a function to change the current language
* - `strings`: an object containing all strings of the current language
+ *
+ * Defaults to Italian.
*/
export default createContext({
- lang: null,
+ lang: "it",
setLang: () => console.error("Trying to setLang while outside a ContextServer.Provider!"),
- strings: null,
+ strings: LocalizationStrings.it,
})
diff --git a/nest_frontend/hooks/useRepositoryViewer.js b/nest_frontend/hooks/useRepositoryViewer.js
index 4ab4914..20e84f5 100644
--- a/nest_frontend/hooks/useRepositoryViewer.js
+++ b/nest_frontend/hooks/useRepositoryViewer.js
@@ -6,7 +6,7 @@ import ContextRepositoryViewer from "../contexts/ContextRepositoryViewer"
/**
* Hook to quickly use {@link ContextRepositoryEditor}.
*/
-export default function useRepositoryEditor() {
+export default function useRepositoryViewer() {
const context = useContext(ContextRepositoryViewer)
if(!context) {
throw new Error("This component must be placed inside a RepositoryViewer.")
diff --git a/nest_frontend/hooks/useStrings.js b/nest_frontend/hooks/useStrings.js
new file mode 100644
index 0000000..4a02c4a
--- /dev/null
+++ b/nest_frontend/hooks/useStrings.js
@@ -0,0 +1,10 @@
+import { useContext } from "react"
+import ContextLanguage from "../contexts/ContextLanguage"
+
+
+/**
+ * Hook to quickly use the strings of {@link ContextLanguage}.
+ */
+export default function useStrings() {
+ return useContext(ContextLanguage).strings
+}
\ No newline at end of file
diff --git a/nest_frontend/utils/Filter.js b/nest_frontend/utils/Filter.js
index f9525ce..8e88463 100644
--- a/nest_frontend/utils/Filter.js
+++ b/nest_frontend/utils/Filter.js
@@ -1,8 +1,8 @@
-import { Location } from "../components/interactive/location"
+import {Location} from "./location"
import {
faAt,
faFilter,
- faFont,
+ faFont, faHashtag,
faLocationArrow,
faMap,
faMapMarkerAlt,
@@ -64,6 +64,24 @@ export class ContainsFilter extends Filter {
}
+export class HashtagFilter extends ContainsFilter {
+ hashtag
+
+ constructor(negate, hashtag) {
+ super(negate, `#${hashtag}`);
+ this.hashtag = hashtag
+ }
+
+ icon() {
+ return faHashtag
+ }
+
+ text() {
+ return this.hashtag
+ }
+}
+
+
export class UserFilter extends Filter {
user