Browse Source

feat: snapshot ui and move misc settings to base tab

pull/9879/head
DarkPhoenix2704 9 hours ago
parent
commit
c0dd153ea8
  1. 15
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  2. 228
      packages/nc-gui/components/dashboard/settings/BaseSettings.vue
  3. 82
      packages/nc-gui/components/dashboard/settings/Misc.vue
  4. 22
      packages/nc-gui/components/general/AddBaseButton.vue
  5. 13
      packages/nc-gui/components/project/View.vue
  6. 1
      packages/nc-gui/context/index.ts
  7. 6
      packages/nc-gui/lang/en.json
  8. 23
      packages/nc-gui/pages/index.vue
  9. 17
      packages/nc-gui/pages/index/[typeOrId]/[baseId].vue
  10. 2
      packages/nc-gui/store/config.ts

15
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -95,8 +95,6 @@ const { activeProjectId } = storeToRefs(useBases())
const { baseUrl } = useBase()
const toggleDialog = inject(ToggleDialogInj, () => {})
const { $e } = useNuxtApp()
const isOptionsOpen = ref(false)
@ -525,6 +523,17 @@ watch(
},
)
const { navigateToProject } = useGlobal()
const openBaseSettings = async (baseId: string) => {
navigateToProject({
baseId,
query: {
page: 'base-settings',
},
})
}
const showNodeTooltip = ref(true)
</script>
@ -745,7 +754,7 @@ const showNodeTooltip = ref(true)
key="teamAndSettings"
data-testid="nc-sidebar-base-settings"
class="nc-sidebar-base-base-settings"
@click="toggleDialog(true, 'teamAndAuth', undefined, base.id)"
@click="openBaseSettings(base.id)"
>
<div v-e="['c:base:settings']" class="flex gap-2 items-center">
<GeneralIcon icon="settings" class="group-hover:text-black" />

228
packages/nc-gui/components/dashboard/settings/BaseSettings.vue

@ -0,0 +1,228 @@
<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 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[]
// 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')"
>
<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>
<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>
</div>
</div>
</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;
}
</style>

82
packages/nc-gui/components/dashboard/settings/Misc.vue

@ -1,82 +0,0 @@
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'
import { onMounted } from '@vue/runtime-core'
const { includeM2M, showNull } = useGlobal()
const baseStore = useBase()
const basesStore = useBases()
const { loadTables, hasEmptyOrNullFilters } = baseStore
const { base } = storeToRefs(baseStore)
const _projectId = inject(ProjectIdInj, undefined)
const baseId = computed(() => _projectId?.value ?? base.value?.id)
const { t } = useI18n()
watch(includeM2M, async () => await loadTables())
const showNullAndEmptyInFilter = ref()
onMounted(async () => {
await basesStore.loadProject(baseId.value!, true)
showNullAndEmptyInFilter.value = basesStore.getProjectMeta(baseId.value!)?.showNullAndEmptyInFilter
})
async function showNullAndEmptyInFilterOnChange(evt: CheckboxChangeEvent) {
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.target.checked) {
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 flex-row w-full">
<div class="flex flex-col w-full">
<div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show M2M Tables -->
<a-checkbox v-model:checked="includeM2M" v-e="['c:themes:show-m2m-tables']" class="nc-settings-meta-misc">
{{ $t('msg.info.showM2mTables') }} <br />
<span class="text-gray-500">{{ $t('msg.info.showM2mTablesDesc') }}</span>
</a-checkbox>
</div>
<div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show NULL -->
<a-checkbox v-model:checked="showNull" v-e="['c:settings:show-null']" class="nc-settings-show-null">
{{ $t('msg.info.showNullInCells') }} <br />
<span class="text-gray-500">{{ $t('msg.info.showNullInCellsDesc') }}</span>
</a-checkbox>
</div>
<div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show NULL and EMPTY in Filters -->
<a-checkbox
v-model:checked="showNullAndEmptyInFilter"
v-e="['c:settings:show-null-and-empty-in-filter']"
class="nc-settings-show-null-and-empty-in-filter"
@change="showNullAndEmptyInFilterOnChange"
>
{{ $t('msg.info.showNullAndEmptyInFilter') }} <br />
<span class="text-gray-500">{{ $t('msg.info.showNullAndEmptyInFilterDesc') }}</span>
</a-checkbox>
</div>
</div>
</div>
</template>

22
packages/nc-gui/components/general/AddBaseButton.vue

@ -1,22 +0,0 @@
<script setup lang="ts">
const { isUIAllowed } = useRoles()
const { t } = useI18n()
const toggleDialog = inject(ToggleDialogInj, () => {})
</script>
<template>
<div
v-if="isUIAllowed('settingsPage')"
class="flex items-center w-full pl-3 hover:(text-primary bg-primary bg-opacity-5)"
@click="toggleDialog(true, undefined, undefined, baseId)"
>
<div>
<div class="flex items-center space-x-1">
<component :is="iconMap.users" class="mr-1 nc-new-source" />
<div>{{ t('title.teamAndSettings') }}</div>
</div>
</div>
</div>
</template>

13
packages/nc-gui/components/project/View.vue

@ -58,8 +58,10 @@ watch(
projectPageTab.value = 'collaborator'
} else if (newVal === 'data-source') {
projectPageTab.value = 'data-source'
} else {
} else if (newVal === 'allTable') {
projectPageTab.value = 'allTable'
} else {
projectPageTab.value = 'base-settings'
}
return
}
@ -201,6 +203,15 @@ watch(
</template>
<DashboardSettingsDataSources v-model:state="baseSettingsState" :base-id="base.id" class="max-h-full" />
</a-tab-pane>
<a-tab-pane v-if="isUIAllowed('baseMiscSettings')" key="base-settings">
<template #tab>
<div class="tab-title" data-testid="proj-view-tab__base-settings">
<GeneralIcon icon="ncSettings" />
<div>{{ $t('activity.settings') }}</div>
</div>
</template>
<DashboardSettingsBaseSettings :base-id="base.id!" class="max-h-full" />
</a-tab-pane>
</a-tabs>
</div>
</div>

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

@ -50,7 +50,6 @@ export const EditModeInj: InjectionKey<Ref<boolean>> = Symbol('edit-mode-injecti
export const SharedViewPasswordInj: InjectionKey<Ref<string | null>> = Symbol('shared-view-password-injection')
export const CellUrlDisableOverlayInj: InjectionKey<Ref<boolean>> = Symbol('cell-url-disable-url')
export const DropZoneRef: InjectionKey<Ref<Element | undefined>> = Symbol('drop-zone-ref')
export const ToggleDialogInj: InjectionKey<Function> = Symbol('toggle-dialog-injection')
export const CellClickHookInj: InjectionKey<EventHook<MouseEvent> | undefined> = Symbol('cell-click-injection')
export const SaveRowInj: InjectionKey<(() => void) | undefined> = Symbol('save-row-injection')
export const CurrentCellInj: InjectionKey<Ref<Element | undefined>> = Symbol('current-cell-injection')

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

@ -93,6 +93,8 @@
"none": "None"
},
"general": {
"snapshot": "Snapshot",
"baseSnapshots": "Base Snapshots",
"featurePreview": "Feature Preview",
"scripts": "Scripts",
"configure": "Configure",
@ -648,6 +650,10 @@
"lockedByUser": "Locked by {user}"
},
"labels": {
"miscBaseSettingsLabel": "Snapshots are complete backups of your database at the time of creation. Restoring a snapshot duplicates the data.",
"snapShotSubText": "Snapshots are complete backups of your database at the time of creation. Restoring a snapshot duplicates the data.",
"newSnapshot": "New Snapshot",
"searchASnapshot": "Search a snapshot",
"continue": "Continue",
"toggleExperimentalFeature": "Enable or disable experimental features with ease, allowing you to explore and evaluate upcoming functionalities.",
"modifiedOn": "Modified on",

23
packages/nc-gui/pages/index.vue

@ -4,14 +4,6 @@ definePageMeta({
hasSidebar: true,
})
const dialogOpen = ref(false)
const openDialogKey = ref<string>('')
const dataSourcesState = ref<string>('')
const baseId = ref<string>()
const basesStore = useBases()
const { populateWorkspace } = useWorkspace()
@ -101,15 +93,6 @@ onMounted(() => {
}
})
})
function toggleDialog(value?: boolean, key?: string, dsState?: string, pId?: string) {
dialogOpen.value = value ?? !dialogOpen.value
openDialogKey.value = key || ''
dataSourcesState.value = dsState || ''
baseId.value = pId || ''
}
provide(ToggleDialogInj, toggleDialog)
</script>
<template>
@ -128,12 +111,6 @@ provide(ToggleDialogInj, toggleDialog)
<NuxtPage />
</template>
</NuxtLayout>
<LazyDashboardSettingsModal
v-model:model-value="dialogOpen"
v-model:open-key="openDialogKey"
v-model:data-sources-state="dataSourcesState"
:base-id="baseId"
/>
<DlgSharedBaseDuplicate v-if="isUIAllowed('baseDuplicate')" v-model="isDuplicateDlgOpen" />
</div>
</template>

17
packages/nc-gui/pages/index/[typeOrId]/[baseId].vue

@ -3,23 +3,6 @@ definePageMeta({
hideHeader: true,
hasSidebar: true,
})
const dialogOpen = ref(false)
const openDialogKey = ref<string>('')
const dataSourcesState = ref<string>('')
const baseId = ref<string>()
function toggleDialog(value?: boolean, key?: string, dsState?: string, pId?: string) {
dialogOpen.value = value ?? !dialogOpen.value
openDialogKey.value = key || ''
dataSourcesState.value = dsState || ''
baseId.value = pId || ''
}
provide(ToggleDialogInj, toggleDialog)
</script>
<template>

2
packages/nc-gui/store/config.ts

@ -18,7 +18,7 @@ export const useConfigStore = defineStore('configStore', () => {
const isMobileMode = ref(isViewPortMobile())
const projectPageTab = ref<'allTable' | 'collaborator' | 'data-source'>('allTable')
const projectPageTab = ref<'allTable' | 'collaborator' | 'data-source' | 'base-settings'>('allTable')
const onViewPortResize = () => {
isMobileMode.value = isViewPortMobile()

Loading…
Cancel
Save