UNPKG

9.82 kBJavaScriptView Raw
1/**
2 * Isomorphic logging module with support for colors!
3 *
4 * @module logging
5 */
6
7import * as env from './environment.js'
8import * as set from './set.js'
9import * as pair from './pair.js'
10import * as dom from './dom.js'
11import * as json from './json.js'
12import * as map from './map.js'
13import * as eventloop from './eventloop.js'
14import * as math from './math.js'
15import * as common from './logging.common.js'
16
17export { BOLD, UNBOLD, BLUE, GREY, GREEN, RED, PURPLE, ORANGE, UNCOLOR } from './logging.common.js'
18
19/**
20 * @type {Object<Symbol,pair.Pair<string,string>>}
21 */
22const _browserStyleMap = {
23 [common.BOLD]: pair.create('font-weight', 'bold'),
24 [common.UNBOLD]: pair.create('font-weight', 'normal'),
25 [common.BLUE]: pair.create('color', 'blue'),
26 [common.GREEN]: pair.create('color', 'green'),
27 [common.GREY]: pair.create('color', 'grey'),
28 [common.RED]: pair.create('color', 'red'),
29 [common.PURPLE]: pair.create('color', 'purple'),
30 [common.ORANGE]: pair.create('color', 'orange'), // not well supported in chrome when debugging node with inspector - TODO: deprecate
31 [common.UNCOLOR]: pair.create('color', 'black')
32}
33
34/**
35 * @param {Array<string|Symbol|Object|number|function():any>} args
36 * @return {Array<string|object|number>}
37 */
38/* c8 ignore start */
39const computeBrowserLoggingArgs = (args) => {
40 if (args.length === 1 && args[0]?.constructor === Function) {
41 args = /** @type {Array<string|Symbol|Object|number>} */ (/** @type {[function]} */ (args)[0]())
42 }
43 const strBuilder = []
44 const styles = []
45 const currentStyle = map.create()
46 /**
47 * @type {Array<string|Object|number>}
48 */
49 let logArgs = []
50 // try with formatting until we find something unsupported
51 let i = 0
52 for (; i < args.length; i++) {
53 const arg = args[i]
54 // @ts-ignore
55 const style = _browserStyleMap[arg]
56 if (style !== undefined) {
57 currentStyle.set(style.left, style.right)
58 } else {
59 if (arg === undefined) {
60 break
61 }
62 if (arg.constructor === String || arg.constructor === Number) {
63 const style = dom.mapToStyleString(currentStyle)
64 if (i > 0 || style.length > 0) {
65 strBuilder.push('%c' + arg)
66 styles.push(style)
67 } else {
68 strBuilder.push(arg)
69 }
70 } else {
71 break
72 }
73 }
74 }
75 if (i > 0) {
76 // create logArgs with what we have so far
77 logArgs = styles
78 logArgs.unshift(strBuilder.join(''))
79 }
80 // append the rest
81 for (; i < args.length; i++) {
82 const arg = args[i]
83 if (!(arg instanceof Symbol)) {
84 logArgs.push(arg)
85 }
86 }
87 return logArgs
88}
89/* c8 ignore stop */
90
91/* c8 ignore start */
92const computeLoggingArgs = env.supportsColor
93 ? computeBrowserLoggingArgs
94 : common.computeNoColorLoggingArgs
95/* c8 ignore stop */
96
97/**
98 * @param {Array<string|Symbol|Object|number>} args
99 */
100export const print = (...args) => {
101 console.log(...computeLoggingArgs(args))
102 /* c8 ignore next */
103 vconsoles.forEach((vc) => vc.print(args))
104}
105
106/* c8 ignore start */
107/**
108 * @param {Array<string|Symbol|Object|number>} args
109 */
110export const warn = (...args) => {
111 console.warn(...computeLoggingArgs(args))
112 args.unshift(common.ORANGE)
113 vconsoles.forEach((vc) => vc.print(args))
114}
115/* c8 ignore stop */
116
117/**
118 * @param {Error} err
119 */
120/* c8 ignore start */
121export const printError = (err) => {
122 console.error(err)
123 vconsoles.forEach((vc) => vc.printError(err))
124}
125/* c8 ignore stop */
126
127/**
128 * @param {string} url image location
129 * @param {number} height height of the image in pixel
130 */
131/* c8 ignore start */
132export const printImg = (url, height) => {
133 if (env.isBrowser) {
134 console.log(
135 '%c ',
136 `font-size: ${height}px; background-size: contain; background-repeat: no-repeat; background-image: url(${url})`
137 )
138 // console.log('%c ', `font-size: ${height}x; background: url(${url}) no-repeat;`)
139 }
140 vconsoles.forEach((vc) => vc.printImg(url, height))
141}
142/* c8 ignore stop */
143
144/**
145 * @param {string} base64
146 * @param {number} height
147 */
148/* c8 ignore next 2 */
149export const printImgBase64 = (base64, height) =>
150 printImg(`data:image/gif;base64,${base64}`, height)
151
152/**
153 * @param {Array<string|Symbol|Object|number>} args
154 */
155export const group = (...args) => {
156 console.group(...computeLoggingArgs(args))
157 /* c8 ignore next */
158 vconsoles.forEach((vc) => vc.group(args))
159}
160
161/**
162 * @param {Array<string|Symbol|Object|number>} args
163 */
164export const groupCollapsed = (...args) => {
165 console.groupCollapsed(...computeLoggingArgs(args))
166 /* c8 ignore next */
167 vconsoles.forEach((vc) => vc.groupCollapsed(args))
168}
169
170export const groupEnd = () => {
171 console.groupEnd()
172 /* c8 ignore next */
173 vconsoles.forEach((vc) => vc.groupEnd())
174}
175
176/**
177 * @param {function():Node} createNode
178 */
179/* c8 ignore next 2 */
180export const printDom = (createNode) =>
181 vconsoles.forEach((vc) => vc.printDom(createNode()))
182
183/**
184 * @param {HTMLCanvasElement} canvas
185 * @param {number} height
186 */
187/* c8 ignore next 2 */
188export const printCanvas = (canvas, height) =>
189 printImg(canvas.toDataURL(), height)
190
191export const vconsoles = set.create()
192
193/**
194 * @param {Array<string|Symbol|Object|number>} args
195 * @return {Array<Element>}
196 */
197/* c8 ignore start */
198const _computeLineSpans = (args) => {
199 const spans = []
200 const currentStyle = new Map()
201 // try with formatting until we find something unsupported
202 let i = 0
203 for (; i < args.length; i++) {
204 let arg = args[i]
205 // @ts-ignore
206 const style = _browserStyleMap[arg]
207 if (style !== undefined) {
208 currentStyle.set(style.left, style.right)
209 } else {
210 if (arg === undefined) {
211 arg = 'undefined '
212 }
213 if (arg.constructor === String || arg.constructor === Number) {
214 // @ts-ignore
215 const span = dom.element('span', [
216 pair.create('style', dom.mapToStyleString(currentStyle))
217 ], [dom.text(arg.toString())])
218 if (span.innerHTML === '') {
219 span.innerHTML = '&nbsp;'
220 }
221 spans.push(span)
222 } else {
223 break
224 }
225 }
226 }
227 // append the rest
228 for (; i < args.length; i++) {
229 let content = args[i]
230 if (!(content instanceof Symbol)) {
231 if (content.constructor !== String && content.constructor !== Number) {
232 content = ' ' + json.stringify(content) + ' '
233 }
234 spans.push(
235 dom.element('span', [], [dom.text(/** @type {string} */ (content))])
236 )
237 }
238 }
239 return spans
240}
241/* c8 ignore stop */
242
243const lineStyle =
244 'font-family:monospace;border-bottom:1px solid #e2e2e2;padding:2px;'
245
246/* c8 ignore start */
247export class VConsole {
248 /**
249 * @param {Element} dom
250 */
251 constructor (dom) {
252 this.dom = dom
253 /**
254 * @type {Element}
255 */
256 this.ccontainer = this.dom
257 this.depth = 0
258 vconsoles.add(this)
259 }
260
261 /**
262 * @param {Array<string|Symbol|Object|number>} args
263 * @param {boolean} collapsed
264 */
265 group (args, collapsed = false) {
266 eventloop.enqueue(() => {
267 const triangleDown = dom.element('span', [
268 pair.create('hidden', collapsed),
269 pair.create('style', 'color:grey;font-size:120%;')
270 ], [dom.text('▼')])
271 const triangleRight = dom.element('span', [
272 pair.create('hidden', !collapsed),
273 pair.create('style', 'color:grey;font-size:125%;')
274 ], [dom.text('▶')])
275 const content = dom.element(
276 'div',
277 [pair.create(
278 'style',
279 `${lineStyle};padding-left:${this.depth * 10}px`
280 )],
281 [triangleDown, triangleRight, dom.text(' ')].concat(
282 _computeLineSpans(args)
283 )
284 )
285 const nextContainer = dom.element('div', [
286 pair.create('hidden', collapsed)
287 ])
288 const nextLine = dom.element('div', [], [content, nextContainer])
289 dom.append(this.ccontainer, [nextLine])
290 this.ccontainer = nextContainer
291 this.depth++
292 // when header is clicked, collapse/uncollapse container
293 dom.addEventListener(content, 'click', (_event) => {
294 nextContainer.toggleAttribute('hidden')
295 triangleDown.toggleAttribute('hidden')
296 triangleRight.toggleAttribute('hidden')
297 })
298 })
299 }
300
301 /**
302 * @param {Array<string|Symbol|Object|number>} args
303 */
304 groupCollapsed (args) {
305 this.group(args, true)
306 }
307
308 groupEnd () {
309 eventloop.enqueue(() => {
310 if (this.depth > 0) {
311 this.depth--
312 // @ts-ignore
313 this.ccontainer = this.ccontainer.parentElement.parentElement
314 }
315 })
316 }
317
318 /**
319 * @param {Array<string|Symbol|Object|number>} args
320 */
321 print (args) {
322 eventloop.enqueue(() => {
323 dom.append(this.ccontainer, [
324 dom.element('div', [
325 pair.create(
326 'style',
327 `${lineStyle};padding-left:${this.depth * 10}px`
328 )
329 ], _computeLineSpans(args))
330 ])
331 })
332 }
333
334 /**
335 * @param {Error} err
336 */
337 printError (err) {
338 this.print([common.RED, common.BOLD, err.toString()])
339 }
340
341 /**
342 * @param {string} url
343 * @param {number} height
344 */
345 printImg (url, height) {
346 eventloop.enqueue(() => {
347 dom.append(this.ccontainer, [
348 dom.element('img', [
349 pair.create('src', url),
350 pair.create('height', `${math.round(height * 1.5)}px`)
351 ])
352 ])
353 })
354 }
355
356 /**
357 * @param {Node} node
358 */
359 printDom (node) {
360 eventloop.enqueue(() => {
361 dom.append(this.ccontainer, [node])
362 })
363 }
364
365 destroy () {
366 eventloop.enqueue(() => {
367 vconsoles.delete(this)
368 })
369 }
370}
371/* c8 ignore stop */
372
373/**
374 * @param {Element} dom
375 */
376/* c8 ignore next */
377export const createVConsole = (dom) => new VConsole(dom)
378
379/**
380 * @param {string} moduleName
381 * @return {function(...any):void}
382 */
383export const createModuleLogger = (moduleName) => common.createModuleLogger(print, moduleName)