Skip to content

Commit ebbc462

Browse files
committed
feat: init console 🚧
1 parent d484513 commit ebbc462

File tree

8 files changed

+314
-12
lines changed

8 files changed

+314
-12
lines changed

src/output/Console.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import { CommandData } from '../types';
3+
import ConsoleLine from './ConsoleLine.vue';
4+
5+
6+
const props = defineProps<{ logs: CommandData[] }>()
7+
8+
</script>
9+
10+
<template>
11+
<div class="container">
12+
<ConsoleLine v-for="(log, i) in logs" :log="log" :key="i" />
13+
</div>
14+
</template>
15+
16+
<style>
17+
.container {
18+
position: relative;
19+
}
20+
</style>

src/output/ConsoleLine.vue

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue';
3+
import { CommandData } from '../types';
4+
import JsonTree from './JsonTree.vue';
5+
6+
defineProps<{ log: CommandData }>()
7+
const level = ref(1)
8+
// function toggleGroupCollapse() {
9+
// log.collapsed = !log.collapsed;
10+
// }
11+
</script>
12+
13+
<template>
14+
<div
15+
v-if="log.level === 'table'"
16+
>
17+
<ConsoleTable :data="log.args[0]" :columns="log.args[1]" />
18+
</div>
19+
<div
20+
class="log console-{{ log.level }}"
21+
:style="{ paddingLeft: level * 15 + 'px' }"
22+
>
23+
<span v-if="log.count && log.count > 1" class="count">{{ log.count }}x</span>
24+
25+
<div
26+
v-if="log.level === 'trace' || log.level === 'assert'"
27+
class="arrow"
28+
>▶</div>
29+
30+
<span v-if="log.level === 'assert'" class="assert">Assertion failed:</span>
31+
32+
<span v-if="log.level === 'clear'" class="info">Console was cleared</span>
33+
<span v-if="log.level === 'unclonable'" class="info error">Message could not be cloned. Open devtools to see it</span>
34+
<div v-if="log.level === 'group'" class="arrow" :class="{ expand: !log.collapsed }" @click="toggleGroupCollapse">▶</div>
35+
<span v-if="log.level === 'group'" class="title">{{ log.label }}</span>
36+
37+
<span v-if="log.level.startsWith('system')">
38+
<template v-for="arg in log.args">
39+
{{ arg }}
40+
</template>
41+
</span>
42+
43+
<JSONNode v-if="log.level === 'table'" :value="log.args[0]" />
44+
<template v-else>
45+
<template v-for="arg in log.args">
46+
<JsonTree :data="arg" />
47+
</template>
48+
</template>
49+
50+
<div
51+
v-for="(_, idx) in new Array(level - 1)"
52+
class="outline"
53+
:style="{ left: idx * 15 + 15 + 'px' }"
54+
/>
55+
</div>
56+
57+
</template>
58+
59+
<style>
60+
.log {
61+
border-bottom: 1px solid #eee;
62+
padding: 5px 10px 0px;
63+
display: flex;
64+
position: relative;
65+
font-size: 12px;
66+
font-family: var(--font-mono);
67+
}
68+
69+
.log > :global(*) {
70+
margin-right: 10px;
71+
font-family: var(--font-mono);
72+
}
73+
74+
.console-warn, .console-system-warn {
75+
background: #fffbe6;
76+
border-color: #fff4c4;
77+
}
78+
79+
.console-error, .console-assert {
80+
background: #fff0f0;
81+
border-color: #fed6d7;
82+
}
83+
84+
.console-group, .arrow {
85+
cursor: pointer;
86+
user-select: none;
87+
}
88+
89+
.console-trace, .console-assert {
90+
border-bottom: none;
91+
}
92+
93+
.console-assert + .trace {
94+
background: #fff0f0;
95+
border-color: #fed6d7;
96+
}
97+
98+
.trace {
99+
border-bottom: 1px solid #eee;
100+
font-size: 12px;
101+
font-family: var(--font-mono);
102+
padding: 4px 0 2px;
103+
}
104+
105+
.trace > :global(div) {
106+
margin-left: 15px;
107+
}
108+
109+
.count {
110+
color: #999;
111+
font-size: 12px;
112+
line-height: 1.2;
113+
}
114+
115+
.info {
116+
color: #666;
117+
font-family: var(--font) !important;
118+
font-size: 12px;
119+
}
120+
121+
.error {
122+
color: #da106e; /* todo make this a var */
123+
}
124+
125+
.outline {
126+
border-left: 1px solid #9c9cab;
127+
position: absolute;
128+
top: 0;
129+
bottom: -1px;
130+
}
131+
132+
.arrow {
133+
position: absolute;
134+
font-size: 0.6em;
135+
transition: 150ms;
136+
transform-origin: 50% 50%;
137+
transform: translateY(1px) translateX(-50%);
138+
}
139+
140+
.arrow.expand {
141+
transform: translateY(1px) translateX(-50%) rotateZ(90deg);
142+
}
143+
144+
.title {
145+
font-family: var(--font-mono);
146+
font-size: 13px;
147+
font-weight: bold;
148+
padding-left: 11px;
149+
height: 19px;
150+
}
151+
152+
.assert {
153+
padding-left: 11px;
154+
font-weight: bold;
155+
color: #da106e;
156+
}
157+
</style>

src/output/JsonNode.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script setup lang="ts">
2+
import { word } from '../utils';
3+
defineProps({
4+
key: {
5+
key: String,
6+
required: true,
7+
},
8+
value: Object as any,
9+
marginOffset: {
10+
type: Number,
11+
default: 0,
12+
},
13+
open: Boolean,
14+
})
15+
</script>
16+
17+
<template>
18+
19+
<div class="tree" :style="`margin-left: ${marginOffset}rem`">
20+
<span v-if="open && key">
21+
<
22+
</span>
23+
<span v-else-if="!open && key">
24+
>
25+
</span>
26+
<span v-if="key" class="tree__title">
27+
{{ key }}:
28+
</span>
29+
<span>
30+
{{typeof value === 'object' && value ? word(Object.keys(value).length, 'property', 'properties') : value}}
31+
</span>
32+
</div>
33+
34+
</template>
35+
36+
<style>
37+
.tree {
38+
color: white;
39+
cursor: 'pointer';
40+
user-select: none;
41+
margin-bottom: 1em;
42+
display: flex;
43+
align-items: center;
44+
gap: 0.2em;
45+
}
46+
47+
.tree__title{
48+
font-weight: 500;
49+
}
50+
51+
.block{
52+
display:block;
53+
}
54+
</style>
55+

src/output/JsonTree.vue

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
<script setup lang="ts">
3+
import { computed, ref, type PropType } from 'vue'
4+
import JsonNode from './JsonNode.vue';
5+
import { parse } from '../utils';
6+
7+
const props = defineProps({
8+
key: {
9+
key: String,
10+
default: 'root',
11+
},
12+
data: Object as PropType<Record<string, any>>,
13+
marginOffset: {
14+
type: Number,
15+
default: 1,
16+
}
17+
})
18+
19+
const open = ref(false)
20+
21+
const entries = computed(() => parse(props.data))
22+
</script>
23+
24+
<template>
25+
<JsonNode :key="key" :value="data" :margin-offset="marginOffset" />
26+
<template v-if="entries && open">
27+
<JsonTree v-for="[k, v] in entries" :key="k" :data="v" :margin-offset="marginOffset + 1" />
28+
</template>
29+
</template>

src/output/Preview.vue

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import SplitPane from '../SplitPane.vue'
23
import Message from '../Message.vue'
34
import {
45
type WatchStopHandle,
@@ -13,7 +14,8 @@ import {
1314
import srcdoc from './srcdoc.html?raw'
1415
import { PreviewProxy } from './PreviewProxy'
1516
import { compileModulesForPreview } from './moduleCompiler'
16-
import { injectKeyProps } from '../../src/types'
17+
import { CommandData, injectKeyProps } from '../../src/types'
18+
import Console from './Console.vue'
1719
1820
const props = defineProps<{ show: boolean; ssr: boolean }>()
1921
@@ -23,6 +25,7 @@ const { store, clearConsole, theme, previewTheme, previewOptions } =
2325
const containerRef = useTemplateRef('container')
2426
const runtimeError = ref<string>()
2527
const runtimeWarning = ref<string>()
28+
const logs = ref<CommandData[]>([])
2629
2730
let sandbox: HTMLIFrameElement
2831
let proxy: PreviewProxy
@@ -146,6 +149,8 @@ function createSandbox() {
146149
.replace(/\[Vue warn\]:/, '')
147150
.trim()
148151
}
152+
} else {
153+
push_logs(log)
149154
}
150155
},
151156
on_console_group: (action: any) => {
@@ -159,6 +164,11 @@ function createSandbox() {
159164
},
160165
})
161166
167+
function push_logs(log: CommandData) {
168+
//current_log_group.push(last_console_event = log);
169+
logs.value.push(log)
170+
}
171+
162172
sandbox.addEventListener('load', () => {
163173
proxy.handle_links()
164174
stopUpdateWatcher = watchEffect(updatePreview)
@@ -285,17 +295,24 @@ defineExpose({ reload, container: containerRef })
285295
</script>
286296

287297
<template>
288-
<div
289-
v-show="show"
290-
ref="container"
291-
class="iframe-container"
292-
:class="{ [theme]: previewTheme }"
293-
/>
294-
<Message :err="(previewOptions?.showRuntimeError ?? true) && runtimeError" />
295-
<Message
296-
v-if="!runtimeError && (previewOptions?.showRuntimeWarning ?? true)"
297-
:warn="runtimeWarning"
298-
/>
298+
<SplitPane layout="vertical">
299+
<template #left>
300+
<div
301+
v-show="show"
302+
ref="container"
303+
class="iframe-container"
304+
:class="{ [theme]: previewTheme }"
305+
/>
306+
<Message :err="(previewOptions?.showRuntimeError ?? true) && runtimeError" />
307+
<Message
308+
v-if="!runtimeError && (previewOptions?.showRuntimeWarning ?? true)"
309+
:warn="runtimeWarning"
310+
/>
311+
</template>
312+
<template #right>
313+
<Console :logs="logs" />
314+
</template>
315+
</SplitPane>
299316
</template>
300317

301318
<style scoped>

src/output/PreviewProxy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export class PreviewProxy {
3030
return new Promise((resolve, reject) => {
3131
const cmd_id = uid++
3232

33+
//console.log('resolve', resolve)
34+
//console.log('reject', reject)
3335
this.pending_cmds.set(cmd_id, { resolve, reject })
3436

3537
this.iframe.contentWindow!.postMessage({ action, cmd_id, args }, '*')

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ export interface EditorProps {
88
readonly?: boolean
99
mode?: EditorMode
1010
}
11+
export type ConsoleLevel = 'clear' | 'log' | 'info' | 'dir' | 'warn' | 'error' | 'table' | 'group' | 'unclonable' | 'assert'
12+
export interface CommandData{
13+
action: 'console' | 'cmd_error' | 'cmd_ok' | 'error' | 'unhandledrejection'
14+
args: string[]
15+
level: ConsoleLevel
16+
stack?: string
17+
label?: string
18+
count?: number
19+
}
1120
export interface EditorEmits {
1221
(e: 'change', code: string): void
1322
}

src/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,16 @@ export function atou(base64: string): string {
3131
// https://base64.guru/developers/javascript/examples/unicode-strings
3232
return decodeURIComponent(escape(binary))
3333
}
34+
35+
export function parse(value: any = false) {
36+
value = value ?? false
37+
if (typeof value === 'object') {
38+
return Object.entries(value) as [string, any][]
39+
}
40+
return false
41+
}
42+
43+
export function word(total: number, singular: string, plural: string) {
44+
const choose = total > 1 ? plural : singular
45+
return `${total} ${choose}`
46+
}

0 commit comments

Comments
 (0)