Added language preference system
This commit is contained in:
		
							parent
							
								
									3cf890d70d
								
							
						
					
					
						commit
						fb88e97825
					
				| @ -14,6 +14,7 @@ import { | ||||
| import { useEffect, useState } from "react"; | ||||
| import { useSwipeable } from "react-swipeable"; | ||||
| import { ImageQuality } from "./Img"; | ||||
| import OrderableList from "./OrderableList"; | ||||
| import MainPanel from "./Panels/MainPanel"; | ||||
| import Popup from "./Popup"; | ||||
| import Select from "./Select"; | ||||
| @ -250,154 +251,177 @@ export default function AppLayout(props: Props): JSX.Element { | ||||
|           </span> | ||||
|         </div> | ||||
| 
 | ||||
|         <Popup | ||||
|           state={appLayout.languagePanelOpen} | ||||
|           setState={appLayout.setLanguagePanelOpen} | ||||
|         > | ||||
|           <h2 className="text-2xl">{langui.select_language}</h2> | ||||
|           <div className="flex flex-wrap flex-row gap-2 mobile:flex-col"> | ||||
|             {router.locales?.map((locale) => ( | ||||
|               <Button | ||||
|                 key={locale} | ||||
|                 active={locale === router.locale} | ||||
|                 href={router.asPath} | ||||
|                 locale={locale} | ||||
|                 onClick={() => appLayout.setLanguagePanelOpen(false)} | ||||
|               > | ||||
|                 {prettyLanguage(locale, languages)} | ||||
|               </Button> | ||||
|             ))} | ||||
|           </div> | ||||
|         </Popup> | ||||
| 
 | ||||
|         <Popup | ||||
|           state={appLayout.configPanelOpen} | ||||
|           setState={appLayout.setConfigPanelOpen} | ||||
|         > | ||||
|           <h2 className="text-2xl">{langui.settings}</h2> | ||||
| 
 | ||||
|           <div className="mt-4 grid gap-8 place-items-center text-center desktop:grid-cols-2"> | ||||
|             <div> | ||||
|               <h3 className="text-xl">{langui.theme}</h3> | ||||
|               <div className="flex flex-row"> | ||||
|                 <Button | ||||
|                   onClick={() => { | ||||
|                     appLayout.setDarkMode(false); | ||||
|                     appLayout.setSelectedThemeMode(true); | ||||
|                   }} | ||||
|                   active={ | ||||
|                     appLayout.selectedThemeMode === true && | ||||
|                     appLayout.darkMode === false | ||||
|                   } | ||||
|                   className="rounded-r-none" | ||||
|                 > | ||||
|                   {langui.light} | ||||
|                 </Button> | ||||
|                 <Button | ||||
|                   onClick={() => { | ||||
|                     appLayout.setSelectedThemeMode(false); | ||||
|                   }} | ||||
|                   active={appLayout.selectedThemeMode === false} | ||||
|                   className="rounded-l-none rounded-r-none border-x-0" | ||||
|                 > | ||||
|                   {langui.auto} | ||||
|                 </Button> | ||||
|                 <Button | ||||
|                   onClick={() => { | ||||
|                     appLayout.setDarkMode(true); | ||||
|                     appLayout.setSelectedThemeMode(true); | ||||
|                   }} | ||||
|                   active={ | ||||
|                     appLayout.selectedThemeMode === true && | ||||
|                     appLayout.darkMode === true | ||||
|                   } | ||||
|                   className="rounded-l-none" | ||||
|                 > | ||||
|                   {langui.dark} | ||||
|                 </Button> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div> | ||||
|               <h3 className="text-xl">{langui.currency}</h3> | ||||
|           <div className="mt-4 grid gap-16 justify-items-center text-center desktop:grid-cols-[auto_auto]"> | ||||
|             {router.locales && ( | ||||
|               <div> | ||||
|                 <Select | ||||
|                   options={currencyOptions} | ||||
|                   state={currencySelect} | ||||
|                   setState={setCurrencySelect} | ||||
|                   className="w-28" | ||||
|                 <h3 className="text-xl">{langui.languages}</h3> | ||||
|                 {appLayout.preferredLanguages && ( | ||||
|                   <OrderableList | ||||
|                     items={ | ||||
|                       appLayout.preferredLanguages.length > 0 | ||||
|                         ? new Map( | ||||
|                             appLayout.preferredLanguages.map((locale) => [ | ||||
|                               locale, | ||||
|                               prettyLanguage(locale, languages), | ||||
|                             ]) | ||||
|                           ) | ||||
|                         : new Map( | ||||
|                             router.locales.map((locale) => [ | ||||
|                               locale, | ||||
|                               prettyLanguage(locale, languages), | ||||
|                             ]) | ||||
|                           ) | ||||
|                     } | ||||
|                     onChange={(items) => { | ||||
|                       const preferredLanguages = [...items].map( | ||||
|                         ([code]) => code | ||||
|                       ); | ||||
|                       console.log(router.asPath); | ||||
|                       appLayout.setPreferredLanguages(preferredLanguages); | ||||
|                       router.push(router.asPath, router.asPath, { | ||||
|                         locale: preferredLanguages[0], | ||||
|                       }); | ||||
|                     }} | ||||
|                   /> | ||||
|                 )} | ||||
|               </div> | ||||
|             )} | ||||
|             <div className="grid gap-8 place-items-center text-center desktop:grid-cols-2"> | ||||
|               <div> | ||||
|                 <h3 className="text-xl">{langui.theme}</h3> | ||||
|                 <div className="flex flex-row"> | ||||
|                   <Button | ||||
|                     onClick={() => { | ||||
|                       appLayout.setDarkMode(false); | ||||
|                       appLayout.setSelectedThemeMode(true); | ||||
|                     }} | ||||
|                     active={ | ||||
|                       appLayout.selectedThemeMode === true && | ||||
|                       appLayout.darkMode === false | ||||
|                     } | ||||
|                     className="rounded-r-none" | ||||
|                   > | ||||
|                     {langui.light} | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     onClick={() => { | ||||
|                       appLayout.setSelectedThemeMode(false); | ||||
|                     }} | ||||
|                     active={appLayout.selectedThemeMode === false} | ||||
|                     className="rounded-l-none rounded-r-none border-x-0" | ||||
|                   > | ||||
|                     {langui.auto} | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     onClick={() => { | ||||
|                       appLayout.setDarkMode(true); | ||||
|                       appLayout.setSelectedThemeMode(true); | ||||
|                     }} | ||||
|                     active={ | ||||
|                       appLayout.selectedThemeMode === true && | ||||
|                       appLayout.darkMode === true | ||||
|                     } | ||||
|                     className="rounded-l-none" | ||||
|                   > | ||||
|                     {langui.dark} | ||||
|                   </Button> | ||||
|                 </div> | ||||
|               </div> | ||||
| 
 | ||||
|               <div> | ||||
|                 <h3 className="text-xl">{langui.currency}</h3> | ||||
|                 <div> | ||||
|                   <Select | ||||
|                     options={currencyOptions} | ||||
|                     state={currencySelect} | ||||
|                     setState={setCurrencySelect} | ||||
|                     className="w-28" | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
| 
 | ||||
|               <div> | ||||
|                 <h3 className="text-xl">{langui.font_size}</h3> | ||||
|                 <div className="flex flex-row"> | ||||
|                   <Button | ||||
|                     className="rounded-r-none" | ||||
|                     onClick={() => | ||||
|                       appLayout.setFontSize( | ||||
|                         appLayout.fontSize | ||||
|                           ? appLayout.fontSize / 1.05 | ||||
|                           : 1 / 1.05 | ||||
|                       ) | ||||
|                     } | ||||
|                   > | ||||
|                     <span className="material-icons">text_decrease</span> | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     className="rounded-l-none rounded-r-none border-x-0" | ||||
|                     onClick={() => appLayout.setFontSize(1)} | ||||
|                   > | ||||
|                     {((appLayout.fontSize ?? 1) * 100).toLocaleString( | ||||
|                       undefined, | ||||
|                       { | ||||
|                         maximumFractionDigits: 0, | ||||
|                       } | ||||
|                     )} | ||||
|                     % | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     className="rounded-l-none" | ||||
|                     onClick={() => | ||||
|                       appLayout.setFontSize( | ||||
|                         appLayout.fontSize | ||||
|                           ? appLayout.fontSize * 1.05 | ||||
|                           : 1 * 1.05 | ||||
|                       ) | ||||
|                     } | ||||
|                   > | ||||
|                     <span className="material-icons">text_increase</span> | ||||
|                   </Button> | ||||
|                 </div> | ||||
|               </div> | ||||
| 
 | ||||
|               <div> | ||||
|                 <h3 className="text-xl">{langui.font}</h3> | ||||
|                 <div className="grid gap-2"> | ||||
|                   <Button | ||||
|                     active={appLayout.dyslexic === false} | ||||
|                     onClick={() => appLayout.setDyslexic(false)} | ||||
|                     className="font-zenMaruGothic" | ||||
|                   > | ||||
|                     Zen Maru Gothic | ||||
|                   </Button> | ||||
|                   <Button | ||||
|                     active={appLayout.dyslexic === true} | ||||
|                     onClick={() => appLayout.setDyslexic(true)} | ||||
|                     className="font-openDyslexic" | ||||
|                   > | ||||
|                     OpenDyslexic | ||||
|                   </Button> | ||||
|                 </div> | ||||
|               </div> | ||||
| 
 | ||||
|               <div> | ||||
|                 <h3 className="text-xl">{langui.player_name}</h3> | ||||
|                 <input | ||||
|                   type="text" | ||||
|                   placeholder="<player>" | ||||
|                   className="w-48" | ||||
|                   onInput={(event) => | ||||
|                     appLayout.setPlayerName( | ||||
|                       (event.target as HTMLInputElement).value | ||||
|                     ) | ||||
|                   } | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div> | ||||
|               <h3 className="text-xl">{langui.font_size}</h3> | ||||
|               <div className="flex flex-row"> | ||||
|                 <Button | ||||
|                   className="rounded-r-none" | ||||
|                   onClick={() => | ||||
|                     appLayout.setFontSize( | ||||
|                       appLayout.fontSize ? appLayout.fontSize / 1.05 : 1 / 1.05 | ||||
|                     ) | ||||
|                   } | ||||
|                 > | ||||
|                   <span className="material-icons">text_decrease</span> | ||||
|                 </Button> | ||||
|                 <Button | ||||
|                   className="rounded-l-none rounded-r-none border-x-0" | ||||
|                   onClick={() => appLayout.setFontSize(1)} | ||||
|                 > | ||||
|                   {((appLayout.fontSize ?? 1) * 100).toLocaleString(undefined, { | ||||
|                     maximumFractionDigits: 0, | ||||
|                   })} | ||||
|                   % | ||||
|                 </Button> | ||||
|                 <Button | ||||
|                   className="rounded-l-none" | ||||
|                   onClick={() => | ||||
|                     appLayout.setFontSize( | ||||
|                       appLayout.fontSize ? appLayout.fontSize * 1.05 : 1 * 1.05 | ||||
|                     ) | ||||
|                   } | ||||
|                 > | ||||
|                   <span className="material-icons">text_increase</span> | ||||
|                 </Button> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div> | ||||
|               <h3 className="text-xl">{langui.font}</h3> | ||||
|               <div className="grid gap-2"> | ||||
|                 <Button | ||||
|                   active={appLayout.dyslexic === false} | ||||
|                   onClick={() => appLayout.setDyslexic(false)} | ||||
|                   className="font-zenMaruGothic" | ||||
|                 > | ||||
|                   Zen Maru Gothic | ||||
|                 </Button> | ||||
|                 <Button | ||||
|                   active={appLayout.dyslexic === true} | ||||
|                   onClick={() => appLayout.setDyslexic(true)} | ||||
|                   className="font-openDyslexic" | ||||
|                 > | ||||
|                   OpenDyslexic | ||||
|                 </Button> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div> | ||||
|               <h3 className="text-xl">{langui.player_name}</h3> | ||||
|               <input | ||||
|                 type="text" | ||||
|                 placeholder="<player>" | ||||
|                 className="w-48" | ||||
|                 onInput={(event) => | ||||
|                   appLayout.setPlayerName( | ||||
|                     (event.target as HTMLInputElement).value | ||||
|                   ) | ||||
|                 } | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </Popup> | ||||
|       </div> | ||||
|  | ||||
| @ -10,7 +10,7 @@ interface Props { | ||||
|   locale?: string; | ||||
|   target?: "_blank"; | ||||
|   onClick?: MouseEventHandler<HTMLDivElement>; | ||||
|   draggable?: boolean | ||||
|   draggable?: boolean; | ||||
| } | ||||
| 
 | ||||
| export default function Button(props: Props): JSX.Element { | ||||
|  | ||||
							
								
								
									
										69
									
								
								src/components/OrderableList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/components/OrderableList.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| import { arrayMove } from "queries/helpers"; | ||||
| import { useEffect, useState } from "react"; | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   items: Map<string, string>; | ||||
|   onChange?: (items: Map<string, string>) => void; | ||||
| } | ||||
| 
 | ||||
| export default function LanguageSwitcher(props: Props): JSX.Element { | ||||
|   const [items, setItems] = useState<Map<string, string>>(props.items); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     props.onChange?.(items); | ||||
|   }, [items]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="grid gap-2"> | ||||
|       {[...items].map(([key, value], index) => ( | ||||
|         <> | ||||
|           {index === 0 ? ( | ||||
|             <p>Primary language</p> | ||||
|           ) : index === 1 ? ( | ||||
|             <p>Secondary languages</p> | ||||
|           ) : ( | ||||
|             "" | ||||
|           )} | ||||
|           <div | ||||
|             onDragStart={(event) => { | ||||
|               const source = event.target as HTMLElement; | ||||
|               const sourceIndex = source.parentElement | ||||
|                 ? Array.from(source.parentElement.children) | ||||
|                     .filter((element) => element.tagName === "DIV") | ||||
|                     .indexOf(source) | ||||
|                 : -1; | ||||
|               event.dataTransfer.setData("text", sourceIndex.toString()); | ||||
|             }} | ||||
|             onDragOver={(event) => { | ||||
|               event.preventDefault(); | ||||
|             }} | ||||
|             onDrop={(event) => { | ||||
|               event.preventDefault(); | ||||
|               const target = event.target as HTMLElement; | ||||
|               const targetIndex = target.parentElement | ||||
|                 ? Array.from(target.parentElement.children) | ||||
|                     .filter((element) => element.tagName === "DIV") | ||||
|                     .indexOf(target) | ||||
|                 : -1; | ||||
|               const sourceIndex = parseInt( | ||||
|                 event.dataTransfer.getData("text"), | ||||
|                 10 | ||||
|               ); | ||||
|               const newItems = arrayMove([...items], sourceIndex, targetIndex); | ||||
|               setItems(new Map(newItems)); | ||||
|             }} | ||||
|             className="grid place-content-center place-items-center  | ||||
|             border-[1px] transition-all hover:text-light hover:bg-dark  | ||||
|             hover:drop-shadow-shade-lg border-dark bg-light text-dark  | ||||
|             rounded-full px-4 pt-[0.4rem] pb-[0.5rem] cursor-grab select-none" | ||||
|             key={key} | ||||
|             draggable | ||||
|           > | ||||
|             {value} | ||||
|           </div> | ||||
|         </> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -89,26 +89,6 @@ export default function MainPanel(props: Props): JSX.Element { | ||||
|               </Button> | ||||
|             </ToolTip> | ||||
| 
 | ||||
|             {router.locale && ( | ||||
|               <ToolTip | ||||
|                 content={<h3 className="text-2xl">{langui.change_language}</h3>} | ||||
|                 placement="right" | ||||
|                 className="text-left" | ||||
|                 disabled={!appLayout.mainPanelReduced} | ||||
|               > | ||||
|                 <Button | ||||
|                   onClick={() => appLayout.setLanguagePanelOpen(true)} | ||||
|                   className={ | ||||
|                     appLayout.mainPanelReduced && isDesktop | ||||
|                       ? "" | ||||
|                       : "!py-0.5 !px-2.5 !text-sm" | ||||
|                   } | ||||
|                 > | ||||
|                   {router.locale.toUpperCase()} | ||||
|                 </Button> | ||||
|               </ToolTip> | ||||
|             )} | ||||
| 
 | ||||
|             {/* <ToolTip | ||||
|               content={<h3 className="text-2xl">{langui.open_search}</h3>} | ||||
|               placement="right" | ||||
|  | ||||
| @ -4,7 +4,6 @@ import React, { ReactNode, useContext } from "react"; | ||||
| 
 | ||||
| interface AppLayoutState { | ||||
|   subPanelOpen: boolean | undefined; | ||||
|   languagePanelOpen: boolean | undefined; | ||||
|   configPanelOpen: boolean | undefined; | ||||
|   mainPanelReduced: boolean | undefined; | ||||
|   mainPanelOpen: boolean | undefined; | ||||
| @ -14,10 +13,8 @@ interface AppLayoutState { | ||||
|   dyslexic: boolean | undefined; | ||||
|   currency: string | undefined; | ||||
|   playerName: string | undefined; | ||||
|   preferredLanguages: string[] | undefined; | ||||
|   setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>; | ||||
|   setLanguagePanelOpen: React.Dispatch< | ||||
|     React.SetStateAction<boolean | undefined> | ||||
|   >; | ||||
|   setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>; | ||||
|   setMainPanelReduced: React.Dispatch< | ||||
|     React.SetStateAction<boolean | undefined> | ||||
| @ -31,12 +28,14 @@ interface AppLayoutState { | ||||
|   setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>; | ||||
|   setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>; | ||||
|   setPlayerName: React.Dispatch<React.SetStateAction<string | undefined>>; | ||||
|   setPreferredLanguages: React.Dispatch< | ||||
|     React.SetStateAction<string[] | undefined> | ||||
|   >; | ||||
| } | ||||
| 
 | ||||
| /* eslint-disable @typescript-eslint/no-empty-function */ | ||||
| const initialState: AppLayoutState = { | ||||
|   subPanelOpen: false, | ||||
|   languagePanelOpen: false, | ||||
|   configPanelOpen: false, | ||||
|   mainPanelReduced: false, | ||||
|   mainPanelOpen: false, | ||||
| @ -46,8 +45,8 @@ const initialState: AppLayoutState = { | ||||
|   dyslexic: false, | ||||
|   currency: "USD", | ||||
|   playerName: "", | ||||
|   preferredLanguages: [], | ||||
|   setSubPanelOpen: () => {}, | ||||
|   setLanguagePanelOpen: () => {}, | ||||
|   setMainPanelReduced: () => {}, | ||||
|   setMainPanelOpen: () => {}, | ||||
|   setDarkMode: () => {}, | ||||
| @ -57,6 +56,7 @@ const initialState: AppLayoutState = { | ||||
|   setDyslexic: () => {}, | ||||
|   setCurrency: () => {}, | ||||
|   setPlayerName: () => {}, | ||||
|   setPreferredLanguages: () => {}, | ||||
| }; | ||||
| /* eslint-enable @typescript-eslint/no-empty-function */ | ||||
| 
 | ||||
| @ -77,10 +77,6 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|     boolean | undefined | ||||
|   >("subPanelOpen", initialState.subPanelOpen); | ||||
| 
 | ||||
|   const [languagePanelOpen, setLanguagePanelOpen] = useStateWithLocalStorage< | ||||
|     boolean | undefined | ||||
|   >("languagePanelOpen", initialState.languagePanelOpen); | ||||
| 
 | ||||
|   const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage< | ||||
|     boolean | undefined | ||||
|   >("configPanelOpen", initialState.configPanelOpen); | ||||
| @ -115,11 +111,14 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|     string | undefined | ||||
|   >("playerName", initialState.playerName); | ||||
| 
 | ||||
|   const [preferredLanguages, setPreferredLanguages] = useStateWithLocalStorage< | ||||
|     string[] | undefined | ||||
|   >("preferredLanguages", initialState.preferredLanguages); | ||||
| 
 | ||||
|   return ( | ||||
|     <AppContext.Provider | ||||
|       value={{ | ||||
|         subPanelOpen, | ||||
|         languagePanelOpen, | ||||
|         configPanelOpen, | ||||
|         mainPanelReduced, | ||||
|         mainPanelOpen, | ||||
| @ -129,8 +128,8 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|         dyslexic, | ||||
|         currency, | ||||
|         playerName, | ||||
|         preferredLanguages, | ||||
|         setSubPanelOpen, | ||||
|         setLanguagePanelOpen, | ||||
|         setConfigPanelOpen, | ||||
|         setMainPanelReduced, | ||||
|         setMainPanelOpen, | ||||
| @ -140,6 +139,7 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|         setDyslexic, | ||||
|         setCurrency, | ||||
|         setPlayerName, | ||||
|         setPreferredLanguages, | ||||
|       }} | ||||
|     > | ||||
|       {props.children} | ||||
|  | ||||
| @ -274,6 +274,18 @@ export function prettyLanguage( | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| export function prettyLanguageToCode( | ||||
|   prettyLanguage: string, | ||||
|   languages: AppStaticProps["languages"] | ||||
| ): string { | ||||
|   let result = prettyLanguage; | ||||
|   languages.forEach((language) => { | ||||
|     if (language?.attributes?.localized_name === prettyLanguage) | ||||
|       result = language.attributes.code; | ||||
|   }); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| export function prettyTestWarning( | ||||
|   router: NextRouter, | ||||
|   message: string, | ||||
| @ -463,3 +475,8 @@ export function getVideoThumbnailURL(uid: string): string { | ||||
| export function getVideoFile(uid: string): string { | ||||
|   return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`; | ||||
| } | ||||
| 
 | ||||
| export function arrayMove<T>(arr: T[], old_index: number, new_index: number) { | ||||
|   arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); | ||||
|   return arr; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint