diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/EditApiAccessMethod.tsx | 86 | ||||
| -rw-r--r-- | gui/src/renderer/components/FormattableTextInput.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/components/List.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/components/Map.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/NavigationBar.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationBanner.tsx | 17 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Input.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Section.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/main-view/FeatureIndicators.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/lib/history.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/lib/utilityHooks.ts | 12 | ||||
| -rw-r--r-- | gui/src/renderer/redux/store.ts | 2 |
12 files changed, 88 insertions, 59 deletions
diff --git a/gui/src/renderer/components/EditApiAccessMethod.tsx b/gui/src/renderer/components/EditApiAccessMethod.tsx index 44d86969ab..470f34a064 100644 --- a/gui/src/renderer/components/EditApiAccessMethod.tsx +++ b/gui/src/renderer/components/EditApiAccessMethod.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useState } from 'react'; import { useParams } from 'react-router'; import { sprintf } from 'sprintf-js'; @@ -12,7 +12,7 @@ import { useScheduler } from '../../shared/scheduler'; import { useAppContext } from '../context'; import { useApiAccessMethodTest } from '../lib/api-access-methods'; import { useHistory } from '../lib/history'; -import { useEffectEvent } from '../lib/utilityHooks'; +import { useLastDefinedValue } from '../lib/utilityHooks'; import { useSelector } from '../redux/store'; import { SettingsForm } from './cell/SettingsForm'; import { BackAction } from './KeyboardNavigation'; @@ -47,35 +47,47 @@ function AccessMethodForm() { const { id } = useParams<{ id: string | undefined }>(); const method = methods.find((method) => method.id === id); - const updatedMethod = useRef<NewAccessMethodSetting<CustomProxy> | undefined>(method); + const [updatedMethod, setUpdatedMethod] = useState< + NewAccessMethodSetting<CustomProxy> | undefined + >(method); - const save = useCallback(() => { - if (updatedMethod.current !== undefined) { - resetTestResult(); - if (id === undefined) { - void addApiAccessMethod(updatedMethod.current); - } else { - void updateApiAccessMethod({ ...updatedMethod.current, id }); + const save = useCallback( + (method: NewAccessMethodSetting<CustomProxy>) => { + if (method !== undefined) { + resetTestResult(); + if (id === undefined) { + void addApiAccessMethod(method); + } else { + void updateApiAccessMethod({ ...method, id }); + } + pop(); } - pop(); - } - }, [resetTestResult, id, pop, addApiAccessMethod, updateApiAccessMethod]); + }, + [resetTestResult, id, pop, addApiAccessMethod, updateApiAccessMethod], + ); const onSave = useCallback( async (newMethod: NamedCustomProxy) => { const enabled = id === undefined ? true : (method?.enabled ?? true); - updatedMethod.current = { ...newMethod, enabled }; + const updatedMethod = { ...newMethod, enabled }; + setUpdatedMethod(updatedMethod); if ( - updatedMethod.current !== undefined && - (await testApiAccessMethod(updatedMethod.current as CustomProxy)) + updatedMethod !== undefined && + (await testApiAccessMethod(updatedMethod as CustomProxy)) ) { // Hide the save dialog after 1.5 seconds. - saveScheduler.schedule(save, 1500); + saveScheduler.schedule(() => save(updatedMethod), 1500); } }, [id, method?.enabled, testApiAccessMethod, saveScheduler, save], ); + const handleDialogSave = useCallback(() => { + if (updatedMethod !== undefined) { + save(updatedMethod); + } + }, [save, updatedMethod]); + const title = getTitle(id === undefined); const subtitle = getSubtitle(id === undefined); @@ -106,12 +118,12 @@ function AccessMethodForm() { </StyledSettingsContent> <TestingDialog - name={updatedMethod.current?.name ?? ''} + name={updatedMethod?.name ?? ''} newMethod={id === undefined} testing={testing} testResult={testResult} cancel={resetTestResult} - save={save} + save={handleDialogSave} /> </StyledContent> </StyledNavigationScrollbars> @@ -144,32 +156,26 @@ interface TestingDialogProps { } function TestingDialog(props: TestingDialogProps) { - const type = props.testing - ? ModalAlertType.loading - : props.testResult - ? ModalAlertType.success - : ModalAlertType.failure; - const prevType = useRef<ModalAlertType>(type); - - const isOpen = props.testing || props.testResult !== undefined; - const typeValue = isOpen ? type : prevType.current; - - const typeChangeEvent = useEffectEvent((type: ModalAlertType) => { - if (isOpen) { - prevType.current = type; - } - }); + let currentType: ModalAlertType | undefined; + if (props.testing) { + currentType = ModalAlertType.loading; + } else if (props.testResult) { + currentType = ModalAlertType.success; + } else if (props.testResult === false) { + currentType = ModalAlertType.failure; + } - useEffect(() => typeChangeEvent(type), [type]); + const type = useLastDefinedValue(currentType); + const displayType = type ?? ModalAlertType.failure; return ( <ModalAlert - isOpen={isOpen} - type={typeValue} - gridButtons={getTestingDialogButtons(typeValue, props.save, props.cancel)} + isOpen={!!currentType} + type={type} + gridButtons={getTestingDialogButtons(displayType, props.save, props.cancel)} close={props.cancel} - title={getTestingDialogTitle(typeValue, props.newMethod)} - message={getTestingDialogSubTitle(typeValue, props.newMethod, props.name)} + title={getTestingDialogTitle(displayType, props.newMethod)} + message={getTestingDialogSubTitle(displayType, props.newMethod, props.name)} /> ); } diff --git a/gui/src/renderer/components/FormattableTextInput.tsx b/gui/src/renderer/components/FormattableTextInput.tsx index 27e28d27b5..dd78545966 100644 --- a/gui/src/renderer/components/FormattableTextInput.tsx +++ b/gui/src/renderer/components/FormattableTextInput.tsx @@ -124,6 +124,7 @@ function FormattableTextInput( // Use value provided in props if it differs from current input value. useEffect(() => { if (typeof value === 'string' && ref.current && unformat(ref.current.value) !== value) { + // eslint-disable-next-line react-compiler/react-compiler ref.current.value = format(value, addTrailingSeparator); } }, [format, value, addTrailingSeparator, ref, unformat]); diff --git a/gui/src/renderer/components/List.tsx b/gui/src/renderer/components/List.tsx index 259136b7ee..ce38ed293b 100644 --- a/gui/src/renderer/components/List.tsx +++ b/gui/src/renderer/components/List.tsx @@ -36,7 +36,9 @@ export default function List<T>(props: ListProps<T>) { convertToRowDisplayData(props.items, props.getKey), ); // Skip add transition on first render when initial items are added. - const skipAddTransition = useRef(props.skipInitialAddTransition ?? false); + const [skipAddTransition, setSkipAddTransition] = useState( + props.skipInitialAddTransition ?? false, + ); const removeFallbackSchedulers = useRef<Record<string, Scheduler>>({}); @@ -56,7 +58,7 @@ export default function List<T>(props: ListProps<T>) { useEffect(() => { // Set to animate accordion for added items after first render unless // props.skipAddTransition === true. - skipAddTransition.current = props.skipAddTransition ?? false; + setSkipAddTransition(props.skipAddTransition ?? false); }, [props.skipAddTransition]); const onRemoved = useCallback((key: string) => { @@ -95,7 +97,7 @@ export default function List<T>(props: ListProps<T>) { data={displayItem} onRemoved={onRemoved} render={props.children} - skipAddTransition={skipAddTransition.current} + skipAddTransition={skipAddTransition} /> ))} </> diff --git a/gui/src/renderer/components/Map.tsx b/gui/src/renderer/components/Map.tsx index c2227abaf0..8b3f66b50e 100644 --- a/gui/src/renderer/components/Map.tsx +++ b/gui/src/renderer/components/Map.tsx @@ -34,6 +34,7 @@ export default function Map() { const hasLocationValue = hasLocation(connection); const location = useMemo<Coordinate | undefined>(() => { return hasLocationValue ? connection : defaultLocation; + // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasLocationValue, connection.longitude, connection.latitude]); @@ -89,8 +90,11 @@ function MapInner(props: MapInnerProps) { const mapRef = useRef<GlMap>(); const canvasRef = useRef<HTMLCanvasElement>(); + // eslint-disable-next-line react-compiler/react-compiler const width = applyPixelRatio(canvasRef.current?.clientWidth ?? window.innerWidth); + // This constant is used for the height the first frame that is rendered only. + // eslint-disable-next-line react-compiler/react-compiler const height = applyPixelRatio(canvasRef.current?.clientHeight ?? 493); // Hack to rerender when window size changes or when ref is set. diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx index e4d5777aa4..48a8a3d08d 100644 --- a/gui/src/renderer/components/NavigationBar.tsx +++ b/gui/src/renderer/components/NavigationBar.tsx @@ -99,7 +99,7 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba const beforeunload = useEffectEvent(() => { if (ref.current) { - history.location.state.scrollPosition = ref.current.getScrollPosition(); + history.recordScrollPosition(ref.current.getScrollPosition()); setNavigationHistory(history.asObject); } }); @@ -117,9 +117,8 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba }); const onUnmount = useEffectEvent(() => { - const location = history.location; if (history.action === 'PUSH' && ref.current) { - location.state.scrollPosition = ref.current.getScrollPosition(); + history.recordScrollPosition(ref.current.getScrollPosition()); setNavigationHistory(history.asObject); } }); diff --git a/gui/src/renderer/components/NotificationBanner.tsx b/gui/src/renderer/components/NotificationBanner.tsx index 50a9dbcff2..d5cedbb270 100644 --- a/gui/src/renderer/components/NotificationBanner.tsx +++ b/gui/src/renderer/components/NotificationBanner.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; import { InAppNotificationIndicatorType } from '../../shared/notifications/notification'; -import { useStyledRef } from '../lib/utilityHooks'; +import { useEffectEvent, useLastDefinedValue, useStyledRef } from '../lib/utilityHooks'; import * as AppButton from './AppButton'; import { tinyText } from './common-styles'; import ImageView from './ImageView'; @@ -159,14 +159,9 @@ export function NotificationBanner(props: INotificationBannerProps) { const contentRef = useStyledRef<HTMLDivElement>(); - // Save last non-undefined children to be able to show them during the hide-transition. - const prevChildren = useRef<React.ReactNode>(); - useEffect(() => { - prevChildren.current = props.children ?? prevChildren.current; - }, [props.children]); + const children = useLastDefinedValue(props.children); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => { + const updateHeightEvent = useEffectEvent(() => { const newHeight = props.children !== undefined ? (contentRef.current?.getBoundingClientRect().height ?? 0) : 0; if (newHeight !== contentHeight) { @@ -175,9 +170,11 @@ export function NotificationBanner(props: INotificationBannerProps) { } }); + useEffect(() => updateHeightEvent()); + return ( <Collapsible $height={contentHeight} className={props.className} $alignBottom={alignBottom}> - <Content ref={contentRef}>{props.children ?? prevChildren.current}</Content> + <Content ref={contentRef}>{children}</Content> </Collapsible> ); } diff --git a/gui/src/renderer/components/cell/Input.tsx b/gui/src/renderer/components/cell/Input.tsx index c87994b516..9ff4994e42 100644 --- a/gui/src/renderer/components/cell/Input.tsx +++ b/gui/src/renderer/components/cell/Input.tsx @@ -355,6 +355,7 @@ export function RowInput(props: IRowInputProps) { const input = textAreaRef.current; if (input) { input.focus(); + // eslint-disable-next-line react-compiler/react-compiler input.selectionStart = input.selectionEnd = value.length; } }, [textAreaRef, value.length]); diff --git a/gui/src/renderer/components/cell/Section.tsx b/gui/src/renderer/components/cell/Section.tsx index 3f393c0643..051730e424 100644 --- a/gui/src/renderer/components/cell/Section.tsx +++ b/gui/src/renderer/components/cell/Section.tsx @@ -72,7 +72,7 @@ export function ExpandableSection(props: ExpandableSectionProps) { const [expanded, , , toggleExpanded] = useBoolean(expandedValue); const updateHistory = useEffectEvent((expanded: boolean) => { - history.location.state.expandedSections[props.expandableId] = expanded; + history.recordSectionExpandedState(props.expandableId, expanded); setNavigationHistory(history.asObject); }); diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx index 2d7914fd87..695129e2ac 100644 --- a/gui/src/renderer/components/main-view/FeatureIndicators.tsx +++ b/gui/src/renderer/components/main-view/FeatureIndicators.tsx @@ -169,6 +169,7 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { // Place the ellipsis at the end of the last visible indicator. const left = lastVisibleIndicatorRect.right - containerRect.left; + // eslint-disable-next-line react-compiler/react-compiler ellipsisRef.current.style.left = `${left}px`; ellipsisRef.current.style.visibility = 'visible'; diff --git a/gui/src/renderer/lib/history.tsx b/gui/src/renderer/lib/history.tsx index 741c298da6..6d92a0e88c 100644 --- a/gui/src/renderer/lib/history.tsx +++ b/gui/src/renderer/lib/history.tsx @@ -82,6 +82,14 @@ export default class History { return history; } + public recordScrollPosition(position: [number, number]) { + this.location.state.scrollPosition = position; + } + + public recordSectionExpandedState(id: string, expanded: boolean) { + this.location.state.expandedSections[id] = expanded; + } + public get location(): Location<LocationState> { return this.entries[this.index]; } diff --git a/gui/src/renderer/lib/utilityHooks.ts b/gui/src/renderer/lib/utilityHooks.ts index 1a281d4011..865e078b9e 100644 --- a/gui/src/renderer/lib/utilityHooks.ts +++ b/gui/src/renderer/lib/utilityHooks.ts @@ -17,8 +17,8 @@ export function useMounted() { return isMounted; } -export function useStyledRef<T>(): React.RefObject<T> { - return useRef() as React.RefObject<T>; +export function useStyledRef<T>(): React.MutableRefObject<T> { + return useRef() as React.MutableRefObject<T>; } export function useCombinedRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> { @@ -92,3 +92,11 @@ export function useEffectEvent<Args extends unknown[]>( // Alias for useEffectEvent, but with another name since the effect event is named after a very // specific usecase. export const useRefCallback = useEffectEvent; + +export function useLastDefinedValue<T>(value: T): T { + const [definedValue, setDefinedValue] = useState(value); + + useEffect(() => setDefinedValue((prev) => value ?? prev), [value]); + + return value ?? definedValue; +} diff --git a/gui/src/renderer/redux/store.ts b/gui/src/renderer/redux/store.ts index 1617acce3d..073fe67d30 100644 --- a/gui/src/renderer/redux/store.ts +++ b/gui/src/renderer/redux/store.ts @@ -84,8 +84,10 @@ export function useSelector<R>(fn: (state: IReduxState) => R): R { const willExit = useWillExit(); if (!willExit) { + // eslint-disable-next-line react-compiler/react-compiler valueBeforeExit.current = value; } + // eslint-disable-next-line react-compiler/react-compiler return valueBeforeExit.current; } |
