Browse Source

Merge pull request #2946 from nocodb/refactor/gui-v2-added-json

vue3: Added `Json` cell
pull/3042/head
Pranav C 2 years ago committed by GitHub
parent
commit
c0f194c7e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 123
      packages/nc-gui-v2/components/cell/Json.vue
  2. 119
      packages/nc-gui-v2/components/cell/JsonEditableCell.vue
  3. 72
      packages/nc-gui-v2/components/monaco/Editor.vue
  4. 3
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  5. 1
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  6. 19
      packages/nc-gui-v2/package-lock.json
  7. 1
      packages/nc-gui-v2/package.json

123
packages/nc-gui-v2/components/cell/Json.vue

@ -0,0 +1,123 @@
<script setup lang="ts">
import { Modal as AModal } from 'ant-design-vue'
import Editor from '~/components/monaco/Editor.vue'
import FullScreenIcon from '~icons/cil/fullscreen'
import FullScreenExitIcon from '~icons/cil/fullscreen-exit'
import { inject } from '#imports'
import { EditModeInj } from '~/context'
interface Props {
modelValue: string | Record<string, any> | undefined
}
interface Emits {
(event: 'update:modelValue', model: string): void
}
const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const editEnabled = inject(EditModeInj, ref(false))
let vModel = $(useVModel(props, 'modelValue', emits))
let localValueState = $ref<string | undefined>(undefined)
let localValue = $(
computed<string | undefined>({
get: () => localValueState,
set: (val: undefined | string | Record<string, any>) => {
localValueState = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
},
}),
)
let error = $ref<string | undefined>(undefined)
let isExpanded = $ref(false)
const clear = () => {
error = undefined
isExpanded = false
editEnabled.value = false
localValue = vModel
}
const formatJson = (json: string) => {
try {
return JSON.stringify(JSON.parse(json), null, 2)
} catch (e) {
return json
}
}
const onSave = () => {
isExpanded = false
editEnabled.value = false
localValue = localValue ? formatJson(localValue) : localValue
vModel = localValue
}
watch(
$$(vModel),
(val) => {
localValue = val
},
{ immediate: true },
)
watch($$(localValue), (val) => {
try {
JSON.parse(val)
error = undefined
} catch (e: any) {
error = e
}
})
watch(editEnabled, () => {
isExpanded = false
localValue = vModel
})
</script>
<template>
<component :is="isExpanded ? AModal : 'div'" v-model:visible="isExpanded" :closable="false" centered :footer="null">
<div v-if="editEnabled" class="flex flex-col w-full">
<div class="flex flex-row justify-between pt-1 pb-2">
<a-button type="text" size="small" @click="isExpanded = !isExpanded">
<FullScreenExitIcon v-if="isExpanded" class="h-2.5" />
<FullScreenIcon v-else class="h-2.5" />
</a-button>
<div class="flex flex-row">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel">
<div class="text-xs" :onclick="onSave">Save</div>
</a-button>
</div>
</div>
<Editor
:model-value="localValue"
class="min-w-full w-80"
:class="{ 'expanded-editor': isExpanded, 'editor': !isExpanded }"
:hide-minimap="true"
:disable-deep-compare="true"
@update:model-value="localValue = $event"
/>
<span v-if="error" class="text-xs w-full py-1 text-red-500">
{{ error.toString() }}
</span>
</div>
<span v-else>{{ vModel }}</span>
</component>
</template>
<style scoped lang="scss">
.expanded-editor {
min-height: min(600px, 80vh);
}
.editor {
min-height: min(200px, 10vh);
}
</style>

119
packages/nc-gui-v2/components/cell/JsonEditableCell.vue

@ -1,119 +0,0 @@
<script lang="ts" setup>
import MonacoJsonObjectEditor from '@/components/monaco/Editor.vue'
import { computed, inject } from '#imports'
import { EditModeInj } from '~/context'
interface Props {
modelValue: string | Record<string, any> | null
isForm: boolean
}
const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue', 'cancel'])
const editEnabled = inject(EditModeInj)
let expand = $ref(false)
let isValid = $ref(true)
let error = $ref()
const vModel = computed({
get: () => (typeof props.modelValue === 'string' ? JSON.parse(props.modelValue) : props.modelValue),
set: (val) => {
if (props.isForm) {
emits('update:modelValue', JSON.stringify(val))
}
},
})
function save() {
expand = false
emits('update:modelValue', JSON.stringify(vModel.value))
}
function validate(n: boolean, e: any) {
isValid = n
error = e
}
</script>
<script lang="ts">
export default {
name: 'JsonEditableCell',
}
</script>
<template>
<v-dialog :is="expand ? 'v-dialog' : 'div'" v-model="expand" max-width="800px" class="cell-container" @keydown.stop.enter>
<div class="d-flex pa-1" :class="{ backgroundColor: expand }">
<v-spacer />
<v-icon small class="mr-2" @click="expand = !expand">
{{ expand ? 'mdi-arrow-collapse' : 'mdi-arrow-expand' }}
</v-icon>
<template v-if="!isForm">
<v-btn outlined x-small class="mr-1" @click="$emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</v-btn>
<v-btn x-small color="primary" :disabled="!isValid" @click="save">
<!-- Save -->
{{ $t('general.save') }}
</v-btn>
</template>
<v-btn v-else-if="expand" x-small @click="expand = false">
<!-- Close -->
{{ $t('general.close') }}
</v-btn>
</div>
<MonacoJsonObjectEditor
v-if="expand"
v-model="vModel"
class="text-left caption"
style="width: 300px; min-height: min(600px, 80vh); min-width: 100%"
@validate="validate"
/>
<MonacoJsonObjectEditor
v-else
v-model="vModel"
class="text-left caption"
style="width: 300px; min-height: 200px; min-width: 100%"
@validate="validate"
/>
<div v-show="error" class="px-2 py-1 text-left caption error--text">
{{ error }}
</div>
</v-dialog>
</template>
<style scoped>
.cell-container {
width: 100%;
}
</style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->

72
packages/nc-gui-v2/components/monaco/Editor.vue

@ -7,14 +7,37 @@ import { onMounted } from '#imports'
import { deepCompare } from '~/utils'
interface Props {
modelValue: string
modelValue: string | Record<string, any>
hideMinimap?: boolean
lang?: string
validate?: boolean
disableDeepCompare?: boolean
}
const { modelValue, lang = 'json', validate = true } = defineProps<Props>()
const { hideMinimap, lang = 'json', validate = true, disableDeepCompare = false, modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const emits = defineEmits(['update:modelValue'])
let vModel = $computed<string>({
get: () => {
if (typeof modelValue === 'object') {
return JSON.stringify(modelValue, null, 2)
} else {
return modelValue
}
},
set: (newVal: string | Record<string, any>) => {
if (typeof modelValue === 'object') {
try {
emits('update:modelValue', typeof newVal === 'object' ? newVal : JSON.parse(newVal))
} catch (e) {
console.error(e)
}
} else {
emits('update:modelValue', newVal)
}
},
})
const isValid = ref(true)
@ -49,7 +72,8 @@ defineExpose({
onMounted(() => {
if (root.value && lang) {
const model = monaco.editor.createModel(JSON.stringify(modelValue, null, 2), lang)
const model = monaco.editor.createModel(vModel, lang)
if (lang === 'json') {
// configure the JSON language support with schemas and schema associations
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
@ -68,19 +92,20 @@ onMounted(() => {
},
tabSize: 2,
automaticLayout: true,
minimap: {
enabled: !hideMinimap,
},
})
editor.onDidChangeModelContent(async (e) => {
editor.onDidChangeModelContent(async () => {
try {
isValid.value = true
const value = editor?.getValue()
if (typeof modelValue === 'object') {
if (!value || !deepCompare(modelValue, JSON.parse(value))) {
emit('update:modelValue', JSON.stringify(modelValue, null, 2))
}
if (disableDeepCompare) {
vModel = editor.getValue()
} else {
if (value !== modelValue) emit('update:modelValue', value)
const obj = JSON.parse(editor.getValue())
if (!obj || !deepCompare(vModel, obj)) vModel = obj
}
} catch (e) {
isValid.value = false
@ -90,21 +115,18 @@ onMounted(() => {
}
})
watch(
() => modelValue,
(v) => {
if (editor && v) {
const value = editor?.getValue()
if (typeof v === 'object') {
if (!value || !deepCompare(v, JSON.parse(value))) {
editor.setValue(JSON.stringify(v, null, 2))
}
} else {
if (value !== v) editor.setValue(v)
}
watch($$(vModel), (v) => {
if (!editor || !v) return
const editorValue = editor?.getValue()
if (!disableDeepCompare) {
if (!editorValue || !deepCompare(JSON.parse(v), JSON.parse(editorValue))) {
editor.setValue(v)
}
},
)
} else {
if (editorValue !== v) editor.setValue(v)
}
})
</script>
<template>

3
packages/nc-gui-v2/components/smartsheet/Cell.vue

@ -92,6 +92,8 @@ const {
} = useColumn(column)
const syncAndNavigate = (dir: NavigateDir) => {
if (isJSON) return
if (changed) {
emit('save')
changed = false
@ -130,6 +132,7 @@ const syncAndNavigate = (dir: NavigateDir) => {
<CellFloat v-else-if="isFloat" v-model="vModel" />
<CellText v-else-if="isString" v-model="vModel" />
<CellPercent v-else-if="isPercent" v-model="vModel" />
<CellJson v-else-if="isJSON" v-model="vModel" />
<CellText v-else v-model="vModel" />
</div>
</template>

1
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -420,7 +420,6 @@ const onNavigate = (dir: NavigateDir) => {
td {
text-overflow: ellipsis;
white-space: nowrap;
}
td.active::after,

19
packages/nc-gui-v2/package-lock.json generated

@ -28,6 +28,7 @@
},
"devDependencies": {
"@antfu/eslint-config": "^0.25.2",
"@iconify-json/cil": "^1.1.2",
"@iconify-json/clarity": "^1.1.4",
"@iconify-json/eva": "^1.1.2",
"@iconify-json/ic": "^1.1.7",
@ -990,6 +991,15 @@
"dev": true,
"peer": true
},
"node_modules/@iconify-json/cil": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@iconify-json/cil/-/cil-1.1.2.tgz",
"integrity": "sha512-fu9x1f+A2H5qGWnApU1aw0EREAKqg5EP2Z6cWHV11XchlKgzY+jWQCalctkV+Jsef2M2m3C2DX/ukgyhMclIcw==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/clarity": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@iconify-json/clarity/-/clarity-1.1.4.tgz",
@ -15502,6 +15512,15 @@
"dev": true,
"peer": true
},
"@iconify-json/cil": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@iconify-json/cil/-/cil-1.1.2.tgz",
"integrity": "sha512-fu9x1f+A2H5qGWnApU1aw0EREAKqg5EP2Z6cWHV11XchlKgzY+jWQCalctkV+Jsef2M2m3C2DX/ukgyhMclIcw==",
"dev": true,
"requires": {
"@iconify/types": "*"
}
},
"@iconify-json/clarity": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@iconify-json/clarity/-/clarity-1.1.4.tgz",

1
packages/nc-gui-v2/package.json

@ -34,6 +34,7 @@
},
"devDependencies": {
"@antfu/eslint-config": "^0.25.2",
"@iconify-json/cil": "^1.1.2",
"@iconify-json/clarity": "^1.1.4",
"@iconify-json/eva": "^1.1.2",
"@iconify-json/ic": "^1.1.7",

Loading…
Cancel
Save