mirror of https://github.com/nocodb/nocodb
DarkPhoenix2704
3 days ago
4 changed files with 234 additions and 205 deletions
@ -1,228 +1,59 @@
|
||||
<script setup lang="ts"> |
||||
import { onMounted } from '@vue/runtime-core' |
||||
|
||||
interface Props { |
||||
reload?: boolean |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
|
||||
const searchSnapshotQuery = ref('') |
||||
|
||||
const { t } = useI18n() |
||||
|
||||
const { sorts, sortDirection, loadSorts, handleGetSortedData, saveOrUpdate: saveOrUpdateSort } = useUserSorts('Webhook') |
||||
|
||||
const orderBy = computed<Record<string, SordDirectionType>>({ |
||||
get: () => { |
||||
return sortDirection.value |
||||
}, |
||||
set: (value: Record<string, SordDirectionType>) => { |
||||
// Check if value is an empty object |
||||
if (Object.keys(value).length === 0) { |
||||
saveOrUpdateSort({}) |
||||
return |
||||
} |
||||
|
||||
const [field, direction] = Object.entries(value)[0] |
||||
|
||||
saveOrUpdateSort({ |
||||
field, |
||||
direction, |
||||
}) |
||||
}, |
||||
}) |
||||
const activeMenu = ref('snapshots') |
||||
|
||||
const snapshots = ref([]) |
||||
const selectMenu = (option: string) => { |
||||
activeMenu.value = option |
||||
} |
||||
|
||||
const columns = [ |
||||
const options: { key: string; label: string; icon: keyof typeof iconMap }[] = [ |
||||
{ |
||||
key: 'name', |
||||
title: t('general.snapshot'), |
||||
name: 'Snapshot', |
||||
minWidth: 397, |
||||
padding: '12px 24px', |
||||
showOrderBy: true, |
||||
dataIndex: 'title', |
||||
key: 'snapshots', |
||||
label: t('general.snapshots'), |
||||
icon: 'camera', |
||||
}, |
||||
{ |
||||
key: 'action', |
||||
title: t('general.action'), |
||||
width: 162, |
||||
minWidth: 162, |
||||
padding: '12px 24px', |
||||
key: 'visibility', |
||||
label: t('labels.visibilityAndDataHandling'), |
||||
icon: 'ncEye', |
||||
}, |
||||
] as NcTableColumnProps[] |
||||
|
||||
// Misc Settings |
||||
|
||||
const baseStore = useBase() |
||||
const basesStore = useBases() |
||||
const { base } = storeToRefs(baseStore) |
||||
|
||||
const _projectId = inject(ProjectIdInj, undefined) |
||||
const { loadTables, hasEmptyOrNullFilters } = baseStore |
||||
|
||||
const baseId = computed(() => _projectId?.value ?? base.value?.id) |
||||
|
||||
const showNullAndEmptyInFilter = ref() |
||||
|
||||
const { includeM2M, showNull } = useGlobal() |
||||
|
||||
watch(includeM2M, async () => await loadTables()) |
||||
|
||||
onMounted(async () => { |
||||
await basesStore.loadProject(baseId.value!, true) |
||||
showNullAndEmptyInFilter.value = basesStore.getProjectMeta(baseId.value!)?.showNullAndEmptyInFilter |
||||
}) |
||||
|
||||
async function showNullAndEmptyInFilterOnChange(evt: boolean) { |
||||
const base = basesStore.bases.get(baseId.value!) |
||||
if (!base) throw new Error(`Base ${baseId.value} not found`) |
||||
|
||||
const meta = basesStore.getProjectMeta(baseId.value!) ?? {} |
||||
|
||||
// users cannot hide null & empty option if there is existing null / empty filters |
||||
if (!evt) { |
||||
if (await hasEmptyOrNullFilters()) { |
||||
showNullAndEmptyInFilter.value = true |
||||
message.warning(t('msg.error.nullFilterExists')) |
||||
} |
||||
} |
||||
const newProjectMeta = { |
||||
...meta, |
||||
showNullAndEmptyInFilter: showNullAndEmptyInFilter.value, |
||||
} |
||||
// update local state |
||||
base.meta = newProjectMeta |
||||
// update db |
||||
await basesStore.updateProject(baseId.value!, { |
||||
meta: JSON.stringify(newProjectMeta), |
||||
}) |
||||
} |
||||
] |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex h-[calc(100vh-92px)] flex-col items-center nc-base-settings pb-10 overflow-y-auto nc-scrollbar-x-lg px-6"> |
||||
<div class="item-card flex flex-col w-full"> |
||||
<div class="text-nc-content-gray-emphasis font-semibold text-lg"> |
||||
{{ $t('general.baseSnapshots') }} |
||||
</div> |
||||
|
||||
<div class="text-nc-content-gray-subtle2 mt-2 leading-5"> |
||||
{{ $t('labels.snapShotSubText') }} |
||||
</div> |
||||
|
||||
<div class="flex items-center mt-6 gap-5"> |
||||
<a-input |
||||
v-model:value="searchSnapshotQuery" |
||||
class="w-full h-8 flex-1" |
||||
size="small" |
||||
:placeholder="$t('labels.searchASnapshot')" |
||||
<div class="flex p-4 justify-center overflow-auto gap-8"> |
||||
<!-- Left Pane --> |
||||
<div class="flex flex-col"> |
||||
<div class="h-full w-60"> |
||||
<div |
||||
v-for="option in options" |
||||
:key="option.key" |
||||
:class="{ |
||||
'active-menu': activeMenu === option.key, |
||||
}" |
||||
class="gap-3 !hover:bg-gray-50 transition-all text-nc-content-gray flex rounded-lg items-center cursor-pointer py-1.5 px-3" |
||||
@click="selectMenu(option.key)" |
||||
> |
||||
<template #prefix> |
||||
<GeneralIcon icon="search" class="w-4 text-gray-500 h-4 mr-2" /> |
||||
</template> |
||||
</a-input> |
||||
<NcButton class="!w-36" size="small" type="secondary"> |
||||
{{ $t('labels.newSnapshot') }} |
||||
</NcButton> |
||||
</div> |
||||
|
||||
<NcTable |
||||
v-model:order-by="orderBy" |
||||
:columns="columns" |
||||
:data="snapshots" |
||||
class="h-full mt-5" |
||||
body-row-class-name="nc-base-settings-snapshot-item" |
||||
> |
||||
<template #bodyCell="{ column, record: hook }"> |
||||
<template v-if="column.key === 'name'"> |
||||
<NcTooltip class="truncate max-w-full text-gray-800 font-semibold text-sm" show-on-truncate-only> |
||||
{{ hook.title }} |
||||
|
||||
<template #title> |
||||
{{ hook.title }} |
||||
</template> |
||||
</NcTooltip> |
||||
</template> |
||||
<template v-if="column.key === 'action'"> |
||||
<div class="flex items-center gap-2"> |
||||
<NcButton size="small" type="text" class="!text-xs"> |
||||
{{ $t('labels.edit') }} |
||||
</NcButton> |
||||
<NcButton size="small" type="text" class="!text-xs"> |
||||
{{ $t('labels.delete') }} |
||||
</NcButton> |
||||
</div> |
||||
</template> |
||||
</template> |
||||
</NcTable> |
||||
</div> |
||||
|
||||
<div class="item-card flex flex-col w-full"> |
||||
<div class="text-nc-content-gray-emphasis font-semibold text-lg"> |
||||
{{ $t('general.misc') }} |
||||
</div> |
||||
<GeneralIcon :icon="option.icon" /> |
||||
|
||||
<div class="text-nc-content-gray-subtle2 mt-2 leading-5"> |
||||
{{ $t('labels.miscBaseSettingsLabel') }} |
||||
</div> |
||||
|
||||
<div class="flex flex-col border-1 rounded-lg mt-6 border-nc-border-gray-medium"> |
||||
<div class="flex w-full p-3 gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showM2mTables') }} |
||||
</span> |
||||
<NcSwitch v-model:checked="includeM2M" v-e="['c:themes:show-m2m-tables']" class="ml-2" /> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showM2mTablesDesc') }}</span> |
||||
</div> |
||||
|
||||
<div class="flex w-full p-3 gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showNullInCells') }} |
||||
</span> |
||||
<NcSwitch v-model:checked="showNull" v-e="['c:settings:show-null']" class="ml-2" /> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showNullInCellsDesc') }}</span> |
||||
</div> |
||||
|
||||
<div class="flex w-full p-3 gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showNullAndEmptyInFilter') }} |
||||
</span> |
||||
<NcSwitch |
||||
v-model:checked="showNullAndEmptyInFilter" |
||||
v-e="['c:settings:show-null-and-empty-in-filter']" |
||||
class="ml-2" |
||||
@change="showNullAndEmptyInFilterOnChange" |
||||
/> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showNullAndEmptyInFilterDesc') }}</span> |
||||
<span> |
||||
{{ option.label }} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- Data Pane --> |
||||
|
||||
<div class="flex flex-col py-4 w-[800px]"> |
||||
<DashboardSettingsBaseSettingsSnapshots v-if="activeMenu === 'snapshots'" /> |
||||
<DashboardSettingsBaseSettingsVisibility v-if="activeMenu === 'visibility'" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.item-card { |
||||
@apply p-6 rounded-2xl border-1 max-w-[600px] mt-10 min-w-100 w-full; |
||||
} |
||||
|
||||
.ant-input::placeholder { |
||||
@apply !text-nc-content-gray-muted; |
||||
} |
||||
|
||||
.ant-input:placeholder-shown { |
||||
@apply !text-nc-content-gray-muted !text-md; |
||||
} |
||||
|
||||
.ant-input-affix-wrapper { |
||||
@apply px-3 rounded-lg py-2 w-84 border-1 focus:border-brand-500 border-gray-200 !ring-0; |
||||
.active-menu { |
||||
@apply !bg-brand-50 font-semibold !text-brand-500; |
||||
} |
||||
</style> |
||||
|
@ -0,0 +1,96 @@
|
||||
<script setup lang="ts"> |
||||
const { t } = useI18n() |
||||
|
||||
const { sorts, sortDirection, loadSorts, handleGetSortedData, saveOrUpdate: saveOrUpdateSort } = useUserSorts('Webhook') |
||||
|
||||
const orderBy = computed<Record<string, SordDirectionType>>({ |
||||
get: () => { |
||||
return sortDirection.value |
||||
}, |
||||
set: (value: Record<string, SordDirectionType>) => { |
||||
// Check if value is an empty object |
||||
if (Object.keys(value).length === 0) { |
||||
saveOrUpdateSort({}) |
||||
return |
||||
} |
||||
|
||||
const [field, direction] = Object.entries(value)[0] |
||||
|
||||
saveOrUpdateSort({ |
||||
field, |
||||
direction, |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
const snapshots = ref([]) |
||||
|
||||
const columns = [ |
||||
{ |
||||
key: 'name', |
||||
title: t('general.snapshot'), |
||||
name: 'Snapshot', |
||||
minWidth: 397, |
||||
padding: '12px 24px', |
||||
showOrderBy: true, |
||||
dataIndex: 'title', |
||||
}, |
||||
{ |
||||
key: 'action', |
||||
title: t('general.action'), |
||||
width: 162, |
||||
minWidth: 162, |
||||
padding: '12px 24px', |
||||
}, |
||||
] as NcTableColumnProps[] |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="item-card flex flex-col w-full"> |
||||
<div class="text-nc-content-gray-emphasis font-semibold text-lg"> |
||||
{{ $t('general.baseSnapshots') }} |
||||
</div> |
||||
|
||||
<div class="text-nc-content-gray-subtle2 mt-2 leading-5"> |
||||
{{ $t('labels.snapShotSubText') }} |
||||
</div> |
||||
|
||||
<div class="flex items-center mt-6 gap-5"> |
||||
<NcButton class="!w-36" size="small" type="secondary"> |
||||
{{ $t('labels.newSnapshot') }} |
||||
</NcButton> |
||||
</div> |
||||
|
||||
<NcTable |
||||
v-model:order-by="orderBy" |
||||
:columns="columns" |
||||
:data="snapshots" |
||||
class="h-full mt-5" |
||||
body-row-class-name="nc-base-settings-snapshot-item" |
||||
> |
||||
<template #bodyCell="{ column, record: hook }"> |
||||
<template v-if="column.key === 'name'"> |
||||
<NcTooltip class="truncate max-w-full text-gray-800 font-semibold text-sm" show-on-truncate-only> |
||||
{{ hook.title }} |
||||
|
||||
<template #title> |
||||
{{ hook.title }} |
||||
</template> |
||||
</NcTooltip> |
||||
</template> |
||||
<template v-if="column.key === 'action'"> |
||||
<div class="flex items-center gap-2"> |
||||
<NcButton size="small" type="text" class="!text-xs"> |
||||
{{ $t('labels.edit') }} |
||||
</NcButton> |
||||
<NcButton size="small" type="text" class="!text-xs"> |
||||
{{ $t('labels.delete') }} |
||||
</NcButton> |
||||
</div> |
||||
</template> |
||||
</template> |
||||
</NcTable> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,99 @@
|
||||
<script setup lang="ts"> |
||||
const { t } = useI18n() |
||||
|
||||
const baseStore = useBase() |
||||
const basesStore = useBases() |
||||
const { base } = storeToRefs(baseStore) |
||||
|
||||
const _projectId = inject(ProjectIdInj, undefined) |
||||
const { loadTables, hasEmptyOrNullFilters } = baseStore |
||||
|
||||
const baseId = computed(() => _projectId?.value ?? base.value?.id) |
||||
|
||||
const showNullAndEmptyInFilter = ref() |
||||
|
||||
const { includeM2M, showNull } = useGlobal() |
||||
|
||||
watch(includeM2M, async () => await loadTables()) |
||||
|
||||
onMounted(async () => { |
||||
await basesStore.loadProject(baseId.value!, true) |
||||
showNullAndEmptyInFilter.value = basesStore.getProjectMeta(baseId.value!)?.showNullAndEmptyInFilter |
||||
}) |
||||
|
||||
async function showNullAndEmptyInFilterOnChange(evt: boolean) { |
||||
const base = basesStore.bases.get(baseId.value!) |
||||
if (!base) throw new Error(`Base ${baseId.value} not found`) |
||||
|
||||
const meta = basesStore.getProjectMeta(baseId.value!) ?? {} |
||||
|
||||
// users cannot hide null & empty option if there is existing null / empty filters |
||||
if (!evt) { |
||||
if (await hasEmptyOrNullFilters()) { |
||||
showNullAndEmptyInFilter.value = true |
||||
message.warning(t('msg.error.nullFilterExists')) |
||||
} |
||||
} |
||||
const newProjectMeta = { |
||||
...meta, |
||||
showNullAndEmptyInFilter: showNullAndEmptyInFilter.value, |
||||
} |
||||
// update local state |
||||
base.meta = newProjectMeta |
||||
// update db |
||||
await basesStore.updateProject(baseId.value!, { |
||||
meta: JSON.stringify(newProjectMeta), |
||||
}) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="item-card flex flex-col w-full"> |
||||
<div class="text-nc-content-gray-emphasis font-semibold text-lg"> |
||||
{{ $t('labels.visibilityAndDataHandling') }} |
||||
</div> |
||||
|
||||
<div class="text-nc-content-gray-subtle2 mt-2 leading-5"> |
||||
{{ $t('labels.miscBaseSettingsLabel') }} |
||||
</div> |
||||
|
||||
<div class="flex flex-col border-1 rounded-lg mt-6 border-nc-border-gray-medium"> |
||||
<div class="flex w-full px-3 py-2 gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showM2mTables') }} |
||||
</span> |
||||
<NcSwitch v-model:checked="includeM2M" v-e="['c:themes:show-m2m-tables']" class="ml-2" /> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showM2mTablesDesc') }}</span> |
||||
</div> |
||||
|
||||
<div class="flex w-full px-3 border-t-1 border-nc-border-gray-medium py-2 gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showNullInCells') }} |
||||
</span> |
||||
<NcSwitch v-model:checked="showNull" v-e="['c:settings:show-null']" class="ml-2" /> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showNullInCellsDesc') }}</span> |
||||
</div> |
||||
|
||||
<div class="flex w-full px-3 py-2 border-t-1 border-nc-border-gray-medium gap-2 flex-col"> |
||||
<div class="flex w-full items-center"> |
||||
<span class="text-nc-content-gray font-semibold flex-1"> |
||||
{{ $t('msg.info.showNullAndEmptyInFilter') }} |
||||
</span> |
||||
<NcSwitch |
||||
v-model:checked="showNullAndEmptyInFilter" |
||||
v-e="['c:settings:show-null-and-empty-in-filter']" |
||||
class="ml-2" |
||||
@change="showNullAndEmptyInFilterOnChange" |
||||
/> |
||||
</div> |
||||
<span class="text-gray-500">{{ $t('msg.info.showNullAndEmptyInFilterDesc') }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
Loading…
Reference in new issue