refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -17,64 +17,55 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import '@app/components/modals/SudoVerificationModal';
import 'highlight.js/styles/github-dark.css';
import 'katex/dist/katex.min.css';
import styles from '@app/App.module.css';
import * as ModalActionCreators from '@app/actions/ModalActionCreators';
import * as WindowActionCreators from '@app/actions/WindowActionCreators';
import Config from '@app/Config';
import {DndContext} from '@app/components/layout/DndContext';
import GlobalOverlays from '@app/components/layout/GlobalOverlays';
import {NativeTitlebar} from '@app/components/layout/NativeTitlebar';
import {NativeTrafficLightsBackdrop} from '@app/components/layout/NativeTrafficLightsBackdrop';
import {UserSettingsModal} from '@app/components/modals/UserSettingsModal';
import {QUICK_SWITCHER_PORTAL_ID} from '@app/components/quick_switcher/QuickSwitcherConstants';
import FocusRingScope from '@app/components/uikit/focus_ring/FocusRingScope';
import {SVGMasks} from '@app/components/uikit/SVGMasks';
import {IncomingCallManager} from '@app/components/voice/IncomingCallManager';
import {type LayoutVariant, LayoutVariantProvider} from '@app/contexts/LayoutVariantContext';
import {showMyselfTypingHelper} from '@app/devtools/ShowMyselfTypingHelper';
import {useActivityRecorder} from '@app/hooks/useActivityRecorder';
import {useElectronScreenSharePicker} from '@app/hooks/useElectronScreenSharePicker';
import {useNativePlatform} from '@app/hooks/useNativePlatform';
import {useTextInputContextMenu} from '@app/hooks/useTextInputContextMenu';
import CaptchaInterceptorStore from '@app/lib/CaptchaInterceptor';
import FocusManager from '@app/lib/FocusManager';
import KeybindManager from '@app/lib/KeybindManager';
import {Logger} from '@app/lib/Logger';
import {startReadStateCleanup} from '@app/lib/ReadStateCleanup';
import {Outlet, RouterProvider} from '@app/lib/router/React';
import {router} from '@app/Router';
import AccessibilityStore, {HdrDisplayMode} from '@app/stores/AccessibilityStore';
import ModalStore from '@app/stores/ModalStore';
import PopoutStore from '@app/stores/PopoutStore';
import ReadStateStore from '@app/stores/ReadStateStore';
import RuntimeCrashStore from '@app/stores/RuntimeCrashStore';
import ThemeStore from '@app/stores/ThemeStore';
import UserStore from '@app/stores/UserStore';
import MediaEngineStore from '@app/stores/voice/MediaEngineFacade';
import {ensureAutostartDefaultEnabled} from '@app/utils/AutostartUtils';
import {startDeepLinkHandling} from '@app/utils/DeepLinkUtils';
import {attachExternalLinkInterceptor, getElectronAPI, getNativePlatform} from '@app/utils/NativeUtils';
import {i18n} from '@lingui/core';
import {I18nProvider} from '@lingui/react';
import {RoomAudioRenderer, RoomContext} from '@livekit/components-react';
import {IconContext} from '@phosphor-icons/react';
import * as Sentry from '@sentry/react';
import {observer} from 'mobx-react-lite';
import React, {type ReactNode} from 'react';
import React, {type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import * as ModalActionCreators from '~/actions/ModalActionCreators';
import * as WindowActionCreators from '~/actions/WindowActionCreators';
import {DndContext} from '~/components/layout/DndContext';
import GlobalOverlays from '~/components/layout/GlobalOverlays';
import {NativeTitlebar} from '~/components/layout/NativeTitlebar';
import {NativeTrafficLightsBackdrop} from '~/components/layout/NativeTrafficLightsBackdrop';
import {UserSettingsModal} from '~/components/modals/UserSettingsModal';
import '~/components/modals/SudoVerificationModal';
import {QUICK_SWITCHER_PORTAL_ID} from '~/components/quick-switcher/QuickSwitcherConstants';
import FocusRingScope from '~/components/uikit/FocusRing/FocusRingScope';
import {SVGMasks} from '~/components/uikit/SVGMasks';
import {IncomingCallManager} from '~/components/voice/IncomingCallManager';
import {type LayoutVariant, LayoutVariantProvider} from '~/contexts/LayoutVariantContext';
import {showMyselfTypingHelper} from '~/devtools/ShowMyselfTypingHelper';
import {useActivityRecorder} from '~/hooks/useActivityRecorder';
import {useElectronScreenSharePicker} from '~/hooks/useElectronScreenSharePicker';
import {useNativePlatform} from '~/hooks/useNativePlatform';
import {useTextInputContextMenu} from '~/hooks/useTextInputContextMenu';
import CaptchaInterceptorStore from '~/lib/CaptchaInterceptor';
import FocusManager from '~/lib/FocusManager';
import KeybindManager from '~/lib/KeybindManager';
import {startReadStateCleanup} from '~/lib/ReadStateCleanup';
import {Outlet, RouterProvider} from '~/lib/router';
import {router} from '~/router';
import AccessibilityStore from '~/stores/AccessibilityStore';
import ConnectionStore from '~/stores/ConnectionStore';
import ModalStore from '~/stores/ModalStore';
import PopoutStore from '~/stores/PopoutStore';
import ReadStateStore from '~/stores/ReadStateStore';
import UserSettingsStore from '~/stores/UserSettingsStore';
import UserStore from '~/stores/UserStore';
import MediaEngineStore from '~/stores/voice/MediaEngineFacade';
import {ensureAutostartDefaultEnabled} from '~/utils/AutostartUtils';
import {startDeepLinkHandling} from '~/utils/DeepLinkUtils';
import {attachExternalLinkInterceptor, getElectronAPI, getNativePlatform} from '~/utils/NativeUtils';
import styles from './App.module.css';
import Config from './Config';
const logger = new Logger('App');
interface AppWrapperProps {
children: ReactNode;
@@ -88,13 +79,13 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
const messageGutter = AccessibilityStore.messageGutter;
const messageGroupSpacing = AccessibilityStore.messageGroupSpacingValue;
const reducedMotion = AccessibilityStore.useReducedMotion;
const hdrDisplayMode = AccessibilityStore.hdrDisplayMode;
const {platform, isNative, isMacOS} = useNativePlatform();
useElectronScreenSharePicker();
const syncThemeAcrossDevices = AccessibilityStore.syncThemeAcrossDevices;
const localThemeOverride = AccessibilityStore.localThemeOverride;
const customThemeCss = AccessibilityStore.customThemeCss;
const [layoutVariant, setLayoutVariant] = React.useState<LayoutVariant>('app');
const layoutVariantContextValue = React.useMemo(
const effectiveTheme = ThemeStore.effectiveTheme;
const [layoutVariant, setLayoutVariant] = useState<LayoutVariant>('app');
const layoutVariantContextValue = useMemo(
() => ({variant: layoutVariant, setVariant: setLayoutVariant}),
[layoutVariant],
);
@@ -103,24 +94,19 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
const topPopout = popouts.length ? popouts[popouts.length - 1] : null;
const topPopoutRequiresBackdrop = Boolean(topPopout && !topPopout.disableBackdrop);
const userSettings = UserSettingsStore;
const room = MediaEngineStore.room;
const ringsContainerRef = React.useRef<HTMLDivElement>(null);
const overlayScopeRef = React.useRef<HTMLDivElement>(null);
const ringsContainerRef = useRef<HTMLDivElement>(null);
const overlayScopeRef = useRef<HTMLDivElement>(null);
const recordActivity = useActivityRecorder();
const handleUserActivity = React.useCallback(() => recordActivity(), [recordActivity]);
const handleImmediateActivity = React.useCallback(() => recordActivity(true), [recordActivity]);
const handleResize = React.useCallback(() => WindowActionCreators.resized(), []);
const handleUserActivity = useCallback(() => recordActivity(), [recordActivity]);
const handleImmediateActivity = useCallback(() => recordActivity(true), [recordActivity]);
const handleResize = useCallback(() => WindowActionCreators.resized(), []);
useTextInputContextMenu();
const effectiveTheme = React.useMemo(() => {
return UserSettingsStore.getTheme();
}, [userSettings.theme, syncThemeAcrossDevices, localThemeOverride]);
const hasBlockingModal = ModalStore.hasModalOpen();
React.useEffect(() => {
useEffect(() => {
const node = ringsContainerRef.current;
if (!node) return;
@@ -133,16 +119,16 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, [hasBlockingModal, topPopoutRequiresBackdrop]);
React.useEffect(() => {
useEffect(() => {
showMyselfTypingHelper.start();
return () => showMyselfTypingHelper.stop();
}, []);
React.useEffect(() => {
useEffect(() => {
startReadStateCleanup();
}, []);
React.useEffect(() => {
useEffect(() => {
if (!('serviceWorker' in navigator)) {
return;
}
@@ -155,7 +141,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
try {
controller.postMessage({type: 'APP_UPDATE_BADGE', count});
} catch (error) {
console.warn('[Badge] Failed to post badge update to service worker', error);
logger.warn('Failed to post badge update to service worker', error);
}
};
@@ -174,7 +160,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, []);
React.useEffect(() => {
useEffect(() => {
void KeybindManager.init(i18n);
void CaptchaInterceptorStore;
return () => {
@@ -182,7 +168,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, []);
React.useEffect(() => {
useEffect(() => {
void AccessibilityStore.applyStoredZoom();
const electronApi = getElectronAPI();
@@ -203,7 +189,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, []);
React.useEffect(() => {
useEffect(() => {
const root = document.documentElement;
root.classList.toggle('reduced-motion', reducedMotion);
return () => {
@@ -211,12 +197,12 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, [reducedMotion]);
React.useEffect(() => {
useEffect(() => {
if (Config.PUBLIC_BUILD_SHA && Config.PUBLIC_BUILD_TIMESTAMP) {
const buildInfo = Config.PUBLIC_BUILD_NUMBER
? `build ${Config.PUBLIC_BUILD_NUMBER} (${Config.PUBLIC_BUILD_SHA})`
: Config.PUBLIC_BUILD_SHA;
console.info(`[BUILD INFO] ${Config.PUBLIC_PROJECT_ENV} - ${buildInfo} - ${Config.PUBLIC_BUILD_TIMESTAMP}`);
logger.info(`[BUILD INFO] ${Config.PUBLIC_RELEASE_CHANNEL} - ${buildInfo} - ${Config.PUBLIC_BUILD_TIMESTAMP}`);
}
FocusManager.init();
@@ -228,13 +214,13 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
const preventScroll = (event: Event) => event.preventDefault();
const handleBlur = () => {
WindowActionCreators.focus(false);
WindowActionCreators.focused(false);
if (shouldRegisterWindowListeners) {
document.documentElement.classList.remove('window-focused');
}
};
const handleFocus = () => {
WindowActionCreators.focus(true);
WindowActionCreators.focused(true);
if (shouldRegisterWindowListeners) {
document.documentElement.classList.add('window-focused');
}
@@ -256,7 +242,6 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
window.addEventListener('focus', handleFocus);
document.addEventListener('visibilitychange', handleVisibilityChange);
window.addEventListener('mousedown', handleImmediateActivity);
window.addEventListener('mousemove', handleUserActivity);
window.addEventListener('keydown', handleUserActivity);
window.addEventListener('resize', handleResize);
window.addEventListener('touchstart', handleImmediateActivity);
@@ -272,7 +257,6 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
window.removeEventListener('mousedown', handleImmediateActivity);
window.removeEventListener('mousemove', handleUserActivity);
window.removeEventListener('keydown', handleUserActivity);
window.removeEventListener('resize', handleResize);
window.removeEventListener('touchstart', handleImmediateActivity);
@@ -280,9 +264,9 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
document.removeEventListener('touchmove', preventPinchZoom);
}
};
}, [handleImmediateActivity, handleUserActivity, handleResize, isNative]);
}, [handleImmediateActivity, handleResize, isNative]);
React.useEffect(() => {
useEffect(() => {
if (!isNative) {
return;
}
@@ -292,12 +276,12 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
const handleFocus = () => {
updateClass(true);
WindowActionCreators.focus(true);
WindowActionCreators.focused(true);
handleImmediateActivity();
};
const handleBlur = () => {
updateClass(false);
WindowActionCreators.focus(false);
WindowActionCreators.focused(false);
};
const handleVisibilityChange = () => {
WindowActionCreators.visibilityChanged(!document.hidden);
@@ -312,7 +296,6 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
window.addEventListener('blur', handleBlur);
document.addEventListener('visibilitychange', handleVisibilityChange);
window.addEventListener('mousedown', handleImmediateActivity);
window.addEventListener('mousemove', handleUserActivity);
window.addEventListener('keydown', handleUserActivity);
window.addEventListener('resize', handleResize);
window.addEventListener('touchstart', handleImmediateActivity);
@@ -323,16 +306,15 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
window.removeEventListener('blur', handleBlur);
document.removeEventListener('visibilitychange', handleVisibilityChange);
window.removeEventListener('mousedown', handleImmediateActivity);
window.removeEventListener('mousemove', handleUserActivity);
window.removeEventListener('keydown', handleUserActivity);
window.removeEventListener('resize', handleResize);
window.removeEventListener('touchstart', handleImmediateActivity);
document.removeEventListener('touchstart', preventPinchZoom);
document.removeEventListener('touchmove', preventPinchZoom);
};
}, [handleImmediateActivity, handleResize, handleUserActivity, isNative]);
}, [handleImmediateActivity, handleResize, isNative]);
React.useEffect(() => {
useEffect(() => {
const htmlNode = document.documentElement;
const platformClasses = [isNative ? 'platform-native' : 'platform-web', `platform-${platform}`];
@@ -343,48 +325,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
};
}, [isNative, platform]);
React.useEffect(() => {
if (isNative) {
return;
}
const handlePageUnload = () => {
const guildId = MediaEngineStore.guildId;
const connected = MediaEngineStore.connected;
const room = MediaEngineStore.room;
const socket = ConnectionStore.socket;
if (socket && connected && guildId) {
try {
if (room) {
room.disconnect(true);
}
socket.updateVoiceState({
guild_id: guildId,
channel_id: null,
self_mute: true,
self_deaf: true,
self_video: false,
self_stream: false,
connection_id: MediaEngineStore.connectionId ?? null,
});
} catch (error) {
console.error('Failed to send disconnect on page unload:', error);
}
}
};
window.addEventListener('beforeunload', handlePageUnload);
window.addEventListener('pagehide', handlePageUnload);
return () => {
window.removeEventListener('beforeunload', handlePageUnload);
window.removeEventListener('pagehide', handlePageUnload);
};
}, [isNative]);
React.useEffect(() => {
useEffect(() => {
const htmlNode = document.documentElement;
htmlNode.classList.add(`theme-${effectiveTheme}`);
htmlNode.style.setProperty('--saturation-factor', saturationFactor.toString());
@@ -392,6 +333,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
htmlNode.style.setProperty('--font-size', `${fontSize}px`);
htmlNode.style.setProperty('--chat-horizontal-padding', `${messageGutter}px`);
htmlNode.style.setProperty('--message-group-spacing', `${messageGroupSpacing}px`);
htmlNode.style.setProperty('dynamic-range-limit', hdrDisplayMode === HdrDisplayMode.FULL ? 'high' : 'standard');
if (alwaysUnderlineLinks) {
htmlNode.style.setProperty('--link-decoration', 'underline');
@@ -407,6 +349,7 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
htmlNode.style.removeProperty('--font-size');
htmlNode.style.removeProperty('--chat-horizontal-padding');
htmlNode.style.removeProperty('--message-group-spacing');
htmlNode.style.removeProperty('dynamic-range-limit');
};
}, [
effectiveTheme,
@@ -416,9 +359,10 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
fontSize,
messageGutter,
messageGroupSpacing,
hdrDisplayMode,
]);
React.useEffect(() => {
useEffect(() => {
const styleElementId = 'fluxer-custom-theme-style';
const existing = document.getElementById(styleElementId) as HTMLStyleElement | null;
@@ -469,8 +413,13 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
export const App = observer((): React.ReactElement => {
const currentUser = UserStore.currentUser;
const fatalError = RuntimeCrashStore.fatalError;
React.useEffect(() => {
if (fatalError) {
throw fatalError;
}
useEffect(() => {
const initAutostart = async () => {
const platform = await getNativePlatform();
if (platform === 'macos') {
@@ -481,16 +430,16 @@ export const App = observer((): React.ReactElement => {
void initAutostart();
}, []);
React.useEffect(() => {
useEffect(() => {
void startDeepLinkHandling();
}, []);
React.useEffect(() => {
useEffect(() => {
const detach = attachExternalLinkInterceptor();
return () => detach?.();
}, []);
React.useEffect(() => {
useEffect(() => {
if (currentUser) {
Sentry.setUser({
id: currentUser.id,