scripting: print(), css, palette layout, flex
make syncdev/prod: fixed mime type upload
This commit is contained in:
parent
9076ede5c1
commit
6cee4e26e4
4
Makefile
4
Makefile
|
@ -52,7 +52,7 @@ VERSION := $(shell git tag -l --points-at HEAD)
|
|||
syncdev: distro
|
||||
cp config.js $(TMP)
|
||||
#aws --profile pzp s3 sync --follow-symlinks $(TMP)/ s3://8bitworkshop.com/dev/
|
||||
s3cmd -c ~/.s3pzp sync -MFP $(TMP)/ s3://8bitworkshop.com/dev/
|
||||
s3cmd -c ~/.s3pzp sync -MFP --no-mime-magic $(TMP)/ s3://8bitworkshop.com/dev/
|
||||
rsync --stats -riltz --delete --chmod=a+rx -e "ssh" $(TMP)/ config.js $(RSYNC_PATH)/dev/
|
||||
|
||||
syncprod: distro
|
||||
|
@ -63,5 +63,5 @@ syncprod: distro
|
|||
read
|
||||
cp config.js $(TMP)
|
||||
#aws --profile pzp s3 sync --follow-symlinks $(TMP)/ s3://8bitworkshop.com/v$(VERSION)/
|
||||
s3cmd -c ~/.s3pzp sync -MFP $(TMP)/ config.js s3://8bitworkshop.com/v$(VERSION)/
|
||||
s3cmd -c ~/.s3pzp sync -MFP --no-mime-magic $(TMP)/ config.js s3://8bitworkshop.com/v$(VERSION)/
|
||||
rsync --stats -riltz --chmod=a+rx -e "ssh" $(TMP)/ config.js $(RSYNC_PATH)/v$(VERSION)/
|
||||
|
|
43
css/ui.css
43
css/ui.css
|
@ -718,6 +718,7 @@ div.asset_toolbar {
|
|||
padding-top: 0.5em;
|
||||
}
|
||||
.tree-header {
|
||||
display: flex;
|
||||
border: 2px solid #555;
|
||||
border-radius:8px;
|
||||
color: #fff;
|
||||
|
@ -730,8 +731,11 @@ div.asset_toolbar {
|
|||
padding-right:0.75em;
|
||||
font-size: small;
|
||||
}
|
||||
.tree-key {
|
||||
padding-right:1em;
|
||||
}
|
||||
.tree-value {
|
||||
float:right;
|
||||
margin-left:auto;
|
||||
font-weight:normal;
|
||||
padding-right:1em;
|
||||
}
|
||||
|
@ -779,24 +783,45 @@ div.asset_toolbar {
|
|||
pointer-events: auto;
|
||||
}
|
||||
.scripting-cell canvas {
|
||||
height: 20vw;
|
||||
min-height: 20em;
|
||||
max-width: 95%;
|
||||
border: 2px solid #222;
|
||||
outline-color: #ccc;
|
||||
background: #000;
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
padding: 4px;
|
||||
margin: 1px;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
.scripting-cell canvas:hover {
|
||||
outline:none;
|
||||
border-color:#ccc;
|
||||
}
|
||||
.scripting-flex canvas {
|
||||
min-height: 2vw;
|
||||
max-height: 20vw;
|
||||
}
|
||||
div.scripting-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.scripting-editor canvas {
|
||||
min-height: 30vw;
|
||||
max-height: 50vw;
|
||||
}
|
||||
div.scripting-color {
|
||||
padding:0.1em;
|
||||
margin:0.1em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
div.scripting-color span {
|
||||
visibility: hidden;
|
||||
}
|
||||
div.scripting-color:hover span {
|
||||
visibility: visible;
|
||||
}
|
||||
div.scripting-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(2em, 1fr) );
|
||||
grid-template-columns: repeat( auto-fill, minmax(4em, 1fr) );
|
||||
justify-items: center;
|
||||
}
|
||||
}
|
||||
div.scripting-flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
|
@ -77,7 +77,8 @@
|
|||
"test-worker": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testworker.js",
|
||||
"test-platforms": "NODE_PATH=$(pwd) mocha --timeout 60000 test/cli/testplatforms.js",
|
||||
"test-verilog": "NODE_PATH=$(pwd) mocha --timeout 60000 --reporter mocha-simple-html-reporter --reporter-options output=test/output/verilog.html test/cli/testverilog.js",
|
||||
"test-web": "nightwatch -e chrome test/web",
|
||||
"test-web-quick": "nightwatch -e chrome test/web/testembed.js",
|
||||
"test-web-all": "nightwatch -e chrome test/web",
|
||||
"start": "electron .",
|
||||
"fuzzbasic": "jsfuzz gen/common/basic/fuzz.js ~/basic/corpus/ --versifier false",
|
||||
"fuzzhdl": "jsfuzz -r binaryen gen/common/hdl/fuzz.js ~/verilator/corpus/ --versifier false",
|
||||
|
|
|
@ -55,14 +55,19 @@ export class Environment {
|
|||
obj: {};
|
||||
seq: number;
|
||||
declvars : {[name : string] : acorn.Node};
|
||||
builtins : {}
|
||||
|
||||
constructor(
|
||||
public readonly globalenv: any,
|
||||
public readonly path: string
|
||||
) {
|
||||
var badlst = Object.getOwnPropertyNames(this.globalenv).filter(name => GLOBAL_GOODLIST.indexOf(name) < 0);
|
||||
this.builtins = {
|
||||
print: (...args) => this.print(args),
|
||||
...IMPORTS
|
||||
}
|
||||
this.preamble = `'use strict';var ${badlst.join(',')};`;
|
||||
for (var impname in IMPORTS) {
|
||||
for (var impname in this.builtins) {
|
||||
this.preamble += `var ${impname}=$$.${impname};`
|
||||
}
|
||||
this.preamble += '{\n';
|
||||
|
@ -73,6 +78,11 @@ export class Environment {
|
|||
console.log(varname, obj);
|
||||
throw new RuntimeError(obj && obj.loc, msg);
|
||||
}
|
||||
print(args: any[]) {
|
||||
if (args && args.length > 0 && args[0] != null) {
|
||||
this.obj[`$$print__${this.seq++}`] = args.length == 1 ? args[0] : args;
|
||||
}
|
||||
}
|
||||
preprocess(code: string): string {
|
||||
this.declvars = {};
|
||||
this.seq = 0;
|
||||
|
@ -94,15 +104,20 @@ export class Environment {
|
|||
}
|
||||
};
|
||||
const result = yufka(code, options, (node, { update, source, parent }) => {
|
||||
function isTopLevel() {
|
||||
const isTopLevel = () => {
|
||||
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
|
||||
}
|
||||
let left = node['left'];
|
||||
const convertTopToPrint = () => {
|
||||
if (isTopLevel()) update(`print(${source()});`)
|
||||
}
|
||||
const left = node['left'];
|
||||
switch (node.type) {
|
||||
// error on forbidden keywords
|
||||
case 'Identifier':
|
||||
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
|
||||
update(`__FORBIDDEN__KEYWORD__${source()}__`) // TODO? how to preserve line number?
|
||||
} else {
|
||||
convertTopToPrint();
|
||||
}
|
||||
break;
|
||||
// x = expr --> var x = expr (first use)
|
||||
|
@ -118,10 +133,19 @@ export class Environment {
|
|||
}
|
||||
}
|
||||
break;
|
||||
// convert lone expressions to print()
|
||||
case 'UnaryExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'CallExpression':
|
||||
case 'MemberExpression':
|
||||
convertTopToPrint();
|
||||
break;
|
||||
// literal comments
|
||||
case 'Literal':
|
||||
if (isTopLevel() && typeof node['value'] === 'string') {
|
||||
if (typeof node['value'] === 'string' && isTopLevel()) {
|
||||
update(`this.$$doc__${this.seq++} = { literaltext: ${source()} };`);
|
||||
} else {
|
||||
convertTopToPrint();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -133,7 +157,7 @@ export class Environment {
|
|||
code = this.preprocess(code);
|
||||
this.obj = {};
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
||||
const fn = new AsyncFunction('$$', this.preamble + code + this.postamble).bind(this.obj, IMPORTS);
|
||||
const fn = new AsyncFunction('$$', this.preamble + code + this.postamble).bind(this.obj, this.builtins);
|
||||
await fn.call(this);
|
||||
this.checkResult(this.obj, new Set(), []);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as fastpng from 'fast-png';
|
|||
import { Palette } from './color';
|
||||
import * as io from './io'
|
||||
import * as color from './color'
|
||||
import { findIntegerFactors, RGBA } from '../../util';
|
||||
|
||||
export type PixelMapFunction = (x: number, y: number) => number;
|
||||
|
||||
|
@ -19,6 +20,7 @@ export abstract class AbstractBitmap<T> {
|
|||
abstract setarray(arr: ArrayLike<number>) : AbstractBitmap<T>;
|
||||
abstract set(x: number, y: number, val: number) : AbstractBitmap<T>;
|
||||
abstract get(x: number, y: number): number;
|
||||
abstract getrgba(x: number, y: number): number;
|
||||
|
||||
inbounds(x: number, y: number): boolean {
|
||||
return (x >= 0 && x < this.width && y >= 0 && y < this.height);
|
||||
|
@ -45,6 +47,17 @@ export abstract class AbstractBitmap<T> {
|
|||
dest.assign((x, y) => this.get(x + srcx, y + srcy));
|
||||
return dest;
|
||||
}
|
||||
blit(src: BitmapType, dest: BitmapType,
|
||||
destx: number, desty: number,
|
||||
srcx: number, srcy: number)
|
||||
{
|
||||
for (var y=0; y<src.height; y++) {
|
||||
for (var x=0; x<src.width; x++) {
|
||||
let rgba = src.getrgba(x+srcx, y+srcy);
|
||||
dest.set(x+destx, y+desty, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
|
||||
|
@ -70,6 +83,9 @@ export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
|
|||
get(x: number, y: number): number {
|
||||
return this.inbounds(x,y) ? this.rgba[y * this.width + x] : 0;
|
||||
}
|
||||
getrgba(x: number, y: number): number {
|
||||
return this.get(x, y);
|
||||
}
|
||||
blank(width: number, height: number) : RGBABitmap {
|
||||
return new RGBABitmap(width, height);
|
||||
}
|
||||
|
@ -106,7 +122,6 @@ export abstract class MappedBitmap extends AbstractBitmap<MappedBitmap> {
|
|||
get(x: number, y: number): number {
|
||||
return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0;
|
||||
}
|
||||
abstract getRGBAForIndex(index: number): number;
|
||||
}
|
||||
|
||||
export class IndexedBitmap extends MappedBitmap {
|
||||
|
@ -122,8 +137,8 @@ export class IndexedBitmap extends MappedBitmap {
|
|||
this.palette = color.palette.colors(1 << this.bitsPerPixel);
|
||||
}
|
||||
|
||||
getRGBAForIndex(index: number): number {
|
||||
return this.palette.colors[index];
|
||||
getrgba(x: number, y: number): number {
|
||||
return this.palette && this.palette.colors[this.get(x, y)];
|
||||
}
|
||||
blank(width: number, height: number) : IndexedBitmap {
|
||||
let bitmap = new IndexedBitmap(width, height, this.bitsPerPixel);
|
||||
|
@ -153,9 +168,64 @@ export function decode(arr: Uint8Array, fmt: PixelEditorImageFormat) {
|
|||
// 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));
|
||||
let bpp = (fmt.bpp||1) * (fmt.np||1);
|
||||
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, bpp, data));
|
||||
}
|
||||
|
||||
export interface BitmapAnalysis {
|
||||
min: {w: number, h: number};
|
||||
max: {w: number, h: number};
|
||||
}
|
||||
|
||||
export function analyze(bitmaps: BitmapType[]) {
|
||||
let r = {min:{w:0,h:0}, max:{w:0,h:0}};
|
||||
for (let bmp of bitmaps) {
|
||||
if (!(bmp instanceof AbstractBitmap)) return null;
|
||||
r.min.w = Math.min(bmp.width);
|
||||
r.max.w = Math.max(bmp.width);
|
||||
r.min.h = Math.min(bmp.height);
|
||||
r.max.h = Math.max(bmp.height);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export interface MontageOptions {
|
||||
analysis?: BitmapAnalysis;
|
||||
gap?: number;
|
||||
aspect?: number;
|
||||
}
|
||||
|
||||
export function montage(bitmaps: BitmapType[], options?: MontageOptions) {
|
||||
let minmax = (options && options.analysis) || analyze(bitmaps);
|
||||
if (minmax == null) throw new Error(`Expected an array of bitmaps`);
|
||||
let hitrects = [];
|
||||
let aspect = (options && options.aspect) || 1;
|
||||
let gap = (options && options.gap) || 0;
|
||||
if (minmax.min.w == minmax.max.w && minmax.min.h == minmax.max.h) {
|
||||
let totalPixels = minmax.min.w * minmax.min.h * bitmaps.length;
|
||||
let factors = findIntegerFactors(totalPixels, minmax.max.w, minmax.max.h, aspect);
|
||||
let columns = Math.ceil(factors.a / minmax.min.w); // TODO: rounding?
|
||||
let rows = Math.ceil(factors.b / minmax.min.h);
|
||||
let result = new RGBABitmap(factors.a + gap * (columns-1), factors.b + gap * (rows-1));
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
bitmaps.forEach((bmp) => {
|
||||
result.blit(bmp, result, x, y, 0, 0);
|
||||
hitrects.push({x, y, w: bmp.width, h: bmp.height })
|
||||
x += bmp.width + gap;
|
||||
if (x >= result.width) {
|
||||
x = 0;
|
||||
y += bmp.height + gap;
|
||||
}
|
||||
})
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`combine() only supports uniformly-sized images right now`); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
export namespace png {
|
||||
export function read(url: string): BitmapType {
|
||||
return decode(io.readbin(url));
|
||||
|
@ -325,6 +395,22 @@ export type PixelEditorImageFormat = {
|
|||
skip?:number
|
||||
aspect?:number
|
||||
};
|
||||
|
||||
function remapBits(x:number, arr:number[]) : number {
|
||||
if (!arr) return x;
|
||||
var y = 0;
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
var s = arr[i];
|
||||
if (s < 0) {
|
||||
s = -s-1;
|
||||
y ^= 1 << s;
|
||||
}
|
||||
if (x & (1 << i)) {
|
||||
y ^= 1 << s;
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export function convertWordsToImages(words:ArrayLike<number>, fmt:PixelEditorImageFormat) : Uint8Array[] {
|
||||
var width = fmt.w;
|
||||
|
@ -346,7 +432,7 @@ export type PixelEditorImageFormat = {
|
|||
var shift = 0;
|
||||
for (var x=0; x<width; x++) {
|
||||
var color = 0;
|
||||
var ofs = ofs0; // TODO: remapBits(ofs0, fmt.remap);
|
||||
var ofs = remapBits(ofs0, fmt.remap);
|
||||
// TODO: if (fmt.reindex) { [ofs, shift] = reindexMask(x, fmt.reindex); ofs += ofs0; }
|
||||
for (var p=0; p<nplanes; p++) {
|
||||
var byte = words[ofs + p*pofs + skip];
|
||||
|
|
|
@ -4,12 +4,19 @@ import { isArray } from '../../util';
|
|||
|
||||
export type ColorSource = number | [number,number,number] | [number,number,number,number] | string;
|
||||
|
||||
function checkCount(count) {
|
||||
if (count < 0 || count > 65536) {
|
||||
throw new Error("Palettes cannot have more than 2^16 (65536) colors.");
|
||||
}
|
||||
}
|
||||
|
||||
export class Palette {
|
||||
readonly colors: Uint32Array;
|
||||
|
||||
constructor(arg: number | any[] | Uint32Array) {
|
||||
// TODO: more array types
|
||||
if (typeof arg === 'number') {
|
||||
checkCount(arg);
|
||||
this.colors = new Uint32Array(arg);
|
||||
} else if (arg instanceof Uint32Array) {
|
||||
this.colors = new Uint32Array(arg);
|
||||
|
@ -42,9 +49,10 @@ export function rgba(r: number, g: number, b: number, a: number) : number;
|
|||
export function rgba(obj: ColorSource, g?: number, b?: number, a?: number) : number {
|
||||
if (typeof obj === 'number') {
|
||||
let r = obj;
|
||||
if (g != null && b != null)
|
||||
if (typeof g === 'number' && typeof b === 'number')
|
||||
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24);
|
||||
return obj;
|
||||
else
|
||||
return obj;
|
||||
}
|
||||
if (typeof obj !== 'string' && isArray(obj) && typeof obj[0] === 'number') {
|
||||
let arr = obj;
|
||||
|
@ -75,6 +83,7 @@ type ColorGenFunc = (index: number) => number;
|
|||
|
||||
export namespace palette {
|
||||
export function from(obj: number | any[] | Uint32Array | ColorGenFunc, count?: number) {
|
||||
checkCount(count);
|
||||
if (typeof obj === 'function') {
|
||||
if (!count) throw new Error(`You must also pass the number of colors to generate.`)
|
||||
var pal = new Palette(count);
|
||||
|
@ -87,7 +96,7 @@ export namespace palette {
|
|||
}
|
||||
}
|
||||
export function mono() {
|
||||
return greys(1);
|
||||
return greys(2);
|
||||
}
|
||||
function rgb2() {
|
||||
return new Palette([
|
||||
|
@ -124,6 +133,7 @@ export namespace palette {
|
|||
}
|
||||
}
|
||||
export function helix(count: number) {
|
||||
checkCount(count);
|
||||
return new Palette(chroma.cubehelix().scale().colors(count));
|
||||
}
|
||||
export function factors(count: number, mult?: number) {
|
||||
|
|
|
@ -76,7 +76,7 @@ export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
|
|||
|
||||
export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
|
||||
if (url.startsWith('http:') || url.startsWith('https:')) {
|
||||
return fetchurl(url);
|
||||
return fetchurl(url, type);
|
||||
}
|
||||
if ($$store) {
|
||||
return $$store.getFileData(url);
|
||||
|
@ -93,6 +93,8 @@ export function read(url: string, type?: 'binary' | 'text'): FileData {
|
|||
}
|
||||
let data = readnocache(url, type);
|
||||
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 === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`);
|
||||
$$cache.set(cachekey, data);
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ export class ScriptUISliderType {
|
|||
value: number;
|
||||
constructor(
|
||||
readonly min: number,
|
||||
readonly max: number
|
||||
readonly max: number,
|
||||
readonly step: number
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +26,6 @@ export class ScriptUISlider extends ScriptUISliderType implements io.Loadable {
|
|||
}
|
||||
}
|
||||
|
||||
export function slider(min: number, max: number) {
|
||||
return new ScriptUISlider(min, max);
|
||||
export function slider(min: number, max: number, step?: number) {
|
||||
return new ScriptUISlider(min, max, step || 1);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { BitmapType, IndexedBitmap, RGBABitmap } from "../lib/bitmap";
|
||||
import { Component, render, h, ComponentType } from 'preact';
|
||||
import { Cell } from "../env";
|
||||
import { hex, rgb2bgr, RGBA } from "../../util";
|
||||
import { findIntegerFactors, hex, rgb2bgr, RGBA } from "../../util";
|
||||
import { dumpRAM } from "../../emu";
|
||||
// TODO: can't call methods from this end
|
||||
import { Palette } from "../lib/color";
|
||||
|
@ -10,6 +10,30 @@ import { ScriptUISlider, ScriptUISliderType } from "../lib/scriptui";
|
|||
import { current_project } from "../../../ide/ui";
|
||||
|
||||
const MAX_STRING_LEN = 100;
|
||||
const DEFAULT_ASPECT = 1;
|
||||
|
||||
interface ObjectStats {
|
||||
type: 'prim' | 'complex' | 'bitmap'
|
||||
width: number
|
||||
height: number
|
||||
units: 'em' | 'px'
|
||||
}
|
||||
|
||||
// TODO
|
||||
class ObjectAnalyzer {
|
||||
recurse(obj: any) : ObjectStats {
|
||||
if (typeof obj === 'string') {
|
||||
return { type: 'prim', width: obj.length, height: 1, units: 'em' }
|
||||
} else if (obj instanceof Uint8Array) {
|
||||
return { type: 'complex', width: 60, height: Math.ceil(obj.length / 16), units: 'em' }
|
||||
} else if (typeof obj === 'object') {
|
||||
let stats : ObjectStats = { type: 'complex', width: 0, height: 0, units: 'em'};
|
||||
return stats; // TODO
|
||||
} else {
|
||||
return { type: 'prim', width: 12, height: 1, units: 'em' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ColorComponentProps {
|
||||
rgbavalue: number;
|
||||
|
@ -18,13 +42,17 @@ interface ColorComponentProps {
|
|||
class ColorComponent extends Component<ColorComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let rgb = this.props.rgbavalue & 0xffffff;
|
||||
var htmlcolor = `#${hex(rgb2bgr(rgb),6)}`;
|
||||
var textcol = (rgb & 0x008000) ? 'black' : 'white';
|
||||
let bgr = rgb2bgr(rgb);
|
||||
var htmlcolor = `#${hex(bgr,6)}`;
|
||||
var textcolor = (rgb & 0x008000) ? 'black' : 'white';
|
||||
var printcolor = hex(rgb & 0xffffff, 6); // TODO: show index instead?
|
||||
return h('div', {
|
||||
class: 'scripting-color',
|
||||
style: `background-color: ${htmlcolor}; color: ${textcol}`,
|
||||
style: `background-color: ${htmlcolor}; color: ${textcolor}`,
|
||||
alt: htmlcolor, // TODO
|
||||
}, '\u00a0');
|
||||
}, [
|
||||
h('span', { }, printcolor )
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +73,6 @@ class BitmapComponent extends Component<BitmapComponentProps> {
|
|||
}
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
return h('canvas', {
|
||||
class: 'pixelated',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
...this.props
|
||||
|
@ -124,7 +151,7 @@ class BitmapEditor extends Component<BitmapComponentProps, BitmapEditorState> {
|
|||
}
|
||||
return h('div', {
|
||||
tabIndex: 0,
|
||||
class: this.state.isEditing ? 'scripting-cell' : '' // TODO
|
||||
class: this.state.isEditing ? 'scripting-editor' : '' // TODO
|
||||
}, [
|
||||
bitmapRender,
|
||||
okCancel,
|
||||
|
@ -163,6 +190,8 @@ class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, Object
|
|||
let hdrclass = '';
|
||||
if (expandable)
|
||||
hdrclass = this.state.expanded ? 'tree-expanded' : 'tree-collapsed'
|
||||
let propName = this.props.name || null;
|
||||
if (propName.startsWith("$$")) propName = null;
|
||||
return h('div', {
|
||||
class: 'tree-content',
|
||||
key: `${this.props.objpath}__tree`
|
||||
|
@ -171,10 +200,8 @@ class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, Object
|
|||
class: 'tree-header ' + hdrclass,
|
||||
onClick: expandable ? () => this.toggleExpand() : null
|
||||
}, [
|
||||
this.props.name + "",
|
||||
h('span', { class: 'tree-value' }, [
|
||||
getShortName(this.props.object)
|
||||
])
|
||||
h('span', { class: 'tree-key' }, [ propName, expandable ]),
|
||||
h('span', { class: 'tree-value' }, [ getShortName(this.props.object) ])
|
||||
]),
|
||||
this.state.expanded ? objectToContentsDiv(this.props.object, this.props.objpath) : null
|
||||
]);
|
||||
|
@ -243,26 +270,25 @@ function objectToDiv(object: any, name: string, objpath: string) {
|
|||
if (object == null) {
|
||||
return object + "";
|
||||
} else if (object['uitype']) {
|
||||
children.push(h(UISliderComponent, { iokey: objpath, uiobject: object }));
|
||||
return h(UISliderComponent, { iokey: objpath, uiobject: object });
|
||||
} else if (object['literaltext']) {
|
||||
children.push(h("pre", { }, [ object['literaltext'] ])); // TODO
|
||||
} else if (isIndexedBitmap(object)) {
|
||||
//Object.setPrototypeOf(object, IndexedBitmap.prototype); // TODO: use Object.create()?
|
||||
addBitmapComponent(children, object);
|
||||
} else if (isRGBABitmap(object)) {
|
||||
//Object.setPrototypeOf(object, RGBABitmap.prototype); // TODO: use Object.create()?
|
||||
addBitmapComponent(children, object);
|
||||
return h("pre", { }, [ object['literaltext'] ]);
|
||||
} else if (isIndexedBitmap(object) || isRGBABitmap(object)) {
|
||||
return h(BitmapEditor, { bitmap: object, width: object.width, height: object.height });
|
||||
} else if (isPalette(object)) {
|
||||
// TODO: make sets of 2/4/8/16/etc
|
||||
props.class += ' scripting-grid ';
|
||||
object.colors.forEach((val) => {
|
||||
children.push(h(ColorComponent, { rgbavalue: val }));
|
||||
})
|
||||
if (object.colors.length <= 64) {
|
||||
props.class += ' scripting-flex ';
|
||||
object.colors.forEach((val) => {
|
||||
children.push(h(ColorComponent, { rgbavalue: val }));
|
||||
})
|
||||
return h('div', props, children);
|
||||
} else {
|
||||
let {a,b} = findIntegerFactors(object.colors.length, 1, 1, DEFAULT_ASPECT);
|
||||
return objectToDiv({ rgba: object.colors, width: a, height: b }, name, objpath);
|
||||
}
|
||||
} else {
|
||||
return h(ObjectKeyValueComponent, { name, object, objpath }, []);
|
||||
}
|
||||
let div = h('div', props, children);
|
||||
return div;
|
||||
}
|
||||
|
||||
function fixedArrayToDiv(tyarr: Array<number>, bpel: number, objpath: string) {
|
||||
|
@ -288,11 +314,7 @@ function objectToContentsDiv(object: {} | [], objpath: string) {
|
|||
}
|
||||
let objectEntries = Object.entries(object);
|
||||
let objectDivs = objectEntries.map(entry => objectToDiv(entry[1], entry[0], `${objpath}.${entry[1]}`));
|
||||
return h('div', {}, objectDivs);
|
||||
}
|
||||
|
||||
function addBitmapComponent(children, bitmap: BitmapType) {
|
||||
children.push(h(BitmapEditor, { bitmap: bitmap, width: bitmap.width, height: bitmap.height }));
|
||||
return h('div', { class: 'scripting-flex' }, objectDivs);
|
||||
}
|
||||
|
||||
interface UISliderComponentProps {
|
||||
|
@ -307,17 +329,16 @@ class UISliderComponent extends Component<UISliderComponentProps> {
|
|||
this.props.iokey,
|
||||
h('input', {
|
||||
type: 'range',
|
||||
min: slider.min,
|
||||
max: slider.max,
|
||||
value: this.props.uiobject.value,
|
||||
min: slider.min / slider.step,
|
||||
max: slider.max / slider.step,
|
||||
value: this.props.uiobject.value / slider.step,
|
||||
onInput: (ev) => {
|
||||
let newValue = { value: ev.target.value };
|
||||
slider.value = parseFloat(ev.target.value);
|
||||
let newValue = { value: parseFloat(ev.target.value) * slider.step };
|
||||
this.setState(this.state);
|
||||
current_project.updateDataItems([{key: this.props.iokey, value: newValue}]);
|
||||
}
|
||||
}, []),
|
||||
slider.value
|
||||
getShortName(slider.value)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -328,22 +349,15 @@ export class Notebook {
|
|||
public readonly maindiv: HTMLElement
|
||||
) {
|
||||
maindiv.classList.add('vertical-scroll');
|
||||
//maindiv.classList.add('container')
|
||||
}
|
||||
updateCells(cells: Cell[]) {
|
||||
let hTree = cells.map(cell => {
|
||||
//return objectToDiv(cell.object, cell.id)
|
||||
return h('div', {
|
||||
class: 'scripting-cell',
|
||||
key: `${cell.id}__cell`
|
||||
}, [
|
||||
objectToDiv(cell.object, cell.id, cell.id)
|
||||
])
|
||||
/*
|
||||
let cellDiv = objectToDiv(cell.object, cell.id);
|
||||
cellDiv.props['class'] += ' scripting-cell ';
|
||||
return cellDiv;
|
||||
*/
|
||||
});
|
||||
render(hTree, this.maindiv);
|
||||
}
|
||||
|
|
|
@ -631,3 +631,34 @@ export function escapeHTML(s: string): string {
|
|||
return s.replace(/[&]/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>');
|
||||
}
|
||||
|
||||
// lame factorization for displaying bitmaps
|
||||
// returns a > b such that a * b == x (or higher), a >= mina, b >= minb
|
||||
export function findIntegerFactors(x: number, mina: number, minb: number, aspect: number) : {a: number, b: number} {
|
||||
let a = x;
|
||||
let b = 1;
|
||||
if (minb > 1 && minb < a) {
|
||||
a = Math.ceil(x / minb);
|
||||
b = minb;
|
||||
}
|
||||
while (a > b) {
|
||||
let a2 = a;
|
||||
let b2 = b;
|
||||
if ((a & 1) == 0) {
|
||||
b2 = b * 2;
|
||||
a2 = a / 2;
|
||||
}
|
||||
if ((a % 3) == 0) {
|
||||
b2 = b * 3;
|
||||
a2 = a / 3;
|
||||
}
|
||||
if ((a % 5) == 0) {
|
||||
b2 = b * 5;
|
||||
a2 = a / 5;
|
||||
}
|
||||
if (a2 < mina) break;
|
||||
if (a2 < b2 * aspect) break;
|
||||
a = a2;
|
||||
b = b2;
|
||||
}
|
||||
return {a, b};
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class VCSPlatform extends BasePlatform {
|
|||
async start() {
|
||||
var self : VCSPlatform = this;
|
||||
// load Javatari and configure settings
|
||||
await loadScript("javatari.js/release/javatari/javatari.js");
|
||||
await loadScript("javatari/javatari.js");
|
||||
Javatari.AUTO_START = false;
|
||||
Javatari.SHOW_ERRORS = false;
|
||||
Javatari.CARTRIDGE_CHANGE_DISABLED = true;
|
||||
|
|
|
@ -27,6 +27,7 @@ export async function runJavascript(step: BuildStep): Promise<BuildStepResult> {
|
|||
let output : RunResult = { cells, state };
|
||||
return { output: output };
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return { errors: env.extractErrors(e) };
|
||||
} finally {
|
||||
io.$$setupFS(null);
|
||||
|
|
Loading…
Reference in New Issue