Skip to content

Instantly share code, notes, and snippets.

@steveruizok
Last active November 17, 2020 14:01
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save steveruizok/7028cb4ef0e4113b81d046348a9a3a3f to your computer and use it in GitHub Desktop.
Save steveruizok/7028cb4ef0e4113b81d046348a9a3a3f to your computer and use it in GitHub Desktop.
Render a State Designer state in the terminal.
import log from "ololog"
class Grid {
rows = []
width = 0
height = 0
chars = {
active: ["┌", "─", "┒", "┃", "┛", "━", "┕", "│"],
inactive: ["┌", "─", "┐", "│", "┘", "─", "└", "│"],
root: ["┌", "╌", "┐", "╎", "┘", "╌", "└", "╎"],
}
setSize(width, height) {
this.rows = Array.from(Array(height)).map(() =>
Array.from(Array(width)).map(() => ({ char: " ", node: undefined }))
)
}
insert(char, col, row, node) {
if (this.rows[row] === undefined) this.rows[row] = []
this.rows[row][col] = { char, node }
}
drawRect(x, y, width, height, style, node) {
let i
const chars = this.chars[style]
this.insert(chars[0], x, y, node)
this.insert(chars[2], x + width, y, node)
this.insert(chars[4], x + width, y + height, node)
this.insert(chars[6], x, y + height, node)
for (i = 1; i < width; i++) {
this.insert(chars[1], x + i, y, node)
this.insert(chars[5], x + i, y + height, node)
}
for (i = 1; i < height; i++) {
this.insert(chars[7], x, y + i, node)
this.insert(chars[3], x + width, y + i, node)
}
}
drawText(text, x, y, node) {
for (let i = 0; i < text.length; i++) {
this.insert(text[i], x + i, y, node)
}
}
drawNode(node) {
const { x, y, width, height, type, state, name } = node
const style =
type === "root" ? "root" : state.active ? "active" : "inactive"
if (node.hasChildren) {
this.drawRect(x, y, width, height, style, node)
this.insert(" ", x + 1, y, node)
this.insert(" ", x + name.length + 2, y, node)
this.drawText(name, x + 2, y, node)
} else {
this.drawText(name, x, y, node)
}
for (let child of node.children) {
this.drawNode(child)
}
}
render() {
console.clear()
log(
this.rows
.map((row) =>
row
.map((cell) =>
cell
? cell.node?.state.active
? `\x1b[0;37m${cell.char}\x1b[0m`
: `\x1b[0;37;2m${cell.char}\x1b[0m`
: " "
)
.join("")
)
.join("\n")
)
}
init(node) {
node.moveTo(0, 0)
this.setSize(node.width, node.height)
this.drawNode(node)
this.render()
}
}
const grid = new Grid()
class TNode {
x = 0
y = 0
constructor(state, parent) {
this.state = state
this.name = state.name
this.parent = parent
this.children = Object.values(state.states).map((s) => new TNode(s, this))
}
get maxX() {
return this.x + this.width
}
get maxY() {
return this.y + this.height
}
get width() {
if (!this.hasChildren) {
return this.name.length
}
let cx = Math.max(
this.x + this.name.length + 5,
...this.children.map((c) => c.maxX)
)
if (this.children.find((c) => c.type === "branch")) cx++
cx++
return cx - this.x
}
get height() {
if (!this.hasChildren) {
return 1
}
let cy = Math.max(...this.children.map((c) => c.maxY))
if (cy > this.y + 2) cy++
return cy - this.y
}
get hasChildren() {
return this.children.length > 0
}
get type() {
if (!this.parent) return "root"
if (this.children.length === 0) return "leaf"
return "branch"
}
moveTo(x, y) {
this.x = x
this.y = y
let cw = 0
let ch = 0
let sy = 1
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i]
child.moveTo(x + 2 + cw, y + sy)
ch = Math.max(ch, child.height + (child.type === "leaf" ? 0 : 1))
if (cw < 32) {
cw += child.width + (child.type === "leaf" ? 1 : 2)
} else {
cw = 0
sy += ch
}
}
}
}
export default function renderState(state) {
const tree = new TNode(state.stateTree)
grid.init(tree)
grid.render()
return state.onUpdate(() => grid.render())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment