typescriptify Developer.js and refactor size calculation (#3176)

* typescriptify Developer.js and refactor size calculation

* remove logs

* fix typography component types / spacing, clean up naming

* fix type annotation, unused export, and variable name
This commit is contained in:
Ryan Stelly
2025-11-13 16:43:41 -06:00
committed by GitHub
parent 799efb8b4c
commit fd712f10e2
2 changed files with 141 additions and 93 deletions

View File

@@ -10,8 +10,7 @@ import {
} from "components/SharedComponents";
import { fontMonoClass, View } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React, { useCallback } from "react";
import React, { type PropsWithChildren } from "react";
import { I18nManager, Platform, Text } from "react-native";
import Config from "react-native-config";
import RNFS from "react-native-fs";
@@ -19,13 +18,30 @@ import RNRestart from "react-native-restart";
import useLogs from "sharedHooks/useLogs";
import useAppSize, {
DirectoryEntrySize,
formatAppSizeString, formatSizeUnits, getTotalDirectorySize
} from "./hooks/useAppSize";
const H1 = ( { children } ) => <Heading1 className="mt-3 mb-2">{children}</Heading1>;
const H2 = ( { children } ) => <Heading2 className="mt-3 mb-2">{children}</Heading2>;
const P = ( { children } ) => <Text selectable className="mb-2">{children}</Text>;
const CODE = ( { children, optionalClassName } ) => (
const H1 = ( { children }: PropsWithChildren ) => (
<Heading1 className="mt-3 mb-2">
{children}
</Heading1>
);
const H2 = ( { children }: PropsWithChildren ) => (
<Heading2 className="mt-3 mb-2">
{children}
</Heading2>
);
const P = ( { children }: PropsWithChildren ) => (
<Text selectable className="mb-2">
{children}
</Text>
);
interface CODEProps extends PropsWithChildren {
optionalClassName?: string;
}
const CODE = ( { children, optionalClassName }: CODEProps ) => (
<Text
selectable
className={classnames(
@@ -37,7 +53,7 @@ const CODE = ( { children, optionalClassName } ) => (
</Text>
);
const modelFileName: string = Platform.select( {
const modelFileName = Platform.select( {
ios: Config.IOS_MODEL_FILE_NAME,
android: Config.ANDROID_MODEL_FILE_NAME
} );
@@ -49,48 +65,66 @@ const geomodelFileName = Platform.select( {
ios: Config.IOS_GEOMODEL_FILE_NAME,
android: Config.ANDROID_GEOMODEL_FILE_NAME
} );
const boldClassname = ( line: string, isDirectory = false ) => classnames(
{
"text-red font-bold": line.includes( "MB" ),
"text-blue": isDirectory
}
);
interface DirectorySizesProps {
directoryName: string;
directoryEntrySizes: DirectoryEntrySize[]
}
/* eslint-disable i18next/no-literal-string */
const Developer = (): Node => {
const fileSizes = useAppSize( );
const boldClassname = ( line, isDirectory = false ) => classnames(
{
"text-red font-bold": line.includes( "MB" ),
"text-blue": isDirectory
}
const DirectoryFileSizes = ( { directoryName, directoryEntrySizes }: DirectorySizesProps ) => {
const totalDirectorySize = formatSizeUnits( getTotalDirectorySize( directoryEntrySizes ) );
return (
<View key={directoryName}>
<H2>
File Sizes:
{" "}
{directoryName}
</H2>
<P>
<CODE optionalClassName={boldClassname( totalDirectorySize, true )}>
{`Total Directory Size: ${totalDirectorySize}`}
</CODE>
</P>
{directoryEntrySizes.map( ( { name, size } ) => {
const line = formatAppSizeString( name, size );
return (
<P key={name}>
<CODE optionalClassName={boldClassname( line )}>
{line}
</CODE>
</P>
);
} )}
</View>
);
};
const displayFileSizes = useCallback( ( ) => Object.keys( fileSizes ).map( directory => {
const contents = fileSizes[directory];
if ( !directory || !contents ) { return null; }
const totalDirectorySize = formatSizeUnits( getTotalDirectorySize( contents ) );
return (
<View key={directory}>
<H2>
File Sizes:
{" "}
{directory}
</H2>
<P>
<CODE optionalClassName={boldClassname( totalDirectorySize, true )}>
{`Total Directory Size: ${totalDirectorySize}`}
</CODE>
</P>
{contents.map( ( { name, size } ) => {
const line = formatAppSizeString( name, size );
return (
<P key={name}>
<CODE optionalClassName={boldClassname( line )}>
{line}
</CODE>
</P>
);
} )}
</View>
);
} ), [fileSizes] );
const AppFileSizes = () => {
const appSize = useAppSize();
if ( !appSize ) {
return null;
}
return (
<>
{Object.entries( appSize ).map( ( [directoryName, directoryEntrySizes] ) => (
<DirectoryFileSizes
key={directoryName}
directoryName={directoryName}
directoryEntrySizes={directoryEntrySizes}
/>
) )}
</>
);
};
const Developer = () => {
const toggleRTLandLTR = async ( ) => {
const { isRTL, forceRTL } = I18nManager;
await forceRTL( !isRTL );
@@ -196,7 +230,7 @@ const Developer = (): Node => {
<P>
<CODE>{getUserAgent()}</CODE>
</P>
{displayFileSizes( )}
<AppFileSizes />
<H1>Log file contents</H1>
<Button
level="focus"

View File

@@ -10,22 +10,28 @@ import { useEffect, useState } from "react";
import { Platform } from "react-native";
import RNFS from "react-native-fs";
export type DirectoryEntrySize = {
name: string;
size: number;
}
// https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
export function formatSizeUnits( bytes ) {
export function formatSizeUnits( bytes: number ) {
let humanReadableSize = "";
if ( bytes >= 1073741824 ) {
bytes = `${( bytes / 1073741824 ).toFixed( 2 )} GB`;
humanReadableSize = `${( bytes / 1073741824 ).toFixed( 2 )} GB`;
} else if ( bytes >= 1048576 ) {
bytes = `${( bytes / 1048576 ).toFixed( 2 )} MB`;
humanReadableSize = `${( bytes / 1048576 ).toFixed( 2 )} MB`;
} else if ( bytes >= 1024 ) {
bytes = `${( bytes / 1024 ).toFixed( 2 )} KB`;
humanReadableSize = `${( bytes / 1024 ).toFixed( 2 )} KB`;
} else if ( bytes > 1 ) {
bytes += " bytes";
humanReadableSize = `${bytes} bytes`;
} else if ( bytes === 1 ) {
bytes += " byte";
humanReadableSize = `${bytes} byte`;
} else {
bytes = "0 bytes";
humanReadableSize = "0 bytes";
}
return bytes;
return humanReadableSize;
}
const sharedDirectories = [
@@ -89,58 +95,66 @@ const androidDirectories = [
}
];
export const directories = Platform.OS === "android"
? sharedDirectories.concat( androidDirectories )
: iOSDirectories.concat( sharedDirectories );
export const directories = sharedDirectories.concat( Platform.OS === "android"
? androidDirectories
: iOSDirectories );
export function formatAppSizeString( name, size ) {
export function formatAppSizeString( name: string, size: number ): string {
return `${name}: ${formatSizeUnits( size )}`;
}
export async function getDirectoryContentSizes( directory ) {
const contents = await RNFS.readDir( directory );
const sortedContents = _.orderBy( contents, "size", "desc" );
return sortedContents.map( ( { name, size } ) => ( {
export async function getDirectoryEntrySizes( directory: string ): Promise<DirectoryEntrySize[]> {
const entries = await RNFS.readDir( directory );
const sortedEntries = _.orderBy( entries, "size", "desc" );
return sortedEntries.map( ( { name, size } ) => ( {
name,
size
} ) );
}
export function getTotalDirectorySize( directory ) {
let totalSizes = 0;
export function getTotalDirectorySize( directoryItems: DirectoryEntrySize[] ): number {
const totalSize = directoryItems
.map( item => item.size )
.reduce( ( sum, current ) => sum + current, 0 );
directory.forEach( item => {
totalSizes += item.size;
} );
return totalSizes;
return totalSize;
}
const useAppSize = ( ) => {
const [fileSizes, setFileSizes] = useState( { } );
type AppSize = {
[directoryName: string]: DirectoryEntrySize[]
}
useEffect( ( ) => {
const fetchAppSize = async ( ) => {
// using some funky code here because react will only update state
// once while in a for loop, so we need to create the whole fileSize
// object and then setState to have this update the Developer UI
// on first render. feel free to rewrite if there's a better way to do this
const tempFileSizes = { };
const size = await Promise.all( directories.map( async ( { directoryName, path } ) => {
const pathExists = await RNFS.exists( path );
if ( !pathExists ) { return null; }
const contentSizes = await getDirectoryContentSizes( path );
tempFileSizes[directoryName] = contentSizes;
return tempFileSizes;
} ) );
if ( !_.isEmpty( size[0] ) ) {
setFileSizes( size[0] );
}
};
fetchAppSize( );
async function fetchAppSize(): Promise<AppSize> {
const maybeExistingDirectories = await Promise.all(
directories.map( async directory => ( {
directory,
exists: await RNFS.exists( directory.path )
} ) )
);
const existingDirectories = maybeExistingDirectories
.filter( dir => dir.exists )
.map( dir => dir.directory );
const directoryToDirectorySizesKvps = await Promise.all(
existingDirectories.map( async dir => {
const directoryEntrySizes = await getDirectoryEntrySizes( dir.path );
return [dir.directoryName, directoryEntrySizes] as [string, DirectoryEntrySize[]];
} )
);
const directorySizesByDirectory = Object.fromEntries( directoryToDirectorySizesKvps );
return directorySizesByDirectory;
}
export default function useAppSize(): null | AppSize {
const [appSize, setAppSize] = useState<null | AppSize>( null );
useEffect( () => {
async function fetchAndSetAppSize() {
const appSize = await fetchAppSize();
setAppSize( appSize );
}
fetchAndSetAppSize();
}, [] );
return fileSizes;
};
export default useAppSize;
return appSize;
}