mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-02 23:30:21 +00:00
use preact for scripting notebook, moved files
This commit is contained in:
parent
a8b2b7c043
commit
7f86ed0cb6
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,3 +8,6 @@ tmp/
|
||||
web/
|
||||
release/
|
||||
gen/
|
||||
config.js
|
||||
chromedriver.log
|
||||
nightwatch.conf.js
|
||||
|
@ -793,3 +793,11 @@ div.asset_toolbar {
|
||||
.scripting-cell div {
|
||||
display: inline;
|
||||
}
|
||||
div.scripting-color {
|
||||
padding:0.1em;
|
||||
margin:0.1em;
|
||||
}
|
||||
div.scripting-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(2em, 1fr) );
|
||||
}
|
BIN
meta/icons/8bitworkshop-icon-1024.jpg
Normal file
BIN
meta/icons/8bitworkshop-icon-1024.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
15
package-lock.json
generated
15
package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"preact": "^10.5.14",
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
},
|
||||
@ -7742,6 +7743,15 @@
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.5.14",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz",
|
||||
"integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@ -16281,6 +16291,11 @@
|
||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"preact": {
|
||||
"version": "10.5.14",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz",
|
||||
"integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ=="
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"preact": "^10.5.14",
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
},
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { WorkerError } from "../workertypes";
|
||||
import ErrorStackParser = require("error-stack-parser");
|
||||
import yufka from 'yufka';
|
||||
import * as bitmap from "./bitmap";
|
||||
import * as io from "./io";
|
||||
import * as output from "./output";
|
||||
import * as bitmap from "./lib/bitmap";
|
||||
import * as io from "./lib/io";
|
||||
import * as output from "./lib/output";
|
||||
import { escapeHTML } from "../util";
|
||||
|
||||
export interface Cell {
|
||||
@ -54,6 +54,7 @@ export class Environment {
|
||||
preprocess(code: string): string {
|
||||
var declvars = {};
|
||||
const result = yufka(code, (node, { update, source, parent }) => {
|
||||
let left = node['left'];
|
||||
switch (node.type) {
|
||||
case 'Identifier':
|
||||
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
|
||||
@ -61,16 +62,17 @@ export class Environment {
|
||||
}
|
||||
break;
|
||||
case 'AssignmentExpression':
|
||||
/*
|
||||
// x = expr --> var x = expr
|
||||
// x = expr --> var x = expr (first use)
|
||||
if (parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program') { // TODO
|
||||
let left = node['left'];
|
||||
if (left && left.type === 'Identifier' && declvars[left.name] == null) {
|
||||
update(`var ${source()}`)
|
||||
if (left && left.type === 'Identifier') {
|
||||
if (!declvars[left.name]) {
|
||||
update(`var ${left.name}=this.${source()}`)
|
||||
declvars[left.name] = true;
|
||||
} else {
|
||||
update(`${left.name}=this.${source()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import * as fastpng from 'fast-png';
|
||||
import { convertWordsToImages, PixelEditorImageFormat } from '../../ide/pixeleditor';
|
||||
import { arrayCompare } from '../util';
|
||||
import { convertWordsToImages, PixelEditorImageFormat } from '../../../ide/pixeleditor';
|
||||
import { arrayCompare } from '../../util';
|
||||
import * as io from './io'
|
||||
|
||||
export abstract class AbstractBitmap {
|
||||
@ -107,8 +107,8 @@ export function indexed(width: number, height: number, bpp: number) {
|
||||
export type BitmapType = RGBABitmap | IndexedBitmap;
|
||||
|
||||
export namespace png {
|
||||
export function load(url: string): BitmapType {
|
||||
return decode(io.loadbin(url));
|
||||
export function read(url: string): BitmapType {
|
||||
return decode(io.readbin(url));
|
||||
}
|
||||
export function decode(data: Uint8Array): BitmapType {
|
||||
let png = fastpng.decode(data);
|
||||
@ -147,13 +147,11 @@ export namespace png {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace from {
|
||||
// TODO: check arguments
|
||||
export function bytes(arr: Uint8Array, fmt: PixelEditorImageFormat) {
|
||||
export function decode(arr: Uint8Array, fmt: PixelEditorImageFormat) {
|
||||
var pixels = convertWordsToImages(arr, fmt);
|
||||
// TODO: guess if missing w/h/count?
|
||||
// TODO: reverse mapping
|
||||
// TODO: maybe better composable functions
|
||||
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, fmt.bpp|1, data));
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { ProjectFilesystem } from "../../ide/project";
|
||||
import { FileData } from "../workertypes";
|
||||
import { ProjectFilesystem } from "../../../ide/project";
|
||||
import { FileData } from "../../workertypes";
|
||||
import * as output from "./output";
|
||||
|
||||
// TODO
|
||||
|
||||
var $$fs: ProjectFilesystem;
|
||||
var $$cache: { [path: string]: FileData } = {};
|
||||
|
||||
@ -18,6 +18,19 @@ function getFS(): ProjectFilesystem {
|
||||
return $$fs;
|
||||
}
|
||||
|
||||
export function ___load(path: string): FileData {
|
||||
var data = $$cache[path];
|
||||
if (data == null) {
|
||||
getFS().getFileData(path).then((value) => {
|
||||
$$cache[path] = value;
|
||||
})
|
||||
throw new IOWaitError(path);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function canonicalurl(url: string) : string {
|
||||
// get raw resource URL for github
|
||||
if (url.startsWith('https://github.com/')) {
|
||||
@ -29,7 +42,7 @@ export function canonicalurl(url: string) : string {
|
||||
return url;
|
||||
}
|
||||
|
||||
export function load(url: string, type?: 'binary' | 'text'): FileData {
|
||||
export function read(url: string, type?: 'binary' | 'text'): FileData {
|
||||
url = canonicalurl(url);
|
||||
// TODO: only works in web worker
|
||||
var xhr = new XMLHttpRequest();
|
||||
@ -47,23 +60,10 @@ export function load(url: string, type?: 'binary' | 'text'): FileData {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadbin(url: string): Uint8Array {
|
||||
var data = load(url, 'binary');
|
||||
export function readbin(url: string): Uint8Array {
|
||||
var data = read(url, 'binary');
|
||||
if (data instanceof Uint8Array)
|
||||
return data;
|
||||
else
|
||||
throw new Error(`The resource at "${url}" is not a binary file.`);
|
||||
}
|
||||
|
||||
export function xload(path: string): FileData {
|
||||
var data = $$cache[path];
|
||||
if (data == null) {
|
||||
getFS().getFileData(path).then((value) => {
|
||||
$$cache[path] = value;
|
||||
})
|
||||
throw new IOWaitError(path);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -1,165 +0,0 @@
|
||||
|
||||
var lastTimestamp = 0;
|
||||
|
||||
function newTimestamp() {
|
||||
return ++lastTimestamp;
|
||||
}
|
||||
|
||||
export type DependencySet = {[id:string] : ComputeNode | any}
|
||||
|
||||
export abstract class ComputeNode {
|
||||
private src_ts: number = newTimestamp();
|
||||
private result_ts: number = 0;
|
||||
private depends: DependencySet = {};
|
||||
private busy: Promise<void> = null;
|
||||
|
||||
modified() {
|
||||
this.src_ts = newTimestamp();
|
||||
}
|
||||
|
||||
isStale(ts: number) {
|
||||
return this.result_ts < ts;
|
||||
}
|
||||
|
||||
setDependencies(depends: DependencySet) {
|
||||
this.depends = depends;
|
||||
// compute latest timestamp of all dependencies
|
||||
var ts = 0;
|
||||
for (let [key, dep] of Object.entries(this.depends)) {
|
||||
if (dep instanceof ComputeNode && dep.result_ts) {
|
||||
ts = Math.max(ts, dep.result_ts);
|
||||
} else {
|
||||
ts = newTimestamp();
|
||||
}
|
||||
}
|
||||
this.src_ts = ts;
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return this.depends;
|
||||
}
|
||||
|
||||
async update() : Promise<void> {
|
||||
let maxts = 0;
|
||||
let dependsComputes = []
|
||||
for (let [key, dep] of Object.entries(this.depends)) {
|
||||
if (dep instanceof ComputeNode && dep.isStale(this.src_ts)) {
|
||||
dependsComputes.push(dep.compute());
|
||||
}
|
||||
}
|
||||
if (dependsComputes.length) {
|
||||
await Promise.all(dependsComputes);
|
||||
this.recompute(maxts);
|
||||
}
|
||||
}
|
||||
|
||||
async recompute(ts: number) : Promise<void> {
|
||||
// are we currently waiting for a computation to finish?
|
||||
if (this.busy == null || ts > this.result_ts) {
|
||||
// wait for previous operation to finish (no-op if null)
|
||||
await this.busy;
|
||||
this.result_ts = ts;
|
||||
this.busy = this.compute();
|
||||
}
|
||||
await this.busy;
|
||||
this.busy = null;
|
||||
}
|
||||
|
||||
abstract compute(): Promise<void>;
|
||||
}
|
||||
|
||||
class ValueNode<T> extends ComputeNode {
|
||||
private value : T;
|
||||
|
||||
constructor(value : T) {
|
||||
super();
|
||||
this.set(value);
|
||||
}
|
||||
|
||||
get() : T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
set(newValue : T) {
|
||||
this.value = newValue;
|
||||
this.modified();
|
||||
}
|
||||
|
||||
async compute() { }
|
||||
}
|
||||
|
||||
class ArrayNode<T> extends ValueNode<T> {
|
||||
}
|
||||
|
||||
class IntegerNode extends ValueNode<number> {
|
||||
}
|
||||
|
||||
abstract class BitmapNode extends ComputeNode {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
class RGBABitmapNode extends BitmapNode {
|
||||
rgba: ArrayNode<Uint32Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class IndexedBitmapNode extends BitmapNode {
|
||||
indices: ArrayNode<Uint8Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class PaletteNode {
|
||||
colors: ArrayNode<Uint32Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
class PaletteMapNode extends ComputeNode {
|
||||
palette: PaletteNode;
|
||||
indices: ArrayNode<Uint8Array>;
|
||||
|
||||
compute(): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
function valueOf<T>(node : ValueNode<T>) : T {
|
||||
return node.get();
|
||||
}
|
||||
|
||||
class TestNode extends ComputeNode {
|
||||
value : string;
|
||||
|
||||
async compute() {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
this.value = Object.values(this.getDependencies()).map(valueOf).join('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
async function test() {
|
||||
var val1 = new ValueNode<number>(1234);
|
||||
var arr1 = new ValueNode<number[]>([1,2,3]);
|
||||
var join = new TestNode();
|
||||
join.setDependencies({a:val1, b:arr1});
|
||||
await join.update();
|
||||
console.log(join);
|
||||
val1.set(9999);
|
||||
join.update();
|
||||
val1.set(9989)
|
||||
await join.update();
|
||||
console.log(join);
|
||||
}
|
||||
|
||||
test();
|
@ -1,13 +0,0 @@
|
||||
|
||||
import 'fs';
|
||||
import * as bitmap from './bitmap'
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
var data = fs.readFileSync('images/book_a2600.png');
|
||||
//var data = fs.readFileSync('images/print-head.png');
|
||||
console.log(data);
|
||||
|
||||
var png = bitmap.png.decode(data);
|
||||
console.log(png)
|
||||
|
210
src/common/script/ui/notebook.ts
Normal file
210
src/common/script/ui/notebook.ts
Normal file
@ -0,0 +1,210 @@
|
||||
|
||||
import { BitmapType, IndexedBitmap, RGBABitmap } from "../lib/bitmap";
|
||||
import { Component, render, h, ComponentType } from 'preact';
|
||||
import { Cell } from "../env";
|
||||
import { rgb2bgr } from "../../util";
|
||||
import { dumpRAM } from "../../emu";
|
||||
|
||||
interface ColorComponentProps {
|
||||
rgbavalue: number;
|
||||
}
|
||||
|
||||
class ColorComponent extends Component<ColorComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let rgb = this.props.rgbavalue & 0xffffff;
|
||||
var htmlcolor = `#${rgb2bgr(rgb).toString(16)}`;
|
||||
var textcol = (rgb & 0x008000) ? 'black' : 'white';
|
||||
return h('div', {
|
||||
class: 'scripting-color',
|
||||
style: `background-color: ${htmlcolor}; color: ${textcol}`,
|
||||
alt: htmlcolor, // TODO
|
||||
}, '\u00a0');
|
||||
}
|
||||
}
|
||||
|
||||
interface BitmapComponentProps {
|
||||
bitmap: BitmapType;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
class BitmapComponent extends Component<BitmapComponentProps> {
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
imageData: ImageData;
|
||||
datau32: Uint32Array;
|
||||
|
||||
constructor(props: BitmapComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
return h('canvas', {
|
||||
class: 'pixelated',
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
this.canvas = this.base as HTMLCanvasElement;
|
||||
this.prepare();
|
||||
this.refresh();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.canvas = null;
|
||||
this.imageData = null;
|
||||
this.datau32 = null;
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
this.refresh();
|
||||
}
|
||||
prepare() {
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
|
||||
this.datau32 = new Uint32Array(this.imageData.data.buffer);
|
||||
}
|
||||
refresh() {
|
||||
// preact can reuse this component but it can change shape :^P
|
||||
if (this.imageData.width != this.props.width || this.imageData.height != this.props.height) {
|
||||
this.prepare();
|
||||
}
|
||||
this.updateCanvas(this.datau32, this.props.bitmap);
|
||||
this.ctx.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
updateCanvas(vdata: Uint32Array, bmp: BitmapType) {
|
||||
if (bmp['palette']) {
|
||||
this.updateCanvasIndexed(vdata, bmp as IndexedBitmap);
|
||||
}
|
||||
if (bmp['rgba']) {
|
||||
this.updateCanvasRGBA(vdata, bmp as RGBABitmap);
|
||||
}
|
||||
}
|
||||
updateCanvasRGBA(vdata: Uint32Array, bmp: RGBABitmap) {
|
||||
vdata.set(bmp.rgba);
|
||||
}
|
||||
updateCanvasIndexed(vdata: Uint32Array, bmp: IndexedBitmap) {
|
||||
let pal = bmp.palette.colors;
|
||||
for (var i = 0; i < bmp.pixels.length; i++) {
|
||||
vdata[i] = pal[bmp.pixels[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectTreeComponentProps {
|
||||
object: {} | [];
|
||||
}
|
||||
|
||||
interface ObjectTreeComponentState {
|
||||
expanded : boolean;
|
||||
}
|
||||
|
||||
class ObjectTreeComponent extends Component<ObjectTreeComponentProps, ObjectTreeComponentState> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
if (this.state.expanded) {
|
||||
var minus = h('span', { onClick: () => this.toggleExpand() }, [ '-' ]);
|
||||
return h('minus', { }, [
|
||||
minus,
|
||||
getShortName(this.props.object),
|
||||
objectToContentsDiv(this.props.object)
|
||||
]);
|
||||
} else {
|
||||
var plus = h('span', { onClick: () => this.toggleExpand() }, [ '+' ]);
|
||||
return h('div', { }, [
|
||||
plus,
|
||||
getShortName(this.props.object)
|
||||
]);
|
||||
}
|
||||
}
|
||||
toggleExpand() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
}
|
||||
|
||||
function getShortName(object: any) {
|
||||
if (typeof object === 'object') {
|
||||
try {
|
||||
var s = Object.getPrototypeOf(object).constructor.name;
|
||||
if (object.length > 0) {
|
||||
s += `[${object.length}]`
|
||||
}
|
||||
return s;
|
||||
} catch (e) {
|
||||
return 'object';
|
||||
}
|
||||
} else {
|
||||
return object+"";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: need id?
|
||||
function objectToDiv(object: any) {
|
||||
var props = { class: '' };
|
||||
var children = [];
|
||||
// TODO: tile editor
|
||||
// TODO: limit # of items
|
||||
// TODO: detect table
|
||||
if (Array.isArray(object)) {
|
||||
return objectToContentsDiv(object);
|
||||
} else if (object['bitsPerPixel'] && object['pixels'] && object['palette']) {
|
||||
addBitmapComponent(children, object as IndexedBitmap);
|
||||
} else if (object['rgba'] instanceof Uint32Array) {
|
||||
addBitmapComponent(children, object as RGBABitmap);
|
||||
} else if (object['colors'] instanceof Uint32Array) {
|
||||
// TODO: make sets of 2/4/8/16/etc
|
||||
props.class += ' scripting-grid ';
|
||||
object['colors'].forEach((val) => {
|
||||
children.push(h(ColorComponent, { rgbavalue: val }));
|
||||
})
|
||||
} else if (typeof object === 'object') {
|
||||
children.push(h(ObjectTreeComponent, { object }));
|
||||
} else {
|
||||
children.push(JSON.stringify(object));
|
||||
}
|
||||
let div = h('div', props, children);
|
||||
return div;
|
||||
}
|
||||
|
||||
function objectToContentsDiv(object: {} | []) {
|
||||
// is typed array?
|
||||
let bpel = object['BYTES_PER_ELEMENT'];
|
||||
if (typeof bpel === 'number') {
|
||||
const maxBytes = 0x100;
|
||||
let tyarr = object as Uint8Array;
|
||||
if (tyarr.length <= maxBytes) {
|
||||
// TODO
|
||||
let dumptext = dumpRAM(tyarr, 0, tyarr.length);
|
||||
return h('pre', { }, dumptext);
|
||||
} else {
|
||||
let children = [];
|
||||
for (var ofs=0; ofs<tyarr.length; ofs+=maxBytes) {
|
||||
children.push(objectToDiv(tyarr.slice(ofs, ofs+maxBytes)));
|
||||
}
|
||||
return h('div', { }, children);
|
||||
}
|
||||
}
|
||||
let objectEntries = Object.entries(object);
|
||||
// TODO: id?
|
||||
let objectDivs = objectEntries.map(entry => objectToDiv(entry[1]));
|
||||
return h('div', { }, objectDivs);
|
||||
}
|
||||
|
||||
function addBitmapComponent(children, bitmap: BitmapType) {
|
||||
children.push(h(BitmapComponent, { bitmap: bitmap, width: bitmap.width, height: bitmap.height}));
|
||||
}
|
||||
|
||||
export class Notebook {
|
||||
constructor(
|
||||
public readonly maindoc: HTMLDocument,
|
||||
public readonly maindiv: HTMLElement
|
||||
) {
|
||||
maindiv.classList.add('vertical-scroll');
|
||||
//maindiv.classList.add('container')
|
||||
}
|
||||
updateCells(cells: Cell[]) {
|
||||
let hTree = cells.map(cell => {
|
||||
let cellDiv = objectToDiv(cell.object);
|
||||
cellDiv.props['class'] += ' scripting-cell ';
|
||||
return cellDiv;
|
||||
});
|
||||
render(hTree, this.maindiv);
|
||||
}
|
||||
}
|
@ -2,111 +2,7 @@
|
||||
import { PLATFORMS, RasterVideo } from "../common/emu";
|
||||
import { Platform } from "../common/baseplatform";
|
||||
import { Cell } from "../common/script/env";
|
||||
import { escapeHTML } from "../common/util";
|
||||
import { BitmapType, IndexedBitmap, RGBABitmap } from "../common/script/bitmap";
|
||||
|
||||
abstract class TileEditor<T> {
|
||||
video: RasterVideo;
|
||||
|
||||
constructor(
|
||||
public readonly tileWidth: number,
|
||||
public readonly tileHeight: number,
|
||||
public readonly numColumns: number,
|
||||
public readonly numRows: number,
|
||||
) {
|
||||
}
|
||||
getPixelWidth() { return this.tileWidth * this.numColumns }
|
||||
getPixelHeight() { return this.tileHeight * this.numRows }
|
||||
attach(div: HTMLElement) {
|
||||
this.video = new RasterVideo(div, this.getPixelWidth(), this.getPixelHeight());
|
||||
this.video.create();
|
||||
}
|
||||
detach() {
|
||||
this.video = null;
|
||||
}
|
||||
update() {
|
||||
if (this.video) this.video.updateFrame();
|
||||
}
|
||||
abstract getTile(x: number, y: number): T;
|
||||
abstract renderTile(x: number, y: number): void;
|
||||
}
|
||||
|
||||
class RGBABitmapEditor extends TileEditor<number> {
|
||||
constructor(
|
||||
public readonly bitmap: RGBABitmap
|
||||
) {
|
||||
super(1, 1, bitmap.width, bitmap.height);
|
||||
}
|
||||
getTile(x: number, y: number) {
|
||||
return this.bitmap.getPixel(x, y);
|
||||
}
|
||||
renderTile(x: number, y: number): void {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
function bitmap2image(doc: HTMLDocument, div: HTMLElement, bitmap: BitmapType): HTMLCanvasElement {
|
||||
var video = new RasterVideo(div, bitmap.width, bitmap.height);
|
||||
video.create(doc);
|
||||
video.canvas.className = 'pixelated';
|
||||
let vdata = video.getFrameData();
|
||||
if (bitmap['palette'] != null) {
|
||||
let bmp = bitmap as IndexedBitmap;
|
||||
let pal = bmp.palette.colors;
|
||||
for (var i = 0; i < bmp.pixels.length; i++) {
|
||||
vdata[i] = pal[bmp.pixels[i]];
|
||||
}
|
||||
} else {
|
||||
let bmp = bitmap as RGBABitmap;
|
||||
vdata.set(bmp.rgba);
|
||||
}
|
||||
video.updateFrame();
|
||||
return video.canvas;
|
||||
}
|
||||
|
||||
class Notebook {
|
||||
constructor(
|
||||
public readonly maindoc: HTMLDocument,
|
||||
public readonly maindiv: HTMLElement
|
||||
) {
|
||||
maindiv.classList.add('vertical-scroll');
|
||||
//maindiv.classList.add('container')
|
||||
}
|
||||
updateCells(cells: Cell[]) {
|
||||
let body = this.maindiv;
|
||||
body.innerHTML = '';
|
||||
//var body = $(this.iframe).contents().find('body');
|
||||
//body.empty();
|
||||
for (let cell of cells) {
|
||||
if (cell.object != null) {
|
||||
let div = this.objectToDiv(cell.object);
|
||||
div.id = cell.id;
|
||||
div.classList.add('scripting-cell')
|
||||
//div.classList.add('row')
|
||||
body.append(div);
|
||||
}
|
||||
}
|
||||
}
|
||||
objectToDiv(object: any) {
|
||||
let div = document.createElement('div');
|
||||
//div.classList.add('col-auto')
|
||||
//grid-template-columns: repeat(auto-fit, minmax(50px, 1fr));
|
||||
// TODO: tile editor
|
||||
if (Array.isArray(object) || object.BYTES_PER_ELEMENT) {
|
||||
object.forEach((obj) => {
|
||||
div.appendChild(this.objectToDiv(obj));
|
||||
});
|
||||
// TODO
|
||||
} else if (object['bitsPerPixel'] && object['pixels'] && object['palette']) {
|
||||
bitmap2image(this.maindoc, div, object as IndexedBitmap);
|
||||
} else if (object['rgba']) {
|
||||
bitmap2image(this.maindoc, div, object as RGBABitmap);
|
||||
} else if (object != null) {
|
||||
div.innerHTML = escapeHTML(JSON.stringify(object));
|
||||
}
|
||||
return div;
|
||||
}
|
||||
}
|
||||
import { Notebook } from "../common/script/ui/notebook";
|
||||
|
||||
class ScriptingPlatform implements Platform {
|
||||
mainElement: HTMLElement;
|
||||
|
@ -1094,7 +1094,7 @@ var TOOL_PRELOADFS = {
|
||||
'wiz': 'wiz',
|
||||
}
|
||||
|
||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||
//const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); // for testing
|
||||
|
||||
async function handleMessage(data : WorkerMessage) : Promise<WorkerResult> {
|
||||
// preload file system
|
||||
@ -1128,4 +1128,3 @@ if (ENVIRONMENT_IS_WORKER) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user