From 3aa6e34ae00d575a7913496af83e9d4974d5de51 Mon Sep 17 00:00:00 2001 From: dudubtw Date: Sat, 12 Oct 2024 13:23:01 -0300 Subject: [PATCH 1/7] - Add search tags component and routes that return all the tags --- src/RequestAPI.d.ts | 1 + src/main/lib/osu-file-parser/OsuParser.ts | 2 +- src/main/router/import.ts | 1 + src/main/router/tags-router.ts | 31 +++++++++++++ .../src/components/dropdown/Dropdown.tsx | 3 +- .../{DropdownList.tsx => DropdownContent.tsx} | 15 +++++-- .../src/components/dropdown/styles.css | 4 +- .../song/song-list-search/SongListSearch.tsx | 2 + .../song-list-search/SongListSearchTags.tsx | 45 +++++++++++++++++++ .../song/song-list-search/styles.css | 21 +++++++++ 10 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/main/router/tags-router.ts rename src/renderer/src/components/dropdown/{DropdownList.tsx => DropdownContent.tsx} (50%) create mode 100644 src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx diff --git a/src/RequestAPI.d.ts b/src/RequestAPI.d.ts index 51798a88..f83c8b6f 100644 --- a/src/RequestAPI.d.ts +++ b/src/RequestAPI.d.ts @@ -66,6 +66,7 @@ export type RequestAPI = { ) => InfiniteScrollerResponse; "query::queue::init": () => InfiniteScrollerInitResponse; "query::queue": (request: InfiniteScrollerRequest) => InfiniteScrollerResponse; + "query::tags::search": () => string[]; "save::localVolume": (volume: number, song: ResourceID) => void; diff --git a/src/main/lib/osu-file-parser/OsuParser.ts b/src/main/lib/osu-file-parser/OsuParser.ts index 2fe669a0..3deb2f98 100644 --- a/src/main/lib/osu-file-parser/OsuParser.ts +++ b/src/main/lib/osu-file-parser/OsuParser.ts @@ -281,7 +281,7 @@ export class OsuParser { song.mode = db.readByte(); db.readString(); // song source - song.tags = db.readString(); + song.tags = db.readString().split(" "); db.readShort(); // online offset db.readString(); // song title font diff --git a/src/main/router/import.ts b/src/main/router/import.ts index 3adf755a..03dfd26e 100644 --- a/src/main/router/import.ts +++ b/src/main/router/import.ts @@ -10,4 +10,5 @@ import "./queue-router"; import "./resource-router"; import "./settings-router"; import "./songs-pool-router"; +import "./tags-router"; import "./window-router"; diff --git a/src/main/router/tags-router.ts b/src/main/router/tags-router.ts new file mode 100644 index 00000000..48fbcdc2 --- /dev/null +++ b/src/main/router/tags-router.ts @@ -0,0 +1,31 @@ +import { Router } from "../lib/route-pass/Router"; +import { Storage } from "../lib/storage/Storage"; + +Router.respond("query::tags::search", () => { + const allTags = Storage.getTable("system").get("allTags"); + if (allTags.isNone) { + return []; + } + + const tagsByUse: string[][] = []; + Object.entries(allTags.value).forEach(([tagName, songs]) => { + if (!tagName) { + return; + } + + const size = songs.length; + tagsByUse[size] = (tagsByUse[size] ?? []).concat(tagName); + }); + + let result: string[] = []; + for (let i = tagsByUse.length - 1; i > 0; i--) { + const tagGroup = tagsByUse[i]; + if (!tagGroup) { + continue; + } + + result = result.concat(tagGroup); + } + + return result; +}); diff --git a/src/renderer/src/components/dropdown/Dropdown.tsx b/src/renderer/src/components/dropdown/Dropdown.tsx index 68ad3bb7..15f85125 100644 --- a/src/renderer/src/components/dropdown/Dropdown.tsx +++ b/src/renderer/src/components/dropdown/Dropdown.tsx @@ -1,5 +1,5 @@ import Popover, { Props as PopoverProps } from "../popover/Popover"; -import DropdownList from "./DropdownList"; +import { DropdownContent, DropdownList } from "./DropdownContent"; import DropdownListItem from "./DropdownListItem"; import DropdownSelectTrigger from "./DropdownSelectTrigger"; import DropdownTrigger from "./DropdownTrigger"; @@ -12,6 +12,7 @@ const DropdownRoot: ParentComponent = (props) => { const Dropdown = Object.assign(DropdownRoot, { SelectTrigger: DropdownSelectTrigger, Trigger: DropdownTrigger, + Content: DropdownContent, List: DropdownList, Item: DropdownListItem, }); diff --git a/src/renderer/src/components/dropdown/DropdownList.tsx b/src/renderer/src/components/dropdown/DropdownContent.tsx similarity index 50% rename from src/renderer/src/components/dropdown/DropdownList.tsx rename to src/renderer/src/components/dropdown/DropdownContent.tsx index c7e7c83a..45015db3 100644 --- a/src/renderer/src/components/dropdown/DropdownList.tsx +++ b/src/renderer/src/components/dropdown/DropdownContent.tsx @@ -4,15 +4,22 @@ import Popover from "../popover/Popover"; import { ParentComponent } from "solid-js"; import { Portal } from "solid-js/web"; -const DropdownList: ParentComponent = (props) => { +export const DropdownContent: ParentComponent = (props) => { return ( - + {props.children} + + ); +}; + +export const DropdownList: ParentComponent = (props) => { + return ( + + + ); }; - -export default DropdownList; diff --git a/src/renderer/src/components/dropdown/styles.css b/src/renderer/src/components/dropdown/styles.css index 6860b866..8c4eba7c 100644 --- a/src/renderer/src/components/dropdown/styles.css +++ b/src/renderer/src/components/dropdown/styles.css @@ -1,4 +1,4 @@ -.dropdown-list { +.dropdown-content { display: flex; flex-direction: column; background-color: var(--background); @@ -6,6 +6,8 @@ width: 400px; border-radius: var(--rounded); border: 1px solid var(--separator); + max-height: 300px; + overflow-y: auto; } .dropdown-list-item { diff --git a/src/renderer/src/components/song/song-list-search/SongListSearch.tsx b/src/renderer/src/components/song/song-list-search/SongListSearch.tsx index 38f3850e..a564924a 100644 --- a/src/renderer/src/components/song/song-list-search/SongListSearch.tsx +++ b/src/renderer/src/components/song/song-list-search/SongListSearch.tsx @@ -3,6 +3,7 @@ import { SearchQueryError } from "../../../../../main/lib/search-parser/@search- import { Tag } from "../../search/TagSelect"; import { setSongsSearch } from "../song-list/song-list.utils"; import SongListSearchOrderBy from "./SongListSearchOrderBy"; +import SongListSearchTags from "./SongListSearchTags"; import "./styles.css"; import { Accessor, Component, Setter, Signal } from "solid-js"; @@ -81,6 +82,7 @@ const SongListSearch: Component = (props) => {
+
{/*
diff --git a/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx new file mode 100644 index 00000000..d44c4b41 --- /dev/null +++ b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx @@ -0,0 +1,45 @@ +import Dropdown from "@renderer/components/dropdown/Dropdown"; +import { createSignal, For } from "solid-js"; + +const SongListSearchTags = () => { + const [hasFetchedTags, setHasFetchedTags] = createSignal(false); + const [isPopopOpen, setIsPopopOpen] = createSignal(false); + const [tags, setTags] = createSignal([]); + + const fetchTags = async () => { + setHasFetchedTags(true); + const remoteTags = await window.api.request("query::tags::search"); + setTags(remoteTags); + }; + + const handlePopoverChange = (newValue: boolean) => { + setIsPopopOpen(newValue); + + if (!newValue || hasFetchedTags()) { + return; + } + + fetchTags(); + }; + + return ( + + Tags + +
+

+ ⓘ Click on any tag once to include it. Click on it again to exclude it. Click once more + to clear it +

+
+ + {(tag) => } + +
+
+
+
+ ); +}; + +export default SongListSearchTags; diff --git a/src/renderer/src/components/song/song-list-search/styles.css b/src/renderer/src/components/song/song-list-search/styles.css index cf434397..a162bd0f 100644 --- a/src/renderer/src/components/song/song-list-search/styles.css +++ b/src/renderer/src/components/song/song-list-search/styles.css @@ -44,3 +44,24 @@ font-size: 20px; } } + +.song-list-search-tags { + .song-list-search-tags__hint { + font-size: 12px; + } + + .song-list-search-tags__content { + padding-top: 8px; + display: flex; + flex-wrap: wrap; + row-gap: 6px; + column-gap: 8px; + } + + .song-list-search-tags__tag { + font-size: 14px; + background-color: #101013; + padding: 2px 8px; + border-radius: 6px; + } +} From cf3cab18562767f5f45a5a9dd4ab45207d4f5643 Mon Sep 17 00:00:00 2001 From: dudubtw Date: Sat, 12 Oct 2024 13:28:11 -0300 Subject: [PATCH 2/7] Dismiss tags search hint --- .../song/song-list-search/SongListSearchTags.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx index d44c4b41..2868381c 100644 --- a/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx +++ b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx @@ -1,10 +1,11 @@ import Dropdown from "@renderer/components/dropdown/Dropdown"; -import { createSignal, For } from "solid-js"; +import { createSignal, For, Show } from "solid-js"; const SongListSearchTags = () => { const [hasFetchedTags, setHasFetchedTags] = createSignal(false); const [isPopopOpen, setIsPopopOpen] = createSignal(false); const [tags, setTags] = createSignal([]); + const [showHint, setShowHint] = createSignal(true); const fetchTags = async () => { setHasFetchedTags(true); @@ -27,10 +28,15 @@ const SongListSearchTags = () => { Tags
-

- ⓘ Click on any tag once to include it. Click on it again to exclude it. Click once more - to clear it -

+ +
+ + ⓘ Click on any tag once to include it. Click on it again to exclude it. Click once + more to clear it + {" "} + +
+
{(tag) => } From 6aaa1c818b95477d970d912235f1e6c56887b4d2 Mon Sep 17 00:00:00 2001 From: dudubtw Date: Sat, 12 Oct 2024 14:26:59 -0300 Subject: [PATCH 3/7] - Add search tags state --- .../song-list-search/SongListSearchTags.tsx | 30 ++++++++++++++++++- .../song/song-list-search/styles.css | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx index 2868381c..7fe072f7 100644 --- a/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx +++ b/src/renderer/src/components/song/song-list-search/SongListSearchTags.tsx @@ -1,11 +1,14 @@ import Dropdown from "@renderer/components/dropdown/Dropdown"; import { createSignal, For, Show } from "solid-js"; +type TagMode = "include" | "discart"; + const SongListSearchTags = () => { const [hasFetchedTags, setHasFetchedTags] = createSignal(false); const [isPopopOpen, setIsPopopOpen] = createSignal(false); const [tags, setTags] = createSignal([]); const [showHint, setShowHint] = createSignal(true); + const [selectedTags, setSelectedTags] = createSignal(new Map()); const fetchTags = async () => { setHasFetchedTags(true); @@ -23,6 +26,23 @@ const SongListSearchTags = () => { fetchTags(); }; + const handleTagClick = (tag: string) => { + setSelectedTags((oldSelectedTags) => { + const newSelectedTags = new Map(oldSelectedTags); + const tagMode = newSelectedTags.get(tag); + + if (typeof tagMode === "undefined") { + newSelectedTags.set(tag, "include"); + } else if (tagMode === "include") { + newSelectedTags.set(tag, "discart"); + } else if (tagMode === "discart") { + newSelectedTags.delete(tag); + } + + return newSelectedTags; + }); + }; + return ( Tags @@ -39,7 +59,15 @@ const SongListSearchTags = () => {
- {(tag) => } + {(tag) => ( + + )}
diff --git a/src/renderer/src/components/song/song-list-search/styles.css b/src/renderer/src/components/song/song-list-search/styles.css index a162bd0f..abe8015d 100644 --- a/src/renderer/src/components/song/song-list-search/styles.css +++ b/src/renderer/src/components/song/song-list-search/styles.css @@ -63,5 +63,6 @@ background-color: #101013; padding: 2px 8px; border-radius: 6px; + user-select: none; } } From e3649f7d29dcf1d6571df09d43ef20c51dbbb354 Mon Sep 17 00:00:00 2001 From: dudubtw Date: Sun, 13 Oct 2024 15:47:42 -0300 Subject: [PATCH 4/7] - Connect tag popup to the search --- .../song/song-list-search/SongListSearch.tsx | 23 ++++- .../song-list-search/SongListSearchTags.tsx | 96 +++++++++++++++++-- .../song/song-list-search/styles.css | 35 ++++++- .../components/song/song-list/SongList.tsx | 5 +- 4 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/components/song/song-list-search/SongListSearch.tsx b/src/renderer/src/components/song/song-list-search/SongListSearch.tsx index a564924a..47e7301a 100644 --- a/src/renderer/src/components/song/song-list-search/SongListSearch.tsx +++ b/src/renderer/src/components/song/song-list-search/SongListSearch.tsx @@ -3,18 +3,20 @@ import { SearchQueryError } from "../../../../../main/lib/search-parser/@search- import { Tag } from "../../search/TagSelect"; import { setSongsSearch } from "../song-list/song-list.utils"; import SongListSearchOrderBy from "./SongListSearchOrderBy"; -import SongListSearchTags from "./SongListSearchTags"; +import SongListSearchTags, { TagMode } from "./SongListSearchTags"; import "./styles.css"; -import { Accessor, Component, Setter, Signal } from "solid-js"; +import { Accessor, Component, createSignal, Setter } from "solid-js"; export type SearchProps = { - tags: Signal; count: Accessor; error: Accessor>; setOrder: Setter; + setTags: Setter; }; const SongListSearch: Component = (props) => { + const [selectedTags, setSelectedTags] = createSignal(new Map()); + // const [editable, setEditable] = createSignal(); // const [doShowError, setDoShowError] = createSignal(false); // const [doShowSuggestion, setDoShowSuggestion] = createSignal(false); @@ -63,6 +65,19 @@ const SongListSearch: Component = (props) => { // } // }); + const handleValueChange = (tags: Map) => { + setSelectedTags(tags); + + const searchFormattedTags = Array.from( + tags.entries(), + ([tagName, mode]): Tag => ({ + name: tagName, + isSpecial: mode === "discart", + }), + ); + props.setTags(searchFormattedTags); + }; + return (