mirror of
https://github.com/decompme/decomp.me.git
synced 2026-05-20 15:20:43 -05:00
React error fixes (#435)
* Use keys for FormatDiffText * Fix React errors due to unused useCombobox props * Use <select value> instead of <option selected>
This commit is contained in:
@@ -12,6 +12,7 @@ sandbox/
|
||||
.DS_Store
|
||||
.env.*
|
||||
*.log
|
||||
/frontend/*.tsbuildinfo
|
||||
/frontend/.next
|
||||
/frontend/.cache
|
||||
/frontend/cache
|
||||
|
||||
@@ -21,13 +21,13 @@ const SelectedSourceLineContext = createContext<number | null>(null)
|
||||
|
||||
function FormatDiffText({ texts }: { texts: api.DiffText[] }) {
|
||||
return <> {
|
||||
texts.map(t => {
|
||||
texts.map((t, i) => {
|
||||
if (t.format == "rotation") {
|
||||
return <span className={styles[`rotation${t.index % 9}`]}>{t.text}</span>
|
||||
return <span key={i} className={styles[`rotation${t.index % 9}`]}>{t.text}</span>
|
||||
} else if (t.format) {
|
||||
return <span className={styles[t.format]}>{t.text}</span>
|
||||
return <span key={i} className={styles[t.format]}>{t.text}</span>
|
||||
} else {
|
||||
return <span>{t.text}</span>
|
||||
return <span key={i}>{t.text}</span>
|
||||
}
|
||||
})
|
||||
} </>
|
||||
|
||||
@@ -85,6 +85,18 @@
|
||||
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
opacity: 0;
|
||||
transform: scaleY(0.75);
|
||||
visibility: hidden;
|
||||
transition-duration: 0.125s;
|
||||
transition-property: opacity, transform, visibility;
|
||||
}
|
||||
|
||||
.results.isOpen {
|
||||
opacity: 0.9999;
|
||||
transform: none;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.item {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import Image from "next/image"
|
||||
import { useRouter } from "next/router"
|
||||
@@ -6,7 +6,6 @@ import { useRouter } from "next/router"
|
||||
import { SearchIcon } from "@primer/octicons-react"
|
||||
import classNames from "classnames"
|
||||
import { useCombobox } from "downshift"
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import { usePlausible } from "next-plausible"
|
||||
import { useLayer } from "react-laag"
|
||||
import useSWR from "swr"
|
||||
@@ -27,7 +26,7 @@ function useRecentScratches(): api.TerseScratch[] {
|
||||
return data?.results || []
|
||||
}
|
||||
|
||||
export default function Search({ className }: { className?: string }) {
|
||||
function MountedSearch({ className }: { className?: string }) {
|
||||
const [query, setQuery] = useState("")
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@@ -102,21 +101,11 @@ export default function Search({ className }: { className?: string }) {
|
||||
},
|
||||
})
|
||||
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
useEffect(() => setIsMounted(true), [])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
if (!isMounted) {
|
||||
return <div className={classNames(styles.container, className)}>
|
||||
<SearchIcon className={styles.icon} />
|
||||
<input
|
||||
className={styles.input}
|
||||
type="text"
|
||||
placeholder="Search decomp.me..."
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
const lastWidthRef = useRef(0)
|
||||
if (triggerBounds) {
|
||||
lastWidthRef.current = triggerBounds.width
|
||||
}
|
||||
|
||||
return <div
|
||||
@@ -149,33 +138,32 @@ export default function Search({ className }: { className?: string }) {
|
||||
onClick={() => setIsFocused(true)}
|
||||
/>
|
||||
{isLoading && isFocused && <LoadingSpinner className={styles.loadingIcon} />}
|
||||
{isMounted && renderLayer(
|
||||
<AnimatePresence>
|
||||
{isOpen && <motion.ul
|
||||
{...getMenuProps(layerProps)}
|
||||
className={classNames(verticalMenuStyles.menu, styles.results)}
|
||||
style={{
|
||||
width: triggerBounds.width,
|
||||
...layerProps.style,
|
||||
}}
|
||||
initial={{ opacity: 0, scaleY: 0.75 }}
|
||||
animate={{ opacity: 1, scaleY: 1 }}
|
||||
exit={{ opacity: 0, scaleY: 0.75 }}
|
||||
transition={{ duration: 0.125 }}
|
||||
>
|
||||
{items.map((scratch, index) => {
|
||||
const props = getItemProps({ item: scratch, index })
|
||||
const oldOnClick = props.onClick
|
||||
props.onClick = evt => {
|
||||
evt.preventDefault() // Don't visit the link
|
||||
return oldOnClick(evt)
|
||||
}
|
||||
{renderLayer(
|
||||
<ul
|
||||
{...getMenuProps(layerProps)}
|
||||
className={classNames(verticalMenuStyles.menu, styles.results, {
|
||||
[styles.isOpen]: isOpen,
|
||||
})}
|
||||
style={{
|
||||
width: lastWidthRef.current,
|
||||
...layerProps.style,
|
||||
}}
|
||||
>
|
||||
{items.map((scratch, index) => {
|
||||
const props = getItemProps({ item: scratch, index })
|
||||
const oldOnClick = props.onClick
|
||||
props.onClick = evt => {
|
||||
evt.preventDefault() // Don't visit the link
|
||||
return oldOnClick(evt)
|
||||
}
|
||||
|
||||
return <a
|
||||
key={scratch.url}
|
||||
return <li
|
||||
key={scratch.url}
|
||||
{...props}
|
||||
>
|
||||
<a
|
||||
href={scratch.html_url}
|
||||
className={classNames(verticalMenuStyles.item, styles.item)}
|
||||
{...props}
|
||||
>
|
||||
<ScratchIcon scratch={scratch} size={16} />
|
||||
<span className={styles.itemName}>
|
||||
@@ -189,12 +177,33 @@ export default function Search({ className }: { className?: string }) {
|
||||
className={styles.scratchOwnerAvatar}
|
||||
/>}
|
||||
</a>
|
||||
})}
|
||||
{items.length === 0 && <div className={classNames(verticalMenuStyles.item, styles.noResults)}>
|
||||
</li>
|
||||
})}
|
||||
{items.length === 0 && <li>
|
||||
<div className={classNames(verticalMenuStyles.item, styles.noResults)}>
|
||||
No search results
|
||||
</div>}
|
||||
</motion.ul>}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</li>}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default function Search({ className }: { className?: string }) {
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
useEffect(() => setIsMounted(true), [])
|
||||
|
||||
if (!isMounted) {
|
||||
return <div className={classNames(styles.container, className)}>
|
||||
<SearchIcon className={styles.icon} />
|
||||
<input
|
||||
className={styles.input}
|
||||
type="text"
|
||||
placeholder="Search decomp.me..."
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <MountedSearch className={className} />
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ export type Props = {
|
||||
className?: string
|
||||
onChange: ChangeEventHandler<HTMLSelectElement>
|
||||
children: ReactNode
|
||||
value?: string
|
||||
}
|
||||
|
||||
export default function Select({ onChange, children, className }: Props) {
|
||||
export default function Select({ onChange, children, className, value }: Props) {
|
||||
return <div className={`${styles.group} ${className}`}>
|
||||
<select onChange={onChange}>
|
||||
<select onChange={onChange} value={value}>
|
||||
{children}
|
||||
</select>
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ import Select from "../Select"
|
||||
|
||||
import styles from "./CompilerOpts.module.css"
|
||||
import { useCompilersForPlatform } from "./compilers"
|
||||
import Flags, { NO_TRANSLATION } from "./Flags"
|
||||
import PresetSelect from "./PresetSelect"
|
||||
|
||||
const NO_TRANSLATION = "NO_TRANSLATION"
|
||||
|
||||
interface IOptsContext {
|
||||
checkFlag(flag: string): boolean
|
||||
setFlag(flag: string, value: boolean): void
|
||||
@@ -18,7 +19,7 @@ interface IOptsContext {
|
||||
|
||||
const OptsContext = createContext<IOptsContext>(undefined)
|
||||
|
||||
export function Checkbox({ flag, description }) {
|
||||
function Checkbox({ flag, description }) {
|
||||
const { checkFlag, setFlag } = useContext(OptsContext)
|
||||
|
||||
const isChecked = checkFlag(flag)
|
||||
@@ -30,7 +31,7 @@ export function Checkbox({ flag, description }) {
|
||||
</div>
|
||||
}
|
||||
|
||||
export function FlagSet({ name, children }) {
|
||||
function FlagSet({ name, children, value }) {
|
||||
const { setFlag } = useContext(OptsContext)
|
||||
|
||||
return <div className={styles.flagSet}>
|
||||
@@ -43,23 +44,43 @@ export function FlagSet({ name, children }) {
|
||||
|
||||
setFlag((event.target as HTMLSelectElement).value, true)
|
||||
}}
|
||||
value={value}
|
||||
>
|
||||
{children}
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function FlagOption({ flag, description }: { flag: string, description?: string }) {
|
||||
const { checkFlag } = useContext(OptsContext)
|
||||
|
||||
return <option
|
||||
value={flag}
|
||||
selected={checkFlag(flag)}
|
||||
>
|
||||
function FlagOption({ flag, description }: { flag: string, description?: string }) {
|
||||
return <option value={flag}>
|
||||
{flag} {description && description !== NO_TRANSLATION && `(${description})`}
|
||||
</option>
|
||||
}
|
||||
|
||||
interface FlagsProps {
|
||||
schema: api.Flag[]
|
||||
}
|
||||
|
||||
function Flags({ schema }: FlagsProps) {
|
||||
const compilersTranslation = useTranslation("compilers")
|
||||
const { checkFlag } = useContext(OptsContext)
|
||||
|
||||
return <>
|
||||
{schema.map(flag => {
|
||||
if (flag.type === "checkbox") {
|
||||
return <Checkbox key={flag.id} flag={flag.flag} description={compilersTranslation.t(flag.id)} />
|
||||
} else if (flag.type === "flagset") {
|
||||
const selectedFlag = flag.flags.filter(checkFlag)[0] || flag.flags[0]
|
||||
return <FlagSet key={flag.id} name={compilersTranslation.t(flag.id)} value={selectedFlag}>
|
||||
{flag.flags.map(f => <FlagOption key={f} flag={f} description={
|
||||
compilersTranslation.t(flag.id + "." + f, null, { default: NO_TRANSLATION })
|
||||
} />)}
|
||||
</FlagSet>
|
||||
}
|
||||
})}
|
||||
</>
|
||||
}
|
||||
|
||||
export type CompilerOptsT = {
|
||||
compiler: string
|
||||
compiler_flags: string
|
||||
@@ -188,11 +209,11 @@ export function OptsEditor({ platform, compiler: compilerId, setCompiler, opts,
|
||||
<Select
|
||||
className={styles.compilerSelect}
|
||||
onChange={e => setCompiler((e.target as HTMLSelectElement).value)}
|
||||
value={compilerId}
|
||||
>
|
||||
{Object.keys(compilers).map(id => <option
|
||||
key={id}
|
||||
value={id}
|
||||
selected={id === compilerId}
|
||||
>
|
||||
{compilersTranslation.t(id)}
|
||||
</option>)}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import useTranslation from "next-translate/useTranslation"
|
||||
|
||||
import * as api from "../../lib/api"
|
||||
|
||||
import { Checkbox, FlagSet, FlagOption } from "./CompilerOpts"
|
||||
|
||||
export const NO_TRANSLATION = "NO_TRANSLATION"
|
||||
|
||||
export interface Props {
|
||||
schema: api.Flag[]
|
||||
}
|
||||
|
||||
export default function Flags({ schema }: Props) {
|
||||
const compilersTranslation = useTranslation("compilers")
|
||||
|
||||
return <>
|
||||
{schema.map(flag => {
|
||||
if (flag.type === "checkbox") {
|
||||
return <Checkbox key={flag.id} flag={flag.flag} description={compilersTranslation.t(flag.id)} />
|
||||
} else if (flag.type === "flagset") {
|
||||
return <FlagSet key={flag.id} name={compilersTranslation.t(flag.id)}>
|
||||
{flag.flags.map(f => <FlagOption key={f} flag={f} description={
|
||||
compilersTranslation.t(flag.id + "." + f, null, { default: NO_TRANSLATION })
|
||||
} />)}
|
||||
</FlagSet>
|
||||
}
|
||||
})}
|
||||
</>
|
||||
}
|
||||
Reference in New Issue
Block a user