Skip to content

Commit

Permalink
TS Refactor!
Browse files Browse the repository at this point in the history
Well, kinda. It's JSDoc JavaScript, haha. Now that I have editor type-safety, I redid a buch of the code where it was starting to stink XD

Found some confusing recursive function calls along the way, and having type checking made it much easier to tell what the kinds of data I was working with. Some examples of this were the interactions between `readdir()` and `dirtree()`, which had some extra handling and recursive calls that weren't needed anymore. I removed the `Array.prototype.flat(Infinity)` calls alongside that too, and that helped slim things down. I wasn't using the recursive option anywhere in the codebase, so it didn't have to be around anymore. It was making those two functions more complicated than they had to be, too, and now it's more straightforward as to what they each do.

I also simplified the `DirTree` interface, now it only has the `value` property, rather than two optional properties, `value` and `handle`, which would appear in separate situations. Now it's simply just a union type!

I love it! Opening up this codebase again is really cool, I've learned a lot about a ton of different things since I started making this, so now I'm thinking of how I can build this app in a different way now, in a more TypeScript-y way or something, haha.

This was my first ESM-based web app structure, so I had only scratched the surface of breaking up the different app components into modules. Now I have a much better idea of what should be grouped together, and what shouldn't. It also makes way more sense of how to hook differnt things together, and how you should break them apart. This was a big slow down for STE I think, looking back at it now. I would write big functions that do a lot of things, and then call each of those from the top level. If the stuff the function did had to be used somewhere else, I had to add a different access point into the function. Now I try to build my things more modularly, so then you can use the smaller parts in other places, rather than only on the inside of the implementation. Hopefully that makes sense? I think that's one of the best things I've learned with ESM actually, how to break up your code into recyclable pieces, rather than having to tape together the outside so it can fit into tons of different locations. Something like that! (Thanks, Marco)

Learned about how to narrow types with `Array.prototype.filter()` and TypeScript/JSDoc! It's not too complex on the TypeScript side, since you have Generic parameters, but I couldn't figure out how to pass a type param to a `.filter()` call using JSDoc. These links present a very similar way to narrow the type returned by the `.filter()` call, it's extra smart!

microsoft/TypeScript#20812 (comment)
https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates (Just the TS docs for Type Predicates)
https://stackoverflow.com/questions/43118692/typescript-filter-out-nulls-from-an-array/51577579#51577579

I was trying to do the same thing with something like `(fileHandle: FileSystemHandle) => fileHandle is not null`, but `is not` isn't a thing you can do. The inverse works just the same, and it's actually more explicit! `(fileHandle: FileSystemHandle) => fileHandle is FileSystemHandle` Yay!
  • Loading branch information
Offroaders123 committed Dec 27, 2022
1 parent ab3ab9f commit 2af5169
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 36 deletions.
28 changes: 18 additions & 10 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const title = /** @type { HTMLElement } */ (document.querySelector("#title"));
// createTree({ tree: library });

/**
* Creates a DOM tree that mirrors the content of the given DirTree object.
*
* @param { { tree: fs.DirTree[]; parent?: HTMLElement; } } options
*/
function createTree({ tree, parent = main }){
Expand All @@ -44,46 +46,52 @@ function createTree({ tree, parent = main }){
const content = document.createElement("section");
const opener = document.createElement("button");

if (value){
if (!(value instanceof FileSystemFileHandle)){
// details.open = true;
summary.textContent = name;
createTree({ tree: value, parent: content });
details.append(summary,content);
parent.append(details);
} else {
opener.textContent = formatSong(name);
opener.addEventListener("click",() => playSong(/** @type { FileSystemFileHandle } */ (entry.handle)));
// opener.textContent = formatSong(name);
opener.textContent = name;

opener.addEventListener("click",() => playSong(value));
content.append(opener);
parent.append(content);
}
}
}

/**
* Plays a song from a given FileSystemFileHandle object.
*
* @param { FileSystemFileHandle } fileHandle
*/
async function playSong(fileHandle){
if (fileHandle instanceof FileSystemFileHandle !== true) return;
if (!(fileHandle instanceof FileSystemFileHandle)) return;

const file = await fileHandle.getFile();
const { name, type } = file;
if (!type.includes("audio")) return;

const song = window.URL.createObjectURL(file);
const song = URL.createObjectURL(file);
player.src = song;
title.textContent = formatSong(name);
await player.play();

const tags = await jsmediatags.read(file,{ artwork: true });

if ("mediaSession" in navigator){
navigator.mediaSession.metadata = new MediaMetadata(tags);
}
if (!tags.artwork) return art.src = "";
navigator.mediaSession.metadata = new MediaMetadata(tags);

art.src = tags.artwork[0].src;
if (tags.artwork !== undefined){
art.src = tags.artwork[0].src;
}
}

/**
* Formats the FileSystemFileHandle name string to be only the song name portion.
*
* @param { string } name
*/
function formatSong(name){
Expand Down
50 changes: 24 additions & 26 deletions src/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,33 @@
*
* @param { DataTransfer } dataTransfer
*/
export async function dataTransferHandles(dataTransfer){
const files = [...dataTransfer.items].filter(item => item.kind === "file");
const handles = /** @type { FileSystemHandle[] } */ ((await Promise.all(files.map(item => item.getAsFileSystemHandle()))).filter(item => item !== null));
return handles;
export async function dataTransferHandles({ items }){
const files = [...items].filter(item => item.kind === "file");
const handles = await Promise.all(files.map(item => item.getAsFileSystemHandle()));
const result = handles.filter(/** @returns { handle is FileSystemHandle } */ handle => handle !== null);
return result;
}

/**
* Gets all FileSystemHandle objects from within a given FileSystemDirectoryHandle object. If the recursive flag is set to true, all of the handles will be flattened to a singular, single-depth array of all of the directory's file handles.
* Gets all FileSystemHandle objects from a FileSystemDirectoryHandle object.
*
* @param { FileSystemDirectoryHandle | FileSystemHandle[] } directoryHandle
* @param { FileSystemDirectoryHandle } directoryHandle
*/
export async function readdir(directoryHandle,{ recursive = false } = {}){
/** @type { RecursiveHandleArray } */
export async function readdir(directoryHandle){
/** @type { FileSystemHandle[] } */
const handles = [];

for await (const handle of directoryHandle.values()){
handles.push(handle.kind === "directory" && recursive === true ? await readdir(/** @type { FileSystemDirectoryHandle } */ (handle),{ recursive }) : /** @type { FileSystemHandle } */ (handle));
handles.push(handle);
}
// @ts-expect-error
return /** @type { FileSystemHandle[] | RecursiveHandleArray } */ (recursive === true ? handles.flat(Infinity) : handles);

return handles;
}

/**
* @typedef DirTree
* @property { string } name
* @property { DirTree[] } [value]
* @property { FileSystemFileHandle } [handle]
* @property { DirTree[] | FileSystemFileHandle } value
*/

/**
Expand All @@ -37,22 +38,19 @@ export async function readdir(directoryHandle,{ recursive = false } = {}){
* @param { FileSystemDirectoryHandle | FileSystemHandle[] } directoryHandle
*/
export async function dirtree(directoryHandle){
const handles = /** @type { FileSystemHandle[] } */ (await readdir(directoryHandle));
const handles = (directoryHandle instanceof FileSystemDirectoryHandle) ? await readdir(directoryHandle) : directoryHandle;

/** @type { DirTree[] } */
const entries = await Promise.all(handles.map(async entry => {
const { name, kind } = entry;
const value = kind === "directory" ? await dirtree(/** @type { FileSystemDirectoryHandle } */ (entry)) : null;
const handle = kind === "file" ? /** @type { FileSystemFileHandle } */ (entry) : null;

return {
name,
...value && ({ value }),
...handle && ({ handle })
};
const { name } = entry;
const value = (entry instanceof FileSystemDirectoryHandle) ? await dirtree(entry) : /** @type { FileSystemFileHandle } */ (entry);
/** @type { DirTree } */
const result = { name, value };
return result;
}));

return sortArray(entries);
const result = sortArray(entries);

return result;
}

/**
Expand All @@ -71,7 +69,7 @@ function sortArray(array){
}

/**
* Returns a sanitized version of a given key string, without any "A" or "The" prefixes.
* Returns a sanitized version of a given key string, without any word prefixes. This is used to sort an array of DirTree objects by each entry's name property, alphabetically.
*
* @param { string } key
*/
Expand Down

0 comments on commit 2af5169

Please sign in to comment.