Browse Source

feat(gui): allow project reordering for super admin user

Signed-off-by: Pranav C <pranavxc@gmail.com>
feat/sortable-project-list
Pranav C 2 years ago
parent
commit
4945b5655a
  1. 113
      packages/nc-gui/pages/index/index/index.vue
  2. 2
      packages/nocodb/src/lib/Noco.ts
  3. 10
      packages/nocodb/src/lib/meta/api/projectApis.ts
  4. 11
      packages/nocodb/src/lib/models/Project.ts
  5. 2
      packages/nocodb/src/lib/models/ProjectUser.ts
  6. 2
      packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts
  7. 18
      packages/nocodb/src/lib/version-upgrader/ncProjectOrderUpgrader.ts

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

@ -1,5 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Table } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import Sortable from 'sortablejs'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import { breakpointsTailwind } from '@vueuse/core' import { breakpointsTailwind } from '@vueuse/core'
import { import {
@ -43,6 +45,7 @@ const { appInfo } = useGlobal()
const loadProjects = async () => { const loadProjects = async () => {
const response = await api.project.list({}) const response = await api.project.list({})
projects.value = response.list projects.value = response.list
await initSortable()
} }
const filteredProjects = computed( const filteredProjects = computed(
@ -139,6 +142,113 @@ const copyProjectMeta = async () => {
copy(JSON.stringify(aggregatedMetaInfo)) copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard') message.info('Copied aggregated project meta to clipboard')
} }
const table = ref<typeof Table>()
let sortableInstance: Sortable
const extractTableRowId = (el?: HTMLElement | null) => {
return el?.querySelector('[data-project-id]')?.getAttribute('data-project-id')
}
// todo: replace with vuedraggable
function initSortable() {
// const base_id = el.getAttribute('nc-base')
sortableInstance?.destroy()
sortableInstance = Sortable.create(table.value?.$el?.querySelector('.ant-table-tbody') as HTMLLIElement, {
// handle: '.nc-drag-icon',
onEnd: async (evt) => {
console.log(evt)
const projectId = extractTableRowId(evt.item)
console.log(projectId)
const prevElement = evt.item?.previousElementSibling
const nextElement = evt.item?.nextElementSibling
let prevElementIndex = -1
let nextElementIndex = -1
// find the adjacent siblings index
if (!prevElement && !nextElement) {
return
} else if (!prevElement) {
nextElementIndex = projects.value?.findIndex((p) => p.id === extractTableRowId(nextElement))
if (nextElementIndex > 0) prevElementIndex = nextElementIndex - 1
} else {
prevElementIndex = projects.value?.findIndex((p) => p.id === extractTableRowId(prevElement))
if (prevElementIndex < projects.value!.length - 1) nextElementIndex = prevElementIndex + 1
}
if (prevElementIndex === -1 && nextElementIndex === -1) return
let newOrder
if (prevElementIndex === -1) {
newOrder = projects.value![nextElementIndex].order / 2
} else if (nextElementIndex === -1) {
newOrder = projects.value![prevElementIndex].order + 1
} else {
newOrder = ((projects.value![prevElementIndex].order ?? 1) + (projects.value![nextElementIndex].order ?? 1)) / 2
}
console.log({ newOrder, prevElementIndex, nextElementIndex, nextElement, prevElement })
// Update local project
const localProject = projects.value?.find((p) => p.id === projectId)
if (localProject) {
localProject.order = newOrder
}
// sync project order with server
await $api.project.update(projectId, { order: newOrder })
/* let updatedElementOrder: number
// if moved to the beginning
if (!prevElement) {
const nextElementIndex = projects.value?.findIndex(
(p) => p.id === nextElement?.querySelector('[data-project-id]').getAttribute('data-project-id'),
)
updatedElementOrder = projects.value?.[nextElementIndex!].order! / 2
}
// if moved to the ending
else if (!nextElement) {
const prevElementIndex = projects.value?.findIndex(
(p) => p.id === prevElement?.querySelector('[data-project-id]')?.getAttribute('data-project-id'),
)
updatedElementOrder = (projects.value?.[prevElementIndex!].order ?? 1) + 1
}
// if moved in between
else {
const nextElementIndex = projects.value?.findIndex(
(p) => p.id === nextElement?.querySelector('[data-project-id]')?.getAttribute('data-project-id'),
)
const prevElementIndex = projects.value?.findIndex(
(p) => p.id === prevElement?.querySelector('[data-project-id]')?.getAttribute('data-project-id'),
)
console.log({
prevElement,
nextElement,
nextElementIndex,
prevElementIndex,
prevElementOrder: projects.value?.[prevElementIndex!].order,
nextElementOrder: projects.value?.[nextElementIndex!].order,
})
updatedElementOrder = ((projects.value?.[nextElementIndex!].order ?? 1) + (projects.value?.[prevElementIndex!].order ?? 1)) / 2
}
console.log({ updatedElementOrder })
*/
},
animation: 150,
})
}
</script> </script>
<template> <template>
@ -221,6 +331,7 @@ const copyProjectMeta = async () => {
<a-table <a-table
v-else v-else
ref="table"
:custom-row="customRow" :custom-row="customRow"
:data-source="filteredProjects" :data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }" :pagination="{ position: ['bottomCenter'] }"
@ -233,7 +344,7 @@ const copyProjectMeta = async () => {
<!-- Title --> <!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title"> <a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text, record }"> <template #default="{ text, record }">
<div class="flex items-center"> <div class="flex items-center nc-project-title" :data-project-id="record.id">
<div @click.stop> <div @click.stop>
<a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click"> <a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click">
<template v-if="isUIAllowed('projectTheme')"> <template v-if="isUIAllowed('projectTheme')">

2
packages/nocodb/src/lib/Noco.ts

@ -101,7 +101,7 @@ export default class Noco {
constructor() { constructor() {
process.env.PORT = process.env.PORT || '8080'; process.env.PORT = process.env.PORT || '8080';
// todo: move // todo: move
process.env.NC_VERSION = '0098005'; process.env.NC_VERSION = '0100003';
// if env variable NC_MINIMAL_DBS is set, then disable project creation with external sources // if env variable NC_MINIMAL_DBS is set, then disable project creation with external sources
if (process.env.NC_MINIMAL_DBS) { if (process.env.NC_MINIMAL_DBS) {

10
packages/nocodb/src/lib/meta/api/projectApis.ts

@ -56,8 +56,18 @@ export async function projectUpdate(
'title', 'title',
'meta', 'meta',
'color', 'color',
'order',
]); ]);
// todo: maintain project order in user_project table for non super admin users
// block project order update for non super admin users
if (
'order' in data &&
!req['user']?.roles?.includes(OrgUserRoles.SUPER_ADMIN)
) {
NcError.forbidden('Only super admin can update order');
}
if ( if (
data?.title && data?.title &&
project.title !== data.title && project.title !== data.title &&

11
packages/nocodb/src/lib/models/Project.ts

@ -54,6 +54,9 @@ export default class Project implements ProjectType {
is_meta: projectBody.is_meta, is_meta: projectBody.is_meta,
created_at: projectBody.created_at, created_at: projectBody.created_at,
updated_at: projectBody.updated_at, updated_at: projectBody.updated_at,
order:
projectBody.order ??
(await ncMeta.metaGetNextOrder(MetaTable.PROJECT, {})),
} }
); );
@ -101,6 +104,9 @@ export default class Project implements ProjectType {
}, },
], ],
}, },
orderBy: {
order: 'asc',
},
}); });
await NocoCache.setList(CacheScope.PROJECT, [], projectList); await NocoCache.setList(CacheScope.PROJECT, [], projectList);
} }
@ -261,6 +267,11 @@ export default class Project implements ProjectType {
} }
o = { ...o, ...updateObj }; o = { ...o, ...updateObj };
// if order is changed, delete existing project list to get new ordered list
if ('order' in updateObj) {
await NocoCache.delAll(CacheScope.PROJECT, '*');
}
await NocoCache.del(CacheScope.INSTANCE_META); await NocoCache.del(CacheScope.INSTANCE_META);
// set cache // set cache

2
packages/nocodb/src/lib/models/ProjectUser.ts

@ -245,7 +245,7 @@ export default class ProjectUser {
this.where(`${MetaTable.PROJECT}.deleted`, false).orWhereNull( this.where(`${MetaTable.PROJECT}.deleted`, false).orWhereNull(
`${MetaTable.PROJECT}.deleted` `${MetaTable.PROJECT}.deleted`
); );
}); }).orderBy('order', 'asc');
if (projectList?.length) { if (projectList?.length) {
await NocoCache.setList(CacheScope.USER_PROJECT, [userId], projectList); await NocoCache.setList(CacheScope.USER_PROJECT, [userId], projectList);

2
packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts

@ -8,6 +8,7 @@ import ncProjectEnvUpgrader0011045 from './ncProjectEnvUpgrader0011045';
import ncProjectUpgraderV2_0090000 from './ncProjectUpgraderV2_0090000'; import ncProjectUpgraderV2_0090000 from './ncProjectUpgraderV2_0090000';
import ncDataTypesUpgrader from './ncDataTypesUpgrader'; import ncDataTypesUpgrader from './ncDataTypesUpgrader';
import ncProjectRolesUpgrader from './ncProjectRolesUpgrader'; import ncProjectRolesUpgrader from './ncProjectRolesUpgrader';
import ncProjectOrderUpgrader from './ncProjectOrderUpgrader';
const log = debug('nc:version-upgrader'); const log = debug('nc:version-upgrader');
import boxen from 'boxen'; import boxen from 'boxen';
@ -35,6 +36,7 @@ export default class NcUpgrader {
{ name: '0090000', handler: ncProjectUpgraderV2_0090000 }, { name: '0090000', handler: ncProjectUpgraderV2_0090000 },
{ name: '0098004', handler: ncDataTypesUpgrader }, { name: '0098004', handler: ncDataTypesUpgrader },
{ name: '0098005', handler: ncProjectRolesUpgrader }, { name: '0098005', handler: ncProjectRolesUpgrader },
{ name: '0100003', handler: ncProjectOrderUpgrader },
]; ];
if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) { if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) {
return; return;

18
packages/nocodb/src/lib/version-upgrader/ncProjectOrderUpgrader.ts

@ -0,0 +1,18 @@
import { MetaTable } from '../utils/globals'
import { NcUpgraderCtx } from './NcUpgrader'
/** Upgrader for upgrading roles */
export default async function({ ncMeta }: NcUpgraderCtx) {
const projects = await ncMeta.metaList2(null, null, MetaTable.PROJECT)
let order = 0;
for (const project of projects) {
await ncMeta.metaUpdate(
null,
null,
MetaTable.PROJECT,
{
order: ++order,
}, project.id)
}
}
Loading…
Cancel
Save