Skip to content

Commit 02909b6

Browse files
authored
#15: Console integration (#33)
* #15: Starting to integrate luna-console Modifying the vue-repl based on vuejs/repl#333 * Missing file. * fix: Hiding container for controls when none of them are presented. * #15: Serializing and rebuilding the messages. This enables elements to be rendered, for example. * Updating lock file * Updating lock file once more * #15: Adding some configs to show and hide the console and to display horizontally/vertically within the preview * #15 #28 Adding toggles for the different layout configs For some reason I had to rename LunaConsole.vue to luna-console.vue or vite would lose my luna-console.css and break the built component (not the build process, but the dist files got messed up and yv would not load it). * #15: Adding some animations Working on some styles too * #15: Making luna console theme reactive * #15: Actually making luna console theme reactive this time * Small tweak on the history snapshots * chore: bump version to 0.2.0-beta * Fixing variation in the position for the connected/disconnected indicator It was driving me mad. * #28: Adding a toggle for reverse layout Abstracting the layout menu into its own component * #28: WIP fixing mobile output layout * #28: Fixing resizing for output in horizontal mode * Using more specific class names to preserve resizing on horizontal layout even after building the package
1 parent ac142b3 commit 02909b6

File tree

25 files changed

+1212
-277
lines changed

25 files changed

+1212
-277
lines changed

packages/vue-repl/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"import": "./dist/codemirror-editor.js",
2727
"require": null
2828
},
29+
"./luna-console": {
30+
"types": "./dist/luna-console.d.ts",
31+
"import": "./dist/luna-console.js",
32+
"require": null
33+
},
2934
"./core": {
3035
"types": "./dist/core.d.ts",
3136
"import": "./dist/core.js",
@@ -103,7 +108,12 @@
103108
"eslint-plugin-vue": "^10.4.0",
104109
"fflate": "^0.8.2",
105110
"hash-sum": "^2.0.0",
111+
"licia": "^1.48.0",
106112
"lint-staged": "^16.1.4",
113+
"luna-console": "^1.3.5",
114+
"luna-data-grid": "^1.3.0",
115+
"luna-dom-viewer": "^1.8.3",
116+
"luna-object-viewer": "^0.3.1",
107117
"monaco-editor-core": "0.52.2",
108118
"prettier": "^3.6.2",
109119
"shiki": "^3.9.2",
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<script setup lang="ts">
2+
import { computed, inject, reactive, useTemplateRef } from 'vue'
3+
import { injectKeyPreviewRef, injectKeyProps } from './types'
4+
5+
const props = defineProps<{ layout?: 'horizontal' | 'vertical' }>()
6+
const isVertical = computed(() => props.layout === 'vertical')
7+
8+
const containerRef = useTemplateRef('container')
9+
const previewRef = inject(injectKeyPreviewRef)!
10+
11+
const { store, layoutReverse, splitPaneOptions } = inject(injectKeyProps)!
12+
13+
const state = reactive({
14+
dragging: false,
15+
split: 50,
16+
viewHeight: 0,
17+
viewWidth: 0,
18+
})
19+
20+
const boundSplit = computed(() => {
21+
const { split } = state
22+
return split < 20 ? 20 : split > 80 ? 80 : split
23+
})
24+
25+
let startPosition = 0
26+
let startSplit = 0
27+
28+
function dragStart(e: MouseEvent) {
29+
state.dragging = true
30+
startPosition = isVertical.value ? e.pageY : e.pageX
31+
startSplit = boundSplit.value
32+
33+
changeViewSize()
34+
}
35+
36+
function dragMove(e: MouseEvent) {
37+
if (containerRef.value && state.dragging) {
38+
const position = isVertical.value ? e.pageY : e.pageX
39+
const totalSize = isVertical.value
40+
? containerRef.value.offsetHeight
41+
: containerRef.value.offsetWidth
42+
const dp = position - startPosition
43+
state.split = startSplit + +((dp / totalSize) * 100).toFixed(2)
44+
45+
changeViewSize()
46+
}
47+
}
48+
49+
function dragEnd() {
50+
state.dragging = false
51+
}
52+
53+
function changeViewSize() {
54+
const el = previewRef.value
55+
if (!el) return
56+
state.viewHeight = el.offsetHeight
57+
state.viewWidth = el.offsetWidth
58+
}
59+
</script>
60+
61+
<template>
62+
<div
63+
ref="container" class="output-split-pane" :class="{
64+
'osp-dragging': state.dragging,
65+
reverse: layoutReverse,
66+
vertical: isVertical,
67+
}" @mousemove="dragMove" @mouseup="dragEnd" @mouseleave="dragEnd"
68+
>
69+
<div class="first" :style="{ [isVertical ? 'height' : 'width']: boundSplit + '%' }">
70+
<slot name="first" />
71+
<div class="dragger" @mousedown.prevent="dragStart" />
72+
</div>
73+
<div class="second" :style="{ [isVertical ? 'height' : 'width']: 100 - boundSplit + '%' }">
74+
<div v-show="state.dragging" class="view-size">
75+
{{ `${state.viewWidth}px x ${state.viewHeight}px` }}
76+
</div>
77+
<slot name="second" />
78+
</div>
79+
80+
<button class="toggler" @click="store.showOutput = !store.showOutput">
81+
{{
82+
store.showOutput
83+
? splitPaneOptions?.codeTogglerText || '< Code' : splitPaneOptions?.outputTogglerText || 'Output >' }}
84+
</button>
85+
</div>
86+
</template>
87+
88+
<style scoped>
89+
.output-split-pane {
90+
display: flex;
91+
flex-direction: row;
92+
height: 100%;
93+
position: relative;
94+
}
95+
96+
.output-split-pane.osp-dragging {
97+
cursor: ew-resize;
98+
}
99+
100+
.osp-dragging .first,
101+
.osp-dragging .second {
102+
pointer-events: none;
103+
}
104+
105+
.first,
106+
.second {
107+
position: relative;
108+
height: 100%;
109+
}
110+
111+
.view-size {
112+
position: absolute;
113+
top: 40px;
114+
left: 10px;
115+
font-size: 12px;
116+
color: var(--text-light);
117+
z-index: 100;
118+
background-color: var(--bg-soft);
119+
padding: 0.5rem;
120+
border-radius: 4px;
121+
}
122+
123+
.first {
124+
border-right: 1px solid var(--border);
125+
}
126+
127+
.dragger {
128+
position: absolute;
129+
z-index: 3;
130+
}
131+
132+
.vertical .dragger {
133+
top: auto;
134+
height: 10px;
135+
width: 100%;
136+
left: 0;
137+
right: 0;
138+
bottom: -5px;
139+
cursor: ns-resize;
140+
}
141+
142+
.toggler {
143+
display: none;
144+
z-index: 3;
145+
font-family: var(--font-code);
146+
color: var(--text-light);
147+
position: absolute;
148+
left: 50%;
149+
bottom: 20px;
150+
background-color: var(--bg);
151+
padding: 8px 12px;
152+
border-radius: 8px;
153+
transform: translateX(-50%);
154+
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.25);
155+
}
156+
157+
.dark .toggler {
158+
background-color: var(--bg);
159+
}
160+
161+
162+
.output-split-pane:not(.vertical) .dragger {
163+
top: 0;
164+
bottom: 0;
165+
left: auto;
166+
right: -5px;
167+
width: 10px;
168+
height: 100%;
169+
cursor: ew-resize;
170+
}
171+
172+
.output-split-pane.vertical {
173+
flex-direction: column;
174+
}
175+
176+
.output-split-pane.vertical.osp-dragging {
177+
cursor: ns-resize;
178+
}
179+
180+
.vertical .first,
181+
.vertical .second {
182+
width: 100%;
183+
}
184+
185+
.vertical .first {
186+
border-right: none;
187+
border-bottom: 1px solid var(--border);
188+
}
189+
</style>

packages/vue-repl/src/Repl.vue

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,34 @@ import Output from './output/Output.vue'
44
import { type Store, useStore } from './store'
55
import { computed, provide, toRefs, useTemplateRef } from 'vue'
66
import {
7+
type ConsoleComponentType,
8+
9+
710
type EditorComponentType,
811
injectKeyPreviewRef,
912
injectKeyProps,
1013
} from './types'
1114
import EditorContainer from './editor/EditorContainer.vue'
1215
import type * as monaco from 'monaco-editor-core'
16+
import LunaConsole from './output/luna-console.vue'
1317
1418
export interface Props {
1519
theme?: 'dark' | 'light'
1620
previewTheme?: boolean
1721
editor: EditorComponentType
22+
console?: ConsoleComponentType
1823
store?: Store
1924
autoResize?: boolean
2025
showCompileOutput?: boolean
2126
showOpenSourceMap?: boolean
2227
showImportMap?: boolean
2328
showSsrOutput?: boolean
2429
showTsConfig?: boolean
30+
showConsole?: boolean
2531
clearConsole?: boolean
32+
showOutput?: boolean
2633
layout?: 'horizontal' | 'vertical'
34+
outputLayout?: 'horizontal' | 'vertical'
2735
layoutReverse?: boolean
2836
ssr?: boolean
2937
previewOptions?: {
@@ -53,16 +61,20 @@ const props = withDefaults(defineProps<Props>(), {
5361
theme: 'light',
5462
previewTheme: false,
5563
store: () => useStore(),
64+
console: LunaConsole,
5665
autoResize: true,
5766
showCompileOutput: true,
5867
showOpenSourceMap: false,
5968
showImportMap: true,
6069
showSsrOutput: false,
6170
showTsConfig: true,
71+
showOutput: true,
72+
showConsole: false,
6273
clearConsole: true,
6374
layoutReverse: false,
6475
ssr: false,
6576
layout: 'horizontal',
77+
outputLayout: 'vertical',
6678
previewOptions: () => ({}),
6779
editorOptions: () => ({}),
6880
splitPaneOptions: () => ({}),
@@ -72,6 +84,10 @@ if (!props.editor) {
7284
throw new Error('The "editor" prop is now required.')
7385
}
7486
87+
const consoleWrapper = computed<ConsoleComponentType>(
88+
() => props.console ?? (() => ({})),
89+
)
90+
7591
const outputRef = useTemplateRef('output')
7692
7793
props.store.init()
@@ -81,6 +97,9 @@ const outputSlotName = computed(() => (props.layoutReverse ? 'left' : 'right'))
8197
8298
provide(injectKeyProps, {
8399
...toRefs(props),
100+
console: consoleWrapper,
101+
102+
84103
autoSave,
85104
})
86105
provide(
@@ -100,21 +119,26 @@ defineExpose({ reload })
100119

101120
<template>
102121
<div class="vue-repl">
103-
<SplitPane :layout="layout">
122+
<SplitPane v-if="showOutput" :layout="layout">
104123
<template #[editorSlotName]>
105124
<EditorContainer :editor-component="editor" />
106125
</template>
107126
<template #[outputSlotName]>
108127
<Output
109128
ref="output"
110129
:editor-component="editor"
130+
:console-component="consoleWrapper"
131+
:show-output="props.showOutput"
132+
:layout="outputLayout"
111133
:show-compile-output="props.showCompileOutput"
112134
:show-open-source-map="props.showOpenSourceMap"
113135
:show-ssr-output="props.showSsrOutput"
114136
:ssr="!!props.ssr"
115137
/>
116138
</template>
117139
</SplitPane>
140+
141+
<EditorContainer v-else :editor-component="editor" />
118142
</div>
119143
</template>
120144

packages/vue-repl/src/SplitPane.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ function changeViewSize() {
125125
font-size: 12px;
126126
color: var(--text-light);
127127
z-index: 100;
128+
background-color: var(--bg-soft);
129+
padding: 0.5rem;
130+
border-radius: 4px;
128131
}
129132
.left {
130133
border-right: 1px solid var(--border);

packages/vue-repl/src/editor/EditorContainer.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ watch(showMessage, () => {
4343
/>
4444
<Message v-show="showMessage" :err="store.errors[0]" />
4545

46-
<div class="editor-floating">
46+
<div
47+
v-show="editorOptions?.showErrorText !== false || editorOptions?.autoSaveText !== false"
48+
class="editor-floating"
49+
>
4750
<ToggleButton
4851
v-if="editorOptions?.showErrorText !== false"
4952
v-model="showMessage"

packages/vue-repl/src/output/Output.vue

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,26 @@ import Preview from './Preview.vue'
33
import SsrOutput from './SsrOutput.vue'
44
import { computed, inject, useTemplateRef, watchEffect } from 'vue'
55
import {
6+
type ConsoleComponentType,
67
type EditorComponentType,
78
type OutputModes,
89
injectKeyProps,
910
} from '../types'
11+
import OutputSplitPane from '../OutputSplitPane.vue'
1012
1113
const props = defineProps<{
1214
editorComponent: EditorComponentType
15+
consoleComponent: ConsoleComponentType
1316
showCompileOutput?: boolean
1417
showOpenSourceMap?: boolean
1518
showSsrOutput?: boolean
1619
ssr: boolean
20+
showOutput: boolean
21+
layout: 'horizontal' | 'vertical'
22+
outputLayout?: 'horizontal' | 'vertical'
1723
}>()
1824
19-
const { store } = inject(injectKeyProps)!
25+
const { store, showConsole } = inject(injectKeyProps)!
2026
const previewRef = useTemplateRef('preview')
2127
const modes = computed(() => {
2228
const outputModes: OutputModes[] = ['preview']
@@ -51,6 +57,9 @@ function openSourceMap() {
5157
5258
function reload() {
5359
previewRef.value?.reload()
60+
store.value.clearConsole?.()
61+
62+
5463
}
5564
5665
defineExpose({ reload, previewRef })
@@ -69,7 +78,15 @@ defineExpose({ reload, previewRef })
6978
</div>
7079

7180
<div class="output-container">
72-
<Preview ref="preview" :show="mode === 'preview'" :ssr="ssr" />
81+
<OutputSplitPane v-if="showConsole" :layout="props.outputLayout || layout">
82+
<template #first>
83+
<Preview ref="preview" :show="mode === 'preview'" :ssr="ssr" />
84+
</template>
85+
<template #second>
86+
<props.consoleComponent />
87+
</template>
88+
</OutputSplitPane>
89+
<Preview v-else ref="preview" :show="mode === 'preview'" :ssr="ssr" />
7390
<SsrOutput
7491
v-if="mode === 'ssr output'"
7592
:context="store.ssrOutput.context"

0 commit comments

Comments
 (0)