Skip to content

Commit 06da467

Browse files
committed
feat: integrate Monaco Editor for enhanced code editing experience and add dark mode support
1 parent 37c087e commit 06da467

File tree

6 files changed

+169
-19
lines changed

6 files changed

+169
-19
lines changed

frontend/package-lock.json

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

frontend/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
"format": "prettier --write src/"
1919
},
2020
"dependencies": {
21-
"@tailwindcss/vite": "^4.1.16",
21+
"@guolao/vue-monaco-editor": "^1.6.0",
2222
"@mdi/font": "^7.4.47",
23+
"@tailwindcss/vite": "^4.1.16",
2324
"date-fns": "^4.1.0",
2425
"pinia": "^3.0.3",
2526
"tailwindcss": "^4.1.16",
@@ -39,14 +40,15 @@
3940
"eslint-plugin-oxlint": "~1.23.0",
4041
"eslint-plugin-vue": "~10.5.0",
4142
"jiti": "^2.6.1",
43+
"monaco-editor": "^0.54.0",
4244
"npm-run-all2": "^8.0.4",
4345
"oxlint": "~1.23.0",
4446
"prettier": "3.6.2",
45-
"typescript": "~5.9.0",
4647
"sass": "^1.79.0",
48+
"typescript": "~5.9.0",
4749
"vite": "npm:rolldown-vite@latest",
48-
"vite-plugin-vuetify": "^2.0.3",
4950
"vite-plugin-vue-devtools": "^8.0.3",
51+
"vite-plugin-vuetify": "^2.0.3",
5052
"vue-tsc": "^3.1.1"
5153
}
5254
}

frontend/src/App.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
<script setup lang="ts">
22
import { computed, ref } from 'vue'
33
import { useTheme } from 'vuetify'
4+
import { useApiStore } from './stores/apiStore'
45
56
const theme = useTheme()
67
// Initialize from system preference
78
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
89
theme.global.name.value = 'dark'
10+
useApiStore().darkMode = theme.global.name.value === 'dark'
911
}
1012
// Listen to system changes
1113
const media = window.matchMedia?.('(prefers-color-scheme: dark)')
1214
media?.addEventListener('change', (e) => {
1315
theme.global.name.value = e.matches ? 'dark' : 'light'
16+
useApiStore().darkMode = theme.global.name.value === 'dark'
1417
})
1518
function toggleTheme() {
1619
theme.global.name.value = theme.global.name.value === 'dark' ? 'light' : 'dark'
20+
useApiStore().darkMode = theme.global.name.value === 'dark'
1721
}
1822
// Full inbound URL (proxied in dev, same origin in prod)
1923
const inboundUrl = computed(() => `${location.origin}/inbound`)

frontend/src/main.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import router from './router'
77
import 'vuetify/styles'
88
import { createVuetify } from 'vuetify'
99
import { aliases, mdi } from 'vuetify/iconsets/mdi'
10+
import { install as VueMonacoEditorPlugin } from '@guolao/vue-monaco-editor'
1011
import '@mdi/font/css/materialdesignicons.css'
1112

1213
const vuetify = createVuetify({
@@ -23,11 +24,14 @@ const vuetify = createVuetify({
2324
sets: { mdi },
2425
},
2526
})
26-
2727
const app = createApp(App)
2828

2929
app.use(createPinia())
3030
app.use(router)
3131
app.use(vuetify)
32-
32+
app.use(VueMonacoEditorPlugin, {
33+
paths: {
34+
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs',
35+
},
36+
})
3337
app.mount('#app')

frontend/src/stores/apiStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const useApiStore = defineStore('api', () => {
5757
const isLoadingList = ref(false)
5858
const selectedLoadingId = ref<number | null>(null)
5959
const isWsConnected = ref(false)
60+
const darkMode = ref(false)
6061

6162
async function updateRequestList() {
6263
console.log('[api] updateRequestList: start')
@@ -208,5 +209,6 @@ export const useApiStore = defineStore('api', () => {
208209
isLoadingList,
209210
selectedLoadingId,
210211
isWsConnected,
212+
darkMode,
211213
}
212214
})

frontend/src/views/HomeView.vue

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
2-
import { ref, computed, watch } from 'vue'
2+
import { ref, computed, watch, shallowRef } from 'vue'
33
import { useApiStore } from '@/stores/apiStore'
4+
import type * as monaco from 'monaco-editor'
45
import { formatISO9075 } from 'date-fns'
56
const apiStore = useApiStore()
67
apiStore.updateRequestList()
@@ -10,6 +11,23 @@ apiStore.connectWS()
1011
const headersOpen = ref(true)
1112
const headerCount = computed(() => Object.keys(apiStore.selectedRequest?.headers || {}).length)
1213
14+
const MONACO_EDITOR_OPTIONS = {
15+
automaticLayout: true,
16+
}
17+
// watch(
18+
// () => apiStore.darkMode,
19+
// (newVal) => {
20+
// console.log('Theme changed:', newVal)
21+
// if (monacoIns.value) {
22+
// console.log(monacoIns.value)
23+
// ;(monacoIns.value as any).editor.setTheme(newVal ? 'vs-dark' : 'vs')
24+
// }
25+
// // if (editor.value) {
26+
// // editor.value.updateOptions({ theme: newVal ? 'vs-dark' : 'vs' })
27+
// // }
28+
// },
29+
// )
30+
1331
// Authorization reveal toggle
1432
const showAuth = ref(false)
1533
watch(
@@ -59,6 +77,37 @@ function decodeBasicAuth(value: string): { isBasic: boolean; user?: string; pass
5977
return { isBasic: false }
6078
}
6179
}
80+
81+
const editor = shallowRef<monaco.editor.IStandaloneCodeEditor>()
82+
const monacoIns = shallowRef<unknown>()
83+
function handleMount(monacoEditor: monaco.editor.IStandaloneCodeEditor, monacoInstance: unknown) {
84+
editor.value = monacoEditor
85+
monacoIns.value = monacoInstance
86+
}
87+
88+
// Derive Monaco language from Content-Type header
89+
function detectLanguageFromContentType(ct?: string | null): string {
90+
if (!ct) return 'plaintext'
91+
const base = ct.split(';')[0]?.trim().toLowerCase() || ''
92+
// Broad matches first
93+
if (base.includes('json')) return 'json'
94+
if (base.includes('xml')) return 'xml'
95+
if (base.includes('html')) return 'html'
96+
if (base.includes('markdown')) return 'markdown'
97+
if (base.includes('yaml') || base.endsWith('/yml')) return 'yaml'
98+
if (base.includes('javascript') || base.endsWith('/js')) return 'javascript'
99+
if (base.includes('typescript') || base.endsWith('/ts')) return 'typescript'
100+
if (base.includes('css')) return 'css'
101+
if (base.includes('sql')) return 'sql'
102+
if (base.includes('form-urlencoded')) return 'plaintext'
103+
if (base.startsWith('text/')) return 'plaintext'
104+
return 'plaintext'
105+
}
106+
107+
const monacoLanguage = computed(() => {
108+
const ct = apiStore.selectedRequest?.headers?.['content-type']
109+
return detectLanguageFromContentType(ct)
110+
})
62111
</script>
63112

64113
<template>
@@ -149,7 +198,7 @@ function decodeBasicAuth(value: string): { isBasic: boolean; user?: string; pass
149198
</v-card>
150199
</div>
151200
<!-- Main Content -->
152-
<div class="flex-1 p-4 pane-scroll">
201+
<div class="flex-1 p-4" style="height: 100%">
153202
<v-fade-transition v-if="apiStore.selectedRequest" mode="out-in">
154203
<v-card
155204
density="compact"
@@ -426,25 +475,22 @@ function decodeBasicAuth(value: string): { isBasic: boolean; user?: string; pass
426475
>
427476
</template>
428477
</p>
478+
429479
<v-sheet
430480
:elevation="2"
431481
border
432482
rounded
433483
class="font-mono pa-2 text-xs rounded mb-2"
434484
tag="pre"
435-
>{{ apiStore.selectedRequest.body_text }}</v-sheet
436485
>
437-
<template v-if="apiStore.selectedRequest.body_bytes_b64">
438-
<p><strong>Body (Base64):</strong></p>
439-
<v-sheet
440-
:elevation="2"
441-
border
442-
rounded
443-
class="font-mono pa-2 text-xs rounded mb-2"
444-
tag="pre"
445-
>{{ apiStore.selectedRequest.body_bytes_b64 }}</v-sheet
446-
>
447-
</template>
486+
<vue-monaco-editor
487+
v-model:value="apiStore.selectedRequest.body_text"
488+
:theme="apiStore.darkMode ? 'vs-dark' : 'vs'"
489+
:language="monacoLanguage"
490+
:options="MONACO_EDITOR_OPTIONS"
491+
style="min-height: 300px"
492+
@mount="handleMount"
493+
/></v-sheet>
448494
</template>
449495
</div>
450496
</v-card>

0 commit comments

Comments
 (0)