Browse Source

Nc Refactor: dialog modals (#8743)

* fix(nc-gui): update view create modal for grid & kanban

* fix(nc-gui): update calendar view create modal

* feat(nc-gui): add support to select gallery cover image while creating/duplicating view

* fix(nc-gui): update view icon size in view create modal

* fix(nc-gui): add input shadow effect

* refactor(nc-gui): dlg ui

* chore(nc-gui): lint

* fix(nc-gui): small changes

* fix(nc-gui): review changes

* chore: revert display message

* chore: lint

* fix(nc-gui): truncate field name

* fix(nc-gui): ignore empty lines at the end in form view #3104

---------

Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/8767/head
Ramesh Mane 4 months ago committed by GitHub
parent
commit
50f287f215
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 64
      packages/nc-gui/assets/style.scss
  2. 2
      packages/nc-gui/components/cell/RichText.vue
  3. 28
      packages/nc-gui/components/cell/attachment/RenameFile.vue
  4. 3
      packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue
  5. 4
      packages/nc-gui/components/dashboard/TreeView/ViewsList.vue
  6. 5
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  7. 2
      packages/nc-gui/components/dlg/ProjectDelete.vue
  8. 11
      packages/nc-gui/components/dlg/ProjectDuplicate.vue
  9. 38
      packages/nc-gui/components/dlg/TableCreate.vue
  10. 2
      packages/nc-gui/components/dlg/TableDelete.vue
  11. 11
      packages/nc-gui/components/dlg/TableDuplicate.vue
  12. 13
      packages/nc-gui/components/dlg/TableRename.vue
  13. 167
      packages/nc-gui/components/dlg/ViewCreate.vue
  14. 2
      packages/nc-gui/components/dlg/ViewDelete.vue
  15. 2
      packages/nc-gui/components/dlg/WorkspaceDelete.vue
  16. 7
      packages/nc-gui/components/general/DeleteModal.vue
  17. 2
      packages/nc-gui/components/nc/Select.vue
  18. 6
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  19. 23
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  20. 15
      packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue
  21. 1
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  22. 15
      packages/nc-gui/components/workspace/CreateProjectDlg.vue
  23. 3
      packages/nc-gui/lang/en.json
  24. 1
      packages/nc-gui/windi.config.ts
  25. 5
      packages/nocodb/src/models/GalleryView.ts
  26. 5
      packages/nocodb/src/models/KanbanView.ts

64
packages/nc-gui/assets/style.scss

@ -60,6 +60,55 @@ main {
@apply !rounded-lg !py-2 !px-3 mb-1; @apply !rounded-lg !py-2 !px-3 mb-1;
} }
.nc-input-sm {
@apply !rounded-lg !py-1 !px-3;
}
.nc-input-shadow {
&.ant-input {
&:not(:hover):not(:focus):not(:disabled) {
@apply shadow-default border-gray-200;
}
&:hover:not(:focus):not(:disabled) {
@apply border-gray-200 shadow-hover;
}
&:focus {
@apply shadow-selected ring-0;
}
}
}
.ant-form-item-explain {
@apply !min-h-5;
.ant-form-item-explain-error {
@apply text-sm;
&:first-child {
@apply mt-1;
}
}
}
.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper,
.ant-form-item-has-error
:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover,
.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input,
.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover,
.ant-form-item-has-error
:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,
.ant-form-item-has-error
:not(.ant-input-number-affix-wrapper-disabled):not(
.ant-input-number-affix-wrapper-borderless
).ant-input-number-affix-wrapper:hover {
border-color: var(--ant-error-color) !important;
}
ant-form-item-explain-error {
&:first-child {
@apply mt-2;
}
}
.mobile { .mobile {
.nc-scrollbar-md, .nc-scrollbar-md,
.nc-scrollbar-lg, .nc-scrollbar-lg,
@ -266,6 +315,21 @@ a {
} }
} }
.ant-form-item:not(.ant-form-item-has-error)
.nc-select-shadow.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input)
.ant-select-selector {
@apply !shadow-selected;
}
.ant-form-item.ant-form-item-has-error
.nc-select-shadow.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused
.ant-select-selector,
.ant-form-item.ant-form-item-has-error
.nc-select-shadow.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open
.ant-select-selector {
@apply !shadow-error;
}
// select dropdown border style // select dropdown border style
.ant-select-dropdown { .ant-select-dropdown {
@apply border-1 border-gray-200 rounded-lg; @apply border-1 border-gray-200 rounded-lg;

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

@ -217,7 +217,7 @@ const onFocusWrapper = () => {
if (props.syncValueChange) { if (props.syncValueChange) {
watch([vModel, editor], () => { watch([vModel, editor], () => {
setEditorContent(vModel.value) setEditorContent(isFormField.value ? (vModel.value || '')?.replace(/(<br \/>)+$/g, '') : vModel.value)
}) })
} }

28
packages/nc-gui/components/cell/attachment/RenameFile.vue

@ -46,28 +46,24 @@ onMounted(() => {
</script> </script>
<template> <template>
<GeneralModal v-model:visible="visible" class="nc-attachment-rename-modal !w-[30rem]"> <GeneralModal v-model:visible="visible" class="nc-attachment-rename-modal" size="small">
<div class="flex flex-col items-center justify-center h-full p-8"> <div class="flex flex-col items-center justify-center h-full p-6">
<div class="text-lg font-semibold self-start mb-4">{{ $t('title.renameFile') }}</div> <div class="text-lg font-semibold self-start mb-5">{{ $t('title.renameFile') }}</div>
<a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)"> <a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)">
<a-form-item class="w-full" name="title" :rules="rules.title"> <a-form-item class="w-full !mb-0" name="title" :rules="rules.title">
<a-input ref="inputEl" v-model:value="form.title" class="w-full" :placeholder="$t('general.rename')" /> <a-input
ref="inputEl"
v-model:value="form.title"
class="w-full nc-input-sm nc-input-shadow"
:placeholder="$t('general.rename')"
/>
</a-form-item> </a-form-item>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">
<NcButton key="back" html-type="back" type="secondary">{{ $t('general.cancel') }}</NcButton> <NcButton key="back" html-type="back" size="small" type="secondary">{{ $t('general.cancel') }}</NcButton>
<NcButton key="submit" html-type="submit" type="primary">{{ $t('general.confirm') }}</NcButton> <NcButton key="submit" html-type="submit" size="small" type="primary">{{ $t('general.confirm') }}</NcButton>
</div> </div>
</a-form> </a-form>
</div> </div>
</GeneralModal> </GeneralModal>
</template> </template>
<style scoped lang="scss">
.nc-attachment-rename-modal {
.ant-input-affix-wrapper,
.ant-input {
@apply !appearance-none my-1 border-1 border-solid border-primary border-opacity-50 rounded;
}
}
</style>

3
packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue

@ -37,6 +37,7 @@ async function onOpenModal({
copyViewId, copyViewId,
groupingFieldColumnId, groupingFieldColumnId,
calendarRange, calendarRange,
coverImageColumnId,
}: { }: {
title?: string title?: string
type: ViewTypes type: ViewTypes
@ -46,6 +47,7 @@ async function onOpenModal({
fk_from_column_id: string fk_from_column_id: string
fk_to_column_id: string | null // for ee only fk_to_column_id: string | null // for ee only
}> }>
coverImageColumnId?: string
}) { }) {
if (isViewListLoading.value) return if (isViewListLoading.value) return
@ -69,6 +71,7 @@ async function onOpenModal({
'selectedViewId': copyViewId, 'selectedViewId': copyViewId,
calendarRange, calendarRange,
groupingFieldColumnId, groupingFieldColumnId,
coverImageColumnId,
'onUpdate:modelValue': closeDialog, 'onUpdate:modelValue': closeDialog,
'onCreated': async (view: ViewType) => { 'onCreated': async (view: ViewType) => {
closeDialog() closeDialog()

4
packages/nc-gui/components/dashboard/TreeView/ViewsList.vue

@ -13,6 +13,7 @@ interface Emits {
title?: string title?: string
copyViewId?: string copyViewId?: string
groupingFieldColumnId?: string groupingFieldColumnId?: string
coverImageColumnId?: string
}, },
): void ): void
@ -337,6 +338,7 @@ function onOpenModal({
copyViewId, copyViewId,
groupingFieldColumnId, groupingFieldColumnId,
calendarRange, calendarRange,
coverImageColumnId,
}: { }: {
title?: string title?: string
type: ViewTypes type: ViewTypes
@ -346,6 +348,7 @@ function onOpenModal({
fk_from_column_id: string fk_from_column_id: string
fk_to_column_id: string | null // for ee only fk_to_column_id: string | null // for ee only
}> }>
coverImageColumnId?: string
}) { }) {
const isOpen = ref(true) const isOpen = ref(true)
@ -358,6 +361,7 @@ function onOpenModal({
groupingFieldColumnId, groupingFieldColumnId,
'views': views, 'views': views,
calendarRange, calendarRange,
coverImageColumnId,
'onUpdate:modelValue': closeDialog, 'onUpdate:modelValue': closeDialog,
'onCreated': async (view: ViewType) => { 'onCreated': async (view: ViewType) => {
closeDialog() closeDialog()

5
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -21,7 +21,10 @@ interface Emits {
(event: 'delete', view: ViewType): void (event: 'delete', view: ViewType): void
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string; groupingFieldColumnId?: string }): void (
event: 'openModal',
data: { type: ViewTypes; title?: string; copyViewId?: string; groupingFieldColumnId?: string; coverImageColumnId?: string },
): void
} }
const props = defineProps<Props>() const props = defineProps<Props>()

2
packages/nc-gui/components/dlg/ProjectDelete.vue

@ -51,7 +51,7 @@ const onDelete = async () => {
<template> <template>
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.project')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.project')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="base" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="base" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700">
<GeneralProjectIcon :color="parseProp(base.meta).iconColor" :type="base.type" class="nc-view-icon w-6 h-6 mx-1" /> <GeneralProjectIcon :color="parseProp(base.meta).iconColor" :type="base.type" class="nc-view-icon w-6 h-6 mx-1" />
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"

11
packages/nc-gui/components/dlg/ProjectDuplicate.vue

@ -128,18 +128,17 @@ const isEaster = ref(false)
<GeneralModal <GeneralModal
v-if="base" v-if="base"
v-model:visible="dialogShow" v-model:visible="dialogShow"
:closable="!isLoading"
:mask-closable="!isLoading" :mask-closable="!isLoading"
:keyboard="!isLoading" :keyboard="!isLoading"
class="!w-[30rem]" class="!w-[30rem]"
wrap-class-name="nc-modal-base-duplicate" wrap-class-name="nc-modal-base-duplicate"
> >
<div> <div>
<div class="prose-xl font-bold self-center" @dblclick="isEaster = !isEaster"> <div class="font-medium text-lg text-gray-800 self-center" @dblclick="isEaster = !isEaster">
{{ $t('general.duplicate') }} {{ $t('objects.project') }} {{ $t('general.duplicate') }} {{ $t('objects.project') }}
</div> </div>
<div class="mt-4">{{ $t('msg.warning.duplicateProject') }}</div> <div class="mt-5">{{ $t('msg.warning.duplicateProject') }}</div>
<div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div> <div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div>
@ -154,8 +153,10 @@ const isEaster = ref(false)
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">
<NcButton v-if="!isLoading" key="back" type="secondary" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton> <NcButton v-if="!isLoading" key="back" type="secondary" size="small" @click="dialogShow = false">{{
<NcButton key="submit" v-e="['a:base:duplicate']" :loading="isLoading" @click="_duplicate" $t('general.cancel')
}}</NcButton>
<NcButton key="submit" v-e="['a:base:duplicate']" size="small" :loading="isLoading" @click="_duplicate"
>{{ $t('general.confirm') }} >{{ $t('general.confirm') }}
</NcButton> </NcButton>
</div> </div>

38
packages/nc-gui/components/dlg/TableCreate.vue

@ -121,20 +121,36 @@ onMounted(() => {
</script> </script>
<template> <template>
<NcModal v-model:visible="dialogShow" :header="$t('activity.createTable')" size="small" @keydown.esc="dialogShow = false"> <NcModal
v-model:visible="dialogShow"
:show-separator="false"
:header="$t('activity.createTable')"
size="small"
@keydown.esc="dialogShow = false"
>
<template #header> <template #header>
<div class="flex flex-row items-center gap-x-2"> <div class="flex flex-row items-center gap-x-2 text-base text-gray-800">
<GeneralIcon icon="table" class="!text-gray-600/75" /> <GeneralIcon icon="table" class="!text-gray-600 w-5 h-5" />
{{ $t('activity.createTable') }} {{ $t('activity.createTable') }}
</div> </div>
</template> </template>
<div class="flex flex-col mt-2"> <div class="flex flex-col mt-1">
<a-form :model="table" name="create-new-table-form" @keydown.enter="_createTable" @keydown.esc="dialogShow = false"> <a-form
<a-form-item v-bind="validateInfos.title" :class="{ '!mb-1': isSnowflake(props.sourceId) }"> :model="table"
name="create-new-table-form"
class="flex flex-col gap-5"
@keydown.enter="_createTable"
@keydown.esc="dialogShow = false"
>
<div>
<a-form-item
v-bind="validateInfos.title"
:class="{ '!mb-1': isSnowflake(props.sourceId), '!mb-0': !isSnowflake(props.sourceId) }"
>
<a-input <a-input
ref="inputEl" ref="inputEl"
v-model:value="table.title" v-model:value="table.title"
class="nc-input-md" class="nc-input-sm nc-input-shadow"
hide-details hide-details
data-testid="create-table-title-input" data-testid="create-table-title-input"
:placeholder="$t('msg.info.enterTableName')" :placeholder="$t('msg.info.enterTableName')"
@ -143,7 +159,8 @@ onMounted(() => {
<template v-if="isSnowflake(props.sourceId)"> <template v-if="isSnowflake(props.sourceId)">
<a-checkbox v-model:checked="table.is_hybrid" class="!flex flex-row items-center"> Hybrid Table </a-checkbox> <a-checkbox v-model:checked="table.is_hybrid" class="!flex flex-row items-center"> Hybrid Table </a-checkbox>
</template> </template>
<div class="nc-table-advanced-options" :class="{ active: isAdvanceOptVisible }"> </div>
<div v-if="isAdvanceOptVisible" class="nc-table-advanced-options" :class="{ active: isAdvanceOptVisible }">
<div> <div>
<div class="mb-1"> <div class="mb-1">
<!-- Add Default Columns --> <!-- Add Default Columns -->
@ -171,12 +188,13 @@ onMounted(() => {
</a-row> </a-row>
</div> </div>
</div> </div>
<div class="flex flex-row justify-end gap-x-2 mt-2"> <div class="flex flex-row justify-end gap-x-2">
<NcButton type="secondary" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton> <NcButton type="secondary" size="small" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton>
<NcButton <NcButton
v-e="['a:table:create']" v-e="['a:table:create']"
type="primary" type="primary"
size="small"
:disabled="validateInfos.title.validateStatus === 'error'" :disabled="validateInfos.title.validateStatus === 'error'"
:loading="creating" :loading="creating"
@click="_createTable" @click="_createTable"

2
packages/nc-gui/components/dlg/TableDelete.vue

@ -110,7 +110,7 @@ const onDelete = async () => {
<template> <template>
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.table')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.table')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="table" class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="table" class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700">
<GeneralTableIcon :meta="table" class="nc-view-icon" /> <GeneralTableIcon :meta="table" class="nc-view-icon" />
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"

11
packages/nc-gui/components/dlg/TableDuplicate.vue

@ -129,7 +129,6 @@ const isEaster = ref(false)
<GeneralModal <GeneralModal
v-model:visible="dialogShow" v-model:visible="dialogShow"
:class="{ active: dialogShow }" :class="{ active: dialogShow }"
:closable="!isLoading"
:mask-closable="!isLoading" :mask-closable="!isLoading"
:keyboard="!isLoading" :keyboard="!isLoading"
centered centered
@ -139,11 +138,11 @@ const isEaster = ref(false)
@keydown.esc="dialogShow = false" @keydown.esc="dialogShow = false"
> >
<div> <div>
<div class="prose-xl font-bold self-center" @dblclick="isEaster = !isEaster"> <div class="font-medium text-lg text-gray-800 self-center" @dblclick="isEaster = !isEaster">
{{ $t('general.duplicate') }} {{ $t('objects.table') }} {{ $t('general.duplicate') }} {{ $t('objects.table') }}
</div> </div>
<div class="mt-4">{{ $t('msg.warning.duplicateTable') }}</div> <div class="mt-5">{{ $t('msg.warning.duplicateTable') }}</div>
<div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div> <div class="prose-md self-center text-gray-500 mt-4">{{ $t('title.advancedSettings') }}</div>
@ -158,8 +157,10 @@ const isEaster = ref(false)
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">
<NcButton v-if="!isLoading" key="back" type="secondary" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton> <NcButton v-if="!isLoading" key="back" type="secondary" size="small" @click="dialogShow = false">{{
<NcButton key="submit" v-e="['a:table:duplicate']" type="primary" :loading="isLoading" @click="_duplicate" $t('general.cancel')
}}</NcButton>
<NcButton key="submit" v-e="['a:table:duplicate']" type="primary" size="small" :loading="isLoading" @click="_duplicate"
>{{ $t('general.confirm') }} >{{ $t('general.confirm') }}
</NcButton> </NcButton>
</div> </div>

13
packages/nc-gui/components/dlg/TableRename.vue

@ -174,33 +174,34 @@ const renameTable = async (undo = false, disableTitleDiffCheck?: boolean | undef
</script> </script>
<template> <template>
<NcModal v-model:visible="dialogShow" size="small"> <NcModal v-model:visible="dialogShow" size="small" :show-separator="false">
<template #header> <template #header>
<div class="flex flex-row items-center gap-x-2"> <div class="flex flex-row items-center gap-x-2">
<GeneralIcon icon="rename" /> <GeneralIcon icon="rename" />
{{ $t('activity.renameTable') }} {{ $t('activity.renameTable') }}
</div> </div>
</template> </template>
<div class="mt-2"> <div class="mt-1">
<a-form :model="formState" name="create-new-table-form"> <a-form :model="formState" name="create-new-table-form">
<a-form-item v-bind="validateInfos.title"> <a-form-item v-bind="validateInfos.title">
<a-input <a-input
ref="inputEl" ref="inputEl"
v-model:value="formState.title" v-model:value="formState.title"
class="nc-input-md" class="nc-input-sm nc-input-shadow"
hide-details hide-details
size="large" size="small"
:placeholder="$t('msg.info.enterTableName')" :placeholder="$t('msg.info.enterTableName')"
@keydown.enter="() => renameTable()" @keydown.enter="() => renameTable()"
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="flex flex-row justify-end gap-x-2 mt-6"> <div class="flex flex-row justify-end gap-x-2 mt-5">
<NcButton type="secondary" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton> <NcButton type="secondary" size="small" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton>
<NcButton <NcButton
key="submit" key="submit"
type="primary" type="primary"
size="small"
:disabled="validateInfos.title.validateStatus === 'error' || formState.title?.trim() === tableMeta.title" :disabled="validateInfos.title.validateStatus === 'error' || formState.title?.trim() === tableMeta.title"
label="Rename Table" label="Rename Table"
loading-label="Renaming Table" loading-label="Renaming Table"

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

@ -17,6 +17,7 @@ interface Props {
fk_from_column_id: string fk_from_column_id: string
fk_to_column_id: string | null // for ee only fk_to_column_id: string | null // for ee only
}> }>
coverImageColumnId?: string
} }
interface Emits { interface Emits {
@ -38,6 +39,7 @@ interface Form {
fk_from_column_id: string fk_from_column_id: string
fk_to_column_id: string | null // for ee only fk_to_column_id: string | null // for ee only
}> }>
fk_cover_image_col_id: string | null
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -45,6 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
groupingFieldColumnId: undefined, groupingFieldColumnId: undefined,
geoDataFieldColumnId: undefined, geoDataFieldColumnId: undefined,
calendarRange: undefined, calendarRange: undefined,
coverImageColumnId: undefined,
}) })
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
@ -55,7 +58,7 @@ const { viewsByTable } = storeToRefs(useViewsStore())
const { refreshCommandPalette } = useCommandPalette() const { refreshCommandPalette } = useCommandPalette()
const { selectedViewId, groupingFieldColumnId, geoDataFieldColumnId, tableId } = toRefs(props) const { selectedViewId, groupingFieldColumnId, geoDataFieldColumnId, tableId, coverImageColumnId } = toRefs(props)
const meta = ref<TableType | undefined>() const meta = ref<TableType | undefined>()
@ -88,13 +91,14 @@ const form = reactive<Form>({
fk_grp_col_id: null, fk_grp_col_id: null,
fk_geo_data_col_id: null, fk_geo_data_col_id: null,
calendar_range: props.calendarRange || [], calendar_range: props.calendarRange || [],
fk_cover_image_col_id: null,
}) })
const viewSelectFieldOptions = ref<SelectProps['options']>([]) const viewSelectFieldOptions = ref<SelectProps['options']>([])
const viewNameRules = [ const viewNameRules = [
// name is required // name is required
{ required: true, message: `${t('labels.viewName')} ${t('general.required')}` }, { required: true, message: `${t('labels.viewName')} ${t('general.required').toLowerCase()}` },
// name is unique // name is unique
{ {
validator: (_: unknown, v: string) => validator: (_: unknown, v: string) =>
@ -231,7 +235,7 @@ const addCalendarRange = async () => {
const isMetaLoading = ref(false) const isMetaLoading = ref(false)
onMounted(async () => { onMounted(async () => {
if (props.type === ViewTypes.KANBAN || props.type === ViewTypes.MAP || props.type === ViewTypes.CALENDAR) { if ([ViewTypes.GALLERY, ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(props.type)) {
isMetaLoading.value = true isMetaLoading.value = true
try { try {
meta.value = (await getMeta(tableId.value))! meta.value = (await getMeta(tableId.value))!
@ -258,6 +262,30 @@ onMounted(async () => {
} }
} }
// preset the cover image field
if (props.type === ViewTypes.GALLERY) {
viewSelectFieldOptions.value = [
{ value: null, label: 'No Image' },
...meta.value
.columns!.filter((el) => el.uidt === UITypes.Attachment)
.map((field) => {
return {
value: field.id,
label: field.title,
uidt: field.uidt,
}
}),
]
if (coverImageColumnId.value) {
form.fk_cover_image_col_id = coverImageColumnId.value
} else if (viewSelectFieldOptions.value.length > 1 && !form.copy_from_id) {
form.fk_cover_image_col_id = viewSelectFieldOptions.value[1].value as string
} else {
form.fk_cover_image_col_id = null
}
}
// preset the grouping field column // preset the grouping field column
if (props.type === ViewTypes.KANBAN) { if (props.type === ViewTypes.KANBAN) {
viewSelectFieldOptions.value = meta.value viewSelectFieldOptions.value = meta.value
@ -266,6 +294,7 @@ onMounted(async () => {
return { return {
value: field.id, value: field.id,
label: field.title, label: field.title,
uidt: field.uidt,
} }
}) })
@ -279,6 +308,14 @@ onMounted(async () => {
// if there is no grouping field column, disable the create button // if there is no grouping field column, disable the create button
isNecessaryColumnsPresent.value = false isNecessaryColumnsPresent.value = false
} }
if (coverImageColumnId.value) {
form.fk_cover_image_col_id = coverImageColumnId.value
} else if (viewSelectFieldOptions.value.length > 1 && !form.copy_from_id) {
form.fk_cover_image_col_id = viewSelectFieldOptions.value[1].value as string
} else {
form.fk_cover_image_col_id = null
}
} }
if (props.type === ViewTypes.CALENDAR) { if (props.type === ViewTypes.CALENDAR) {
@ -319,12 +356,14 @@ onMounted(async () => {
<template> <template>
<NcModal <NcModal
v-model:visible="vModel" v-model:visible="vModel"
:size="[ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(form.type) ? 'medium' : 'small'" class="nc-view-create-modal"
:show-separator="false"
:size="[ViewTypes.MAP].includes(form.type) ? 'medium' : 'small'"
> >
<template #header> <template #header>
<div class="flex w-full flex-row justify-between items-center"> <div class="flex w-full flex-row justify-between items-center">
<div class="flex font-bold text-base gap-x-3 items-center"> <div class="flex font-bold text-base gap-x-3 items-center">
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" /> <GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-[24px] !leading-6 max-h-6 max-w-6" />
<template v-if="form.type === ViewTypes.GRID"> <template v-if="form.type === ViewTypes.GRID">
<template v-if="form.copy_from_id"> <template v-if="form.copy_from_id">
{{ $t('labels.duplicateGridView') }} {{ $t('labels.duplicateGridView') }}
@ -380,24 +419,60 @@ onMounted(async () => {
:href="`https://docs.nocodb.com/views/view-types/${typeAlias}`" :href="`https://docs.nocodb.com/views/view-types/${typeAlias}`"
target="_blank" target="_blank"
> >
Go to Docs Docs
</a> </a>
</div> </div>
</template> </template>
<div class="mt-2"> <div class="mt-1">
<a-form v-if="isNecessaryColumnsPresent" ref="formValidator" :model="form" layout="vertical"> <a-form v-if="isNecessaryColumnsPresent" ref="formValidator" :model="form" layout="vertical" class="flex flex-col gap-y-5">
<a-form-item :rules="viewNameRules" name="title"> <a-form-item :rules="viewNameRules" name="title">
<a-input <a-input
ref="inputEl" ref="inputEl"
v-model:value="form.title" v-model:value="form.title"
:placeholder="$t('labels.viewName')" :placeholder="$t('labels.viewName')"
autofocus autofocus
class="nc-input-md h-10" class="nc-input-sm nc-input-shadow"
@keydown.enter="onSubmit" @keydown.enter="onSubmit"
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
v-if="form.type === ViewTypes.KANBAN" v-if="form.type === ViewTypes.GALLERY && !form.copy_from_id"
:label="`${$t('labels.coverImageField')}`"
name="fk_cover_image_col_id"
>
<NcSelect
v-model:value="form.fk_cover_image_col_id"
:disabled="isMetaLoading"
:loading="isMetaLoading"
dropdown-match-select-width
:not-found-content="$t('placeholder.selectGroupFieldNotFound')"
:placeholder="$t('placeholder.selectCoverImageField')"
class="nc-select-shadow w-full nc-gallery-cover-image-field-select"
>
<a-select-option v-for="option of viewSelectFieldOptions" :key="option.value" :value="option.value">
<div class="w-full flex gap-2 items-center justify-between" :title="option.label">
<div class="flex-1 flex items-center gap-1 max-w-[calc(100%_-_24px)]">
<SmartsheetHeaderIcon v-if="option.value" :column="option" class="!ml-0" />
<NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div>
<GeneralIcon
v-if="form.fk_cover_image_col_id === option.value"
id="nc-selected-item-icon"
icon="check"
class="flex-none text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
</a-form-item>
<a-form-item
v-if="form.type === ViewTypes.KANBAN && !form.copy_from_id"
:label="$t('general.groupingField')" :label="$t('general.groupingField')"
:rules="groupingFieldColumnRules" :rules="groupingFieldColumnRules"
name="fk_grp_col_id" name="fk_grp_col_id"
@ -406,11 +481,32 @@ onMounted(async () => {
v-model:value="form.fk_grp_col_id" v-model:value="form.fk_grp_col_id"
:disabled="isMetaLoading" :disabled="isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
dropdown-match-select-width
:not-found-content="$t('placeholder.selectGroupFieldNotFound')" :not-found-content="$t('placeholder.selectGroupFieldNotFound')"
:options="viewSelectFieldOptions"
:placeholder="$t('placeholder.selectGroupField')" :placeholder="$t('placeholder.selectGroupField')"
class="w-full nc-kanban-grouping-field-select" class="nc-select-shadow w-full nc-kanban-grouping-field-select"
>
<a-select-option v-for="option of viewSelectFieldOptions" :key="option.value" :value="option.value">
<div class="w-full flex gap-2 items-center justify-between" :title="option.label">
<div class="flex-1 flex items-center gap-1 max-w-[calc(100%_-_24px)]">
<SmartsheetHeaderIcon :column="option" class="!ml-0" />
<NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div>
<GeneralIcon
v-if="form.fk_grp_col_id === option.value"
id="nc-selected-item-icon"
icon="check"
class="flex-none text-primary w-4 h-4"
/> />
</div>
</a-select-option>
</NcSelect>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
v-if="form.type === ViewTypes.MAP" v-if="form.type === ViewTypes.MAP"
@ -425,19 +521,19 @@ onMounted(async () => {
:not-found-content="$t('placeholder.selectGeoFieldNotFound')" :not-found-content="$t('placeholder.selectGeoFieldNotFound')"
:options="viewSelectFieldOptions" :options="viewSelectFieldOptions"
:placeholder="$t('placeholder.selectGeoField')" :placeholder="$t('placeholder.selectGeoField')"
class="w-full" class="nc-select-shadow w-full"
/> />
</a-form-item> </a-form-item>
<template v-if="form.type === ViewTypes.CALENDAR"> <template v-if="form.type === ViewTypes.CALENDAR && !form.copy_from_id">
<div v-for="(range, index) in form.calendar_range" :key="`range-${index}`" class="flex w-full mb-2 items-center gap-2"> <div v-for="(range, index) in form.calendar_range" :key="`range-${index}`" class="flex w-full items-center gap-2">
<span> <span class="text-gray-800">
{{ $t('labels.organiseBy') }} {{ $t('labels.organiseBy') }}
</span> </span>
<NcSelect <NcSelect
v-model:value="range.fk_from_column_id" v-model:value="range.fk_from_column_id"
:disabled="isMetaLoading" :disabled="isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
class="nc-from-select" class="nc-select-shadow nc-from-select"
> >
<a-select-option <a-select-option
v-for="(option, id) in [...viewSelectFieldOptions!].filter((f) => { v-for="(option, id) in [...viewSelectFieldOptions!].filter((f) => {
@ -453,7 +549,7 @@ onMounted(async () => {
> >
<div class="flex w-full gap-2 justify-between items-center"> <div class="flex w-full gap-2 justify-between items-center">
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<SmartsheetHeaderIcon :column="option" /> <SmartsheetHeaderIcon :column="option" class="!ml-0" />
<NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only> <NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only>
<template #title>{{ option.label }}</template> <template #title>{{ option.label }}</template>
{{ option.label }} {{ option.label }}
@ -538,16 +634,16 @@ onMounted(async () => {
</a-form> </a-form>
<div v-else-if="!isNecessaryColumnsPresent" class="flex flex-row p-4 border-gray-200 border-1 gap-x-4 rounded-lg w-full"> <div v-else-if="!isNecessaryColumnsPresent" class="flex flex-row p-4 border-gray-200 border-1 gap-x-4 rounded-lg w-full">
<div class="text-gray-500 flex gap-4"> <div class="text-gray-500 flex gap-4">
<GeneralIcon class="min-w-6 h-6 text-orange-500" icon="warning" /> <GeneralIcon class="min-w-6 h-6 text-orange-500" icon="alertTriangle" />
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h2 class="font-semibold text-sm mb-0 text-gray-800">Suitable fields not present</h2> <h2 class="font-semibold text-sm mb-0 text-gray-800">Suitable fields not present</h2>
<span class="text-gray-500 font-default"> {{ errorMessages[form.type] }}</span> <span class="text-gray-500 font-default text-sm"> {{ errorMessages[form.type] }}</span>
</div> </div>
</div> </div>
</div> </div>
<div class="flex flex-row w-full justify-end gap-x-2 mt-7"> <div class="flex flex-row w-full justify-end gap-x-2 mt-5">
<NcButton type="secondary" @click="vModel = false"> <NcButton type="secondary" size="small" @click="vModel = false">
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</NcButton> </NcButton>
@ -556,6 +652,7 @@ onMounted(async () => {
:disabled="!isNecessaryColumnsPresent" :disabled="!isNecessaryColumnsPresent"
:loading="isViewCreating" :loading="isViewCreating"
type="primary" type="primary"
size="small"
@click="onSubmit" @click="onSubmit"
> >
{{ $t('labels.createView') }} {{ $t('labels.createView') }}
@ -582,11 +679,29 @@ onMounted(async () => {
@apply !rounded-r-none; @apply !rounded-r-none;
} }
.ant-input { .ant-form-item {
@apply border-gray-200; @apply !mb-0;
} }
.ant-form-item { .nc-input-sm {
@apply !mb-6; @apply !mb-0;
}
.nc-view-create-modal {
:deep(.nc-modal) {
}
}
:deep(.ant-form-item-label > label) {
@apply !text-sm text-gray-800 flex;
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
@apply content-[''] m-0;
}
}
:deep(.ant-select) {
.ant-select-selector {
@apply !rounded-lg;
}
} }
</style> </style>

2
packages/nc-gui/components/dlg/ViewDelete.vue

@ -46,7 +46,7 @@ async function onDelete() {
<template> <template>
<GeneralDeleteModal v-model:visible="vModel" :entity-name="$t('objects.view')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="vModel" :entity-name="$t('objects.view')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="view" class="flex flex-row items-center py-2 px-3 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="view" class="flex flex-row items-center py-2 px-3 bg-gray-50 rounded-lg text-gray-700">
<GeneralViewIcon :meta="props.view" class="nc-view-icon w-4 min-h-4"></GeneralViewIcon> <GeneralViewIcon :meta="props.view" class="nc-view-icon w-4 min-h-4"></GeneralViewIcon>
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-3" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-3"

2
packages/nc-gui/components/dlg/WorkspaceDelete.vue

@ -50,7 +50,7 @@ const onDelete = async () => {
<template> <template>
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.workspace')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.workspace')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="workspace" class="flex flex-row items-center py-2.25 px-2.75 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="workspace" class="flex flex-row items-center py-2.25 px-2.75 bg-gray-50 rounded-lg text-gray-700">
<GeneralIcon icon="workspace" /> <GeneralIcon icon="workspace" />
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-2.25" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-2.25"

7
packages/nc-gui/components/general/DeleteModal.vue

@ -45,9 +45,7 @@ onKeyStroke('Enter', () => {
<template> <template>
<GeneralModal v-model:visible="visible" size="small" centered> <GeneralModal v-model:visible="visible" size="small" centered>
<div class="flex flex-col p-6"> <div class="flex flex-col p-6">
<div class="flex flex-row pb-2 mb-4 font-medium text-lg border-b-1 border-gray-50 text-gray-800"> <div class="flex flex-row pb-2 mb-3 font-medium text-lg text-gray-800">{{ deleteLabel }} {{ props.entityName }}</div>
{{ deleteLabel }} {{ props.entityName }}
</div>
<div class="mb-3 text-gray-800"> <div class="mb-3 text-gray-800">
{{ {{
@ -60,13 +58,14 @@ onKeyStroke('Enter', () => {
<slot name="entity-preview"></slot> <slot name="entity-preview"></slot>
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">
<NcButton type="secondary" @click="visible = false"> <NcButton type="secondary" size="small" @click="visible = false">
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</NcButton> </NcButton>
<NcButton <NcButton
key="submit" key="submit"
type="danger" type="danger"
size="small"
html-type="submit" html-type="submit"
:loading="isLoading" :loading="isLoading"
data-testid="nc-delete-modal-delete-btn" data-testid="nc-delete-modal-delete-btn"

2
packages/nc-gui/components/nc/Select.vue

@ -84,7 +84,7 @@ const onChange = (value: string) => {
height: fit-content; height: fit-content;
.ant-select-selector { .ant-select-selector {
box-shadow: 0px 5px 3px -2px rgba(0, 0, 0, 0.02), 0px 3px 1px -2px rgba(0, 0, 0, 0.06); box-shadow: 0px 5px 3px -2px rgba(0, 0, 0, 0.02), 0px 3px 1px -2px rgba(0, 0, 0, 0.06);
@apply border-1 border-gray-200 rounded-lg; @apply border-1 border-gray-200 rounded-lg shadow-default;
} }
.ant-select-selection-item { .ant-select-selection-item {

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

@ -959,7 +959,7 @@ export default {
<GeneralDeleteModal v-model:visible="showDeleteRowModal" entity-name="Record" :on-delete="() => onConfirmDeleteRowClick()"> <GeneralDeleteModal v-model:visible="showDeleteRowModal" entity-name="Record" :on-delete="() => onConfirmDeleteRowClick()">
<template #entity-preview> <template #entity-preview>
<span> <span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700">
<div class="text-ellipsis overflow-hidden select-none w-full pl-1.75 break-keep whitespace-nowrap"> <div class="text-ellipsis overflow-hidden select-none w-full pl-1.75 break-keep whitespace-nowrap">
<LazySmartsheetPlainCell v-model="displayValue" :column="displayField" /> <LazySmartsheetPlainCell v-model="displayValue" :column="displayField" />
</div> </div>
@ -978,9 +978,9 @@ export default {
{{ $t('activity.doYouWantToSaveTheChanges') }} {{ $t('activity.doYouWantToSaveTheChanges') }}
</div> </div>
<div class="flex flex-row justify-end gap-x-2 mt-5"> <div class="flex flex-row justify-end gap-x-2 mt-5">
<NcButton type="secondary" @click="discardPreventModal">{{ $t('labels.discard') }}</NcButton> <NcButton type="secondary" size="small" @click="discardPreventModal">{{ $t('labels.discard') }}</NcButton>
<NcButton key="submit" type="primary" :loading="isSaving" @click="saveChanges"> <NcButton key="submit" type="primary" size="small" :loading="isSaving" @click="saveChanges">
{{ $t('tooltip.saveChanges') }} {{ $t('tooltip.saveChanges') }}
</NcButton> </NcButton>
</div> </div>

23
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -422,27 +422,38 @@ useMenuCloseOnEsc(open)
<div class="pl-2 flex text-sm select-none text-gray-600">{{ $t('labels.coverImageField') }}</div> <div class="pl-2 flex text-sm select-none text-gray-600">{{ $t('labels.coverImageField') }}</div>
<div <div
class="flex-1 nc-dropdown-cover-image-wrapper flex items-stretch border-1 border-gray-200 rounded-lg transition-all duration-0.3s" class="flex-1 nc-dropdown-cover-image-wrapper flex items-stretch border-1 border-gray-200 rounded-lg transition-all duration-0.3s max-w-[206px]"
> >
<a-select <a-select
v-model:value="coverImageColumnId" v-model:value="coverImageColumnId"
class="w-full" class="flex-1 max-w-[calc(100%_-_33px)]"
dropdown-class-name="nc-dropdown-cover-image !rounded-lg" dropdown-class-name="nc-dropdown-cover-image !rounded-lg "
:bordered="false" :bordered="false"
@click.stop @click.stop
> >
<template #suffixIcon><GeneralIcon class="text-gray-700" icon="arrowDown" /></template> <template #suffixIcon><GeneralIcon class="text-gray-700" icon="arrowDown" /></template>
<a-select-option v-for="option of coverOptions" :key="option.value" :value="option.value"> <a-select-option v-for="option of coverOptions" :key="option.value" :value="option.value">
<div class="w-full flex gap-2 items-center justify-between"> <div class="w-full flex gap-2 items-center justify-between max-w-[400px]">
<div class="flex items-center gap-1"> <div
class="flex-1 flex items-center gap-1"
:class="{
'max-w-[calc(100%_-_20px)]': coverImageColumnId === option.value,
'max-w-full': coverImageColumnId !== option.value,
}"
>
<component <component
:is="getIcon(metaColumnById[option.value])" :is="getIcon(metaColumnById[option.value])"
v-if="option.value" v-if="option.value"
class="!w-3.5 !h-3.5 !text-gray-700 !ml-0" class="!w-3.5 !h-3.5 !text-gray-700 !ml-0"
/> />
<span> {{ option.label }} </span> <NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div> </div>
<GeneralIcon <GeneralIcon
v-if="coverImageColumnId === option.value" v-if="coverImageColumnId === option.value"

15
packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue

@ -146,9 +146,9 @@ const getIcon = (c: ColumnType) =>
{{ $t('activity.kanban.stackedBy') }} {{ $t('activity.kanban.stackedBy') }}
</span> </span>
<div <div
class="flex items-center rounded-md transition-colors duration-0.3s bg-gray-100 group-hover:bg-gray-200 px-1 min-h-5 text-gray-600" class="flex items-center rounded-md transition-colors duration-0.3s bg-gray-100 group-hover:bg-gray-200 px-1 min-h-5 text-gray-600 max-w-[108px]"
> >
<span class="font-weight-500 text-sm">{{ groupingField }}</span> <span class="font-weight-500 text-sm truncate !leading-5">{{ groupingField }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -158,7 +158,7 @@ const getIcon = (c: ColumnType) =>
<div v-if="open" class="p-4 w-90 bg-white nc-table-toolbar-menu rounded-lg flex flex-col gap-5" @click.stop> <div v-if="open" class="p-4 w-90 bg-white nc-table-toolbar-menu rounded-lg flex flex-col gap-5" @click.stop>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div> <div>
{{ $t('general.groupingField').toLowerCase().replace(/^./, $t('general.groupingField').charAt(0).toUpperCase()) }} {{ $t('general.groupingField') }}
</div> </div>
<div class="nc-fields-list"> <div class="nc-fields-list">
<div class="grouping-field"> <div class="grouping-field">
@ -173,14 +173,19 @@ const getIcon = (c: ColumnType) =>
<template #suffixIcon><GeneralIcon icon="arrowDown" class="text-gray-700" /></template> <template #suffixIcon><GeneralIcon icon="arrowDown" class="text-gray-700" /></template>
<a-select-option v-for="option of singleSelectFieldOptions" :key="option.value" :value="option.value"> <a-select-option v-for="option of singleSelectFieldOptions" :key="option.value" :value="option.value">
<div class="w-full flex gap-2 items-center justify-between" :title="option.label"> <div class="w-full flex gap-2 items-center justify-between" :title="option.label">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1 max-w-[calc(100%_-_20px)]">
<component <component
:is="getIcon(metaColumnById[option.value])" :is="getIcon(metaColumnById[option.value])"
v-if="option.value" v-if="option.value"
class="!w-3.5 !h-3.5 !text-gray-700 !ml-0" class="!w-3.5 !h-3.5 !text-gray-700 !ml-0"
/> />
<span> {{ option.label }} </span> <NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div> </div>
<GeneralIcon <GeneralIcon
v-if="groupingFieldColumnId === option.value" v-if="groupingFieldColumnId === option.value"

1
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -103,6 +103,7 @@ function onDuplicate() {
'groupingFieldColumnId': view.value!.view!.fk_grp_col_id, 'groupingFieldColumnId': view.value!.view!.fk_grp_col_id,
'views': views, 'views': views,
'calendarRange': view.value!.view!.calendar_range, 'calendarRange': view.value!.view!.calendar_range,
'coverImageColumnId': view.value!.view!.fk_cover_image_col_id,
'onUpdate:modelValue': closeDialog, 'onUpdate:modelValue': closeDialog,
'onCreated': async (view: ViewType) => { 'onCreated': async (view: ViewType) => {
closeDialog() closeDialog()

15
packages/nc-gui/components/workspace/CreateProjectDlg.vue

@ -106,10 +106,10 @@ const typeLabel = computed(() => {
</script> </script>
<template> <template>
<NcModal v-model:visible="dialogShow" size="small"> <NcModal v-model:visible="dialogShow" size="small" :show-separator="false">
<template #header> <template #header>
<!-- Create A New Table --> <!-- Create A New Table -->
<div class="flex flex-row items-center"> <div class="flex flex-row items-center text-base text-gray-800">
<GeneralProjectIcon :color="formState.meta.iconColor" :type="baseType" class="mr-2.5 !text-lg !h-4" /> <GeneralProjectIcon :color="formState.meta.iconColor" :type="baseType" class="mr-2.5 !text-lg !h-4" />
{{ {{
$t('general.createEntity', { $t('general.createEntity', {
@ -118,7 +118,7 @@ const typeLabel = computed(() => {
}} }}
</div> </div>
</template> </template>
<div class="mt-3"> <div class="mt-1">
<a-form <a-form
ref="form" ref="form"
:model="formState" :model="formState"
@ -129,24 +129,25 @@ const typeLabel = computed(() => {
autocomplete="off" autocomplete="off"
@finish="createProject" @finish="createProject"
> >
<a-form-item name="title" :rules="nameValidationRules" class="m-10"> <a-form-item name="title" :rules="nameValidationRules" class="!mb-0">
<a-input <a-input
ref="input" ref="input"
v-model:value="formState.title" v-model:value="formState.title"
name="title" name="title"
class="nc-metadb-base-name nc-input-md" class="nc-metadb-base-name nc-input-sm nc-input-shadow"
placeholder="Title" placeholder="Title"
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="flex flex-row justify-end mt-7 gap-x-2"> <div class="flex flex-row justify-end mt-5 gap-x-2">
<NcButton type="secondary" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton> <NcButton type="secondary" size="small" @click="dialogShow = false">{{ $t('general.cancel') }}</NcButton>
<NcButton <NcButton
v-e="['a:base:create']" v-e="['a:base:create']"
data-testid="docs-create-proj-dlg-create-btn" data-testid="docs-create-proj-dlg-create-btn"
:loading="creating" :loading="creating"
type="primary" type="primary"
size="small"
:label="`${$t('general.create')} ${typeLabel}`" :label="`${$t('general.create')} ${typeLabel}`"
:loading-label="`${$t('general.creating')} ${typeLabel}`" :loading-label="`${$t('general.creating')} ${typeLabel}`"
@click="createProject" @click="createProject"

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

@ -157,7 +157,7 @@
"betaNote": "This feature is currently in beta.", "betaNote": "This feature is currently in beta.",
"moreInfo": "More information can be found here", "moreInfo": "More information can be found here",
"logs": "Logs", "logs": "Logs",
"groupingField": "Grouping Field", "groupingField": "Stack by field",
"insertAfter": "Insert After", "insertAfter": "Insert After",
"insertBefore": "Insert Before", "insertBefore": "Insert Before",
"insertAbove": "Insert above", "insertAbove": "Insert above",
@ -1082,6 +1082,7 @@
"projName": "Enter Base Name", "projName": "Enter Base Name",
"selectGroupField": "Select a Grouping Field", "selectGroupField": "Select a Grouping Field",
"selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.", "selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.",
"selectCoverImageField": "Select a cover image field",
"selectGeoField": "Select a GeoData Field", "selectGeoField": "Select a GeoData Field",
"notSelected": "-not selected-", "notSelected": "-not selected-",
"selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.", "selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.",

1
packages/nc-gui/windi.config.ts

@ -115,6 +115,7 @@ export default defineConfig({
default: '0px 0px 4px 0px rgba(0, 0, 0, 0.08)', default: '0px 0px 4px 0px rgba(0, 0, 0, 0.08)',
hover: '0px 0px 4px 0px rgba(0, 0, 0, 0.24)', hover: '0px 0px 4px 0px rgba(0, 0, 0, 0.24)',
selected: '0px 0px 0px 2px var(--ant-primary-color-outline)', selected: '0px 0px 0px 2px var(--ant-primary-color-outline)',
error: '0px 0px 0px 2px var(--ant-error-color-outline)',
}, },
colors: { colors: {
...windiColors, ...windiColors,

5
packages/nocodb/src/models/GalleryView.ts

@ -95,8 +95,9 @@ export default class GalleryView implements GalleryType {
]); ]);
insertObj.fk_cover_image_col_id = insertObj.fk_cover_image_col_id =
view?.fk_cover_image_col_id || view?.fk_cover_image_col_id !== undefined
columns?.find((c) => c.uidt === UITypes.Attachment)?.id; ? view?.fk_cover_image_col_id
: columns?.find((c) => c.uidt === UITypes.Attachment)?.id;
insertObj.meta = { insertObj.meta = {
fk_cover_image_object_fit: fk_cover_image_object_fit:

5
packages/nocodb/src/models/KanbanView.ts

@ -104,8 +104,9 @@ export default class KanbanView implements KanbanType {
]); ]);
insertObj.fk_cover_image_col_id = insertObj.fk_cover_image_col_id =
view?.fk_cover_image_col_id || view?.fk_cover_image_col_id !== undefined
columns?.find((c) => c.uidt === UITypes.Attachment)?.id; ? view?.fk_cover_image_col_id
: columns?.find((c) => c.uidt === UITypes.Attachment)?.id;
insertObj.meta = { insertObj.meta = {
fk_cover_image_object_fit: fk_cover_image_object_fit:

Loading…
Cancel
Save