mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
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:
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user