5htp-core 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "5-HTP, scientifically called 5-Hydroxytryptophan, is the precursor of happiness neurotransmitter.",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -85,13 +85,6 @@ iframe {
85
85
  border: none;
86
86
  }
87
87
 
88
- header.card {
89
- > i {
90
- font-size: 3em;
91
- line-height: 1em;
92
- }
93
- }
94
-
95
88
  .dropArea {
96
89
  padding: @spacing;
97
90
  border-radius: @radius;
@@ -217,6 +217,7 @@
217
217
  Déclarer une grid: grid x-3
218
218
  Placer un element: x1-3
219
219
  */
220
+ @gridUnit: 6.66rem;
220
221
  .zones(@nb, @i: 1) when (@i <= @nb) {
221
222
  /*----------------------------------
222
223
  - ZONES DE LA GRILLE
@@ -287,23 +288,23 @@
287
288
  // Automatique
288
289
  .taille-auto(@min, @max) when (@min <= @max) {
289
290
  &.xa@{min}-@{max} {
290
- grid-template-columns: repeat(auto-fit, minmax(@min * 100px, @max * 100px))
291
+ grid-template-columns: repeat(auto-fit, minmax(@min * @gridUnit, @max * @gridUnit))
291
292
  }
292
293
  &.ya@{min}-@{max} {
293
- grid-template-rows: repeat(auto-fit, minmax(@min * 100px, @max * 100px))
294
+ grid-template-rows: repeat(auto-fit, minmax(@min * @gridUnit, @max * @gridUnit))
294
295
  }
295
296
  .taille-auto(@min + 1, @max);
296
297
  }
297
298
  &.xa@{i} {
298
- grid-template-columns: repeat(auto-fit, minmax(@i * 100px, 1fr));
299
+ grid-template-columns: repeat(auto-fit, minmax(@i * @gridUnit, 1fr));
299
300
  &5 {
300
- grid-template-columns: repeat(auto-fit, minmax(@i * 100px + 4.15rem, 1fr));
301
+ grid-template-columns: repeat(auto-fit, minmax(@i * @gridUnit + 4.15rem, 1fr));
301
302
  }
302
303
  }
303
304
  &.ya@{i} {
304
- grid-template-rows: repeat(auto-fit, minmax(@i * 100px, 1fr));
305
+ grid-template-rows: repeat(auto-fit, minmax(@i * @gridUnit, 1fr));
305
306
  &5 {
306
- grid-template-rows: repeat(auto-fit, minmax(@i * 100px + 4.15rem, 1fr));
307
+ grid-template-rows: repeat(auto-fit, minmax(@i * @gridUnit + 4.15rem, 1fr));
307
308
  }
308
309
  }
309
310
  .taille-auto(1, @i);
@@ -347,10 +348,10 @@
347
348
  break-inside: avoid;
348
349
  }
349
350
 
350
- &.xa1 { column-width: 100px; }
351
- &.xa2 { column-width: 200px; }
351
+ &.xa1 { column-width: @gridUnit; }
352
+ &.xa2 { column-width: @gridUnit * 2; }
352
353
  &,
353
- &.xa3 { column-width: 300px; }
354
- &.xa4 { column-width: 400px; }
354
+ &.xa3 { column-width: @gridUnit * 3; }
355
+ &.xa4 { column-width: @gridUnit * 4; }
355
356
 
356
357
  }
File without changes
@@ -5,6 +5,7 @@ a {
5
5
  color: inherit;
6
6
  cursor: pointer;
7
7
  text-decoration: underline;
8
+ text-decoration: none;
8
9
 
9
10
  &:not([disabled]):hover,
10
11
  &.active {
@@ -12,12 +13,16 @@ a {
12
13
  }
13
14
 
14
15
  &.super {
15
- position: absolute;
16
- top: 0; left: 0; right: 0; bottom: 0;
17
- z-index: 5;
18
-
19
- ~ a, ~ * a, ~ button, ~ * button {
20
- z-index: 6;
16
+ text-decoration: none;
17
+ &::after {
18
+ content: ' ';
19
+ position: absolute;
20
+ top: 0; left: 0; right: 0; bottom: 0;
21
+ z-index: 5;
22
+
23
+ ~ a, ~ * a, ~ button, ~ * button {
24
+ z-index: 6;
25
+ }
21
26
  }
22
27
  }
23
28
  }
@@ -0,0 +1,58 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import React from 'react';
7
+
8
+ // Core
9
+ import Button from '@client/components/button';
10
+ import Dropdown, { TDialogControls, Props as DropdownProps } from '@client/components/dropdown';
11
+
12
+ /*----------------------------------
13
+ - CONST
14
+ ----------------------------------*/
15
+
16
+ /*----------------------------------
17
+ - TYPES
18
+ ----------------------------------*/
19
+
20
+ type Choices = string[]
21
+
22
+ type SearchResultsFunction = (keywords: string) => Choices
23
+
24
+ export type Props = DropdownProps & {
25
+ title: string,
26
+ choices: Choices,
27
+ value?: string,
28
+ onChange: (value: string) => void,
29
+ search?: true | SearchResultsFunction
30
+ }
31
+
32
+ /*----------------------------------
33
+ - COMONENT
34
+ ----------------------------------*/
35
+
36
+ export default ({ title, choices, value, onChange, ...dropDownProps }: Props) => {
37
+ const refModal = React.useRef<TDialogControls>(null);
38
+ return (
39
+ <Dropdown {...dropDownProps} content={(
40
+ <ul class="card col menu">
41
+ {choices.map( jt => (
42
+ <li>
43
+ <Button active={jt === value} onClick={() => {
44
+ onChange(jt);
45
+ refModal.current?.close();
46
+ }}>
47
+ {jt}
48
+ </Button>
49
+ </li>
50
+ ))}
51
+ </ul>
52
+ )} iconR="chevron-down" refModal={refModal}>
53
+
54
+ {value || title}
55
+
56
+ </Dropdown>
57
+ )
58
+ }
@@ -33,7 +33,7 @@ export default ({
33
33
  ratioCercle = 1,
34
34
  className,
35
35
  style = {},
36
- epaisseur = 5,
36
+ epaisseur = 10,
37
37
  rotation,
38
38
  etapes,
39
39
  taille,
@@ -99,8 +99,8 @@ div.progressbar_old {
99
99
  .progressbar-svg {
100
100
 
101
101
  position: relative;
102
- min-width: 100px;
103
- min-height: 100px;
102
+ width: @sizeComponent;
103
+ height: @sizeComponent;
104
104
 
105
105
  > svg {
106
106
 
@@ -142,6 +142,8 @@ div.progressbar_old {
142
142
  left: 0;
143
143
  right: 0;
144
144
  bottom: 0;
145
+ // Text should fit inside the progressbar
146
+ font-size: 0.8em;
145
147
 
146
148
  > i,
147
149
  > img {
@@ -4,10 +4,12 @@
4
4
 
5
5
  // Npm
6
6
  import React from 'react';
7
- import { ComponentChild } from 'preact';
7
+ import { ComponentChild, RefObject } from 'preact';
8
8
 
9
9
  // Core
10
10
  import Button, { Props as ButtonProps } from '../button';
11
+ import { TDialogControls } from '../Dialog/Manager';
12
+ export type { TDialogControls } from '../Dialog/Manager';
11
13
 
12
14
  // Libs
13
15
  import useContexte from '@client/context';
@@ -17,23 +19,8 @@ import useContexte from '@client/context';
17
19
  ----------------------------------*/
18
20
 
19
21
  export type Props = ButtonProps & {
20
- content: ComponentChild
21
- }
22
-
23
- export type TAction<TDonnee> = {
24
- icone?: TIcons,
25
- label: ComponentChild,
26
- multi?: boolean,
27
-
28
- onClick?: (donnees: TDonnee, index: number) => void,
29
- lien?: (donnees: TDonnee, index: number) => string,
30
- bouton?: (donnees: TDonnee, index: number) => ButtonProps
31
- }
32
-
33
- export type TActionsPopover = {
34
- show: () => void,
35
- hide: () => void,
36
- toggle: () => void
22
+ content: ComponentChild,
23
+ refModal?: RefObject<TDialogControls>
37
24
  }
38
25
 
39
26
  /*----------------------------------
@@ -45,15 +32,19 @@ export default (props: Props) => {
45
32
 
46
33
  let {
47
34
  content,
48
- immutable,
35
+ refModal,
49
36
  ...buttonProps
50
37
  } = props;
51
38
 
52
39
  const refButton = React.useRef<HTMLElement>(null);
53
40
 
54
- const open = () => modal.show(() => content);
41
+ const open = () => {
42
+ const modalInstance = modal.show(() => content);
43
+ if (refModal)
44
+ refModal.current = modalInstance;
45
+ }
55
46
 
56
47
  return (
57
- <Button {...buttonProps} onClick={open} refElem={refButton} />
48
+ <Button {...buttonProps} onClick={(open)} refElem={refButton} />
58
49
  )
59
50
  }
@@ -57,8 +57,10 @@ export function useInput<TValue>(
57
57
  // External value change
58
58
  React.useEffect(() => {
59
59
 
60
- console.log("External value change", externalValue);
61
- setState({ value: externalValue, valueSource: 'external' })
60
+ if (externalValue !== undefined && externalValue !== state.value) {
61
+ console.log("External value change", externalValue);
62
+ setState({ value: externalValue, valueSource: 'external' })
63
+ }
62
64
 
63
65
  }, [externalValue]);
64
66
 
@@ -112,42 +112,6 @@ export class ClientContext {
112
112
  this.request = request;
113
113
  this.user = this.request.user || { ...GuestUser };
114
114
 
115
- this.api.reload = (ids?: string | string[], params?: TObjetDonnees) => {
116
-
117
- if (this.page === undefined)
118
- throw new Error("context.page is missing");
119
-
120
- if (ids === undefined)
121
- ids = Object.keys(this.page.fetchers);
122
- else if (typeof ids === 'string')
123
- ids = [ids];
124
-
125
- console.log("[api] Reload data", ids, params, this.page.fetchers);
126
-
127
- for (const id of ids) {
128
-
129
- const fetcher = this.page.fetchers[id];
130
- if (fetcher === undefined)
131
- return console.error(`Unable to reload ${id}: Request not found in fetchers list.`);
132
-
133
- if (params !== undefined)
134
- fetcher.data = { ...(fetcher.data || {}), ...params };
135
-
136
- console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
137
- const indicator = this.toast.loading("Loading ...");
138
-
139
- this.request.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
140
-
141
- this.api.set({ [id]: data });
142
-
143
- }).finally(() => {
144
-
145
- indicator.close(true);
146
-
147
- })
148
- }
149
- }
150
-
151
115
  }
152
116
 
153
117
  // Is overwrote by the native app
@@ -194,10 +158,51 @@ export class ClientContext {
194
158
 
195
159
  // fetch doit appartenir à response, et non clientcontxt
196
160
  // car la méthode varie selon client (http) ou serveur (router.resolve)
197
- public api = this.request.api as this["request"]["api"] & {
198
- reload: (id?: string, params?: TObjetDonnees) => void,
199
- set: (data: TObjetDonnees) => void
200
- };
161
+ public api = {
162
+ ...this.request.api,
163
+ set: (newData: TObjetDonnees) => {
164
+
165
+ console.log("[api] Update page data", newData);
166
+ if (this.page)
167
+ this.page.setAllData(curData => ({ ...curData, ...newData }));
168
+
169
+ },
170
+ reload: (ids?: string | string[], params?: TObjetDonnees) => {
171
+
172
+ if (this.page === undefined)
173
+ throw new Error("context.page is missing");
174
+
175
+ if (ids === undefined)
176
+ ids = Object.keys(this.page.fetchers);
177
+ else if (typeof ids === 'string')
178
+ ids = [ids];
179
+
180
+ console.log("[api] Reload data", ids, params, this.page.fetchers);
181
+
182
+ for (const id of ids) {
183
+
184
+ const fetcher = this.page.fetchers[id];
185
+ if (fetcher === undefined)
186
+ return console.error(`Unable to reload ${id}: Request not found in fetchers list.`);
187
+
188
+ if (params !== undefined)
189
+ fetcher.data = { ...(fetcher.data || {}), ...params };
190
+
191
+ console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
192
+ const indicator = this.toast.loading("Loading ...");
193
+
194
+ this.request.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
195
+
196
+ this.api.set({ [id]: data });
197
+
198
+ }).finally(() => {
199
+
200
+ indicator.close(true);
201
+
202
+ })
203
+ }
204
+ }
205
+ }
201
206
 
202
207
  public handleError(e: Error) {
203
208
  switch (e.http) {
@@ -50,6 +50,7 @@ const Page = ({ page, isCurrent }: { page: PageResponse, isCurrent?: boolean })
50
50
  id={page.id === undefined ? undefined : 'page_' + page.id}
51
51
  >
52
52
 
53
+ {/* Make request parameters and api data accessible from the page component */}
53
54
  {page.component ? (
54
55
 
55
56
  <page.component {...gui.request.data} {...data} />
File without changes
@@ -11,11 +11,13 @@ export type TVariation = {
11
11
  color: string,
12
12
  }
13
13
 
14
- export const variationStr = (value: number, reference: number, lowerIsBetter: boolean = false): TVariation => {
14
+ export const variationStr = (value: number, reference: number, options: {
15
+ lowerIsBetter?: boolean
16
+ } = {}): TVariation => {
15
17
  const pc = variation(value, reference);
16
18
  const pcStr = pc.toFixed(2);
17
19
  return {
18
20
  txt: ((pc > 0) ? '+' + pcStr : pcStr) + '%',
19
- color: (lowerIsBetter ? pc > 0 : pc < 0) ? 'ea3943' : '16c784'
21
+ color: (options.lowerIsBetter ? pc > 0 : pc < 0) ? 'ea3943' : '16c784'
20
22
  }
21
23
  }
@@ -35,7 +35,7 @@ const getLayout = (routePath: string | undefined): Layout | undefined => {
35
35
  if (routePath === layoutPath || routePath.startsWith( layoutPath + '/' ))
36
36
  layout = { path: layoutPath, Component: layouts[layoutPath] };
37
37
  }
38
- layout && console.log(`Using Layout: ${layout.path}`);
38
+ //layout && console.log(`${routePath}: Using Layout: ${layout.path}`);
39
39
  return layout;
40
40
  }
41
41
 
@@ -15,7 +15,7 @@ import ConfigParser, { TEnvConfig } from './config';
15
15
  ----------------------------------*/
16
16
 
17
17
  type THookName = 'ready' | 'cleanup' | 'error'
18
- type THook = () => void;
18
+ type THook = () => Promise<void>;
19
19
 
20
20
  type TServiceOptions = {
21
21
  instanciate: boolean
@@ -91,6 +91,20 @@ export class App {
91
91
  ----------------------------------*/
92
92
 
93
93
  public constructor() {
94
+
95
+ // Gestion crash
96
+ process.on('unhandledRejection', (error: any, promise: any) => {
97
+
98
+ console.error("Unhandled promise rejection:", error);
99
+
100
+ // Send email report
101
+ if (this.isLoaded('console'))
102
+ $.console.bugReport.server(error);
103
+ else
104
+ console.error(`Unable to send bug report: console service not loaded.`);
105
+
106
+ });
107
+
94
108
  // Load config files
95
109
  const configParser = new ConfigParser( this.path.root );
96
110
  this.env = configParser.env();
@@ -106,21 +120,47 @@ export class App {
106
120
  console.log("Configure services with", this.config);
107
121
  }
108
122
 
109
- public register( id: string, Service: TServiceClass, options: Partial<TServiceOptions> = {}) {
123
+ // Register a service
124
+ public register<TServiceName extends keyof Core.Services>(
125
+ id: TServiceName,
126
+ Service: TServiceClass,
127
+ options: Partial<TServiceOptions> = {}
128
+ ) {
110
129
 
111
130
  // Pas d'export default new Service pour chaque fichier de service,
112
131
  // dissuaded'importer ms service sn'importe où, ce qui créé des références circulaires
113
- console.log(`Launching service ${id} ...`, Service);
132
+ console.log(`[services] Registering service ${id} ...`);
114
133
  const service = options.instanciate !== false ? new Service() : Service;
115
- this.services[id] = service;
116
-
117
- // Lorsque service.load est async, une propriété loading doit etre présente
118
- // De façon à ce que les autres services puissent savoir quand ce service est prêt
119
- if ('loading' in service) {
120
- service.loading = service.load();
121
- this.loading.push(service.loading);
122
- } else if ('load' in service)
123
- service.load();
134
+ this.services[id as string] = service;
135
+
136
+ if ('load' in service) {
137
+
138
+ console.log(`[services] Starting service ${id} ...`);
139
+
140
+ // Lorsque service.load est async, une propriété loading doit etre présente
141
+ // De façon à ce que les autres services puissent savoir quand ce service est prêt
142
+ if ('loading' in service) {
143
+
144
+ console.log(`[services] Waiting service ${id} to be fully loaded ...`);
145
+ service.loading = service.load().then(() => {
146
+ console.info(`[service] Service ${id} successfully started.`);
147
+ }).catch(e => {
148
+ // Bug report via email
149
+ console.error(`[service] Error while starting the ${id} service:`, e);
150
+ e.message = `Start ${id} service: ` + e.message;
151
+ $.console.bugReport.server(e);
152
+ });;
153
+
154
+ this.loading.push(service.loading);
155
+
156
+ } else
157
+ service.load();
158
+ }
159
+ }
160
+
161
+ // Test if a service was registered
162
+ public isLoaded( id: keyof Core.Services ) {
163
+ return id in this.services;
124
164
  }
125
165
 
126
166
  public on( name: THookName, callback: THook ) {
@@ -128,23 +168,36 @@ export class App {
128
168
  return this;
129
169
  }
130
170
 
171
+ public runHook( hookName: THookName ) {
172
+ console.info(`[hook] Run all ${hookName} hook (${this.hooks.ready.length}).`);
173
+ return Promise.all(
174
+ this.hooks.ready.map(
175
+ cb => cb().catch(e => {
176
+ console.error(`[hook] Error while executing hook ${hookName}:`, e);
177
+ })
178
+ )
179
+ ).then(() => {
180
+ console.info(`[hook] Hooks ${hookName} executed with success.`);
181
+ })
182
+ }
183
+
131
184
  /*----------------------------------
132
185
  - LAUNCH
133
186
  ----------------------------------*/
134
187
  public async launch() {
135
188
 
136
- console.info(`Waiting for services to be ready ...`);
189
+ console.info(`[boot] Waiting for all services to be ready ...`);
137
190
  await Promise.all( this.loading );
138
191
 
139
- console.info(`Launching application ...`);
140
- await Promise.all( this.hooks.ready.map(cb => cb()) );
192
+ console.info(`[boot] Launching application ...`);
193
+ await this.runHook('ready');
141
194
 
142
195
  // NOTE: Useless ?
143
196
  /*if (this.hmr)
144
197
  this.activateHMR();*/
145
198
 
146
- console.info(`Application is ready.`);
147
-
199
+ console.info(`[boot] Application is ready.`);
200
+
148
201
  this.launched = true;
149
202
 
150
203
  }
@@ -14,6 +14,12 @@ import app from '@server/app';
14
14
 
15
15
  // Libs
16
16
 
17
+ /*----------------------------------
18
+ - CONFIG
19
+ ----------------------------------*/
20
+
21
+ const debug = false;
22
+
17
23
  /*----------------------------------
18
24
  - TYPES
19
25
  ----------------------------------*/
@@ -50,7 +56,7 @@ class Cache {
50
56
 
51
57
  private cleanMem() {
52
58
 
53
- console.log("[cache] Clean memory");
59
+ debug && console.log("[cache] Clean memory");
54
60
 
55
61
  const now = Date.now();
56
62
  for (const key in this.data) {
@@ -88,11 +94,11 @@ class Cache {
88
94
 
89
95
  let retour: CacheEntry | undefined = this.data[cle];
90
96
 
91
- console.log(`[cache] Get "${cle}".`);
97
+ debug && console.log(`[cache] Get "${cle}".`);
92
98
 
93
99
  // Expired
94
100
  if (retour?.expiration && retour.expiration < Date.now()){
95
- console.log(`[cache] Key ${cle} expired.`);
101
+ debug && console.log(`[cache] Key ${cle} expired.`);
96
102
  retour = undefined;
97
103
  }
98
104
 
@@ -133,7 +139,7 @@ class Cache {
133
139
  */
134
140
  public set( cle: string, val: TPrimitiveValue, expiration: TExpirationDelay = null ): void {
135
141
 
136
- console.log("[cache] Updating cache " + cle);
142
+ debug && console.log("[cache] Updating cache " + cle);
137
143
  this.data[ cle ] = {
138
144
  value: val,
139
145
  expiration: this.delayToTimestamp(expiration)
@@ -4,17 +4,6 @@ moduleAlias.addAliases({
4
4
  'react-dom': "preact/compat",
5
5
  })
6
6
 
7
- /*----------------------------------
8
- - DEBUG
9
- ----------------------------------*/
10
-
11
- // Gestion crash
12
- process.on('unhandledRejection', (error: any, promise: any) => {
13
-
14
- console.error("Unhandled promise rejection:", error);
15
-
16
- });
17
-
18
7
  /*----------------------------------
19
8
  - DATES & TIMZEONE
20
9
  ----------------------------------*/
@@ -70,7 +70,7 @@ export default class BugReporter {
70
70
  public async server( error: Error, request?: ServerRequest ) {
71
71
 
72
72
  // error should be printed in the console, so they're acccessible from logs
73
- console.error(error);
73
+ console.error(`Sending bug report for the following error:`, error);
74
74
 
75
75
  // Prevent duplicates
76
76
  if (!this.shouldSendReport('server', request?.user?.name, undefined, error.message))
@@ -79,7 +79,6 @@ export default class BugReporter {
79
79
  // Get context
80
80
  const now = new Date();
81
81
  const hash = uuid();
82
- const erroTitle = "Server Bug: " + error.message;
83
82
  const { channelType, channelId } = this.console.getChannel();
84
83
 
85
84
  // On envoi l'email avant l'insertion dans bla bdd
@@ -90,31 +89,36 @@ export default class BugReporter {
90
89
  );
91
90
 
92
91
  // Send notification
93
- $.email.send({
94
- to: app.identity.author.email,
95
- subject: "Bug serveur: " + erroTitle,
96
- html: `
97
- <a href="${app.env.url}/admin/activity/requests/${channelId}">
98
- View Request details & console
99
- </a>
100
- <br/>
101
- ${logsHtml}
102
- `
103
- });
104
- // Memorize
105
- $.sql.insert('BugServer', {
106
- // Context
107
- hash: hash,
108
- date: now,
109
- channelType,
110
- channelId,
111
- // User
112
- user: request?.user?.name,
113
- ip: request?.ip,
114
- // Error
115
- stacktrace: error.stack || error.message,
116
- logs: logsHtml
117
- });
92
+ if (app.isLoaded('email'))
93
+ $.email.send({
94
+ to: app.identity.author.email,
95
+ subject: "Server bug: " + error.message,
96
+ html: `
97
+ <a href="${app.env.url}/admin/activity/requests/${channelId}">
98
+ View Request details & console
99
+ </a>
100
+ <br/>
101
+ ${logsHtml}
102
+ `
103
+ });
104
+ else
105
+ console.error("Unable to send bug report: email service not loaded.");
106
+
107
+ /*if (app.isLoaded('sql'))
108
+ // Memorize
109
+ $.sql.insert('BugServer', {
110
+ // Context
111
+ hash: hash,
112
+ date: now,
113
+ channelType,
114
+ channelId,
115
+ // User
116
+ user: request?.user?.name,
117
+ ip: request?.ip,
118
+ // Error
119
+ stacktrace: error.stack || error.message,
120
+ logs: logsHtml
121
+ });*/
118
122
 
119
123
  // Update error message
120
124
  error.message = "A bug report has been sent to my personal mailbox. Sorry for the inconvenience.";
File without changes
@@ -256,7 +256,7 @@ export class Console {
256
256
  let html = logs.map( log => logToHTML( log, this )).join('\n');
257
257
 
258
258
  if (full) {
259
- const consoleCss = `background: #000; padding: 20px; font-family: 'monospace'; font-size: 12px; line-height: 20px;`
259
+ const consoleCss = `background: #000; padding: 20px; font-family: 'Fira Mono', 'monospace', 'Monaco'; font-size: 12px; line-height: 20px;`
260
260
  html = '<div style="' + consoleCss + '">' + html + '</div>';
261
261
  }
262
262
 
@@ -49,7 +49,7 @@ export class WebSocketCommander {
49
49
  public scopes: {[path: string]: SocketScope} = {}
50
50
 
51
51
  public constructor() {
52
- app.on('cleanup', () => {
52
+ app.on('cleanup', async () => {
53
53
  this.closeAll();
54
54
  });
55
55
  }