Documentation

System

The system library is composed of functionality in several packages.


launch

system.launch (config) ->
  alert "App launched!"

  system.app.Base
    loadFile: ->
    newFile: ->
    saveData: ->

Launches the application bootstrapping a postmaster instance, adding host and client properties.


app

app.Base

app.Base(app)

Construct a Base application with paste and drop events already wired up.

The app object must include implementations for the following methods:

The app object is extended with the following methods:

config

app.config

baseStyle : boolean - whether to apply the base system stylesheet. Defaults to true.

# Opt out of default system stylesheet
app.config.baseStyle = false

confirmUnsaved

app.confirmUnsaved() Returns a promise that resolves if there are no unsaved changes or if there are unsaved changes then displays a modal that prompts the user to confirmed losing unsaved changes. The promise is fulfilled if the modal is confirmed, rejected otherwise.

currentPath

currentPath(string)

Observable representing the current path of the saved data. This is updated in open, new, and saveAs.

drop

drop(files)

A document drop event listener is automatically added that calls this method app.drop with e.dataTransfer.files if present. The default implementation is to call loadFile on the first file passed in.

exit

exit()

Sends the exit message to the system host application proxy.

extend

extend(object)

Add additional methods to app. Same as Object.assign(app, object).

hotkey

hotkey(key, method: string|function)

Bind a hotkey using Mousetrap to a given function or named function on app.

The function is called with app as this. If the function is a string then the property of app with that name is invoked as a function. In both cases the first argument is the event. e.preventDefault() will automatically be called before invoking the function for all hotkeys that trigger.

new

new()

Displays a modal prompting the user to confirm unsaved if there are any unsaved changes then calls newFile to set the app to an empty state if confirmed.

open

open()

Displays a modal prompting to confirm unsaved then opens a modal prompting for the path to open.

paste

paste(files)

A document paste event listener is automatically added that calls this method app.paste with e.dataTransfer.files if present. The default implementation is to call loadFile on the first file passed in.

save

save()

Gets the current state from saveData then writes blob to the host system at the current path.

saveAs

saveAs()

Displays a modal prompting for the path to save to then saves to that path if confirmed.

saved

saved(boolean)

Observable representing whether the application state is saved or not.

events

boot

dispose

aws

AWS provides an api for the server side of whimsy.space.

Cognito handles all the user credentials for AWS users. Each whimsy.space user has a cognito user. It also handles anonymous credentials. Users can sign up and authenticate. After they have authenticated the keys are cached in the browser's local storage. logout will delete the cached keys from local storage.

Cognito.authenticate

Cognito.authenticate(username, password) -> Promise

Authenticates a user by usernaem and password on AWS, caches credentials in local storage.

Cognito.cachedUser

Cognito.cachedUser() -> Promise

Refreshes the AWS credentials based on cached user token from local storage.

Cognito.logout

Cognito.logout() -> void

Deletes the cached local storage credentials so the user will no longer be logged in when they next visit the page.

Cognito.signUp

Cognito.signup(username, password, attributes)

api

api(path, params)

Execute an api request using the current aws credentials.

cdn

cdn(blob) -> Promise<urlSafeSha>

Uploads a file to the shared CDN using the current credentials. It returns a promise that resolves with a url-safe sha of the uploaded object.


fs

fs contains all the file systems as well as utility functions for files and paths.

absolutizePath

absolutizePath(basePath:String, path:String, preventEscape:Bool) -> String

Convert a path into an absolute path at the given base. Throws an error if the realative path uses '..' to be outside of basePath when preventEscape is true. preventEscape defaults to true.

absolutizePath("/Home/", "app.config") -> "/Home/app.config"

baseDirectory

baseDirectory(path:String) -> String

The directory portion of the path.

baseName("/public/danielx.net/index.html") -> "/public/danielx.net/"

baseName

baseName(path:String) -> String

The base name of a file. This strips all directories from the path.

baseName("/public/danielx.net/index.html") -> "index.html"

extensionFor

extensionFor(path:string) -> string

extensionFor("README.md") -> "md"
extensionFor("/public/index.html") -> "html"
extensionFor("notes") -> ""

normalizePath

normalizePath(path:String) -> String

normalizePath("/public/danielx.net/..") -> "/public/"
normalizePath("/public/./danielx.net/") -> "/public/danielx.net/"
normalizePath("/public///danielx.net/index.html") -> "/public/danielx.net/index.html"

separator

Constant representing character to separate directories. '/'

textMediaType

textMediaType(path:String) -> MediaType

textMediaType("style.css") -> "text/css; charset=utf-8"
textMediaType("index.html") -> "text/html; charset=utf-8"
textMediaType("index.js") -> "text/javascript; charset=utf-8"
textMediaType("index.json") -> "application/json; charset=utf-8"
textMediaType("README.md") -> "text/markdown; charset=utf-8"
textMediaType("app.coffee") -> "text/plain; charset=utf-8"

withoutAllExtensions

withoutAllExtensions(path:String) -> string

withoutAllExtensions("awesome.test.js") -> "awesome"

withoutExtension

withoutExtension(path:String) -> string

withoutExtension("awesome.test.js") -> "awesome.test"

Every file system has the following methods:

The primary file systems are backed by indexeddb using Dexie for local storage within the browser and S3 on AWS for persistence across machines.

There is also a special pkg file system that mounts a json package structure as a virtual directory. A compilers option can be given that will auto-compile the source files into the destination when they change.

The last file system is named mount which mounts any of the other systems at specific paths and proxys and rewrites the paths to them. It also rewrites and proxies the emitted events on the way out.

Paths are strings. Paths that end with a / are folders.

/ is the file separator character.

Every file system also emits the following events:

These events are emitted after a successful operation has completed. One common use for the events is refreshing a file in a window when the path is written, like the wiki viewer should update when the file it is rendering has updated.

The path in the event is a normalized absolute path in the file system.

read

Read a blob from a path, returns a promise fulfilled with the blob object. The blob is annotated with the path i.e.: blob.path == path

(path) -> Promise<blob>

write

Write a blob to a path, returns a promise that is fulfilled when the write succeeds.

(path, blob) -> Promise

delete

Delete a file at a path, returns a promise that is fulfilled when the delete succeeds.

(path) -> Promise

list

Returns a promise that contains an array of mixed FileEntry and FolderEntry objects. The list is not recursive but the folders can be recursed with a utility function.

(path) -> Promise<(FileEntry|FolderEntry)[]>

FileEntry Interface

path: string
size: number
type: string
updatedAt: Date

FolderEntry Interface

folder: true
path: string

fs.Dexie

fs.Dexie(databaseName:String) -> fs.Dexie

Create a database that uses the browser's local indexeddb. Any file type can be stored. The total storage space is dependent on the browser and the user's local system. Sometimes the db can be removed when cleaning up browser data for websites leaving nothing behind.

dfs = DexieFS("test")

dfs.write "/test.txt",
  new Blob ["heyy"],
    type: "text/plain"

fs.Mount

A filesystem that mounts and delegates to subsystems.

Example: Mount many different kinds of file systems to different paths.

{Mount, Dexie, Fdfs, Storage} = system.fs
fs = Mount()

fd = new FormData
fdfs = Fdfs(fd)

fs.mount "/", s3fs
fs.mount "/game/", fdfs
fs.mount "/local/", Storage(localStorage)
fs.mount "/session/", Storage(sessionStorage)
fs.mount "/local/indexeddb/", Dexie()

The longes path wins, so reading from /local/indexeddb/ will not touch the localStorage FS. Similarly all the other paths will not touch the S3FS, but any other path not listed will since the s3fs was mounted at the root "/".

fs.S3

fs.S3(id:IdentityId, bucket:AWS.S3, refreshCredentials:->Promise)

Creates a file system interface backed by an S3 bucket. The S3 bucket needs to be configured properly as well as the AWS Cognito Identity Pool. This is a one time setup for creating the storage for all users for whimsy.space. S3 Setup Doc

id the Cognito Identity Id for the user. bucket the instance of AWS.S3 bucket. refreshCredentials a function to call when credentials have expired that returns a promise that resolves successfully when the credentials have been refreshed. This is so we can retry reading/saving seamlessly if credentials expire.

Example mounting the S3 FS to /My Briefcase/ in the OS:

id = AWS.config.credentials.identityId

bucket = new AWS.S3
  params:
    Bucket: "whimsy-fs"

refreshCredentials = ->
  # This has the side effect of updating the global AWS object's credentials
  cognito.cachedUser()
  .then (AWS) ->
    # Copy the updated credentials to the bucket
    bucket.config.credentials = AWS.config.credentials
  .catch console.debug

s3fs = fs.S3(id, bucket, refreshCredentials)

os.fs.mount "/My Briefcase/", fs

pkg

crudeRequire

crudeRequire(program:string)

Simplest require of a common js style module with no dependencies. Evaluates the program and returns what is assigned to module.exports.

crudeRequire("""
  module.exports = "cool"
""") # -> "cool"

exec

exec(program:string, env, context)

Convenience wrapper around Function(args..., program).apply(context, params) to execute a program string within a given environment context and return the result.

htmlForPackage

htmlForPackage(pkg, opts) -> Blob

opts
  additionalDependencies: url[]
  code: string
  stylesheets: url[]
  systemConfig: SystemConfig

Construct an HTML file for the package.

The default behavior is to load the package's entry point but can be modified to run any file in the package by overriding the code option. The default is require('./main'). The code is wrapped with the system wrapper and the require wrapper. The system wrapper calls: require("!system").launch(...) with the code in the callback and the require wrapper sets up the module system so that require calls work.

It also adds remote dependencies to the HTML head and wraps with the system launch if present.

Package

Package data structure

source: Record<string, File>
distribution: Record<string, File>
config:
  name: string
  version: string
  title: string
  description: string
  lang: string
  iconURL: URL
  manifest: boolean
  dependencies: Record<string, URL>
dependencies: Record<string, Package>
entryPoint: string
remoteDependencies: URL[]
stylesheets: URL[]

type File =
  content: string

ui

applyStyle

applyStyle(styleContent:string, className:string)

Finds or creates the style element in document.head with the given class name then sets the content of that element to the passed in style string. By using the same class name you can have separate styles for system base styles, app styles, custom user styles, and only replace the ones that change which makes hot reloading easier.

window

window
  x: Observable<number>
  y: Observable<number>
  width: Observable<number>
  height: Observable<number>
  title: Observable<string>
  minimized: Observable<bool>
  maximized: Observable<bool>
  iconURL: Observable<url>
  iconEmoji: Observable<string>
  menuBar: MenuBar | Element
  content: Observable<Element>

Methods

Events

close

maximize

minimize

resize

util

Postmaster

Reference to the Postmaster library. Useful for setting up communication between iframes or sub-windows.

postmaster = Postmaster
  delegate:
    save: ->
    exit: ->
  remoteTarget: ->
    iframe.contentWindow

postmaster.invokeRemote method, params...
.then (result) ->

Receive messages by setting delegate. Send messages with invokeRemote. The iframe must have a properly set up Postmaster instance inside. The iframe can send messages to the delegate as it chooses.


OS

OS is the layer of operating system functionality built on top of the system libraries. It handles things like mounting file systems, launching applications, managing the desktop and windows, and communicating events between applications.


Jadelet

compile

compile(source, opts)

Compile a Jadelet template source string and return a string containing code that can be executed in common js format.

Ex.

module.exports = system.ui.Jadelet.exec(<parsedAst>);

This compiler is used in editors to create a distribution file from Jadelet source code. That's why it needs the exports and exec.

exec

exec(ast:string|JadeletAST) -> function(context) -> HTMLElement

If a string is given parse that string and return a render function. The render function takes a context object and return an hmtl element with bindings applied to the context.

parse

parse(source:string) -> JadeletAST

Parse a Jadelet source string and return an AST or throw an error.