Skip to content

Commit 746e28a

Browse files
authored
slightly de-emphasise map and hash files in code browser (#2394)
1 parent a2f230c commit 746e28a

6 files changed

Lines changed: 169 additions & 14 deletions

File tree

components/Icons/index.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,3 +1616,26 @@ export function CodeIcon({ width = 24, height = 24, style }: IconProps) {
16161616
</Svg>
16171617
);
16181618
}
1619+
1620+
export function FileMetadataIcon({ width = 24, height = 24, style }: IconProps) {
1621+
return (
1622+
<Svg width={width} height={height} viewBox="0 0 256 256" style={style}>
1623+
<polyline
1624+
points="168 128 216 176 168 224"
1625+
fill="none"
1626+
stroke="currentColor"
1627+
strokeLinecap="round"
1628+
strokeLinejoin="round"
1629+
strokeWidth="16"
1630+
/>
1631+
<polyline
1632+
points="72 32 72 176 216 176"
1633+
fill="none"
1634+
stroke="currentColor"
1635+
strokeLinecap="round"
1636+
strokeLinejoin="round"
1637+
strokeWidth="16"
1638+
/>
1639+
</Svg>
1640+
);
1641+
}

components/Package/CodeBrowser/CodeBrowserFileRow.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
22
import { Pressable, View } from 'react-native';
33

44
import { P } from '~/common/styleguide';
5-
import { FileIcon, FolderIcon, WarningBlockquote } from '~/components/Icons';
5+
import { FileIcon, FileMetadataIcon, FolderIcon, WarningBlockquote } from '~/components/Icons';
66
import Tooltip from '~/components/Tooltip';
77
import { getFileWarning } from '~/util/codeBrowser';
88
import tw from '~/util/tailwind';
@@ -12,6 +12,7 @@ type Props = {
1212
depth?: number;
1313
isActive?: boolean;
1414
isDirectory?: boolean;
15+
isNested?: boolean;
1516
onPress?: () => void;
1617
};
1718

@@ -20,30 +21,37 @@ export default function CodeBrowserFileRow({
2021
depth = 0,
2122
isActive = false,
2223
isDirectory = false,
24+
isNested = false,
2325
onPress,
2426
}: Props) {
2527
const [isHovered, setIsHovered] = useState(false);
2628
const warning = isDirectory ? undefined : getFileWarning(label);
2729

28-
const Icon = useMemo(() => (isDirectory ? FolderIcon : FileIcon), [isDirectory]);
30+
const Icon = useMemo(
31+
() => (isDirectory ? FolderIcon : isNested ? FileMetadataIcon : FileIcon),
32+
[isDirectory, isNested]
33+
);
2934

3035
const rowStyle = [
3136
tw`flex flex-row items-center gap-1.5 px-3 py-[3px] last:mb-20`,
32-
{ paddingLeft: 12 + depth * 8 },
37+
{ paddingLeft: (isNested ? 6 : 10) + depth * 8 },
3338
];
3439

3540
const content = (
3641
<>
3742
<Icon
3843
style={[
3944
tw`size-4 shrink-0 text-icon`,
45+
isNested && tw`size-3 text-palette-gray5`,
4046
isActive && tw`text-primary-darker dark:text-primary-dark`,
4147
isDirectory && tw`text-tertiary dark:text-accented`,
4248
]}
4349
/>
4450
<P
4551
style={[
4652
tw`font-mono select-none text-[12px] font-light tracking-normal`,
53+
isNested &&
54+
tw`text-[11px] font-extralight text-palette-gray6 dark:font-thin dark:text-palette-gray3`,
4755
isDirectory && tw`font-extralight text-palette-gray5 dark:text-palette-gray4`,
4856
isActive && tw`text-primary-darker dark:text-primary`,
4957
{ wordBreak: 'break-word' },

components/Package/CodeBrowser/CodeBrowserFileTree.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,24 @@ export default function CodeBrowserFileTree({ tree, activeFile, onSelectFile, de
3333
);
3434
})}
3535
{files.map(file => (
36-
<CodeBrowserFileRow
37-
key={file.path}
38-
label={file.name}
39-
depth={depth}
40-
onPress={() => onSelectFile(file.path)}
41-
isActive={file.path === activeFile}
42-
/>
36+
<View key={file.path}>
37+
<CodeBrowserFileRow
38+
label={file.name}
39+
depth={depth}
40+
onPress={() => onSelectFile(file.path)}
41+
isActive={file.path === activeFile}
42+
/>
43+
{file.nestedFiles?.map(nestedFile => (
44+
<CodeBrowserFileRow
45+
isNested
46+
key={nestedFile.path}
47+
label={nestedFile.name}
48+
depth={depth + 1}
49+
onPress={() => onSelectFile(nestedFile.path)}
50+
isActive={nestedFile.path === activeFile}
51+
/>
52+
))}
53+
</View>
4354
))}
4455
</>
4556
);

scenes/PackageCodeScene.tsx

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import ThreeDotsLoader from '~/components/Package/ThreeDotsLoader';
1515
import PageMeta from '~/components/PageMeta';
1616
import { type UnpkgMeta } from '~/types';
1717
import { type PackageCodePageProps } from '~/types/pages';
18-
import { buildCodeBrowserFileTree, getCodeBrowserFilePath } from '~/util/codeBrowser';
18+
import {
19+
buildCodeBrowserFileTree,
20+
getCodeBrowserFilePath,
21+
getCodeBrowserNestedFileParentPath,
22+
} from '~/util/codeBrowser';
1923
import { TimeRange } from '~/util/datetime';
2024
import { formatBytes } from '~/util/formatBytes';
2125
import { pluralize } from '~/util/strings';
@@ -52,9 +56,49 @@ export default function PackageCodeScene({ apiData, packageName }: PackageCodePa
5256
return files;
5357
}
5458

55-
return files.filter(file =>
56-
getCodeBrowserFilePath(file.path, data?.prefix).toLowerCase().includes(normalizedSearch)
57-
);
59+
const filesByPath = new Map<string, (typeof files)[number]>();
60+
const relatedPaths = new Map<string, Set<string>>();
61+
const matchedPaths: string[] = [];
62+
const visiblePaths = new Set<string>();
63+
64+
for (const file of files) {
65+
filesByPath.set(file.path, file);
66+
67+
const nestedFileParentPath = getCodeBrowserNestedFileParentPath(file.path);
68+
69+
if (nestedFileParentPath) {
70+
relatedPaths.set(
71+
file.path,
72+
(relatedPaths.get(file.path) ?? new Set()).add(nestedFileParentPath)
73+
);
74+
relatedPaths.set(
75+
nestedFileParentPath,
76+
(relatedPaths.get(nestedFileParentPath) ?? new Set()).add(file.path)
77+
);
78+
}
79+
80+
const relativePath = getCodeBrowserFilePath(file.path, data?.prefix).toLowerCase();
81+
82+
if (relativePath.includes(normalizedSearch)) {
83+
matchedPaths.push(file.path);
84+
}
85+
}
86+
87+
const queue = [...matchedPaths];
88+
89+
while (queue.length > 0) {
90+
const currentPath = queue.shift();
91+
92+
if (!currentPath || visiblePaths.has(currentPath) || !filesByPath.has(currentPath)) {
93+
continue;
94+
}
95+
96+
visiblePaths.add(currentPath);
97+
98+
queue.push(...(relatedPaths.get(currentPath) ?? []));
99+
}
100+
101+
return files.filter(file => visiblePaths.has(file.path));
58102
}, [data?.files, data?.prefix, normalizedSearch]);
59103

60104
const visibleFilePaths = useMemo(

types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ export type GitHubUser = {
361361
export type CodeBrowserTreeFile = {
362362
name: string;
363363
path: string;
364+
nestedFiles?: CodeBrowserTreeFile[];
364365
};
365366

366367
export type CodeBrowserTreeDirectory = {

util/codeBrowser.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ const FILE_WARNING_MATCHERS = FILE_WARNINGS.map(warning => ({
9393
matchers: warning.fileNames.map(createFileWarningMatcher),
9494
}));
9595

96+
const SOURCE_MAP_PARENT_EXTENSIONS = new Set([
97+
'cjs',
98+
'cts',
99+
'js',
100+
'jsx',
101+
'mjs',
102+
'mts',
103+
'ts',
104+
'tsx',
105+
]);
106+
107+
const HASH_FILE_EXTENSIONS = new Set(['md5', 'sha1', 'sha3', 'sha256', 'sha512']);
108+
96109
export function getFileWarning(fileName?: string) {
97110
if (!fileName) {
98111
return undefined;
@@ -104,6 +117,32 @@ export function getCodeBrowserFilePath(path: string, prefix?: string) {
104117
return prefix ? path.replace(prefix, '') : path;
105118
}
106119

120+
export function getCodeBrowserNestedFileParentPath(path: string) {
121+
const nestedFileExtension = path.split('.').pop()?.toLowerCase();
122+
123+
if (!nestedFileExtension) {
124+
return null;
125+
}
126+
127+
const nestedFileParentPath = path.slice(0, -(nestedFileExtension.length + 1));
128+
129+
if (nestedFileExtension === 'map') {
130+
const sourceMapParentExtension = nestedFileParentPath.split('.').pop()?.toLowerCase();
131+
132+
if (!sourceMapParentExtension || !SOURCE_MAP_PARENT_EXTENSIONS.has(sourceMapParentExtension)) {
133+
return null;
134+
}
135+
136+
return nestedFileParentPath;
137+
}
138+
139+
if (HASH_FILE_EXTENSIONS.has(nestedFileExtension)) {
140+
return nestedFileParentPath;
141+
}
142+
143+
return null;
144+
}
145+
107146
export function buildCodeBrowserFileTree(
108147
files: UnpkgMeta['files'],
109148
prefix?: string
@@ -139,6 +178,8 @@ export function buildCodeBrowserFileTree(
139178
});
140179
});
141180

181+
nestCodeBrowserSidecarFiles(root);
182+
142183
return root;
143184
}
144185

@@ -151,6 +192,33 @@ function createCodeBrowserTreeDirectory(name: string, path: string): CodeBrowser
151192
};
152193
}
153194

195+
function nestCodeBrowserSidecarFiles(directory: CodeBrowserTreeDirectory) {
196+
const filesByPath = new Map(directory.files.map(file => [file.path, file]));
197+
const nestedFilePaths = new Set<string>();
198+
199+
directory.files.forEach(file => {
200+
const nestedFileParentPath = getCodeBrowserNestedFileParentPath(file.path);
201+
202+
if (!nestedFileParentPath) {
203+
return;
204+
}
205+
206+
const nestedFileParent = filesByPath.get(nestedFileParentPath);
207+
208+
if (!nestedFileParent) {
209+
return;
210+
}
211+
212+
nestedFileParent.nestedFiles ??= [];
213+
nestedFileParent.nestedFiles.push(file);
214+
nestedFilePaths.add(file.path);
215+
});
216+
217+
directory.files = directory.files.filter(file => !nestedFilePaths.has(file.path));
218+
219+
Object.values(directory.directories).forEach(nestCodeBrowserSidecarFiles);
220+
}
221+
154222
function createFileWarningMatcher(pattern: string) {
155223
if (!pattern.includes('*')) {
156224
return (fileName: string) => fileName === pattern;

0 commit comments

Comments
 (0)