2026-06-10 15:39:35 -04:00
|
|
|
import React, { useEffect } from 'react';
|
2026-05-21 19:44:51 -04:00
|
|
|
import * as Sentry from '@sentry/react';
|
2026-06-02 15:36:45 -04:00
|
|
|
import { Provider as JotaiProvider, useAtomValue } from 'jotai';
|
2025-09-12 17:22:51 +05:30
|
|
|
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { RouterProvider } from 'react-router-dom';
|
|
|
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
|
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
2024-01-21 23:50:56 +11:00
|
|
|
|
|
|
|
|
import { ClientConfigLoader } from '../components/ClientConfigLoader';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { ClientConfigProvider } from '../hooks/useClientConfig';
|
2024-01-21 23:50:56 +11:00
|
|
|
import { ConfigConfigError, ConfigConfigLoading } from './ConfigConfig';
|
2024-01-24 00:06:55 +11:00
|
|
|
import { FeatureCheck } from './FeatureCheck';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { createRouter } from './Router';
|
|
|
|
|
import { ScreenSizeProvider, useScreenSize } from '../hooks/useScreenSize';
|
2025-09-24 23:35:42 -04:00
|
|
|
import { useCompositionEndTracking } from '../hooks/useComposingCheck';
|
2026-06-02 15:36:45 -04:00
|
|
|
import { settingsAtom } from '../state/settings';
|
2026-06-04 21:32:37 -04:00
|
|
|
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
|
2026-06-10 17:32:38 -04:00
|
|
|
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
|
2026-06-14 00:33:04 -04:00
|
|
|
import { SeasonalEffect } from '../components/seasonal/SeasonalEffect';
|
2026-06-02 15:36:45 -04:00
|
|
|
|
2026-06-10 15:39:35 -04:00
|
|
|
const FONT_MAP: Record<string, string> = {
|
|
|
|
|
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
|
|
|
inter: "'InterVariable', sans-serif",
|
|
|
|
|
'jetbrains-mono': "'JetBrains Mono', monospace",
|
|
|
|
|
'fira-code': "'Fira Code', monospace",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function AppearanceEffects() {
|
|
|
|
|
const settings = useAtomValue(settingsAtom);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const color = settings.mentionHighlightColor;
|
|
|
|
|
if (color) {
|
|
|
|
|
document.body.style.setProperty('--mention-highlight-bg', color);
|
2026-06-18 22:46:19 -04:00
|
|
|
// WCAG 2.1 relative luminance with gamma linearization
|
|
|
|
|
const toLinear = (c: number) => {
|
|
|
|
|
const s = c / 255;
|
|
|
|
|
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
|
|
|
};
|
2026-06-10 15:39:35 -04:00
|
|
|
const r = parseInt(color.slice(1, 3), 16);
|
|
|
|
|
const g = parseInt(color.slice(3, 5), 16);
|
|
|
|
|
const b = parseInt(color.slice(5, 7), 16);
|
2026-06-18 22:46:19 -04:00
|
|
|
const lum = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
|
|
|
document.body.style.setProperty('--mention-highlight-text', lum > 0.179 ? '#000' : '#fff');
|
|
|
|
|
// Derive a visible border: same hue, reduced alpha
|
|
|
|
|
document.body.style.setProperty('--mention-highlight-border', `rgba(${r},${g},${b},0.5)`);
|
2026-06-10 15:39:35 -04:00
|
|
|
} else {
|
|
|
|
|
document.body.style.removeProperty('--mention-highlight-bg');
|
|
|
|
|
document.body.style.removeProperty('--mention-highlight-text');
|
|
|
|
|
document.body.style.removeProperty('--mention-highlight-border');
|
|
|
|
|
}
|
|
|
|
|
}, [settings.mentionHighlightColor]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const font = FONT_MAP[settings.fontFamily ?? 'inter'] ?? FONT_MAP.inter;
|
|
|
|
|
document.body.style.setProperty('--font-secondary', font);
|
|
|
|
|
}, [settings.fontFamily]);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 17:32:38 -04:00
|
|
|
function TauriEffects() {
|
|
|
|
|
useTauriNotificationBadge();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 15:36:45 -04:00
|
|
|
function NightLightOverlay() {
|
|
|
|
|
const settings = useAtomValue(settingsAtom);
|
|
|
|
|
if (!settings.nightLightEnabled) return null;
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
style={{
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
inset: 0,
|
|
|
|
|
pointerEvents: 'none',
|
|
|
|
|
zIndex: 9998,
|
|
|
|
|
backgroundColor: `rgba(255, 140, 0, ${(settings.nightLightOpacity ?? 30) / 100})`,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-01-21 23:50:56 +11:00
|
|
|
|
2024-05-31 19:49:46 +05:30
|
|
|
const queryClient = new QueryClient();
|
2024-01-21 23:50:56 +11:00
|
|
|
|
|
|
|
|
function App() {
|
2024-05-31 19:49:46 +05:30
|
|
|
const screenSize = useScreenSize();
|
2025-09-24 23:35:42 -04:00
|
|
|
useCompositionEndTracking();
|
2024-05-31 19:49:46 +05:30
|
|
|
|
2025-09-12 17:22:51 +05:30
|
|
|
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
2025-08-29 15:04:52 +05:30
|
|
|
|
2024-01-21 23:50:56 +11:00
|
|
|
return (
|
2026-05-21 19:44:51 -04:00
|
|
|
<Sentry.ErrorBoundary
|
|
|
|
|
fallback={({ error, resetError }) => (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
gap: '16px',
|
|
|
|
|
fontFamily: 'sans-serif',
|
|
|
|
|
padding: '24px',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<h2 style={{ margin: 0 }}>Something went wrong</h2>
|
|
|
|
|
<p style={{ margin: 0, color: '#666', maxWidth: '400px' }}>
|
|
|
|
|
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
|
|
|
|
|
</p>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={resetError}
|
|
|
|
|
style={{
|
|
|
|
|
padding: '8px 20px',
|
|
|
|
|
borderRadius: '6px',
|
|
|
|
|
border: 'none',
|
|
|
|
|
background: '#5865f2',
|
|
|
|
|
color: '#fff',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Try again
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<TooltipContainerProvider value={portalContainer}>
|
|
|
|
|
<PopOutContainerProvider value={portalContainer}>
|
|
|
|
|
<OverlayContainerProvider value={portalContainer}>
|
|
|
|
|
<ScreenSizeProvider value={screenSize}>
|
|
|
|
|
<FeatureCheck>
|
|
|
|
|
<ClientConfigLoader
|
|
|
|
|
fallback={() => <ConfigConfigLoading />}
|
|
|
|
|
error={(err, retry, ignore) => (
|
|
|
|
|
<ConfigConfigError error={err} retry={retry} ignore={ignore} />
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{(clientConfig) => (
|
|
|
|
|
<ClientConfigProvider value={clientConfig}>
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
<JotaiProvider>
|
2026-06-10 15:39:35 -04:00
|
|
|
<AppearanceEffects />
|
2026-06-10 17:32:38 -04:00
|
|
|
<TauriEffects />
|
2026-05-21 19:44:51 -04:00
|
|
|
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
2026-06-14 00:33:04 -04:00
|
|
|
<SeasonalEffect />
|
2026-06-02 15:36:45 -04:00
|
|
|
<NightLightOverlay />
|
2026-06-04 21:32:37 -04:00
|
|
|
<LotusToastContainer />
|
2026-05-21 19:44:51 -04:00
|
|
|
</JotaiProvider>
|
|
|
|
|
<ReactQueryDevtools initialIsOpen={false} />
|
|
|
|
|
</QueryClientProvider>
|
|
|
|
|
</ClientConfigProvider>
|
|
|
|
|
)}
|
|
|
|
|
</ClientConfigLoader>
|
|
|
|
|
</FeatureCheck>
|
|
|
|
|
</ScreenSizeProvider>
|
|
|
|
|
</OverlayContainerProvider>
|
|
|
|
|
</PopOutContainerProvider>
|
|
|
|
|
</TooltipContainerProvider>
|
|
|
|
|
</Sentry.ErrorBoundary>
|
2024-01-21 23:50:56 +11:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|