Skip to content

Instantly share code, notes, and snippets.

@bitnetwork
Forked from anonymous/index.js
Created July 29, 2017 18:52
Show Gist options
  • Save bitnetwork/74d49edc9f0927a71fb0c852f838b498 to your computer and use it in GitHub Desktop.
Save bitnetwork/74d49edc9f0927a71fb0c852f838b498 to your computer and use it in GitHub Desktop.
class Pixel {
constructor(options = {}) {
this.character = options.character || null;
this.color = options.color || null;
this.background = options.background || null;
}
merge(pixel) {
if (pixel.character !== null) {
this.character = pixel.character;
}
if (pixel.color !== null) {
this.color = pixel.color;
}
if (pixel.background !== null) {
this.background = pixel.background;
}
return this;
}
}
class Canvas {
constructor(options = {}) {
this.data = [ [ new Pixel() ] ];
this.height = options.height || 20;
this.width = options.width || 80;
}
get height() {
return this.data.length;
}
set height(value) {
let oldValue = this.height;
if (value > oldValue) {
for (let i = oldValue; i < value; i++) {
this.data[i] = [];
for (let j = 0; j < this.width; j++) {
this.data[i][j] = new Pixel;
}
}
} else if (value < oldValue) {
this.data.splice(value);
}
}
get width() {
return this.data[0].length;
}
set width(value) {
let oldValue = this.data[0].length;
if (value > oldValue) {
for (let i = 0; i < this.height; i++) {
for (let j = oldValue; j < value; j++) {
this.data[i][j] = new Pixel();
}
}
} else if (value < oldValue) {
for (let i = 0; i < this.height; i++) {
this.data[i].splice(value);
}
}
}
parseColor(color, background = false) {
switch(color) {
case null:
return "\x1b[0m";
case "red":
return `\x1b[${background ? 4 : 3}1m`;
default:
return "";
}
}
merge(canvas, yOffset = 0, xOffset = 0) {
for (let i = 0; i < (this.height < canvas.height ? this.height : canvas.height); i++) {
for (let j = 0; j < (this.width < canvas.width ? this.width : canvas.width); j++) {
let pixel = canvas.data[i][j];
let thisPixel = this.data[i + yOffset][j + xOffset];
thisPixel.merge(pixel);
}
}
return this;
}
fill(character, fillAll = false) {
for (let i = 0; i < this.height; i++) {
for (let j = 0; j < this.width; j++) {
let pixel = this.data[i][j];
if (pixel.character === null || fillAll) {
pixel.character = character;
}
}
}
return this;
}
toString() {
let text = `\x1b[${this.height.toString(10) - 1}A\x1b[${this.width.toString(10 - 1)}D`;
for (let i = 0; i < this.height; i++) {
for (let j = 0; j < this.width; j++) {
let pixel = this.data[i][j];
text += this.parseColor(pixel.color, false) + this.parseColor(pixel.background, true); // make this optimized
if (pixel.character === null) {
text += " "; // This could also be the right arrow asni key or enter if there's no more characters on the line
} else {
text += pixel.character;
}
}
text += "\n";
}
text = text.substring(0, text.length - 1);
return text;
}
}
class Component {
constructor(options = {}) {
this.parent = null;
this.children = [];
this.root = false;
this.x = options.x || 0;
this.y = options.y || 0;
this.height = options.height || null; // null: takes all space, -x: takes all space minus x
this.width = options.width || null;
}
render(canvas) {
return canvas;
}
renderChildren(canvas) {
for (let i = 0; i < this.children.length; i++) {
let child = this.children[i];
canvas.merge(child.render(new Canvas({
height: child.absoluteHeight,
width: child.absoluteWidth
})), child.absoluteY, child.absoluteX);
}
return canvas;
}
getAbsoluteValueNull(value, fullValue) {
if (value === null) {
return fullValue;
} else if (value < 0) {
return fullValue + value;
} else {
return value;
}
}
appendChild(child) {
if (child.root === true) {
throw new Error("Cannot append a root element as a child");
}
this.children.push(child);
child.parent = this;
return this;
}
appendParent(parent) { // TO-DO: Change this name to something else
if (this.root === true) {
throw new Error("Cannot append a parent of a root element");
}
parent.children.push(this);
this.parent = parent;
return this;
}
removeChild(child) {
for (let i = 0; i < this.children.length; i++) {
if (this.children[i] === child) {
this.children.splice(i, 1);
child.parent = null;
return true;
}
}
return false;
}
get absoluteHeight() {
if (this.height >= 0 && this.height !== null) {
return this.height;
} else if (this.parent === null) {
throw new Error("Trying to use a full height negation value on a orphan");
}
let parentHeight = this.parent.absoluteHeight;
if (this.height === null) {
return parentHeight;
} else if (this.height < 0) {
return parentHeight + this.height;
}
}
get absoluteWidth() {
if (this.width >= 0 && this.width !== null) {
return this.width;
} else if (this.parent === null) {
throw new Error("Trying to use a full width negation value on a orphan");
}
let parentWidth = this.parent.absoluteWidth;
if (this.width === null) {
return parentWidth;
} else if (this.width < 0) {
return parentWidth + this.width;
}
}
get absoluteY() {
if (this.y >= 0 && this.y !== null) {
return this.y;
} else if (this.parent === null) {
throw new Error("Trying to use a full height negation value on a orphan");
}
let parentHeight = this.parent.absoluteHeight;
if (this.y === null) {
return parentHeight;
} else if (this.y < 0) {
return parentHeight + this.y;
}
}
get absoluteX() {
if (this.x >= 0 && this.x !== null) {
return this.x;
} else if (this.parent === null) {
throw new Error("Trying to use a full width negation value on a orphan");
}
let parentWidth = this.parent.absoluteWidth;
if (this.x === null) {
return parentWidth;
} else if (this.x < 0) {
return parentWidth + this.x;
}
}
trace() {
let trace = [this];
let parent = this;
do {
trace.push(parent.parent);
parent = parent.parent;
} while (!parent.root && parent !== this)
return trace;
}
traceChild(child) {
let trace = child.trace();
for (let i = 0; i < trace.length; i++) {
if (trace[i] === this) {
return trace.slice(0, i + 1).reverse();
}
}
return null;
}
traceParent(parent) {
let trace = this.trace();
for (let i = 0; i < trace.length; i++) {
if (trace[i] === parent) {
return trace.slice(0, i + 1);
}
}
return null;
}
getPositionChild(child, y = 0, x = 0) {
}
getPositionParent(parent, y = 0, x = 0) {
let trace = this.traceParent(parent);
if (trace === null) {
return null;
}
y = this.getAbsolutePosition
for (let i = 0; i < trace.length; i++) {
let currentParent = trace[i];
y += currentParent.y;
}
}
}
class Root extends Component {
constructor(options = {}) {
super(options);
this.root = true;
}
getAbsolutePosition(position, child) {
let trace = child.trace();
if (trace[trace.length] !== this) {
return false;
}
}
}
class Window extends Root {
constructor(options = {}) {
super(options);
this.stdin = options.stdin || process.stdin;
this.stdout = options.stdout || process.stdout;
if (this.stdin.isTTY !== true || this.stdout.isTTY !== true) {
throw new Error("Stream is not a TTY terminal");
}
if (this.stdin.isRaw === false) {
this.stdin.setRawMode(true);
}
this.height = this.stdout.rows; // TO-DO: update this when changed, there's prob an event that fires or just use a getter
this.width = this.stdout.columns;
let that = this;
this.stdout.on("resize", function() {
that.height = that.stdout.rows;
that.width = that.stdout.columns;
});
this.stdin.on("data", function(data) {
console.log(data);
});
}
redraw() {
let canvas = new Canvas({height: this.height, width: this.width});
canvas.merge(this.renderChildren(canvas));
this.stdout.write(canvas.toString());
}
}
class Text extends Component {
constructor(options = {}) {
super(options);
this.text = options.text || "";
this.align = options.align || "left";
this.color = options.color || null;
}
render(canvas) {
canvas.merge(this.renderChildren(canvas));
let width = this.absoluteWidth;
let start = 0;
if (this.align === "right") {
start = width - this.text.length;
} else if (this.align.startsWith("center")) {
start = Math[this.align === "center-right" ? "ceil" : "floor"](width / 2 - this.text.length / 2);
}
for (let i = 0; i < this.text.length; i++) {
let pixel = canvas.data[0][i + start];
pixel.character = this.text[i];
pixel.color = this.color;
}
return canvas;
}
}
class Rectangle extends Component {
constructor(options = {}) {
super(options);
this.color = options.color || "white";
}
render(canvas) {
canvas.merge(this.renderChildren(canvas));
for (let i = 0; i < canvas.height; i++) {
for (let j = 0; j < canvas.width; j++) {
canvas.data[i][j].background = this.color;
}
}
return canvas;
}
}
class Input extends Component {
constructor(options = {}) {
super(options);
this.filler = options.filler || "_";
this.value = options.value || "";
this.position = options.position || 0;
}
render(canvas) {
}
}
module.exports = {
Pixel: Pixel,
Canvas: Canvas,
Component: Component,
Window: Window,
Text: Text,
Rectangle: Rectangle,
Input: Input
};
{
"name": "termapp",
"version": "0.1.0",
"description": "A package library backend for developing terminal apps.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"terminal",
"library"
],
"author": "Bit",
"license": "ISC"
}
const termapp = require("./");
let window = new termapp.Window();
window.redraw();
let title = new termapp.Rectangle({
color: "red",
width: null,
height: 1
}).appendParent(window);
let titleText = new termapp.Text({
text: "Test app",
color: null,
align: "center-right"
}).appendParent(title);
window.redraw();
let t = window.traceChild(titleText);
for (let i = 0; i < t.length; i++) {
let ct = t[i];
console.log(`h:${ct.height} ah:${ct.absoluteHeight} w:${ct.width} aw:${ct.absoluteWidth} x:${ct.x} ax:${ct.absoluteX} y:${ct.y} ay:${ct.absoluteY}`);
}
setInterval(function() {process.exit()}, 20000)
/*
console.log(`window: h${window.height} ah${window.absoluteHeight}\ntitle: h${title.height} ah${title.absoluteHeight}\ntitleText: h${titleText.height} ah${titleText.absoluteHeight}\n`);
console.log(`window: w${window.width} aw${window.absoluteWidth}\ntitle: w${title.width} aw${title.absoluteWidth}\ntitleText: w${titleText.width} aw${titleText.absoluteWidth}\n`);
console.log(`window: x${window.x} ax${window.absoluteX}\ntitle: x${title.x} ax${title.absoluteX}\ntitleText: x${titleText.x} ax${titleText.absoluteX}\n`);
*/
/*const readline = require("readline").createInterface({
input: process.stdin,
output: process.stdout
});
readline.on("line", function(line) {
try {
console.log(eval(line));
} catch (error) {
console.log(error);
}
});
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment