Browse Source

feat(gui-v2): update tab state on table rename

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2830/head
Pranav C 2 years ago
parent
commit
b7ce972695
  1. 21
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  2. 105
      packages/nc-gui-v2/components/dlg/TableRename.vue
  3. 21
      packages/nc-gui-v2/composables/useTabs.ts
  4. 2
      packages/nc-gui-v2/pages/nc/[projectId].vue

21
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -113,7 +113,7 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
} }
const deleteTable = (table: TableType) => { const deleteTable = (table: TableType) => {
$e('c:table:delete'); $e('c:table:delete')
// 'Click Submit to Delete The table' // 'Click Submit to Delete The table'
Modal.confirm({ Modal.confirm({
title: `Click Yes to Delete The table : ${table.title}`, title: `Click Yes to Delete The table : ${table.title}`,
@ -195,7 +195,7 @@ const showRenameTableDlg = (table: TableType) => {
<span class="flex-grow text-bold" <span class="flex-grow text-bold"
>{{ $t('objects.tables') }} <template v-if="tables?.length">({{ tables.length }})</template></span >{{ $t('objects.tables') }} <template v-if="tables?.length">({{ tables.length }})</template></span
> >
<MdiPlus class="text-gray-500" @click.stop="tableCreateDlg = true" v-t="['c:table:create:navdraw']"/> <MdiPlus v-t="['c:table:create:navdraw']" class="text-gray-500" @click.stop="tableCreateDlg = true" />
<MdiMenuDown <MdiMenuDown
class="transition-transform !duration-100 text-gray-500" class="transition-transform !duration-100 text-gray-500"
:class="{ 'transform rotate-180': showTableList }" :class="{ 'transform rotate-180': showTableList }"
@ -207,11 +207,11 @@ const showRenameTableDlg = (table: TableType) => {
<a-menu-item <a-menu-item
v-for="table in filteredTables" v-for="table in filteredTables"
:key="table.id" :key="table.id"
v-t="['a:table:open']"
class="!pl-1 py-1 !h-[28px] !my-0 text-sm pointer group" class="!pl-1 py-1 !h-[28px] !my-0 text-sm pointer group"
:data-order="table.order" :data-order="table.order"
:data-id="table.id" :data-id="table.id"
@click="addTab({ type: 'table', title: table.title, id: table.id })" @click="addTab({ type: 'table', title: table.title, id: table.id })"
v-t="['a:table:open']"
> >
<div class="flex align-center gap-1 h-full" @contextmenu="setMenuContext('table', table)"> <div class="flex align-center gap-1 h-full" @contextmenu="setMenuContext('table', table)">
<MdiDrag class="transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move" /> <MdiDrag class="transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 nc-drag-icon cursor-move" />
@ -222,8 +222,9 @@ const showRenameTableDlg = (table: TableType) => {
<MdiMenuIcon class="transition-opacity opacity-0 group-hover:opacity-100" /> <MdiMenuIcon class="transition-opacity opacity-0 group-hover:opacity-100" />
<template #overlay> <template #overlay>
<a-menu class="cursor-pointer"> <a-menu class="cursor-pointer">
<a-menu-item class="!text-xs" <a-menu-item v-t="['c:table:rename:navdraw:options']" class="!text-xs" @click="showRenameTableDlg(table)">
v-t="['c:table:rename:navdraw:options']" @click="showRenameTableDlg(table)"> Rename</a-menu-item> Rename</a-menu-item
>
<a-menu-item class="!text-xs" @click="deleteTable(table)"> Delete</a-menu-item> <a-menu-item class="!text-xs" @click="deleteTable(table)"> Delete</a-menu-item>
</a-menu> </a-menu>
</template> </template>
@ -238,12 +239,16 @@ const showRenameTableDlg = (table: TableType) => {
<template #overlay> <template #overlay>
<a-menu class="cursor-pointer"> <a-menu class="cursor-pointer">
<template v-if="contextMenuTarget.type === 'table'"> <template v-if="contextMenuTarget.type === 'table'">
<a-menu-item class="!text-xs" <a-menu-item
v-t="['c:table:rename:navdraw:right-click']" @click="showRenameTableDlg(contextMenuTarget.value)">Table Rename</a-menu-item> v-t="['c:table:rename:navdraw:right-click']"
class="!text-xs"
@click="showRenameTableDlg(contextMenuTarget.value)"
>Table Rename</a-menu-item
>
<a-menu-item class="!text-xs" @click="deleteTable(contextMenuTarget.value)">Table Delete</a-menu-item> <a-menu-item class="!text-xs" @click="deleteTable(contextMenuTarget.value)">Table Delete</a-menu-item>
</template> </template>
<template v-else> <template v-else>
<a-menu-item class="!text-xs" @click="loadTables" v-t="['a:table:refresh:navdraw']">Tables Refresh</a-menu-item> <a-menu-item v-t="['a:table:refresh:navdraw']" class="!text-xs" @click="loadTables">Tables Refresh</a-menu-item>
</template> </template>
</a-menu> </a-menu>
</template> </template>

105
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -1,90 +1,89 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, watchEffect } from "@vue/runtime-core"; import { onMounted, watchEffect } from '@vue/runtime-core'
import { Form } from "ant-design-vue"; import { Form } from 'ant-design-vue'
import type { TableType } from "nocodb-sdk"; import type { TableType } from 'nocodb-sdk'
import { useToast } from "vue-toastification"; import { useToast } from 'vue-toastification'
import { useProject, useTableCreate, useTabs } from "#imports"; import { useProject, useTableCreate, useTabs } from '#imports'
import { extractSdkResponseErrorMsg } from "~/utils/errorUtils"; import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { validateTableName } from "~/utils/validation"; import { validateTableName } from '~/utils/validation'
import { useNuxtApp } from "#app"; import { useNuxtApp } from '#app'
interface Props { interface Props {
modelValue?: boolean; modelValue?: boolean
tableMeta: TableType; tableMeta: TableType
} }
const { modelValue = false, tableMeta } = defineProps<Props>(); const { modelValue = false, tableMeta } = defineProps<Props>()
const emit = defineEmits(["update:modelValue", "updated"]); const emit = defineEmits(['update:modelValue', 'updated'])
const { $e, $api } = useNuxtApp(); const { $e, $api } = useNuxtApp()
const toast = useToast(); const toast = useToast()
const dialogShow = computed({ const dialogShow = computed({
get() { get() {
return modelValue; return modelValue
}, },
set(v) { set(v) {
emit("update:modelValue", v); emit('update:modelValue', v)
} },
}); })
const { addTab } = useTabs(); const { updateTab } = useTabs()
const { loadTables } = useProject(); const { loadTables } = useProject()
const { project, tables } = useProject(); const { project, tables } = useProject()
const prefix = computed(() => project?.value?.prefix || ""); const prefix = computed(() => project?.value?.prefix || '')
const validateDuplicateAlias = (v: string) => { const validateDuplicateAlias = (v: string) => {
return (tables?.value || []).every((t) => t.title !== (v || "")) || "Duplicate table alias"; return (tables?.value || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias'
}; }
const validateLeadingOrTrailingWhiteSpace = (v: string) => { const validateLeadingOrTrailingWhiteSpace = (v: string) => {
return !/^\s+|\s+$/.test(v) || "Leading or trailing whitespace not allowed in table name"; return !/^\s+|\s+$/.test(v) || 'Leading or trailing whitespace not allowed in table name'
}; }
const validateDuplicate = (v: string) => { const validateDuplicate = (v: string) => {
return (tables?.value || []).every((t) => t.table_name.toLowerCase() !== (v || "").toLowerCase()) || "Duplicate table name"; return (tables?.value || []).every((t) => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name'
}; }
const inputEl = $ref<any>(); const inputEl = $ref<any>()
const useForm = Form.useForm; const useForm = Form.useForm
const formState = reactive({ const formState = reactive({
title: "" title: '',
}); })
const validators = computed(() => { const validators = computed(() => {
return { return {
title: [validateTableName, validateDuplicateAlias], title: [validateTableName, validateDuplicateAlias],
table_name: [validateTableName] table_name: [validateTableName],
}; }
}); })
const { resetFields, validate, validateInfos } = useForm(formState, validators); const { resetFields, validate, validateInfos } = useForm(formState, validators)
watchEffect(() => { watchEffect(() => {
if (tableMeta?.title) formState.title = tableMeta?.title; if (tableMeta?.title) formState.title = tableMeta?.title
// todo: replace setTimeout and follow better approach // todo: replace setTimeout and follow better approach
nextTick(() => { nextTick(() => {
const input = inputEl?.$el; const input = inputEl?.$el
input.setSelectionRange(0, formState.title.length); input.setSelectionRange(0, formState.title.length)
input.focus(); input.focus()
}); })
}); })
const renameTable = async () => { const renameTable = async () => {
try { try {
await $api.dbTable.update(tableMeta?.id as string, { await $api.dbTable.update(tableMeta?.id as string, {
title: formState.title title: formState.title,
}); })
loadTables(); loadTables()
toast.success("Table renamed successfully"); updateTab({ id: tableMeta?.id }, { title: formState.title })
$e("a:table:rename"); toast.success('Table renamed successfully')
$e('a:table:rename')
} catch (e) { } catch (e) {
toast.error(await extractSdkResponseErrorMsg(e)); toast.error(await extractSdkResponseErrorMsg(e))
} }
}; }
</script> </script>
<template> <template>
<a-modal v-model:visible="dialogShow" @keydown.esc="dialogShow = false" @finish="renameTable"> <a-modal v-model:visible="dialogShow" @keydown.esc="dialogShow = false" @finish="renameTable">
<template #footer> <template #footer>
<a-button key="back" @click="dialogShow = false">{{ $t("general.cancel") }}</a-button> <a-button key="back" @click="dialogShow = false">{{ $t('general.cancel') }}</a-button>
<a-button key="submit" type="primary" @click="renameTable">{{ $t("general.submit") }}</a-button> <a-button key="submit" type="primary" @click="renameTable">{{ $t('general.submit') }}</a-button>
</template> </template>
<div class="pl-10 pr-10 pt-5"> <div class="pl-10 pr-10 pt-5">
<a-form :model="formState" name="create-new-table-form"> <a-form :model="formState" name="create-new-table-form">

21
packages/nc-gui-v2/composables/useTabs.ts

@ -6,6 +6,13 @@ export interface TabItem {
id?: string id?: string
} }
function getPredicate(key: Partial<TabItem>) {
return (tab: TabItem) =>
(!('id' in key) || tab.id === key.id) &&
(!('title' in key) || tab.title === key.id) &&
(!('type' in key) || tab.type === key.id)
}
export default () => { export default () => {
const tabs = useState<TabItem[]>('tabs', () => []) const tabs = useState<TabItem[]>('tabs', () => [])
const activeTab = useState<number>('activeTab', () => 0) const activeTab = useState<number>('activeTab', () => 0)
@ -25,13 +32,21 @@ export default () => {
const clearTabs = () => { const clearTabs = () => {
tabs.value = [] tabs.value = []
} }
const closeTab = (key: number | TabItem) => {
const closeTab = (key: number | Partial<TabItem>) => {
if (typeof key === 'number') tabs.value.splice(key, 1) if (typeof key === 'number') tabs.value.splice(key, 1)
else { else {
const index = tabs.value.findIndex((tab) => tab.id === key.id && tab.type === key.type && tab.title === key.title) const index = tabs.value.findIndex(getPredicate(key))
if (index > -1) tabs.value.splice(index, 1) if (index > -1) tabs.value.splice(index, 1)
} }
} }
return { tabs, addTab, activeTab, clearTabs, closeTab } const updateTab = (key: number | Partial<TabItem>, newTabItemProps: Partial<TabItem>) => {
const tab = typeof key === 'number' ? tabs.value[key] : tabs.value.find(getPredicate(key))
if (tab) {
Object.assign(tab, newTabItemProps)
}
}
return { tabs, addTab, activeTab, clearTabs, closeTab, updateTab }
} }

2
packages/nc-gui-v2/pages/nc/[projectId].vue

@ -2,7 +2,7 @@
const route = useRoute() const route = useRoute()
const { loadProject, loadTables } = useProject(route.params.projectId as string) const { loadProject, loadTables } = useProject(route.params.projectId as string)
const { clearTabs, addTab } = useTabs() const { clearTabs, addTab } = useTabs()
const {$state} = useNuxtApp() const { $state } = useNuxtApp()
addTab({ type: 'auth', title: 'Team & Auth' }) addTab({ type: 'auth', title: 'Team & Auth' })

Loading…
Cancel
Save