Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For examples, see https://observablehq.com/@observablehq/standard-library.

* [DOM](#dom) - create HTML and SVG elements.
* [Files](#files) - read local files into memory.
* [FileAttachments](#file_attachments) - read remote files.
* [Generators](#generators) - utilities for generators and iterators.
* [Promises](#promises) - utilities for promises.
* [require](#require) - load third-party libraries.
Expand Down Expand Up @@ -273,6 +274,100 @@ A data URL may be significantly less efficient than [URL.createObjectURL](https:
}
```

### File Attachments

See [File Attachments](https://observablehq.com/@observablehq/file-attachments) on Observable for examples.

<a href="#FileAttachments" name="FileAttachments">#</a> <br>FileAttachments</b>(<i>resolve</i>) [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

The **FileAttachments** function exported by the standard library is an abstract class that can be used to fetch files by name from remote URLs. To make it concrete, you call it with a *resolve* function, which is an async function that takes a *name* and returns a URL at which the file of that name may be loaded. For example:

```js
const FileAttachment = FileAttachments((name) =>
`https://my.server/notebooks/demo/${name}`
);
```

Or, with a more complex example, calling an API to produce temporary URLs:

```js
const FileAttachment = FileAttachments(async (name) =>
if (cachedUrls.has(name)) return cachedUrls.get(name);
const url = await fetchSignedFileUrl(notebookId, name);
cachedUrls.set(name, url);
return url;
);
```

Once you have your **FileAttachment** function defined, you can call it from notebook code:

```js
photo = FileAttachment("sunset.jpg")
```

FileAttachments work similarly to the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), providing methods that return promises to the file’s contents in a handful of convenient forms.

<a href="#FileAttachment_url" name="FileAttachment_url">#</a> FileAttachment(name).<b>url</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to the URL at which the file may be retrieved.

```js
const url = await FileAttachment("file.txt").url();
```

<a href="#FileAttachment_text" name="FileAttachment_text">#</a> FileAttachment(name).<b>text</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to the file’s contents as a JavaScript string.

```js
const data = d3.csvParse(await FileAttachment("cars.csv").text());
```

<a href="#FileAttachment_json" name="FileAttachment_json">#</a> FileAttachment(name).<b>json</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to the file’s contents, parsed as JSON into JavaScript values.

```js
const logs = await FileAttachment("weekend-logs.json").json();
```

<a href="#FileAttachment_image" name="FileAttachment_image">#</a> FileAttachment(name).<b>image</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to a file loaded as an [HTML Image](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image). Note that the promise won't resolve until the image has finished loading — making this a useful value to pass to other cells that need to process the image, or paint it into a `<canvas>`.

```js
const image = await FileAttachment("sunset.jpg").image();
```

<a href="#FileAttachment_arrayBuffer" name="FileAttachment_arrayBuffer">#</a> FileAttachment(name).<b>arrayBuffer</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to the file’s contents as an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).

```js
const city = shapefile.read(await FileAttachment("sf.shp").arrayBuffer());
```

<a href="#FileAttachment_stream" name="FileAttachment_stream">#</a> FileAttachment(name).<b>stream</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to a [Stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) of the file’s contents.

```js
const stream = await FileAttachment("metrics.csv").stream();
const reader = stream.getReader();
let done, value;
while (({done, value} = await reader.read()), !done) {
yield value;
}
```

<a href="#FileAttachment_blob" name="FileAttachment_blob">#</a> FileAttachment(name).<b>blob</b>() [<>](https://github.com/observablehq/stdlib/blob/master/src/fileAttachment.js "Source")

Returns a promise to a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) containing the raw contents of the file.

```js
const blob = await FileAttachment("binary-data.dat").blob();
```

### Generators

<a href="#Generators_disposable" name="Generators_disposable">#</a> Generators.<b>disposable</b>(<i>value</i>, <i>dispose</i>) [<>](https://github.com/observablehq/stdlib/blob/master/src/generators/disposable.js "Source")
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "@observablehq/stdlib",
"version": "3.1.1",
"version": "3.2.0-rc.5",
"publishConfig": {
"tag": "next"
},
"license": "ISC",
"main": "dist/stdlib.js",
"module": "src/index.js",
Expand All @@ -27,7 +30,7 @@
"dist/**/*.js"
],
"dependencies": {
"d3-require": "^1.2.0",
"d3-require": "^1.2.4",
"marked": "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/dom/uid.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function(name) {

function Id(id) {
this.id = id;
this.href = window.location.href + "#" + id;
this.href = new URL(`#${id}`, location) + "";
}

Id.prototype.toString = function() {
Expand Down
50 changes: 50 additions & 0 deletions src/fileAttachment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
async function remote_fetch(file) {
const response = await fetch(await file.url());
if (!response.ok) throw new Error(`Unable to load file: ${file.name}`);
return response;
}

class FileAttachment {
constructor(resolve, name) {
Object.defineProperties(this, {
_resolve: {value: resolve},
name: {value: name, enumerable: true}
});
}
async url() {
const url = await this._resolve(this.name);
if (url == null) throw new Error(`Unknown file: ${this.name}`);
return url;
}
async blob() {
return (await remote_fetch(this)).blob();
}
async arrayBuffer() {
return (await remote_fetch(this)).arrayBuffer();
}
async text() {
return (await remote_fetch(this)).text();
}
async json() {
return (await remote_fetch(this)).json();
}
async stream() {
return (await remote_fetch(this)).body;
}
async image() {
const url = await this.url();
return new Promise((resolve, reject) => {
const i = new Image;
if (new URL(url, document.baseURI).origin !== new URL(location).origin) {
i.crossOrigin = "anonymous";
}
i.onload = () => resolve(i);
i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`));
i.src = url;
});
}
}

export default function FileAttachments(resolve) {
return (name) => new FileAttachment(resolve, name);
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export {default as FileAttachments} from "./fileAttachment";
export {default as Library} from "./library";
8 changes: 7 additions & 1 deletion test/index-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test } from "tap";
import Library from "../src/library";
import {Library, FileAttachments} from "../src";

test("new Library returns a library with the expected keys", async t => {
t.deepEqual(Object.keys(new Library()).sort(), [
Expand All @@ -19,3 +19,9 @@ test("new Library returns a library with the expected keys", async t => {
]);
t.end();
});

test("FileAttachments is exported by stdlib/index", t => {
t.equal(typeof FileAttachments, "function");
t.equal(FileAttachments.name, "FileAttachments");
t.end();
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -818,10 +818,10 @@ csstype@^2.2.0:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41"
integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==

d3-require@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/d3-require/-/d3-require-1.2.3.tgz#65a1c2137e69c5c442f1ca2a04f72a3965124c16"
integrity sha512-zfat3WTZRZmh7jCrTIhg4zZvivA0DZvez8lZq0JwYG9aW8LcSUFO4eiPnL5F2MolHcLE8CLfEt06sPP8N7y3AQ==
d3-require@^1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/d3-require/-/d3-require-1.2.4.tgz#59afc591d5089f99fecd8c45ef7539e1fee112b3"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yarnpkg.com?

integrity sha512-8UseEGCkBkBxIMouLMPONUBmU8DUPC1q12LARV1Lk/2Jwa32SVgmRfX8GdIeR06ZP+CG85YD3N13K2s14qCNyA==

dashdash@^1.12.0:
version "1.14.1"
Expand Down