Skip to content

Commit cf940e3

Browse files
authored
Merge pull request #12 from cwillisf/override-data-format
feat(load): Allow caller to specify data format for an asset fetch
2 parents 106b4ae + 19f4699 commit cf940e3

File tree

9 files changed

+60
-26
lines changed

9 files changed

+60
-26
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ var getAssetUrl = function (asset) {
5353
'https://assets.example.com/path/to/assets/',
5454
asset.assetId,
5555
'.',
56-
asset.assetType.runtimeFormat,
56+
asset.dataFormat,
5757
'/get/'
5858
];
5959
return assetUrlParts.join('');
@@ -71,7 +71,7 @@ If you're using ES6 you may be able to simplify all of the above quite a bit:
7171
```js
7272
storage.addWebSource(
7373
[AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound],
74-
asset => `https://assets.example.com/path/to/assets/${asset.assetId}.${asset.assetType.runtimeFormat}/get/`);
74+
asset => `https://assets.example.com/path/to/assets/${asset.assetId}.${asset.dataFormat}/get/`);
7575
```
7676

7777
Once the storage module is aware of the sources you need, you can start loading assets:

src/Asset.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Asset {
2525
/** @type {string} */
2626
this.assetId = assetId;
2727

28-
this.setData(data, dataFormat);
28+
this.setData(data, dataFormat || assetType.runtimeFormat);
2929

3030
/** @type {Asset[]} */
3131
this.dependencies = [];

src/AssetType.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const DataFormat = require('./DataFormat');
66
* @typedef {Object} AssetType - Information about a supported asset type.
77
* @property {string} contentType - the MIME type associated with this kind of data. Useful for data URIs, etc.
88
* @property {string} name - The human-readable name of this asset type.
9-
* @property {DataFormat} runtimeFormat - The format used for runtime, in-memory storage of this asset. For example, a
10-
* project stored in SB2 format on disk will be returned as JSON when loaded into memory.
9+
* @property {DataFormat} runtimeFormat - The default format used for runtime, in-memory storage of this asset. For
10+
* example, a project stored in SB2 format on disk will be returned as JSON when loaded into memory.
1111
* @property {boolean} immutable - Indicates if the asset id is determined by the asset content.
1212
*/
1313
const AssetType = {

src/DataFormat.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
22
* Enumeration of the supported data formats.
3-
* @type {Object.<string,string>}
3+
* @enum {string}
44
*/
55
const DataFormat = {
6+
JPG: 'jpg',
67
JSON: 'json',
78
PNG: 'png',
89
SB2: 'sb2',

src/Helper.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ class Helper {
1111
* Fetch an asset but don't process dependencies.
1212
* @param {AssetType} assetType - The type of asset to fetch.
1313
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
14+
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
1415
* @return {Promise.<Asset>} A promise for the contents of the asset.
1516
*/
16-
load (assetType, assetId) {
17-
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId}`));
17+
load (assetType, assetId, dataFormat) {
18+
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId} with format ${dataFormat}`));
1819
}
1920
}
2021

src/LocalHelper.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ class LocalHelper extends Helper {
2121
* Fetch an asset but don't process dependencies.
2222
* @param {AssetType} assetType - The type of asset to fetch.
2323
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
24+
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
2425
* @return {Promise.<Asset>} A promise for the contents of the asset.
2526
*/
26-
load (assetType, assetId) {
27+
load (assetType, assetId, dataFormat) {
2728
return new Promise((fulfill, reject) => {
28-
const fileName = [assetId, assetType.runtimeFormat].join('.');
29+
const fileName = [assetId, dataFormat].join('.');
2930
localforage.getItem(fileName).then(
3031
data => {
3132
if (data === null) {
3233
fulfill(null);
3334
} else {
34-
fulfill(new Asset(assetType, assetId, assetType.runtimeFormat, data));
35+
fulfill(new Asset(assetType, assetId, dataFormat, data));
3536
}
3637
},
3738
error => {

src/ScratchStorage.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
const Asset = require('./Asset');
2-
const AssetType = require('./AssetType');
31
const BuiltinHelper = require('./BuiltinHelper');
42
const LocalHelper = require('./LocalHelper');
53
const WebHelper = require('./WebHelper');
64

5+
const _Asset = require('./Asset');
6+
const _AssetType = require('./AssetType');
7+
const _DataFormat = require('./DataFormat');
8+
79
class ScratchStorage {
810
constructor () {
911
this.defaultAssetId = {};
@@ -20,15 +22,23 @@ class ScratchStorage {
2022
* @constructor
2123
*/
2224
get Asset () {
23-
return Asset;
25+
return _Asset;
2426
}
2527

2628
/**
2729
* @return {AssetType} - the list of supported asset types.
2830
* @constructor
2931
*/
3032
get AssetType () {
31-
return AssetType;
33+
return _AssetType;
34+
}
35+
36+
/**
37+
* @return {DataFormat} - the list of supported data formats.
38+
* @constructor
39+
*/
40+
get DataFormat () {
41+
return _DataFormat;
3242
}
3343

3444
/**
@@ -37,7 +47,7 @@ class ScratchStorage {
3747
* @constructor
3848
*/
3949
static get Asset () {
40-
return Asset;
50+
return _Asset;
4151
}
4252

4353
/**
@@ -46,7 +56,7 @@ class ScratchStorage {
4656
* @constructor
4757
*/
4858
static get AssetType () {
49-
return AssetType;
59+
return _AssetType;
5060
}
5161

5262
/**
@@ -94,23 +104,25 @@ class ScratchStorage {
94104
* Fetch an asset by type & ID.
95105
* @param {AssetType} assetType - The type of asset to fetch. This also determines which asset store to use.
96106
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
107+
* @param {DataFormat} [dataFormat] - Optional: load this format instead of the AssetType's default.
97108
* @return {Promise.<Asset>} A promise for the requested Asset.
98109
* If the promise is fulfilled with non-null, the value is the requested asset or a fallback.
99110
* If the promise is fulfilled with null, the desired asset could not be found with the current asset sources.
100111
* If the promise is rejected, there was an error on at least one asset source. HTTP 404 does not count as an
101112
* error here, but (for example) HTTP 403 does.
102113
*/
103-
load (assetType, assetId) {
114+
load (assetType, assetId, dataFormat) {
104115
/** @type {Helper[]} */
105116
const helpers = [this.builtinHelper, this.localHelper, this.webHelper];
106117
const errors = [];
107118
let helperIndex = 0;
119+
dataFormat = dataFormat || assetType.runtimeFormat;
108120

109121
return new Promise((fulfill, reject) => {
110122
const tryNextHelper = () => {
111123
if (helperIndex < helpers.length) {
112124
const helper = helpers[helperIndex++];
113-
helper.load(assetType, assetId)
125+
helper.load(assetType, assetId, dataFormat)
114126
.then(
115127
asset => {
116128
if (asset === null) {

src/WebHelper.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ class WebHelper extends Helper {
3838
* Fetch an asset but don't process dependencies.
3939
* @param {AssetType} assetType - The type of asset to fetch.
4040
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
41+
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
4142
* @return {Promise.<Asset>} A promise for the contents of the asset.
4243
*/
43-
load (assetType, assetId) {
44+
load (assetType, assetId, dataFormat) {
4445

4546
/** @type {Array.<{url:string, result:*}>} List of URLs attempted & errors encountered. */
4647
const errors = [];
4748
const sources = this.sources.slice();
48-
const asset = new Asset(assetType, assetId);
49+
const asset = new Asset(assetType, assetId, dataFormat);
4950
let sourceIndex = 0;
5051

5152
return new Promise((fulfill, reject) => {
@@ -77,7 +78,7 @@ class WebHelper extends Helper {
7778
}
7879
tryNextSource();
7980
} else {
80-
asset.setData(response.body, assetType.runtimeFormat);
81+
asset.setData(response.body, dataFormat);
8182
fulfill(asset);
8283
}
8384
},

test/integration/download-known-assets.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ test('constructor', t => {
1616
* @typedef {object} AssetTestInfo
1717
* @property {AssetType} type - The type of the asset.
1818
* @property {string} id - The asset's unique ID.
19+
* @property {DataFormat} [ext] - Optional: the asset's data format / file extension.
1920
*/
2021
const testAssets = [
2122
{
@@ -33,11 +34,29 @@ const testAssets = [
3334
id: 'f88bf1935daea28f8ca098462a31dbb0', // cat1-a
3435
md5: 'f88bf1935daea28f8ca098462a31dbb0'
3536
},
37+
{
38+
type: storage.AssetType.ImageVector,
39+
id: '6e8bd9ae68fdb02b7e1e3df656a75635', // cat1-b
40+
md5: '6e8bd9ae68fdb02b7e1e3df656a75635',
41+
ext: storage.DataFormat.SVG
42+
},
3643
{
3744
type: storage.AssetType.ImageBitmap,
3845
id: '7e24c99c1b853e52f8e7f9004416fa34', // squirrel
3946
md5: '7e24c99c1b853e52f8e7f9004416fa34'
4047
},
48+
{
49+
type: storage.AssetType.ImageBitmap,
50+
id: '66895930177178ea01d9e610917f8acf', // bus
51+
md5: '66895930177178ea01d9e610917f8acf',
52+
ext: storage.DataFormat.PNG
53+
},
54+
{
55+
type: storage.AssetType.ImageBitmap,
56+
id: 'fe5e3566965f9de793beeffce377d054', // building at MIT
57+
md5: 'fe5e3566965f9de793beeffce377d054',
58+
ext: storage.DataFormat.JPG
59+
},
4160
{
4261
type: storage.AssetType.Sound,
4362
id: '83c36d806dc92327b9e7049a565c6bff', // meow
@@ -59,7 +78,7 @@ test('addWebSource', t => {
5978
t.doesNotThrow(() => {
6079
storage.addWebSource(
6180
[storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound],
62-
asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.assetType.runtimeFormat}/get/`
81+
asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
6382
);
6483
});
6584
t.end();
@@ -82,12 +101,11 @@ test('load', t => {
82101
for (var i = 0; i < testAssets.length; ++i) {
83102
const assetInfo = testAssets[i];
84103

85-
const promise = storage.load(assetInfo.type, assetInfo.id);
104+
var promise = storage.load(assetInfo.type, assetInfo.id, assetInfo.ext);
86105
t.type(promise, 'Promise');
87106

107+
promise = promise.then(asset => checkAsset(assetInfo, asset));
88108
promises.push(promise);
89-
90-
promise.then(asset => checkAsset(assetInfo, asset));
91109
}
92110

93111
return Promise.all(promises);

0 commit comments

Comments
 (0)