Skip to content

Commit 0d94be9

Browse files
committed
Merge branch 'improve/console'
2 parents 1a751b2 + 88a4de5 commit 0d94be9

25 files changed

Lines changed: 1708 additions & 48 deletions

File tree

package-lock.json

Lines changed: 308 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
"packages/vscode-extension"
66
],
77
"dependencies": {
8-
"@yodaos-jsar/dom": "0.3.1-alpha.20240117.1705494038380",
8+
"@yodaos-jsar/dom": "0.3.1-alpha.20240118.1705570624102",
99
"babylonjs": "^6.10.0"
1010
},
1111
"devDependencies": {
1212
"@jest/globals": "^29.7.0",
1313
"@rspack/cli": "^0.4.4",
14+
"@types/linkifyjs": "^2.1.3",
1415
"@types/node": "^20.3.3",
1516
"jest": "^29.7.0",
1617
"jest-junit": "^16.0.0",

packages/vscode-extension/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,21 @@
233233
},
234234
"dependencies": {
235235
"@devicefarmer/adbkit": "^3.2.5",
236+
"@emotion/styled": "^10.3.0",
236237
"@gltf-transform/core": "^3.5.1",
237238
"@gltf-transform/extensions": "^3.5.1",
238239
"@vscode/l10n": "^0.0.16",
239240
"@vscode/webview-ui-toolkit": "^1.2.2",
240241
"antd": "^5.8.6",
241242
"bootstrap": "^5.3.2",
242243
"draco3dgltf": "^1.5.6",
244+
"emotion-theming": "^10.3.0",
243245
"js-beautify": "^1.14.9",
246+
"linkifyjs": "^2.1.6",
244247
"meshoptimizer": "^0.19.0",
245248
"react": "^18.2.0",
246249
"react-dom": "^18.2.0",
250+
"react-inspector": "^6.0.2",
247251
"react-terminal": "^1.3.0",
248252
"three": "^0.160.0",
249253
"vscode-languageclient": "^9.0.1",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as React from 'react';
2+
import { ThemeProvider } from 'emotion-theming';
3+
import { Props } from './definitions/component';
4+
import Styles from './theme/default';
5+
6+
import { Root } from './elements';
7+
import Message from './Message';
8+
9+
// https://stackoverflow.com/a/48254637/4089357
10+
const customStringify = function (v) {
11+
const cache = new Set()
12+
return JSON.stringify(v, function (key, value) {
13+
if (typeof value === 'object' && value !== null) {
14+
if (cache.has(value)) {
15+
// Circular reference found, discard key
16+
return
17+
}
18+
// Store value in our set
19+
cache.add(value)
20+
}
21+
return value
22+
})
23+
}
24+
25+
const getTheme = (props: Props) => ({
26+
variant: props.variant || 'light',
27+
styles: {
28+
...Styles(props),
29+
...props.styles,
30+
},
31+
})
32+
33+
class Console extends React.PureComponent<Props, any> {
34+
state = {
35+
theme: getTheme(this.props),
36+
prevStyles: this.props.styles,
37+
prevVariant: this.props.variant,
38+
}
39+
40+
static getDerivedStateFromProps(props, state) {
41+
if (
42+
props.variant !== state.prevVariant ||
43+
JSON.stringify(props.styles) !== JSON.stringify(props.prevStyles)
44+
) {
45+
return {
46+
theme: getTheme(props),
47+
prevStyles: props.styles,
48+
prevVariant: props.variant,
49+
}
50+
}
51+
return null
52+
}
53+
54+
render() {
55+
let {
56+
filter = [],
57+
logs = [],
58+
searchKeywords,
59+
logFilter,
60+
logGrouping = true,
61+
} = this.props
62+
63+
if (searchKeywords) {
64+
const regex = new RegExp(searchKeywords)
65+
66+
const filterFun = logFilter
67+
? logFilter
68+
: (log) => {
69+
try {
70+
return regex.test(customStringify(log))
71+
} catch (e) {
72+
return true
73+
}
74+
}
75+
76+
// @ts-ignore
77+
logs = logs.filter(filterFun)
78+
}
79+
80+
if (logGrouping) {
81+
// @ts-ignore
82+
logs = logs.reduce((acc, log) => {
83+
const prevLog = acc[acc.length - 1]
84+
85+
if (
86+
prevLog &&
87+
prevLog.amount &&
88+
prevLog.method === log.method &&
89+
prevLog.data.length === log.data.length &&
90+
prevLog.data.every((value, i) => log.data[i] === value)
91+
) {
92+
prevLog.amount += 1
93+
94+
return acc
95+
}
96+
97+
acc.push({ ...log, amount: 1 })
98+
99+
return acc
100+
}, [])
101+
}
102+
103+
return (
104+
<ThemeProvider theme={this.state.theme}>
105+
<Root>
106+
{logs.map((log, i) => {
107+
// If the filter is defined and doesn't include the method
108+
const filtered =
109+
filter.length !== 0 &&
110+
log.method &&
111+
filter.indexOf(log.method) === -1
112+
113+
return filtered ? null : (
114+
<Message
115+
log={log}
116+
key={log.id || `${log.method}-${i}`}
117+
linkifyOptions={this.props.linkifyOptions}
118+
/>
119+
)
120+
})}
121+
</Root>
122+
</ThemeProvider>
123+
)
124+
}
125+
}
126+
127+
export default Console;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import * as React from 'react';
2+
import { MessageProps, Theme } from './definitions/component';
3+
import { ThemeProvider } from 'emotion-theming';
4+
5+
import { Message, Icon, Content, AmountIcon, Timestamp } from './elements';
6+
7+
import Formatted from './message-parser/Formatted';
8+
import ObjectTree from './message-parser/Object';
9+
import ErrorPanel from './message-parser/Error';
10+
11+
// https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions
12+
const reSubstitutions = /(%[coOs])|(%(([0-9]*[.])?[0-9]+)?[dif])/g
13+
14+
class ConsoleMessage extends React.Component<MessageProps, any> {
15+
shouldComponentUpdate(nextProps) {
16+
return this.props.log.amount !== nextProps.log.amount
17+
}
18+
19+
theme = (theme: Theme) => ({
20+
...theme,
21+
method: this.props.log.method,
22+
})
23+
24+
render() {
25+
const { log } = this.props;
26+
return (
27+
<ThemeProvider theme={this.theme}>
28+
<Message data-method={log.method}>
29+
{log.amount > 1 ? <AmountIcon>{log.amount}</AmountIcon> : <Icon />}
30+
{log.timestamp ? <Timestamp>{log.timestamp}</Timestamp> : <span/>}
31+
<Content>{this.getNode()}</Content>
32+
</Message>
33+
</ThemeProvider>
34+
);
35+
}
36+
37+
getNode() {
38+
const { log } = this.props;
39+
40+
// Error handling
41+
const error = this.typeCheck(log);
42+
if (error) return error;
43+
44+
// Chrome formatting
45+
if (log.data.length > 0 && typeof log.data[0] === 'string') {
46+
const matchLength = log.data[0].match(reSubstitutions)?.length;
47+
if (matchLength) {
48+
const restData = log.data.slice(1 + matchLength);
49+
return (
50+
<>
51+
<Formatted data={log.data} />
52+
{restData.length > 0 && (
53+
<ObjectTree
54+
quoted={false}
55+
log={{ ...log, data: restData }}
56+
linkifyOptions={this.props.linkifyOptions}
57+
/>
58+
)}
59+
</>
60+
);
61+
}
62+
}
63+
64+
// Error panel
65+
if (
66+
log.data.every((message) => typeof message === 'string') &&
67+
log.method === 'error'
68+
) {
69+
return <ErrorPanel error={log.data.join(' ')} />;
70+
}
71+
72+
// Normal inspector
73+
const quoted = typeof log.data[0] !== 'string';
74+
return (
75+
<ObjectTree
76+
log={log}
77+
quoted={quoted}
78+
linkifyOptions={this.props.linkifyOptions}
79+
/>
80+
);
81+
}
82+
83+
typeCheck(log: any) {
84+
if (!log) {
85+
return (
86+
<Formatted
87+
data={[
88+
`%c[console-feed] %cFailed to parse message! %clog was typeof ${typeof log}, but it should've been a log object`,
89+
'color: red',
90+
'color: orange',
91+
'color: cyan',
92+
]}
93+
/>
94+
);
95+
} else if (!(log.data instanceof Array)) {
96+
return (
97+
<Formatted
98+
data={[
99+
'%c[console-feed] %cFailed to parse message! %clog.data was not an array!',
100+
'color: red',
101+
'color: orange',
102+
'color: cyan',
103+
]}
104+
/>
105+
);
106+
}
107+
return false;
108+
}
109+
}
110+
111+
export default ConsoleMessage;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Payload } from './payload'
2+
import { Styles } from './styles'
3+
import { Methods } from './methods'
4+
import type { Options } from 'linkifyjs'
5+
6+
export type Variants = 'light' | 'dark'
7+
8+
export interface Theme {
9+
variant: Variants
10+
styles: Styles
11+
}
12+
13+
export interface Context extends Theme {
14+
method: Methods
15+
}
16+
17+
export interface Message extends Payload {
18+
data: any[]
19+
amount?: number
20+
}
21+
22+
export interface Props {
23+
logs: Message[]
24+
variant?: Variants
25+
styles?: Styles
26+
filter?: Methods[]
27+
searchKeywords?: string
28+
logFilter?: Function
29+
logGrouping?: Boolean
30+
linkifyOptions?: Options
31+
}
32+
33+
export interface MessageProps {
34+
log: Message
35+
linkifyOptions?: Options
36+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Methods as _Methods } from './methods';
2+
import { Payload } from './payload';
3+
4+
export interface Storage {
5+
pointers: {
6+
[name: string]: Function;
7+
};
8+
src: any;
9+
}
10+
11+
export interface HookedConsole extends Console {
12+
feed: Storage;
13+
}
14+
15+
export type Methods = _Methods;
16+
17+
export interface Message {
18+
method: Methods;
19+
data?: any[];
20+
timestamp?: string;
21+
}
22+
23+
export type Callback = (encoded: Message, message: Payload) => void;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export type Methods =
2+
| 'log'
3+
| 'debug'
4+
| 'info'
5+
| 'warn'
6+
| 'error'
7+
| 'table'
8+
| 'clear'
9+
| 'time'
10+
| 'timeEnd'
11+
| 'count'
12+
| 'assert'
13+
| 'command'
14+
| 'result'
15+
| 'dir';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Message } from './console';
2+
3+
export interface Payload extends Message {
4+
id: string
5+
}

0 commit comments

Comments
 (0)