master
Michael Murtaugh 3 years ago
parent a4e25fd2fd
commit e57793cd6b

1
.gitignore vendored

@ -1,3 +1,4 @@
.ipynb_checkpoints/
node_modules/
package-lock.json
build/

@ -1,8 +1,13 @@
all: dist/codemirror.css dist/inspect_tic.js
all: build/codemirror.css build/ticeditor.js
dist/inspect_tic.js: src/inspect_tic.js
# dist/inspect_tic.js: src/inspect_tic.js
# npx webpack
build/ticeditor.js: src/ticeditor.js
npx webpack
dist/codemirror.css: node_modules/codemirror/lib/codemirror.css
build/codemirror.css: node_modules/codemirror/lib/codemirror.css
cp $< $@
build/font-awesome.min.css:
wget http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css -O $@

File diff suppressed because one or more lines are too long

@ -1,18 +1,53 @@
<html>
<head>
<meta charset="utf-8">
<title>TIC-80 files</title>
<link rel="stylesheet" href="dist/codemirror.css">
<title>TICLAB</title>
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<!-- <link rel="stylesheet" href="build/codemirror.css"> -->
<!-- <link rel="stylesheet" href="node_modules/codemirror/theme/base16-dark.css"> -->
<!-- <link rel="stylesheet" href="node_modules/codemirror/theme/monokai.css"> -->
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
<style>
.CodeMirror { height: 500px }
#dialog {
position: absolute;
z-index: 10000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
top: 0px;
left: 0px;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #00000066;
display: none;
}
#openfile {
width: 300px;
padding: 0px 20px;
background: white;
color: black;
display: none;
}
</style>
</head>
<body>
<h1>TIC80 files</h1>
<!-- <h1>TIC80 files</h1>
<div id="listing"></div>
<div id="code"></div>
<script src="dist/inspect_tic.js"></script>
<div id="code"></div>-->
<div id="dialog">
<div id="openfile">
<h2>Open file...</h2>
<p>
<input type="file" id="openfile_fileinput">
<button id="openfile_cancel" class="cancel">Cancel</button>
</p>
</div>
</div>
<script src="build/ticeditor.js"></script>
</body>
</html>

@ -1,22 +1,36 @@
{
"name": "ticparse.js",
"private": true,
"version": "1.0.0",
"description": "",
"main": "db.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "webpack",
"clean": "rimraf build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"rimraf": "^2.5.2",
"source-map-loader": "0.2.4",
"style-loader": "^1.0.2",
"webpack": "^5.16.0",
"webpack-cli": "^4.4.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"@lumino/commands": "^1.12.0",
"@lumino/default-theme": "^0.9.0",
"@lumino/dragdrop": "^1.7.1",
"@lumino/messaging": "^1.4.3",
"@lumino/widgets": "^1.18.0",
"codemirror": "^5.59.2",
"d3-selection": "^2.0.0",
"es6-promise": "^4.0.5",
"filesize": "^6.1.0",
"indexed-pdb": "^1.1.2"
"indexed-pdb": "^1.1.2",
"url-loader": "^4.1.1"
}
}

@ -0,0 +1,177 @@
import { CommandRegistry } from '@lumino/commands';
import { Message } from '@lumino/messaging';
import { BoxPanel, CommandPalette, ContextMenu, DockPanel, Menu, MenuBar, Widget } from '@lumino/widgets';
import '../style/index.css';
// import '@fortawesome/fontawesome-free/css/all.min.css';
import { EditorWidget } from './editor.js';
import { ListingWidget } from './listing.js';
import * as ticdb from './ticdb.js';
// class ContentWidget extends Widget {
// static createNode() {
// let node = document.createElement('div');
// let content = document.createElement('div');
// let input = document.createElement('input');
// input.placeholder = 'Placeholder...';
// content.appendChild(input);
// node.appendChild(content);
// return node;
// }
// constructor(name) {
// super({ node: ContentWidget.createNode() });
// this.setFlag(Widget.Flag.DisallowLayout);
// this.addClass('content');
// this.addClass(name.toLowerCase());
// this.title.label = name;
// this.title.closable = true;
// this.title.caption = `Long description for: ${name}`;
// }
// get inputNode() {
// return this.node.getElementsByTagName('input')[0];
// }
// onActivateRequest(Message) {
// if (this.isAttached) {
// this.inputNode.focus();
// }
// }
// }
window._tic = {};
const commands = new CommandRegistry();
function main () {
const dialog = document.getElementById("dialog"),
openfile = document.getElementById("openfile"),
openfile_fileinput = document.getElementById("openfile_fileinput"),
openfile_cancel = document.getElementById("openfile_cancel");
function dialog_is_open () {
return dialog.style.display != "none";
}
function close_dialog() {
dialog.style.display = "none";
openfile.style.display = "none";
openfile_fileinput.value = null;
}
function show_openfile() {
dialog.style.display = "flex";
openfile.style.display = "block";
openfile_fileinput.focus();
}
openfile_cancel.addEventListener("click", e=> {
close_dialog();
})
commands.addCommand('tic:open', {
label: 'Open...',
mnemonic: 1,
iconClass: 'fa fa-file',
execute: () => {
// console.log('Import...');
show_openfile();
}
});
commands.addCommand('tic:save', {
label: 'Save',
mnemonic: 0,
iconClass: 'fa fa-save',
execute: async () => {
console.log('Save');
// current_cart.code.text = codesrc; // code.getValue();
if (window._tic.filename && window._tic.cart) {
let cart = window._tic.cart;
cart.code.text = window.code.getValue();
// console.log("saving code", cart.code.text);
await ticdb.save_cart(window._tic.filename, cart, window._tic.item.mode);
listing.sync();
}
}
});
commands.addCommand('tic:save-as', {
label: 'Save as...',
mnemonic: 0,
iconClass: 'fa fa-save',
execute: async () => {
console.log('Save as...');
if (window._tic.cart) {
let filename = prompt("Save as...");
if (!filename) { return; }
window._tic.filename = filename;
let cart = window._tic.cart;
cart.code.text = window.code.getValue();
// console.log("saving code", cart.code.text);
await ticdb.save_cart(filename, cart, window._tic.item.mode);
listing.sync();
}
}
});
commands.addKeyBinding({
keys: ['Accel S'],
selector: 'body',
command: 'tic:save'
});
commands.addKeyBinding({
keys: ['Accel O'],
selector: 'body',
command: 'tic:open'
});
let filemenu = new Menu({ commands });
filemenu.addItem({ command: 'tic:open' });
filemenu.addItem({ command: 'tic:save' });
filemenu.addItem({ command: 'tic:save-as' });
// filemenu.addItem({ type: 'separator' });
filemenu.title.label = 'File';
filemenu.title.mnemonic = 0;
let bar = new MenuBar();
bar.addMenu(filemenu);
bar.id = 'menuBar';
document.addEventListener('keydown', (event) => {
// console.log("keydown", event);
if (event.key == "Escape" && dialog_is_open()) {
close_dialog();
} else {
commands.processKeydownEvent(event);
}
});
let listing = new ListingWidget('Listing');
let editor = new EditorWidget('Editor');
let dock = new DockPanel();
dock.addWidget(listing);
dock.addWidget(editor, { mode: 'split-right', ref: listing });
// dock.addWidget(editor2, { ref: editor1 });
dock.id = 'dock';
BoxPanel.setStretch(dock, 1);
let main = new BoxPanel({ direction: 'left-to-right', spacing: 0 });
main.id = 'main';
// main.addWidget(palette);
main.addWidget(dock);
window.onresize = () => { main.update(); };
Widget.attach(bar, document.body);
Widget.attach(main, document.body);
}
window.onload = main;

@ -0,0 +1,63 @@
import { Widget } from '@lumino/widgets';
import * as CodeMirror from 'codemirror';
import 'codemirror/mode/lua/lua.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter.js';
// import 'codemirror/lib/codemirror.css';
// import 'codemirror/theme/monokai.css';
const TIC_SPECIALS = ["TIC", "SCN", "OVR", "btn", "btnp", "clip", "cls", "circ", "circb", "exit", "fget", "font", "fset", "key", "keyp", "line", "map", "memcpy", "memset", "mget", "mouse", "mset", "music", "peek", "peek4", "pix", "pmem", "poke", "poke4", "print", "rect", "rectb", "reset", "sfx", "spr", "sync", "time", "tstamp", "trace", "tri", "textri"],
CM_OPTS = {
mode: {'name': 'lua', 'specials': TIC_SPECIALS, 'fold': true},
// mode: {'name': 'lua'},
theme: 'default',
lineNumbers: true,
lineWrapping: true,
// extraKeys: {"Ctrl-S": function(cm){ save(); }},
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
};
export class EditorWidget extends Widget {
static createNode() {
let node = document.createElement('div');
let editor = document.createElement('div');
// let input = document.createElement('input');
// input.placeholder = 'Placeholder...';
// content.appendChild(input);
node.appendChild(editor);
return node;
}
constructor(name) {
super({ node: EditorWidget.createNode() });
// let editor_div = this.node.getElementsByTagName('div')[0];
this.cm = CodeMirror(this.editorDiv, CM_OPTS);
window.code = this.cm;
this.setFlag(Widget.Flag.DisallowLayout);
this.addClass('content');
this.addClass(name.toLowerCase());
this.title.label = name;
this.title.closable = false;
this.title.caption = `Editor: ${name}`;
}
get editorDiv () {
return this.node.getElementsByTagName('div')[0];
}
get inputNode() {
return this.node.getElementsByTagName('input')[0];
}
onActivateRequest(Message) {
// if (this.isAttached) {
// this.inputNode.focus();
// }
}
}

@ -48,6 +48,18 @@ listing.append("button").html("save as...").on("click", (e, d) => {
}
});
listing.append("input").attr("type", "file").on("change", function (e, d) {
console.log("filechange", this.files);
if (this.files.length == 1) {
this.files[0].arrayBuffer().then(result => {
console.log("file", result);
var file_cart = tic.parsetic(result);
console.log("file_cart", file_cart);
})
// reader.readAsArrayBuffer(this.files[0]);
}
});
thead.selectAll("th").data(["name", "size", "time", "&nbsp;"]).enter().append("th").html(d => d);
async function save() {
@ -98,6 +110,7 @@ function filename_from_key (key) {
current_item = await load_key(DB_NAME+"/"+current_filename);
console.log("loaded item", current_item);
current_cart = tic.parsetic(current_item.contents);
console.log("current_cart", current_cart);
code.setValue(current_cart.code.text);
}
});

@ -0,0 +1,139 @@
import { Widget } from '@lumino/widgets';
import { openDB } from 'indexed-pdb'
import * as tic from './ticparse.js'
import * as filesize from 'filesize';
import {select, selectAll} from "d3-selection";
import * as CodeMirror from 'codemirror';
import * as ticdb from './ticdb.js';
export class ListingWidget extends Widget {
static createNode() {
let node = document.createElement('div');
let editor = document.createElement('div');
// let input = document.createElement('input');
// input.placeholder = 'Placeholder...';
// content.appendChild(input);
node.appendChild(editor);
return node;
}
constructor(name) {
super({ node: ListingWidget.createNode() });
// let editor_div = this.node.getElementsByTagName('div')[0];
// this.cm = CodeMirror(this.editorDiv, CM_OPTS);
this.setFlag(Widget.Flag.DisallowLayout);
// this.addClass('content');
this.addClass(name.toLowerCase());
this.title.label = name;
this.title.closable = false;
this.title.caption = `Editor: ${name}`;
this.listing = select(this.node),
this.table = this.listing.append("table"),
this.thead = this.table.append("thead").append("tr"),
this.tbody = this.table.append("tbody");
this.thead.selectAll("th").data(["name", "size", "time", "&nbsp;"]).enter().append("th").html(d => d);
this.sync();
}
// get inputNode() {
// return this.node.getElementsByTagName('input')[0];
// }
onActivateRequest(Message) {
// if (this.isAttached) {
// this.inputNode.focus();
// }
}
async sync () {
console.log("listing.sync");
try {
const db = await openDB(ticdb.DB_NAME);
var objectStore = db.transaction([ticdb.DB_STORE], "readonly").objectStore(ticdb.DB_STORE);
var files = [];
await objectStore.openCursor().then(function push_item(cursor) {
if (!cursor) { return }
var filename = ticdb.filename_from_key(cursor.key);
if (filename && !filename.startsWith(".")) {
// console.log(cursor.value.contents);
var blob = new Blob([cursor.value.contents]);
// console.log("blob", blob, {type: "application/octet-stream"});
var objecturl = URL.createObjectURL(blob);
files.push({
key: cursor.key,
filename: filename,
size: cursor.value.contents.length,
timestamp: cursor.value.timestamp,
objecturl: objecturl
});
}
return cursor.continue().then(push_item)
})
// console.log("files", files);
let join = this.tbody.selectAll("tr").data(files, d=>d.key);
join.exit().remove();
let tr = join.enter().append("tr");
tr.append("td").attr("class", "link").append("a").attr("href", "#").html(d => d.filename).on("click", async (e, d) => {
e.preventDefault();
var filename = ticdb.filename_from_key(d.key);
if (filename) {
window._tic.filename = filename;
window._tic.item = await ticdb.load_key(ticdb.DB_NAME+"/"+window._tic.filename);
// console.log("loaded item", window._tic.item);
window._tic.cart = tic.parsetic(window._tic.item.contents.buffer);
// console.log("current_cart", current_cart);
window.code.setValue(window._tic.cart.code.text);
}
});
tr.append("td").attr("class", "size");
tr.append("td").attr("class", "timestamp");
tr.append("td").attr("class", "download").append("a").attr("href", d=>d.objecturl).attr("download", d=>d.filename).html("download");
// update Merged enter + join
let update = tr.merge(join);
update.select(".size").html(d => filesize(d.size));
update.select(".timestamp").html(d => d.timestamp.toLocaleString());
} catch (error) {
console.log(error, 'any error during the process');
}
}
}
/*
listing.append("button").html("save as...").on("click", (e, d) => {
if (current_filename) {
var save_filename = prompt("key", current_filename);
if (save_filename) {
current_filename = save_filename;
save();
}
}
});
*/
/*
listing.append("input").attr("type", "file").on("change", function (e, d) {
console.log("filechange", this.files);
if (this.files.length == 1) {
this.files[0].arrayBuffer().then(result => {
console.log("file", result);
var file_cart = tic.parsetic(result);
console.log("file_cart", file_cart);
})
// reader.readAsArrayBuffer(this.files[0]);
}
});
*/

@ -0,0 +1,36 @@
import { openDB } from 'indexed-pdb'
export const DB_NAME = "/com.nesbox.tic/TIC-80";
export const DB_STORE = "FILE_DATA";
export function filename_from_key (key) {
if (key.startsWith(DB_NAME+"/")) { return key.substr((DB_NAME+"/").length); }
}
export async function load_key (key) {
const db = await openDB(DB_NAME),
objectStore = db.transaction([DB_STORE], "readonly").objectStore(DB_STORE);
return await objectStore.get(key);
}
export async function save_key (key, item) {
const db = await openDB(DB_NAME),
objectStore = db.transaction([DB_STORE], "readwrite").objectStore(DB_STORE);
return await objectStore.put(item, key);
}
export async function save_cart(filename, cart, mode) {
return await save_key(DB_NAME+"/"+filename, {timestamp: new Date(), contents: cart.tobuffer(), mode: mode});
}
export function compareBuffers (a, b) {
var ad = new DataView(a.buffer),
bd = new DataView(b.buffer);
if (ad.byteLength != bd.byteLength) { return false; }
for (var i=0; i<ad.byteLength; i++) {
if (ad.getUint8(i, 1) != bd.getUint8(i, 1)) { return false;}
}
return true;
}

@ -30,9 +30,9 @@ function tohex (a) {
return ret;
}
export function parsetic (buf) {
export function parsetic (buffer) {
var ret = {},
data = new DataView(buf.buffer),
data = new DataView(buffer),
i=0,
bank,
ctype,
@ -42,7 +42,7 @@ export function parsetic (buf) {
ret.chunks = [];
ret.chunks_by_type = {};
while (i<buf.length) {
while (i<data.byteLength) {
firstbyte = data.getUint8(i, LITTLE_ENDIAN);
bank = firstbyte >> 5;
ctype = firstbyte & 0b11111;
@ -55,7 +55,7 @@ export function parsetic (buf) {
chunk.original_length = clen; // can be out of sync with data, only for control purposes
ret.chunks.push(chunk);
ret.chunks_by_type[ctype] = chunk;
var chunk_data = buf.slice(i, i+clen);
var chunk_data = buffer.slice(i, i+clen);
if (ctype == CHUNK_CODE) {
ret.code = chunk;
chunk.text = new TextDecoder().decode(chunk_data);

@ -0,0 +1,59 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
.content {
min-width: 50px;
min-height: 50px;
display: flex;
flex-direction: column;
/*padding: 8px;*/
border: 1px solid #C0C0C0;
border-top: none;
background: white;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
.CodeMirror {
height: 100%;
}
.content > div {
flex: 1 1 auto;
/*border: 1px solid #C0C0C0;*/
overflow: auto;
}
.content input {
margin: 8px;
}
.red > div {
background: #E74C3C;
}
.yellow > div {
background: #F1C40F;
}
.green > div {
background: #27AE60;
}
.blue > div {
background: #3498DB;
}

@ -0,0 +1,44 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2018, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
@import '~@lumino/default-theme/style/index.css';
@import './content.css';
body {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
#menuBar {
flex: 0 0 auto;
}
#main {
flex: 1 1 auto;
}
#palette {
min-width: 300px;
border-right: 1px solid #DDDDDD;
}
#dock {
padding: 4px;
}

@ -1,10 +1,17 @@
const path = require('path');
module.exports = {
entry: './src/inspect_tic.js',
entry: './src/app.js',
mode: "development",
output: {
filename: 'inspect_tic.js',
path: path.resolve(__dirname, 'dist'),
filename: 'ticeditor.js',
path: path.resolve(__dirname, 'build'),
},
module: {
rules: [
{ test: /\.js$/, use: ["source-map-loader"], enforce: "pre" },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.png$/, use: 'file-loader' },
]
}
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save