Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: replaced wopr with blessed stream implementation
  • Loading branch information
jkga committed Apr 11, 2020
commit 7e264034b8b9649469d7ef5c674350db9643eab8
5 changes: 2 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const express = require('express'),
util = require('./bin/util'),
axios = require('axios'),
covid19 = require('./lib/cli'),
covid19GFX = require ('./lib/cli/gfx'),
covid19GFX = require('./lib/cli/gfx'),
pkg = require('./package.json'), // package.json info
apiBaseURL = "https://corona.lmao.ninja", // NovelCOVID API
port = process.env.port || 7070; // set port
Expand Down Expand Up @@ -132,7 +132,7 @@ app.get('/history/:country/:chartType(cases|deaths)?', async (req, res, next) =>

// historical chart by country
app.get('/history/charts/:country', async (req, res, next) => {
const userAgent = req.headers['user-agent'],
const userAgent = req.headers['user-agent'],
countryData = req.params.country,
chartType = req.params.chartType || 'cases',
summary = await axios.get(`${apiBaseURL}/countries/${countryData}`),
Expand All @@ -148,7 +148,6 @@ app.get('/history/charts/:country', async (req, res, next) => {
s.active, s.critical, s.casesPerOneMillion,
s.updated, h, chartType, s.countryInfo
)

return null;
}
return next();
Expand Down
135 changes: 33 additions & 102 deletions lib/cli/gfx/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
const
blessed = require('blessed'),
fs = require('fs'),
contrib = require('blessed-contrib'),
present = require('./presenter'),
pkg = require('../../../package.json'),
xml2js = require('xml2js'),
say = require('../../sayings/threads.json'),
serverUtil = require('./server-util')
template = require ('./template.js'),
ts = Date.now(),
date_ob = new Date(ts),
date = date_ob.getDate(),
Expand All @@ -24,10 +22,8 @@ const
gcashNum = '+639176462753',
ansiBMC = '`(Buy Me A Coffee)` '+bmcurl,
ansiTwitter = twitterhandle+space+twitterhashtag,
ansiGCash = '(GCash) '+gcashNum,
XMLTemplate = '/template.xml';
ansiGCash = '(GCash) '+gcashNum;

let cachedTemplate = null

// random sayings
const randomSay = () => {
Expand All @@ -42,7 +38,6 @@ const patchBlessed = () => {

patchBlessed();


exports.historyCountryTracker = (req, res, n, c, tC, d, tD, r, a, cl, cPOM, u, h, chartType, countryInfo) => {
const name = n, cases = c, todayCases = tC,
deaths = d, todayDeaths = tD, recovered = r,
Expand All @@ -52,101 +47,37 @@ exports.historyCountryTracker = (req, res, n, c, tC, d, tD, r, a, cl, cPOM, u, h
dates = Object.keys(h.timeline[chartType]),
from = dates[0],
to = dates[dates.length - 1],
parser = new xml2js.Parser(),
builder = new xml2js.Builder(),
tableFooter = randomSay(),
defaultfooter = ansiBMC+ansiTwitter+br+br,
specialfooter = ansiGCash+br+ansiBMC+ansiTwitter+br+br;

const parseXML = (data) => {
parser.parseString(data, function (err, result) {

const casesDataTable =
'\n' +
` ,${cases},${deaths},${recovered},${active},${casesPerOneMillion}\n` +
' ,Today Cases,Today Deaths,Critical,Mortality %,Recovery %\n' +
` ,${todayCases},${todayDeaths},${critical},${mortalityPercentage},${recoveredPercentage}\n`;

// header
result.document.page[0].item[0].markdown[0].markdown[0] = header+br+tagline

// map
result.document.page[0].item[1].map[0].markers[0].m[0].$ = {
lat: countryInfo.lat,
lon: countryInfo.long,
char: '\u24E7'+` ${name}`,
color: 'magenta',
};

// Doughnut/donut
result.document.page[0].item[4].donut[0].data[0].m[0].$={
color: 'red',
percent: parseFloat(mortalityPercentage).toFixed(2),
label: 'Mortality',
};

result.document.page[0].item[4].donut[0].data[0].m[1].$={
color: 'green',
percent: parseFloat(recoveredPercentage).toFixed(2),
label: 'Recovery',
};

// bar graph
result.document.page[0].item[3].bar[0].$['data-data'] = `${cases},${deaths},${recovered},${active}`;

// line graph
result.document.page[0].item[5].line[0].label = `Cases from ${from} to ${to}`
result.document.page[0].item[5].line[0].data[0].m[0].$ = {
title: 'Cases',
'style-line': 'blue',
x:Object.keys(h.timeline.cases).join(','),
y:Object.values(h.timeline.cases).join(','),
};

result.document.page[0].item[5].line[0].data[0].m[1].$ = {
title: 'Deaths',
'style-line': 'red',
x:Object.keys(h.timeline.deaths).join(','),
y:Object.values(h.timeline.deaths).join(','),
};

result.document.page[0].item[5].line[0].data[0].m[2].$ = {
title: 'Recovered',
'style-line': 'green',
x:Object.keys(h.timeline.recovered).join(','),
y:Object.values(h.timeline.recovered).join(','),
};

// Historical data table
result.document.page[0].item[2].table[0].label = 'Historical data as of '+asof.toLocaleString()+' [Date:'+currentdate+']'
result.document.page[0].item[2].table[0]['data-data'][0] = casesDataTable;

// footer
result.document.page[0].item[6].markdown[0].markdown[0] = (n.toLowerCase() == 'philippines') ? tableFooter+br+specialfooter+br+source+br+repo : tableFooter+br+defaultfooter+br+source+br+repo;

present(req, res, builder.buildObject(result), function(err) {
if (err) console.log(new Error().stack);
if (err) return contrib.serverError(req, res, err);
});

});
}

const readXML = () => {
fs.readFile(__dirname + XMLTemplate, (err, data) => {
cachedTemplate = data
parseXML(data)
});
}

// read XML template from file or cache
if(!cachedTemplate) return readXML ()

try {
return parseXML(new Buffer.from(cachedTemplate.toString()))
} catch (e) {
return readXML ()
}

specialfooter = ansiGCash+br+ansiBMC+ansiTwitter+br+br,
defaultHeader = header+br+tagline,
footer = (n.toLowerCase() == 'philippines') ? tableFooter+br+specialfooter+br+source+br+repo : tableFooter+br+defaultfooter+br+source+br+repo;

// load template with data
// serverUtil.loadTemplate(template, {jsonData}, callback)
serverUtil.loadTemplate(template, {
name,
cases,
deaths,
recovered,
active,
casesPerOneMillion,
todayCases,
todayDeaths,
critical,
mortalityPercentage,
recoveredPercentage,
countryInfo,
from,
to,
currentdate,
asof,
h,
defaultHeader,
footer,
}, (screen) => {
res.send(screen+'\r\n'+'\033[?25h')
})

};

57 changes: 0 additions & 57 deletions lib/cli/gfx/presenter.js

This file was deleted.

79 changes: 79 additions & 0 deletions lib/cli/gfx/server-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/** Modified script from blessed-contrib server-util */
const blessed = require('blessed'),
contrib = require('blessed-contrib'),
Stream = require('stream');

function OutputBuffer(options) {
this.isTTY = true;
this.columns = options.cols;
this.rows = options.rows;
this.write = function(s) {
s = s.replace('\x1b8', ''); //not clear from where in blessed this code comes from. It forces the terminal to clear and loose existing content.
options.stream.write(s)
};

this.on = function() {};
}

function InputBuffer() {
this.isTTY = true;
this.isRaw = true;
this.emit = function() {};
this.setRawMode = function() {};
this.resume = function() {};
this.pause = function() {};
this.on = function() {};
}


createScreen = (opt = {}) => {
const output = new OutputBuffer({stream: opt.stream, cols: 250, rows: 50});
const input = new InputBuffer(); //required to run under forever since it replaces stdin to non-tty
const program = blessed.program({output: output, input: input});

let screen = blessed.screen({program: program});
return screen
}


loadTemplate = (gridTemplateClass, json, callback) => {

blessed.Screen.global = null
blessed.Program.global = null

const
customStream = new Stream.Transform()
screen = createScreen({stream: customStream}),
grid = new contrib.grid({rows: 12, cols: 16, screen: screen});

let result = []

if(!gridTemplateClass) throw new Error('No template loaded')

// parse template
gridTemplateClass.load(grid, json)

customStream._transform = function (chunk,encoding,done) {
result.push(chunk.toString())
done()
}

customStream._final = () => {
callback(result.join(''))
}

customStream.on('error', (e) => {
// do nothing here
})

screen.render()

setTimeout(() => {
customStream.end()
}, 1000)
}

module.exports = {
createScreen,
loadTemplate,
}
Loading