mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-25 06:24:19 +00:00
💥 Refactor even more...
This commit is contained in:
parent
c9cf2a1142
commit
919cbefd0f
12 changed files with 244 additions and 239 deletions
|
@ -2,7 +2,7 @@ import React from "react"
|
|||
import BoxFull from "../base/BoxFull"
|
||||
import useRepositoryViewer from "../../hooks/useRepositoryViewer"
|
||||
import useStrings from "../../hooks/useStrings"
|
||||
import { ContainsFilter } from "../../utils/Filter"
|
||||
import { FilterContains } from "../../utils/Filter"
|
||||
import FormInlineText from "./FormInlineText"
|
||||
import { faFont } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
@ -13,7 +13,7 @@ export default function BoxFilterContains({ ...props }) {
|
|||
const { appendFilter } = useRepositoryViewer()
|
||||
|
||||
const submit = value => {
|
||||
appendFilter(new ContainsFilter(false, value))
|
||||
appendFilter(new FilterContains(false, value))
|
||||
}
|
||||
|
||||
// TODO: add this string
|
||||
|
|
|
@ -3,7 +3,7 @@ import BoxFull from "../base/BoxFull"
|
|||
import { faClock, faHashtag } from "@fortawesome/free-solid-svg-icons"
|
||||
import useRepositoryViewer from "../../hooks/useRepositoryViewer"
|
||||
import useStrings from "../../hooks/useStrings"
|
||||
import { AfterDatetimeFilter } from "../../utils/Filter"
|
||||
import { FilterInsideTimeRay } from "../../utils/Filter"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import FormInlineBADatetime from "./FormInlineBADatetime"
|
||||
|
||||
|
@ -13,7 +13,7 @@ export default function BoxFilterDatetime({ ...props }) {
|
|||
const { appendFilter } = useRepositoryViewer()
|
||||
|
||||
const submit = ({ date, isBefore }) => {
|
||||
appendFilter(new AfterDatetimeFilter(isBefore, date))
|
||||
appendFilter(new FilterInsideTimeRay(isBefore, date))
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@ import useRepositoryViewer from "../../hooks/useRepositoryViewer"
|
|||
import useStrings from "../../hooks/useStrings"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faLocationArrow, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
import { HasPlaceFilter } from "../../utils/Filter"
|
||||
import { FilterWithPlace } from "../../utils/Filter"
|
||||
import ButtonIconOnly from "../base/ButtonIconOnly"
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ export default function BoxFilterHasPlace({ ...props }) {
|
|||
const { appendFilter } = useRepositoryViewer()
|
||||
|
||||
const submit = () => {
|
||||
appendFilter(new HasPlaceFilter(false))
|
||||
appendFilter(new FilterWithPlace(false))
|
||||
}
|
||||
|
||||
// TODO: translate this
|
||||
|
|
|
@ -3,7 +3,7 @@ import BoxFull from "../base/BoxFull"
|
|||
import { faClock } from "@fortawesome/free-solid-svg-icons"
|
||||
import useRepositoryViewer from "../../hooks/useRepositoryViewer"
|
||||
import useStrings from "../../hooks/useStrings"
|
||||
import { HashtagFilter } from "../../utils/Filter"
|
||||
import { FilterHashtag } from "../../utils/Filter"
|
||||
import FormInlineHashtag from "./FormInlineHashtag"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
@ -13,7 +13,7 @@ export default function BoxFilterHashtag({ ...props }) {
|
|||
const { appendFilter } = useRepositoryViewer()
|
||||
|
||||
const submit = value => {
|
||||
appendFilter(new HashtagFilter(false, value))
|
||||
appendFilter(new FilterHashtag(false, value))
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,7 @@ import useStrings from "../../hooks/useStrings"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faMapPin } from "@fortawesome/free-solid-svg-icons"
|
||||
import FormInlineLocation from "./FormInlineLocation"
|
||||
import { LocationRadiusFilter } from "../../utils/Filter"
|
||||
import { FilterInsideMapArea } from "../../utils/Filter"
|
||||
|
||||
|
||||
export default function BoxFilterLocation({ ...props }) {
|
||||
|
@ -15,7 +15,7 @@ export default function BoxFilterLocation({ ...props }) {
|
|||
const { appendFilter, mapViewHook } = useRepositoryViewer()
|
||||
|
||||
const submit = () => {
|
||||
appendFilter(new LocationRadiusFilter(false, mapViewHook.center, mapViewHook.radius))
|
||||
appendFilter(new FilterInsideMapArea(false, mapViewHook.center, mapViewHook.radius))
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,7 +3,7 @@ import BoxFull from "../base/BoxFull"
|
|||
import { faAt } from "@fortawesome/free-solid-svg-icons"
|
||||
import useRepositoryViewer from "../../hooks/useRepositoryViewer"
|
||||
import useStrings from "../../hooks/useStrings"
|
||||
import { UserFilter } from "../../utils/Filter"
|
||||
import { FilterPoster } from "../../utils/Filter"
|
||||
import FormInlineUser from "./FormInlineUser"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default function BoxFilterUser({ ...props }) {
|
|||
const { appendFilter } = useRepositoryViewer()
|
||||
|
||||
const submit = value => {
|
||||
appendFilter(new UserFilter(false, value))
|
||||
appendFilter(new FilterPoster(false, value))
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,7 +4,7 @@ import ContextLanguage from "../../contexts/ContextLanguage"
|
|||
import BoxFull from "../base/BoxFull"
|
||||
import Empty from "./Empty"
|
||||
import ContextRepositoryViewer from "../../contexts/ContextRepositoryViewer"
|
||||
import { ContainsFilter } from "../../utils/Filter"
|
||||
import { FilterContains } from "../../utils/Filter"
|
||||
|
||||
|
||||
export default function BoxVisualizationWordcloud({ ...props }) {
|
||||
|
@ -20,7 +20,7 @@ export default function BoxVisualizationWordcloud({ ...props }) {
|
|||
}
|
||||
|
||||
const onWordClick = word => {
|
||||
appendFilter(new ContainsFilter(false, word.text))
|
||||
appendFilter(new FilterContains(false, word.text))
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
222
nest_frontend/objects/Filter.js
Normal file
222
nest_frontend/objects/Filter.js
Normal file
|
@ -0,0 +1,222 @@
|
|||
import {
|
||||
faAt,
|
||||
faClock,
|
||||
faFilter,
|
||||
faFont,
|
||||
faHashtag,
|
||||
faLocationArrow,
|
||||
faMapMarkerAlt,
|
||||
faMapPin,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
/**
|
||||
* A filter applicable in the Analysis mode.
|
||||
*/
|
||||
export class Filter {
|
||||
negate
|
||||
|
||||
/**
|
||||
* @param negate - If the filter output should be reversed.
|
||||
*/
|
||||
constructor(negate = false) {
|
||||
this.negate = negate
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tweet passed through the filter or not, without applying `negate`.
|
||||
*
|
||||
* @param tweet - The tweet to check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
check(tweet) {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tweet passed through the filter or not, applying `negate`.
|
||||
*
|
||||
* @param tweet - The tweet to check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exec(tweet) {
|
||||
return Boolean(this.check(tweet) ^ this.negate)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Grey",
|
||||
icon: faFilter,
|
||||
children: this.negate ? "False" : "True"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a tweet contains a string.
|
||||
*/
|
||||
export class FilterContains extends Filter {
|
||||
string
|
||||
|
||||
constructor(word, negate = false) {
|
||||
super(negate)
|
||||
this.string = word.toLowerCase().trim()
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return tweet.content?.toLowerCase().includes(this.string)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Grey",
|
||||
icon: faFont,
|
||||
children: this.string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet contains an hashtag.
|
||||
*/
|
||||
export class FilterHashtag extends FilterContains {
|
||||
hashtag
|
||||
|
||||
constructor(hashtag, negate = false) {
|
||||
super(negate, `#${hashtag}`)
|
||||
this.hashtag = hashtag
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Grey",
|
||||
icon: faHashtag,
|
||||
children: this.hashtag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet was posted by a certain user.
|
||||
*/
|
||||
export class FilterPoster extends Filter {
|
||||
poster
|
||||
|
||||
constructor(poster, negate = false) {
|
||||
super(negate)
|
||||
this.poster = poster
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return tweet.poster.toLowerCase() === this.poster.toLowerCase()
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Green",
|
||||
icon: faAt,
|
||||
children: this.poster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet contains `location` metadata.
|
||||
*/
|
||||
export class FilterWithLocation extends Filter {
|
||||
constructor(negate = false) {
|
||||
super(negate)
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return Boolean(tweet["location"])
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Red",
|
||||
icon: faLocationArrow,
|
||||
children: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet contains `place` metadata.
|
||||
*/
|
||||
export class FilterWithPlace extends Filter {
|
||||
constructor(negate = false) {
|
||||
super(negate)
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return Boolean(tweet["place"])
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Red",
|
||||
icon: faMapMarkerAlt,
|
||||
children: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet's `location` is inside a {@link MapArea}.
|
||||
*/
|
||||
export class FilterInsideMapArea extends FilterWithLocation {
|
||||
mapArea
|
||||
|
||||
constructor(mapArea, negate = false) {
|
||||
super(negate)
|
||||
this.mapArea = mapArea
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
if(!super.check(tweet)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.mapArea.includes(tweet.location)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Red",
|
||||
icon: faLocationArrow,
|
||||
children: this.mapArea.toHumanString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a tweet's `post_time` is inside a {@link TimeRay}.
|
||||
*/
|
||||
export class FilterInsideTimeRay extends Filter {
|
||||
timeRay
|
||||
|
||||
constructor(timeRay, negate = false) {
|
||||
super(negate)
|
||||
this.timeRay = timeRay
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return this.datetime < new Date(tweet["insert_time"])
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Yellow",
|
||||
icon: faClock,
|
||||
children: this.timeRay.toString()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ export default class MapArea {
|
|||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
return `${this.radius} ${this.center.toString()}`
|
||||
return `< ${this.radius} ${this.center.toString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,9 +47,9 @@ export default class MapArea {
|
|||
toHumanString() {
|
||||
if(this.radius >= 2000) {
|
||||
const kmRadius = Math.round(this.radius / 1000)
|
||||
return `${kmRadius}km ${this.center.toHumanString()}`
|
||||
return `< ${kmRadius}km ${this.center.toHumanString()}`
|
||||
}
|
||||
return `${this.radius}m ${this.center.toHumanString()}`
|
||||
return `< ${this.radius}m ${this.center.toHumanString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,5 +9,5 @@ test("MapArea can be constructed", () => {
|
|||
|
||||
test("MapArea can be rendered to a spec-compatible string", () => {
|
||||
const mapArea = new MapArea(1000, new Coordinates(0.0, 0.0))
|
||||
expect(mapArea.toString()).toBe("1000 0.0000000 0.0000000")
|
||||
expect(mapArea.toString()).toBe("< 1000 0.0000000 0.0000000")
|
||||
})
|
||||
|
|
|
@ -21,4 +21,8 @@ export default class TimeRay {
|
|||
toString() {
|
||||
return `${this.isBefore ? "<" : ">"} ${this.date.toISOString()}`
|
||||
}
|
||||
|
||||
includes(date) {
|
||||
return Boolean((this.date > date) ^ this.isBefore)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
import { Location } from "./location"
|
||||
import {
|
||||
faAt,
|
||||
faClock,
|
||||
faFilter,
|
||||
faFont,
|
||||
faHashtag,
|
||||
faLocationArrow,
|
||||
faMapMarkerAlt,
|
||||
faMapPin,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
export class Filter {
|
||||
negate
|
||||
|
||||
constructor(negate) {
|
||||
this.negate = negate
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return true
|
||||
}
|
||||
|
||||
exec(tweet) {
|
||||
return this.check(tweet) ^ this.negate
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Grey"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faFilter
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.negate ? "False" : "True"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ContainsFilter extends Filter {
|
||||
word
|
||||
|
||||
constructor(negate, word) {
|
||||
super(negate)
|
||||
this.word = word.toLowerCase().trim()
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return tweet.content?.toLowerCase().includes(this.word)
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Grey"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faFont
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.word
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
constructor(negate, user) {
|
||||
super(negate)
|
||||
this.user = user.toLowerCase().trim().replace(/^@/, "")
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return tweet.poster.toLowerCase() === this.user
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Green"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faAt
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.user
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class HasLocationFilter extends Filter {
|
||||
constructor(negate) {
|
||||
super(negate)
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return Boolean(tweet["location"])
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Red"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faMapMarkerAlt
|
||||
}
|
||||
|
||||
text() {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class HasPlaceFilter extends Filter {
|
||||
constructor(negate) {
|
||||
super(negate)
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return Boolean(tweet["place"])
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Red"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faLocationArrow
|
||||
}
|
||||
|
||||
text() {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class LocationRadiusFilter extends HasLocationFilter {
|
||||
center
|
||||
radius
|
||||
|
||||
constructor(negate, center, radius) {
|
||||
super(negate)
|
||||
this.center = center
|
||||
this.radius = radius
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
if(!super.check(tweet)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// FIXME: Maths is hard
|
||||
const location = Location.fromTweet(tweet)
|
||||
const latDiff = Math.abs(location.lat - this.center.lat)
|
||||
const lngDiff = Math.abs(location.lng - this.center.lng)
|
||||
const squaredDistance = Math.pow(latDiff, 2) + Math.pow(lngDiff, 2)
|
||||
const squaredRadius = Math.pow(this.radius, 2)
|
||||
|
||||
return squaredDistance < squaredRadius
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Red"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faMapPin
|
||||
}
|
||||
|
||||
text() {
|
||||
return `< ${this.radius}m ${this.center.lat.toFixed(3)} ${this.center.lng.toFixed(3)}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AfterDatetimeFilter extends Filter {
|
||||
datetime
|
||||
|
||||
constructor(negate, datetime) {
|
||||
super(negate)
|
||||
this.datetime = datetime
|
||||
}
|
||||
|
||||
check(tweet) {
|
||||
return this.datetime < new Date(tweet["insert_time"])
|
||||
}
|
||||
|
||||
color() {
|
||||
return "Yellow"
|
||||
}
|
||||
|
||||
icon() {
|
||||
return faClock
|
||||
}
|
||||
|
||||
text() {
|
||||
return `${this.negate ? "<" : ">"} ${this.datetime.toISOString()}`
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue