1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-13 09:29:35 +00:00

scripting: fixed cache, io.module(), return values, button, blank()

This commit is contained in:
Steven Hugg 2021-08-23 13:12:02 -05:00
parent 6343c75953
commit 5542555193
9 changed files with 211 additions and 94 deletions

View File

@ -802,9 +802,8 @@ div.scripting-editor {
max-height: 50vw; max-height: 50vw;
} }
div.scripting-color { div.scripting-color {
padding: 0.5em; min-width: 2em;
min-width: 3em; min-height: 2em;
min-height: 3em;
border: 3px solid black; border: 3px solid black;
} }
div.scripting-color span { div.scripting-color span {
@ -836,3 +835,14 @@ div.scripting-select > .scripting-selected {
border-style: solid; border-style: solid;
border-color: #eee; border-color: #eee;
} }
div.scripting-cell button {
background-color: #333;
color: #fff;
padding: 0.5em;
}
div.scripting-cell button:hover {
border-color: #fff;
}
div.scripting-cell button.scripting-enabled {
background-color: #339999;
}

View File

@ -192,7 +192,7 @@ export class Environment {
function prkey() { return fullkey.join('.') } function prkey() { return fullkey.join('.') }
// go through all object properties recursively // go through all object properties recursively
for (var [key, value] of Object.entries(o)) { for (var [key, value] of Object.entries(o)) {
if (value == null && fullkey.length == 0 && !key.startsWith("$$")) { if (value == null && fullkey.length == 0 && !key.startsWith("$")) {
this.error(key, `"${key}" has no value.`) this.error(key, `"${key}" has no value.`)
} }
fullkey.push(key); fullkey.push(key);
@ -244,10 +244,22 @@ export class Environment {
start: loc.column, start: loc.column,
}] }]
} }
// TODO: Cannot parse given Error object // TODO: Cannot parse given Error object?
let frames = ErrorStackParser.parse(e); let frames = ErrorStackParser.parse(e);
let frame = frames.findIndex(f => f.functionName === 'anonymous'); let frame = frames.findIndex(f => f.functionName === 'anonymous');
let errors = []; let errors = [];
// if ErrorStackParser fails, resort to regex
if (frame < 0 && e.stack != null) {
let m = /.anonymous.:(\d+):(\d+)/g.exec(e.stack);
if (m != null) {
errors.push( {
path: this.path,
msg: e.message,
line: parseInt(m[1]) - LINE_NUMBER_OFFSET,
});
}
}
// otherwise iterate thru all the frames
while (frame >= 0) { while (frame >= 0) {
console.log(frames[frame]); console.log(frames[frame]);
if (frames[frame].fileName.endsWith('Function')) { if (frames[frame].fileName.endsWith('Function')) {
@ -261,6 +273,7 @@ export class Environment {
} }
--frame; --frame;
} }
// if no stack frames parsed, last resort error msg
if (errors.length == 0) { if (errors.length == 0) {
errors.push( { errors.push( {
path: this.path, path: this.path,

View File

@ -4,7 +4,7 @@ import * as fastpng from 'fast-png';
import { Palette } from './color'; import { Palette } from './color';
import * as io from './io' import * as io from './io'
import * as color from './color' import * as color from './color'
import { findIntegerFactors, RGBA } from '../../util'; import { coerceToArray, findIntegerFactors, RGBA } from '../../util';
export type PixelMapFunction = (x: number, y: number) => number; export type PixelMapFunction = (x: number, y: number) => number;
@ -17,17 +17,16 @@ export abstract class AbstractBitmap<T> {
public readonly height: number, public readonly height: number,
) { ) {
} }
abstract blank(width: number, height: number) : AbstractBitmap<T>; abstract blank(width: number, height: number) : AbstractBitmap<T>;
abstract setarray(arr: ArrayLike<number>) : AbstractBitmap<T>; abstract setarray(arr: ArrayLike<number>) : void;
abstract set(x: number, y: number, val: number) : AbstractBitmap<T>; abstract set(x: number, y: number, val: number) : void;
abstract get(x: number, y: number): number; abstract get(x: number, y: number): number;
abstract getrgba(x: number, y: number): number; abstract getrgba(x: number, y: number): number;
inbounds(x: number, y: number): boolean { inbounds(x: number, y: number): boolean {
return (x >= 0 && x < this.width && y >= 0 && y < this.height); return (x >= 0 && x < this.width && y >= 0 && y < this.height);
} }
assign(fn: ArrayLike<number> | PixelMapFunction) { assign(fn: ArrayLike<number> | PixelMapFunction) : void {
if (typeof fn === 'function') { if (typeof fn === 'function') {
for (let y=0; y<this.height; y++) { for (let y=0; y<this.height; y++) {
for (let x=0; x<this.width; x++) { for (let x=0; x<this.width; x++) {
@ -39,10 +38,11 @@ export abstract class AbstractBitmap<T> {
} else { } else {
throw new Error(`Illegal argument to assign(): ${fn}`) throw new Error(`Illegal argument to assign(): ${fn}`)
} }
return this;
} }
clone() : AbstractBitmap<T> { clone() : AbstractBitmap<T> {
return this.blank(this.width, this.height).assign((x,y) => this.get(x,y)); let bmp = this.blank(this.width, this.height);
bmp.assign((x,y) => this.get(x,y));
return bmp;
} }
crop(srcx: number, srcy: number, width: number, height: number) { crop(srcx: number, srcy: number, width: number, height: number) {
let dest = this.blank(width, height); let dest = this.blank(width, height);
@ -64,6 +64,13 @@ export abstract class AbstractBitmap<T> {
} }
} }
} }
fill(destx: number, desty: number, width:number, height:number, value:number) {
for (var y=0; y<height; y++) {
for (var x=0; x<width; x++) {
this.set(x+destx, y+desty, value);
}
}
}
} }
export class RGBABitmap extends AbstractBitmap<RGBABitmap> { export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
@ -80,11 +87,9 @@ export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
} }
setarray(arr: ArrayLike<number>) { setarray(arr: ArrayLike<number>) {
this.rgba.set(arr); this.rgba.set(arr);
return this;
} }
set(x: number, y: number, rgba: number) { set(x: number, y: number, rgba: number) {
if (this.inbounds(x,y)) this.rgba[y * this.width + x] = rgba; if (this.inbounds(x,y)) this.rgba[y * this.width + x] = rgba;
return this;
} }
get(x: number, y: number): number { get(x: number, y: number): number {
return this.inbounds(x,y) ? this.rgba[y * this.width + x] : 0; return this.inbounds(x,y) ? this.rgba[y * this.width + x] : 0;
@ -92,8 +97,8 @@ export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
getrgba(x: number, y: number): number { getrgba(x: number, y: number): number {
return this.get(x, y); return this.get(x, y);
} }
blank(width: number, height: number) : RGBABitmap { blank(width?: number, height?: number) : RGBABitmap {
return new RGBABitmap(width, height); return new RGBABitmap(width || this.width, height || this.height);
} }
clone() : RGBABitmap { clone() : RGBABitmap {
let bitmap = this.blank(this.width, this.height); let bitmap = this.blank(this.width, this.height);
@ -108,47 +113,55 @@ export abstract class MappedBitmap extends AbstractBitmap<MappedBitmap> {
constructor( constructor(
width: number, width: number,
height: number, height: number,
public readonly bitsPerPixel: number, public readonly bpp: number,
initial?: Uint8Array | PixelMapFunction initial?: Uint8Array | PixelMapFunction
) { ) {
super(width, height); super(width, height);
if (bitsPerPixel != 1 && bitsPerPixel != 2 && bitsPerPixel != 4 && bitsPerPixel != 8) if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8)
throw new Error(`Invalid bits per pixel: ${bitsPerPixel}`); throw new Error(`Invalid bits per pixel: ${bpp}`);
this.pixels = new Uint8Array(this.width * this.height); this.pixels = new Uint8Array(this.width * this.height);
if (initial) this.assign(initial); if (initial) this.assign(initial);
} }
setarray(arr: ArrayLike<number>) { setarray(arr: ArrayLike<number>) {
this.pixels.set(arr); this.pixels.set(arr);
return this;
} }
set(x: number, y: number, index: number) { set(x: number, y: number, index: number) {
if (this.inbounds(x,y)) this.pixels[y * this.width + x] = index; if (this.inbounds(x,y)) this.pixels[y * this.width + x] = index;
return this;
} }
get(x: number, y: number): number { get(x: number, y: number): number {
return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0; return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0;
} }
} }
function getbpp(x : number | Palette) : number {
if (typeof x === 'number') return x;
if (x instanceof Palette) {
if (x.colors.length <= 2) return 1;
else if (x.colors.length <= 4) return 2;
else if (x.colors.length <= 16) return 4;
}
return 8;
}
export class IndexedBitmap extends MappedBitmap { export class IndexedBitmap extends MappedBitmap {
public palette: Palette; public palette: Palette;
constructor( constructor(
width: number, width: number,
height: number, height: number,
bitsPerPixel: number, bppOrPalette: number | Palette,
initial?: Uint8Array | PixelMapFunction initial?: Uint8Array | PixelMapFunction
) { ) {
super(width, height, bitsPerPixel || 8, initial); super(width, height, getbpp(bppOrPalette), initial);
this.palette = color.palette.colors(1 << this.bitsPerPixel); this.palette = bppOrPalette instanceof Palette
? bppOrPalette
: color.palette.colors(1 << this.bpp);
} }
getrgba(x: number, y: number): number { getrgba(x: number, y: number): number {
return this.palette && this.palette.colors[this.get(x, y)]; return this.palette && this.palette.colors[this.get(x, y)];
} }
blank(width: number, height: number) : IndexedBitmap { blank(width?: number, height?: number, newPalette?: Palette) : IndexedBitmap {
let bitmap = new IndexedBitmap(width, height, this.bitsPerPixel); let bitmap = new IndexedBitmap(width || this.width, height || this.height, newPalette || this.palette);
bitmap.palette = this.palette;
return bitmap; return bitmap;
} }
clone() : IndexedBitmap { clone() : IndexedBitmap {
@ -184,6 +197,7 @@ export interface BitmapAnalysis {
} }
export function analyze(bitmaps: BitmapType[]) { export function analyze(bitmaps: BitmapType[]) {
bitmaps = coerceToArray(bitmaps);
let r = {min:{w:0,h:0}, max:{w:0,h:0}}; let r = {min:{w:0,h:0}, max:{w:0,h:0}};
for (let bmp of bitmaps) { for (let bmp of bitmaps) {
if (!(bmp instanceof AbstractBitmap)) return null; if (!(bmp instanceof AbstractBitmap)) return null;
@ -202,6 +216,7 @@ export interface MontageOptions {
} }
export function montage(bitmaps: BitmapType[], options?: MontageOptions) { export function montage(bitmaps: BitmapType[], options?: MontageOptions) {
bitmaps = coerceToArray(bitmaps);
let minmax = (options && options.analysis) || analyze(bitmaps); let minmax = (options && options.analysis) || analyze(bitmaps);
if (minmax == null) throw new Error(`Expected an array of bitmaps`); if (minmax == null) throw new Error(`Expected an array of bitmaps`);
let hitrects = []; let hitrects = [];

View File

@ -1,12 +1,15 @@
import { FileDataCache } from "../../util";
import { FileData, WorkingStore } from "../../workertypes"; import { FileData, WorkingStore } from "../../workertypes";
// remote resource cache // remote resource cache
var $$cache: WeakMap<object,FileData> = new WeakMap(); var $$cache = new FileDataCache(); // TODO: better cache?
// file read/write interface // file read/write interface
var $$store: WorkingStore; var $$store: WorkingStore;
// backing store for data // backing store for data
var $$data: {} = {}; var $$data: {} = {};
// module cache
var $$modules: Map<string,{}> = new Map();
export function $$setupFS(store: WorkingStore) { export function $$setupFS(store: WorkingStore) {
$$store = store; $$store = store;
@ -38,7 +41,7 @@ export namespace data {
return object; return object;
} }
export function save(object: Loadable, key: string): Loadable { export function save(object: Loadable, key: string): Loadable {
if ($$data && object.$$getstate) { if ($$data && object && object.$$getstate) {
$$data[key] = object.$$getstate(); $$data[key] = object.$$getstate();
} }
return object; return object;
@ -67,10 +70,6 @@ export function canonicalurl(url: string) : string {
return url; return url;
} }
export function clearcache() {
$$cache = new WeakMap();
}
export function fetchurl(url: string, type?: 'binary' | 'text'): FileData { export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
// TODO: only works in web worker // TODO: only works in web worker
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -99,17 +98,19 @@ export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
// TODO: read files too // TODO: read files too
export function read(url: string, type?: 'binary' | 'text'): FileData { export function read(url: string, type?: 'binary' | 'text'): FileData {
// canonical-ize url
url = canonicalurl(url); url = canonicalurl(url);
// check cache // check cache first
let cachekey = {url: url}; let cachekey = url;
if ($$cache.has(cachekey)) { let data = $$cache.get(cachekey);
return $$cache.get(cachekey); if (data != null) return data;
} // not in cache, read it
let data = readnocache(url, type); data = readnocache(url, type);
if (data == null) throw new Error(`Cannot find resource "${url}"`); if (data == null) throw new Error(`Cannot find resource "${url}"`);
if (type === 'text' && typeof data !== 'string') throw new Error(`Resource "${url}" is not a string`); if (type === 'text' && typeof data !== 'string') throw new Error(`Resource "${url}" is not a string`);
if (type === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`); if (type === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`);
$$cache.set(cachekey, data); // store in cache
$$cache.put(cachekey, data);
return data; return data;
} }
@ -129,6 +130,22 @@ export function splitlines(text: string) : string[] {
return text.split(/\n|\r\n/g); return text.split(/\n|\r\n/g);
} }
export function module(url: string) {
// find module in cache?
let key = `${url}::${url.length}`;
let exports = $$modules.get(key);
if (exports == null) {
let code = readnocache(url, 'text') as string;
let func = new Function('exports', 'module', code);
let module = {}; // TODO?
exports = {};
func(exports, module);
$$modules.set(key, exports);
}
return exports;
}
///
// TODO: what if this isn't top level? // TODO: what if this isn't top level?
export class Mutable<T> implements Loadable { export class Mutable<T> implements Loadable {

View File

@ -1,6 +1,10 @@
import { coerceToArray } from "../../util";
import * as io from "./io"; import * as io from "./io";
// sequence counter
var $$seq : number = 0;
// if an event is specified, it goes here // if an event is specified, it goes here
export const EVENT_KEY = "$$event"; export const EVENT_KEY = "$$event";
@ -17,17 +21,22 @@ export interface InteractEvent {
button?: boolean; button?: boolean;
} }
export type InteractCallback = (event: InteractEvent) => void;
// InteractionRecord maps a target object to an interaction ID // InteractionRecord maps a target object to an interaction ID
// the $$callback is used once per script eval, then gets nulled // the $$callback is used once per script eval, then gets nulled
// whether or not it's invoked // whether or not it's invoked
// event comes from $$data.$$event // event comes from $$data.$$event
export class InteractionRecord implements io.Loadable { export class InteractionRecord implements io.Loadable {
readonly interacttarget: Interactive;
interactid : number; interactid : number;
lastevent : {} = null; lastevent : {} = null;
constructor( constructor(
public readonly interacttarget: Interactive, interacttarget: Interactive,
private $$callback private $$callback: InteractCallback
) { ) {
this.interacttarget = interacttarget || (<any>this as Interactive);
this.interactid = ++$$seq;
} }
$$setstate(newstate: {interactid: number}) { $$setstate(newstate: {interactid: number}) {
this.interactid = newstate.interactid; this.interactid = newstate.interactid;
@ -46,7 +55,7 @@ export class InteractionRecord implements io.Loadable {
//TODO: this isn't always cleared before we serialize (e.g. if exception or move element) //TODO: this isn't always cleared before we serialize (e.g. if exception or move element)
//and we do it in checkResult() too //and we do it in checkResult() too
this.$$callback = null; this.$$callback = null;
return this; return {interactid: this.interactid};
} }
} }
@ -125,17 +134,42 @@ export function select(options: any[]) {
/// ///
export class ScriptUIButtonType implements ScriptUIType { export class ScriptUIButtonType extends InteractionRecord implements ScriptUIType, Interactive {
readonly uitype = 'button'; readonly uitype = 'button';
$$interact: InteractionRecord;
enabled?: boolean;
constructor( constructor(
readonly name: string readonly label: string,
callback: InteractCallback
) { ) {
super(null, callback);
this.$$interact = this;
} }
} }
export class ScriptUIButton extends ScriptUIButtonType { export class ScriptUIButton extends ScriptUIButtonType {
} }
export function button(name: string) { export function button(name: string, callback: InteractCallback) {
return new ScriptUIButton(name); return new ScriptUIButton(name, callback);
}
export class ScriptUIToggle extends ScriptUIButton implements io.Loadable {
// share with InteractionRecord
$$getstate() {
let state = super.$$getstate() as any;
state.enabled = this.enabled;
return state;
}
$$setstate(newstate: any) {
this.enabled = newstate.enabled;
super.$$setstate(newstate);
}
}
export function toggle(name: string) {
return new ScriptUIToggle(name, function(e) {
this.enabled = !this.enabled;
});
} }

View File

@ -12,27 +12,27 @@ import * as scriptui from "../lib/scriptui";
const MAX_STRING_LEN = 100; const MAX_STRING_LEN = 100;
const DEFAULT_ASPECT = 1; const DEFAULT_ASPECT = 1;
interface ObjectStats { function sendInteraction(iobj: scriptui.Interactive, type: string, event: Event, xtraprops: {}) {
type: 'prim' | 'complex' | 'bitmap' let irec = iobj.$$interact;
width: number let ievent : scriptui.InteractEvent = {interactid: irec.interactid, type, ...xtraprops};
height: number if (event instanceof PointerEvent) {
units: 'em' | 'px' const canvas = event.target as HTMLCanvasElement;
} const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
// TODO const scaleY = canvas.height / rect.height;
class ObjectAnalyzer { const x = (event.clientX - rect.left) * scaleX;
recurse(obj: any) : ObjectStats { const y = (event.clientY - rect.top) * scaleY;
if (typeof obj === 'string') { ievent.x = Math.floor(x);
return { type: 'prim', width: obj.length, height: 1, units: 'em' } ievent.y = Math.floor(y);
} else if (obj instanceof Uint8Array) { // TODO: pressure, etc.
return { type: 'complex', width: 60, height: Math.ceil(obj.length / 16), units: 'em' } } else {
} else if (typeof obj === 'object') { console.log("Unknown event type", event);
let stats : ObjectStats = { type: 'complex', width: 0, height: 0, units: 'em'};
return stats; // TODO
} else {
return { type: 'prim', width: 12, height: 1, units: 'em' }
}
} }
// TODO: add events to queue?
current_project.updateDataItems([{
key: scriptui.EVENT_KEY,
value: ievent
}]);
} }
interface ColorComponentProps { interface ColorComponentProps {
@ -56,27 +56,6 @@ class ColorComponent extends Component<ColorComponentProps> {
} }
} }
function sendInteraction(iobj: scriptui.Interactive, type: string, event: Event, xtraprops: {}) {
let irec = iobj.$$interact;
let ievent : scriptui.InteractEvent = {interactid: irec.interactid, type, ...xtraprops};
if (event instanceof PointerEvent) {
const canvas = event.target as HTMLCanvasElement;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (event.clientX - rect.left) * scaleX;
const y = (event.clientY - rect.top) * scaleY;
ievent.x = Math.round(x);
ievent.y = Math.round(y);
// TODO: pressure, etc.
}
// TODO: add events to queue?
current_project.updateDataItems([{
key: scriptui.EVENT_KEY,
value: ievent
}]);
}
interface BitmapComponentProps { interface BitmapComponentProps {
bitmap: bitmap.BitmapType; bitmap: bitmap.BitmapType;
width: number; width: number;
@ -253,7 +232,7 @@ function primitiveToString(obj) {
} }
function isIndexedBitmap(object): object is bitmap.IndexedBitmap { function isIndexedBitmap(object): object is bitmap.IndexedBitmap {
return object['bitsPerPixel'] && object['pixels'] && object['palette']; return object['bpp'] && object['pixels'] && object['palette'];
} }
function isRGBABitmap(object): object is bitmap.RGBABitmap { function isRGBABitmap(object): object is bitmap.RGBABitmap {
return object['rgba'] instanceof Uint32Array; return object['rgba'] instanceof Uint32Array;
@ -275,7 +254,10 @@ function objectToDiv(object: any, name: string, objpath: string): VNode<any> {
// don't view any keys that start with "$" // don't view any keys that start with "$"
if (name && name.startsWith("$")) { if (name && name.startsWith("$")) {
// don't view any values that start with "$$" // don't view any values that start with "$$"
if (name.startsWith("$$")) { return; } if (name.startsWith("$$")) return;
// don't print if null or undefined
if (object == null) return;
// don't print key in any case
name = null; name = null;
} }
// TODO: limit # of items // TODO: limit # of items
@ -400,9 +382,24 @@ class UISelectComponent extends Component<UIComponentProps> {
} }
} }
class UIButtonComponent extends Component<UIComponentProps> {
render(virtualDom, containerNode, replaceNode) {
let button = this.props.uiobject as scriptui.ScriptUIButtonType;
return h('button', {
class: button.enabled ? 'scripting-button scripting-enabled' : 'scripting-button',
onClick: (e: MouseEvent) => {
sendInteraction(button, 'click', e, { });
},
}, [
button.label
])
}
}
const UI_COMPONENTS = { const UI_COMPONENTS = {
'slider': UISliderComponent, 'slider': UISliderComponent,
'select': UISelectComponent, 'select': UISelectComponent,
'button': UIButtonComponent,
} }
/// ///

View File

@ -1,4 +1,3 @@
import { UintArray } from "../ide/pixeleditor";
export function lpad(s:string, n:number):string { export function lpad(s:string, n:number):string {
s += ''; // convert to string s += ''; // convert to string
@ -56,7 +55,7 @@ export function toradix(v:number, nd:number, radix:number) {
} }
} }
export function arrayCompare(a:any[]|UintArray, b:any[]|UintArray):boolean { export function arrayCompare(a:ArrayLike<any>, b:ArrayLike<any>):boolean {
if (a == null && b == null) return true; if (a == null && b == null) return true;
if (a == null) return false; if (a == null) return false;
if (b == null) return false; if (b == null) return false;
@ -662,3 +661,34 @@ export function findIntegerFactors(x: number, mina: number, minb: number, aspect
} }
return {a, b}; return {a, b};
} }
export class FileDataCache {
maxSize : number = 8000000;
size : number;
cache : Map<string, string|Uint8Array>;
constructor() {
this.reset();
}
get(key : string) : string|Uint8Array {
return this.cache.get(key);
}
put(key : string, value : string|Uint8Array) {
this.cache.set(key, value);
this.size += value.length;
if (this.size > this.maxSize) {
console.log('cache reset', this);
this.reset();
}
}
reset() {
this.cache = new Map();
this.size = 0;
}
}
export function coerceToArray<T>(arrobj: any) : T[] {
if (Array.isArray(arrobj)) return arrobj;
else if (arrobj != null && typeof arrobj[Symbol.iterator] === 'function') return Array.from(arrobj);
else if (typeof arrobj === 'object') return Array.from(Object.values(arrobj))
else throw new Error(`Expected array or object, got "${arrobj}"`);
}

View File

@ -147,6 +147,7 @@ function fatalError(s:string) {
} }
function newWorker() : Worker { function newWorker() : Worker {
// TODO: return new Worker("https://8bitworkshop.com.s3-website-us-east-1.amazonaws.com/dev/gen/worker/bundle.js");
return new Worker("./gen/worker/bundle.js"); return new Worker("./gen/worker/bundle.js");
} }

View File

@ -1,5 +1,5 @@
import { PLATFORMS, RasterVideo } from "../common/emu"; import { PLATFORMS } from "../common/emu";
import { Platform } from "../common/baseplatform"; import { Platform } from "../common/baseplatform";
import { RunResult } from "../common/script/env"; import { RunResult } from "../common/script/env";
import { Notebook } from "../common/script/ui/notebook"; import { Notebook } from "../common/script/ui/notebook";