Browse Source

refactor(nc-gui): use `useDialog` to open view create modal

pull/4031/head
braks 2 years ago
parent
commit
d8cdc56ef2
  1. 31
      packages/nc-gui/components/dlg/ViewCreate.vue
  2. 36
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  3. 79
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  4. 22
      packages/nc-gui/composables/useDialog/index.ts
  5. 2
      packages/nc-gui/composables/useViews.ts
  6. 1
      packages/nc-gui/context/index.ts
  7. 1
      packages/nc-gui/lang/en.json

31
packages/nc-gui/components/dlg/ViewCreate.vue

@ -2,17 +2,16 @@
import type { ComponentPublicInstance } from '@vue/runtime-core'
import type { Form as AntForm, SelectProps } from 'ant-design-vue'
import { capitalize } from '@vue/runtime-core'
import type { FormType, GalleryType, GridType, KanbanType } from 'nocodb-sdk'
import type { FormType, GalleryType, GridType, KanbanType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, ViewTypes } from 'nocodb-sdk'
import {
MetaInj,
ViewListInj,
computed,
generateUniqueTitle,
inject,
message,
nextTick,
onBeforeMount,
reactive,
ref,
unref,
useApi,
useI18n,
@ -26,6 +25,8 @@ interface Props {
title?: string
selectedViewId?: string
groupingFieldColumnId?: string
viewList: ViewType[]
meta: TableType
}
interface Emits {
@ -41,7 +42,7 @@ interface Form {
fk_grp_col_id: string | null
}
const props = defineProps<Props>()
const { viewList = [], meta, selectedViewId, ...props } = defineProps<Props>()
const emits = defineEmits<Emits>()
@ -55,10 +56,6 @@ const { t } = useI18n()
const { isLoading: loading, api } = useApi()
const meta = inject(MetaInj, ref())
const viewList = inject(ViewListInj)
const form = reactive<Form>({
title: props.title || '',
type: props.type,
@ -75,7 +72,7 @@ const viewNameRules = [
{
validator: (_: unknown, v: string) =>
new Promise((resolve, reject) => {
;(unref(viewList) || []).every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== v)
viewList.every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== v)
? resolve(true)
: reject(new Error(`View name should be unique`))
}),
@ -98,7 +95,7 @@ const typeAlias = computed(
}[props.type]),
)
watch(vModel, (value) => value && init())
onBeforeMount(init)
watch(
() => props.type,
@ -108,22 +105,23 @@ watch(
)
function init() {
form.title = generateUniqueTitle(capitalize(ViewTypes[props.type].toLowerCase()), viewList?.value || [], 'title')
form.title = generateUniqueTitle(capitalize(ViewTypes[props.type].toLowerCase()), viewList, 'title')
if (props.selectedViewId) {
form.copy_from_id = props.selectedViewId
if (selectedViewId) {
form.copy_from_id = selectedViewId
}
// preset the grouping field column
if (props.type === ViewTypes.KANBAN) {
singleSelectFieldOptions.value = meta
.value!.columns!.filter((el) => el.uidt === UITypes.SingleSelect)
.columns!.filter((el) => el.uidt === UITypes.SingleSelect)
.map((field) => {
return {
value: field.id,
label: field.title,
}
})
if (props.groupingFieldColumnId) {
// take from the one from copy view
form.fk_grp_col_id = props.groupingFieldColumnId
@ -186,7 +184,8 @@ async function onSubmit() {
<template>
<a-modal v-model:visible="vModel" class="!top-[35%]" :confirm-loading="loading" wrap-class-name="nc-modal-view-create">
<template #title>
{{ $t('general.create') }} <span class="text-capitalize">{{ typeAlias }}</span> {{ $t('objects.view') }}
{{ $t(`general.${selectedViewId ? 'duplicate' : 'create'}`) }} <span class="text-capitalize">{{ typeAlias }}</span>
{{ $t('objects.view') }}
</template>
<a-form ref="formValidator" layout="vertical" :model="form">

36
packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue

@ -2,11 +2,9 @@
import type { ViewType, ViewTypes } from 'nocodb-sdk'
import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue'
import type { Ref } from 'vue'
import Sortable from 'sortablejs'
import {
ActiveViewInj,
ViewListInj,
extractSdkResponseErrorMsg,
inject,
message,
@ -22,9 +20,9 @@ import {
watch,
} from '#imports'
const emits = defineEmits<Emits>()
const { t } = useI18n()
interface Props {
views: ViewType[]
}
interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string; groupingFieldColumnId?: string }): void
@ -32,12 +30,16 @@ interface Emits {
(event: 'deleted'): void
}
const { views } = defineProps<Props>()
const emits = defineEmits<Emits>()
const { t } = useI18n()
const { $e } = useNuxtApp()
const activeView = inject(ActiveViewInj, ref())
const views = inject<Ref<ViewType[]>>(ViewListInj, ref([]))
const { api } = useApi()
const router = useRouter()
@ -54,10 +56,8 @@ let isMarked = $ref<string | false>(false)
/** Watch currently active view, so we can mark it in the menu */
watch(activeView, (nextActiveView) => {
const _nextActiveView = nextActiveView as ViewType
if (_nextActiveView && _nextActiveView.id) {
selected.value = [_nextActiveView.id]
if (nextActiveView && nextActiveView.id) {
selected.value = [nextActiveView.id]
}
})
@ -75,7 +75,7 @@ function validate(view: ViewType) {
return 'View name is required'
}
if (views.value.some((v) => v.title === view.title && v.id !== view.id)) {
if (views.some((v) => v.title === view.title && v.id !== view.id)) {
return 'View name should be unique'
}
@ -93,7 +93,7 @@ async function onSortEnd(evt: SortableEvent) {
evt.preventDefault()
dragging = false
if (views.value.length < 2) return
if (views.length < 2) return
const { newIndex = 0, oldIndex = 0 } = evt
@ -104,17 +104,17 @@ async function onSortEnd(evt: SortableEvent) {
const previousEl = children[newIndex - 1]
const nextEl = children[newIndex + 1]
const currentItem = views.value.find((v) => v.id === evt.item.id)
const currentItem = views.find((v) => v.id === evt.item.id)
if (!currentItem || !currentItem.id) return
const previousItem = (previousEl ? views.value.find((v) => v.id === previousEl.id) : {}) as ViewType
const nextItem = (nextEl ? views.value.find((v) => v.id === nextEl.id) : {}) as ViewType
const previousItem = (previousEl ? views.find((v) => v.id === previousEl.id) : {}) as ViewType
const nextItem = (nextEl ? views.find((v) => v.id === nextEl.id) : {}) as ViewType
let nextOrder: number
// set new order value based on the new order of the items
if (views.value.length - 1 === newIndex) {
if (views.length - 1 === newIndex) {
nextOrder = parseFloat(String(previousItem.order)) + 1
} else if (newIndex === 0) {
nextOrder = parseFloat(String(nextItem.order)) / 2
@ -198,7 +198,7 @@ function openDeleteDialog(view: Record<string, any>) {
// return to the default view
router.replace({
params: {
viewTitle: views.value[0].title,
viewTitle: views[0].title,
},
})
}

79
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -3,11 +3,11 @@ import type { ViewType, ViewTypes } from 'nocodb-sdk'
import {
ActiveViewInj,
MetaInj,
ViewListInj,
computed,
inject,
provide,
ref,
resolveComponent,
useDialog,
useNuxtApp,
useRoute,
useRouter,
@ -31,8 +31,6 @@ const route = useRoute()
const { $e } = useNuxtApp()
provide(ViewListInj, views)
/** Sidebar visible */
const { isOpen } = useSidebar('nc-right-sidebar')
@ -41,21 +39,6 @@ const sidebarCollapsed = computed(() => !isOpen.value)
/** Sidebar ref */
const sidebar = ref()
/** View type to create from modal */
let viewCreateType = $ref<ViewTypes>()
/** View title to create from modal (when duplicating) */
let viewCreateTitle = $ref('')
/** selected view id for copying view meta */
let selectedViewId = $ref('')
/** Kanban Grouping Column Id for copying view meta */
let kanbanGrpColumnId = $ref('')
/** is view creation modal open */
let modalOpen = $ref(false)
/** Watch route param and change active view based on `viewTitle` */
watch(
[views, () => route.params.viewTitle],
@ -88,31 +71,45 @@ watch(
{ immediate: true },
)
/** Open view creation modal */
function openModal({
/** Open delete modal */
function openCreateDialog({
title,
type,
title = '',
copyViewId,
groupingFieldColumnId,
}: {
title?: string
type: ViewTypes
title: string
copyViewId: string
groupingFieldColumnId: string
copyViewId?: string
groupingFieldColumnId?: string
}) {
modalOpen = true
viewCreateType = type
viewCreateTitle = title
selectedViewId = copyViewId
kanbanGrpColumnId = groupingFieldColumnId
}
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgViewCreate'), {
'modelValue': isOpen,
title,
type,
meta,
'selectedViewId': copyViewId,
groupingFieldColumnId,
'viewList': views,
'onUpdate:modelValue': closeDialog,
'onCreated': async (view: ViewType) => {
closeDialog()
/** Handle view creation */
async function onCreate(view: ViewType) {
await loadViews()
router.push({ params: { viewTitle: view.title || '' } })
modalOpen = false
$e('a:view:create', { view: view.type })
},
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
@ -132,22 +129,12 @@ async function onCreate(view: ViewType) {
/>
<div v-if="isOpen" class="flex-1 flex flex-col min-h-0">
<LazySmartsheetSidebarMenuTop @open-modal="openModal" @deleted="loadViews" />
<LazySmartsheetSidebarMenuTop :views="views" @open-modal="openCreateDialog" @deleted="loadViews" />
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="!my-3 w-full border-b-1" />
<LazySmartsheetSidebarMenuBottom @open-modal="openModal" />
<LazySmartsheetSidebarMenuBottom @open-modal="openCreateDialog" />
</div>
<LazyDlgViewCreate
v-if="views"
v-model="modalOpen"
:title="viewCreateTitle"
:type="viewCreateType"
:selected-view-id="selectedViewId"
:grouping-field-column-id="kanbanGrpColumnId"
@created="onCreate"
/>
</a-layout-sider>
</template>

22
packages/nc-gui/composables/useDialog/index.ts

@ -1,8 +1,20 @@
import type { VNode } from '@vue/runtime-dom'
import { isVNode, render } from '@vue/runtime-dom'
import type { ComponentPublicInstance } from '@vue/runtime-core'
import type { MaybeRef } from '@vueuse/core'
import { isClient } from '@vueuse/core'
import { createEventHook, h, ref, toReactive, tryOnScopeDispose, useNuxtApp, watch } from '#imports'
import {
createEventHook,
getCurrentInstance,
getCurrentScope,
h,
ref,
toReactive,
tryOnScopeDispose,
unref,
useNuxtApp,
watch,
} from '#imports'
/**
* Programmatically create a component and attach it to the body (or a specific mount target), like a dialog or modal.
@ -38,7 +50,7 @@ import { createEventHook, h, ref, toReactive, tryOnScopeDispose, useNuxtApp, wat
export function useDialog(
componentOrVNode: any,
props: NonNullable<Parameters<typeof h>[1]> = {},
mountTarget?: Element | ComponentPublicInstance,
mountTarget?: MaybeRef<Element | ComponentPublicInstance>,
) {
if (typeof document === 'undefined' || !isClient) {
console.warn('[useDialog]: Cannot use outside of browser!')
@ -53,10 +65,12 @@ export function useDialog(
const vNodeRef = ref<VNode>()
mountTarget = mountTarget ? ('$el' in mountTarget ? (mountTarget.$el as HTMLElement) : mountTarget) : document.body
let _mountTarget = unref(mountTarget)
_mountTarget = _mountTarget ? ('$el' in _mountTarget ? (_mountTarget.$el as HTMLElement) : _mountTarget) : document.body
/** if specified, append vnode to mount target instead of document.body */
mountTarget.appendChild(domNode)
_mountTarget.appendChild(domNode)
/** When props change, we want to re-render the element with the new prop values */
const stop = watch(

2
packages/nc-gui/composables/useViews.ts

@ -18,7 +18,7 @@ export function useViews(meta: MaybeRef<TableType | undefined>) {
}
}
watch(meta, loadViews, { immediate: true })
watch(() => meta, loadViews, { immediate: true })
return { views: $$(views), loadViews }
}

1
packages/nc-gui/context/index.ts

@ -27,7 +27,6 @@ export const ReloadViewMetaHookInj: InjectionKey<EventHook<boolean | void>> = Sy
export const ReloadRowDataHookInj: InjectionKey<EventHook<boolean | void>> = Symbol('reload-row-data-injection')
export const OpenNewRecordFormHookInj: InjectionKey<EventHook<void>> = Symbol('open-new-record-form-injection')
export const FieldsInj: InjectionKey<Ref<any[]>> = Symbol('fields-injection')
export const ViewListInj: InjectionKey<Ref<ViewType[]>> = Symbol('view-list-injection')
export const EditModeInj: InjectionKey<Ref<boolean>> = Symbol('edit-mode-injection')
export const SharedViewPasswordInj: InjectionKey<Ref<string | null>> = Symbol('shared-view-password-injection')
export const CellUrlDisableOverlayInj: InjectionKey<Ref<boolean>> = Symbol('cell-url-disable-url')

1
packages/nc-gui/lang/en.json

@ -16,6 +16,7 @@
"cancel": "Cancel",
"submit": "Submit",
"create": "Create",
"duplicate": "Duplicate",
"insert": "Insert",
"delete": "Delete",
"update": "Update",

Loading…
Cancel
Save