Browse Source

refactor(gui-v2): replace sortable with vue draggable next

# What's changed?

* add ant plugin to explicitly register Ant Menu component
pull/2837/head
braks 2 years ago
parent
commit
1cf91dc952
  1. 109
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  2. 4
      packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue
  3. 4
      packages/nc-gui-v2/package.json
  4. 6
      packages/nc-gui-v2/plugins/ant.ts

109
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk' import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk'
import Sortable from 'sortablejs' import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue' import { Menu as AntMenu, notification } from 'ant-design-vue'
import { notification } from 'ant-design-vue' import Draggable from 'vuedraggable'
import RenameableMenuItem from './RenameableMenuItem.vue' import RenameableMenuItem from './RenameableMenuItem.vue'
import { computed, inject, onBeforeUnmount, onMounted, ref, unref, useApi, useTabs, watch } from '#imports' import { computed, inject, ref, useApi, useTabs, watch } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
import type { TabItem } from '~/composables/useTabs' import type { TabItem } from '~/composables/useTabs'
import { TabType } from '~/composables/useTabs' import { TabType } from '~/composables/useTabs'
@ -26,18 +26,15 @@ const { addTab } = useTabs()
const { api } = useApi() const { api } = useApi()
/** sortable instance */
let sortable: Sortable
/** Selected view(s) for menu */ /** Selected view(s) for menu */
const selected = ref<string[]>([]) const selected = ref<string[]>([])
const menuRef = ref<typeof AntMenu>()
let deleteModalVisible = $ref(false) let deleteModalVisible = $ref(false)
let toDelete = $ref<Record<string, any> | undefined>() let toDelete = $ref<Record<string, any> | undefined>()
const sortedViews = computed(() => ((views.value as any[]) || []).sort((a, b) => a.order - b.order))
/** Watch currently active view, so we can mark it in the menu */ /** Watch currently active view, so we can mark it in the menu */
watch(activeView, (nextActiveView) => { watch(activeView, (nextActiveView) => {
const _nextActiveView = nextActiveView as GridType | FormType | KanbanType const _nextActiveView = nextActiveView as GridType | FormType | KanbanType
@ -47,62 +44,48 @@ watch(activeView, (nextActiveView) => {
} }
}) })
onBeforeUnmount(() => {
if (sortable) sortable.destroy()
})
function validate(value?: string) { function validate(value?: string) {
if (!value || value.trim().length < 0) { if (!value || value.trim().length < 0) {
return 'View name is required' return 'View name is required'
} }
if ((unref(views) || []).every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== value)) { if (sortedViews.value.every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== value)) {
return 'View name should be unique' return 'View name should be unique'
} }
return true return true
} }
function initializeSortable(el: HTMLElement) { async function onSortEnd(evt: SortableEvent) {
/** if instance exists, destroy it first */ if (sortedViews.value.length < 2) return
if (sortable) sortable.destroy()
sortable = Sortable.create(el, { const { newIndex = 0, oldIndex = 0 } = evt
handle: '.nc-drag-icon',
filter: '.nc-headline',
onEnd: async (evt) => {
if (views.value.length < 2) return
const { newIndex = 0, oldIndex = 0 } = evt if (newIndex === oldIndex) return
if (newIndex === oldIndex) return const currentItem: Record<string, any> = sortedViews.value[oldIndex]
const currentItem: Record<string, any> = views.value[oldIndex] // get items meta of before and after the moved item
const previousItem: Record<string, any> = sortedViews.value[newIndex]
const nextItem: Record<string, any> = sortedViews.value[newIndex + 1]
// get items meta of before and after the moved item let nextOrder: number
const previousItem: Record<string, any> = views.value[newIndex]
const nextItem: Record<string, any> = views.value[newIndex + 1]
let nextOrder: number // set new order value based on the new order of the items
if (sortedViews.value.length - 1 === newIndex) {
nextOrder = parseFloat(previousItem.order) + 1
} else if (newIndex === 0) {
nextOrder = parseFloat(nextItem.order) / 2
} else {
nextOrder = (parseFloat(previousItem.order) + parseFloat(nextItem.order)) / 2
}
// set new order value based on the new order of the items const _nextOrder = !isNaN(Number(nextOrder)) ? nextOrder.toString() : oldIndex.toString()
if (views.value.length - 1 === newIndex) {
nextOrder = parseFloat(previousItem.order) + 1
} else if (newIndex === 0) {
nextOrder = (parseFloat(nextItem.order) > 1 ? parseFloat(nextItem.order) - 1 : parseFloat(nextItem.order) / 2) || 0
} else {
nextOrder = (parseFloat(previousItem.order) + parseFloat(nextItem.order)) / 2
}
await api.dbView.update(currentItem.id, { order: !isNaN(Number(nextOrder)) ? nextOrder.toString() : oldIndex.toString() }) currentItem.order = _nextOrder
},
animation: 150,
})
}
onMounted(() => { await api.dbView.update(currentItem.id, { order: _nextOrder })
initializeSortable(menuRef.value?.$el) }
})
// todo: fix view type, alias is missing for some reason? // todo: fix view type, alias is missing for some reason?
/** Navigate to view and add new tab if necessary */ /** Navigate to view and add new tab if necessary */
@ -159,24 +142,34 @@ function onDeleted() {
toDelete = undefined toDelete = undefined
deleteModalVisible = false deleteModalVisible = false
} }
const sortedViews = computed(() => (views.value as any[]).sort((a, b) => a.order - b.order))
</script> </script>
<template> <template>
<h3 class="nc-headline pt-3 px-3 text-xs font-semibold">{{ $t('objects.views') }}</h3> <h3 class="nc-headline pt-3 px-3 text-xs font-semibold">{{ $t('objects.views') }}</h3>
<a-menu ref="menuRef" class="flex-1 max-h-50vh overflow-y-scroll scrollbar-thin-primary" :selected-keys="selected"> <Draggable
<RenameableMenuItem :list="sortedViews"
v-for="view of sortedViews" :tag="AntMenu.name"
:key="view.id" item-key="title"
:view="view" handle=".nc-drag-icon"
@change-view="changeView" :component-data="{
@open-modal="$emit('openModal', $event)" class: 'flex-1 max-h-50vh overflow-y-scroll scrollbar-thin-primary',
@delete="onDelete" selectedKeys: selected,
@rename="onRename" }"
/> @end="onSortEnd"
</a-menu> >
<template #item="{ element: view }">
<div>
<RenameableMenuItem
:view="view"
@change-view="changeView"
@open-modal="$emit('openModal', $event)"
@delete="onDelete"
@rename="onRename"
/>
</div>
</template>
</Draggable>
<dlg-view-delete v-model="deleteModalVisible" :view="toDelete" @deleted="onDeleted" /> <dlg-view-delete v-model="deleteModalVisible" :view="toDelete" @deleted="onDeleted" />
</template> </template>

4
packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -137,8 +137,8 @@ function onStopEdit() {
<div v-t="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2"> <div v-t="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2">
<div class="flex w-auto"> <div class="flex w-auto">
<MdiDrag <MdiDrag
class="hidden group-hover:block" class="hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move"
:class="`transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move nc-child-draggable-icon-${vModel.title}`" :class="`nc-child-draggable-icon-${vModel.title}`"
/> />
<component :is="viewIcons[vModel.type].icon" class="group-hover:hidden" :class="`text-${viewIcons[vModel.type].color}`" /> <component :is="viewIcons[vModel.type].icon" class="group-hover:hidden" :class="`text-${viewIcons[vModel.type].color}`" />

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

@ -25,9 +25,9 @@
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"vue-i18n": "^9.1.10", "vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5", "vue-toastification": "^2.0.0-rc.5",
"xlsx": "^0.17.3",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13" "vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.25.2", "@antfu/eslint-config": "^0.25.2",

6
packages/nc-gui-v2/plugins/ant.ts

@ -0,0 +1,6 @@
import { Menu as AntMenu } from 'ant-design-vue'
import { defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component(AntMenu.name, AntMenu)
})
Loading…
Cancel
Save