index.vue 5.17 KB
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, unref, watch } from 'vue'
import { Tooltip } from 'ant-design-vue'
import { Icon } from '@iconify/vue'
import { useFullscreen } from '@vueuse/core'
import type { Ace } from 'ace-builds'
import AceEditor from 'ace-builds'
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url'
import githubTheme from 'ace-builds/src-noconflict/theme-github?url'
import { isNumber, isObject, isString } from '@wry-smile/utils-is'
import 'ace-builds/src-noconflict/mode-json'

const props = withDefaults(
  defineProps<{
    value?: string | Recordable
    height?: number | string
    title?: string
    disabled?: boolean
  }>(),
  {
    height: 150,
  },
)

const emit = defineEmits<{
  (e: EventEnum.UPDATE_VALUE, value: any, instance?: Ace.Editor): void
  (e: EventEnum.CHANGE, value: any, instance?: Ace.Editor): void
  (e: EventEnum.BLUR, event: Event, instance?: Ace.Editor): void
  (e: EventEnum.FOCUS, event: Event, instance?: Ace.Editor): void
}>()

enum EventEnum {
  UPDATE_VALUE = 'update:value',
  CHANGE = 'change',
  BLUR = 'blur',
  FOCUS = 'focus',
}

const jsonEditorElRef = ref<Nullable<any>>()

const editoreRef = ref<Ace.Editor>()

const isFocus = ref(false)

const handleOnChange = () => {
  const value = get()
  emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef))
  emit(EventEnum.CHANGE, value, unref(editoreRef))
}

const handleOnBlur = (event: Event) => {
  isFocus.value = false
  emit(EventEnum.BLUR, event, unref(editoreRef))
}

const handleOnFocus = (event: Event) => {
  isFocus.value = true
  emit(EventEnum.FOCUS, event, unref(editoreRef))
}

const getFormatValue = (value: Recordable | string = '') => {
  return isObject(value) ? JSON.stringify(value, null, 2) : value
}

const initialize = () => {
  AceEditor.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl)
  AceEditor.config.setModuleUrl('ace/theme/github', githubTheme)
  const editor = AceEditor.edit(unref(jsonEditorElRef)!, {
    mode: 'ace/mode/json',
  })
  editor.setTheme('ace/theme/github')
  editor.setOptions({
    fontSize: 14,
  })

  editor.on('change', handleOnChange)
  editor.on('blur', handleOnBlur)
  editor.on('focus', handleOnFocus)

  editoreRef.value = editor
  unref(editoreRef)?.setValue(getFormatValue(props.value), 1)
  unref(editoreRef)?.setReadOnly(props.disabled)
}

watch(
  () => props.value,
  (target) => {
    // const position = unref(editoreRef)?.getCursorPosition();
    if (unref(isFocus)) return
    unref(editoreRef)?.setValue(getFormatValue(target))
    unref(editoreRef)?.clearSelection()
    // position && unref(editoreRef)?.moveCursorToPosition(position!);
  },
  {
    immediate: true,
  },
)

watch(
  () => props.disabled,
  (value) => {
    unref(editoreRef)?.setReadOnly(value)
  },
)

function get(): string {
  return unref(editoreRef)?.getValue() || ''
}

function set(data: any) {
  return unref(editoreRef)?.setValue(getFormatValue(data))
}

onMounted(() => {
  initialize()
  unref(editoreRef)?.setValue(getFormatValue(props.value))
})

onUnmounted(() => {
  unref(editoreRef)?.off('change', handleOnChange)
  unref(editoreRef)?.off('blur', handleOnBlur)
  unref(editoreRef)?.off('focus', handleOnFocus)
  unref(editoreRef)?.destroy()
  unref(editoreRef)?.container.remove()
})

const handleFormat = () => {
  const value = get()
  if (isString(value) && !value) return
  unref(editoreRef)?.setValue(JSON.stringify(JSON.parse(value), null, 2))
  unref(editoreRef)?.clearSelection()
}

const handleCompress = () => {
  const value = get()
  if (isString(value) && !value) return
  unref(editoreRef)?.setValue(JSON.stringify(JSON.parse(value)))
  unref(editoreRef)?.clearSelection()
}

const jsonEditorContainerElRef = ref<Nullable<HTMLDivElement>>()
const { isFullscreen, isSupported, toggle } = useFullscreen(jsonEditorContainerElRef)
const handleFullScreen = () => {
  toggle()
}

const getHeight = computed(() => {
  const { height } = props
  return isNumber(height) ? `${height}px` : height
})

defineExpose({
  get,
  set,
})
</script>

<template>
  <div
    ref="jsonEditorContainerElRef"
    class="p-2 bg-gray-200 flex flex-col"
    :style="{ height: getHeight }"
  >
    <div class="w-full h-8 flex justify-between items-center">
      <div>
        {{ title }}
      </div>
      <slot name="header" />
      <div class="flex h-8 gap-3 justify-end items-center text-dark-500 svg:text-2xl">
        <slot name="beforeFormat" />
        <Tooltip title="整洁">
          <Icon class="cursor-pointer" icon="gg:format-left" @click="handleFormat" />
        </Tooltip>
        <slot name="beforeCompress" />

        <Tooltip title="迷你">
          <Icon class="cursor-pointer" icon="material-symbols:compress" @click="handleCompress" />
        </Tooltip>
        <slot name="beforeFullScreen" />

        <Tooltip title="全屏">
          <Icon
            v-if="isSupported"
            class="cursor-pointer"
            :icon="
              isFullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'
            "
            @click="handleFullScreen"
          />
        </Tooltip>
        <slot name="afterFullScreen" />
      </div>
    </div>
    <div ref="jsonEditorElRef" class="flex-auto" />
  </div>
</template>