Refactor mscript GUI code into Typescript

This commit is contained in:
Matthew McWilliams 2022-08-07 22:10:53 -04:00
parent 3ec1373f08
commit 6601c030f7
11 changed files with 907 additions and 237 deletions

243
app/lib/mscript/index.d.ts vendored Normal file
View File

@ -0,0 +1,243 @@
/** @module lib/mscript */
interface RGB extends Array<number> {
[index: number]: number;
}
/** class Mscript */
export declare class Mscript {
output: any;
lines: string[];
cam: number;
cam2: number;
proj: number;
proj2: number;
color: string;
loops: any[];
rec: number;
two: string;
three: string;
four: string;
arr: any[];
meta: string[];
target: number;
dist: number;
variables: any;
/**
* @constructor
* Create new Mscript interpreter
**/
constructor();
/**
* Clear the state of the script
*/
clear(): void;
/**
* Main function, accepts multi-line string, parses into lines
* and interprets the instructions from the text. Returns an array
* of steps to be fed into the mcopy sequence.
*
* @param {string} text Mscript text to interpret
* @param {function} callback Function to call when string is interpreted
*
* @returns {object} if callback is not provided
*/
interpret(text: string, callback?: Function): any;
/**
* Interprets variables for complex sequence behavior.
* TODO: Fully implement, add test coverage
*
* @param {string} line Line containing a variable assignment
*
**/
variable(line: string): void;
/**
* Replace variable with value at time of interpretation
* TODO: Implement this please
*
* @param {string} line Line containing variable to be replaced with value
*
* @returns {string} New string to be interpreted
**/
variable_replace(line: string): string;
/**
* Interpret a basic two character command
*
* @param {string} line Line of script to interpret
* @param {string} short The short command to use
*/
basic_cmd(line: string, short: string): void;
/**
* Start a new loop
*
* @param {string} line Line to evaluate as either loop or fade
* @param {boolean} fade Flag as true if fade
*/
new_loop(line: string, fade?: boolean): void;
/**
* Close the most recent loop
*
* @param {string} line Line to interpret
*/
end_loop(line: string): void;
/**
* Move camera to explicitly-defined frame
*
* @param {string} line Line to interpret with camera move statement
*/
move_cam(line: string): void;
/**
* Move secondary camera to explicitly-defined frame
*
* @param {string} line Line to interpret with camera move statement
*/
move_cam2(line: string): void;
/**
* Move projector to explicitly-defined frame
*
* @param {string} line Line containing `move` statement to interpret
*/
move_proj(line: string): void;
/**
* Move projector to explicitly-defined frame
*
* @param {string} line Line containing `move` statement to interpret
*/
move_proj2(line: string): void;
/**
* Set the state of either the cam or projector
*
* @param line {string} String containing set statement
*/
set_state(line: string): void;
/**
* Return the last loop
*
* @returns {object}
*/
last_loop(): any;
/**
* Return the second-last loop
*
* @returns {object} Loop array
*/
parent_loop(): any;
/**
* Extract the loop count integer from a LOOP cmd
*
* @returns {integer} Loop count in string parsed into integer
*/
loop_count(str: string): number;
/**
* Execute a fade of frame length, from color to another color
*
* @param {string} line Line containing a fade initiator
*/
fade(line: string): void;
/**
* Extract the fade length integer from a FADE cmd
*
* @param {string} str Line containing the length of fade in frames
*/
fade_count(str: string): number;
/**
* Extract the start color from a string
*
* @param {string} str Line containing the start color value in a fade initiator
*
* @returns {array} Array containing RGB color values
*/
fade_start(str: string): RGB;
/**
* Extract the end color from a string
*
* @param {string} str Line containing the end color value in a fade initiator
*
* @returns {array} Array containing RGB color values
*/
fade_end(str: string): RGB;
/**
* Determine the state of a fade at a particular frame in the sequence, x
*
* @param {array} start Color the fade starts at
* @param {array} end Color the fade finishes at
* @param {integer} len Total length of the fade in frames
* @param {integer} x Position of the fade to get color value of
*
* @returns {array} Array containing RGB color values
*/
fade_rgb(start: RGB, end: RGB, len: number, x: number): string;
/**
* Parse string into array of RGB color values. 0-255 octet.
*
* @param {string} str String containing only color values as `#,#,#`
**/
rgb(str: string): RGB;
/**
* Cast RGB color values as string
*
* @param {array} arr Array to join into string
*
* @returns {string} String of RGB values
**/
rgb_str(arr: RGB): string;
/**
* Increase the state of a specific object, such as the camera/projector,
* by the value defined in val.
*
* @param {string} cmd String representing command to interpret and update state
*/
update(cmd: string, val?: number): void;
/**
* Split string on command, turn into array of commands
* as long as count variable. Default 1.
*
* @param {string} str String to split
* @param {string} cmd String representing command to split at
*
* @returns {array} Array containing commands
*/
str_to_arr(str: string, cmd: string): string[];
/**
* Split a string on a command to extract data for light array
*
* @param {string} str String containing light command
* @param {string} cmd String representing command
*
* @returns {array} An RGB array containing the color values
*/
light_to_arr(str: string, cmd: string): RGB;
/**
* Split a string to extract an rgb color value
*
* @param {string} Color string assign to color property
*/
light_state(str: string): void;
/**
* Interpret a pause command
*
* @param {string} line String containing pause command
**/
pause(line: string): void;
/**
* Interpret an alert command
*
* @param {string} line String containing pause command
**/
alert(line: string): void;
/**
* Throw an error with specific message
*
* @param {string} msg Error message to print
*/
fail(msg: string): void;
/**
* Determine if array contains matching elements of
* another array
*
* @param {Array} arr Original array to compare
* @param {Array} arr2 Array to compare elements from
*
* @returns {boolean} Whether arr contains elements in arr2
**/
contains(arr: string[], arr2: string[]): boolean;
}
export {};

View File

@ -1,4 +1,6 @@
'use strict'; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mscript = void 0;
const BLACK = '0,0,0'; const BLACK = '0,0,0';
const WHITE = '255,255,255'; const WHITE = '255,255,255';
const CMD = [ const CMD = [
@ -122,7 +124,7 @@ class Mscript {
* *
* @returns {object} if callback is not provided * @returns {object} if callback is not provided
*/ */
interpret(text, callback) { interpret(text, callback = null) {
this.clear(); this.clear();
if (typeof text === 'undefined') { if (typeof text === 'undefined') {
return this.fail('No input'); return this.fail('No input');
@ -200,7 +202,7 @@ class Mscript {
if (this.contains(this.arr, PROJECTOR_SECONDARY)) { if (this.contains(this.arr, PROJECTOR_SECONDARY)) {
this.output.proj2 = this.proj2; this.output.proj2 = this.proj2;
} }
if (typeof callback !== 'undefined') { if (typeof callback !== 'undefined' && callback != null) {
//should only be invoked by running mscript.tests() //should only be invoked by running mscript.tests()
callback(this.output); callback(this.output);
} }
@ -967,5 +969,6 @@ class Mscript {
return arr.some(r => arr2.includes(r)); return arr.some(r => arr2.includes(r));
} }
} }
exports.Mscript = Mscript;
module.exports = Mscript; module.exports = Mscript;
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,233 +1,280 @@
const mse = {}; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference path ="jquery.d.ts"/>
const mscript_1 = require("mscript");
/****** /******
Mscript GUI Mscript GUI
*******/ *******/
mse.mscript = {}; class MscriptGUI {
mse.mscript.editor = {}; constructor() {
mse.mscript.data = {}; this.editor = {};
mse.mscript.raw = ''; this.data = {};
mse.mscript.init = function () { this.raw = '';
'use strict'; }
$('#editor').val('CF 1\nPF 1'); /**
mse.mscript.editor = CodeMirror.fromTextArea(document.getElementById('editor'), { * Initializes the mscript GUI. Sets up CodeMirror instance,
lineNumbers: true, * binds events and sets height of editor.
mode: 'python', **/
matchBrackets: true, init() {
theme: 'monokai' const startingScript = `CF 1
}); PF 1`;
mse.mscript.editor.setSize(null, $(window).height() - $('footer').eq(0).height() - 30); const editorHeight = $(window).height() - $('footer').eq(0).height() - 30;
mse.mscript.editor.on('change', function (e) { const editorElem = document.getElementById('editor');
// const editorConfig = {
}); lineNumbers: true,
$(document).on('resize', function (e) { mode: 'python',
mse.mscript.editor.setSize(null, $(window).height() - $('footer').eq(0).height() - 30); matchBrackets: true,
}); theme: 'monokai'
}; };
mse.mscript.open = function () { $('#editor').val(startingScript);
'use strict'; this.editor = CodeMirror.fromTextArea(editorElem, editorConfig);
mse.mscript.editor.setSize(null, $(window).height() - $('footer').eq(0).height() - 30); this.editor.setSize(null, editorHeight);
mse.mscript.editor.refresh(); this.editor.on('change', (e) => { });
}; $(document).on('resize', function (e) {
mse.mscript.fromSequence = function () { this.editor.setSize(null, editorHeight);
//ehhhhh }.bind(this));
'use strict'; }
let str; /**
let tmp = []; * Callback for when open event occurs.
let cont; **/
let cmd; open() {
//str = seq.grid.map(step => { return step.cmd }).join('\n'); //quick hack //recalcuate in case resize has occurred needed
//console.dir(seq.grid); const editorHeight = $(window).height() - $('footer').eq(0).height() - 30;
for (let step of seq.grid) { this.editor.setSize(null, editorHeight);
if (!step || !step.cmd) continue; this.editor.refresh();
cmd = step.cmd; }
if (tmp.length > 0 && tmp[tmp.length - 1].cmd === cmd) { /**
tmp[tmp.length - 1].num++; * Create script from the sequencer's current state.
continue; * Previous comment: ehhhh
} * TODO: Make this smarter.
tmp.push({ cmd : cmd, num : 1 }); **/
} fromSequence() {
tmp = tmp.map(line => { let str;
return `${line.cmd} ${line.num}` let tmp = [];
}) let cont;
//console.dir(tmp) let cmd;
if (seq.gridLoops > 1) { //str = seq.grid.map(step => { return step.cmd }).join('\n'); //quick hack
tmp.map(line => { //console.dir(seq.grid);
return ` ${line}`; for (let step of seq.grid) {
}) if (!step || !step.cmd) {
tmp.reverse(); continue;
tmp.push(`LOOP ${seq.gridLoops}`); }
tmp.reverse(); cmd = step.cmd;
tmp.push('END'); if (tmp.length > 0 && tmp[tmp.length - 1].cmd === cmd) {
} tmp[tmp.length - 1].num++;
continue;
str = tmp.join('\n'); }
tmp.push({ cmd, num: 1 });
nav.change('script'); }
cont = confirm(`Are you sure you want to over-write the current sequence?`); tmp = tmp.map(line => {
if (cont) { return `${line.cmd} ${line.num}`;
mse.mscript.editor.getDoc().setValue(str); });
} if (seq.gridLoops > 1) {
}; tmp.map(line => {
mse.mscript.toGUI = function () { return ` ${line}`;
'use strict'; });
let c; tmp.reverse();
let step; tmp.push(`LOOP ${seq.gridLoops}`);
for (let x = 0; x < mse.mscript.data.arr.length; x++) { tmp.reverse();
c = mse.mscript.data.arr[x]; tmp.push('END');
seq.set(x, c); }
if (c === 'CF' || c === 'CB') { str = tmp.join('\n');
if (typeof mse.mscript.data.meta[x] !== 'undefined' && mse.mscript.data.meta[x] !== '') { nav.change('script');
seq.setLight(x, mse.mscript.data.meta[x]); cont = confirm(`Are you sure you want to over-write the current sequence?`);
} else { if (cont) {
seq.setLight(x, light.color); this.editor.getDoc().setValue(str);
} }
} else { }
//unset light? /**
} * Take current compiled mscript state and send it to the sequencer
grid.state(x); * GUI. TODO: Add confirm step if sequence is longer than X steps.
} * TODO: Make this smarter (detect outer non-fade loop and assign to loop counter)
}; **/
mse.mscript.toSequence = function () { toGUI() {
'use strict'; let c;
const data = mse.mscript.editor.getValue(); let step;
let cont; for (let x = 0; x < this.data.arr.length; x++) {
if (data !== mse.mscript.raw) { c = this.data.arr[x];
cont = confirm(`Current script has not been compiled. Compile first?`); seq.set(x, c);
if (cont) { if (c === 'CF' || c === 'CB') {
mse.mscript.compile() if (typeof this.data.meta[x] !== 'undefined' && this.data.meta[x] !== '') {
} seq.setLight(x, this.data.meta[x]);
} }
mse.console.print(`Sending compiled script to GUI sequencer...`); else {
seq.clear(); seq.setLight(x, light.color);
mse.mscript.toGUI(); }
grid.refresh(); }
seq.stats(); else {
return nav.change('sequencer'); //unset light?
}
grid.state(x);
}
}
/**
* Handles compilation of mscript and switches to sequencer
* GUI after confirmation questions.
**/
toSequence() {
const data = this.editor.getValue();
let cont = false;
if (data !== this.raw) {
cont = confirm(`Current script has not been compiled. Compile first?`);
if (cont) {
this.compile();
}
}
mse.console.print(`Sending compiled script to GUI sequencer...`);
seq.clear();
this.toGUI();
grid.refresh();
seq.stats();
return nav.change('sequencer');
}
/**
* Compiles text in editor using the Mscript library.
*
**/
compile() {
const data = this.editor.getValue();
const mscript = new mscript_1.Mscript();
const output = mscript.interpret(data);
const len = output.arr.length;
const cam2 = typeof output.cam2 !== 'undefined' ? `, CAM2 : ${output.cam2}` : '';
const proj2 = typeof output.proj2 !== 'undefined' ? `, PROJ2 : ${output.proj2}` : '';
const report = `Sequence contains ${len} step${(len === 1 ? '' : 's')}, CAM: ${output.cam}, PROJ: ${output.proj}${cam2}${proj2}`;
this.raw = data;
this.data = output;
//mse.console.print(JSON.stringify(output, null, '\t') + '\n')
mse.console.print(report);
}
/**
* This function re-writes the optional "meta" attribute
* of an mcopy command object to "light". TODO: change this.
* Do not re-write this object and improve the consumers
* of the compiled data.
**/
prepare() {
const arr = [];
let obj;
for (let i = 0; i < this.data.arr.length; i++) {
obj = {
cmd: this.data.arr[i]
};
if (typeof this.data.meta[i] !== 'undefined' && this.data.meta[i] !== '') {
obj.light = this.data.meta[i];
}
else {
obj.light = light.color.join(',');
}
arr.push(obj);
}
return arr;
}
/**
* Method which compiles script if needs and then runs as a sequence.
**/
run() {
const data = this.editor.getValue();
let arr;
let cont = false;
if (data !== this.raw) {
cont = confirm(`Current script has not been compiled. Compile first?`);
if (cont) {
this.compile();
}
}
arr = this.prepare();
mse.console.print(`Started running compiled sequence...`);
gui.overlay(true);
gui.spinner(true, `Running mscript sequence...`, true, true);
return seq.exec(arr, 1);
}
} }
mse.mscript.compile = function () {
'use strict';
const data = mse.mscript.editor.getValue();
const mscript = new Mscript();
let output = mscript.interpret(data);
let len = output.arr.length;
const cam2 = typeof output.cam2 !== 'undefined' ? `, CAM2 : ${output.cam2}` : '';
const proj2 = typeof output.proj2 !== 'undefined' ? `, PROJ2 : ${output.proj2}` : '';
mse.mscript.raw = data;
mse.mscript.data = output;
//mse.console.print(JSON.stringify(output, null, '\t') + '\n')
mse.console.print(`Sequence contains ${len} step${(len === 1 ? '' : 's')}, CAM: ${output.cam}, PROJ: ${output.proj}${cam2}${proj2}`);
};
mse.mscript.prepare = function () {
'use strict';
const arr = [];
let obj;
for (let i = 0; i < mse.mscript.data.arr.length; i++) {
obj = {
cmd : mse.mscript.data.arr[i]
};
if (typeof mse.mscript.data.meta[i] !== 'undefined' && mse.mscript.data.meta[i] !== '') {
obj.light = mse.mscript.data.meta[i];
} else {
obj.light = light.color.join(',');
}
arr.push(obj);
}
return arr;
};
mse.mscript.run = function () {
'use strict';
const data = mse.mscript.editor.getValue();
let arr;
let cont;
if (data !== mse.mscript.raw) {
cont = confirm(`Current script has not been compiled. Compile first?`);
if (cont) {
mse.mscript.compile();
}
}
arr = mse.mscript.prepare();
mse.console.print(`Started running compiled sequence...`);
gui.overlay(true);
gui.spinner(true, `Running mscript sequence...`, true, true);
return seq.exec(arr, 1);
};
/******* /*******
* gui console * Mscript GUI Console
*******/ *******/
mse.console = {}; class MscriptConsole {
mse.console.elem = {}; /**
mse.console.init = function () { * Initializes the console by creating the element
'use script'; * containing the output text and binding to
mse.console.elem = $('#console textarea'); * keyup event.
mse.console.elem.on('keyup', function (e) { **/
var code = e.keyCode || e.which; init() {
if (code === 13) { this.elem = $('#console textarea');
mse.console.exec(); this.elem.on('keyup', function (e) {
e.preventDefault(); var code = e.keyCode || e.which;
return false; if (code === 13) {
} this.exec();
}); e.preventDefault();
return false;
}
}.bind(this));
}
/**
* Parse the current state of the console and get the last
* line to add to the current state array.
**/
parse() {
const lines = (this.elem.val() + '').split('\n');
const line = lines[lines.length - 2].replace('>', '').trim();
this.lines.push(line);
}
/**
* Executes the command in the last line of the console.
* TODO: implement the remaining commands. Currently only camera
* forward and backward will be executed.
**/
exec() {
let command;
this.parse();
command = this.lines[this.lines.length - 1].replace('>', '').trim();
log.info(command);
this.newLine();
if (mscript.cmd.indexOf(command) !== -1) {
if (command === 'CF') {
cmd.camera_forward(light.color);
}
else if (cmd === 'CB') {
cmd.camera_backward(light.color);
}
}
if (command === 'compile') {
mse.mscript.compile();
}
else if (command === 'run') {
mse.mscript.run();
}
}
/**
* Adds a new line to the console after an event
* and re-establishes the height of the array. Animates
* the console to scroll down to last line.
**/
newLine() {
let current = (this.elem.val() + '');
let height;
current += '> ';
this.elem.val(current);
height = this.elem[0].scrollHeight;
this.elem.animate({
scrollTop: height
}, 'normal');
}
/**
* Print string to the console and add new line
**/
print(str) {
let current = (this.elem.val() + '');
let height;
current += str;
mse.console.elem.val(current);
mse.console.elem.focus();
this.newLine();
}
}
const mse = {
mscript: new MscriptGUI(),
console: new MscriptConsole()
}; };
mse.console.lines = []; module.exports = mse;
mse.console.parse = function () { //# sourceMappingURL=mscript.js.map
'use strict';
const lines = mse.console.elem.val().split('\n');
const line = lines[lines.length - 2].replace('>', '').trim();
mse.console.lines.push(line);
};
mse.console.exec = function () {
'use strict';
let command;
mse.console.parse();
command = mse.console.lines[mse.console.lines.length - 1].replace('>', '').trim();
log.info(command);
mse.console.newLine();
if (mscript.cmd.indexOf(command) !== -1) {
if (command === 'CF') {
cmd.camera_forward(light.color);
} else if (cmd === 'CB') {
cmd.camera_backward(light.color);
}
}
if (command === 'compile') {
mse.mscript.compile();
} else if (command === 'run') {
mse.mscript.run();
}
};
mse.console.newLine = function () {
'use strict';
let current = mse.console.elem.val();
let height;
current += '> ';
mse.console.elem.val(current);
height = mse.console.elem[0].scrollHeight;
mse.console.elem.animate({
scrollTop : height
},'normal');
};
mse.console.print = function (str) {
'use strict'
let current = mse.console.elem.val();
let height;
current += str;
current += '\n> ';
mse.console.elem.val(current);
mse.console.elem.focus();
height = mse.console.elem[0].scrollHeight;
mse.console.elem.animate({
scrollTop : height
},'normal');
};
module.exports = mse;

File diff suppressed because one or more lines are too long

49
app/package-lock.json generated
View File

@ -51,6 +51,7 @@
"winston": "^3.7.2" "winston": "^3.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/codemirror": "^5.60.5",
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"chai": "^4.3.6", "chai": "^4.3.6",
"electron": "^19.0.1", "electron": "^19.0.1",
@ -1045,12 +1046,27 @@
"@types/responselike": "*" "@types/responselike": "*"
} }
}, },
"node_modules/@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"dev": true,
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/color-name": { "node_modules/@types/color-name": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"devOptional": true "devOptional": true
}, },
"node_modules/@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"node_modules/@types/fs-extra": { "node_modules/@types/fs-extra": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz",
@ -1120,6 +1136,15 @@
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
"dev": true "dev": true
}, },
"node_modules/@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"dev": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@types/yauzl": { "node_modules/@types/yauzl": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
@ -12125,12 +12150,27 @@
"@types/responselike": "*" "@types/responselike": "*"
} }
}, },
"@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"dev": true,
"requires": {
"@types/tern": "*"
}
},
"@types/color-name": { "@types/color-name": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"devOptional": true "devOptional": true
}, },
"@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"@types/fs-extra": { "@types/fs-extra": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz",
@ -12200,6 +12240,15 @@
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
"dev": true "dev": true
}, },
"@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"dev": true,
"requires": {
"@types/estree": "*"
}
},
"@types/yauzl": { "@types/yauzl": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",

View File

@ -35,6 +35,7 @@
}, },
"homepage": "https://github.com/sixteenmillimeter/mcopy#readme", "homepage": "https://github.com/sixteenmillimeter/mcopy#readme",
"devDependencies": { "devDependencies": {
"@types/codemirror": "^5.60.5",
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"chai": "^4.3.6", "chai": "^4.3.6",
"electron": "^19.0.1", "electron": "^19.0.1",

320
app/src/lib/ui/mscript.ts Normal file
View File

@ -0,0 +1,320 @@
'use strict';
/// <reference path ="jquery.d.ts"/>
import { Mscript } from 'mscript';
declare var nav : any;
declare var gui : any;
declare var CodeMirror : any;
declare var mscript : any;
declare var cmd : any;
interface MSE {
mscript : MscriptGUI,
console : MscriptConsole
}
/******
Mscript GUI
*******/
class MscriptGUI {
public editor : any = {};
public data : any = {};
public raw : string = '';
constructor () {
}
/**
* Initializes the mscript GUI. Sets up CodeMirror instance,
* binds events and sets height of editor.
**/
public init () {
const startingScript : string = `CF 1
PF 1`;
const editorHeight : number = $(window).height() - $('footer').eq(0).height() - 30;
const editorElem : HTMLTextAreaElement = document.getElementById('editor') as HTMLTextAreaElement;
const editorConfig : any = {
lineNumbers: true,
mode: 'python',
matchBrackets: true,
theme: 'monokai'
};
$('#editor').val(startingScript);
this.editor = CodeMirror.fromTextArea(editorElem, editorConfig);
this.editor.setSize(null, editorHeight);
this.editor.on('change', (e : Event) => { });
$(document).on('resize', function (e : Event) {
this.editor.setSize(null, editorHeight);
}.bind(this));
}
/**
* Callback for when open event occurs.
**/
public open () {
//recalcuate in case resize has occurred needed
const editorHeight : number = $(window).height() - $('footer').eq(0).height() - 30;
this.editor.setSize(null, editorHeight);
this.editor.refresh();
}
/**
* Create script from the sequencer's current state.
* Previous comment: ehhhh
* TODO: Make this smarter.
**/
fromSequence () {
let str : string;
let tmp : any[] = [];
let cont : boolean;
let cmd : string;
//str = seq.grid.map(step => { return step.cmd }).join('\n'); //quick hack
//console.dir(seq.grid);
for (let step of seq.grid) {
if (!step || !step.cmd) {
continue;
}
cmd = step.cmd;
if (tmp.length > 0 && tmp[tmp.length - 1].cmd === cmd) {
tmp[tmp.length - 1].num++;
continue;
}
tmp.push({ cmd, num : 1 });
}
tmp = tmp.map(line => {
return `${line.cmd} ${line.num}`
})
if (seq.gridLoops > 1) {
tmp.map(line => {
return ` ${line}`;
})
tmp.reverse();
tmp.push(`LOOP ${seq.gridLoops}`);
tmp.reverse();
tmp.push('END');
}
str = tmp.join('\n');
nav.change('script');
cont = confirm(`Are you sure you want to over-write the current sequence?`);
if (cont) {
this.editor.getDoc().setValue(str);
}
}
/**
* Take current compiled mscript state and send it to the sequencer
* GUI. TODO: Add confirm step if sequence is longer than X steps.
* TODO: Make this smarter (detect outer non-fade loop and assign to loop counter)
**/
toGUI () {
let c : string;
let step : string;
for (let x : number = 0; x < this.data.arr.length; x++) {
c = this.data.arr[x];
seq.set(x, c);
if (c === 'CF' || c === 'CB') {
if (typeof this.data.meta[x] !== 'undefined' && this.data.meta[x] !== '') {
seq.setLight(x, this.data.meta[x]);
} else {
seq.setLight(x, light.color);
}
} else {
//unset light?
}
grid.state(x);
}
}
/**
* Handles compilation of mscript and switches to sequencer
* GUI after confirmation questions.
**/
toSequence () {
const data : string = this.editor.getValue();
let cont : boolean = false;
if (data !== this.raw) {
cont = confirm(`Current script has not been compiled. Compile first?`);
if (cont) {
this.compile()
}
}
mse.console.print(`Sending compiled script to GUI sequencer...`);
seq.clear();
this.toGUI();
grid.refresh();
seq.stats();
return nav.change('sequencer');
}
/**
* Compiles text in editor using the Mscript library.
*
**/
compile () {
const data : string = this.editor.getValue();
const mscript : Mscript = new Mscript();
const output : any = mscript.interpret(data);
const len : number = output.arr.length;
const cam2 : string = typeof output.cam2 !== 'undefined' ? `, CAM2 : ${output.cam2}` : '';
const proj2 : string = typeof output.proj2 !== 'undefined' ? `, PROJ2 : ${output.proj2}` : '';
const report : string = `Sequence contains ${len} step${(len === 1 ? '' : 's')}, CAM: ${output.cam}, PROJ: ${output.proj}${cam2}${proj2}`;
this.raw = data;
this.data = output;
//mse.console.print(JSON.stringify(output, null, '\t') + '\n')
mse.console.print(report);
}
/**
* This function re-writes the optional "meta" attribute
* of an mcopy command object to "light". TODO: change this.
* Do not re-write this object and improve the consumers
* of the compiled data.
**/
prepare () {
const arr : any[] = [];
let obj : any;
for (let i : number = 0; i < this.data.arr.length; i++) {
obj = {
cmd : this.data.arr[i]
};
if (typeof this.data.meta[i] !== 'undefined' && this.data.meta[i] !== '') {
obj.light = this.data.meta[i];
} else {
obj.light = light.color.join(',');
}
arr.push(obj);
}
return arr;
}
/**
* Method which compiles script if needs and then runs as a sequence.
**/
run () {
const data : string = this.editor.getValue();
let arr : any[];
let cont : boolean = false;
if (data !== this.raw) {
cont = confirm(`Current script has not been compiled. Compile first?`);
if (cont) {
this.compile();
}
}
arr = this.prepare();
mse.console.print(`Started running compiled sequence...`);
gui.overlay(true);
gui.spinner(true, `Running mscript sequence...`, true, true);
return seq.exec(arr, 1);
}
}
/*******
* Mscript GUI Console
*******/
class MscriptConsole {
public elem : JQuery;
private lines : string[];
/**
* Initializes the console by creating the element
* containing the output text and binding to
* keyup event.
**/
public init () {
this.elem = $('#console textarea');
this.elem.on('keyup', function (e : KeyboardEvent) {
var code : number = e.keyCode || e.which;
if (code === 13) {
this.exec();
e.preventDefault();
return false;
}
}.bind(this));
}
/**
* Parse the current state of the console and get the last
* line to add to the current state array.
**/
parse () {
const lines : string[] = (this.elem.val() + '').split('\n');
const line : string = lines[lines.length - 2].replace('>', '').trim();
this.lines.push(line);
}
/**
* Executes the command in the last line of the console.
* TODO: implement the remaining commands. Currently only camera
* forward and backward will be executed.
**/
exec () {
let command : string;
this.parse();
command = this.lines[this.lines.length - 1].replace('>', '').trim();
log.info(command);
this.newLine();
if (mscript.cmd.indexOf(command) !== -1) {
if (command === 'CF') {
cmd.camera_forward(light.color);
} else if (cmd === 'CB') {
cmd.camera_backward(light.color);
}
}
if (command === 'compile') {
mse.mscript.compile();
} else if (command === 'run') {
mse.mscript.run();
}
}
/**
* Adds a new line to the console after an event
* and re-establishes the height of the array. Animates
* the console to scroll down to last line.
**/
newLine () {
let current : string = (this.elem.val() + '');
let height : number;
current += '> ';
this.elem.val(current);
height = this.elem[0].scrollHeight;
this.elem.animate({
scrollTop : height
}, 'normal');
}
/**
* Print string to the console and add new line
**/
print (str : string) {
let current : string = (this.elem.val() + '');
let height : number;
current += str;
mse.console.elem.val(current);
mse.console.elem.focus();
this.newLine();
}
}
const mse : MSE = {
mscript : new MscriptGUI(),
console : new MscriptConsole()
};
module.exports = mse;

View File

@ -11,7 +11,8 @@
"outDir": "./", "outDir": "./",
"rootDir" : "./src/", "rootDir" : "./src/",
"paths" : { "paths" : {
"log" : ["./lib/log"] "log" : ["./lib/log"],
"mscript" : ["./lib/mscript"]
} }
}, },
"exclude" : [ "exclude" : [

View File

@ -72,8 +72,8 @@ const ALTS : any = {
'PBPF' : [ ] 'PBPF' : [ ]
}; };
const PAUSE = 'PAUSE'; const PAUSE : string = 'PAUSE';
const ALERT = 'ALERT'; const ALERT : string = 'ALERT';
/** helper functions */ /** helper functions */
@ -86,20 +86,24 @@ const ALERT = 'ALERT';
**/ **/
function startsWith (str : string, target : string, position? : number) : boolean { function startsWith (str : string, target : string, position? : number) : boolean {
const { length } = str; const { length } = str;
position = position == null ? 0 : position; position = position == null ? 0 : position;
if (position < 0) { if (position < 0) {
position = 0; position = 0;
} else if (position > length) { } else if (position > length) {
position = length; position = length;
} }
target = `${target}`; target = `${target}`;
return str.slice(position, position + target.length) == target; return str.slice(position, position + target.length) == target;
} }
/** class Mscript */ /** class Mscript */
class Mscript { export class Mscript {
output : any; output : any;
lines : any[]; lines : string[];
cam : number; cam : number;
cam2 : number; cam2 : number;
proj : number; proj : number;
@ -159,7 +163,7 @@ class Mscript {
* *
* @returns {object} if callback is not provided * @returns {object} if callback is not provided
*/ */
interpret (text : string, callback : Function) { interpret (text : string, callback : Function = null) {
this.clear() this.clear()
if (typeof text === 'undefined') { if (typeof text === 'undefined') {
@ -228,7 +232,7 @@ class Mscript {
this.output.proj2 = this.proj2; this.output.proj2 = this.proj2;
} }
if (typeof callback !== 'undefined') { if (typeof callback !== 'undefined' && callback != null) {
//should only be invoked by running mscript.tests() //should only be invoked by running mscript.tests()
callback(this.output); callback(this.output);
} else { } else {

View File

@ -7,6 +7,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,
"removeComments" : false, "removeComments" : false,
"declaration" : true,
"baseUrl" : "lib", "baseUrl" : "lib",
"outDir": "./lib/", "outDir": "./lib/",
"rootDir" : "./src/", "rootDir" : "./src/",