Skip to content

Commit

Permalink
fix: avoid hardcoding entry when initially selecting entry id (#20)
Browse files Browse the repository at this point in the history
* fix: avoid hardcoding entry when initially selecting entry id

* fix: avoid flickering when loading initial entry

* fix: add back animation to enetry selection

* fix: avoid resize-flicker in graph
  • Loading branch information
byCedric authored Mar 27, 2024
1 parent b3d7633 commit 92a9256
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 30 deletions.
30 changes: 8 additions & 22 deletions webui/src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Slot } from 'expo-router';
import { useColorScheme } from 'nativewind';
import { useEffect } from 'react';

import { ModuleFilterProvider } from '~/providers/modules';
import { QueryProvider } from '~/providers/query';
import { StatsEntryProvider } from '~/providers/stats';
import { ThemeProvider } from '~/providers/theme';

// Import the Expo-required radix styles
import '@radix-ui/colors/green.css';
Expand All @@ -31,28 +30,15 @@ import '~/styles-expo.css';
import '~/styles.css';

export default function RootLayout() {
useWorkaroundForThemeClass();

return (
<QueryProvider>
<StatsEntryProvider>
<ModuleFilterProvider>
<Slot />
</ModuleFilterProvider>
</StatsEntryProvider>
<ThemeProvider>
<StatsEntryProvider>
<ModuleFilterProvider>
<Slot />
</ModuleFilterProvider>
</StatsEntryProvider>
</ThemeProvider>
</QueryProvider>
);
}

function useWorkaroundForThemeClass() {
const { colorScheme } = useColorScheme();

useEffect(() => {
if (document.body) {
document.body.classList.remove('light-theme', 'dark-theme');
if (colorScheme) {
document.body.className = `${colorScheme}-theme`;
}
}
}, [colorScheme]);
}
6 changes: 3 additions & 3 deletions webui/src/components/forms/StatsEntrySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export function StatsEntrySelect() {
side="bottom"
collisionPadding={{ left: 16, right: 16 }}
className={cn(
'flex min-w-[220px] flex-col gap-0.5 rounded-md border border-default bg-default p-1 shadow-md'
// 'will-change-[opacity,transform]',
// 'data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade'
'flex min-w-[220px] flex-col gap-0.5 rounded-md border border-default bg-default p-1 shadow-md',
'transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:duration-300',
'data-[state=closed]:fade-out data-[state=closed]:slide-out-to-top-1/3 data-[state=open]:fade-in data-[state=open]:slide-in-from-top-1/3'
)}
>
<Select.ScrollUpButton className="SelectScrollButton">
Expand Down
11 changes: 10 additions & 1 deletion webui/src/components/graphs/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ export const Graph = forwardRef<ReactECharts, ComponentProps<typeof ReactECharts
);
});

function useDynamicHeight<T extends HTMLElement>(ref: RefObject<T>, initialHeight = 300) {
let lastKnownHeight = 300;

function useDynamicHeight<T extends HTMLElement>(
ref: RefObject<T>,
initialHeight = lastKnownHeight
) {
const [size, setSize] = useState({ height: initialHeight, width: 0 });

useEffect(() => {
lastKnownHeight = size.height;
}, [size.height]);

useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
Expand Down
42 changes: 38 additions & 4 deletions webui/src/providers/stats.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';

import { Spinner } from '~/ui/Spinner';
import { fetchApi } from '~/utils/api';
import { type PartialStatsEntry } from '~core/data/types';

Expand All @@ -24,18 +25,51 @@ export const useStatsEntryContext = () => useContext(statsEntryContext);

export function StatsEntryProvider({ children }: PropsWithChildren) {
const entries = useStatsEntriesData();
const [entryId, setEntryId] = useState('2');
const [entryId, setEntryId] = useState<string>();
const entryIdOrFirstEntry = entryId ?? entries.data?.[0]?.id;

const entry = useMemo(
() => entries.data?.find((entry) => entry.id === entryId),
[entries, entryId]
() => entries.data?.find((entry) => entry.id === entryIdOrFirstEntry),
[entries, entryIdOrFirstEntry]
);

function entryFilePath(absolutePath: string) {
return entry?.projectRoot ? absolutePath.replace(entry.projectRoot + '/', '') : absolutePath;
}

// TODO: add better UX for loading
if (entries.isFetching && !entries.data?.length) {
return (
<div className="flex flex-1 justify-center items-center">
<Spinner />
</div>
);
}

// TODO: add better UX for empty state
if (entries.isFetched && !entries.data?.length) {
return (
<div className="flex flex-1 justify-center items-center">
<h2 className="text-lg font-bold m-4">No stats found.</h2>
<p>Open your app in the browser, or device, to collect the stats.</p>
</div>
);
}

// TODO: add better UX for error state
if (!entryIdOrFirstEntry) {
return (
<div className="flex flex-1 justify-center items-center">
<h2 className="text-lg font-bold m-4">Unable to load stats.</h2>
<p>Make sure you configured Expo Atlas properly.</p>
</div>
);
}

return (
<statsEntryContext.Provider value={{ entryId, setEntryId, entries, entry, entryFilePath }}>
<statsEntryContext.Provider
value={{ entryId: entryIdOrFirstEntry, setEntryId, entries, entry, entryFilePath }}
>
{children}
</statsEntryContext.Provider>
);
Expand Down
21 changes: 21 additions & 0 deletions webui/src/providers/theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useColorScheme } from 'nativewind';
import { useEffect, type PropsWithChildren } from 'react';

export function ThemeProvider({ children }: PropsWithChildren) {
useWorkaroundForThemeClass();

return children;
}

function useWorkaroundForThemeClass() {
const { colorScheme } = useColorScheme();

useEffect(() => {
if (document.body) {
document.body.classList.remove('light-theme', 'dark-theme');
if (colorScheme) {
document.body.className = `${colorScheme}-theme`;
}
}
}, [colorScheme]);
}
8 changes: 8 additions & 0 deletions webui/src/ui/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import cn from 'classnames';
// @ts-expect-error
import LoaderIcon from 'lucide-react/dist/esm/icons/loader-2';
import { type ComponentProps } from 'react';

export function Spinner({ className, ...props }: ComponentProps<typeof LoaderIcon>) {
return <LoaderIcon className={cn('animate-spin', className)} {...props} />;
}

0 comments on commit 92a9256

Please sign in to comment.