summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/EditApiAccessMethod.tsx86
-rw-r--r--gui/src/renderer/components/FormattableTextInput.tsx1
-rw-r--r--gui/src/renderer/components/List.tsx8
-rw-r--r--gui/src/renderer/components/Map.tsx4
-rw-r--r--gui/src/renderer/components/NavigationBar.tsx5
-rw-r--r--gui/src/renderer/components/NotificationBanner.tsx17
-rw-r--r--gui/src/renderer/components/cell/Input.tsx1
-rw-r--r--gui/src/renderer/components/cell/Section.tsx2
-rw-r--r--gui/src/renderer/components/main-view/FeatureIndicators.tsx1
-rw-r--r--gui/src/renderer/lib/history.tsx8
-rw-r--r--gui/src/renderer/lib/utilityHooks.ts12
-rw-r--r--gui/src/renderer/redux/store.ts2
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;
}