diff --git a/README.md b/README.md index 2303e9a..e50785e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,11 @@ Minecraftの`/playsound`コマンドを容易にするためのデスクトッ A desktop app that lets you easily create Minecraft `/playsound` commands -![SampleImage](image.png) +
+ + +
+ # Supported ### 大感謝   Thank you so so so so so much diff --git a/image-1.png b/image-1.png new file mode 100644 index 0000000..46794cb Binary files /dev/null and b/image-1.png differ diff --git a/image.png b/image.png index 7aa704c..bd21777 100644 Binary files a/image.png and b/image.png differ diff --git a/package.json b/package.json index 5b193bd..c6f88b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "knead", - "version": "0.3.2", + "version": "0.3.3", "description": "Playsound generator for Minecraft", "main": "dist/main.js", "scripts": { diff --git a/src/ipc-main-handler.ts b/src/ipc-main-handler.ts index b320e10..e5625fe 100644 --- a/src/ipc-main-handler.ts +++ b/src/ipc-main-handler.ts @@ -93,7 +93,10 @@ export const initIpcMain = (): void => { result.push(sound) } - return result + return result.sort((a, b) => { + if (a.id > b.id) return 1 + else return -1 + }) }) ipcMain.handle('get_mcSoundHash', async (event, hash: string): Promise => { diff --git a/src/web/App.tsx b/src/web/App.tsx index b25999f..6b43ad9 100644 --- a/src/web/App.tsx +++ b/src/web/App.tsx @@ -1,16 +1,23 @@ import './App.css' -import React from 'react' +import React, { useState } from 'react' import { VersionSelector } from './VersionSelector' import { SoundSelector } from './SoundSelector' -import { ThemeChange } from './ThemeChange' import { Footer } from './Footer' -// import { Configuration } from './Configuration' +import { Configuration } from './Configuration' import { VolumeChange } from './VolumeChange' -import { LanguageChange } from './LanguageChange' import { Separator, Flex, Spacer, useColorMode, Box, VStack } from '@yamada-ui/react' +import { useTranslation } from 'react-i18next' export const App = () => { const { colorMode } = useColorMode() + const { i18n } = useTranslation() + + const [lang, setLang] = useState('') + if (lang === '') { + const get_lang = localStorage.getItem('lang') ?? 'en' + setLang(get_lang) + i18n.changeLanguage(get_lang) + } const style = document.createElement('style') style.textContent += '::-webkit-scrollbar { width: 7px; height: 7px; }' @@ -28,15 +35,13 @@ export const App = () => { <> - + - - - {/* */} + diff --git a/src/web/Configuration.tsx b/src/web/Configuration.tsx index 1428594..8eb4b69 100644 --- a/src/web/Configuration.tsx +++ b/src/web/Configuration.tsx @@ -1,28 +1,75 @@ -import React from 'react' -import { SettingsIcon } from '@yamada-ui/lucide' -import { Drawer, DrawerHeader, DrawerBody, useDisclosure, IconButton } from '@yamada-ui/react' +import React, { useState } from 'react' +import { SettingsIcon, ArrowDownAZIcon, FilterIcon } from '@yamada-ui/lucide' +import { Drawer, DrawerHeader, DrawerBody, useDisclosure, IconButton, Switch, Text, HStack, Card, CardHeader, CardBody, VStack } from '@yamada-ui/react' import { ThemeChange } from './ThemeChange' +import { useTranslation } from 'react-i18next' +import { LanguageChange } from './LanguageChange' export const Configuration = () => { const { isOpen, onOpen, onClose } = useDisclosure() + const { t } = useTranslation() + + const [holdSoundsSort, setHoldSoundsSort] = useState(undefined) + if (holdSoundsSort === undefined) setHoldSoundsSort(JSON.parse(localStorage.getItem('holdSoundsSort') ?? 'false')) + const changesetHoldSoundsSort = (v: boolean) => { + setHoldSoundsSort(v) + localStorage.setItem('holdSoundsSort', v.toString()) + } + + const [holdRatingFilter, setHoldRatingFilter] = useState(undefined) + if (holdRatingFilter === undefined) setHoldRatingFilter(JSON.parse(localStorage.getItem('holdRatingFilter') ?? 'false')) + const changeHoldRatingFilter = (v: boolean) => { + setHoldRatingFilter(v) + localStorage.setItem('holdRatingFilter', v.toString()) + } + return ( <> - } /> + } /> - - 設定 + + {t('settings')} - + + + + + + + + + + + + + + + {t('sort')} + + + + changesetHoldSoundsSort(!holdSoundsSort)}>{t('hold_sounds_sort')} + + + + + + + + {t('rating_filter')} + + + + changeHoldRatingFilter(!holdRatingFilter)}>{t('hold_rating_filter')} + + + + + - {/* - - - */} ) diff --git a/src/web/Footer.tsx b/src/web/Footer.tsx index 288625c..11118a3 100644 --- a/src/web/Footer.tsx +++ b/src/web/Footer.tsx @@ -28,7 +28,7 @@ export const Footer = () => { if (!sessionStorage.getItem('appVolume')) sessionStorage.setItem('appVolume', `${volume}`) useEffect(() => { if (selectedSound && AudioController.context.isSomePlaying) AudioController.commands.setVolume(selectedSound, appVolume - 1) - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [appVolume]) // 1.20.5(24w09a)以降はを省略できるようになった @@ -233,7 +233,7 @@ export const Footer = () => { } } })() - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [soundSelectDetector]) return ( @@ -286,7 +286,7 @@ export const Footer = () => { - } onClick={toggleSlash} /> + } onClick={toggleSlash} /> @@ -298,7 +298,7 @@ export const Footer = () => { - + @@ -308,21 +308,15 @@ export const Footer = () => { - - } variant="ghost" /> - + } variant="outline" borderColor="inherit" /> - - } variant="ghost" /> - + } variant="outline" borderColor="inherit" /> - - } variant="ghost" /> - + } variant="outline" borderColor="inherit" /> @@ -332,7 +326,7 @@ export const Footer = () => { - } /> + } /> @@ -341,7 +335,7 @@ export const Footer = () => { {command} - + : } onClick={() => onCopy(command)} variant="ghost" borderLeftRadius={0} borderRightRadius={2} /> diff --git a/src/web/LanguageChange.tsx b/src/web/LanguageChange.tsx index 96c8cf2..54b56b6 100644 --- a/src/web/LanguageChange.tsx +++ b/src/web/LanguageChange.tsx @@ -1,18 +1,14 @@ import React, { useState } from 'react' -import { Menu, MenuButton, MenuList, MenuItem, IconButton, Box } from '@yamada-ui/react' +import { SegmentedControl, SegmentedControlButton, HStack, Text, CardHeader, CardBody } from '@yamada-ui/react' -import { CheckIcon, GlobeIcon } from '@yamada-ui/lucide' +import { GlobeIcon } from '@yamada-ui/lucide' import { useTranslation } from 'react-i18next' export const LanguageChange = () => { const { i18n } = useTranslation() const [lang, setLang] = useState('') - if (lang === '') { - const get_lang = localStorage.getItem('lang') ?? 'ja' - setLang(get_lang) - i18n.changeLanguage(get_lang) - } + if (lang === '') setLang(localStorage.getItem('lang') ?? 'en') const onClickLang = (lang: string) => { i18n.changeLanguage(lang) @@ -20,23 +16,19 @@ export const LanguageChange = () => { } return ( - - } variant="outline" /> - - - } - onClick={() => onClickLang('en')} - > - English - - } - onClick={() => onClickLang('ja')} - > - 日本語 - - - + <> + + + + Language / 言語 + + + + + English + 日本語 + + + ) } diff --git a/src/web/SoundSelector.tsx b/src/web/SoundSelector.tsx index 4dfaff8..1da9e5e 100644 --- a/src/web/SoundSelector.tsx +++ b/src/web/SoundSelector.tsx @@ -1,46 +1,96 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { useAddDispatch, useAppSelector } from '../store/_store' -import { Box, Flex, Input, InputGroup, InputLeftElement, Spacer, Toggle, useBoolean } from '@yamada-ui/react' -import { FilterIcon, FilterXIcon, SearchIcon } from '@yamada-ui/lucide' +import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, Menu, MenuButton, MenuGroup, MenuItem, MenuList, MenuOptionGroup, MenuOptionItem, MenuSeparator, Spacer, Toggle, useColorModeValue } from '@yamada-ui/react' +import { ArrowDownAZIcon, FilterIcon, SearchIcon, SquareCheckBigIcon, XIcon } from '@yamada-ui/lucide' import { useVirtualScroll } from '../hooks/useVirtualScroll' import { RatingStars } from './RatingStars' import { updateSelectedSound } from '../store/fetchSlice' import { useWindowSize } from '../hooks/useWindowSize' import { useTranslation } from 'react-i18next' import { VersionInfoType } from '../types/VersionInfo' +import { GoStarFill } from 'react-icons/go' export const SoundSelector = () => { const dispatch = useAddDispatch() const { t } = useTranslation() const Sounds = useAppSelector(state => state.fetch.sounds) - // const soundRatings = useAppSelector(state => state.fetch.soundRatings) const [soundRatings, setSoundRatings] = useState<({ [key: string]: number })>({}) if (!Object.keys(soundRatings).some(v => v)) setSoundRatings(JSON.parse(localStorage.getItem('soundRatings') ?? '{"_":0}')) const [targetVersion, setTargetVersion] = useState(undefined) if (targetVersion === undefined) setTargetVersion(JSON.parse(localStorage.getItem('targetVersion') ?? '{"_":0}')) const selectedSound = useAppSelector(state => state.fetch.selectedSound) + const [searchTxt, setSearchTxt] = useState('') const [txtFilters, setTxtFilters] = useState([]) - const [ratingFilter, setRatingFilter] = useState(0) - const [ratingFilterSwitch, { toggle: toggleRatingFilter }] = useBoolean(false) - - const filteredSounds = Sounds.filter(value => txtFilters.every(filter => value.id.includes(filter))).filter((value) => { - const rate = soundRatings[value.id] ?? 0 - let result = false - // (ratingFilterSwitch && ratingFilter ? true : true && ) - if (ratingFilterSwitch) { - if (rate === ratingFilter) result = true - } - else { - result = true - } - return result - }) + + const [holdSoundsSort, setHoldSoundsSort] = useState(undefined) + if (holdSoundsSort === undefined) setHoldSoundsSort(JSON.parse(localStorage.getItem('holdSoundsSort') ?? 'false')) + + // ソート情報を保存しないパターン + const [SoundsSort, setSoundsSort] = useState<{ id: string, rating: string } | undefined>(undefined) + if (SoundsSort === undefined) { + if (holdSoundsSort == true) setSoundsSort(JSON.parse(localStorage.getItem('soundsSort') ?? '{ "id": "ascending", "rating": "none" }')) + else if (holdSoundsSort == false) setSoundsSort({ id: 'ascending', rating: 'none' }) + } + + const changeSoundSorts = ({ id, rating }: { id?: string, rating?: string }) => { + if (!SoundsSort) return + const newSort = (() => { + if (id && rating) return { id, rating } + else if (id) return { ...SoundsSort, id } + else if (rating) return { ...SoundsSort, rating } + return SoundsSort + })() + setSoundsSort(newSort) + localStorage.setItem('soundsSort', JSON.stringify(newSort)) + } + + const [holdRatingFilter, setHoldRatingFilter] = useState(undefined) + if (holdRatingFilter === undefined) setHoldRatingFilter(JSON.parse(localStorage.getItem('holdRatingFilter') ?? 'false')) + + const [ratingFilter, setRatingFilter] = useState(undefined) + if (ratingFilter === undefined) { + if (holdRatingFilter == true) setRatingFilter(localStorage.getItem('ratingFilter')?.split(',').map(v => parseInt(v)).filter(v => !Number.isNaN(v)) ?? []) + else if (holdRatingFilter == false) setRatingFilter([]) + } + const clearRatingFilters = () => { + setRatingFilter([]) + localStorage.setItem('ratingFilter', '') + } + const allOnRatingFilters = () => { + setRatingFilter([0, 1, 2, 3, 4, 5]) + localStorage.setItem('ratingFilter', '0, 1, 2, 3, 4, 5') + } + const changeRatingFilters = (v: string[]) => { + setRatingFilter(v.map(v => parseInt(v))) + localStorage.setItem('ratingFilter', v.join(',')) + } + + const filteredSounds = (() => { + const filtered = Sounds.filter(value => txtFilters.every(filter => value.id.includes(filter))).filter((value) => { + const rate = soundRatings[value.id] ?? 0 + let result = false + if (ratingFilter && ratingFilter.some(v => v >= 0 ? true : false)) { + if (ratingFilter.some(v => v === rate)) result = true + } + else { + result = true + } + return result + }) + if (!SoundsSort) return filtered + const id_sorted = SoundsSort.id == 'ascending' ? filtered : filtered.reverse() + return (() => { + if (SoundsSort.rating == 'none') return id_sorted + + if (SoundsSort.rating == 'ascending') return id_sorted.sort((a, b) => (soundRatings[a.id] ?? 0) - (soundRatings[b.id] ?? 0)) + else return id_sorted.sort((a, b) => (soundRatings[b.id] ?? 0) - (soundRatings[a.id] ?? 0)) + })() + })() const scrollRef = useRef(null) - const CorrectedRatingFilter = ratingFilterSwitch ? ratingFilter : 0 - useEffect(() => scrollRef.current?.scrollTo({ top: 0 }), [txtFilters, CorrectedRatingFilter, ratingFilterSwitch, targetVersion?.raw]) + useEffect(() => scrollRef.current?.scrollTo({ top: 0 }), [txtFilters, ratingFilter, targetVersion?.raw]) const itemHeight = 40 @@ -56,7 +106,10 @@ export const SoundSelector = () => { const listBoxHeightCSS = 'calc(100vh - 391px)' - const onChangeSearchWord = useCallback((e: React.ChangeEvent) => setTxtFilters(e.target.value.split(' ')), [setTxtFilters]) + const onChangeSearchWord = useCallback((e: React.ChangeEvent) => { + setSearchTxt(e.target.value) + setTxtFilters(e.target.value.split(' ')) + }, [setTxtFilters]) const onSelectSound = useCallback((e: React.MouseEvent) => { dispatch(updateSelectedSound({ id: e.currentTarget.id })) @@ -71,6 +124,22 @@ export const SoundSelector = () => { setSoundRatings(CorrectSoundRatings) localStorage.setItem('soundRatings', JSON.stringify(CorrectSoundRatings)) } + + const activeStarColor = useColorModeValue('var(--ui-colors-amber-500)', 'var(--ui-colors-amber-400)') + const inactiveStarColor = useColorModeValue('var(--ui-colors-blackAlpha-300)', 'var(--ui-colors-whiteAlpha-300)') + + const labelRatingStar = (size: number, rating: number, marginBottom: number, paddingY?: number) => { + return ( + + = 1 ? activeStarColor : inactiveStarColor} /> + = 2 ? activeStarColor : inactiveStarColor} /> + = 3 ? activeStarColor : inactiveStarColor} /> + = 4 ? activeStarColor : inactiveStarColor} /> + = 5 ? activeStarColor : inactiveStarColor} /> + + ) + } + const items = displayingItems.map(item => (
  • { - + + { + setTxtFilters([]) + setSearchTxt('') + }} + > + + - + - - - setRatingFilter(rate)} /> - - - {/* */} - : } onClick={toggleRatingFilter} /> - {/* */} + + } variant="outline" borderColor="inherit" /> + + + changeSoundSorts({ id: value })}> + {t('ascending')} + {t('descending')} + + + changeSoundSorts({ rating: value })}> + {t('none')} + {t('ascending')} + {t('descending')} + + + + + + + v >= 0 ? true : false)} + icon={} + variant="outline" + colorScheme="primary" + /> + + + + clearRatingFilters()} icon={}>{t('clear_filter')} + allOnRatingFilters()} icon={}>{t('all_on_filter')} + + + v.toString())} + onChange={(v: string[]) => changeRatingFilters(v)} + > + {labelRatingStar(18, 5, -2, 1)} + {labelRatingStar(18, 4, -2, 1)} + {labelRatingStar(18, 3, -2, 1)} + {labelRatingStar(18, 2, -2, 1)} + {labelRatingStar(18, 1, -2, 1)} + {labelRatingStar(18, 0, -2, 1)} + + + + @@ -131,7 +249,7 @@ export const SoundSelector = () => { ref={scrollRef} style={{ width: '100%', height: listBoxHeightCSS, overflowY: 'scroll' }} > -
    +
      {items}
    diff --git a/src/web/ThemeChange.tsx b/src/web/ThemeChange.tsx index 38c29ca..3e7dbf3 100644 --- a/src/web/ThemeChange.tsx +++ b/src/web/ThemeChange.tsx @@ -1,42 +1,50 @@ import React from 'react' -import { useColorMode, Menu, MenuButton, MenuList, MenuItem, IconButton, Box } from '@yamada-ui/react' +import { useColorMode, HStack, Text, SegmentedControl, SegmentedControlButton, ColorModeWithSystem, CardBody, CardHeader } from '@yamada-ui/react' import { MoonIcon, PaletteIcon, SunIcon, MonitorCogIcon } from '@yamada-ui/lucide' +import { useTranslation } from 'react-i18next' export const ThemeChange = () => { const { changeColorMode, internalColorMode } = useColorMode() + const { t } = useTranslation() - const isSystemColor = (internalColorMode == 'system') ? 'primary' : '' - const isLightColor = (internalColorMode == 'light') ? 'primary' : '' - const isDarkColor = (internalColorMode == 'dark') ? 'primary' : '' + const getColorModeWithSystem = (v: string): ColorModeWithSystem => { + if (v == 'system') return 'system' + if (v == 'light') return 'light' + if (v == 'dark') return 'dark' + return internalColorMode + } return ( - - } variant="outline" /> - - - } - textColor={isSystemColor} - onClick={() => changeColorMode('system')} - > - System - - } - textColor={isLightColor} - onClick={() => changeColorMode('light')} - > - Light - - } - textColor={isDarkColor} - onClick={() => changeColorMode('dark')} - > - Dark - - - + <> + + + + {t('theme_select')} + + + + changeColorMode(getColorModeWithSystem(v))}> + + + + System + + + + + + Light + + + + + + Dark + + + + + ) } diff --git a/src/web/VolumeChange.tsx b/src/web/VolumeChange.tsx index 964cb8e..7d7a6b9 100644 --- a/src/web/VolumeChange.tsx +++ b/src/web/VolumeChange.tsx @@ -23,15 +23,14 @@ export const VolumeChange = () => { } const volumeIcon = (volume: number, mute: boolean) => { - if (mute) return - else if (volume <= 0.0) return - else if (volume <= 0.5) return - else return + if (mute) return + else if (volume <= 0.0) return + else if (volume <= 0.5) return + else return } return ( -