From 0c7d0b64327b18dc86fd1dce6be05f99915eda0a Mon Sep 17 00:00:00 2001 From: GRBurst Date: Fri, 24 May 2024 18:58:44 +0200 Subject: [PATCH] store custome filters in localstorage and restore on load Signed-off-by: GRBurst --- frontend/src/components/CustomFilters.tsx | 17 ++++---- frontend/src/components/FilterableJobList.tsx | 16 ++++--- frontend/src/components/HnJobs.tsx | 12 +++++- frontend/src/components/TagFilterBar.tsx | 43 ++++++++++--------- frontend/src/models/TagFilter.ts | 12 +++++- frontend/src/utils/hn.ts | 1 - 6 files changed, 64 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/CustomFilters.tsx b/frontend/src/components/CustomFilters.tsx index 5868bef..3997317 100644 --- a/frontend/src/components/CustomFilters.tsx +++ b/frontend/src/components/CustomFilters.tsx @@ -9,14 +9,15 @@ import { replaceTagCaptureGroup } from '../utils/hn' interface CustomTagFilterProps { onTagAdd: (key: string, tag: TagFilter) => void } -const CustomTagFilter = ({onTagAdd}: CustomTagFilterProps) => { +const CustomTagFilter = ({ onTagAdd }: CustomTagFilterProps) => { const [tagName, setTagName] = useState("") const [tagPattern, setTagPattern] = useState("") const [tagPatternFlags, setTagPatternFlags] = useState("") const addNewTag = () => { - if(tagName !== undefined && tagName != "") { - const newTag = (tagPattern !== undefined && tagPattern != "") ? TagFilter({name: tagName, pattern: RegExp(tagPattern, tagPatternFlags)}) : TagFilterSimple(tagName) + if (tagName !== undefined && tagName != "") { + const newFlags = (tagPatternFlags !== undefined && tagPatternFlags != "") ? tagPattern : "gmi" + const newTag = (tagPattern !== undefined && tagPattern != "") ? TagFilter({ name: tagName, pattern: RegExp(tagPattern, newFlags) }) : TagFilterSimple(tagName) onTagAdd("Custom", replaceTagCaptureGroup(newTag)) setTagName("") setTagPattern("") @@ -56,10 +57,10 @@ const CustomTagFilter = ({onTagAdd}: CustomTagFilterProps) => { interface SearchFilterProps { onTextSearch: (needle: string | undefined) => void } -const SearchFilter = ({onTextSearch}: SearchFilterProps) => { +const SearchFilter = ({ onTextSearch }: SearchFilterProps) => { const { Search } = Input; - const onSearchInput: SearchProps['onSearch'] = (value) => { - if(value.length >= 3) { + const onSearchInput: SearchProps['onSearch'] = (value) => { + if (value.length >= 3) { onTextSearch(value) } else if (value.length == 0) { onTextSearch(undefined) @@ -71,7 +72,7 @@ const SearchFilter = ({onTextSearch}: SearchFilterProps) => { placeholder="input search text" allowClear onSearch={onSearchInput} - onChange={(e) => {if(e.target.value.length >= 3) { onTextSearch(e.target.value)}}} + onChange={(e) => { if (e.target.value.length >= 3) { onTextSearch(e.target.value) } }} enterButton /> ) @@ -81,7 +82,7 @@ interface CustomFiltersProps { onTagAdd: (key: string, tag: TagFilter) => void onSearch: (needle: string | undefined) => void } -const CustomFilters = ({onTagAdd, onSearch}: CustomFiltersProps) => { +const CustomFilters = ({ onTagAdd, onSearch }: CustomFiltersProps) => { return ( diff --git a/frontend/src/components/FilterableJobList.tsx b/frontend/src/components/FilterableJobList.tsx index cdf72c0..47f6959 100644 --- a/frontend/src/components/FilterableJobList.tsx +++ b/frontend/src/components/FilterableJobList.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import sanitizeHtml from "sanitize-html"; import { Item } from "../models/Item"; -import { TagFilter, TagFilters } from "../models/TagFilter"; +import { TagFilter, TagFilters, tagFilterToString } from "../models/TagFilter"; import { filterByRegexAny, flatFilters, itemFilter } from "../utils/hn"; import { TagFilterBar } from "./TagFilterBar"; @@ -61,9 +61,9 @@ const ItemList = ({ items, tagFilters, searchFilter }: ItemListProps) => ( sanitizeHtml(item.text ?? ""), searchFilter !== undefined ? [ - ...tagFilters, - TagFilter({ name: "_Search_", pattern: RegExp(searchFilter) }), - ] + ...tagFilters, + TagFilter({ name: "_Search_", pattern: RegExp(searchFilter) }), + ] : tagFilters ); return ( @@ -181,8 +181,14 @@ const FilterableJobList = ({ onInactive={(key: string, tag: TagFilter) => removeFilters(key, tag, activeTagFilters, setActiveTagFilters) } - onTagAdd={(key: string, tag: TagFilter) => + onTagAdd={(key: string, tag: TagFilter) => { addFilters(key, tag, allTagFilters, setAllTagFilters) + //TODO: put name at central place + localStorage.setItem( + "CustomFilters", + JSON.stringify(Array.from(allTagFilters.get("Custom") ?? []).map(f => tagFilterToString(f))) + ) + } } onSearch={(needle: string | undefined) => setSearchFilter(needle)} /> diff --git a/frontend/src/components/HnJobs.tsx b/frontend/src/components/HnJobs.tsx index 7d39c69..66dc313 100644 --- a/frontend/src/components/HnJobs.tsx +++ b/frontend/src/components/HnJobs.tsx @@ -1,3 +1,4 @@ +import { HashSet as HSet } from "effect"; import type { HashSet } from "effect/HashSet"; import { lazy, useEffect, useState } from "react"; @@ -11,7 +12,7 @@ import { import { App, ConfigProvider, theme } from "antd"; import { GithubIcon } from "./Icons"; -import { TagFilter } from "../models/TagFilter"; +import { TagFilter, tagFilterFromString } from "../models/TagFilter"; import { locations, technologies, misc } from "../utils/predefined"; @@ -57,6 +58,15 @@ const HnJobs = () => { console.info("Changing color mode to: ", colorScheme); setIsDarkMode(true); }); + + // Add custom filters from local storage + const customFilters = localStorage.getItem("CustomFilters") + if (customFilters) { + const restoredFilters = JSON.parse(customFilters).map((f: string) => tagFilterFromString(f)) + console.log("Restoring custom filters: ", restoredFilters) + predefinedFilterTags.set("Custom", HSet.fromIterable(restoredFilters)); + } + }, []); return ( diff --git a/frontend/src/components/TagFilterBar.tsx b/frontend/src/components/TagFilterBar.tsx index 50dc6da..8859892 100644 --- a/frontend/src/components/TagFilterBar.tsx +++ b/frontend/src/components/TagFilterBar.tsx @@ -20,7 +20,7 @@ function TagButton({ tagFilter, filterKey, isActive, onActiveChange }: TagProps) TagButton.defaultProps = { isActive: false, - onActiveChange: () => {} + onActiveChange: () => { } } interface TagFilterProps { @@ -32,28 +32,29 @@ interface TagFilterProps { onSearch: (needle: string | undefined) => void } -function TagFilterBar({allTags, activeTags, onActive, onInactive, onTagAdd, onSearch}: TagFilterProps) { +// TODO: Drawer instead of Collapsible? +function TagFilterBar({ allTags, activeTags, onActive, onInactive, onTagAdd, onSearch }: TagFilterProps) { const tagSort = (t1: TagF, t2: TagF): number => { - if(t1.name == "Remote") return -1 - if(t2.name == "Remote") return 1 + if (t1.name == "Remote") return -1 + if (t2.name == "Remote") return 1 return (t1.name < t2.name) ? -1 : 1 } - + const filterNonEmpty = Array.from(activeTags.keys()).reduce((acc, key) => acc || Array.from(activeTags.get(key) ?? []).length > 0, false) const panelStyle: CSSProperties = { border: 'none' }; - + const activeFiltersDisplay = (filterNonEmpty) ? <>

Active Filter

- {Array.from(activeTags.keys()).map((key) => + {Array.from(activeTags.keys()).map((key) => Array.from(activeTags.get(key) ?? []).length > 0 ? ( - Array.from(activeTags.get(key) ?? HSet.empty()).sort(tagSort).map(tag => - ) + Array.from(activeTags.get(key) ?? HSet.empty()).sort(tagSort).map(tag => + ) ) : )} - : <> + : <> const collapsableTagFilters: CollapseProps['items'] = ( Array.from(allTags.keys()).map((tagKey) => ( @@ -62,8 +63,8 @@ function TagFilterBar({allTags, activeTags, onActive, onInactive, onTagAdd, onSe label:

{tagKey}

, children: ( - { Array.from(HSet.difference(allTags.get(tagKey) ?? HSet.empty(), activeTags.get(tagKey) ?? HSet.empty())).sort(tagSort).map(tag => - ) + {Array.from(HSet.difference(allTags.get(tagKey) ?? HSet.empty(), activeTags.get(tagKey) ?? HSet.empty())).sort(tagSort).map(tag => + ) } ), @@ -73,18 +74,18 @@ function TagFilterBar({allTags, activeTags, onActive, onInactive, onTagAdd, onSe ) const collapsableCustomFilter: CollapseProps['items'] = [ - { - key: "custom", - label:

Custom Filters

, - children: , - style: panelStyle - } - ] - + { + key: "custom", + label:

Custom Filters

, + children: , + style: panelStyle + } + ] + return
{activeFiltersDisplay} -
+ } export { TagFilterBar, TagButton } \ No newline at end of file diff --git a/frontend/src/models/TagFilter.ts b/frontend/src/models/TagFilter.ts index 6666c64..335fb38 100644 --- a/frontend/src/models/TagFilter.ts +++ b/frontend/src/models/TagFilter.ts @@ -10,6 +10,16 @@ export interface TagFilter { export const TagFilter = Data.case() export const TagFilterSimple = (tagName: string) => TagFilter({name: tagName, pattern: RegExp(`(?:^|\\b|\\s)(?:${tagName})(?:$|\\b|\\s)`, "gmi")}) - +export const tagFilterToString = (tag: TagFilter): string => JSON.stringify({ + name: tag.name, + pattern: { + source: tag.pattern.source, + flags: tag.pattern.flags, + } +}) +export const tagFilterFromString = (tagStr: string): TagFilter => { + const parsedJson = JSON.parse(tagStr) + return TagFilter({name: parsedJson.name, pattern: RegExp(parsedJson.pattern.source, parsedJson.pattern.flags)}) +} export type TagFilters = HashSet diff --git a/frontend/src/utils/hn.ts b/frontend/src/utils/hn.ts index deb7549..37dae4f 100644 --- a/frontend/src/utils/hn.ts +++ b/frontend/src/utils/hn.ts @@ -82,4 +82,3 @@ const itemFilter = (items: Item[], tagFilters: TagFilter[], searchFilter: string const flatFilters = (filters: Map): TagFilter[] => Array.from(filters.values()).map(filterSet => Array.from(filterSet)).flat() export { filterByRegex, filterByRegexAny, flatFilters, getItemFromId, getItemsFromIds, getItemsFromQueryId, getItemsFromQueryIds, getKidItemsFromIds, itemFilter, replaceTagCaptureGroup }; -