177 lines
3.9 KiB
Vue
177 lines
3.9 KiB
Vue
<template>
|
|
<div
|
|
class="editor flex flex-col gap-1"
|
|
:style="{
|
|
height: height,
|
|
}"
|
|
>
|
|
<span class="text-xs text-ink-gray-7 mb-1" v-if="label">
|
|
{{ label }}
|
|
</span>
|
|
<div
|
|
ref="editor"
|
|
class="h-auto flex-1 overflow-hidden overscroll-none !rounded border border-outline-gray-2 bg-surface-gray-2 dark:bg-gray-900"
|
|
/>
|
|
<span
|
|
class="mt-1 text-xs text-ink-gray-5"
|
|
v-show="description"
|
|
v-html="description"
|
|
></span>
|
|
<Button
|
|
v-if="showSaveButton"
|
|
@click="emit('save', aceEditor?.getValue())"
|
|
class="mt-3"
|
|
>
|
|
{{ __('Save') }}
|
|
</Button>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import ace from 'ace-builds'
|
|
import 'ace-builds/src-min-noconflict/ext-searchbox'
|
|
import 'ace-builds/src-min-noconflict/theme-chrome'
|
|
import 'ace-builds/src-min-noconflict/theme-twilight'
|
|
import { PropType, onMounted, ref, watch } from 'vue'
|
|
import { Button } from 'frappe-ui'
|
|
|
|
const isDark = ref(false)
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: [Object, String, Array],
|
|
},
|
|
type: {
|
|
type: String as PropType<'JSON' | 'HTML' | 'Python' | 'JavaScript' | 'CSS'>,
|
|
default: 'JSON',
|
|
},
|
|
label: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
height: {
|
|
type: String,
|
|
default: '250px',
|
|
},
|
|
showLineNumbers: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
autofocus: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
showSaveButton: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
description: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['save', 'update:modelValue'])
|
|
const editor = ref<HTMLElement | null>(null)
|
|
let aceEditor = null as ace.Ace.Editor | null
|
|
|
|
onMounted(() => {
|
|
isDark.value = localStorage.getItem('theme') === 'dark'
|
|
setupEditor()
|
|
})
|
|
|
|
const setupEditor = () => {
|
|
aceEditor = ace.edit(editor.value as HTMLElement)
|
|
resetEditor(props.modelValue as string, true)
|
|
aceEditor.setReadOnly(props.readonly)
|
|
aceEditor.setOptions({
|
|
fontSize: '12px',
|
|
useWorker: false,
|
|
showGutter: props.showLineNumbers,
|
|
wrap: props.showLineNumbers,
|
|
})
|
|
if (props.type === 'CSS') {
|
|
import('ace-builds/src-noconflict/mode-css').then(() => {
|
|
aceEditor?.session.setMode('ace/mode/css')
|
|
})
|
|
} else if (props.type === 'JavaScript') {
|
|
import('ace-builds/src-noconflict/mode-javascript').then(() => {
|
|
aceEditor?.session.setMode('ace/mode/javascript')
|
|
})
|
|
} else if (props.type === 'Python') {
|
|
import('ace-builds/src-noconflict/mode-python').then(() => {
|
|
aceEditor?.session.setMode('ace/mode/python')
|
|
})
|
|
} else if (props.type === 'JSON') {
|
|
import('ace-builds/src-noconflict/mode-json').then(() => {
|
|
aceEditor?.session.setMode('ace/mode/json')
|
|
})
|
|
} else {
|
|
import('ace-builds/src-noconflict/mode-html').then(() => {
|
|
aceEditor?.session.setMode('ace/mode/html')
|
|
})
|
|
}
|
|
aceEditor.on('blur', () => {
|
|
try {
|
|
let value = aceEditor?.getValue() || ''
|
|
if (props.type === 'JSON') {
|
|
value = JSON.parse(value)
|
|
}
|
|
if (value === props.modelValue) return
|
|
if (!props.showSaveButton && !props.readonly) {
|
|
emit('update:modelValue', value)
|
|
}
|
|
} catch (e) {
|
|
// do nothing
|
|
}
|
|
})
|
|
}
|
|
|
|
const getModelValue = () => {
|
|
let value = props.modelValue || ''
|
|
try {
|
|
if (props.type === 'JSON' || typeof value === 'object') {
|
|
value = JSON.stringify(value, null, 2)
|
|
}
|
|
} catch (e) {
|
|
// do nothing
|
|
}
|
|
return value as string
|
|
}
|
|
|
|
function resetEditor(value: string, resetHistory = false) {
|
|
value = getModelValue()
|
|
aceEditor?.setValue(value)
|
|
aceEditor?.clearSelection()
|
|
aceEditor?.setTheme(isDark.value ? 'ace/theme/twilight' : 'ace/theme/chrome')
|
|
props.autofocus && aceEditor?.focus()
|
|
if (resetHistory) {
|
|
aceEditor?.session.getUndoManager().reset()
|
|
}
|
|
}
|
|
|
|
watch(isDark, () => {
|
|
console.log(isDark.value)
|
|
aceEditor?.setTheme(isDark.value ? 'ace/theme/twilight' : 'ace/theme/chrome')
|
|
})
|
|
|
|
watch(
|
|
() => props.type,
|
|
() => {
|
|
setupEditor()
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
() => {
|
|
resetEditor(props.modelValue as string)
|
|
}
|
|
)
|
|
|
|
defineExpose({ resetEditor })
|
|
</script>
|