diff --git a/src/components/avatar-editor/AvatarEditorModelView.tsx b/src/components/avatar-editor/AvatarEditorModelView.tsx index 612773d..1656996 100644 --- a/src/components/avatar-editor/AvatarEditorModelView.tsx +++ b/src/components/avatar-editor/AvatarEditorModelView.tsx @@ -89,13 +89,13 @@ export const AvatarEditorModelView: FC<{
{ advancedColorMode ? - : } + : }
} { (maxPaletteCount === 2) &&
{ advancedColorMode ? - : } + : }
} diff --git a/src/components/avatar-editor/AvatarEditorView.tsx b/src/components/avatar-editor/AvatarEditorView.tsx index 2270a7b..e3197b5 100644 --- a/src/components/avatar-editor/AvatarEditorView.tsx +++ b/src/components/avatar-editor/AvatarEditorView.tsx @@ -79,6 +79,7 @@ export const AvatarEditorView: FC<{}> = props => return ( setIsVisible(false) } /> diff --git a/src/components/avatar-effects/AvatarEffectsView.tsx b/src/components/avatar-effects/AvatarEffectsView.tsx index 9070495..14d4b89 100644 --- a/src/components/avatar-effects/AvatarEffectsView.tsx +++ b/src/components/avatar-effects/AvatarEffectsView.tsx @@ -1,6 +1,6 @@ import { AddLinkEventTracker, AvatarDirectionAngle, AvatarEffectActivatedComposer, GetConfiguration, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; -import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FaChevronLeft, FaChevronRight, FaSearch } from 'react-icons/fa'; import { LocalizeText, SendMessageComposer } from '../../api'; import { Button, Column, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; import { AvatarEffectPreviewView } from './AvatarEffectPreviewView'; @@ -14,6 +14,7 @@ interface EffectMapEntry } const DEFAULT_DIRECTION = 4; +const PAGE_SIZE = 50; export const AvatarEffectsView: FC<{}> = () => { @@ -22,6 +23,8 @@ export const AvatarEffectsView: FC<{}> = () => const [ loadError, setLoadError ] = useState(null); const [ selectedId, setSelectedId ] = useState(0); const [ direction, setDirection ] = useState(DEFAULT_DIRECTION); + const [ query, setQuery ] = useState(''); + const [ visibleCount, setVisibleCount ] = useState(PAGE_SIZE); useEffect(() => { @@ -107,48 +110,160 @@ export const AvatarEffectsView: FC<{}> = () => const onClose = useCallback(() => setIsVisible(false), []); + const filteredEffects = useMemo(() => + { + const trimmed = query.trim().toLowerCase(); + if(!trimmed) return effects; + return effects.filter(e => + e.id.toLowerCase().includes(trimmed) || + e.lib.toLowerCase().includes(trimmed)); + }, [ effects, query ]); + + const onQueryChange = useCallback((event: ChangeEvent) => + { + setQuery(event.target.value); + setVisibleCount(PAGE_SIZE); + }, []); + + const visibleEffects = filteredEffects.slice(0, visibleCount); + const hasMore = filteredEffects.length > visibleEffects.length; + const selectedEffect = selectedId ? effects.find(e => parseInt(e.id, 10) === selectedId) : null; + + const selectedRowRef = useRef(null); + + const jumpToSelected = useCallback(() => + { + if(!selectedId) return; + + const indexInFiltered = filteredEffects.findIndex(e => parseInt(e.id, 10) === selectedId); + const indexInAll = effects.findIndex(e => parseInt(e.id, 10) === selectedId); + + if(indexInFiltered === -1) + { + setQuery(''); + if(indexInAll >= 0 && indexInAll >= visibleCount) + { + setVisibleCount(Math.ceil((indexInAll + 1) / PAGE_SIZE) * PAGE_SIZE); + } + } + else if(indexInFiltered >= visibleCount) + { + setVisibleCount(Math.ceil((indexInFiltered + 1) / PAGE_SIZE) * PAGE_SIZE); + } + + requestAnimationFrame(() => + { + selectedRowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); + }, [ selectedId, filteredEffects, effects, visibleCount ]); + if(!isVisible) return null; return ( - + -
+
-
- - +
+ +
+ { selectedEffect && +
+
#{ parseInt(selectedEffect.id, 10) }
+
{ selectedEffect.lib }
+
+ }
- - { loadError &&
{ loadError }
} - { !loadError && !effects.length &&
{ LocalizeText('generic.loading') || 'Loading…' }
} - { !!effects.length && -
- { effects.map(effect => - { - const id = parseInt(effect.id, 10); - const isSelected = (id === selectedId); - return ( - - ); - }) } -
- } + +
+ + +
+
+ { filteredEffects.length === effects.length ? `${ effects.length } effects` : `${ filteredEffects.length } of ${ effects.length }` } + { selectedId > 0 && + } +
+
+ { loadError &&
{ loadError }
} + { !loadError && !effects.length &&
{ LocalizeText('generic.loading') || 'Loading…' }
} + { !!effects.length && !filteredEffects.length && +
{ LocalizeText('generic.search.noresults') || 'No effects match your search.' }
+ } + { !!visibleEffects.length && +
    + { visibleEffects.map((effect, index) => + { + const id = parseInt(effect.id, 10); + const isSelected = (id === selectedId); + return ( +
  • + +
  • + ); + }) } + { hasMore && +
  • + +
  • + } +
+ } +