Umami events + natural sort
This commit is contained in:
		
							parent
							
								
									bd0185358c
								
							
						
					
					
						commit
						40d893eba8
					
				
							
								
								
									
										24
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										24
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -23,6 +23,7 @@ | ||||
|         "react-dom": "18.2.0", | ||||
|         "react-hot-keys": "^2.7.2", | ||||
|         "react-swipeable": "^7.0.0", | ||||
|         "string-natural-compare": "^3.0.1", | ||||
|         "throttle-debounce": "^5.0.0", | ||||
|         "tippy.js": "^6.3.7", | ||||
|         "turndown": "^7.1.1", | ||||
| @ -39,6 +40,7 @@ | ||||
|         "@types/nodemailer": "^6.4.5", | ||||
|         "@types/react": "18.0.17", | ||||
|         "@types/react-dom": "^18.0.6", | ||||
|         "@types/string-natural-compare": "^3.0.2", | ||||
|         "@types/throttle-debounce": "^5.0.0", | ||||
|         "@types/turndown": "^5.0.1", | ||||
|         "@typescript-eslint/eslint-plugin": "^5.35.1", | ||||
| @ -2660,6 +2662,12 @@ | ||||
|       "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/@types/string-natural-compare": { | ||||
|       "version": "3.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/string-natural-compare/-/string-natural-compare-3.0.2.tgz", | ||||
|       "integrity": "sha512-teA6gjoKrX+eeweVnk3bbQsbKUQyrP6CDJGsu0x33U4wyq2aF4y6CwPC/ygnLL0rf8twMXRmLdXo7DVhp1XBBw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/@types/throttle-debounce": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz", | ||||
| @ -8160,6 +8168,11 @@ | ||||
|       "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/string-natural-compare": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", | ||||
|       "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" | ||||
|     }, | ||||
|     "node_modules/string-width": { | ||||
|       "version": "4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
| @ -11115,6 +11128,12 @@ | ||||
|       "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@types/string-natural-compare": { | ||||
|       "version": "3.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/string-natural-compare/-/string-natural-compare-3.0.2.tgz", | ||||
|       "integrity": "sha512-teA6gjoKrX+eeweVnk3bbQsbKUQyrP6CDJGsu0x33U4wyq2aF4y6CwPC/ygnLL0rf8twMXRmLdXo7DVhp1XBBw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@types/throttle-debounce": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz", | ||||
| @ -15107,6 +15126,11 @@ | ||||
|       "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "string-natural-compare": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", | ||||
|       "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" | ||||
|     }, | ||||
|     "string-width": { | ||||
|       "version": "4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|  | ||||
| @ -34,6 +34,7 @@ | ||||
|     "react-dom": "18.2.0", | ||||
|     "react-hot-keys": "^2.7.2", | ||||
|     "react-swipeable": "^7.0.0", | ||||
|     "string-natural-compare": "^3.0.1", | ||||
|     "throttle-debounce": "^5.0.0", | ||||
|     "tippy.js": "^6.3.7", | ||||
|     "turndown": "^7.1.1", | ||||
| @ -50,6 +51,7 @@ | ||||
|     "@types/nodemailer": "^6.4.5", | ||||
|     "@types/react": "18.0.17", | ||||
|     "@types/react-dom": "^18.0.6", | ||||
|     "@types/string-natural-compare": "^3.0.2", | ||||
|     "@types/throttle-debounce": "^5.0.0", | ||||
|     "@types/turndown": "^5.0.1", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.35.1", | ||||
|  | ||||
| @ -382,13 +382,19 @@ export const AppLayout = ({ | ||||
|           <Button | ||||
|             text="Let me in regardless" | ||||
|             className="mt-8" | ||||
|             onClick={disgardSafariWarning} | ||||
|             onClick={() => { | ||||
|               disgardSafariWarning(); | ||||
|               umami("[Safari] Disgard warning"); | ||||
|             }} | ||||
|           /> | ||||
|         </Popup> | ||||
| 
 | ||||
|         <Popup | ||||
|           state={configPanelOpen} | ||||
|           onClose={() => setConfigPanelOpen(false)} | ||||
|           onClose={() => { | ||||
|             setConfigPanelOpen(false); | ||||
|             umami("[Settings] Close settings"); | ||||
|           }} | ||||
|         > | ||||
|           <h2 className="text-2xl">{langui.settings}</h2> | ||||
| 
 | ||||
| @ -423,6 +429,7 @@ export const AppLayout = ({ | ||||
|                         (item) => item.code | ||||
|                       ); | ||||
|                       setPreferredLanguages(newPreferredLanguages); | ||||
|                       umami("[Settings] Change preferred languages"); | ||||
|                     }} | ||||
|                   /> | ||||
|                 )} | ||||
| @ -442,6 +449,7 @@ export const AppLayout = ({ | ||||
|                       onClick: () => { | ||||
|                         setDarkMode(false); | ||||
|                         setSelectedThemeMode(true); | ||||
|                         umami("[Settings] Change theme (light)"); | ||||
|                       }, | ||||
|                       active: selectedThemeMode && !darkMode, | ||||
|                       text: langui.light, | ||||
| @ -449,6 +457,7 @@ export const AppLayout = ({ | ||||
|                     { | ||||
|                       onClick: () => { | ||||
|                         setSelectedThemeMode(false); | ||||
|                         umami("[Settings] Change theme (auto)"); | ||||
|                       }, | ||||
|                       active: !selectedThemeMode, | ||||
|                       text: langui.auto, | ||||
| @ -457,6 +466,7 @@ export const AppLayout = ({ | ||||
|                       onClick: () => { | ||||
|                         setDarkMode(true); | ||||
|                         setSelectedThemeMode(true); | ||||
|                         umami("[Settings] Change theme (dark)"); | ||||
|                       }, | ||||
|                       active: selectedThemeMode && darkMode, | ||||
|                       text: langui.dark, | ||||
| @ -471,7 +481,12 @@ export const AppLayout = ({ | ||||
|                   <Select | ||||
|                     options={currencyOptions} | ||||
|                     value={currencySelect} | ||||
|                     onChange={setCurrencySelect} | ||||
|                     onChange={(newCurrency) => { | ||||
|                       setCurrencySelect(newCurrency); | ||||
|                       umami( | ||||
|                         `[Settings] Change currency (${currencyOptions[newCurrency]})}` | ||||
|                       ); | ||||
|                     }} | ||||
|                     className="w-28" | ||||
|                   /> | ||||
|                 </div> | ||||
| @ -482,17 +497,41 @@ export const AppLayout = ({ | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { | ||||
|                       onClick: () => setFontSize(fontSize / 1.05), | ||||
|                       onClick: () => { | ||||
|                         setFontSize((current) => current / 1.05); | ||||
|                         umami( | ||||
|                           `[Settings] Change font size (${( | ||||
|                             (fontSize / 1.05) * | ||||
|                             100 | ||||
|                           ).toLocaleString(undefined, { | ||||
|                             maximumFractionDigits: 0, | ||||
|                           })}%)` | ||||
|                         ); | ||||
|                       }, | ||||
|                       icon: Icon.TextDecrease, | ||||
|                     }, | ||||
|                     { | ||||
|                       onClick: () => setFontSize(1), | ||||
|                       onClick: () => { | ||||
|                         setFontSize(1); | ||||
|                         umami("[Settings] Change font size (100%)"); | ||||
|                       }, | ||||
|                       text: `${(fontSize * 100).toLocaleString(undefined, { | ||||
|                         maximumFractionDigits: 0, | ||||
|                       })}%`,
 | ||||
|                     }, | ||||
|                     { | ||||
|                       onClick: () => setFontSize(fontSize * 1.05), | ||||
|                       onClick: () => { | ||||
|                         setFontSize((current) => current * 1.05); | ||||
|                         umami( | ||||
|                           `[Settings] Change font size (${( | ||||
|                             fontSize * | ||||
|                             1.05 * | ||||
|                             100 | ||||
|                           ).toLocaleString(undefined, { | ||||
|                             maximumFractionDigits: 0, | ||||
|                           })}%)` | ||||
|                         ); | ||||
|                       }, | ||||
|                       icon: Icon.TextIncrease, | ||||
|                     }, | ||||
|                   ]} | ||||
| @ -504,13 +543,19 @@ export const AppLayout = ({ | ||||
|                 <div className="grid gap-2"> | ||||
|                   <Button | ||||
|                     active={!dyslexic} | ||||
|                     onClick={() => setDyslexic(false)} | ||||
|                     onClick={() => { | ||||
|                       setDyslexic(false); | ||||
|                       umami("[Settings] Change font (Zen Maru Gothic)"); | ||||
|                     }} | ||||
|                     className="font-zenMaruGothic" | ||||
|                     text="Zen Maru Gothic" | ||||
|                   /> | ||||
|                   <Button | ||||
|                     active={dyslexic} | ||||
|                     onClick={() => setDyslexic(true)} | ||||
|                     onClick={() => { | ||||
|                       setDyslexic(true); | ||||
|                       umami("[Settings] Change font (OpenDyslexic)"); | ||||
|                     }} | ||||
|                     className="font-openDyslexic" | ||||
|                     text="OpenDyslexic" | ||||
|                   /> | ||||
| @ -523,7 +568,10 @@ export const AppLayout = ({ | ||||
|                   placeholder="<player>" | ||||
|                   className="w-48" | ||||
|                   value={playerName} | ||||
|                   onChange={setPlayerName} | ||||
|                   onChange={(newName) => { | ||||
|                     setPlayerName(newName); | ||||
|                     umami("[Settings] Change username"); | ||||
|                   }} | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|  | ||||
| @ -40,7 +40,10 @@ export const LanguageSwitcher = ({ | ||||
|             <Fragment key={index}> | ||||
|               <Button | ||||
|                 active={value === localesIndex} | ||||
|                 onClick={() => onLanguageChanged(value)} | ||||
|                 onClick={() => { | ||||
|                   onLanguageChanged(value); | ||||
|                   umami(`[Language Switcher] Switch language (${locale})`); | ||||
|                 }} | ||||
|                 text={prettyLanguage(locale, languages)} | ||||
|               /> | ||||
|             </Fragment> | ||||
|  | ||||
| @ -38,9 +38,16 @@ export const MainPanel = (): JSX.Element => { | ||||
|             "fixed top-1/2", | ||||
|             cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]") | ||||
|           )} | ||||
|           onClick={toggleMainPanelReduced} | ||||
|         > | ||||
|           <Button | ||||
|             onClick={() => { | ||||
|               if (mainPanelReduced) { | ||||
|                 umami("[MainPanel] Expand"); | ||||
|               } else { | ||||
|                 umami("[MainPanel] Reduce"); | ||||
|               } | ||||
|               toggleMainPanelReduced(); | ||||
|             }} | ||||
|             className="bg-light !px-2" | ||||
|             icon={mainPanelReduced ? Icon.ChevronRight : Icon.ChevronLeft} | ||||
|           /> | ||||
| @ -83,24 +90,11 @@ export const MainPanel = (): JSX.Element => { | ||||
|               <Button | ||||
|                 onClick={() => { | ||||
|                   setConfigPanelOpen(true); | ||||
|                   umami("[Settings] Open settings"); | ||||
|                 }} | ||||
|                 icon={Icon.Settings} | ||||
|               /> | ||||
|             </ToolTip> | ||||
| 
 | ||||
|             {/* <ToolTip | ||||
|               content={<h3 className="text-2xl">{langui.open_search}</h3>} | ||||
|               placement="right" | ||||
|               className="text-left" | ||||
|               disabled={!mainPanelReduced} | ||||
|             > | ||||
|               <Button | ||||
|                 onClick={() => { | ||||
|                   setSearchPanelOpen(true); | ||||
|                 }} | ||||
|                 icon={Icon.Search} | ||||
|               /> | ||||
|             </ToolTip> */} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| @ -193,6 +187,7 @@ export const MainPanel = (): JSX.Element => { | ||||
|         )} | ||||
|         <div className="mt-4 mb-8 grid place-content-center"> | ||||
|           <a | ||||
|             onClick={() => umami("[MainPanel] Visit license")} | ||||
|             aria-label="Read more about the license we use for this website" | ||||
|             className="group grid grid-flow-col place-content-center gap-1 transition-[filter]" | ||||
|             href="https://creativecommons.org/licenses/by-sa/4.0/" | ||||
| @ -222,15 +217,17 @@ export const MainPanel = (): JSX.Element => { | ||||
|         <div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8"> | ||||
|           <a | ||||
|             aria-label="Browse our GitHub repository, which include this website source code" | ||||
|             onClick={() => umami("[MainPanel] Visit GitHub")} | ||||
|             className="aspect-square w-10 | ||||
|             bg-black transition-colors [mask:url('/icons/github-brands.svg')] | ||||
|             ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark" | ||||
|               bg-black transition-colors [mask:url('/icons/github-brands.svg')] | ||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark" | ||||
|             href="https://github.com/Accords-Library" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|           ></a> | ||||
|           <a | ||||
|             aria-label="Follow us on Twitter" | ||||
|             onClick={() => umami("[MainPanel] Visit Twitter")} | ||||
|             className="aspect-square w-10 | ||||
|               bg-black transition-colors [mask:url('/icons/twitter-brands.svg')] | ||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark" | ||||
| @ -240,9 +237,10 @@ export const MainPanel = (): JSX.Element => { | ||||
|           ></a> | ||||
|           <a | ||||
|             aria-label="Join our Discord server!" | ||||
|             onClick={() => umami("[MainPanel] Visit Discord")} | ||||
|             className="aspect-square w-10 | ||||
|             bg-black transition-colors [mask:url('/icons/discord-brands.svg')] | ||||
|             ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark" | ||||
|               bg-black transition-colors [mask:url('/icons/discord-brands.svg')] | ||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark" | ||||
|             href="/discord" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import naturalCompare from "string-natural-compare"; | ||||
| import { Chip } from "./Chip"; | ||||
| import { PageSelector } from "./Inputs/PageSelector"; | ||||
| import { Ico, Icon } from "./Ico"; | ||||
| @ -15,7 +16,7 @@ interface Group<T> { | ||||
| } | ||||
| 
 | ||||
| const defaultGroupSortingFunction = <T,>(a: Group<T>, b: Group<T>) => | ||||
|   a.name.localeCompare(b.name); | ||||
|   naturalCompare(a.name, b.name); | ||||
| const defaultGroupCountingFunction = () => 1; | ||||
| const defaultFilteringFunction = () => true; | ||||
| const defaultSortingFunction = () => 0; | ||||
|  | ||||
| @ -75,17 +75,19 @@ const AboutUs = (props: PostStaticProps): JSX.Element => { | ||||
|                   case "OKAY": | ||||
|                     setFormResponse(langui.response_email_success ?? ""); | ||||
|                     setFormState("completed"); | ||||
| 
 | ||||
|                     umami("[Contact] Send email (success)"); | ||||
|                     break; | ||||
| 
 | ||||
|                   case "EENVELOPE": | ||||
|                     setFormResponse(langui.response_invalid_email ?? ""); | ||||
|                     setFormState("stale"); | ||||
|                     umami("[Contact] Send email (invalid email)"); | ||||
|                     break; | ||||
| 
 | ||||
|                   default: | ||||
|                     setFormResponse(response.message ?? ""); | ||||
|                     setFormState("stale"); | ||||
|                     umami("[Contact] Send email (error)"); | ||||
|                     break; | ||||
|                 } | ||||
|               }); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useState, useMemo, useCallback } from "react"; | ||||
| import { useBoolean } from "usehooks-ts"; | ||||
| import naturalCompare from "string-natural-compare"; | ||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||
| import { Select } from "components/Inputs/Select"; | ||||
| import { Switch } from "components/Inputs/Switch"; | ||||
| @ -17,7 +18,11 @@ import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { filterDefined, filterHasAttributes } from "helpers/others"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/others"; | ||||
| import { GetContentsQuery } from "graphql/generated"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||
| @ -150,7 +155,14 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|           className="mb-6 w-full" | ||||
|           placeholder={langui.search_title ?? "Search..."} | ||||
|           value={searchName} | ||||
|           onChange={setSearchName} | ||||
|           onChange={(name) => { | ||||
|             setSearchName(name); | ||||
|             if (isDefinedAndNotEmpty(name)) { | ||||
|               umami("[Contents/All] Change search term"); | ||||
|             } else { | ||||
|               umami("[News] Clear search term"); | ||||
|             } | ||||
|           }} | ||||
|         /> | ||||
| 
 | ||||
|         <WithLabel label={langui.group_by}> | ||||
| @ -158,14 +170,31 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|             className="w-full" | ||||
|             options={[langui.category ?? "Category", langui.type ?? "Type"]} | ||||
|             value={groupingMethod} | ||||
|             onChange={setGroupingMethod} | ||||
|             onChange={(value) => { | ||||
|               setGroupingMethod(value); | ||||
|               umami( | ||||
|                 `[Contents/All] Change grouping method (${ | ||||
|                   ["none", "category", "type"][value + 1] | ||||
|                 })` | ||||
|               ); | ||||
|             }} | ||||
|             allowEmpty | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         {hoverable && ( | ||||
|           <WithLabel label={langui.always_show_info}> | ||||
|             <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> | ||||
|             <Switch | ||||
|               value={keepInfoVisible} | ||||
|               onClick={() => { | ||||
|                 toggleKeepInfoVisible(); | ||||
|                 umami( | ||||
|                   `[Contents/All] Always ${ | ||||
|                     keepInfoVisible ? "hide" : "show" | ||||
|                   } info` | ||||
|                 ); | ||||
|               }} | ||||
|             /> | ||||
|           </WithLabel> | ||||
|         )} | ||||
| 
 | ||||
| @ -177,6 +206,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             umami("[Contents/All] Reset all filters"); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
| @ -299,7 +329,7 @@ export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   contents.contents.data.sort((a, b) => { | ||||
|     const titleA = a.attributes?.slug ?? ""; | ||||
|     const titleB = b.attributes?.slug ?? ""; | ||||
|     return titleA.localeCompare(titleB); | ||||
|     return naturalCompare(titleA, titleB); | ||||
|   }); | ||||
| 
 | ||||
|   const props: Props = { | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { useCallback, useMemo } from "react"; | ||||
| import naturalCompare from "string-natural-compare"; | ||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||
| import { | ||||
|   ContentPanel, | ||||
| @ -237,6 +238,23 @@ export const getStaticProps: GetStaticProps = async (context) => { | ||||
| 
 | ||||
|   const folder = contentsFolder.contentsFolders.data[0].attributes; | ||||
| 
 | ||||
|   const subFolders = { | ||||
|     // eslint-disable-next-line id-denylist
 | ||||
|     data: filterHasAttributes(folder.subfolders?.data, [ | ||||
|       "attributes.slug", | ||||
|     ]).sort((a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)), | ||||
|   }; | ||||
| 
 | ||||
|   const contents = { | ||||
|     // eslint-disable-next-line id-denylist
 | ||||
|     data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort( | ||||
|       (a, b) => naturalCompare(a.attributes.slug, b.attributes.slug) | ||||
|     ), | ||||
|   }; | ||||
| 
 | ||||
|   folder.contents = contents; | ||||
|   folder.subfolders = subFolders; | ||||
| 
 | ||||
|   const title = (() => { | ||||
|     if (slug === "root") { | ||||
|       return langui.contents ?? "Contents"; | ||||
|  | ||||
							
								
								
									
										5
									
								
								src/pages/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/pages/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| export {}; | ||||
| 
 | ||||
| declare global { | ||||
|   function umami(eventName: string): void; | ||||
| } | ||||
| @ -1,33 +1,39 @@ | ||||
| import { PostPage } from "components/PostPage"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { | ||||
|   getPostStaticProps, | ||||
|   PostStaticProps, | ||||
| } from "graphql/getPostStaticProps"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => ( | ||||
|   <PostPage | ||||
|     {...otherProps} | ||||
|     prependBody={ | ||||
|       <div className="grid w-full place-content-center place-items-center gap-5 text-center"> | ||||
|         <div | ||||
|           className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')] | ||||
| const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => { | ||||
|   const { langui } = useAppLayout(); | ||||
|   return ( | ||||
|     <PostPage | ||||
|       {...otherProps} | ||||
|       prependBody={ | ||||
|         <div className="grid w-full place-content-center place-items-center gap-5 text-center"> | ||||
|           <div | ||||
|             className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')] | ||||
|             [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center]" | ||||
|         /> | ||||
|         <h1 className="mb-0 text-5xl">Accord’s Library</h1> | ||||
|         <h2 className="-mt-5 text-xl"> | ||||
|           Discover • Analyze • Translate • Archive | ||||
|         </h2> | ||||
|       </div> | ||||
|     } | ||||
|     displayTitle={false} | ||||
|     displayLanguageSwitcher | ||||
|   /> | ||||
| ); | ||||
|           /> | ||||
|           <h1 className="mb-0 text-5xl">Accord’s Library</h1> | ||||
|           <h2 className="-mt-5 text-xl"> | ||||
|             Discover • Analyze • Translate • Archive | ||||
|           </h2> | ||||
|         </div> | ||||
|       } | ||||
|       displayTitle={false} | ||||
|       openGraph={getOpenGraph(langui)} | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default Home; | ||||
| 
 | ||||
|  | ||||
| @ -471,7 +471,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | ||||
|                     )} | ||||
|                   > | ||||
|                     <h3 className="text-xl">{langui.type_information}</h3> | ||||
|                     <div className="grid w-full grid-cols-2 place-content-between"> | ||||
|                     <div className="flex flex-wrap place-content-between gap-x-8"> | ||||
|                       {item.metadata?.[0]?.__typename === | ||||
|                         "ComponentMetadataBooks" && ( | ||||
|                         <> | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { Fragment, useCallback, useEffect, useMemo } from "react"; | ||||
| import { Fragment, useCallback, useMemo } from "react"; | ||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||
| import { ReturnButton } from "components/PanelComponents/ReturnButton"; | ||||
| import { | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useState, useMemo, useCallback } from "react"; | ||||
| import { useBoolean } from "usehooks-ts"; | ||||
| import naturalCompare from "string-natural-compare"; | ||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||
| import { Select } from "components/Inputs/Select"; | ||||
| import { Switch } from "components/Inputs/Switch"; | ||||
| @ -23,7 +24,12 @@ import { isUntangibleGroupItem } from "helpers/libraryItem"; | ||||
| import { PreviewCard } from "components/PreviewCard"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { ButtonGroup } from "components/Inputs/ButtonGroup"; | ||||
| import { filterHasAttributes, isDefined, isUndefined } from "helpers/others"; | ||||
| import { | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
|   isUndefined, | ||||
| } from "helpers/others"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { convertPrice } from "helpers/numbers"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| @ -162,7 +168,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|             b.attributes.title, | ||||
|             b.attributes.subtitle | ||||
|           ); | ||||
|           return titleA.localeCompare(titleB); | ||||
|           return naturalCompare(titleA, titleB); | ||||
|         } | ||||
|         case 1: { | ||||
|           const priceA = a.attributes.price | ||||
| @ -269,7 +275,14 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|           className="mb-6 w-full" | ||||
|           placeholder={langui.search_title ?? "Search..."} | ||||
|           value={searchName} | ||||
|           onChange={setSearchName} | ||||
|           onChange={(name) => { | ||||
|             setSearchName(name); | ||||
|             if (isDefinedAndNotEmpty(name)) { | ||||
|               umami("[Library] Change search term"); | ||||
|             } else { | ||||
|               umami("[Library] Clear search term"); | ||||
|             } | ||||
|           }} | ||||
|         /> | ||||
| 
 | ||||
|         <WithLabel label={langui.group_by}> | ||||
| @ -281,7 +294,14 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|               langui.release_year ?? "Year", | ||||
|             ]} | ||||
|             value={groupingMethod} | ||||
|             onChange={setGroupingMethod} | ||||
|             onChange={(value) => { | ||||
|               setGroupingMethod(value); | ||||
|               umami( | ||||
|                 `[Library] Change grouping method (${ | ||||
|                   ["none", "category", "type", "year"][value + 1] | ||||
|                 })` | ||||
|               ); | ||||
|             }} | ||||
|             allowEmpty | ||||
|           /> | ||||
|         </WithLabel> | ||||
| @ -295,28 +315,64 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|               langui.release_date ?? "Release date", | ||||
|             ]} | ||||
|             value={sortingMethod} | ||||
|             onChange={setSortingMethod} | ||||
|             onChange={(value) => { | ||||
|               setSortingMethod(value); | ||||
|               umami( | ||||
|                 `[Library] Change sorting method (${ | ||||
|                   ["name", "price", "release date"][value] | ||||
|                 })` | ||||
|               ); | ||||
|             }} | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         <WithLabel label={langui.show_subitems}> | ||||
|           <Switch value={showSubitems} onClick={toggleShowSubitems} /> | ||||
|           <Switch | ||||
|             value={showSubitems} | ||||
|             onClick={() => { | ||||
|               toggleShowSubitems(); | ||||
|               umami(`[Library] ${showSubitems ? "Hide" : "Show"} subitems`); | ||||
|             }} | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         <WithLabel label={langui.show_primary_items}> | ||||
|           <Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} /> | ||||
|           <Switch | ||||
|             value={showPrimaryItems} | ||||
|             onClick={() => { | ||||
|               toggleShowPrimaryItems(); | ||||
|               umami( | ||||
|                 `[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items` | ||||
|               ); | ||||
|             }} | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         <WithLabel label={langui.show_secondary_items}> | ||||
|           <Switch | ||||
|             value={showSecondaryItems} | ||||
|             onClick={toggleShowSecondaryItems} | ||||
|             onClick={() => { | ||||
|               toggleShowSecondaryItems(); | ||||
|               umami( | ||||
|                 `[Library] ${ | ||||
|                   showSecondaryItems ? "Hide" : "Show" | ||||
|                 } secondary items` | ||||
|               ); | ||||
|             }} | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         {hoverable && ( | ||||
|           <WithLabel label={langui.always_show_info}> | ||||
|             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||
|             <Switch | ||||
|               value={keepInfoVisible} | ||||
|               onClick={() => { | ||||
|                 toggleKeepInfoVisible(); | ||||
|                 umami( | ||||
|                   `[Library] Always ${keepInfoVisible ? "hide" : "show"} info` | ||||
|                 ); | ||||
|               }} | ||||
|             /> | ||||
|           </WithLabel> | ||||
|         )} | ||||
| 
 | ||||
| @ -326,25 +382,37 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|             { | ||||
|               tooltip: langui.only_display_items_i_want, | ||||
|               icon: Icon.Favorite, | ||||
|               onClick: () => setFilterUserStatus(LibraryItemUserStatus.Want), | ||||
|               onClick: () => { | ||||
|                 setFilterUserStatus(LibraryItemUserStatus.Want); | ||||
|                 umami("[Library] Set filter status (I want)"); | ||||
|               }, | ||||
|               active: filterUserStatus === LibraryItemUserStatus.Want, | ||||
|             }, | ||||
|             { | ||||
|               tooltip: langui.only_display_items_i_have, | ||||
|               icon: Icon.BackHand, | ||||
|               onClick: () => setFilterUserStatus(LibraryItemUserStatus.Have), | ||||
|               onClick: () => { | ||||
|                 setFilterUserStatus(LibraryItemUserStatus.Have); | ||||
|                 umami("[Library] Set filter status (I have)"); | ||||
|               }, | ||||
|               active: filterUserStatus === LibraryItemUserStatus.Have, | ||||
|             }, | ||||
|             { | ||||
|               tooltip: langui.only_display_unmarked_items, | ||||
|               icon: Icon.RadioButtonUnchecked, | ||||
|               onClick: () => setFilterUserStatus(LibraryItemUserStatus.None), | ||||
|               onClick: () => { | ||||
|                 setFilterUserStatus(LibraryItemUserStatus.None); | ||||
|                 umami("[Library] Set filter status (unmarked)"); | ||||
|               }, | ||||
|               active: filterUserStatus === LibraryItemUserStatus.None, | ||||
|             }, | ||||
|             { | ||||
|               tooltip: langui.only_display_unmarked_items, | ||||
|               text: langui.all, | ||||
|               onClick: () => setFilterUserStatus(undefined), | ||||
|               onClick: () => { | ||||
|                 setFilterUserStatus(undefined); | ||||
|                 umami("[Library] Set filter status (all)"); | ||||
|               }, | ||||
|               active: isUndefined(filterUserStatus), | ||||
|             }, | ||||
|           ]} | ||||
| @ -363,6 +431,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | ||||
|             setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             setFilterUserStatus(DEFAULT_FILTERS_STATE.filterUserStatus); | ||||
|             umami("[Library] Reset all filters"); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
|  | ||||
| @ -17,7 +17,7 @@ import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { compareDate } from "helpers/date"; | ||||
| @ -75,12 +75,27 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => { | ||||
|           className="mb-6 w-full" | ||||
|           placeholder={langui.search_title ?? "Search..."} | ||||
|           value={searchName} | ||||
|           onChange={setSearchName} | ||||
|           onChange={(name) => { | ||||
|             setSearchName(name); | ||||
|             if (isDefinedAndNotEmpty(name)) { | ||||
|               umami("[News] Change search term"); | ||||
|             } else { | ||||
|               umami("[News] Clear search term"); | ||||
|             } | ||||
|           }} | ||||
|         /> | ||||
| 
 | ||||
|         {hoverable && ( | ||||
|           <WithLabel label={langui.always_show_info}> | ||||
|             <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> | ||||
|             <Switch | ||||
|               value={keepInfoVisible} | ||||
|               onClick={() => { | ||||
|                 toggleKeepInfoVisible(); | ||||
|                 umami( | ||||
|                   `[News] Always ${keepInfoVisible ? "hide" : "show"} info` | ||||
|                 ); | ||||
|               }} | ||||
|             /> | ||||
|           </WithLabel> | ||||
|         )} | ||||
| 
 | ||||
| @ -91,6 +106,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => { | ||||
|           onClick={() => { | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             umami("[News] Reset all filters"); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
|  | ||||
| @ -18,7 +18,11 @@ import { Switch } from "components/Inputs/Switch"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { filterDefined, filterHasAttributes } from "helpers/others"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/others"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| import { Select } from "components/Inputs/Select"; | ||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||
| @ -84,7 +88,14 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||
|           className="mb-6 w-full" | ||||
|           placeholder={langui.search_title ?? "Search..."} | ||||
|           value={searchName} | ||||
|           onChange={setSearchName} | ||||
|           onChange={(name) => { | ||||
|             setSearchName(name); | ||||
|             if (isDefinedAndNotEmpty(name)) { | ||||
|               umami("[Wiki] Change search term"); | ||||
|             } else { | ||||
|               umami("[Wiki] Clear search term"); | ||||
|             } | ||||
|           }} | ||||
|         /> | ||||
| 
 | ||||
|         <WithLabel label={langui.group_by}> | ||||
| @ -92,14 +103,29 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||
|             className="w-full" | ||||
|             options={[langui.category ?? "Category"]} | ||||
|             value={groupingMethod} | ||||
|             onChange={setGroupingMethod} | ||||
|             onChange={(value) => { | ||||
|               setGroupingMethod(value); | ||||
|               umami( | ||||
|                 `[Wiki] Change grouping method (${ | ||||
|                   ["none", "category"][value + 1] | ||||
|                 })` | ||||
|               ); | ||||
|             }} | ||||
|             allowEmpty | ||||
|           /> | ||||
|         </WithLabel> | ||||
| 
 | ||||
|         {hoverable && ( | ||||
|           <WithLabel label={langui.always_show_info}> | ||||
|             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||
|             <Switch | ||||
|               value={keepInfoVisible} | ||||
|               onClick={() => { | ||||
|                 toggleKeepInfoVisible(); | ||||
|                 umami( | ||||
|                   `[Wiki] Always ${keepInfoVisible ? "hide" : "show"} info` | ||||
|                 ); | ||||
|               }} | ||||
|             /> | ||||
|           </WithLabel> | ||||
|         )} | ||||
| 
 | ||||
| @ -109,7 +135,9 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||
|           icon={Icon.Replay} | ||||
|           onClick={() => { | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             umami("[Wiki] Reset all filters"); | ||||
|           }} | ||||
|         /> | ||||
| 
 | ||||
|  | ||||
| @ -164,7 +164,7 @@ input[type="submit"] { | ||||
|   @apply grid cursor-pointer place-content-center place-items-center rounded-full border-[1px] | ||||
|   border-dark px-4 pt-[0.4rem] pb-[0.5rem] text-dark transition-all hover:bg-dark hover:text-light | ||||
|   hover:drop-shadow-shade-lg active:border-black active:bg-black active:text-light | ||||
|   active:drop-shadow-black-lg; | ||||
|   active:drop-shadow-black-lg outline-none; | ||||
| } | ||||
| 
 | ||||
| .texture-paper-dots { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint