mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 13:47:55 +00:00
Add shortcut metadata to data models & CommandMenu (#7977)
Resolves https://github.com/twentyhq/twenty/issues/7503 --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -27,11 +27,17 @@ export const GotoHotkeysEffectsProvider = () => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => (
|
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => {
|
||||||
|
if (!objectMetadataItem.shortcut) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<GoToHotkeyItemEffect
|
<GoToHotkeyItemEffect
|
||||||
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
||||||
hotkey={objectMetadataItem.namePlural[0]}
|
hotkey={objectMetadataItem.shortcut}
|
||||||
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
||||||
/>
|
/>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ describe('useCommandMenu', () => {
|
|||||||
labelSingular: 'Task',
|
labelSingular: 'Task',
|
||||||
labelPlural: 'Tasks',
|
labelPlural: 'Tasks',
|
||||||
shouldSyncLabelAndName: true,
|
shouldSyncLabelAndName: true,
|
||||||
|
shortcut: 'T',
|
||||||
description: 'A task',
|
description: 'A task',
|
||||||
icon: 'IconCheckbox',
|
icon: 'IconCheckbox',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export const useCommandMenu = () => {
|
|||||||
to: `/objects/${item.namePlural}`,
|
to: `/objects/${item.namePlural}`,
|
||||||
label: `Go to ${item.labelPlural}`,
|
label: `Go to ${item.labelPlural}`,
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
firstHotKey: 'G',
|
firstHotKey: item.shortcut ? 'G' : undefined,
|
||||||
secondHotKey: item.labelPlural[0],
|
secondHotKey: item.shortcut,
|
||||||
Icon: ALL_ICONS[
|
Icon: ALL_ICONS[
|
||||||
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
|
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
|||||||
updatedAt
|
updatedAt
|
||||||
labelIdentifierFieldMetadataId
|
labelIdentifierFieldMetadataId
|
||||||
imageIdentifierFieldMetadataId
|
imageIdentifierFieldMetadataId
|
||||||
|
shortcut
|
||||||
shouldSyncLabelAndName
|
shouldSyncLabelAndName
|
||||||
indexMetadatas(paging: { first: 100 }) {
|
indexMetadatas(paging: { first: 100 }) {
|
||||||
edges {
|
edges {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export const query = gql`
|
|||||||
updatedAt
|
updatedAt
|
||||||
labelIdentifierFieldMetadataId
|
labelIdentifierFieldMetadataId
|
||||||
imageIdentifierFieldMetadataId
|
imageIdentifierFieldMetadataId
|
||||||
|
shortcut
|
||||||
|
shouldSyncLabelAndName
|
||||||
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ export const objectMetadataItemSchema = z.object({
|
|||||||
namePlural: camelCaseStringSchema,
|
namePlural: camelCaseStringSchema,
|
||||||
nameSingular: camelCaseStringSchema,
|
nameSingular: camelCaseStringSchema,
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.string().datetime(),
|
||||||
|
shortcut: z.string().nullable().optional(),
|
||||||
shouldSyncLabelAndName: z.boolean(),
|
shouldSyncLabelAndName: z.boolean(),
|
||||||
}) satisfies z.ZodType<ObjectMetadataItem>;
|
}) satisfies z.ZodType<ObjectMetadataItem>;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddObjectShortcut1729676165199 implements MigrationInterface {
|
||||||
|
name = 'AddObjectShortcut1729676165199';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."objectMetadata" ADD "shortcut" character varying`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE UNIQUE INDEX "IDX_objectMetadata_shortcut_upper_workspace" ON "metadata"."objectMetadata" (UPPER("shortcut"), "workspaceId")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DROP INDEX "metadata"."IDX_objectMetadata_shortcut_upper_workspace"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."objectMetadata" DROP COLUMN "shortcut"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,11 @@ export class CreateObjectInput {
|
|||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
@Field({ nullable: true })
|
||||||
|
shortcut?: string;
|
||||||
|
|
||||||
@HideField()
|
@HideField()
|
||||||
dataSourceId: string;
|
dataSourceId: string;
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ export class ObjectMetadataDTO {
|
|||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
shortcut: string;
|
||||||
|
|
||||||
@FilterableField()
|
@FilterableField()
|
||||||
isCustom: boolean;
|
isCustom: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ export class UpdateObjectPayload {
|
|||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
@Field({ nullable: true })
|
||||||
|
shortcut?: string;
|
||||||
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
|
|||||||
@Column({ default: true })
|
@Column({ default: true })
|
||||||
isAuditLogged: boolean;
|
isAuditLogged: boolean;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
shortcut: string;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'uuid' })
|
@Column({ nullable: true, type: 'uuid' })
|
||||||
labelIdentifierFieldMetadataId?: string | null;
|
labelIdentifierFieldMetadataId?: string | null;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface WorkspaceEntityOptions {
|
|||||||
labelPlural: string;
|
labelPlural: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
shortcut?: string;
|
||||||
labelIdentifierStandardId?: string;
|
labelIdentifierStandardId?: string;
|
||||||
imageIdentifierStandardId?: string;
|
imageIdentifierStandardId?: string;
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,7 @@ export function WorkspaceEntity(
|
|||||||
options.labelIdentifierStandardId ?? BASE_OBJECT_STANDARD_FIELD_IDS.id,
|
options.labelIdentifierStandardId ?? BASE_OBJECT_STANDARD_FIELD_IDS.id,
|
||||||
imageIdentifierStandardId: options.imageIdentifierStandardId ?? null,
|
imageIdentifierStandardId: options.imageIdentifierStandardId ?? null,
|
||||||
icon: options.icon,
|
icon: options.icon,
|
||||||
|
shortcut: options.shortcut,
|
||||||
isAuditLogged,
|
isAuditLogged,
|
||||||
isSystem,
|
isSystem,
|
||||||
gate,
|
gate,
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ export interface WorkspaceEntityMetadataArgs {
|
|||||||
*/
|
*/
|
||||||
readonly icon?: string;
|
readonly icon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity shortcut.
|
||||||
|
*/
|
||||||
|
readonly shortcut?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is audit logged.
|
* Is audit logged.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export const SEARCH_FIELDS_FOR_COMPANY: FieldTypeAndNameMetadata[] = [
|
|||||||
labelPlural: 'Companies',
|
labelPlural: 'Companies',
|
||||||
description: 'A company',
|
description: 'A company',
|
||||||
icon: 'IconBuildingSkyscraper',
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
shortcut: 'C',
|
||||||
labelIdentifierStandardId: COMPANY_STANDARD_FIELD_IDS.name,
|
labelIdentifierStandardId: COMPANY_STANDARD_FIELD_IDS.name,
|
||||||
})
|
})
|
||||||
export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const SEARCH_FIELDS_FOR_NOTES: FieldTypeAndNameMetadata[] = [
|
|||||||
labelPlural: 'Notes',
|
labelPlural: 'Notes',
|
||||||
description: 'A note',
|
description: 'A note',
|
||||||
icon: 'IconNotes',
|
icon: 'IconNotes',
|
||||||
|
shortcut: 'N',
|
||||||
labelIdentifierStandardId: NOTE_STANDARD_FIELD_IDS.title,
|
labelIdentifierStandardId: NOTE_STANDARD_FIELD_IDS.title,
|
||||||
})
|
})
|
||||||
export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
|
export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const SEARCH_FIELDS_FOR_OPPORTUNITY: FieldTypeAndNameMetadata[] = [
|
|||||||
labelPlural: 'Opportunities',
|
labelPlural: 'Opportunities',
|
||||||
description: 'An opportunity',
|
description: 'An opportunity',
|
||||||
icon: 'IconTargetArrow',
|
icon: 'IconTargetArrow',
|
||||||
|
shortcut: 'O',
|
||||||
labelIdentifierStandardId: OPPORTUNITY_STANDARD_FIELD_IDS.name,
|
labelIdentifierStandardId: OPPORTUNITY_STANDARD_FIELD_IDS.name,
|
||||||
})
|
})
|
||||||
@WorkspaceIsNotAuditLogged()
|
@WorkspaceIsNotAuditLogged()
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const SEARCH_FIELDS_FOR_PERSON: FieldTypeAndNameMetadata[] = [
|
|||||||
labelPlural: 'People',
|
labelPlural: 'People',
|
||||||
description: 'A person',
|
description: 'A person',
|
||||||
icon: 'IconUser',
|
icon: 'IconUser',
|
||||||
|
shortcut: 'P',
|
||||||
labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name,
|
labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name,
|
||||||
imageIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.avatarUrl,
|
imageIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.avatarUrl,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
|||||||
labelPlural: 'Tasks',
|
labelPlural: 'Tasks',
|
||||||
description: 'A task',
|
description: 'A task',
|
||||||
icon: 'IconCheckbox',
|
icon: 'IconCheckbox',
|
||||||
|
shortcut: 'T',
|
||||||
labelIdentifierStandardId: TASK_STANDARD_FIELD_IDS.title,
|
labelIdentifierStandardId: TASK_STANDARD_FIELD_IDS.title,
|
||||||
})
|
})
|
||||||
export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
|
export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const WorkflowStatusOptions = [
|
|||||||
labelPlural: 'Workflows',
|
labelPlural: 'Workflows',
|
||||||
description: 'A workflow',
|
description: 'A workflow',
|
||||||
icon: 'IconSettingsAutomation',
|
icon: 'IconSettingsAutomation',
|
||||||
|
shortcut: 'W',
|
||||||
labelIdentifierStandardId: WORKFLOW_STANDARD_FIELD_IDS.name,
|
labelIdentifierStandardId: WORKFLOW_STANDARD_FIELD_IDS.name,
|
||||||
})
|
})
|
||||||
@WorkspaceGate({
|
@WorkspaceGate({
|
||||||
|
|||||||
Reference in New Issue
Block a user