Seed Front Components (#19220)
https://github.com/user-attachments/assets/6b0887e8-8ba4-49c9-9371-e3db3e9fdde8
This commit is contained in:
committed by
GitHub
parent
268543a08e
commit
885ab8b444
+1
-1
@@ -33,7 +33,7 @@ export const fromCreateFrontComponentInputToFlatFrontComponentToCreate = ({
|
||||
builtComponentPath: createFrontComponentInput.builtComponentPath,
|
||||
componentName: createFrontComponentInput.componentName,
|
||||
builtComponentChecksum: createFrontComponentInput.builtComponentChecksum,
|
||||
isHeadless: false,
|
||||
isHeadless: createFrontComponentInput.isHeadless ?? false,
|
||||
usesSdkClient: false,
|
||||
workspaceId,
|
||||
createdAt: now,
|
||||
|
||||
+107
File diff suppressed because one or more lines are too long
+120
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
useFrontComponentExecutionContext,
|
||||
useUserId,
|
||||
} from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const HelloWorld = () => {
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
const userId = useUserId();
|
||||
const fullContext = useFrontComponentExecutionContext((context) => context);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 16,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
background: '#f0f9ff',
|
||||
borderRadius: 12,
|
||||
border: '2px solid #38bdf8',
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
color: '#0369a1',
|
||||
fontWeight: 700,
|
||||
fontSize: 18,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Hello World
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: '#64748b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
}}
|
||||
>
|
||||
User ID
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
color: '#0c4a6e',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{userId ?? 'null'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: '#64748b',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
}}
|
||||
>
|
||||
Execution Context
|
||||
</p>
|
||||
<pre
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: '#0c4a6e',
|
||||
background: '#e0f2fe',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
overflow: 'auto',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(fullContext, null, 2) ?? 'undefined'}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<button
|
||||
onClick={() => setRenderCount((c) => c + 1)}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#0284c7',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Re-render
|
||||
</button>
|
||||
<span style={{ fontSize: 14, color: '#64748b' }}>
|
||||
Render count: {renderCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'seed-front-component-hello-world',
|
||||
name: 'Hello World',
|
||||
description:
|
||||
'A sample visual front component that displays execution context',
|
||||
component: HelloWorld,
|
||||
});
|
||||
+107
File diff suppressed because one or more lines are too long
+32
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
enqueueSnackbar,
|
||||
unmountFrontComponent,
|
||||
} from 'twenty-sdk';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const ShowNotification = () => {
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
await enqueueSnackbar({
|
||||
message: 'Hello from a headless front component!',
|
||||
variant: 'success',
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
await unmountFrontComponent();
|
||||
};
|
||||
|
||||
run();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'seed-front-component-show-notification',
|
||||
name: 'Show Notification',
|
||||
description: 'A sample headless front component that displays a notification',
|
||||
isHeadless: true,
|
||||
component: ShowNotification,
|
||||
});
|
||||
+12
-1
@@ -1,6 +1,12 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@@ -16,6 +22,11 @@ export class CreateFrontComponentInput {
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@HideField()
|
||||
isHeadless?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field()
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
|
||||
export type FrontComponentSeedProjectFile = {
|
||||
name: string;
|
||||
path: string;
|
||||
content: Buffer;
|
||||
};
|
||||
|
||||
const getAllFiles = async (
|
||||
rootDir: string,
|
||||
dir: string = rootDir,
|
||||
files: FrontComponentSeedProjectFile[] = [],
|
||||
): Promise<FrontComponentSeedProjectFile[]> => {
|
||||
const dirEntries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of dirEntries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await getAllFiles(rootDir, fullPath, files);
|
||||
} else {
|
||||
files.push({
|
||||
path: path.relative(rootDir, dir),
|
||||
name: entry.name,
|
||||
content: await fs.readFile(fullPath),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
export const getFrontComponentSeedProjectFiles = async (
|
||||
subdirectory: string,
|
||||
): Promise<FrontComponentSeedProjectFile[]> => {
|
||||
const seedProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
'engine/metadata-modules/front-component/constants/seed-project',
|
||||
subdirectory,
|
||||
);
|
||||
|
||||
return getAllFiles(seedProjectPath);
|
||||
};
|
||||
+19
-8
@@ -5,7 +5,7 @@ import { readFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { FeatureFlagKey, FileFolder } from 'twenty-shared/types';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
@@ -56,10 +56,6 @@ import {
|
||||
MESSAGE_CHANNEL_DATA_SEED_COLUMNS,
|
||||
MESSAGE_CHANNEL_DATA_SEEDS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant';
|
||||
import {
|
||||
MESSAGE_FOLDER_DATA_SEED_COLUMNS,
|
||||
MESSAGE_FOLDER_DATA_SEEDS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant';
|
||||
import {
|
||||
MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_COLUMNS,
|
||||
MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEEDS,
|
||||
@@ -68,6 +64,10 @@ import {
|
||||
MESSAGE_DATA_SEED_COLUMNS,
|
||||
MESSAGE_DATA_SEEDS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant';
|
||||
import {
|
||||
MESSAGE_FOLDER_DATA_SEED_COLUMNS,
|
||||
MESSAGE_FOLDER_DATA_SEEDS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant';
|
||||
import {
|
||||
getMessageParticipantDataSeeds,
|
||||
MESSAGE_PARTICIPANT_DATA_SEED_COLUMNS,
|
||||
@@ -121,10 +121,13 @@ import {
|
||||
WORKSPACE_MEMBER_DATA_SEED_COLUMNS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
|
||||
import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service';
|
||||
import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util';
|
||||
import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util';
|
||||
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util';
|
||||
import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service';
|
||||
import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service';
|
||||
import { prefillFrontComponentCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-command-menu-items.util';
|
||||
import { getSeedFrontComponentDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
|
||||
import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util';
|
||||
import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util';
|
||||
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
type RecordSeedConfig = {
|
||||
@@ -307,6 +310,7 @@ export class DevSeederDataService {
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly prefillLogicFunctionService: PrefillLogicFunctionService,
|
||||
private readonly prefillFrontComponentService: PrefillFrontComponentService,
|
||||
) {}
|
||||
|
||||
public async seed({
|
||||
@@ -342,6 +346,11 @@ export class DevSeederDataService {
|
||||
),
|
||||
});
|
||||
|
||||
await this.prefillFrontComponentService.ensureSeeded({
|
||||
workspaceId,
|
||||
definitions: getSeedFrontComponentDefinitions(workspaceId),
|
||||
});
|
||||
|
||||
await this.coreDataSource.transaction(
|
||||
async (entityManager: WorkspaceEntityManager) => {
|
||||
await this.seedRecordsInBatches({
|
||||
@@ -377,6 +386,8 @@ export class DevSeederDataService {
|
||||
);
|
||||
|
||||
await prefillWorkflowCommandMenuItems(entityManager, workspaceId);
|
||||
|
||||
await prefillFrontComponentCommandMenuItems(entityManager, workspaceId);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
|
||||
import {
|
||||
type FrontComponentSeedProjectFile,
|
||||
getFrontComponentSeedProjectFiles,
|
||||
} from 'src/engine/metadata-modules/front-component/utils/get-front-component-seed-project-files.util';
|
||||
import { type SeedFrontComponentDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
|
||||
|
||||
@Injectable()
|
||||
export class PrefillFrontComponentService {
|
||||
constructor(
|
||||
private readonly frontComponentService: FrontComponentService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
async ensureSeeded({
|
||||
workspaceId,
|
||||
definitions,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
definitions: SeedFrontComponentDefinition[];
|
||||
}) {
|
||||
const { flatFrontComponentMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatFrontComponentMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const { workspaceCustomFlatApplication: ownerFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
for (const definition of definitions) {
|
||||
const existingFrontComponent = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: definition.id,
|
||||
flatEntityMaps: flatFrontComponentMaps,
|
||||
});
|
||||
|
||||
if (isDefined(existingFrontComponent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const seedFiles = await getFrontComponentSeedProjectFiles(
|
||||
definition.seedProjectSubdir,
|
||||
);
|
||||
|
||||
const sourceFile = seedFiles.find((file: FrontComponentSeedProjectFile) =>
|
||||
file.name.endsWith('index.tsx'),
|
||||
);
|
||||
|
||||
const builtFile = seedFiles.find((file: FrontComponentSeedProjectFile) =>
|
||||
file.name.endsWith('index.mjs'),
|
||||
);
|
||||
|
||||
if (!isDefined(sourceFile) || !isDefined(builtFile)) {
|
||||
throw new Error(
|
||||
`Seed project for front component "${definition.name}" must have an index.tsx and an index.mjs file`,
|
||||
);
|
||||
}
|
||||
|
||||
const sourceComponentPath = `${definition.id}/index.tsx`;
|
||||
const builtComponentPath = `${definition.id}/index.mjs`;
|
||||
|
||||
await this.fileStorageService.writeFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: sourceComponentPath,
|
||||
sourceFile: sourceFile.content,
|
||||
mimeType: 'application/typescript',
|
||||
settings: {
|
||||
isTemporaryFile: false,
|
||||
toDelete: false,
|
||||
},
|
||||
});
|
||||
|
||||
await this.fileStorageService.writeFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
fileFolder: FileFolder.BuiltFrontComponent,
|
||||
resourcePath: builtComponentPath,
|
||||
sourceFile: builtFile.content,
|
||||
mimeType: 'application/javascript',
|
||||
settings: {
|
||||
isTemporaryFile: false,
|
||||
toDelete: false,
|
||||
},
|
||||
});
|
||||
|
||||
const checksum = crypto
|
||||
.createHash('md5')
|
||||
.update(builtFile.content)
|
||||
.digest('hex');
|
||||
|
||||
await this.frontComponentService.createOne({
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
input: {
|
||||
id: definition.id,
|
||||
universalIdentifier: definition.universalIdentifier,
|
||||
name: definition.name,
|
||||
description: definition.description,
|
||||
componentName: definition.componentName,
|
||||
sourceComponentPath,
|
||||
builtComponentPath,
|
||||
builtComponentChecksum: checksum,
|
||||
isHeadless: definition.isHeadless,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -1,12 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module';
|
||||
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
|
||||
import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service';
|
||||
import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service';
|
||||
|
||||
@Module({
|
||||
imports: [LogicFunctionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
|
||||
providers: [PrefillLogicFunctionService],
|
||||
exports: [PrefillLogicFunctionService],
|
||||
imports: [
|
||||
LogicFunctionModule,
|
||||
FrontComponentModule,
|
||||
FileStorageModule,
|
||||
ApplicationModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [PrefillLogicFunctionService, PrefillFrontComponentService],
|
||||
exports: [PrefillLogicFunctionService, PrefillFrontComponentService],
|
||||
})
|
||||
export class StandardObjectsPrefillModule {}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { type EntityManager } from 'typeorm';
|
||||
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
|
||||
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
|
||||
import {
|
||||
getSeedFrontComponentCommandMenuItemDefinitions,
|
||||
getSeedFrontComponentIds,
|
||||
} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
|
||||
|
||||
export const prefillFrontComponentCommandMenuItems = async (
|
||||
entityManager: EntityManager,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
const { helloWorldId } = getSeedFrontComponentIds(workspaceId);
|
||||
|
||||
const frontComponentRow = await entityManager
|
||||
.createQueryBuilder()
|
||||
.select('fc.applicationId', 'applicationId')
|
||||
.from('core.frontComponent', 'fc')
|
||||
.where('fc.id = :id', { id: helloWorldId })
|
||||
.andWhere('fc.workspaceId = :workspaceId', { workspaceId })
|
||||
.getRawOne();
|
||||
|
||||
if (!frontComponentRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const definitions =
|
||||
getSeedFrontComponentCommandMenuItemDefinitions(workspaceId);
|
||||
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into('core.commandMenuItem', [
|
||||
'workspaceId',
|
||||
'universalIdentifier',
|
||||
'applicationId',
|
||||
'workflowVersionId',
|
||||
'frontComponentId',
|
||||
'engineComponentKey',
|
||||
'label',
|
||||
'icon',
|
||||
'shortLabel',
|
||||
'position',
|
||||
'isPinned',
|
||||
'availabilityType',
|
||||
'conditionalAvailabilityExpression',
|
||||
'availabilityObjectMetadataId',
|
||||
'hotKeys',
|
||||
])
|
||||
.values(
|
||||
definitions.map((definition) => ({
|
||||
workspaceId,
|
||||
universalIdentifier: definition.universalIdentifier,
|
||||
applicationId: frontComponentRow.applicationId,
|
||||
workflowVersionId: null,
|
||||
frontComponentId: definition.frontComponentId,
|
||||
engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER,
|
||||
label: definition.label,
|
||||
icon: definition.icon,
|
||||
shortLabel: definition.label,
|
||||
position: definition.position,
|
||||
isPinned: false,
|
||||
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
|
||||
conditionalAvailabilityExpression: null,
|
||||
availabilityObjectMetadataId: null,
|
||||
hotKeys: null,
|
||||
})),
|
||||
)
|
||||
.orIgnore()
|
||||
.execute();
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { v5 as uuidv5 } from 'uuid';
|
||||
|
||||
const SEED_FRONT_COMPONENT_ID_NAMESPACE =
|
||||
'e7a3b1c4-f5d6-4e8a-9b2c-3d4e5f6a7b8c';
|
||||
|
||||
export type SeedFrontComponentDefinition = {
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
description: string;
|
||||
componentName: string;
|
||||
isHeadless: boolean;
|
||||
usesSdkClient: boolean;
|
||||
seedProjectSubdir: string;
|
||||
};
|
||||
|
||||
export type SeedFrontComponentCommandMenuItemDefinition = {
|
||||
universalIdentifier: string;
|
||||
frontComponentId: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
export const getSeedFrontComponentIds = (workspaceId: string) => ({
|
||||
helloWorldId: uuidv5(
|
||||
`${workspaceId}:seed-front-component:hello-world`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
showNotificationId: uuidv5(
|
||||
`${workspaceId}:seed-front-component:show-notification`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
});
|
||||
|
||||
export const getSeedFrontComponentDefinitions = (
|
||||
workspaceId: string,
|
||||
): SeedFrontComponentDefinition[] => {
|
||||
const { helloWorldId, showNotificationId } =
|
||||
getSeedFrontComponentIds(workspaceId);
|
||||
|
||||
return [
|
||||
{
|
||||
id: helloWorldId,
|
||||
universalIdentifier: uuidv5(
|
||||
`${workspaceId}:seed-front-component-uid:hello-world`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
name: 'Hello World',
|
||||
description:
|
||||
'A sample visual front component that displays execution context',
|
||||
componentName: 'HelloWorld',
|
||||
isHeadless: false,
|
||||
usesSdkClient: false,
|
||||
seedProjectSubdir: 'hello-world',
|
||||
},
|
||||
{
|
||||
id: showNotificationId,
|
||||
universalIdentifier: uuidv5(
|
||||
`${workspaceId}:seed-front-component-uid:show-notification`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
name: 'Show Notification',
|
||||
description:
|
||||
'A sample headless front component that displays a notification',
|
||||
componentName: 'ShowNotification',
|
||||
isHeadless: true,
|
||||
usesSdkClient: false,
|
||||
seedProjectSubdir: 'show-notification',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getSeedFrontComponentCommandMenuItemDefinitions = (
|
||||
workspaceId: string,
|
||||
): SeedFrontComponentCommandMenuItemDefinition[] => {
|
||||
const { helloWorldId, showNotificationId } =
|
||||
getSeedFrontComponentIds(workspaceId);
|
||||
|
||||
return [
|
||||
{
|
||||
universalIdentifier: uuidv5(
|
||||
`${workspaceId}:seed-front-component-command:hello-world`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
frontComponentId: helloWorldId,
|
||||
label: 'Hello World',
|
||||
icon: 'IconAppWindow',
|
||||
position: 200,
|
||||
},
|
||||
{
|
||||
universalIdentifier: uuidv5(
|
||||
`${workspaceId}:seed-front-component-command:show-notification`,
|
||||
SEED_FRONT_COMPONENT_ID_NAMESPACE,
|
||||
),
|
||||
frontComponentId: showNotificationId,
|
||||
label: 'Show Notification',
|
||||
icon: 'IconBell',
|
||||
position: 201,
|
||||
},
|
||||
];
|
||||
};
|
||||
Reference in New Issue
Block a user