npm package: @nystik/foundry-file-utils
Description: A utility library for interfacing with Foundry's filesystem
Author: Nystik
Latest version: 0.7.3

![Latest Release)](https://img.shields.io/gitlab/v/release/dnd-5e/foundry-vtt/foundry-file-utils?color=dodgerblue&label=Latest)
![Foundry Core Supported Versions](https://img.shields.io/endpoint?url=https://foundryshields.com/version?url=https%3A%2F%2Fgitlab.com%2Fdnd-5e%2Ffoundry-vtt%2Ffoundry-file-utils%2F-%2Fraw%2Fmain%2Fsrc%2Fmodule.json)

# Foundry File Utils

> **NOTE: This project is in initial development and the API is subject to change. The API will be considered established on version 1.0.0 as per [Semantic Versioning Specification](https://semver.org/#spec-item-4).**

A collection of abstractions, helpers, and enhancements for interacting with files and directories in Foundry Virtual Tabletop

Includes:

- A FilePicker Svelte component that displays the source (and bucket in the case of s3 storage) of the path selected.
- A type constructor to easily use the improved FilePicker in settings, storing source, bucket, and path as json.
- Helper functions to handle various file operations like: creating directory paths, uploading files from URLs, etc.

# Installation

If you're using a bundler for your module you can use the CommonJS module available on NPM, complete with TypeScript definitions:

```
npm install --save-dev @nystik/foundry-file-utils
```

Otherwise you can set this module as a dependency in your manifest.

```json
"dependencies": [
  {
    "name": "foundry-file-utils",
    "manifest": "https://gitlab.com/dnd-5e/foundry-vtt/foundry-file-utils/-/raw/main/src/module.json",
  }
]
```

# Basic Usage

The library module is loaded on `init` and available at `ready`. It can be invoked in three different ways:

Using a global variable.

```typescript
window.FileUtils.createDirectory('path/to/dir')
```

By grabbing the module.

```typescript
const module = game.modules.get('foundry-file-utils')

if (module) {
  module.api.createDirectory('path/to/dir')
}
```

Or by using the hook `fileUtilsReady`.

```typescript
Hooks.on('fileUtilsReady', (fileUtils) => {
  fileUtils.createDirectory('path/to/dir')
})
```

## Using in settings

When registering settings, you can use the type constructor `Types.FilePicker(type)`; where `type` is a valid type for the built-in [FilePicker](https://foundryvtt.com/api/FilePicker.html). If type is left `undefined` then the FilePicker will let you pick any file type.

A default value of this FilePicker must be an object containing the properties: `path`, `activeSource`, and `bucket`.

```typescript
game.settings.register('my-module', 'upload-dir', {
  ...
  type: window.FileUtils.Types.FilePicker('folder'),
  default: {
    path: 'my-module/images',
    activeSource: 'data', // Can be 'data' (for user data), 'public' (for core data), or 's3' (for s3 buckets, duh)
    bucket: null
  },
})
```

```typescript
game.settings.register('my-module', 'sound-effect', {
  ...
  type: window.FileUtils.Types.FilePicker('audio'),
})
```

![Settings Screenshot](https://gitlab.com/dnd-5e/foundry-vtt/foundry-file-utils/-/raw/main/media/settings.jpg)

# API reference

### **`async createDirectory(source, path, [options])`**

Creates a directory path in the specified file source.

- `source`: a string of type [FilePicker.activeSource] (if running on the Forge, use `forgevtt` to target the assets storage).
- `path`: the path to the directory. Parent directories are created if they do not exist.
- `options` (optional): If `source` equals `s3`, this parameter is required.
  - `bucket`: the name of the s3 bucket

**Returns** (Promise): true if the directory was created, otherwise returns false.

```typescript
const success = await FileUtils.createDirectory('data', 'path/to/create')

// if source equals 's3' then a bucket name is required.
const success = await FileUtils.createDirectory('s3', 'path/to/create', { bucket: 'bucket-name' })
```

---

### **`async downloadFile(url, [filename])`**

Download a file and save it as the specified filename. If no filename is provided, it will attempt to infer the filename from the response data and url.

- `url`: url of the file.
- `filename` (optional): a filename without extension.

**Returns** (Promise): a [File] object, or `null` on a bad response.

```typescript
const file = await FileUtils.downloadFile('https://example.com/image.jpg')
// file.name == 'image.jpg'

const file = await FileUtils.downloadFile('https://example.com/image.jpg', 'download')
// file.name == 'download.jpg'

// e.g. content-type === "audio/aac"
const file = await FileUtils.downloadFile('https://example.com/birdnoise')
// file.name == 'birdnoise.aac'
```

---

### **`async uploadFile(source, path, file, [options])`**

Upload a file to a specified file source and path.

- `source`: a string of type [FilePicker.activeSource] (if running on the forge, use `forgevtt` to target the assets storage).
- `path`: path to the target directory.
- `file`: a [File] object.
- `options` (optional): If `source` equals `s3`, this parameter is required.
  - `bucket`: the name of the s3 bucket.

**Returns** (Promise): a Response object which also contains the path of the uploaded file as `response.path`.

```typescript
const response = await FileUtils.uploadFile('data', 'path/to/dir', file)

// if source equals 's3' then a bucket name is required.
const response = await FileUtils.uploadFile('data', 'path/to/dir', file, { bucket: 'bucket-name' })
```

---

### **`async uploadFileFromUrl([transform])(source, path, file, [options])(url, [filename])`**

Function composition to download a file from a url, perform an optional transform function, and then upload the file to foundry.

- `transform`: a transform function which takes a [File] object as a parameter, and returns a modified [File] object. Can be either synchronous or asynchronous.

---

- `source`: a string of type [FilePicker.activeSource] (if running on the forge, use `forgevtt` to target the assets storage).
- `path`: path to the target directory.
- `file`: a [File] object.
- `options` (optional): If `source` equals `s3`, this parameter is required.
  - `bucket`: the name of the s3 bucket.

---

- `url`: url to download.
- `filename` (optional): a filename without extension.

**Returns** (Promise): a Response object which also contains the path of the uploaded file as `response.path`.

```typescript
const crop = (file: File) => {
  // crop image
  ...
  return file
}

const response = await FileUtils.uploadFileFromUrl(crop)(
  's3',
  'tokens/cropped',
  { bucket: 'fvtt' }
  )('https://example.com/token.png', 'cropped_token')
// uploaded to tokens/cropped/cropped_token.png
```

```typescript
const upload = FileUtils.uploadFileFromUrl()('data', 'images/tokens')

// upload all files to 'images/tokens'
const promises = []
for (const url of links) {
  promises.push(upload(url))
}
await Promise.all(promises)
```

---

### **`fileFromBlob(blob, filename)`**

Create a [File] object from a given blob

- `url`: blob of data.
- `filename`: name of the file.

**Returns**: a [File] object.

---

### **`Classes.ExtendedFilePicker`**

A modified FilePicker class. The only difference between this picker and the built-in one is that when a file is selected (submitted) it sets the linked fields value to a json string containing `path`, `activeSource`, and `bucket` instead of just the `path`.

- `json object`:
  - `path`: path to file or directory
  - `activeSource`: the source identifier. e.g: `data`, `public` `s3`; additionally on the forge: `forge-bazaar` and `forgevtt`.
  - `bucket`: name of the s3 bucket, or `null` if source isn't `s3`

---

### **`Components.FilePicker`**

A Svelte component that displays the source of the selected path. Refer to [Svelte Documentation](https://svelte.dev/docs#Client-side_component_API) for more details on usage.

```typescript
const component = new FilePickerComponent({
  target: containerHTMLElement,
  props: {
    name: 'input-field-name',
    type: 'folder', // 'folder', 'image', 'imagevideo', 'video', 'audio' (if undefined, allows any file type).
    value: JSON.stringify({ path: 'my-module/tokens', activeSource: 'data' }), // if activeSource is 's3' you should also specify 'bucket'
  },
})
```

![Svelte Component Screenshot](https://gitlab.com/dnd-5e/foundry-vtt/foundry-file-utils/-/raw/main/media/svelte_component.jpg)

---

### **`Types.FilePicker(type)`**

A factory function for when registering settings

- `type`: a string of type [FilePicker.type](https://foundryvtt.com/api/FilePicker.html). If omitted the file picker will allow any file.

**Returns**: a named constructor function.

```typescript
game.settings.register('my-module', 'sound-effect', {
  ...
  type: window.FileUtils.Types.FilePicker('audio'),
})
```

---

### **`Types.TYPE_PREFIX`**

The prefix used by the type constructor, can be used in conjunction with `DOM.injectComponents` to inject the Svelte component into other html than just settings.

---

### **`DOM.injectComponents(html)`**

Injects the Svelte component into DOM. It replaces any input field with `data-dtype` matching `${TYPE_PREFIX}_${type}` where `type` is a [FilePicker.type](https://foundryvtt.com/api/FilePicker.html).

- `html`: the html to inject the component into.

---

# License

This project is licensed under the MIT license, see [LICENSE](https://gitlab.com/dnd-5e/foundry-vtt/foundry-file-utils/-/blob/main/LICENSE) for details.

[file]: https://developer.mozilla.org/en-US/docs/Web/API/File
[filepicker.activesource]: https://foundryvtt.com/api/FilePicker.html