Browse Source

feat(gui-v2): open quickimport dialog on file drop

pull/3211/head
braks 2 years ago
parent
commit
eb3bc5cedc
  1. 2
      packages/nc-gui-v2/assets/style-v2.scss
  2. 35
      packages/nc-gui-v2/components/dlg/QuickImport.vue
  3. 40
      packages/nc-gui-v2/components/template/Editor.vue
  4. 9
      packages/nc-gui-v2/composables/useDialog/index.ts
  5. 69
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/index.vue

2
packages/nc-gui-v2/assets/style-v2.scss

@ -194,7 +194,7 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
}
.scaling-btn {
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white bg-primary;
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;

35
packages/nc-gui-v2/components/dlg/QuickImport.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import { Form, message } from 'ant-design-vue'
import type { TableType } from 'nocodb-sdk'
import type { UploadChangeParam } from 'ant-design-vue'
import type { UploadChangeParam, UploadFile } from 'ant-design-vue'
import {
ExcelTemplateAdapter,
ExcelUrlTemplateAdapter,
@ -23,11 +23,10 @@ import {
interface Props {
modelValue: boolean
importType: 'csv' | 'json' | 'excel'
importOnly: boolean
files?: Record<string, any>[]
importOnly?: boolean
}
const { importType, importOnly, files: initialFiles = [], ...rest } = defineProps<Props>()
const { importType, importOnly = false, ...rest } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
@ -54,7 +53,7 @@ const templateEditorModal = ref(false)
const useForm = Form.useForm
const importState = reactive({
fileList: initialFiles as Record<string, any>[],
fileList: [] as (UploadFile & { data: any })[],
url: '',
jsonEditor: {},
parserConfig: {
@ -207,8 +206,22 @@ function handleChange(info: UploadChangeParam) {
reader.onload = (e: ProgressEvent<FileReader>) => {
const target = importState.fileList.find((f) => f.uid === info.file.uid)
if (target && e.target) {
target.data = (e.target as any).result
if (e.target) {
if (target) {
target.data = e.target.result
} else if (!target) {
// push new file to fileList
importState.fileList.push({
...info.file.originFileObj!,
name: info.file.originFileObj!.name,
fileName: info.file.originFileObj!.name,
originFileObj: info.file.originFileObj!,
type: info.file.originFileObj!.type,
uid: Math.random().toString(36).substring(2),
status: 'done',
data: e.target.result,
})
}
}
}
@ -257,6 +270,10 @@ function getAdapter(name: string, val: any) {
return null
}
defineExpose({
handleChange,
})
</script>
<template>
@ -277,7 +294,7 @@ function getAdapter(name: string, val: any) {
@import="handleImport"
/>
<a-tabs v-else v-model:activeKey="activeKey" hide-add type="editable-card" :tab-position="top">
<a-tabs v-else v-model:activeKey="activeKey" hide-add type="editable-card" tab-position="top">
<a-tab-pane key="uploadTab" :closable="false">
<template #tab>
<div class="flex items-center gap-2">
@ -292,7 +309,7 @@ function getAdapter(name: string, val: any) {
name="file"
class="nc-input-import !scrollbar-thin-dull"
:accept="importMeta.acceptTypes"
:max-count="2"
:max-count="1"
list-type="picture"
@change="handleChange"
@reject="rejectDrop"

40
packages/nc-gui-v2/components/template/Editor.vue

@ -7,6 +7,7 @@ import {
MetaInj,
ReloadViewDataHookInj,
computed,
createEventHook,
extractSdkResponseErrorMsg,
fieldRequiredValidator,
getUIDTIcon,
@ -40,11 +41,11 @@ const { quickImportType, projectTemplate, importData, importColumns, importOnly,
const emit = defineEmits(['import'])
const meta = inject(MetaInj)!
const meta = inject(MetaInj, ref({} as TableType))
const columns = computed(() => meta.value?.columns || [])
const reloadHook = inject(ReloadViewDataHookInj)!
const reloadHook = inject(ReloadViewDataHookInj, createEventHook())
const useForm = Form.useForm
@ -673,6 +674,7 @@ onMounted(() => {
<mdi-key-star class="text-lg" />
</div>
</a-tooltip>
<a-tooltip v-else>
<template #title>
<!-- TODO: i18n -->
@ -688,15 +690,19 @@ onMounted(() => {
</template>
</template>
</a-table>
<div class="text-center mt-5">
<div class="mt-5 flex gap-2 justify-center">
<a-tooltip bottom>
<template #title>
<!-- TODO: i18n -->
<span>Add Number Column</span>
</template>
<a-button @click="addNewColumnRow(table, 'Number')">
<div class="flex items-center">
<a-button
class="group hover:(ring-1 ring-pink-500 !border-transparent)"
@click="addNewColumnRow(table, 'Number')"
>
<div class="group-hover:!text-pink-500 flex items-center">
<mdi-numeric class="text-lg" />
</div>
</a-button>
@ -707,8 +713,12 @@ onMounted(() => {
<!-- TODO: i18n -->
<span>Add SingleLineText Column</span>
</template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')">
<div class="flex items-center">
<a-button
class="group hover:(ring-1 ring-pink-500 !border-transparent)"
@click="addNewColumnRow(table, 'SingleLineText')"
>
<div class="group-hover:!text-pink-500 flex items-center">
<mdi-alpha-a class="text-lg" />
</div>
</a-button>
@ -719,8 +729,12 @@ onMounted(() => {
<!-- TODO: i18n -->
<span>Add LongText Column</span>
</template>
<a-button @click="addNewColumnRow(table, 'LongText')">
<div class="flex items-center">
<a-button
class="group hover:(ring-1 ring-pink-500 !border-transparent)"
@click="addNewColumnRow(table, 'LongText')"
>
<div class="group-hover:!text-pink-500 flex items-center">
<mdi-text class="text-lg" />
</div>
</a-button>
@ -731,9 +745,13 @@ onMounted(() => {
<!-- TODO: i18n -->
<span>Add Other Column</span>
</template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')">
<a-button
class="group hover:(ring-1 ring-pink-500 !border-transparent)"
@click="addNewColumnRow(table, 'SingleLineText')"
>
<div class="flex items-center">
<mdi-plus class="text-lg" />
<mdi-plus class="group-hover:!text-pink-500 text-lg" />
Column
</div>
</a-button>

9
packages/nc-gui-v2/composables/useDialog/index.ts

@ -1,6 +1,7 @@
import type { VNode } from '@vue/runtime-dom'
import { render } from '@vue/runtime-dom'
import type { ComponentPublicInstance } from '@vue/runtime-core'
import { createEventHook, h, toReactive, tryOnScopeDispose, useNuxtApp, watch } from '#imports'
import { createEventHook, h, ref, toReactive, tryOnScopeDispose, useNuxtApp, watch } from '#imports'
/**
* Programmatically create a component and attach it to the body (or a specific mount target), like a dialog or modal.
@ -17,6 +18,8 @@ export function useDialog(
const domNode = document.createElement('div')
const vNodeRef = ref<VNode>()
/** if specified, append vnode to mount target instead of document.body */
if (mountTarget) {
if ('$el' in mountTarget) {
@ -36,6 +39,8 @@ export function useDialog(
vNode.appContext = useNuxtApp().vueApp._context
vNodeRef.value = vNode
render(vNode, domNode)
if (!isMounted) mountedHook.trigger()
@ -63,5 +68,7 @@ export function useDialog(
close,
onClose: closeHook.on,
onMounted: mountedHook.on,
domNode,
vNode: vNodeRef,
}
}

69
packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/index.vue

@ -1,34 +1,89 @@
<script lang="ts" setup>
import type { UploadChangeParam, UploadFile } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import { ref, useDialog, useDropZone, useNuxtApp } from '#imports'
import DlgQuickImport from '~/components/dlg/QuickImport.vue'
const el = ref<HTMLDivElement>()
const dropZone = ref<HTMLDivElement>()
const { isOverDropZone } = useDropZone(el, onDrop)
const { isOverDropZone } = useDropZone(dropZone, onDrop)
const { $e } = useNuxtApp()
type QuickImportTypes = 'excel' | 'json' | 'csv'
const allowedQuickImportTypes = [
// Excel
'.xls, .xlsx, .xlsm, .ods, .ots',
// CSV
'.csv',
// JSON
'.json',
]
function onDrop(droppedFiles: File[] | null) {
console.log('onDrop', droppedFiles)
if (!droppedFiles) return
/** we can only handle one file per drop */
if (droppedFiles.length > 1) {
return message.error({
content: `Only one file can be imported at a time.`,
duration: 2,
})
}
let fileType: QuickImportTypes | null = null
const isValid = allowedQuickImportTypes.some((type) => {
const isAllowed = droppedFiles[0].type.replace('/', '.').endsWith(type)
if (isAllowed) {
fileType = type.replace('.', '') as QuickImportTypes
}
return isAllowed
})
/** Invalid file type was dropped */
if (!isValid) {
return message.error({
content: 'Invalid file type',
duration: 2,
})
}
if (fileType && isValid) {
openQuickImportDialog(fileType, droppedFiles[0])
}
}
function openQuickImportDialog(type: string) {
function openQuickImportDialog(type: QuickImportTypes, file: File) {
$e(`a:actions:import-${type}`)
const { close } = useDialog(DlgQuickImport, {
'modelValue': true,
const isOpen = ref(true)
const { close, vNode } = useDialog(DlgQuickImport, {
'modelValue': isOpen,
'importType': type,
'onUpdate:modelValue': closeDialog,
})
vNode.value?.component?.exposed?.handleChange({
file: { originFileObj: file },
event: { percent: 100 },
} as UploadChangeParam<UploadFile<File>>)
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
<template>
<div ref="el" class="h-full w-full text-gray-400 flex items-center justify-center relative">
<div ref="dropZone" class="h-full w-full text-gray-400 flex items-center justify-center relative">
<general-overlay
v-model="isOverDropZone"
inline

Loading…
Cancel
Save