Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move entry id from context to url #24

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 5 additions & 71 deletions webui/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { Redirect } from 'expo-router';

import type { EntryGraphData } from '~/app/api/stats/[entry]/modules/index+api';
import { Page, PageHeader, PageTitle } from '~/components/Page';
import { StatsModuleFilter } from '~/components/forms/StatsModuleFilter';
import { TreemapGraph } from '~/components/graphs/TreemapGraph';
import {
type ModuleFilters,
useModuleFilterContext,
filtersToUrlParams,
} from '~/providers/modules';
import { useStatsEntryContext } from '~/providers/stats';
import { Tag } from '~/ui/Tag';
import { fetchApi } from '~/utils/api';
import { formatFileSize } from '~/utils/formatString';
import { useStatsEntry } from '~/providers/stats';

export default function GraphScreen() {
const { entryId } = useStatsEntryContext();
const { filters } = useModuleFilterContext();
export default function HomeScreen() {
const { entry } = useStatsEntry();

const graph = useBundleGraphData(entryId, filters);

return (
<Page variant="viewport">
<div className="flex flex-1 flex-col">
<PageHeader>
<PageTitle>
<h1 className="text-lg font-bold mr-4">Bundle</h1>
{!!graph.data && <BundleSummary data={graph.data} />}
</PageTitle>
<StatsModuleFilter />
</PageHeader>
<TreemapGraph key={`bundle-graph-${entryId}`} modules={graph.data?.data?.modules ?? []} />
</div>
</Page>
);
}

function BundleSummary({ data }: { data: EntryGraphData }) {
return (
<div className="font-sm text-secondary inline-block">
<Tag variant={data.metadata.platform} />
<span className="text-tertiary mx-2 select-none">-</span>
<span>{data.metadata.modulesCount} modules</span>
<span className="text-tertiary mx-2 select-none">-</span>
<span>{formatFileSize(data.metadata.size)}</span>
{data.metadata.modulesCount !== data.data.modulesCount && (
<div className="text-tertiary italic inline">
<span className="mx-2 select-none">—</span>
<span className="mr-2 select-none italic ">visible:</span>
<span>{data.data.modulesCount} modules</span>
<span className="mx-2 select-none">-</span>
<span>{formatFileSize(data.data.size)}</span>
</div>
)}
</div>
);
}

/** Load the bundle graph data from API, with default or custom filters */
function useBundleGraphData(entryId: string, filters?: ModuleFilters) {
return useQuery<EntryGraphData>({
queryKey: [`bundle-graph`, entryId, filters],
queryFn: ({ queryKey }) => {
const [_key, entry, filters] = queryKey as [string, string, ModuleFilters | undefined];
const url = filters
? `/api/stats/${entry}/modules?${filtersToUrlParams(filters)}`
: `/api/stats/${entry}/modules`;

return fetchApi(url)
.then((res) => (res.ok ? res : Promise.reject(res)))
.then((res) => res.json());
},
});
return <Redirect href={{ pathname: '/stats/[entry]/', params: { entry: entry.id } }} />;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { useQuery } from '@tanstack/react-query';
import { useLocalSearchParams } from 'expo-router';

import { type FolderGraphData } from '../api/stats/[entry]/folders/index+api';

import { type FolderGraphData } from '~/app/api/stats/[entry]/folders/index+api';
import { Page, PageHeader, PageTitle } from '~/components/Page';
import { TreemapGraph } from '~/components/graphs/TreemapGraph';
import { useStatsEntryContext } from '~/providers/stats';
import { useStatsEntry } from '~/providers/stats';
import { Skeleton } from '~/ui/Skeleton';
import { Tag } from '~/ui/Tag';
import { fetchApi } from '~/utils/api';
import { formatFileSize } from '~/utils/formatString';
import { relativeEntryPath } from '~/utils/stats';
import { type PartialStatsEntry } from '~core/data/types';

export default function FolderPage() {
const { entryId, entry, entryFilePath } = useStatsEntryContext();
const { entry } = useStatsEntry();
const { path: absolutePath } = useLocalSearchParams<{ path: string }>();
const folder = useFolderData(entryId, absolutePath!);
const folder = useFolderData(entry.id, absolutePath!);

if (folder.isLoading) {
return <FolderPageSkeleton />;
Expand All @@ -39,15 +39,15 @@ export default function FolderPage() {
className="text-slate-50 font-bold text-lg mr-4"
title={folder.data.metadata.folderPath}
>
{entryFilePath(folder.data.metadata.folderPath)}/
{relativeEntryPath(entry, folder.data.metadata.folderPath)}/
</h1>
<FolderSummary platform={entry?.platform} folder={folder.data.metadata} />
</PageTitle>
</PageHeader>

<TreemapGraph
key={`folder-graph-${entryId}`}
name={`.../${entryFilePath(folder.data.metadata.folderName)}`}
key={`folder-graph-${entry.id}`}
name={`.../${relativeEntryPath(entry, folder.data.metadata.folderName)}`}
modules={folder.data?.data?.modules ?? []}
/>
</div>
Expand Down
75 changes: 75 additions & 0 deletions webui/src/app/stats/[entry]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useQuery } from '@tanstack/react-query';

import type { EntryGraphData } from '~/app/api/stats/[entry]/modules/index+api';
import { Page, PageHeader, PageTitle } from '~/components/Page';
import { StatsModuleFilter } from '~/components/forms/StatsModuleFilter';
import { TreemapGraph } from '~/components/graphs/TreemapGraph';
import {
type ModuleFilters,
useModuleFilterContext,
filtersToUrlParams,
} from '~/providers/modules';
import { useStatsEntry } from '~/providers/stats';
import { Tag } from '~/ui/Tag';
import { fetchApi } from '~/utils/api';
import { formatFileSize } from '~/utils/formatString';

export default function StatsScreen() {
const { entry } = useStatsEntry();
const { filters } = useModuleFilterContext();

const graph = useBundleGraphData(entry.id, filters);

return (
<Page variant="viewport">
<div className="flex flex-1 flex-col">
<PageHeader>
<PageTitle>
<h1 className="text-lg font-bold mr-4">Bundle</h1>
{!!graph.data && <BundleSummary data={graph.data} />}
</PageTitle>
<StatsModuleFilter />
</PageHeader>
<TreemapGraph key={`bundle-graph-${entry.id}`} modules={graph.data?.data?.modules ?? []} />
</div>
</Page>
);
}

function BundleSummary({ data }: { data: EntryGraphData }) {
return (
<div className="font-sm text-secondary inline-block">
<Tag variant={data.metadata.platform} />
<span className="text-tertiary mx-2 select-none">-</span>
<span>{data.metadata.modulesCount} modules</span>
<span className="text-tertiary mx-2 select-none">-</span>
<span>{formatFileSize(data.metadata.size)}</span>
{data.metadata.modulesCount !== data.data.modulesCount && (
<div className="text-tertiary italic inline">
<span className="mx-2 select-none">—</span>
<span className="mr-2 select-none italic ">visible:</span>
<span>{data.data.modulesCount} modules</span>
<span className="mx-2 select-none">-</span>
<span>{formatFileSize(data.data.size)}</span>
</div>
)}
</div>
);
}

/** Load the bundle graph data from API, with default or custom filters */
function useBundleGraphData(entryId: string, filters?: ModuleFilters) {
return useQuery<EntryGraphData>({
queryKey: [`bundle-graph`, entryId, filters],
queryFn: ({ queryKey }) => {
const [_key, entry, filters] = queryKey as [string, string, ModuleFilters | undefined];
const url = filters
? `/api/stats/${entry}/modules?${filtersToUrlParams(filters)}`
: `/api/stats/${entry}/modules`;

return fetchApi(url)
.then((res) => (res.ok ? res : Promise.reject(res)))
.then((res) => res.json());
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { useQuery } from '@tanstack/react-query';
import { Link, useLocalSearchParams } from 'expo-router';

import { Page, PageHeader, PageTitle } from '~/components/Page';
import { useStatsEntryContext } from '~/providers/stats';
import { useStatsEntry } from '~/providers/stats';
import { CodeBlock, CodeBlockSectionWithPrettier, guessLanguageFromPath } from '~/ui/CodeBlock';
import { Skeleton } from '~/ui/Skeleton';
import { Tag } from '~/ui/Tag';
import { fetchApi } from '~/utils/api';
import { formatFileSize } from '~/utils/formatString';
import { relativeEntryPath } from '~/utils/stats';
import { type PartialStatsEntry, type StatsModule } from '~core/data/types';

export default function ModulePage() {
const { entryId, entry, entryFilePath } = useStatsEntryContext();
const { entry } = useStatsEntry();
const { path: absolutePath } = useLocalSearchParams<{ path: string }>();
const module = useModuleData(entryId, absolutePath!);
const module = useModuleData(entry.id, absolutePath!);

const outputCode = module.data?.output?.map((output) => output.data.code).join('\n');

Expand All @@ -35,7 +36,7 @@ export default function ModulePage() {
<PageHeader>
<PageTitle>
<h1 className="text-slate-50 font-bold text-lg mr-4" title={module.data.path}>
{entryFilePath(module.data.path)}
{relativeEntryPath(entry, module.data.path)}
</h1>
<ModuleSummary platform={entry?.platform} module={module.data} />
</PageTitle>
Expand All @@ -50,9 +51,12 @@ export default function ModulePage() {
<li key={path} className="ml-4">
<Link
className="text-link hover:underline"
href={{ pathname: '/modules/[path]', params: { path } }}
href={{
pathname: '/stats/[entry]/modules/[path]',
params: { entry: entry.id, path },
}}
>
{entryFilePath(path)}
{relativeEntryPath(entry, path)}
</Link>
</li>
))}
Expand Down
27 changes: 12 additions & 15 deletions webui/src/components/forms/StatsEntrySelect.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import * as Select from '@radix-ui/react-select';
import cn from 'classnames';
import { useRouter } from 'expo-router';
// @ts-expect-error
import ChevronDownIcon from 'lucide-react/dist/esm/icons/chevron-down';
// @ts-expect-error
import ChevronUpIcon from 'lucide-react/dist/esm/icons/chevron-up';

import { useStatsEntryContext } from '~/providers/stats';
import { useStatsEntry } from '~/providers/stats';
import { Button } from '~/ui/Button';
import { Tag } from '~/ui/Tag';
import { relativeEntryPath } from '~/utils/stats';

export function StatsEntrySelect() {
const { entryId, setEntryId, entry, entries } = useStatsEntryContext();

function onEntryChange(value: string) {
setEntryId(value);
}
const router = useRouter();
const { entry, entries } = useStatsEntry();

return (
<Select.Root value={String(entryId)} onValueChange={onEntryChange}>
<Select.Root value={entry.id} onValueChange={(entry) => router.setParams({ entry })}>
<Select.Trigger asChild>
<Button variant="quaternary" size="sm">
{!!entry && <Tag variant={entry?.platform} size="xs" className="mr-2" />}
<Tag variant={entry.platform} size="xs" className="mr-2" />
<Select.Value placeholder="Select bundle to inspect" />
<Select.Icon className="text-icon-default">
<ChevronDownIcon size={16} className="m-1 mr-0 align-middle" />
Expand All @@ -41,14 +40,12 @@ export function StatsEntrySelect() {
<ChevronUpIcon />
</Select.ScrollUpButton>
<Select.Viewport className="SelectViewport">
{entries?.data?.map((entry) => (
<div key={entry.id}>
<Select.Item value={String(entry.id)} asChild>
{entries.map((item) => (
<div key={item.id}>
<Select.Item value={item.id} asChild>
<Button variant="quaternary" size="sm" className="w-full">
<Tag variant={entry.platform} className="mr-2" />
<Select.ItemText>
{entry.entryPoint.replace(entry.projectRoot + '/', '')}
</Select.ItemText>
<Tag variant={item.platform} className="mr-2" />
<Select.ItemText>{relativeEntryPath(entry, item.entryPoint)}</Select.ItemText>
</Button>
</Select.Item>
</div>
Expand Down
28 changes: 19 additions & 9 deletions webui/src/components/graphs/TreemapGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as echarts from 'echarts';
import { useRouter } from 'expo-router';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';

import { Graph } from './Graph';

import type { ModuleMetadata } from '~/app/api/stats/[entry]/modules/index+api';
import { useStatsEntry } from '~/providers/stats';
import { formatFileSize } from '~/utils/formatString';

type TreemapGraphProps = {
Expand All @@ -18,16 +19,23 @@ const ICON_STRINGS = {
pkg: `<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 -960 960 960" width="16"><path d="M440-183v-274L200-596v274l240 139Zm80 0 240-139v-274L520-457v274Zm-40-343 237-137-237-137-237 137 237 137ZM160-252q-19-11-29.5-29T120-321v-318q0-22 10.5-40t29.5-29l280-161q19-11 40-11t40 11l280 161q19 11 29.5 29t10.5 40v318q0 22-10.5 40T800-252L520-91q-19 11-40 11t-40-11L160-252Zm320-228Z"/></svg>`,
};

export function TreemapGraph(props: TreemapGraphProps) {
function useInspectCallback() {
const router = useRouter();
const { entry } = useStatsEntry();

return useCallback(
(type: 'folder' | 'module', path: string) => {
router.push({
pathname:
type === 'module' ? '/stats/[entry]/modules/[path]' : '/stats/[entry]/folders/[path]',
params: { entry: entry.id, path },
});
},
[entry.id]
);
}

function onInspectPath(type: 'folder' | 'module', absolutePath: string) {
router.push({
pathname: type === 'module' ? '/modules/[path]' : '/folders/[path]',
params: { path: absolutePath },
});
}

export function TreemapGraph(props: TreemapGraphProps) {
const { data, maxDepth, maxNodeModules } = useMemo(
() => createModuleTree(props.modules.filter((module) => module.path.startsWith('/'))),
[props.modules]
Expand Down Expand Up @@ -55,6 +63,8 @@ export function TreemapGraph(props: TreemapGraphProps) {
...getLabelObj({ multiLevel: false }),
};

const onInspectPath = useInspectCallback();

return (
<Graph
theme="dark"
Expand Down
Loading