@zikojs/console 0.1.0 → 0.3.0
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/LICENSE +1 -1
- package/package.json +7 -2
- package/src/components/Console.js +27 -23
- package/src/components/ConsoleBody.js +44 -32
- package/src/components/ConsoleFeed.js +29 -125
- package/src/components/Row.js +27 -50
- package/src/components/index.js +0 -2
- package/src/components/nodeEntry.js +10 -0
- package/src/feed/index.js +96 -0
- package/src/index.css +4 -117
- package/src/index.js +4 -51
- package/src/utils/const.js +11 -0
- package/src/utils/feed.js +5 -0
- package/src/utils/index.js +3 -1
- package/src/utils/value-formatting.js +1 -0
- package/src/components/ConsolePrompt.js +0 -74
- package/src/components/ConsoleToolbar.js +0 -61
- package/src/feed/createFeed.js +0 -67
package/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zikojs/console",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/zikojs/console.git",
|
|
12
|
+
"directory": "packages/console"
|
|
13
|
+
},
|
|
9
14
|
"keywords": [
|
|
10
15
|
"zikojs",
|
|
11
16
|
"console",
|
|
@@ -16,7 +21,7 @@
|
|
|
16
21
|
"license": "MIT",
|
|
17
22
|
"type": "module",
|
|
18
23
|
"dependencies": {
|
|
19
|
-
"ziko": "
|
|
24
|
+
"ziko": "latest"
|
|
20
25
|
},
|
|
21
26
|
"scripts": {
|
|
22
27
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import van from "https://cdn.jsdelivr.net/npm/vanjs-core@1.5.3/src/van.js";
|
|
2
|
+
const { div } = van.tags;
|
|
3
|
+
import { nodeEntry, makeEntry } from "./nodeEntry.js";
|
|
4
|
+
import { Row } from "./Row.js";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const entryNode =
|
|
7
|
+
(method) =>
|
|
8
|
+
(...args) => {
|
|
9
|
+
const e = makeEntry(method, args);
|
|
10
|
+
const node = div(
|
|
11
|
+
{ class: () => "cf cf-solo " + Console.theme }, // <-- using a function or string
|
|
12
|
+
Row(e),
|
|
13
|
+
);
|
|
14
|
+
nodeEntry.set(node, e);
|
|
15
|
+
return node;
|
|
16
|
+
};
|
|
4
17
|
|
|
5
18
|
export const Console = {
|
|
6
|
-
theme:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const node =
|
|
20
|
-
document.createComment("groupEnd");
|
|
21
|
-
|
|
22
|
-
nodeEntry.set(node, {
|
|
23
|
-
method: "groupEnd",
|
|
24
|
-
args: []
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return node;
|
|
19
|
+
theme: { val: "dark" },
|
|
20
|
+
log: entryNode("log"),
|
|
21
|
+
info: entryNode("info"),
|
|
22
|
+
debug: entryNode("debug"),
|
|
23
|
+
warn: entryNode("warn"),
|
|
24
|
+
error: entryNode("error"),
|
|
25
|
+
table: entryNode("table"),
|
|
26
|
+
group: entryNode("group"),
|
|
27
|
+
groupEnd: () => {
|
|
28
|
+
const node = document.createComment("groupEnd");
|
|
29
|
+
nodeEntry.set(node, { method: "groupEnd", args: [] });
|
|
30
|
+
return node;
|
|
31
|
+
},
|
|
28
32
|
};
|
|
@@ -1,46 +1,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { tags } from "ziko/dom";
|
|
2
|
+
const { div } = tags;
|
|
3
|
+
import { Row, cache } from "./Row.js";
|
|
4
|
+
|
|
5
|
+
export function ConsoleBody(feed) {
|
|
6
|
+
const listContainer = div();
|
|
7
|
+
|
|
8
|
+
const renderLogs = () => {
|
|
9
|
+
const visible = feed.getVisibleLogs();
|
|
10
|
+
listContainer.element.replaceChildren(
|
|
11
|
+
(
|
|
12
|
+
visible.length
|
|
13
|
+
? div(...visible.map((e) => Row(e)))
|
|
14
|
+
: div(
|
|
15
|
+
{ class: "cf-empty" },
|
|
16
|
+
"No messages yet. Anything sent to console.* shows up here.",
|
|
17
|
+
)
|
|
18
|
+
).element
|
|
3
19
|
|
|
4
|
-
|
|
20
|
+
);
|
|
21
|
+
};
|
|
5
22
|
|
|
6
|
-
export function ConsoleBody(visible) {
|
|
7
23
|
let stick = true;
|
|
8
|
-
|
|
9
24
|
const body = div(
|
|
10
25
|
{
|
|
11
26
|
class: "cf-body",
|
|
12
|
-
|
|
13
27
|
onscroll: () => {
|
|
14
|
-
stick =
|
|
15
|
-
|
|
16
|
-
body.scrollTop -
|
|
17
|
-
body.clientHeight < 24;
|
|
18
|
-
}
|
|
28
|
+
stick = body.scrollHeight - body.scrollTop - body.clientHeight < 24;
|
|
29
|
+
},
|
|
19
30
|
},
|
|
31
|
+
listContainer
|
|
32
|
+
).element;
|
|
33
|
+
|
|
34
|
+
feed.addEventListener("logs-change", () => {
|
|
35
|
+
renderLogs();
|
|
36
|
+
if (stick) {
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
body.scrollTop = body.scrollHeight;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
visible.val.map(entry =>
|
|
25
|
-
Row(entry)
|
|
26
|
-
)
|
|
27
|
-
)
|
|
28
|
-
: div(
|
|
29
|
-
{ class: "cf-empty" },
|
|
30
|
-
"No messages yet. Anything sent to console.* shows up here."
|
|
31
|
-
)
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
van.derive(() => {
|
|
35
|
-
visible.val;
|
|
43
|
+
feed.addEventListener("clear", () => {
|
|
44
|
+
renderLogs();
|
|
45
|
+
});
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
47
|
+
feed.addEventListener("row-update", (e) => {
|
|
48
|
+
const rowEl = cache.get(e.detail);
|
|
49
|
+
if (rowEl) {
|
|
50
|
+
const badge = rowEl.querySelector(".cf-badge");
|
|
51
|
+
if (badge && badge.updateBadge) badge.updateBadge();
|
|
52
|
+
}
|
|
43
53
|
});
|
|
44
54
|
|
|
55
|
+
renderLogs();
|
|
56
|
+
|
|
45
57
|
return body;
|
|
46
58
|
}
|
|
@@ -1,141 +1,45 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { tags } from 'ziko/dom'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import { nodeEntry } from "../entries/entryNode.js";
|
|
5
|
-
|
|
6
|
-
import { ConsoleToolbar } from "./ConsoleToolbar.js";
|
|
7
|
-
import { ConsoleBody } from "./ConsoleBody.js";
|
|
8
|
-
import { ConsolePrompt } from "./ConsolePrompt.js";
|
|
3
|
+
const { div } = tags;
|
|
9
4
|
|
|
5
|
+
import { isProps } from "../utils/index.js";
|
|
10
6
|
import { Hook } from "../hook/index.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "
|
|
15
|
-
|
|
16
|
-
const { div } = van.tags;
|
|
17
|
-
|
|
18
|
-
const LEVEL = {
|
|
19
|
-
log: "log",
|
|
20
|
-
dir: "log",
|
|
21
|
-
table: "log",
|
|
22
|
-
group: "log",
|
|
23
|
-
|
|
24
|
-
info: "info",
|
|
25
|
-
debug: "debug",
|
|
26
|
-
warn: "warn",
|
|
27
|
-
error: "error"
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const LEVELS = [
|
|
31
|
-
"log",
|
|
32
|
-
"info",
|
|
33
|
-
"warn",
|
|
34
|
-
"error",
|
|
35
|
-
"debug"
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const isProps = value =>
|
|
39
|
-
value &&
|
|
40
|
-
typeof value === "object" &&
|
|
41
|
-
!(value instanceof Node) &&
|
|
42
|
-
Object.getPrototypeOf(value) === Object.prototype;
|
|
7
|
+
import { createFeed } from "../feed/index.js";
|
|
8
|
+
import { nodeEntry } from "./nodeEntry.js";
|
|
9
|
+
import { cache } from "./Row.js";
|
|
10
|
+
import { ConsoleBody } from "./ConsoleBody.js";
|
|
43
11
|
|
|
44
12
|
export function ConsoleFeed(...args) {
|
|
45
|
-
const [props, ...
|
|
46
|
-
|
|
47
|
-
? args
|
|
48
|
-
: [{}, ...args];
|
|
49
|
-
|
|
13
|
+
const [props, ...kids] = isProps(args[0]) ? args : [{}, ...args];
|
|
14
|
+
const feed = props.feed || createFeed(1000, cache);
|
|
50
15
|
const {
|
|
51
|
-
feed = createFeed(),
|
|
52
16
|
variant = "dark",
|
|
53
|
-
|
|
54
|
-
hook = false
|
|
17
|
+
hook = false,
|
|
55
18
|
} = props;
|
|
56
19
|
|
|
57
|
-
if (hook)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
children
|
|
62
|
-
.flat(Infinity)
|
|
63
|
-
.forEach(child => {
|
|
64
|
-
const entry = nodeEntry.get(child);
|
|
65
|
-
|
|
66
|
-
if (entry) {
|
|
67
|
-
feed.push(entry);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const theme =
|
|
72
|
-
typeof variant === "string"
|
|
73
|
-
? van.state(variant)
|
|
74
|
-
: variant;
|
|
75
|
-
|
|
76
|
-
const levels = Object.fromEntries(
|
|
77
|
-
LEVELS.map(level => [
|
|
78
|
-
level,
|
|
79
|
-
van.state(true)
|
|
80
|
-
])
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const query = van.state("");
|
|
84
|
-
|
|
85
|
-
const count = level =>
|
|
86
|
-
van.derive(() =>
|
|
87
|
-
feed.logs.val.filter(
|
|
88
|
-
entry =>
|
|
89
|
-
LEVEL[entry.method] === level
|
|
90
|
-
).length
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const visible = van.derive(() => {
|
|
94
|
-
const search =
|
|
95
|
-
query.val.toLowerCase();
|
|
96
|
-
|
|
97
|
-
return feed.logs.val.filter(entry => {
|
|
98
|
-
const level =
|
|
99
|
-
LEVEL[entry.method];
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
level &&
|
|
103
|
-
!levels[level].val
|
|
104
|
-
) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!search) {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return fmt(entry.args)
|
|
113
|
-
.map(safe)
|
|
114
|
-
.join(" ")
|
|
115
|
-
.toLowerCase()
|
|
116
|
-
.includes(search);
|
|
117
|
-
});
|
|
20
|
+
if (hook) Hook(console, (e) => feed.push(e));
|
|
21
|
+
kids.flat(Infinity).forEach((c) => {
|
|
22
|
+
const e = nodeEntry.get(c);
|
|
23
|
+
if (e) feed.push(e);
|
|
118
24
|
});
|
|
119
25
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class: () =>
|
|
123
|
-
"cf " + theme.val,
|
|
26
|
+
const initialTheme = typeof variant === "string" ? variant : (variant.val || "dark");
|
|
27
|
+
feed.setTheme(initialTheme);
|
|
124
28
|
|
|
125
|
-
|
|
126
|
-
},
|
|
29
|
+
const body = ConsoleBody(feed);
|
|
127
30
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
31
|
+
const container = div(
|
|
32
|
+
{ class: "cf " + feed.theme },
|
|
33
|
+
// bar,
|
|
34
|
+
body,
|
|
35
|
+
// prompt,
|
|
36
|
+
).style({
|
|
37
|
+
flex : 1
|
|
38
|
+
});
|
|
134
39
|
|
|
135
|
-
|
|
40
|
+
feed.addEventListener("theme-change", (e) => {
|
|
41
|
+
container.className = "cf " + e.detail + " flex:1";
|
|
42
|
+
});
|
|
136
43
|
|
|
137
|
-
|
|
138
|
-
? ConsolePrompt({ feed })
|
|
139
|
-
: ""
|
|
140
|
-
);
|
|
44
|
+
return container;
|
|
141
45
|
}
|
package/src/components/Row.js
CHANGED
|
@@ -1,64 +1,41 @@
|
|
|
1
1
|
import van from "https://cdn.jsdelivr.net/npm/vanjs-core@1.5.3/src/van.js";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
fmt,
|
|
5
|
-
isObj
|
|
6
|
-
} from "../utils/index.js";
|
|
7
|
-
|
|
8
|
-
import { Inspect } from "./Inspect.js";
|
|
9
|
-
import { Table } from "./Table.js";
|
|
10
|
-
|
|
11
2
|
const { div, span } = van.tags;
|
|
3
|
+
import { fmt, isObj } from "../utils/index.js";
|
|
4
|
+
import { Inspect, Table } from "./index.js";
|
|
12
5
|
|
|
13
6
|
const cache = new WeakMap();
|
|
14
7
|
|
|
15
|
-
const buildRow =
|
|
16
|
-
const [a0, ...rest] =
|
|
17
|
-
|
|
8
|
+
const buildRow = (e) => {
|
|
9
|
+
const [a0, ...rest] = e.args;
|
|
18
10
|
const items =
|
|
19
|
-
|
|
20
|
-
? [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
e.method === "table" && isObj(a0)
|
|
12
|
+
? [Table(a0), rest.map((x) => Inspect(x, undefined, [], true)).element]
|
|
13
|
+
: fmt(e.args).map((a) => Inspect(a, undefined, [], true).element);
|
|
14
|
+
|
|
15
|
+
const badgeSpan = span({ class: "cf-badge" }, () => (e.count > 1 ? e.count : ""));
|
|
16
|
+
const updateBadge = () => {
|
|
17
|
+
badgeSpan.textContent = e.count > 1 ? e.count : "";
|
|
18
|
+
badgeSpan.style.display = e.count > 1 ? "inline-block" : "none";
|
|
19
|
+
};
|
|
20
|
+
badgeSpan.updateBadge = updateBadge;
|
|
21
|
+
updateBadge();
|
|
29
22
|
|
|
30
23
|
return div(
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
style: `--d:${entry.depth}`
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
() =>
|
|
37
|
-
entry.count.val > 1
|
|
38
|
-
? span(
|
|
39
|
-
{ class: "cf-badge" },
|
|
40
|
-
entry.count.val
|
|
41
|
-
)
|
|
42
|
-
: "",
|
|
43
|
-
|
|
24
|
+
{ class: `cf-row ${e.method}`, style: `--d:${e.depth}` },
|
|
25
|
+
badgeSpan,
|
|
44
26
|
span(
|
|
45
27
|
{ class: "cf-time" },
|
|
46
|
-
|
|
47
|
-
hour12: false
|
|
48
|
-
})
|
|
28
|
+
e.time.toLocaleTimeString([], { hour12: false }),
|
|
49
29
|
),
|
|
50
|
-
|
|
51
|
-
div(
|
|
52
|
-
{ class: "cf-msg" },
|
|
53
|
-
items
|
|
54
|
-
)
|
|
30
|
+
div({ class: "cf-msg" }, items),
|
|
55
31
|
);
|
|
56
32
|
};
|
|
57
33
|
|
|
58
|
-
export const Row =
|
|
59
|
-
cache.get(
|
|
60
|
-
(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
34
|
+
export const Row = (e) => {
|
|
35
|
+
if (cache.has(e)) return cache.get(e);
|
|
36
|
+
const n = buildRow(e);
|
|
37
|
+
cache.set(e, n);
|
|
38
|
+
return n;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { cache };
|
package/src/components/index.js
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { LEVEL, LEVELS, same, fmt, safe } from "../utils/index.js";
|
|
2
|
+
|
|
3
|
+
export class ConsoleStore extends EventTarget {
|
|
4
|
+
constructor(max = 1000, cacheRef = null) {
|
|
5
|
+
super();
|
|
6
|
+
this.logs = [];
|
|
7
|
+
this.max = max;
|
|
8
|
+
this.depth = 0;
|
|
9
|
+
this.id = 0;
|
|
10
|
+
this.levels = Object.fromEntries(LEVELS.map((l) => [l, true]));
|
|
11
|
+
this.query = "";
|
|
12
|
+
this.theme = "dark";
|
|
13
|
+
this.cache = cacheRef;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setTheme(theme) {
|
|
17
|
+
this.theme = theme;
|
|
18
|
+
this.dispatchEvent(new CustomEvent("theme-change", { detail: theme }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toggleLevel(level) {
|
|
22
|
+
this.levels[level] = !this.levels[level];
|
|
23
|
+
this.dispatchEvent(new CustomEvent("filter-change"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setQuery(query) {
|
|
27
|
+
this.query = query;
|
|
28
|
+
this.dispatchEvent(new CustomEvent("filter-change"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
push(src) {
|
|
32
|
+
const { method, args = [] } = src;
|
|
33
|
+
if (method === "clear") {
|
|
34
|
+
this.depth = 0;
|
|
35
|
+
this.logs = [];
|
|
36
|
+
this.dispatchEvent(new CustomEvent("clear"));
|
|
37
|
+
this.dispatchEvent(new CustomEvent("logs-change"));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (method === "groupEnd") {
|
|
41
|
+
this.depth = Math.max(0, this.depth - 1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const isGroup = method === "group" || method === "groupCollapsed";
|
|
45
|
+
const e = src.count
|
|
46
|
+
? Object.assign(src, { id: ++this.id, depth: this.depth })
|
|
47
|
+
: {
|
|
48
|
+
id: ++this.id,
|
|
49
|
+
method: isGroup ? "group" : method,
|
|
50
|
+
args,
|
|
51
|
+
depth: this.depth,
|
|
52
|
+
count: 1,
|
|
53
|
+
time: new Date(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (src.count && this.cache) {
|
|
57
|
+
this.cache.get(e)?.style.setProperty("--d", this.depth);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const last = this.logs.at(-1);
|
|
61
|
+
if (last && !isGroup && same(last, e)) {
|
|
62
|
+
last.count++;
|
|
63
|
+
this.dispatchEvent(new CustomEvent("row-update", { detail: last }));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.logs.push(e);
|
|
68
|
+
if (this.logs.length > this.max) {
|
|
69
|
+
this.logs.shift();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.dispatchEvent(new CustomEvent("logs-change", { detail: e }));
|
|
73
|
+
if (isGroup) this.depth++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear() {
|
|
77
|
+
this.push({ method: "clear" });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getCount(level) {
|
|
81
|
+
return this.logs.filter((e) => LEVEL[e.method] === level).length;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getVisibleLogs() {
|
|
85
|
+
const s = this.query.toLowerCase();
|
|
86
|
+
return this.logs.filter(
|
|
87
|
+
(e) =>
|
|
88
|
+
(!(e.method in LEVEL) || this.levels[LEVEL[e.method]]) &&
|
|
89
|
+
(!s || fmt(e.args).map(safe).join(" ").toLowerCase().includes(s))
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createFeed(max = 1000, cache) {
|
|
95
|
+
return new ConsoleStore(max, cache);
|
|
96
|
+
}
|
package/src/index.css
CHANGED
|
@@ -15,67 +15,15 @@ body {
|
|
|
15
15
|
display: flex;
|
|
16
16
|
flex-direction: column;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
padding: 14px 20px 4px;
|
|
20
|
-
}
|
|
21
|
-
header h1 {
|
|
22
|
-
margin: 0;
|
|
23
|
-
font-size: 17px;
|
|
24
|
-
font-weight: 650;
|
|
25
|
-
}
|
|
26
|
-
header p {
|
|
27
|
-
margin: 2px 0 0;
|
|
28
|
-
color: #5c6578;
|
|
29
|
-
font-size: 13px;
|
|
30
|
-
}
|
|
31
|
-
main {
|
|
32
|
-
flex: 1;
|
|
33
|
-
min-height: 0;
|
|
34
|
-
display: grid;
|
|
35
|
-
grid-template-columns: 230px 1fr;
|
|
36
|
-
gap: 14px;
|
|
37
|
-
padding: 12px 20px 20px;
|
|
38
|
-
}
|
|
18
|
+
|
|
39
19
|
@media (max-width: 720px) {
|
|
40
20
|
main {
|
|
41
21
|
grid-template-columns: 1fr;
|
|
42
22
|
grid-template-rows: auto 1fr;
|
|
43
23
|
}
|
|
44
24
|
}
|
|
45
|
-
aside {
|
|
46
|
-
display: flex;
|
|
47
|
-
flex-wrap: wrap;
|
|
48
|
-
align-content: flex-start;
|
|
49
|
-
gap: 6px;
|
|
50
|
-
}
|
|
51
|
-
aside h2 {
|
|
52
|
-
width: 100%;
|
|
53
|
-
margin: 6px 0 0;
|
|
54
|
-
font-size: 12px;
|
|
55
|
-
font-weight: 600;
|
|
56
|
-
color: #5c6578;
|
|
57
|
-
}
|
|
58
|
-
aside button {
|
|
59
|
-
font: inherit;
|
|
60
|
-
font-size: 13px;
|
|
61
|
-
padding: 5px 10px;
|
|
62
|
-
border: 1px solid #c9cedb;
|
|
63
|
-
background: #f6f7fa;
|
|
64
|
-
color: inherit;
|
|
65
|
-
border-radius: 5px;
|
|
66
|
-
cursor: pointer;
|
|
67
|
-
}
|
|
68
|
-
aside button:hover {
|
|
69
|
-
background: #fff;
|
|
70
|
-
border-color: #8a93a8;
|
|
71
|
-
}
|
|
72
|
-
button:focus-visible,
|
|
73
|
-
input:focus-visible {
|
|
74
|
-
outline: 2px solid #3b82f6;
|
|
75
|
-
outline-offset: 1px;
|
|
76
|
-
}
|
|
77
25
|
|
|
78
|
-
/* ---------- Console
|
|
26
|
+
/* ---------- Console feed ---------- */
|
|
79
27
|
.cf {
|
|
80
28
|
--bg: #171b24;
|
|
81
29
|
--bar: #1e2330;
|
|
@@ -119,49 +67,7 @@ input:focus-visible {
|
|
|
119
67
|
--err-fg: #b3261e;
|
|
120
68
|
--hover: #f3f5f9;
|
|
121
69
|
}
|
|
122
|
-
|
|
123
|
-
display: flex;
|
|
124
|
-
gap: 6px;
|
|
125
|
-
align-items: center;
|
|
126
|
-
padding: 6px 8px;
|
|
127
|
-
background: var(--bar);
|
|
128
|
-
border-bottom: 1px solid var(--line);
|
|
129
|
-
flex-wrap: wrap;
|
|
130
|
-
}
|
|
131
|
-
.cf-bar input {
|
|
132
|
-
flex: 1;
|
|
133
|
-
min-width: 120px;
|
|
134
|
-
font: inherit;
|
|
135
|
-
background: var(--bg);
|
|
136
|
-
color: var(--fg);
|
|
137
|
-
border: 1px solid var(--line);
|
|
138
|
-
border-radius: 4px;
|
|
139
|
-
padding: 3px 8px;
|
|
140
|
-
}
|
|
141
|
-
.cf-bar button {
|
|
142
|
-
font: inherit;
|
|
143
|
-
background: none;
|
|
144
|
-
color: var(--dim);
|
|
145
|
-
border: 1px solid transparent;
|
|
146
|
-
border-radius: 4px;
|
|
147
|
-
padding: 2px 8px;
|
|
148
|
-
cursor: pointer;
|
|
149
|
-
}
|
|
150
|
-
.cf-bar button:hover {
|
|
151
|
-
color: var(--fg);
|
|
152
|
-
border-color: var(--line);
|
|
153
|
-
}
|
|
154
|
-
.cf-bar button.cf-chip.on {
|
|
155
|
-
color: var(--fg);
|
|
156
|
-
border-color: var(--line);
|
|
157
|
-
background: var(--bg);
|
|
158
|
-
}
|
|
159
|
-
.cf-chip.warn.on {
|
|
160
|
-
color: var(--warn-fg) !important;
|
|
161
|
-
}
|
|
162
|
-
.cf-chip.error.on {
|
|
163
|
-
color: var(--err-fg) !important;
|
|
164
|
-
}
|
|
70
|
+
|
|
165
71
|
.cf-body {
|
|
166
72
|
flex: 1;
|
|
167
73
|
overflow: auto;
|
|
@@ -288,23 +194,4 @@ input:focus-visible {
|
|
|
288
194
|
.cf-table th {
|
|
289
195
|
background: var(--bar);
|
|
290
196
|
font-weight: 600;
|
|
291
|
-
}
|
|
292
|
-
.cf-prompt {
|
|
293
|
-
display: flex;
|
|
294
|
-
align-items: center;
|
|
295
|
-
gap: 8px;
|
|
296
|
-
padding: 5px 10px;
|
|
297
|
-
border-top: 1px solid var(--line);
|
|
298
|
-
background: var(--bar);
|
|
299
|
-
}
|
|
300
|
-
.cf-prompt span {
|
|
301
|
-
color: var(--key);
|
|
302
|
-
}
|
|
303
|
-
.cf-prompt input {
|
|
304
|
-
flex: 1;
|
|
305
|
-
font: inherit;
|
|
306
|
-
background: none;
|
|
307
|
-
border: 0;
|
|
308
|
-
color: var(--fg);
|
|
309
|
-
outline: 0;
|
|
310
|
-
}
|
|
197
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,51 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Console
|
|
6
|
-
} from './components'
|
|
7
|
-
import { createFeed } from './feed/createFeed'
|
|
8
|
-
import { Hook } from './hook/index.js'
|
|
9
|
-
|
|
10
|
-
Object.assign(window, {ConsoleFeed: {Hook, createFeed, ConsoleFeed, Console}})
|
|
11
|
-
|
|
12
|
-
/* =====================================================================
|
|
13
|
-
Demo
|
|
14
|
-
===================================================================== */
|
|
15
|
-
|
|
16
|
-
const theme = van.state("light")
|
|
17
|
-
|
|
18
|
-
// 1) Declarative: children only, or props first then children
|
|
19
|
-
document.body.append(
|
|
20
|
-
ConsoleFeed({variant: theme, repl: false},
|
|
21
|
-
Console.log("Hello from VanJS", 42, true, null, undefined),
|
|
22
|
-
Console.info("Server listening on :3000"),
|
|
23
|
-
Console.group("Request"),
|
|
24
|
-
Console.log("parsing"),
|
|
25
|
-
Console.warn("token expires soon"),
|
|
26
|
-
Console.groupEnd(),
|
|
27
|
-
Console.error(new Error("Something broke")),
|
|
28
|
-
Console.table([{id: 1, name: "Ada"}, {id: 2, name: "Linus"}]),
|
|
29
|
-
)
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
// 2) Standalone: a single row, no toolbar
|
|
33
|
-
document.body.append(Console.log("Just one line", {a: 1, b : 1}))
|
|
34
|
-
document.body.append(Console.warn("Standalone warning"))
|
|
35
|
-
document.body.append(Console.table([1,2,3]))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// 3) Live: hook the real console into a feed
|
|
39
|
-
Console.theme = theme
|
|
40
|
-
const feed = createFeed()
|
|
41
|
-
Hook(console, e => feed.push(e))
|
|
42
|
-
globalThis.c = ConsoleFeed({feed})
|
|
43
|
-
document.body.append(c)
|
|
44
|
-
|
|
45
|
-
console.log("Hello from VanJS", 42, true, null, undefined)
|
|
46
|
-
console.log({a: 1})
|
|
47
|
-
console.group("Request")
|
|
48
|
-
console.log("parsing")
|
|
49
|
-
console.group("Auth")
|
|
50
|
-
console.warn("token expires soon")
|
|
51
|
-
console.groupEnd()
|
|
1
|
+
import "./index.css";
|
|
2
|
+
export { Hook } from "./hook/index.js";
|
|
3
|
+
export { createFeed } from "./feed/index.js";
|
|
4
|
+
export { ConsoleFeed, Console } from "./components/index.js";
|
package/src/utils/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const isObj = v => v !== null && typeof v === "object"
|
|
2
|
+
export const isProps = a => a && typeof a === "object" && !(a instanceof Node) && Object.getPrototypeOf(a) === Object.prototype
|
|
2
3
|
export const cname = v => { try { return Object.getPrototypeOf(v)?.constructor?.name || "" } catch { return "" } }
|
|
3
4
|
export const prim = v => typeof v === "string" ? JSON.stringify(v) : typeof v === "bigint" ? v + "n" : String(v)
|
|
4
5
|
export const primCls = v => v == null ? "nul" : typeof v === "string" ? "str" : typeof v === "boolean" ? "bool" : "num"
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { tags } from "ziko/dom";
|
|
2
|
-
const { div, span, input } = tags;
|
|
3
|
-
export function ConsolePrompt({
|
|
4
|
-
feed
|
|
5
|
-
}) {
|
|
6
|
-
const history = [];
|
|
7
|
-
let index = 0;
|
|
8
|
-
|
|
9
|
-
const run = code => {
|
|
10
|
-
feed.push({
|
|
11
|
-
method: "command",
|
|
12
|
-
args: [code]
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
feed.push({
|
|
17
|
-
method: "result",
|
|
18
|
-
args: [(0, eval)(code)]
|
|
19
|
-
});
|
|
20
|
-
} catch (error) {
|
|
21
|
-
feed.push({
|
|
22
|
-
method: "error",
|
|
23
|
-
args: [error]
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const inp = input({
|
|
29
|
-
placeholder: "Run JavaScript",
|
|
30
|
-
"aria-label": "Run JavaScript",
|
|
31
|
-
spellcheck: false,
|
|
32
|
-
autocomplete: "off",
|
|
33
|
-
|
|
34
|
-
onKeyDown: __e__ => {
|
|
35
|
-
const e = __e__.event
|
|
36
|
-
const el = e.target;
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
e.key === "Enter" &&
|
|
40
|
-
el.value.trim()
|
|
41
|
-
) {
|
|
42
|
-
history.push(el.value);
|
|
43
|
-
index = history.length;
|
|
44
|
-
|
|
45
|
-
run(el.value);
|
|
46
|
-
|
|
47
|
-
el.value = "";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
else if (
|
|
51
|
-
e.key === "ArrowUp" &&
|
|
52
|
-
index > 0
|
|
53
|
-
) {
|
|
54
|
-
el.value = history[--index];
|
|
55
|
-
e.preventDefault();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
else if (e.key === "ArrowDown") {
|
|
59
|
-
el.value =
|
|
60
|
-
index < history.length - 1
|
|
61
|
-
? history[++index]
|
|
62
|
-
: (index = history.length, "");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
return div(
|
|
68
|
-
{ class: "cf-prompt" },
|
|
69
|
-
|
|
70
|
-
span("\u203A"),
|
|
71
|
-
inp
|
|
72
|
-
|
|
73
|
-
).element;
|
|
74
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import van from "https://cdn.jsdelivr.net/npm/vanjs-core@1.5.3/src/van.js";
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
div,
|
|
5
|
-
button,
|
|
6
|
-
input
|
|
7
|
-
} = van.tags;
|
|
8
|
-
|
|
9
|
-
export function ConsoleToolbar({
|
|
10
|
-
levels,
|
|
11
|
-
query,
|
|
12
|
-
count,
|
|
13
|
-
clear
|
|
14
|
-
}) {
|
|
15
|
-
const LEVELS = [
|
|
16
|
-
"log",
|
|
17
|
-
"info",
|
|
18
|
-
"warn",
|
|
19
|
-
"error",
|
|
20
|
-
"debug"
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
return div(
|
|
24
|
-
{ class: "cf-bar" },
|
|
25
|
-
|
|
26
|
-
LEVELS.map(level =>
|
|
27
|
-
button(
|
|
28
|
-
{
|
|
29
|
-
class: () =>
|
|
30
|
-
`cf-chip ${level}` +
|
|
31
|
-
(levels[level].val ? " on" : ""),
|
|
32
|
-
|
|
33
|
-
"aria-pressed": () =>
|
|
34
|
-
levels[level].val,
|
|
35
|
-
|
|
36
|
-
onclick: () =>
|
|
37
|
-
levels[level].val =
|
|
38
|
-
!levels[level].val
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
level,
|
|
42
|
-
" ",
|
|
43
|
-
count(level)
|
|
44
|
-
)
|
|
45
|
-
),
|
|
46
|
-
|
|
47
|
-
input({
|
|
48
|
-
type: "search",
|
|
49
|
-
placeholder: "Filter messages",
|
|
50
|
-
"aria-label": "Filter messages",
|
|
51
|
-
|
|
52
|
-
oninput: e =>
|
|
53
|
-
query.val = e.target.value
|
|
54
|
-
}),
|
|
55
|
-
|
|
56
|
-
button(
|
|
57
|
-
{ onclick: clear },
|
|
58
|
-
"Clear"
|
|
59
|
-
)
|
|
60
|
-
);
|
|
61
|
-
}
|
package/src/feed/createFeed.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import van from "https://cdn.jsdelivr.net/npm/vanjs-core@1.5.3/src/van.js";
|
|
2
|
-
import { isObj } from "../utils/index.js";
|
|
3
|
-
|
|
4
|
-
export function createFeed(max = 1000) {
|
|
5
|
-
const logs = van.state([]);
|
|
6
|
-
|
|
7
|
-
let depth = 0;
|
|
8
|
-
let id = 0;
|
|
9
|
-
|
|
10
|
-
const same = (a, b) =>
|
|
11
|
-
a.method === b.method &&
|
|
12
|
-
a.depth === b.depth &&
|
|
13
|
-
a.args.length === b.args.length &&
|
|
14
|
-
a.args.every(
|
|
15
|
-
(x, i) => !isObj(x) && Object.is(x, b.args[i])
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const push = src => {
|
|
19
|
-
const { method, args = [] } = src;
|
|
20
|
-
|
|
21
|
-
if (method === "clear") {
|
|
22
|
-
depth = 0;
|
|
23
|
-
logs.val = [];
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (method === "groupEnd") {
|
|
28
|
-
depth = Math.max(0, depth - 1);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isGroup =
|
|
33
|
-
method === "group" ||
|
|
34
|
-
method === "groupCollapsed";
|
|
35
|
-
|
|
36
|
-
const entry = src.count
|
|
37
|
-
? Object.assign(src, {
|
|
38
|
-
id: ++id,
|
|
39
|
-
depth
|
|
40
|
-
})
|
|
41
|
-
: {
|
|
42
|
-
id: ++id,
|
|
43
|
-
method: isGroup ? "group" : method,
|
|
44
|
-
args,
|
|
45
|
-
depth,
|
|
46
|
-
count: van.state(1),
|
|
47
|
-
time: new Date()
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const last = logs.val.at(-1);
|
|
51
|
-
|
|
52
|
-
if (last && !isGroup && same(last, entry)) {
|
|
53
|
-
last.count.val++;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
logs.val = [...logs.val, entry].slice(-max);
|
|
58
|
-
|
|
59
|
-
if (isGroup) depth++;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
logs,
|
|
64
|
-
push,
|
|
65
|
-
clear: () => push({ method: "clear" })
|
|
66
|
-
};
|
|
67
|
-
}
|