1 | var ansi = require('ansi-escape-sequences')
|
2 | var differ = require('ansi-diff')
|
3 | var pretty = require('prettier-bytes')
|
4 | var keypress = require('keypress')
|
5 | var strip = require('strip-ansi')
|
6 | var nanoraf = require('nanoraf')
|
7 |
|
8 | var StartDelimiter = '|'
|
9 | var EndDelimiter = '|'
|
10 | var Filled = '█'
|
11 | var Empty = '░'
|
12 | var NewlineMatcher = /\n/g
|
13 |
|
14 | var files = [
|
15 | 'assets',
|
16 | 'documents',
|
17 | 'scripts',
|
18 | 'styles',
|
19 | 'manifest',
|
20 | 'service-worker'
|
21 | ]
|
22 |
|
23 | module.exports = render
|
24 |
|
25 | function render (state) {
|
26 | var diff = differ()
|
27 |
|
28 | var render = nanoraf(onrender, raf)
|
29 |
|
30 | process.stdout.on('resize', onresize)
|
31 |
|
32 | if (process.stdin.isTTY) {
|
33 | keypress(process.stdin)
|
34 | process.stdin.setRawMode(true)
|
35 | process.stdin.resume()
|
36 | process.stdin.on('keypress', onkeypress)
|
37 | }
|
38 |
|
39 | return render
|
40 |
|
41 | function onrender () {
|
42 | process.stdout.write(diff.update(view(state)))
|
43 | }
|
44 |
|
45 | function onresize () {
|
46 | diff.resize({width: process.stdout.columns, height: process.stdout.rows})
|
47 | diff.update('')
|
48 | render()
|
49 | }
|
50 |
|
51 | function onkeypress (ch, key) {
|
52 | if (key && key.ctrl && key.name === 'c') {
|
53 | process.exit()
|
54 | } else if (ch === '1') {
|
55 |
|
56 | render()
|
57 | } else if (ch === '2') {
|
58 |
|
59 | render()
|
60 | } else if (ch === '3') {
|
61 |
|
62 | render()
|
63 | }
|
64 | }
|
65 | }
|
66 |
|
67 | function view (state) {
|
68 | if (state.error) {
|
69 | return '\x1b[33c' + state.error
|
70 | }
|
71 |
|
72 | var str = '\x1b[33c'
|
73 | str += header(state)
|
74 | str += '\n\n'
|
75 | str += files.reduce(function (str, filename) {
|
76 | var file = state.files[filename]
|
77 | if (!file) return ''
|
78 | var status = file.status
|
79 | var count = status === 'done' ? String(state.count[filename]) : ''
|
80 | if (status === 'done') status = clr(status, 'green')
|
81 |
|
82 |
|
83 | var name = count === '1'
|
84 | ? file.name.replace(/s$/, '')
|
85 | : file.name
|
86 |
|
87 | str += clr(padLeft(count, 3), 'yellow') + ' '
|
88 | str += padRight(clr(name, 'green'), 14)
|
89 | var size = pretty(file.size).replace(' ', '')
|
90 | str += pad(7 - size.length) + clr(size, 'magenta') + ' '
|
91 | str += clr(file.timestamp, 'cyan') + ' '
|
92 | str += progress(file.progress, 10) + ' '
|
93 | str += status
|
94 |
|
95 | return str + '\n'
|
96 | }, '') + '\n'
|
97 |
|
98 | var ssrState = 'Pending'
|
99 |
|
100 | if (state.ssr) {
|
101 | ssrState = state.ssr.success
|
102 | ? 'Success'
|
103 | : `Skipped - ${state.ssr.error.message} ${state.ssr.error.stack.split('\n')[1].trim()}`
|
104 | }
|
105 | str += 'Server Side Rendering: ' + ssrState + '\n'
|
106 |
|
107 | str += footer(state)
|
108 |
|
109 |
|
110 | var padLines = Math.max(process.stdout.rows - str.match(NewlineMatcher).length - 1, 0)
|
111 | str += '\n'.repeat(padLines)
|
112 |
|
113 | return str
|
114 | }
|
115 |
|
116 |
|
117 | function header (state) {
|
118 | var SSEStatus = state.sse > 0
|
119 | ? clr('connected', 'green')
|
120 | : state.port
|
121 | ? 'ready'
|
122 | : clr('starting', 'yellow')
|
123 |
|
124 | var httpStatus = state.port
|
125 | ? clr(clr('https://localhost:' + state.port, 'underline'), 'blue')
|
126 | : clr('starting', 'yellow')
|
127 |
|
128 | var left = `HTTP: ${httpStatus}`
|
129 | var right = `Live Reload: ${SSEStatus}`
|
130 | return spaceBetween(left, right)
|
131 | }
|
132 |
|
133 |
|
134 | function footer (state) {
|
135 | var size = Object.keys(state.files).reduce(function (num, filename) {
|
136 | var file = state.files[filename]
|
137 | return num + file.size
|
138 | }, 0)
|
139 | var bottomLeft = tabBar(1, 0)
|
140 |
|
141 | var totalSize = clr(pretty(size).replace(' ', ''), 'magenta')
|
142 | var bottomRight = `Total size: ${totalSize}`
|
143 | return spaceBetween(bottomLeft, bottomRight)
|
144 | }
|
145 |
|
146 | function tabBar (count, curr) {
|
147 | var str = ''
|
148 | var tmp
|
149 | for (var i = 0; i < count; i++) {
|
150 | tmp = String(i + 1)
|
151 | if (curr === i) {
|
152 | tmp = `[ ${tmp} ]`
|
153 | } else {
|
154 | tmp = clr(tmp, 'gray')
|
155 | if (i !== 0) tmp = ' ' + tmp
|
156 | if (i !== count) tmp = tmp + ' '
|
157 | }
|
158 | str += tmp
|
159 | }
|
160 | return str
|
161 | }
|
162 |
|
163 | function clr (text, color) {
|
164 | return process.stdout.isTTY ? ansi.format(text, color) : text
|
165 | }
|
166 |
|
167 | function padLeft (str, num, char) {
|
168 | str = String(str)
|
169 | var len = strip(str).length
|
170 | return pad(num - len, char) + str
|
171 | }
|
172 |
|
173 | function padRight (str, num, char) {
|
174 | str = String(str)
|
175 | var len = strip(str).length
|
176 | return str + pad(num - len, char)
|
177 | }
|
178 |
|
179 | function pad (len, char) {
|
180 | char = String(char === undefined ? ' ' : char)
|
181 | var res = ''
|
182 | while (res.length < len) res += char
|
183 | return res
|
184 | }
|
185 |
|
186 | function progress (curr, max) {
|
187 | var filledLength = Math.floor((curr / 100) * max)
|
188 | var emptyLength = max - filledLength
|
189 | var i = 1 + filledLength
|
190 | var j = i + emptyLength
|
191 |
|
192 | var str = StartDelimiter
|
193 | while (str.length < i) str += Filled
|
194 | while (str.length < j) str += Empty
|
195 | str += EndDelimiter
|
196 | return str
|
197 | }
|
198 |
|
199 | function raf (cb) {
|
200 | setTimeout(cb, 50)
|
201 | }
|
202 |
|
203 | function spaceBetween (left, right) {
|
204 | var len = process.stdout.columns - strip(left).length - strip(right).length
|
205 | var space = ''
|
206 | for (var i = 0; i < len; i++) {
|
207 | space += ' '
|
208 | }
|
209 | return left + space + right
|
210 | }
|