feat(category): user hierarchy, fix filter
This commit is contained in:
parent
535aabe4a3
commit
f7f8778707
@ -3,7 +3,10 @@
|
|||||||
"name": "audio",
|
"name": "audio",
|
||||||
"sub": [
|
"sub": [
|
||||||
{ "name": "music", "sub": ["background", "ambient"] },
|
{ "name": "music", "sub": ["background", "ambient"] },
|
||||||
{ "name": "sound effects", "sub": ["footsteps", "weapons"] },
|
{
|
||||||
|
"name": "sound effects",
|
||||||
|
"sub": ["footsteps", "weapons"]
|
||||||
|
},
|
||||||
"voice"
|
"voice"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Category } from 'types'
|
import { Category } from 'types'
|
||||||
import categoriesData from './../../assets/categories/categories.json'
|
import {
|
||||||
import { capitalizeEachWord } from 'utils'
|
addToUserCategories,
|
||||||
|
capitalizeEachWord,
|
||||||
|
deleteFromUserCategories
|
||||||
|
} from 'utils'
|
||||||
|
import { useLocalStorage } from 'hooks'
|
||||||
import styles from './CategoryFilterPopup.module.scss'
|
import styles from './CategoryFilterPopup.module.scss'
|
||||||
|
import categoriesData from './../../assets/categories/categories.json'
|
||||||
|
|
||||||
interface CategoryFilterPopupProps {
|
interface CategoryFilterPopupProps {
|
||||||
categories: string[]
|
categories: string[]
|
||||||
@ -21,6 +25,9 @@ export const CategoryFilterPopup = ({
|
|||||||
setHierarchies,
|
setHierarchies,
|
||||||
handleClose
|
handleClose
|
||||||
}: CategoryFilterPopupProps) => {
|
}: CategoryFilterPopupProps) => {
|
||||||
|
const [userHierarchies, setUserHierarchies] = useLocalStorage<
|
||||||
|
(string | Category)[]
|
||||||
|
>('user-hierarchies', [])
|
||||||
const [filterCategories, setFilterCategories] = useState(categories)
|
const [filterCategories, setFilterCategories] = useState(categories)
|
||||||
const [filterHierarchies, setFilterHierarchies] = useState(hierarchies)
|
const [filterHierarchies, setFilterHierarchies] = useState(hierarchies)
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
@ -54,15 +61,28 @@ export const CategoryFilterPopup = ({
|
|||||||
}
|
}
|
||||||
const handleAddNew = () => {
|
const handleAddNew = () => {
|
||||||
if (inputValue) {
|
if (inputValue) {
|
||||||
const values = inputValue
|
const value = inputValue.toLowerCase()
|
||||||
|
const values = value
|
||||||
.trim()
|
.trim()
|
||||||
.split('>')
|
.split('>')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
if (values.length > 1) {
|
|
||||||
setFilterHierarchies([...filterHierarchies, values.join(':')])
|
setUserHierarchies((prev) => {
|
||||||
} else {
|
addToUserCategories(prev, value)
|
||||||
setFilterCategories([...filterCategories, values[0]])
|
return [...prev]
|
||||||
}
|
})
|
||||||
|
|
||||||
|
const path = values.join(':')
|
||||||
|
|
||||||
|
// Add new hierarchy to current selection and active selection
|
||||||
|
setFilterHierarchies((prev) => {
|
||||||
|
prev.push(path)
|
||||||
|
return [...prev]
|
||||||
|
})
|
||||||
|
setHierarchies((prev) => {
|
||||||
|
prev.push(path)
|
||||||
|
return [...prev]
|
||||||
|
})
|
||||||
setInputValue('')
|
setInputValue('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,23 +124,67 @@ export const CategoryFilterPopup = ({
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
className='inputMain inputMainWithBtn dropdown-toggle'
|
className='inputMain inputMainWithBtn'
|
||||||
placeholder='Select some categories...'
|
placeholder='Select some categories...'
|
||||||
aria-expanded='false'
|
|
||||||
data-bs-toggle='dropdown'
|
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
{true && (
|
{userHierarchies.length > 0 && (
|
||||||
<div className='inputLabelWrapperMain'>
|
<>
|
||||||
<label
|
<div className='inputLabelWrapperMain'>
|
||||||
className='form-label labelMain'
|
<label
|
||||||
style={{ fontWeight: 'bold' }}
|
className='form-label labelMain'
|
||||||
|
style={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Custom categories
|
||||||
|
</label>
|
||||||
|
<p className='labelDescriptionMain'>Maybe</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='inputMain'
|
||||||
|
style={{
|
||||||
|
minHeight: '40px',
|
||||||
|
maxHeight: '500px',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Custom categories
|
{userHierarchies
|
||||||
</label>
|
.filter((c) => typeof c !== 'string')
|
||||||
<p className='labelDescriptionMain'>Maybe</p>
|
.map((c, i) => (
|
||||||
</div>
|
<CategoryCheckbox
|
||||||
|
key={`${c}_${i}`}
|
||||||
|
inputValue={inputValue}
|
||||||
|
category={c}
|
||||||
|
path={[c.name]}
|
||||||
|
handleSingleSelection={handleSingleSelection}
|
||||||
|
handleCombinationSelection={
|
||||||
|
handleCombinationSelection
|
||||||
|
}
|
||||||
|
selectedSingles={filterCategories}
|
||||||
|
selectedCombinations={filterHierarchies}
|
||||||
|
handleRemove={(path) => {
|
||||||
|
setUserHierarchies((prev) => {
|
||||||
|
deleteFromUserCategories(prev, path.join('>'))
|
||||||
|
return [...prev]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove the deleted hierarchies from current filter selection and active selection
|
||||||
|
setFilterHierarchies((prev) =>
|
||||||
|
prev.filter(
|
||||||
|
(h) => !h.startsWith(path.join(':'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setHierarchies((prev) =>
|
||||||
|
prev.filter(
|
||||||
|
(h) => !h.startsWith(path.join(':'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<div className='inputLabelWrapperMain'>
|
<div className='inputLabelWrapperMain'>
|
||||||
<div
|
<div
|
||||||
@ -139,8 +203,12 @@ export const CategoryFilterPopup = ({
|
|||||||
className='dropdown-item dropdownMainMenuItem'
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
onClick={handleAddNew}
|
onClick={handleAddNew}
|
||||||
>
|
>
|
||||||
Search for "{inputValue}" category
|
Add and search for "{inputValue}" category
|
||||||
<button className='btn btnMain btnMainInsideField btnMainRemove'>
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btnMain btnMainInsideField btnMainAdd'
|
||||||
|
title='Add'
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='-32 0 512 512'
|
viewBox='-32 0 512 512'
|
||||||
@ -153,10 +221,10 @@ export const CategoryFilterPopup = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(categoriesData as Category[]).map((category) => (
|
{(categoriesData as Category[]).map((category, i) => (
|
||||||
<CategoryCheckbox
|
<CategoryCheckbox
|
||||||
|
key={`${category.name}_${i}`}
|
||||||
inputValue={inputValue}
|
inputValue={inputValue}
|
||||||
key={category.name}
|
|
||||||
category={category}
|
category={category}
|
||||||
path={[category.name]}
|
path={[category.name]}
|
||||||
handleSingleSelection={handleSingleSelection}
|
handleSingleSelection={handleSingleSelection}
|
||||||
@ -221,6 +289,7 @@ interface CategoryCheckboxProps {
|
|||||||
selectedSingles: string[]
|
selectedSingles: string[]
|
||||||
selectedCombinations: string[]
|
selectedCombinations: string[]
|
||||||
indentLevel?: number
|
indentLevel?: number
|
||||||
|
handleRemove?: (path: string[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
||||||
@ -231,7 +300,8 @@ const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
|||||||
handleCombinationSelection,
|
handleCombinationSelection,
|
||||||
selectedSingles,
|
selectedSingles,
|
||||||
selectedCombinations,
|
selectedCombinations,
|
||||||
indentLevel = 0
|
indentLevel = 0,
|
||||||
|
handleRemove
|
||||||
}) => {
|
}) => {
|
||||||
const name = typeof category === 'string' ? category : category.name
|
const name = typeof category === 'string' ? category : category.name
|
||||||
const isMatching = path
|
const isMatching = path
|
||||||
@ -330,6 +400,24 @@ const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
|||||||
checked={isSingleChecked}
|
checked={isSingleChecked}
|
||||||
onChange={handleSingleChange}
|
onChange={handleSingleChange}
|
||||||
/>
|
/>
|
||||||
|
{typeof handleRemove === 'function' && (
|
||||||
|
<button
|
||||||
|
className='btn btnMain btnMainRemove'
|
||||||
|
title='Remove'
|
||||||
|
type='button'
|
||||||
|
onClick={() => handleRemove(path)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 512 512'
|
||||||
|
width='1em'
|
||||||
|
height='1em'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path d='M480 416C497.7 416 512 430.3 512 448C512 465.7 497.7 480 480 480H150.6C133.7 480 117.4 473.3 105.4 461.3L25.37 381.3C.3786 356.3 .3786 315.7 25.37 290.7L258.7 57.37C283.7 32.38 324.3 32.38 349.3 57.37L486.6 194.7C511.6 219.7 511.6 260.3 486.6 285.3L355.9 416H480zM265.4 416L332.7 348.7L195.3 211.3L70.63 336L150.6 416L265.4 416z'></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -350,6 +438,11 @@ const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
|||||||
selectedSingles={selectedSingles}
|
selectedSingles={selectedSingles}
|
||||||
selectedCombinations={selectedCombinations}
|
selectedCombinations={selectedCombinations}
|
||||||
indentLevel={indentLevel + 1}
|
indentLevel={indentLevel + 1}
|
||||||
|
{...(typeof handleRemove === 'function'
|
||||||
|
? {
|
||||||
|
handleRemove
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -364,6 +457,11 @@ const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
|
|||||||
selectedSingles={selectedSingles}
|
selectedSingles={selectedSingles}
|
||||||
selectedCombinations={selectedCombinations}
|
selectedCombinations={selectedCombinations}
|
||||||
indentLevel={indentLevel + 1}
|
indentLevel={indentLevel + 1}
|
||||||
|
{...(typeof handleRemove === 'function'
|
||||||
|
? {
|
||||||
|
handleRemove
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||||
import React, {
|
import React, {
|
||||||
CSSProperties,
|
|
||||||
Fragment,
|
Fragment,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -11,7 +10,7 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { VariableSizeList, FixedSizeList } from 'react-window'
|
import { FixedSizeList } from 'react-window'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { T_TAG_VALUE } from '../constants'
|
import { T_TAG_VALUE } from '../constants'
|
||||||
import { useAppSelector, useGames, useNDKContext } from '../hooks'
|
import { useAppSelector, useGames, useNDKContext } from '../hooks'
|
||||||
@ -73,9 +72,10 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.pathname === appRoutes.submitMod) {
|
if (location.pathname === appRoutes.submitMod) {
|
||||||
|
// Only trigger when the pathname changes to submit-mod
|
||||||
setFormState(initializeFormState())
|
setFormState(initializeFormState())
|
||||||
}
|
}
|
||||||
}, [location.pathname]) // Only trigger when the pathname changes to submit-mod
|
}, [location.pathname])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (existingModData) {
|
if (existingModData) {
|
||||||
@ -974,7 +974,7 @@ export const CategoryAutocomplete = ({
|
|||||||
}
|
}
|
||||||
const handleAddNew = () => {
|
const handleAddNew = () => {
|
||||||
if (inputValue) {
|
if (inputValue) {
|
||||||
const value = inputValue.trim()
|
const value = inputValue.trim().toLowerCase()
|
||||||
const newOption: Categories = {
|
const newOption: Categories = {
|
||||||
name: value,
|
name: value,
|
||||||
hierarchy: value,
|
hierarchy: value,
|
||||||
@ -993,44 +993,11 @@ export const CategoryAutocomplete = ({
|
|||||||
}))
|
}))
|
||||||
}, [selectedCategories, setFormState])
|
}, [selectedCategories, setFormState])
|
||||||
|
|
||||||
const listRef = useRef<VariableSizeList>(null)
|
const Row = ({ index }: { index: number }) => {
|
||||||
const rowHeights = useRef<{ [index: number]: number }>({})
|
|
||||||
const setRowHeight = (index: number, size: number) => {
|
|
||||||
rowHeights.current = { ...rowHeights.current, [index]: size }
|
|
||||||
if (listRef.current) {
|
|
||||||
listRef.current.resetAfterIndex(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const getRowHeight = (index: number) => {
|
|
||||||
return (rowHeights.current[index] || 35) + 8
|
|
||||||
}
|
|
||||||
|
|
||||||
const Row = ({ index, style }: { index: number; style: CSSProperties }) => {
|
|
||||||
const rowRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const rowElement = rowRef.current
|
|
||||||
if (!rowElement) return
|
|
||||||
const updateHeight = () => {
|
|
||||||
const height = Math.max(rowElement.scrollHeight, 35)
|
|
||||||
setRowHeight(index, height)
|
|
||||||
}
|
|
||||||
const observer = new ResizeObserver(() => {
|
|
||||||
updateHeight()
|
|
||||||
})
|
|
||||||
observer.observe(rowElement)
|
|
||||||
updateHeight()
|
|
||||||
return () => {
|
|
||||||
observer.disconnect()
|
|
||||||
}
|
|
||||||
}, [index])
|
|
||||||
|
|
||||||
if (!filteredOptions) return null
|
if (!filteredOptions) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rowRef}
|
|
||||||
style={style}
|
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
onClick={() => handleSelect(filteredOptions[index])}
|
onClick={() => handleSelect(filteredOptions[index])}
|
||||||
>
|
>
|
||||||
@ -1039,6 +1006,7 @@ export const CategoryAutocomplete = ({
|
|||||||
(cat) => cat.hierarchy === filteredOptions[index].hierarchy
|
(cat) => cat.hierarchy === filteredOptions[index].hierarchy
|
||||||
) && (
|
) && (
|
||||||
<button
|
<button
|
||||||
|
type='button'
|
||||||
className='btn btnMain btnMainInsideField btnMainRemove'
|
className='btn btnMain btnMainInsideField btnMainRemove'
|
||||||
onClick={() => handleRemove(filteredOptions[index])}
|
onClick={() => handleRemove(filteredOptions[index])}
|
||||||
>
|
>
|
||||||
@ -1074,6 +1042,7 @@ export const CategoryAutocomplete = ({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className='btn btnMain btnMainInsideField btnMainRemove'
|
className='btn btnMain btnMainInsideField btnMainRemove'
|
||||||
|
title='Remove'
|
||||||
type='button'
|
type='button'
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
>
|
>
|
||||||
@ -1088,17 +1057,14 @@ export const CategoryAutocomplete = ({
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className='dropdown-menu dropdownMainMenu dropdownMainMenuAlt'>
|
<div
|
||||||
|
className='dropdown-menu dropdownMainMenu dropdownMainMenuAlt category'
|
||||||
|
style={{
|
||||||
|
maxHeight: '500px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{filteredOptions && filteredOptions.length > 0 ? (
|
{filteredOptions && filteredOptions.length > 0 ? (
|
||||||
<VariableSizeList
|
filteredOptions.map((c, i) => <Row key={c.hierarchy} index={i} />)
|
||||||
ref={listRef}
|
|
||||||
height={500}
|
|
||||||
width={'100%'}
|
|
||||||
itemCount={filteredOptions.length}
|
|
||||||
itemSize={getRowHeight}
|
|
||||||
>
|
|
||||||
{Row}
|
|
||||||
</VariableSizeList>
|
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className='dropdown-item dropdownMainMenuItem'
|
className='dropdown-item dropdownMainMenuItem'
|
||||||
@ -1111,7 +1077,11 @@ export const CategoryAutocomplete = ({
|
|||||||
) ? (
|
) ? (
|
||||||
<>
|
<>
|
||||||
Add "{inputValue}"
|
Add "{inputValue}"
|
||||||
<button className='btn btnMain btnMainInsideField btnMainRemove'>
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btnMain btnMainInsideField btnMainAdd'
|
||||||
|
title='Add'
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='-32 0 512 512'
|
viewBox='-32 0 512 512'
|
||||||
|
@ -91,8 +91,38 @@ export const GamePage = () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// If search term is missing, only filter by sources
|
const filterCategoryFn = (mod: ModDetails) => {
|
||||||
if (searchTerm === '') return mods.filter(filterSourceFn)
|
// Linked overrides the category popup selection
|
||||||
|
if (linkedHierarchy && linkedHierarchy !== '') {
|
||||||
|
return mod.LTags.includes(linkedHierarchy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no selections are active return true
|
||||||
|
if (!(hierarchies.length || categories.length)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hierarchy selection active
|
||||||
|
if (hierarchies.length) {
|
||||||
|
const isMatch = mod.LTags.some((item) => hierarchies.includes(item))
|
||||||
|
|
||||||
|
// Matched hierarchy, return true immediately otherwise check categories
|
||||||
|
if (isMatch) return isMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category selection
|
||||||
|
if (categories.length) {
|
||||||
|
// Return result immediately
|
||||||
|
return mod.lTags.some((item) => categories.includes(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matches
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If search term is missing, only filter by sources and category
|
||||||
|
if (searchTerm === '')
|
||||||
|
return mods.filter(filterSourceFn).filter(filterCategoryFn)
|
||||||
|
|
||||||
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
||||||
|
|
||||||
@ -105,8 +135,15 @@ export const GamePage = () => {
|
|||||||
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
||||||
) > -1
|
) > -1
|
||||||
|
|
||||||
return mods.filter(filterFn).filter(filterSourceFn)
|
return mods.filter(filterFn).filter(filterSourceFn).filter(filterCategoryFn)
|
||||||
}, [filterOptions.source, mods, searchTerm])
|
}, [
|
||||||
|
categories,
|
||||||
|
filterOptions.source,
|
||||||
|
hierarchies,
|
||||||
|
linkedHierarchy,
|
||||||
|
mods,
|
||||||
|
searchTerm
|
||||||
|
])
|
||||||
|
|
||||||
const filteredModList = useFilteredMods(
|
const filteredModList = useFilteredMods(
|
||||||
filteredMods,
|
filteredMods,
|
||||||
@ -132,24 +169,13 @@ export const GamePage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!gameName) return
|
||||||
|
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
kinds: [NDKKind.Classified],
|
kinds: [NDKKind.Classified],
|
||||||
'#t': [T_TAG_VALUE]
|
'#t': [T_TAG_VALUE]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Linked category will override the filter
|
|
||||||
if (linkedHierarchy && linkedHierarchy !== '') {
|
|
||||||
filter['#L'] = [`com.degmods:${linkedHierarchy}`]
|
|
||||||
} else {
|
|
||||||
if (categories.length) {
|
|
||||||
filter['#l'] = categories.map((l) => `com.degmods:${l}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hierarchies.length) {
|
|
||||||
filter['#L'] = hierarchies.map((L) => `com.degmods:${L}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscription = ndk.subscribe(filter, {
|
const subscription = ndk.subscribe(filter, {
|
||||||
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
||||||
closeOnEose: true
|
closeOnEose: true
|
||||||
@ -173,7 +199,7 @@ export const GamePage = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
subscription.stop()
|
subscription.stop()
|
||||||
}
|
}
|
||||||
}, [gameName, ndk, linkedHierarchy, categories, hierarchies])
|
}, [gameName, ndk])
|
||||||
|
|
||||||
if (!gameName) return null
|
if (!gameName) return null
|
||||||
|
|
||||||
|
@ -271,16 +271,16 @@ h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* the 4 classes below here are a temp fix for the games dropdown stylings */
|
/* the 4 classes below here are a temp fix for the games dropdown stylings */
|
||||||
|
/* add an exception (not category) for normal dropdown - due !important */
|
||||||
.dropdownMainMenu.dropdownMainMenuAlt {
|
.dropdownMainMenu.dropdownMainMenuAlt:not(.category) {
|
||||||
max-height: unset !important;
|
max-height: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownMainMenu.dropdownMainMenuAlt > div {
|
.dropdownMainMenu.dropdownMainMenuAlt:not(.category) > div {
|
||||||
height: unset !important;
|
height: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownMainMenu.dropdownMainMenuAlt > div > div {
|
.dropdownMainMenu.dropdownMainMenuAlt:not(.category) > div > div {
|
||||||
height: unset !important;
|
height: unset !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -291,7 +291,7 @@ h6 {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownMainMenu.dropdownMainMenuAlt > div > div > div {
|
.dropdownMainMenu.dropdownMainMenuAlt:not(.category) > div > div > div {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
left: unset !important;
|
left: unset !important;
|
||||||
top: unset !important;
|
top: unset !important;
|
||||||
|
@ -25,3 +25,70 @@ const flattenCategories = (
|
|||||||
export const getCategories = () => {
|
export const getCategories = () => {
|
||||||
return flattenCategories(categoriesData)
|
return flattenCategories(categoriesData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const buildCategories = (input: string[]) => {
|
||||||
|
const categories: (string | Category)[] = []
|
||||||
|
|
||||||
|
input.forEach((cat) => {
|
||||||
|
addToUserCategories(categories, cat)
|
||||||
|
})
|
||||||
|
|
||||||
|
return categories
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addToUserCategories = (
|
||||||
|
categories: (string | Category)[],
|
||||||
|
input: string
|
||||||
|
) => {
|
||||||
|
const segments = input.split('>').map((s) => s.trim())
|
||||||
|
let currentLevel: (string | Category)[] = categories
|
||||||
|
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
const segment = segments[i].trim()
|
||||||
|
const existingNode = currentLevel.find(
|
||||||
|
(item) => typeof item !== 'string' && item.name === segment
|
||||||
|
)
|
||||||
|
if (!existingNode) {
|
||||||
|
const newCategory: Category = { name: segment, sub: [] }
|
||||||
|
currentLevel.push(newCategory)
|
||||||
|
if (newCategory.sub) {
|
||||||
|
currentLevel = newCategory.sub
|
||||||
|
}
|
||||||
|
} else if (typeof existingNode !== 'string') {
|
||||||
|
if (!existingNode.sub) {
|
||||||
|
existingNode.sub = []
|
||||||
|
}
|
||||||
|
currentLevel = existingNode.sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteFromUserCategories = (
|
||||||
|
categories: (string | Category)[],
|
||||||
|
input: string
|
||||||
|
) => {
|
||||||
|
const segments = input.split('>').map((s) => s.trim())
|
||||||
|
const value = segments.pop()
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentLevel: (string | Category)[] = categories
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
const key = segments[i]
|
||||||
|
const existingNode = currentLevel.find(
|
||||||
|
(item) => typeof item === 'object' && item.name === key
|
||||||
|
) as Category
|
||||||
|
|
||||||
|
if (existingNode && existingNode.sub) {
|
||||||
|
currentLevel = existingNode.sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const valueIndex = currentLevel.findIndex(
|
||||||
|
(item) =>
|
||||||
|
item === value || (typeof item === 'object' && item.name === value)
|
||||||
|
)
|
||||||
|
if (valueIndex !== -1) {
|
||||||
|
currentLevel.splice(valueIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user