packager/scaffolding-api-docs
2023-04-14 17:48:40 -05:00
..
README.md Use /api instead of /proxy in trampoline examples 2023-04-14 17:48:40 -05:00

Scaffolding API

Scaffolding's API is in an ALPHA state. It WILL change in ways that are backwards incompatible. Please pin to exact versions to avoid pain later!

This documentation is very work in progress. Report any errors you find or any issues you encounter.

Scaffolding is the project player used by the packager.

Here's what Scaffolding does for you:

  • Loads the entire Scratch VM and connects everything together
  • Forwards user input to Scratch and implements things like sprite dragging
  • Removes unnecessary parts of Scratch that you don't need just for running projects
  • Implements the "ask and wait" block
  • Implements highly optimized (much faster than scratch-gui) variable and list monitors with context menus and file dropping
  • Implements cloud variables using a Scratch-compatible WebSocket server or local storage
  • Implements video sensing

Here's what Scaffolding doesn't do for you:

  • Green flag screen
  • Controls
  • A progress bar

Versions

There are two versions of Scaffolding:

  • scaffolding-full (4.6MB) - contains every part of Scratch
  • scaffolding-min (2.5MB) - contains all of Scratch EXCEPT sound files used by the music extension. Significantly smaller than scaffolding-full.

Scratch is a large application so either script is very large. If you don't need the music extension to function, use scaffolding-min.

Supported environments

Scaffolding strives to support any web browser released in 2018 or later.

Scaffolding only runs in web browsers. It will not run in Node.js.

Content-Security-Policy

Meaningfully sandboxing Scaffolding with a Content-Security-Policy is hard. It needs to be able to fetch things from data: and blob: URLs, and the compiler (enabled by default) requires unsafe-eval.

This section will be updated later with more guidance.

Installing

Scaffolding is distributed as one big JavaScript file. Any supplemental CSS etc. is stored inside the JS file.

We make no promise of having a stable API between even minor releases as Scaffolding is effectively just an implementation detail of the packager, which is why it's very important to lock your application to specific versions.

You can only load one version of Scaffolding on a page, but you can create as many Scaffolding project players as you'd like.

From a CDN

<!-- for scaffolding-min: -->
<script src="https://cdn.jsdelivr.net/npm/@turbowarp/packager@0.0.0/dist/scaffolding/scaffolding-min.js"></script>

<!-- for scaffolding-full -->
<script src="https://cdn.jsdelivr.net/npm/@turbowarp/packager@0.0.0/dist/scaffolding/scaffolding-full.js"></script>

Replace 0.0.0 with the latest release from https://github.com/TurboWarp/packager/releases, for example 1.0.0.

DO NOT use versions like @1 or @latest -- your website will break if (when) Scaffolding's API changes!

If you don't want to use a CDN, you can download the JS file linked by the script tag to a server you control and simply load that script instead.

From npm

npm install --save-exact @turbowarp/packager
// for scaffolding-min:
require('@turbowarp/packager/dist/scaffolding/scaffolding-min.js');
// for scaffolding-full:
require('@turbowarp/packager/dist/scaffolding/scaffolding-full.js');

// or, if you prefer ES6 imports,
// for scaffolding-min:
import '@turbowarp/packager/dist/scaffolding/scaffolding-min.js';
// for scaffolding-full:
import '@turbowarp/packager/dist/scaffolding/scaffolding-full.js';

Note that regardless of how you import Scaffolding, it is exported on window.Scaffolding, not on the module. This is strange and weird; it may change in the future.

Usage

For an example, see ../static/example.html. You can also examine the output of files generated by the packager.

At this point you should have Scaffolding installed on window.Scaffolding.

Setup

Create an instance of Scaffolding:

const scaffolding = new Scaffolding.Scaffolding();

You should be able to have multiple of these on one page if you want.

Configuration

Certain Scaffolding options must be configured before you can finish setting up the Scratch environment. These are optional. The options that must be configured at this point are listed below along with their defaults.

scaffolding.width = 480; // Custom stage width
scaffolding.height = 360; // Custom stage height
scaffolding.resizeMode = 'preserve-ratio'; // or 'dynamic-resize' or 'stretch'
scaffolding.editableLists = false; // Affects list monitors

Finalize setup

This will actually create the Scratch VM, Renderer, Audio Engine, etc. and link them all together.

scaffolding.setup();

Put it in the DOM somewhere

You are responsible for making a location in the DOM for Scaffolding and using CSS to appropriately size it.

If your HTML looks like this:

<style>
  #project {
    width: 480px;
    height: 360px;
  }
</style>
<div id="project"></div>

You would append Scaffolding to the DOM like this:

scaffolding.appendTo(document.getElementById('project'));

Scaffolding will automatically resize the project player to fit inside the space you gave it. It listens for the window 'resize' event to do this. If you are resizing the element in a way that doesn't involve a window resize, you can manually call scaffolding.relayout() to force a resize. This can be slow, so don't do it when you don't need to.

Tell it where to fetch projects and assets from

You have to manually configure scratch-storage to know where to fetch files from. If you want to load projects from scratch.mit.edu, you would do:

const storage = scaffolding.storage;
storage.addWebStore(
  [storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound],
  (asset) => `https://assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
);

Downloading shared Scratch projects can be done manually with something like this:

const id = '437419376';
const projectMetadata = await (await fetch(`https://trampoline.turbowarp.org/api/projects/${id}`)).json();
const token = projectMetadata.project_token;
const projectData = await (await fetch(`https://projects.scratch.mit.edu/${id}?token=${token}`)).arrayBuffer();

Configure cloud variables

By default cloud variables are just normal variables. You must reconfigure this manually if you don't want that.

To synchronize cloud variables globally:

const cloudVariableProvider = scaffolding.addCloudProvider(new Scaffolding.Cloud.WebSocketProvider('wss://clouddata.example.com', 'PROJECT ID HERE'));

To store cloud variables locally:

const localStorageProvider = scaffolding.addCloudProvider(new Scaffolding.Cloud.LocalStorageProvider('UNIQUE STORAGE KEY (such as project ID) HERE'));

To use a different provider for different variables, there is an override system.

scaffolding.addCloudProviderOverride("☁ my variable", cloudVariableProvider);
scaffolding.addCloudProviderOverride("☁ other variable", localStorageProvider);

Set username

To change the value of the username block from the default empty string:

scaffolding.setUsername("ExampleUser");

Set accent color

To change the accent color used in prompts, context menus, etc.:

scaffolding.setAccentColor("#abcdef");

The color MUST be a hex color code with 6 characters. 3 character codes will not work. The leading # is required. Color names will also not work.

Load a project

At this point you can load a project. Scaffolding accepts an ArrayBuffer, Uint8Array, or string containing a full compressed project or a project.json.

Full compressed projects are full sb/sb2/sb3 files including assets.

project.json is just the JSON part of an sb2/sb3 file. In this case assets will be fetched separately.

scaffolding.loadProject(projectData)
  .then(() => {
    // ...
  })
  .catch((error) => {
    // ...
  });

This returns a Promise that resolves when the project has finished loading or rejects if the project could not be loaded.

The project is not automatically started when loadProject completes.

If you configured scratch-storage to load projects from scratch.mit.edu, you can use:

scaffolding.storage.load(Scaffolding.Storage.AssetType.Project, "PROJECT ID HERE eg. 104")
  .then((projectDataAsset) => scaffolding.load(projectDataAsset.data))
  .then(() => {
    // ...
  })
  .catch((error) => {
    // ...
  });

Start the project

This will start the project and start scripts that are supposed to run when you press the green flag.

If you want to automatically start the project, you can do:

scaffolding.loadProject(/* ... */) // see previous step
  .then(() => {
    scaffolding.start();
  });

If you want to display something like Scratch's green flag screen, you can create the screen yourself and call scaffolding.start() when the user clicks on it.

Advanced topics

Scaffolding gives you direct access to various internal classes.

Package Instance Constructor
scratch-vm scaffolding.vm Scaffolding.VM
scratch-render scaffolding.renderer Scaffolding.Renderer
scratch-storage scaffolding.storage Scaffolding.Storage
scratch-audio scaffolding.audioEngine Scaffolding.AudioEngine
JSZip N/A Scaffolding.JSZip

Constructors are immediately available when scaffolding-[full|min].js is loaded. Instances are available after scaffolding.setup() is called.

The scratch-audio instance might be null if the browser doesn't support the Web Audio API. This is pretty rare to not be supported, but a few people do turn it off for one reason or another.

The scratch-storage constructor is actually a wrapper used for implementing progress monitoring.

VM Settings

// Press the green flag (like Scratch's green flag button)
scaffolding.greenFlag();

// Stop all (like Scratch's stop sign button)
scaffolding.stopAll();

// Turbo Mode
scaffolding.vm.setTurboMode(true);

// Framerate
scaffolding.vm.setFramerate(60);

// Interpolation
scaffolding.vm.setInterpolation(true);

// High quality pen
scaffolding.renderer.setUseHighQualityRender(true);

// Turn off the compiler
scaffolding.vm.setRuntimeOptions({enabled: false});

// Infinite clones, Remove miscellaneous limits, Remove fencing
scaffolding.vm.setRuntimeOptions({fencing: false, miscLimits: false, maxClones: Infinity});

// Load custom extension
// Do this before calling loadProject() for best results.
scaffolding.vm.extensionManager.loadExtensionURL('https://extensions.turbowarp.org/fetch.js');

// Do something when the project stops
vm.runtime.on('PROJECT_RUN_STOP', () => {
  // ...
});

Get or set variables and lists

scaffolding.getVariable("global variable name"); // -> "Hello, world!"
scaffolding.getList("global list name"); // -> [1, 2, 3]

scaffolding.setVariable("global variable name", "New value");
scaffolding.setList("global list name", [4, 5, 6]);

Variables must be a number, string, or boolean, and lists can only contain numbers, strings, and booleans. The VM was not designed to handle these invalid values and will not behave properly. Invalid values will throw an error.

Lists returned by getList are returned by reference, so any changes you make to them will be reflected in the project. However, changes might not display on the any visible monitors unless you use setList.

Progress monitoring

Scaffolding can't help you monitor the progress of the data you feed into loadProject() -- you have to do that on your own. That might mean using an XMLHttpRequest to fetch the data so you can listen to the progress event.

Scaffolding can help you monitor asset download progress.

scaffolding.storage.onprogress = (totalAssets, loadedAssets) => {
  // ...
};

Again, you are responsible for displaying a progress bar on your own.

Fullscreen

Scaffolding doesn't have an API for fullscreen. Instead, just use the DOM fullscreen API on an element that contains scaffolding.

document.getElementById('project').requestFullScreen();

Advanced styling

You are free to override the styles for any class starting with sc-.

Addons

In the same place as scaffolding-[full|min].js, there is a file called addons.js that contains some extra features. You can load it the same way you load the normal scaffolding script. It exports on window.ScaffoldingAddons instead.

ScaffoldingAddons.run(scaffolding, {
  // Enable what you want.
  gamepad: false,
  pointerlock: false,
  specialCloudBehaviors: false,
  unsafeCloudBehaviors: false,
  pause: false, // Adds API: scaffolding.vm.setPaused(true)
});

Exporting sb3

Here's something to get you started:

Object.assign(document.createElement("a"), {
  href: URL.createObjectURL(await scaffolding.vm.saveProjectSb3()),
  download: 'project.sb3'
}).click();