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>
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk'
import Sortable from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue'
import { notification } from 'ant-design-vue'
import type { SortableEvent } from 'sortablejs'
import { Menu as AntMenu, notification } from 'ant-design-vue'
import Draggable from 'vuedraggable'
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 type { TabItem } from '~/composables/useTabs'
import { TabType } from '~/composables/useTabs'
@ -26,18 +26,15 @@ const { addTab } = useTabs()
const { api } = useApi()
/** sortable instance */
let sortable: Sortable
/** Selected view(s) for menu */
const selected = ref<string[]>([])
const menuRef = ref<typeof AntMenu>()
let deleteModalVisible = $ref(false)
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(activeView, (nextActiveView) => {
const _nextActiveView = nextActiveView as GridType | FormType | KanbanType
@ -47,62 +44,48 @@ watch(activeView, (nextActiveView) => {
}
})
onBeforeUnmount(() => {
if (sortable) sortable.destroy()
})
function validate(value?: string) {
if (!value || value.trim().length < 0) {
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 true
}
function initializeSortable(el: HTMLElement) {
/** if instance exists, destroy it first */
if (sortable) sortable.destroy()
async function onSortEnd(evt: SortableEvent) {
if (sortedViews.value.length < 2) return
sortable = Sortable.create(el, {
handle: '.nc-drag-icon',
filter: '.nc-headline',
onEnd: async (evt) => {
if (views.value.length < 2) return
const { newIndex = 0, oldIndex = 0 } = evt
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
const previousItem: Record<string, any> = views.value[newIndex]
const nextItem: Record<string, any> = views.value[newIndex + 1]
let nextOrder: number
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
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
}
const _nextOrder = !isNaN(Number(nextOrder)) ? nextOrder.toString() : oldIndex.toString()
await api.dbView.update(currentItem.id, { order: !isNaN(Number(nextOrder)) ? nextOrder.toString() : oldIndex.toString() })
},
animation: 150,
})
}
currentItem.order = _nextOrder
onMounted(() => {
initializeSortable(menuRef.value?.$el)
})
await api.dbView.update(currentItem.id, { order: _nextOrder })
}
// todo: fix view type, alias is missing for some reason?
/** Navigate to view and add new tab if necessary */
@ -159,24 +142,34 @@ function onDeleted() {
toDelete = undefined
deleteModalVisible = false
}
const sortedViews = computed(() => (views.value as any[]).sort((a, b) => a.order - b.order))
</script>
<template>
<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">
<RenameableMenuItem
v-for="view of sortedViews"
:key="view.id"
:view="view"
@change-view="changeView"
@open-modal="$emit('openModal', $event)"
@delete="onDelete"
@rename="onRename"
/>
</a-menu>
<Draggable
:list="sortedViews"
:tag="AntMenu.name"
item-key="title"
handle=".nc-drag-icon"
:component-data="{
class: 'flex-1 max-h-50vh overflow-y-scroll scrollbar-thin-primary',
selectedKeys: selected,
}"
@end="onSortEnd"
>
<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" />
</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 class="flex w-auto">
<MdiDrag
class="hidden group-hover:block"
:class="`transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move nc-child-draggable-icon-${vModel.title}`"
class="hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move"
:class="`nc-child-draggable-icon-${vModel.title}`"
/>
<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",
"vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5",
"xlsx": "^0.17.3",
"vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13"
"vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3"
},
"devDependencies": {
"@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