Browse Source

Merge pull request #4141 from nocodb/fix/expanded-form-drawer

pull/4165/head
Braks 2 years ago committed by GitHub
parent
commit
6e10325cd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/cell/DatePicker.vue
  2. 2
      packages/nc-gui/components/cell/DateTimePicker.vue
  3. 6
      packages/nc-gui/components/cell/Text.vue
  4. 2
      packages/nc-gui/components/cell/TimePicker.vue
  5. 2
      packages/nc-gui/components/cell/YearPicker.vue
  6. 2
      packages/nc-gui/components/cell/attachment/utils.ts
  7. 2
      packages/nc-gui/components/shared-view/Gallery.vue
  8. 2
      packages/nc-gui/components/shared-view/Grid.vue
  9. 2
      packages/nc-gui/components/shared-view/Kanban.vue
  10. 3
      packages/nc-gui/components/smartsheet/Cell.vue
  11. 1
      packages/nc-gui/components/smartsheet/Gallery.vue
  12. 15
      packages/nc-gui/components/smartsheet/Grid.vue
  13. 2
      packages/nc-gui/components/smartsheet/Kanban.vue
  14. 26
      packages/nc-gui/components/smartsheet/expanded-form/Detached.vue
  15. 2
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  16. 10
      packages/nc-gui/components/tabs/Smartsheet.vue
  17. 2
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  18. 2
      packages/nc-gui/components/virtual-cell/HasMany.vue
  19. 2
      packages/nc-gui/components/virtual-cell/Lookup.vue
  20. 2
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  21. 41
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  22. 4
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  23. 43
      packages/nc-gui/composables/useDialog/index.ts
  24. 44
      packages/nc-gui/composables/useExpandedFormDetached/index.ts
  25. 8
      packages/nc-gui/composables/useRoles/index.ts
  26. 2
      packages/nc-gui/context/index.ts
  27. 2
      packages/nc-gui/layouts/base.vue
  28. 18
      packages/nc-gui/pages/[projectType]/view/[viewId].vue
  29. 6
      scripts/cypress/integration/common/1a_table_operations.js
  30. 15
      scripts/cypress/integration/common/1b_table_column_operations.js
  31. 6
      scripts/cypress/integration/common/2b_table_with_m2m_column.js

2
packages/nc-gui/components/cell/DatePicker.vue

@ -12,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const columnMeta = inject(ColumnInj, null)! const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
let isDateInvalid = $ref(false) let isDateInvalid = $ref(false)

2
packages/nc-gui/components/cell/DateTimePicker.vue

@ -12,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject() const { isMysql } = useProject()
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
let isDateInvalid = $ref(false) let isDateInvalid = $ref(false)

6
packages/nc-gui/components/cell/Text.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports' import { EditModeInj, ReadonlyInj, inject, ref, useVModel } from '#imports'
interface Props { interface Props {
modelValue?: string | null modelValue?: string | null
@ -12,6 +12,8 @@ const emits = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@ -19,7 +21,7 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<template> <template>
<input <input
v-if="editEnabled" v-if="!readonly && editEnabled"
:ref="focus" :ref="focus"
v-model="vModel" v-model="vModel"
class="h-full w-full outline-none bg-transparent" class="h-full w-full outline-none bg-transparent"

2
packages/nc-gui/components/cell/TimePicker.vue

@ -12,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject() const { isMysql } = useProject()
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
let isTimeInvalid = $ref(false) let isTimeInvalid = $ref(false)

2
packages/nc-gui/components/cell/YearPicker.vue

@ -10,7 +10,7 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
let isYearInvalid = $ref(false) let isYearInvalid = $ref(false)

2
packages/nc-gui/components/cell/attachment/utils.ts

@ -33,7 +33,7 @@ interface AttachmentProps extends File {
export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
(updateModelValue: (data: string | Record<string, any>[]) => void) => { (updateModelValue: (data: string | Record<string, any>[]) => void) => {
const isReadonly = inject(ReadonlyInj, false) const isReadonly = inject(ReadonlyInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))

2
packages/nc-gui/components/shared-view/Gallery.vue

@ -7,7 +7,7 @@ const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true) provide(ReadonlyInj, ref(true))
provide(MetaInj, meta) provide(MetaInj, meta)

2
packages/nc-gui/components/shared-view/Grid.vue

@ -28,7 +28,7 @@ useProvideSmartsheetStore(sharedView, meta, true, sorts, nestedFilters)
const reloadEventHook = createEventHook() const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true) provide(ReadonlyInj, ref(true))
provide(MetaInj, meta) provide(MetaInj, meta)
provide(ActiveViewInj, sharedView) provide(ActiveViewInj, sharedView)
provide(FieldsInj, ref(meta.value?.columns || [])) provide(FieldsInj, ref(meta.value?.columns || []))

2
packages/nc-gui/components/shared-view/Kanban.vue

@ -15,7 +15,7 @@ const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true) provide(ReadonlyInj, ref(true))
provide(MetaInj, meta) provide(MetaInj, meta)

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

@ -8,6 +8,7 @@ import {
IsFormInj, IsFormInj,
IsLockedInj, IsLockedInj,
IsPublicInj, IsPublicInj,
ReadonlyInj,
computed, computed,
inject, inject,
provide, provide,
@ -47,7 +48,7 @@ provide(EditModeInj, useVModel(props, 'editEnabled', emit))
provide(ActiveCellInj, active) provide(ActiveCellInj, active)
if (readOnly?.value) { if (readOnly?.value) {
provide(ReadonlyInj, readOnly.value) provide(ReadonlyInj, readOnly)
} }
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))

1
packages/nc-gui/components/smartsheet/Gallery.vue

@ -58,7 +58,6 @@ provide(IsGalleryInj, ref(true))
provide(IsGridInj, ref(false)) provide(IsGridInj, ref(false))
provide(PaginationDataInj, paginationData) provide(PaginationDataInj, paginationData)
provide(ChangePageInj, changePage) provide(ChangePageInj, changePage)
provide(ReadonlyInj, !isUIAllowed('xcDatatableEditable'))
const fields = inject(FieldsInj, ref([])) const fields = inject(FieldsInj, ref([]))

15
packages/nc-gui/components/smartsheet/Grid.vue

@ -33,6 +33,7 @@ import {
useI18n, useI18n,
useMetas, useMetas,
useMultiSelect, useMultiSelect,
useRoles,
useRoute, useRoute,
useSmartsheetStoreOrThrow, useSmartsheetStoreOrThrow,
useUIPermission, useUIPermission,
@ -51,12 +52,13 @@ const view = inject(ActiveViewInj, ref())
// keep a root fields variable and will get modified from // keep a root fields variable and will get modified from
// fields menu and get used in grid and gallery // fields menu and get used in grid and gallery
const fields = inject(FieldsInj, ref([])) const fields = inject(FieldsInj, ref([]))
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false)) const isLocked = inject(IsLockedInj, ref(false))
const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook()) const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook())
const openNewRecordFormHook = inject(OpenNewRecordFormHookInj, createEventHook()) const openNewRecordFormHook = inject(OpenNewRecordFormHookInj, createEventHook())
const { hasRole } = useRoles()
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable')) const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable'))
@ -225,8 +227,6 @@ provide(PaginationDataInj, paginationData)
provide(ChangePageInj, changePage) provide(ChangePageInj, changePage)
provide(ReadonlyInj, !hasEditPermission)
const disableUrlOverlay = ref(false) const disableUrlOverlay = ref(false)
provide(CellUrlDisableOverlayInj, disableUrlOverlay) provide(CellUrlDisableOverlayInj, disableUrlOverlay)
@ -564,7 +564,12 @@ watch(
<a-checkbox v-model:checked="row.rowMeta.selected" /> <a-checkbox v-model:checked="row.rowMeta.selected" />
</div> </div>
<span class="flex-1" /> <span class="flex-1" />
<div v-if="!readOnly && !isLocked" class="nc-expand" :class="{ 'nc-comment': row.rowMeta?.commentCount }">
<div
v-if="(!readOnly || hasRole('commenter', true) || hasRole('viewer', true)) && !isLocked"
class="nc-expand"
:class="{ 'nc-comment': row.rowMeta?.commentCount }"
>
<a-spin v-if="row.rowMeta.saving" class="!flex items-center" /> <a-spin v-if="row.rowMeta.saving" class="!flex items-center" />
<template v-else> <template v-else>
<span <span
@ -627,7 +632,7 @@ watch(
" "
:row-index="rowIndex" :row-index="rowIndex"
:active="selected.col === colIndex && selected.row === rowIndex" :active="selected.col === colIndex && selected.row === rowIndex"
@update:edit-enabled="editEnabled = false" @update:edit-enabled="editEnabled = $event"
@save="updateOrSaveRow(row, columnObj.title, state)" @save="updateOrSaveRow(row, columnObj.title, state)"
@navigate="onNavigate" @navigate="onNavigate"
@cancel="editEnabled = false" @cancel="editEnabled = false"

2
packages/nc-gui/components/smartsheet/Kanban.vue

@ -85,8 +85,6 @@ provide(IsGridInj, ref(false))
provide(IsKanbanInj, ref(true)) provide(IsKanbanInj, ref(true))
provide(ReadonlyInj, !isUIAllowed('xcDatatableEditable'))
const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable')) const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable'))
const fields = inject(FieldsInj, ref([])) const fields = inject(FieldsInj, ref([]))

26
packages/nc-gui/components/smartsheet/expanded-form/Detached.vue

@ -0,0 +1,26 @@
<script lang="ts" setup>
import { useExpandedFormDetached } from '#imports'
const { states, close } = useExpandedFormDetached()
const shouldClose = (isVisible: boolean, i: number) => {
if (!isVisible) close(i)
}
</script>
<template>
<template v-for="(state, i) of states" :key="`expanded-form-${i}`">
<LazySmartsheetExpandedForm
v-model="state.isOpen"
:row="state.row"
:load-row="state.loadRow"
:meta="state.meta"
:row-id="state.rowId"
:state="state.state"
:use-meta-fields="state.useMetaFields"
:view="state.view"
@update:model-value="shouldClose($event, i)"
@cancel="close(i)"
/>
</template>
</template>

2
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -9,6 +9,8 @@ import {
MetaInj, MetaInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
computedInject, computedInject,
createEventHook,
inject,
message, message,
provide, provide,
ref, ref,

10
packages/nc-gui/components/tabs/Smartsheet.vue

@ -7,6 +7,7 @@ import {
IsLockedInj, IsLockedInj,
MetaInj, MetaInj,
OpenNewRecordFormHookInj, OpenNewRecordFormHookInj,
ReadonlyInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
ReloadViewMetaHookInj, ReloadViewMetaHookInj,
TabMetaInj, TabMetaInj,
@ -18,6 +19,7 @@ import {
useMetas, useMetas,
useProvideKanbanViewStore, useProvideKanbanViewStore,
useProvideSmartsheetStore, useProvideSmartsheetStore,
useUIPermission,
} from '#imports' } from '#imports'
import type { TabItem } from '~/lib' import type { TabItem } from '~/lib'
@ -25,6 +27,8 @@ const props = defineProps<{
activeTab: TabItem activeTab: TabItem
}>() }>()
const { isUIAllowed } = useUIPermission()
const { metas } = useMetas() const { metas } = useMetas()
const activeTab = toRef(props, 'activeTab') const activeTab = toRef(props, 'activeTab')
@ -55,6 +59,10 @@ provide(OpenNewRecordFormHookInj, openNewRecordFormHook)
provide(FieldsInj, fields) provide(FieldsInj, fields)
provide(IsFormInj, isForm) provide(IsFormInj, isForm)
provide(TabMetaInj, activeTab) provide(TabMetaInj, activeTab)
provide(
ReadonlyInj,
computed(() => !isUIAllowed('xcDatatableEditable')),
)
</script> </script>
<template> <template>
@ -79,6 +87,8 @@ provide(TabMetaInj, activeTab)
</Transition> </Transition>
</div> </div>
<LazySmartsheetExpandedFormDetached />
<!-- Lazy loading the sidebar causes issues when deleting elements, i.e. it appears as if multiple elements are removed when they are not --> <!-- Lazy loading the sidebar causes issues when deleting elements, i.e. it appears as if multiple elements are removed when they are not -->
<SmartsheetSidebar v-if="meta" class="nc-right-sidebar" /> <SmartsheetSidebar v-if="meta" class="nc-right-sidebar" />
</div> </div>

2
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -31,7 +31,7 @@ const row = inject(RowInj)!
const active = inject(ActiveCellInj)! const active = inject(ActiveCellInj)!
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))

2
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -27,7 +27,7 @@ const reloadRowTrigger = inject(ReloadRowDataHookInj, createEventHook())
const isForm = inject(IsFormInj) const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)

2
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -17,7 +17,7 @@ import {
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()
provide(ReadonlyInj, true) provide(ReadonlyInj, ref(true))
const column = inject(ColumnInj)! as Ref<ColumnType & { colOptions: LookupType }> const column = inject(ColumnInj)! as Ref<ColumnType & { colOptions: LookupType }>

2
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -28,7 +28,7 @@ const reloadRowTrigger = inject(ReloadRowDataHookInj, createEventHook())
const isForm = inject(IsFormInj) const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)

41
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -1,5 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ActiveCellInj, IsFormInj, IsLockedInj, ReadonlyInj, inject, ref, useLTARStoreOrThrow, useUIPermission } from '#imports' import {
ActiveCellInj,
IsFormInj,
IsLockedInj,
ReadonlyInj,
inject,
ref,
useExpandedFormDetached,
useLTARStoreOrThrow,
useUIPermission,
} from '#imports'
interface Props { interface Props {
value?: string | number | boolean value?: string | number | boolean
@ -14,7 +24,7 @@ const { relatedTableMeta } = useLTARStoreOrThrow()!
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
const readOnly = inject(ReadonlyInj, false) const readOnly = inject(ReadonlyInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
@ -22,7 +32,19 @@ const isForm = inject(IsFormInj)!
const isLocked = inject(IsLockedInj, ref(false)) const isLocked = inject(IsLockedInj, ref(false))
const expandedFormDlg = ref(false) const { open } = useExpandedFormDetached()
function openExpandedForm() {
if (!readOnly && !isLocked.value) {
open({
isOpen: true,
row: { row: item, rowMeta: {}, oldRow: { ...item } },
meta: relatedTableMeta.value,
loadRow: true,
useMetaFields: true,
})
}
}
</script> </script>
<script lang="ts"> <script lang="ts">
@ -35,24 +57,13 @@ export default {
<div <div
class="chip group py-1 px-2 mr-1 my-1 flex items-center bg-blue-100/60 hover:bg-blue-100/40 rounded-[2px]" class="chip group py-1 px-2 mr-1 my-1 flex items-center bg-blue-100/60 hover:bg-blue-100/40 rounded-[2px]"
:class="{ active }" :class="{ active }"
@click="expandedFormDlg = true" @click="openExpandedForm"
> >
<span class="name">{{ value }}</span> <span class="name">{{ value }}</span>
<div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex items-center"> <div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex items-center">
<MdiCloseThick class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" @click.stop="emit('unlink')" /> <MdiCloseThick class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" @click.stop="emit('unlink')" />
</div> </div>
<Suspense>
<SmartsheetExpandedForm
v-if="!readOnly && !isLocked && expandedFormDlg"
v-model="expandedFormDlg"
:row="{ row: item, rowMeta: {}, oldRow: { ...item } }"
:meta="relatedTableMeta"
load-row
use-meta-fields
/>
</Suspense>
</div> </div>
</template> </template>

4
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -29,7 +29,7 @@ const isPublic = inject(IsPublicInj, ref(false))
const column = inject(ColumnInj) const column = inject(ColumnInj)
const readonly = inject(ReadonlyInj, false) const readonly = inject(ReadonlyInj, ref(false))
const { const {
childrenList, childrenList,
@ -181,7 +181,7 @@ watch(
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm
v-if="expandedFormRow && expandedFormDlg" v-if="expandedFormRow && expandedFormDlg"
v-model="expandedFormDlg" v-model="expandedFormDlg"
:row="{ row: expandedFormRow }" :row="{ row: expandedFormRow, oldRow: expandedFormRow, rowMeta: {} }"
:meta="relatedTableMeta" :meta="relatedTableMeta"
load-row load-row
use-meta-fields use-meta-fields

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

@ -1,17 +1,22 @@
import type { VNode } from '@vue/runtime-dom' import type { AppContext, VNode } from '@vue/runtime-dom'
import { isVNode, render } from '@vue/runtime-dom' import { Suspense, isVNode, render } from '@vue/runtime-dom'
import type { ComponentPublicInstance } from '@vue/runtime-core' import type { ComponentPublicInstance } from '@vue/runtime-core'
import type { MaybeRef } from '@vueuse/core' import type { MaybeRef } from '@vueuse/core'
import { isClient } from '@vueuse/core' import { isClient } from '@vueuse/core'
import { createEventHook, h, ref, toReactive, tryOnScopeDispose, unref, useNuxtApp, watch } from '#imports' import { createEventHook, h, ref, toReactive, tryOnScopeDispose, unref, useNuxtApp, watch } from '#imports'
interface UseDialogOptions {
target: MaybeRef<HTMLElement | ComponentPublicInstance>
context: Partial<AppContext>
}
/** /**
* Programmatically create a component and attach it to the body (or a specific mount target), like a dialog or modal. * Programmatically create a component and attach it to the body (or a specific mount target), like a dialog or modal.
* This composable is not SSR friendly - it should be used only on the client. * This composable is not SSR friendly - it should be used only on the client.
* *
* @param componentOrVNode The component to create and attach. Can be a VNode or a component definition. * @param componentOrVNode The component to create and attach. Can be a VNode or a component definition.
* @param props The props to pass to the component. * @param props The props to pass to the component.
* @param mountTarget The target to attach the component to. Defaults to the document body * @param options Additional options to use {@see UseDialogOptions}
* *
* @example * @example
* import { useDialog } from '#imports' * import { useDialog } from '#imports'
@ -39,7 +44,7 @@ import { createEventHook, h, ref, toReactive, tryOnScopeDispose, unref, useNuxtA
export function useDialog( export function useDialog(
componentOrVNode: any, componentOrVNode: any,
props: NonNullable<Parameters<typeof h>[1]> = {}, props: NonNullable<Parameters<typeof h>[1]> = {},
mountTarget?: MaybeRef<Element | ComponentPublicInstance>, { target, context }: Partial<UseDialogOptions> = {},
) { ) {
if (typeof document === 'undefined' || !isClient) { if (typeof document === 'undefined' || !isClient) {
console.warn('[useDialog]: Cannot use outside of browser!') console.warn('[useDialog]: Cannot use outside of browser!')
@ -54,24 +59,36 @@ export function useDialog(
const vNodeRef = ref<VNode>() const vNodeRef = ref<VNode>()
let _mountTarget = unref(mountTarget) const mountTarget = ref<HTMLElement>()
_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)
/** When props change, we want to re-render the element with the new prop values */ /** When props change, we want to re-render the element with the new prop values */
const stop = watch( const stop = watch(
toReactive(props), toReactive(props),
(reactiveProps) => { (reactiveProps) => {
const _mountTarget = unref(target)
/**
* If it's a component instance, use the instance's root element (`$el`), otherwise use the element itself
* If no target is specified, use the document body
*/
mountTarget.value = _mountTarget
? '$el' in _mountTarget
? (_mountTarget.$el as HTMLElement)
: _mountTarget
: document.body
/** if specified, append vnode to mount target instead of document.body */
mountTarget.value.appendChild(domNode)
// if it's a vnode, just render it, otherwise wrap in `h` to create a vnode
const vNode = isVNode(componentOrVNode) ? componentOrVNode : h(componentOrVNode, reactiveProps) const vNode = isVNode(componentOrVNode) ? componentOrVNode : h(componentOrVNode, reactiveProps)
vNode.appContext = useNuxtApp().vueApp._context vNode.appContext = { ...useNuxtApp().vueApp._context, ...context }
vNodeRef.value = vNode vNodeRef.value = vNode
render(vNode, domNode) // wrap in suspense to resolve potential promises
render(h(Suspense, vNode), domNode)
if (!isMounted) mountedHook.trigger() if (!isMounted) mountedHook.trigger()
}, },
@ -90,7 +107,7 @@ export function useDialog(
setTimeout(() => { setTimeout(() => {
try { try {
;(_mountTarget as HTMLElement)?.removeChild(domNode) ;(mountTarget.value as HTMLElement)?.removeChild(domNode)
} catch (e) {} } catch (e) {}
}, 100) }, 100)

44
packages/nc-gui/composables/useExpandedFormDetached/index.ts

@ -0,0 +1,44 @@
import type { TableType, ViewType } from 'nocodb-sdk'
import { createEventHook, ref, useInjectionState } from '#imports'
import type { Row } from '~/lib'
interface UseExpandedFormDetachedProps {
'isOpen'?: boolean
'row': Row | null
'state'?: Record<string, any> | null
'meta': TableType
'loadRow'?: boolean
'useMetaFields'?: boolean
'rowId'?: string
'view'?: ViewType
'onCancel'?: Function
'onUpdate:modelValue'?: Function
}
const [setup, use] = useInjectionState(() => {
return ref<UseExpandedFormDetachedProps[]>([])
})
export function useExpandedFormDetached() {
let states = use()!
if (!states) {
states = setup()
}
const closeHook = createEventHook<void>()
const index = ref(-1)
const open = (props: UseExpandedFormDetachedProps) => {
states.value.push(props)
index.value = states.value.length - 1
}
const close = (i?: number) => {
states.value.splice(i || index.value, 1)
if (index.value === i || !i) closeHook.trigger()
}
return { states, open, close, onClose: closeHook.on }
}

8
packages/nc-gui/composables/useRoles/index.ts

@ -12,7 +12,7 @@ import type { ProjectRole, Role, Roles } from '~/lib'
* * `loadProjectRoles` - a function to load the project roles for a specific project (by id) * * `loadProjectRoles` - a function to load the project roles for a specific project (by id)
*/ */
export const useRoles = createSharedComposable(() => { export const useRoles = createSharedComposable(() => {
const { user } = useGlobal() const { user, previewAs } = useGlobal()
const { api } = useApi() const { api } = useApi()
@ -57,7 +57,11 @@ export const useRoles = createSharedComposable(() => {
} }
} }
function hasRole(role: Role | ProjectRole | string) { function hasRole(role: Role | ProjectRole | string, includePreviewRoles = false) {
if (previewAs.value && includePreviewRoles) {
return previewAs.value === role
}
return allRoles.value[role] return allRoles.value[role]
} }

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

@ -20,7 +20,7 @@ export const IsKanbanInj: InjectionKey<Ref<boolean>> = Symbol('is-kanban-injecti
export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection') export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection')
export const CellValueInj: InjectionKey<Ref<any>> = Symbol('cell-value-injection') export const CellValueInj: InjectionKey<Ref<any>> = Symbol('cell-value-injection')
export const ActiveViewInj: InjectionKey<Ref<ViewType>> = Symbol('active-view-injection') export const ActiveViewInj: InjectionKey<Ref<ViewType>> = Symbol('active-view-injection')
export const ReadonlyInj: InjectionKey<boolean> = Symbol('readonly-injection') export const ReadonlyInj: InjectionKey<Ref<boolean>> = Symbol('readonly-injection')
/** when bool is passed, it indicates if a loading spinner should be visible while reloading */ /** when bool is passed, it indicates if a loading spinner should be visible while reloading */
export const ReloadViewDataHookInj: InjectionKey<EventHook<boolean | void>> = Symbol('reload-view-data-injection') export const ReloadViewDataHookInj: InjectionKey<EventHook<boolean | void>> = Symbol('reload-view-data-injection')
export const ReloadViewMetaHookInj: InjectionKey<EventHook<boolean | void>> = Symbol('reload-view-meta-injection') export const ReloadViewMetaHookInj: InjectionKey<EventHook<boolean | void>> = Symbol('reload-view-meta-injection')

2
packages/nc-gui/layouts/base.vue

@ -127,7 +127,7 @@ hooks.hook('page:finish', () => {
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> Switch language</template> <template #title> Switch language</template>
<LazyGeneralLanguage v-if="!signedIn" class="nc-lang-btn" /> <LazyGeneralLanguage v-if="!signedIn && !route.params.projectId" class="nc-lang-btn" />
</a-tooltip> </a-tooltip>
<div class="w-full h-full overflow-hidden"> <div class="w-full h-full overflow-hidden">

18
packages/nc-gui/pages/[projectType]/view/[viewId].vue

@ -1,16 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { definePageMeta, extractSdkResponseErrorMsg, message, ref, useRoute, useSharedView } from '#imports'
ReadonlyInj,
ReloadViewDataHookInj,
createEventHook,
definePageMeta,
extractSdkResponseErrorMsg,
message,
provide,
ref,
useRoute,
useSharedView,
} from '#imports'
definePageMeta({ definePageMeta({
public: true, public: true,
@ -20,11 +9,6 @@ definePageMeta({
const route = useRoute() const route = useRoute()
const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true)
const { loadSharedView } = useSharedView() const { loadSharedView } = useSharedView()
const showPassword = ref(false) const showPassword = ref(false)

6
scripts/cypress/integration/common/1a_table_operations.js

@ -43,6 +43,7 @@ export const genTest = (apiType, dbType) => {
return cy.get("tbody > tr").eq(row).find("td.ant-table-cell").eq(col); return cy.get("tbody > tr").eq(row).find("td.ant-table-cell").eq(col);
}; };
describe("Check Audit Tab Cells", () => {
it("Open Audit tab", () => { it("Open Audit tab", () => {
// http://localhost:8080/api/v1/db/meta/projects/p_bxp57hmks0n5o2/audits?offset=0&limit=25 // http://localhost:8080/api/v1/db/meta/projects/p_bxp57hmks0n5o2/audits?offset=0&limit=25
cy.intercept("/**/audits?offset=*&limit=*").as("waitForPageLoad"); cy.intercept("/**/audits?offset=*&limit=*").as("waitForPageLoad");
@ -67,9 +68,12 @@ export const genTest = (apiType, dbType) => {
getAuditCell(1, 0).contains("TABLE").should("exist"); getAuditCell(1, 0).contains("TABLE").should("exist");
getAuditCell(1, 1).contains("CREATED").should("exist"); getAuditCell(1, 1).contains("CREATED").should("exist");
getAuditCell(1, 3).contains("user@nocodb.com").should("exist"); getAuditCell(1, 3).contains("user@nocodb.com").should("exist");
});
after(() => {
settingsPage.closeMenu(); settingsPage.closeMenu();
}); })
})
it("Table Rename operation", () => { it("Table Rename operation", () => {
cy.get(".nc-project-tree-tbl-City").should("exist").click(); cy.get(".nc-project-tree-tbl-City").should("exist").click();

15
scripts/cypress/integration/common/1b_table_column_operations.js

@ -36,11 +36,6 @@ export const genTest = (apiType, dbType) => {
const randVal = "Test@1234.com"; const randVal = "Test@1234.com";
const updatedRandVal = "Updated@1234.com"; const updatedRandVal = "Updated@1234.com";
// before(() => {
// cy.restoreLocalStorage();
// cy.createTable(name);
// })
beforeEach(() => { beforeEach(() => {
cy.restoreLocalStorage(); cy.restoreLocalStorage();
}); });
@ -49,11 +44,6 @@ export const genTest = (apiType, dbType) => {
cy.saveLocalStorage(); cy.saveLocalStorage();
}); });
// // delete table
// after(() => {
// cy.deleteTable(name, dbType);
// });
it("Create Table Column", () => { it("Create Table Column", () => {
cy.createTable(name); cy.createTable(name);
mainPage.addColumn(colName, name); mainPage.addColumn(colName, name);
@ -138,8 +128,9 @@ export const genTest = (apiType, dbType) => {
.find(".nc-row-no") .find(".nc-row-no")
.should("exist") .should("exist")
.eq(0) .eq(0)
.trigger("mouseover", { force: true }); .trigger("mouseover", { force: true })
cy.get(".nc-row-expand").click({ force: true }); .get(".nc-row-expand")
.click({ force: true });
// wait for page render to complete // wait for page render to complete
cy.get('button:contains("Save row"):visible').should("exist"); cy.get('button:contains("Save row"):visible').should("exist");

6
scripts/cypress/integration/common/2b_table_with_m2m_column.js

@ -49,8 +49,7 @@ export const genTest = (apiType, dbType) => {
.getCell("Film List", 1) .getCell("Film List", 1)
.should("exist") .should("exist")
.trigger("mouseover") .trigger("mouseover")
.click(); .get(".nc-action-icon").eq(0).should("exist").click({ force: true });
cy.get(".nc-action-icon").eq(0).should("exist").click({ force: true });
// GUI-v2 Kludge: // GUI-v2 Kludge:
// validations // validations
@ -98,8 +97,7 @@ export const genTest = (apiType, dbType) => {
.getCell("Film List", 1) .getCell("Film List", 1)
.should("exist") .should("exist")
.trigger("mouseover") .trigger("mouseover")
.click(); .get(".nc-action-icon").eq(0).should("exist").click({ force: true });
cy.get(".nc-action-icon").eq(0).should("exist").click({ force: true });
cy.getActiveModal(".nc-modal-child-list") cy.getActiveModal(".nc-modal-child-list")
.find(".ant-card") .find(".ant-card")

Loading…
Cancel
Save