diff --git a/src/components/file-browser/file-browser.tsx b/src/components/file-browser/file-browser.tsx index 1a14a49..576c0bb 100644 --- a/src/components/file-browser/file-browser.tsx +++ b/src/components/file-browser/file-browser.tsx @@ -112,7 +112,7 @@ export function FileBrowser({ path: initialPath, config, className }: Props) { export default function PathFileBrowser() { // These values provide the initial state for the application - // It is not necessary for them to be up-to-date + // It is not necessary for them to update the FileBrowser on change, so we only get them once. const { value: homeDirectory, loading: homeDirectoryLoading } = useAwaitValue(() => FileSystemClient.instance.getUserHomeDirectory()); const { value: config, loading: configLoading } = useAwaitValue(() => Configuration.instance.getOptions()); diff --git a/src/components/file-list/icon-image.tsx b/src/components/file-list/icon-image.tsx index 3012dc0..c73a3a6 100644 --- a/src/components/file-list/icon-image.tsx +++ b/src/components/file-list/icon-image.tsx @@ -1,6 +1,7 @@ import useAwaitValue from "app/hooks/useAwaitValue"; import FileSystemClient from "app/services/filesystem-client"; import ImageFile from "../file-view/image-file"; +import Rectangle from "../loading/rectangle"; type Props = Omit< React.DetailedHTMLProps, HTMLImageElement>, @@ -9,9 +10,8 @@ type Props = Omit< export default function IconImage({ src, width, height, ...otherProps }: Props) { const { value: cachedImagePath, loading, error } = useAwaitValue(() => FileSystemClient.instance.getImageIconPath(src, width, height)); - // TODO loading UI if (loading) { - return
loading
; + return } if (error) { diff --git a/src/components/file-view/file-view.tsx b/src/components/file-view/file-view.tsx index 3193dfe..7e0646d 100644 --- a/src/components/file-view/file-view.tsx +++ b/src/components/file-view/file-view.tsx @@ -8,8 +8,8 @@ import useAwaitValue from "app/hooks/useAwaitValue"; import FileSystemClient from "app/services/filesystem-client"; import VideoFile from "./video-file"; import Layout from "./layout"; -import PathClient from "app/services/path"; import HeicImageFile from "./heic-image-file"; +import Rectangle from "../loading/rectangle"; interface Props { file: AppFile; @@ -64,9 +64,12 @@ export default function FileView({ file: partialFile }: Props) { ); } - // TODO better loading UI if (loading || !file) { - return
Loading
; + return ( + + + + ); } if (!viewLargeFile && isTooBig(file.size)) { diff --git a/src/components/file-view/text-file.tsx b/src/components/file-view/text-file.tsx index f157b8a..3ce5e37 100644 --- a/src/components/file-view/text-file.tsx +++ b/src/components/file-view/text-file.tsx @@ -1,19 +1,32 @@ import FileSystemClient from "app/services/filesystem-client"; import useAwaitValue from "app/hooks/useAwaitValue"; import { AppFile } from "app/types/filesystem"; +import { RectangleList } from "../loading/rectangle-list"; +import { useWindowSize } from "@uidotdev/usehooks"; +import { useMemo } from "react"; interface Props { file: AppFile; } +const HEIGHT = 12; +const GAP = 10; + export default function TextFile({ file }: Props) { - const { value: content, loading } = useAwaitValue( - () => FileSystemClient.instance.getTextFileContext(file.path) - ); + const { value: content, loading } = useAwaitValue(() => FileSystemClient.instance.getTextFileContext(file.path)); + + const { height: windowHeight } = useWindowSize(); + const count = useMemo(() => { + if (windowHeight === null) { + return 10; + } + + // Rough calculation to make sure we have nough bars to cover the ui + return Math.max(10, Math.floor((windowHeight - 40) / (HEIGHT + GAP))); + }, [windowHeight]); - // TODO better loading ui if (loading) { - return
Loading
; + return } return ( diff --git a/src/components/loading/rectangle-list.tsx b/src/components/loading/rectangle-list.tsx new file mode 100644 index 0000000..18c43ed --- /dev/null +++ b/src/components/loading/rectangle-list.tsx @@ -0,0 +1,19 @@ +import Rectangle from "./rectangle"; + +interface Props { + width?: number | string; + height?: number | string; + count: number; + gap: number; + className?: string; +} + +export function RectangleList({ width, height, count, gap, className }: Props) { + return ( +
+ {(new Array(count).fill(0)).map((_, index) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/components/loading/rectangle.tsx b/src/components/loading/rectangle.tsx new file mode 100644 index 0000000..0089a69 --- /dev/null +++ b/src/components/loading/rectangle.tsx @@ -0,0 +1,11 @@ +interface Props { + width?: number | string; + height?: number | string; +} + +export default function Rectangle({ width, height }: Props) { + return ( +
+
+ ); +} \ No newline at end of file diff --git a/src/server/image.ts b/src/server/image.ts index 8791877..f15b51a 100644 --- a/src/server/image.ts +++ b/src/server/image.ts @@ -12,8 +12,13 @@ export async function createIcon(inputPath: string, outputPath: string, width: n const inputBuffer = await readFile(inputPath); const { data, width: imageWidth, height: imageHeight } = await decode({ buffer: inputBuffer }); - // TODO 4 is a guess - need to validate - await sharp(data, { raw: { width: imageWidth, height: imageHeight, channels: 4 } }).resize(width, height).jpeg().toFile(outputPath); + // data is returned as type ImageData, which has 4 channels RGBA + // https://github.com/catdad-experiments/heic-decode + // "When the images are decoded, the return value is a plain object in the format of ImageData" + // https://developer.mozilla.org/en-US/docs/Web/API/ImageData/data + await sharp(data, { + raw: { width: imageWidth, height: imageHeight, channels: 4 } + }).resize(width, height).jpeg().toFile(outputPath); } else { await sharp(inputPath).resize(width, height).jpeg().toFile(outputPath); } diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..c44f34b --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,5 @@ +export default function sleep(milliseconds: number): Promise { + return new Promise((resolve) => { + setTimeout(() => resolve(), milliseconds); + }); +} \ No newline at end of file