const rl = require('readline'); const list_mock = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50' ] let active = 0; scroll = 0; draw = { t: 'p', v: ['Loading Notes App...'] }, appTitle = '', menu = '' const pr = t => process.stdout.write(t) const cls = () => console.clear() const cursor = { hide: () => pr('\u001B[?25l'), show: () => pr('\u001B[?25h') } // =========== DRAW APP ============= const drawApp = () => { const lines = process.stdout.rows const columns = process.stdout.columns // process.stdout.write('\x1Bc') cls() for (var i = 0; i < lines - 1; i++) { const dist = t => [Math.floor((columns - t) / 2), Math.ceil((columns - t) / 2) - 2] switch(i){ case 0: pr('╔'+'═'.repeat(columns - 2)+'╗') break case 1: pr('║'+' '.repeat(dist(appTitle.length)[0])+appTitle+' '.repeat(dist(appTitle.length)[1])+'║') break case 2: pr('╠'+'═'.repeat(columns - 2) +'╣') break case lines - 3: pr('╟'+'─'.repeat(columns - 2) +'╢') break case lines - 2: pr('║'+' '+menu.substring(0,columns-4)+' '.repeat(columns-menu.length-3)+'║') break default: const max = Math.max(...(draw.v.map(el => el.length))) if (draw.t === 'p') { if (i === (Math.floor((lines - draw.v.length) / 2) - 2)){ pr('║'+' '.repeat(dist(max+4)[0])+'┌'+'─'.repeat(max+2)+'┐'+' '.repeat(dist(max+4)[1])+'║') } else if (i === (Math.floor((lines + draw.v.length) / 2) + 1)) { pr('║'+' '.repeat(dist(max+4)[0])+'└'+'─'.repeat(max+2)+'┘'+' '.repeat(dist(max+4)[1])+'║') } else if (i === (Math.floor((lines - draw.v.length) / 2) - 1) || i === (Math.floor((lines + draw.v.length) / 2))) { pr('║'+' '.repeat(dist(max+4)[0])+'│'+' '.repeat(max+2)+'│'+' '.repeat(dist(max+4)[1])+'║') } else if (i < (Math.floor((lines - draw.v.length) / 2) - 1) || (i > (Math.floor((lines + draw.v.length) / 2)))) { pr('║'+' '.repeat(columns - 2)+'║') } else { const n = i - (Math.floor((lines - draw.v.length) / 2)) const t = draw.v[n] const s = [Math.floor((max+2-t.length) / 2), Math.ceil((max+2-t.length) / 2)] pr('║'+' '.repeat(dist(max+4)[0])+'│'+' '.repeat(s[0])+t+' '.repeat(s[1])+'│'+' '.repeat(dist(max+4)[1])+'║') } } else if (draw.t === 'l') { const l = draw.v[i-3+scroll] && draw.v[i-3+scroll].substring(0,columns-4) pr(l ? ('║'+(active===i-3+scroll?'\x1b[7m ':' ')+l+' '.repeat(columns-l.length-3)+'\x1b[0m║') : ('║'+' '.repeat(columns - 2)+'║') ) } } } pr('╚'+'═'.repeat(columns - 2)+'╝') } // ============ GET KEY ================= const getKey = () => { rl.emitKeypressEvents(process.stdin); process.stdin.setRawMode(true); process.stdin.on('keypress', (_, key) => { const lines = process.stdout.rows switch(key.name) { case 'up': case 'k': active > 0 && active-- active < scroll && scroll-- drawApp() break case 'down': case 'j': if (draw.t === 'l') { active < draw.v.length - 1 && active++ active - scroll > lines - 7 && scroll++ drawApp() } break case 'return': case 'o': // draw = { t: 'p', v: ['popup test'] } // draw = { t: 'p', v: ['popup test', 'aaaaaaa'] } draw = { t: 'p', v: ['popup test', '', `This will display note nr ${active + 1}.`] } drawApp() // process.stdout.write(draw.v[active]); break case 'q': cursor.show() console.clear() pr('Bye!\n\n') process.exit() default: } }) } // ============ EXECUTION ================ cls() cursor.hide() process.stdout.on('resize', drawApp); appTitle = "Notes App" menu = '[Q]uit [↓/j] Down [↑/k] Up [Enter/o] Open' draw = { t: 'l', v: list_mock } drawApp() getKey()