Skip to content

Commit fafd3ed

Browse files
authored
feat: add readonly mode (#6)
* feat: add readonly prop to Repl * chore: add watcher for readonly to update editor options * refactor: merge multiple watchers into one watchEffect
1 parent 9bd878d commit fafd3ed

File tree

5 files changed

+49
-63
lines changed

5 files changed

+49
-63
lines changed

src/Repl.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface Props {
1919
sfcOptions?: SFCOptions
2020
layout?: 'horizontal' | 'vertical'
2121
ssr?: boolean
22+
readonly?: boolean
2223
previewOptions?: {
2324
headHTML?: string
2425
bodyHTML?: string
@@ -38,6 +39,7 @@ const props = withDefaults(defineProps<Props>(), {
3839
showTsConfig: true,
3940
clearConsole: true,
4041
ssr: false,
42+
readonly: false,
4143
previewOptions: () => ({
4244
headHTML: '',
4345
bodyHTML: '',
@@ -79,6 +81,7 @@ provide('tsconfig', toRef(props, 'showTsConfig'))
7981
provide('clear-console', toRef(props, 'clearConsole'))
8082
provide('preview-options', props.previewOptions)
8183
provide('theme', toRef(props, 'theme'))
84+
provide('readonly', toRef(props, 'readonly'))
8285
/**
8386
* Reload the preview iframe
8487
*/
@@ -128,9 +131,8 @@ defineExpose({ reload })
128131
margin: 0;
129132
overflow: hidden;
130133
font-size: 13px;
131-
font-family:
132-
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
133-
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
134+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
135+
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
134136
background-color: var(--bg-soft);
135137
}
136138
</style>

src/editor/EditorContainer.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ const props = defineProps<{
1616
}>()
1717
1818
const store = inject('store') as Store
19+
const readonly = inject('readonly', ref(false))
1920
const showMessage = ref((getItem(SHOW_ERROR_KEY) ?? 'true') === 'true')
2021
store.state.wordWrap = (getItem(TOGGLE_WRAP_KEY) ?? 'false') === 'true'
2122
2223
const onChange = debounce((code: string, filename: string) => {
24+
if (readonly.value) return
2325
store.state.files[filename].code = code
2426
}, 250)
2527
@@ -50,6 +52,7 @@ watch(
5052
@change="onChange($event, store.state.activeFile.filename)"
5153
:value="store.state.activeFile.code"
5254
:filename="store.state.activeFile.filename"
55+
:readonly="readonly"
5356
/>
5457
<Message v-show="showMessage" :err="store.state.errors[0]" />
5558
<MessageToggle v-model="showMessage" />

src/editor/FileExplorer.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<template v-slot:append>
1414
<v-btn
15+
v-if="!readonly"
1516
variant="text"
1617
size="small"
1718
:append-icon="`svg:${mdiFilePlusOutline}`"
@@ -50,6 +51,7 @@
5051

5152
<template v-slot:append>
5253
<v-icon-btn
54+
v-if="!readonly"
5355
icon="$close"
5456
size="26"
5557
icon-size="14"
@@ -60,7 +62,7 @@
6062
</v-list-item>
6163

6264
<v-text-field
63-
v-if="pending"
65+
v-if="pending && !readonly"
6466
v-model="pendingFilename"
6567
density="compact"
6668
hide-details
@@ -117,7 +119,7 @@
117119

118120
<script setup lang="ts">
119121
import type { Store } from 'src/store'
120-
import { inject } from 'vue'
122+
import { inject, ref } from 'vue'
121123
import { useFileSelector } from '../composables/useFileSelector'
122124
import { VIconBtn } from 'vuetify/labs/components'
123125
import {
@@ -129,6 +131,7 @@ import {
129131
} from '@mdi/js'
130132
131133
const store = inject('store') as Store
134+
const readonly = inject('readonly', ref(false))
132135
133136
const {
134137
activeFile,

src/editor/FileSelector.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</template>
3434
<span>{{ stripSrcPrefix(file) }}</span>
3535
<v-icon-btn
36-
v-if="recentFiles.length > 1"
36+
v-if="recentFiles.length > 1 && !readonly"
3737
icon="$close"
3838
size="20px"
3939
icon-size="15px"
@@ -57,6 +57,7 @@ import { useDisplay } from 'vuetify'
5757
const MAX_RECENT_FILES = 10
5858
5959
const store = inject('store') as Store
60+
const readonly = inject('readonly', ref(false))
6061
const { mdAndDown } = useDisplay()
6162
const { activeFile, stripSrcPrefix, getFileIcon, getFileIconColor } =
6263
useFileSelector()

src/monaco/Monaco.vue

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
watch,
1010
computed,
1111
type Ref,
12+
watchEffect,
1213
} from 'vue'
1314
import * as monaco from 'monaco-editor-core'
1415
import { initMonaco } from './env'
@@ -44,7 +45,6 @@ const store = inject<Store>('store')!
4445
4546
initMonaco(store)
4647
47-
const lang = computed(() => (props.mode === 'css' ? 'css' : 'javascript'))
4848
const extension = computed(() => props.filename.split('.').at(-1))
4949
5050
const replTheme = inject<Ref<'dark' | 'light'>>('theme')!
@@ -58,9 +58,6 @@ onMounted(async () => {
5858
}
5959
6060
const editorInstance = monaco.editor.create(containerRef.value, {
61-
...(props.readonly
62-
? { value: props.value, language: lang.value }
63-
: { model: null }),
6461
fontSize: 13,
6562
theme: replTheme.value === 'light' ? theme.light : theme.dark,
6663
readOnly: props.readonly,
@@ -100,55 +97,42 @@ onMounted(async () => {
10097
}
10198
}
10299
103-
watch(
104-
() => props.value,
105-
(value) => {
106-
if (editorInstance.getValue() === value) return
107-
editorInstance.setValue(value || '')
108-
},
109-
{ immediate: true }
110-
)
100+
watchEffect(() => {
101+
if (editorInstance.getValue() !== props.value)
102+
editorInstance.setValue(props.value || '')
111103
112-
watch(lang, (lang) =>
113-
monaco.editor.setModelLanguage(editorInstance.getModel()!, lang)
114-
)
115-
116-
if (!props.readonly) {
117-
watch(
118-
() => props.filename,
119-
(_, oldFilename) => {
120-
if (!editorInstance) return
121-
const file = store.state.files[props.filename]
122-
if (!file) return null
123-
const model = getOrCreateModel(
124-
monaco.Uri.parse(`file:///${props.filename}`),
125-
file.language,
126-
file.code
127-
)
128-
129-
const oldFile = oldFilename ? store.state.files[oldFilename] : null
130-
if (oldFile) {
131-
oldFile.editorViewState = editorInstance.saveViewState()
132-
}
133-
134-
editorInstance.setModel(model)
135-
136-
if (file.editorViewState) {
137-
editorInstance.restoreViewState(file.editorViewState)
138-
editorInstance.focus()
139-
}
140-
},
141-
{ immediate: true }
142-
)
143-
}
104+
editorInstance.updateOptions({
105+
readOnly: props.readonly,
106+
wordWrap: store.state.wordWrap ? 'on' : 'off',
107+
theme: replTheme.value === 'light' ? theme.light : theme.dark,
108+
})
109+
})
144110
145111
watch(
146-
() => store.state.wordWrap,
147-
() => {
148-
editorInstance.updateOptions({
149-
wordWrap: store.state.wordWrap ? 'on' : 'off',
150-
})
151-
}
112+
() => props.filename,
113+
(_, oldFilename) => {
114+
if (!editorInstance) return
115+
const file = store.state.files[props.filename]
116+
if (!file) return null
117+
const model = getOrCreateModel(
118+
monaco.Uri.parse(`file:///${props.filename}`),
119+
file.language,
120+
file.code
121+
)
122+
123+
const oldFile = oldFilename ? store.state.files[oldFilename] : null
124+
if (oldFile) {
125+
oldFile.editorViewState = editorInstance.saveViewState()
126+
}
127+
128+
editorInstance.setModel(model)
129+
130+
if (file.editorViewState) {
131+
editorInstance.restoreViewState(file.editorViewState)
132+
editorInstance.focus()
133+
}
134+
},
135+
{ immediate: true }
152136
)
153137
154138
await loadGrammars(monaco, editorInstance)
@@ -197,13 +181,6 @@ onMounted(async () => {
197181
emit('change', code)
198182
}
199183
})
200-
201-
// update theme
202-
watch(replTheme, (n) => {
203-
editorInstance.updateOptions({
204-
theme: n === 'light' ? theme.light : theme.dark,
205-
})
206-
})
207184
})
208185
209186
onBeforeUnmount(() => {

0 commit comments

Comments
 (0)