Skip to content

Commit

Permalink
feature: add inspect folder support
Browse files Browse the repository at this point in the history
  • Loading branch information
byCedric committed Mar 17, 2024
1 parent 0bd604b commit e7c662f
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 19 deletions.
70 changes: 70 additions & 0 deletions webui/src/app/api/stats/[entry]/folders/index+api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { ModuleMetadata } from '~/app/api/stats/[entry]/modules/index+api';
import { getSource } from '~/utils/atlas';
import { StatsEntry } from '~core/data/types';

export type FolderGraphData = {
metadata: {
platform: 'android' | 'ios' | 'web';
size: number;
modulesCount: number;
folder: string;
};
data: {
size: number;
modulesCount: number;
modules: ModuleMetadata[];
};
};

export async function POST(request: Request, params: Record<'entry', string>) {
const folderRef: string | undefined = (await request.json()).path;
if (!folderRef) {
return Response.json(
{ error: `Folder ID not provided, expected a "path" property.` },
{ status: 406 }
);
}

let entry: StatsEntry;

try {
entry = await getSource().getEntry(params.entry);
} catch (error: any) {
return Response.json({ error: error.message }, { status: 406 });
}

const folder = collectFolderInfo(entry, folderRef);
return folder
? Response.json(folder)
: Response.json({ error: `Folder "${folderRef}" not found.` }, { status: 404 });
}

function collectFolderInfo(entry: StatsEntry, folderRef: string): FolderGraphData | null {
const modules: ModuleMetadata[] = [];

for (const [moduleRef, module] of entry.modules) {
if (moduleRef.startsWith(folderRef)) {
modules.push({ ...module, source: undefined, output: undefined });
}
}

if (!modules.length) {
return null;
}

const size = modules.reduce((size, module) => size + module.size, 0);

return {
metadata: {
platform: entry.platform as any,
size,
modulesCount: modules.length,
folder: folderRef,
},
data: {
modules,
size,
modulesCount: modules.length,
},
};
}
116 changes: 116 additions & 0 deletions webui/src/app/folders/[path].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useQuery } from '@tanstack/react-query';
import { useLocalSearchParams } from 'expo-router';

import { type FolderGraphData } from '../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 { Skeleton } from '~/ui/Skeleton';
import { Tag } from '~/ui/Tag';
import { formatFileSize } from '~/utils/formatString';
import { type PartialStatsEntry } from '~core/data/types';

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

if (folder.isLoading) {
return <FolderPageSkeleton />;
}

if (!folder.data || folder.isError) {
// TODO: improve
return (
<div className="flex flex-1 flex-col justify-center items-center">
<h2 className="text-slate-50 font-bold text-lg">Folder not found</h2>
</div>
);
}

return (
<Page variant="viewport">
<div className="flex flex-1 flex-col">
<PageHeader>
<PageTitle>
<h1
className="text-slate-50 font-bold text-lg mr-4"
title={folder.data.metadata.folder}
>
{entryFilePath(folder.data.metadata.folder)}/
</h1>
<FolderSummary platform={entry?.platform} folder={folder.data.metadata} />
</PageTitle>
</PageHeader>

<TreemapGraph
key={`folder-graph-${entryId}`}
name={entryFilePath(folder.data.metadata.folder)}
modules={folder.data?.data?.modules ?? []}
/>
</div>
</Page>
);
}

function FolderSummary({
folder,
platform,
}: {
folder: FolderGraphData['metadata'];
platform?: PartialStatsEntry['platform'];
}) {
return (
<div className="font-sm text-secondary">
{!!platform && (
<>
<Tag variant={platform} />
<span className="text-tertiary mx-2 select-none">-</span>
</>
)}
<span>folder</span>
<span className="text-tertiary mx-2 select-none">-</span>
<span>
{folder?.modulesCount === 1
? `${folder.modulesCount} module`
: `${folder.modulesCount} modules`}
</span>
<span className="text-tertiary mx-2 select-none">-</span>
<span>{formatFileSize(folder.size)}</span>
</div>
);
}

/** Load the folder data from API, by path reference only */
function useFolderData(entryId: string, path: string) {
return useQuery<FolderGraphData>({
queryKey: [`module`, entryId, path],
queryFn: async ({ queryKey }) => {
const [_key, entry, path] = queryKey as [string, string, string];
return fetch(`/api/stats/${entry}/folders`, {
method: 'POST',
body: JSON.stringify({ path }),
})
.then((res) => (res.ok ? res : Promise.reject(res)))
.then((res) => res.json());
},
});
}

function FolderPageSkeleton() {
return (
<div className="flex flex-1 flex-col overflow-auto">
<PageHeader>
<PageTitle>
<Skeleton className="w-48 h-6 bg-selected" />
<Skeleton className="w-96 h-6 mx-2" />
</PageTitle>
</PageHeader>

<div className="mx-8 flex-1">
<Skeleton className="h-full rounded-md" />
</div>
</div>
);
}
15 changes: 1 addition & 14 deletions webui/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'expo-router';

import type { EntryGraphData } from '~/app/api/stats/[entry]/modules/index+api';
import { Page, PageHeader, PageTitle } from '~/components/Page';
Expand All @@ -19,14 +18,6 @@ export default function GraphScreen() {
const { filters } = useModuleFilterContext();

const graph = useBundleGraphData(entryId, filters);
const router = useRouter();

function onInspectModule(absolutePath: string) {
router.push({
pathname: '/modules/[path]',
params: { path: absolutePath },
});
}

return (
<Page variant="viewport">
Expand All @@ -38,11 +29,7 @@ export default function GraphScreen() {
</PageTitle>
<StatsModuleFilter />
</PageHeader>
<TreemapGraph
key={`bundle-graph-${entryId}`}
modules={graph.data?.data?.modules ?? []}
onModuleClick={onInspectModule}
/>
<TreemapGraph key={`bundle-graph-${entryId}`} modules={graph.data?.data?.modules ?? []} />
</div>
</Page>
);
Expand Down
34 changes: 29 additions & 5 deletions webui/src/components/graphs/TreemapGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as echarts from 'echarts';
import { useRouter } from 'expo-router';
import { useMemo } from 'react';

import { Graph } from './Graph';
Expand All @@ -7,8 +8,8 @@ import type { ModuleMetadata } from '~/app/api/stats/[entry]/modules/index+api';
import { formatFileSize } from '~/utils/formatString';

type TreemapGraphProps = {
name?: string;
modules: ModuleMetadata[];
onModuleClick: (absolutePath: string) => void;
};

const ICON_STRINGS = {
Expand All @@ -18,6 +19,20 @@ const ICON_STRINGS = {
};

export function TreemapGraph(props: TreemapGraphProps) {
const router = useRouter();

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

console.log('REDIRECTING TO', {
pathname: type === 'module' ? '/modules/[path]' : '/folders/[path]',
params: { path: absolutePath },
});
}

const { data, maxDepth, maxNodeModules } = useMemo(
() => createModuleTree(props.modules.filter((module) => module.path.startsWith('/'))),
[props.modules]
Expand Down Expand Up @@ -51,8 +66,11 @@ export function TreemapGraph(props: TreemapGraphProps) {
onEvents={{
click({ event, data }: any) {
const shouldFireClick = event.event.altKey || event.event.ctrlKey || event.event.metaKey;
if (data?.path && shouldFireClick) {
props.onModuleClick(data.path);
if (!shouldFireClick) return;
if (data?.moduleHref) {
onInspectPath('module', data.moduleHref);
} else if (data?.folderHerf) {
onInspectPath('folder', data.folderHerf);
}
},
}}
Expand Down Expand Up @@ -120,10 +138,11 @@ export function TreemapGraph(props: TreemapGraphProps) {
}
} else {
// Full bundle
const typeName = !props.name ? 'Bundle' : props.name;
components.push(
`<div style="padding:0 ${padding}px;display:flex;flex-direction:row;justify-content:space-between;">
<div style="display:flex;align-items:center;gap:6px">${ICON_STRINGS.pkg}
<span style="padding-right:8px;">Bundle</span></div>
<span style="padding-right:8px;">${typeName}</span></div>
<span>100%</span>
</div>`
);
Expand All @@ -137,7 +156,7 @@ export function TreemapGraph(props: TreemapGraphProps) {
series: [
{
// roam: 'move',
name: 'Bundle',
name: !props.name ? 'Bundle' : props.name,
type: 'treemap',
height: '85%',
width: '95%',
Expand Down Expand Up @@ -245,6 +264,7 @@ type TreemapItem = {
path: string;
value: [number, number];
moduleHref?: string;
folderHerf?: string;
tip: string;
sizeString: string;
ratio: number;
Expand All @@ -270,6 +290,7 @@ function createModuleTree(paths: ModuleMetadata[]): {
}

const root: TreemapItem = {
folderHerf: '/',
path: '/',
children: [],
name: '/',
Expand All @@ -292,10 +313,13 @@ function createModuleTree(paths: ModuleMetadata[]): {

parts.forEach((part, index) => {
const isLast = index === parts.length - 1;
const pathFull = '/' + parts.slice(0, index + 1).join('/');

let next = current.children.find((g) => g.path === part);

if (!next) {
next = {
folderHerf: pathFull,
path: part,
name: part,
children: [],
Expand Down

0 comments on commit e7c662f

Please sign in to comment.