Browse Source

feat: added command palette for oss

pull/7490/head
Ramesh Mane 7 months ago
parent
commit
4dff9339d0
  1. 9
      packages/nc-gui/app.vue
  2. 6
      packages/nc-gui/components/cmd-k/index.vue
  3. 8
      packages/nc-gui/composables/useCommandPalette/index.ts
  4. 19
      packages/nocodb/src/controllers/command-palette.controller.spec.ts
  5. 27
      packages/nocodb/src/controllers/command-palette.controller.ts
  6. 4
      packages/nocodb/src/modules/metas/metas.module.ts
  7. 26
      packages/nocodb/src/schema/swagger.json
  8. 19
      packages/nocodb/src/services/command-palette.service.spec.ts
  9. 120
      packages/nocodb/src/services/command-palette.service.ts

9
packages/nc-gui/app.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { applyNonSelectable, computed, useRouter, useTheme, useCommandPalette } from '#imports'
import { applyNonSelectable, computed, useCommandPalette, useRouter, useTheme } from '#imports'
const router = useRouter()
@ -63,12 +63,6 @@ if (typeof window !== 'undefined') {
// @ts-expect-error using arbitrary window key
window.__ncvue = true
}
function onScope(scope: string) {
if (scope === 'root') {
loadTemporaryScope({ scope: 'root', data: {} })
}
}
</script>
<template>
@ -85,7 +79,6 @@ function onScope(scope: string) {
:data="cmdData"
:placeholder="cmdPlaceholder"
:load-temporary-scope="loadTemporaryScope"
@scope="onScope"
/>
<!-- Recent Views. Cycles through recently visited Views -->
<CmdL v-model:open="cmdL" />

6
packages/nc-gui/components/cmd-k/index.vue

@ -431,12 +431,12 @@ defineExpose({
<div class="flex flex-grow-1 w-full text-sm items-center gap-2 justify-center">
<MdiFileOutline class="h-4 w-4" />
<span
class="cursor-pointer"
@click.stop="
() => {
console.log('clicked')
}
"
class="cursor-pointer"
>
Document
</span>
@ -445,12 +445,12 @@ defineExpose({
<div class="flex flex-grow-1 text-brand-500 w-full text-sm items-center gap-2 justify-center">
<MdiMapMarkerOutline class="h-4 w-4" />
<span
class="cursor-pointer"
@click.stop="
() => {
console.log('clicked')
}
"
class="cursor-pointer"
>
Quick Navigation
</span>
@ -461,12 +461,12 @@ defineExpose({
<div class="flex flex-grow-1 w-full text-sm items-center gap-2 justify-center">
<MdiClockOutline class="h-4 w-4" />
<span
class="cursor-pointer"
@click.stop="
() => {
console.log('clicked')
}
"
class="cursor-pointer"
>
Recent
</span>

8
packages/nc-gui/composables/useCommandPalette/index.ts

@ -149,11 +149,11 @@ export const useCommandPalette = createSharedComposable(() => {
if (activeScope.value.scope === 'disabled') return
activeScope.value = { scope: 'disabled', data: {} }
loadScope()
} else if (route.value.params.typeOrId.startsWith('w')) {
if (activeScope.value.data.workspace_id === route.value.params.typeOrId) return
} else if (route.value.params.typeOrId === 'nc') {
if (activeScope.value.data.base_id === route.value.params.baseId) return
activeScope.value = {
scope: `ws-${route.value.params.typeOrId}`,
data: { workspace_id: route.value.params.typeOrId },
scope: `p-${route.value.params.baseId}`,
data: {},
}
loadScope()
}

19
packages/nocodb/src/controllers/command-palette.controller.spec.ts

@ -0,0 +1,19 @@
import { Test } from '@nestjs/testing';
import { CommandPaletteController } from './command-palette.controller';
import type { TestingModule } from '@nestjs/testing';
describe('CommandPaletteController', () => {
let controller: CommandPaletteController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CommandPaletteController],
}).compile();
controller = module.get<CommandPaletteController>(CommandPaletteController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

27
packages/nocodb/src/controllers/command-palette.controller.ts

@ -0,0 +1,27 @@
import { Controller, HttpCode, Post, Req, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import type { UserType } from 'nocodb-sdk';
import { GlobalGuard } from '~/guards/global/global.guard';
import { CommandPaletteService } from '~/services/command-palette.service';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';
import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
@Controller()
@UseGuards(MetaApiLimiterGuard, GlobalGuard)
export class CommandPaletteController {
constructor(private commandPaletteService: CommandPaletteService) {}
@Post('/api/v1/command_palette')
@Acl('commandPalette', {
scope: 'org',
})
@HttpCode(200)
async commandPalette(@Req() req: Request) {
const data = this.commandPaletteService.commandPalette({
user: req?.user as UserType,
body: req.body,
});
return data;
}
}

4
packages/nocodb/src/modules/metas/metas.module.ts

@ -73,6 +73,8 @@ import { BaseUsersService } from '~/services/base-users/base-users.service';
import { NotificationsService } from '~/services/notifications.service';
import { NotificationsController } from '~/controllers/notifications.controller';
import { NotificationsGateway } from '~/gateways/notifications/notifications.gateway';
import { CommandPaletteService } from '~/services/command-palette.service';
import { CommandPaletteController } from '~/controllers/command-palette.controller';
export const metaModuleMetadata = {
imports: [
@ -122,6 +124,7 @@ export const metaModuleMetadata = {
SortsController,
SharedBasesController,
NotificationsController,
CommandPaletteController,
]
: []),
],
@ -163,6 +166,7 @@ export const metaModuleMetadata = {
BulkDataAliasService,
NotificationsService,
NotificationsGateway,
CommandPaletteService,
],
exports: [
TablesService,

26
packages/nocodb/src/schema/swagger.json

@ -16329,6 +16329,32 @@
]
}
},
"/api/v1/command_palette": {
"parameters": [],
"post": {
"summary": "Get command palette suggestions",
"operationId": "utils-command-palette",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"tags": [
"Utils"
],
"requestBody": {
"content": {
"application/json": {}
}
},
"description": "Get dynamic command palette suggestions based on scope"
}
},
"/jobs/listen": {
"post": {
"summary": "Jobs Listen",

19
packages/nocodb/src/services/command-palette.service.spec.ts

@ -0,0 +1,19 @@
import { Test } from '@nestjs/testing';
import { CommandPaletteService } from './command-palette.service';
import type { TestingModule } from '@nestjs/testing';
describe('CommandPaletteService', () => {
let service: CommandPaletteService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CommandPaletteService],
}).compile();
service = module.get<CommandPaletteService>(CommandPaletteService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

120
packages/nocodb/src/services/command-palette.service.ts

@ -0,0 +1,120 @@
import { Injectable } from '@nestjs/common';
import { type UserType, ViewTypes } from 'nocodb-sdk';
import { Base } from '~/models';
import { TablesService } from '~/services/tables.service';
const viewTypeAlias: Record<number, string> = {
[ViewTypes.GRID]: 'grid',
[ViewTypes.FORM]: 'form',
[ViewTypes.GALLERY]: 'gallery',
[ViewTypes.KANBAN]: 'kanban',
[ViewTypes.MAP]: 'map',
};
@Injectable()
export class CommandPaletteService {
constructor(private tablesService: TablesService) {}
async commandPalette(param: { body: any; user: UserType }) {
const cmdData = [];
try {
const { scope, data } = param.body;
console.log('param.user.id', param.user, scope, data);
if (scope === 'root') {
const bases = await Base.list({ user: param.user });
console.log('bases', bases);
for (const base of bases) {
cmdData.push({
id: `p-${base.id}`,
title: base.title,
icon: 'project',
section: 'Bases',
scopePayload: {
scope: `p-${base.id}`,
data: {
base_id: base.id,
},
},
});
}
console.log('scope');
} else if (scope.startsWith('p-')) {
const allBases = [];
const bases = await Base.list({ user: param.user });
console.log('bases', bases);
allBases.push(...bases);
const viewList = [];
for (const base of bases) {
viewList.push(
...(
(await this.tablesService.xcVisibilityMetaGet(
base.id,
null,
false,
)) as any[]
).filter((v) => {
return Object.keys(param.user.roles).some(
(role) => param.user.roles[role] && !v.disabled[role],
);
}),
);
}
const tableList = [];
const vwList = [];
for (const b of allBases) {
cmdData.push({
id: `p-${b.id}`,
title: b.title,
icon: 'project',
section: 'Bases',
});
}
for (const v of viewList) {
if (!tableList.find((el) => el.id === `tbl-${v.fk_model_id}`)) {
tableList.push({
id: `tbl-${v.fk_model_id}`,
title: v._ptn,
parent: `p-${v.base_id}`,
icon: v?.table_meta?.icon || v.ptype,
projectName: bases.find((el) => el.id === v.base_id)?.title,
section: 'Tables',
});
}
vwList.push({
id: `vw-${v.id}`,
title: `${v.title}`,
parent: `tbl-${v.fk_model_id}`,
icon: v?.meta?.icon || viewTypeAlias[v.type] || 'table',
projectName: bases.find((el) => el.id === v.base_id)?.title,
section: 'Views',
is_default: v?.is_default,
handler: {
type: 'navigate',
payload: `/nc/${v.base_id}/${v.fk_model_id}/${encodeURIComponent(
v.id,
)}`,
},
});
}
cmdData.push(...tableList);
cmdData.push(...vwList);
}
} catch (e) {
console.log(e);
return [];
}
return cmdData;
}
}
Loading…
Cancel
Save