Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion projects/yeoman-ui/packages/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "yeoman-ui",
"displayName": "Application Wizard",
"version": "1.27.1",
"version": "1.27.2",
"private": true,
"description": "Provides rich user experience for Yeoman generators.",
"categories": [
Expand Down
4 changes: 0 additions & 4 deletions projects/yeoman-ui/packages/backend/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ export default {
`The '${artifactName}' project has been generated.`,
show_progress_button: "Open Output View",
show_progress_message: "Generating...",
progress_preparing: "Preparing...",
progress_writing_files: "Creating project files...",
progress_installing: "Installing dependencies...",
progress_finalising: "Finalising...",
add_to_workspace: "Open the project in a multi-root workspace",
open_in_a_new_workspace: "Open the project in a stand-alone",
create_and_close: "Create the project and close it for future use",
Expand Down
168 changes: 23 additions & 145 deletions projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { getFileSchemeWorkspaceFolders } from "./utils/workspaceFolders.js";

const { isEmpty, isNil, set } = lodash;

// App Wizard wrapper that delegates to VSCodeYouiEvents
class YoUiAppWizard extends AppWizard {
constructor(private readonly events: VSCodeYouiEvents) {
super();
Expand Down Expand Up @@ -61,13 +60,7 @@ export class VSCodeYouiEvents implements YouiEvents {
private readonly rpc: IRpc;
private webviewPanel: WebviewPanel;
private readonly messages: any;
private resolveFunc: (() => void) | undefined;
private progressReporter: {
report(value: { message?: string; increment?: number }): void;
} | null = null;
private currentProjectName: string | undefined;
private phaseStartTime: number = 0;
private currentPhase: "writing" | "install" | "end" | null = null;
private resolveFunc: any;
public output: GeneratorOutput;
private readonly logger: IChildLogger;
private readonly appWizard: AppWizard;
Expand Down Expand Up @@ -95,103 +88,27 @@ export class VSCodeYouiEvents implements YouiEvents {
void this.rpc.invoke("setBanner", [bannerProps]);
}

public async doGeneratorDone(
public doGeneratorDone(
success: boolean,
message: string,
selectedWorkspace: string,
type: string,
targetFolderPath?: string
): Promise<any> {
// Show "Finalising..." before closing
if (this.progressReporter) {
this.progressReporter.report({
message: this.messages.progress_finalising,
});
// Add a brief delay so "Finalising..." is visible to users
await new Promise((resolve) => setTimeout(resolve, 1000));
}

this.resolveInstallingProgress();
): void {
set(this.webviewPanel, Constants.GENERATOR_COMPLETED, success);
this.doClose();
return this.showDoneMessage(
void this.showDoneMessage(
success,
message,
selectedWorkspace,
type,
targetFolderPath,
true // Skip resolving progress since we already did it
targetFolderPath
);
}

public doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end",
showProgress: boolean = false
): void {
// Check VS Code setting (default: true)
// Note: This setting is not declared in yeoman-ui's package.json - consumers
// (e.g., application-modeler, app-generator) declare it in their package.json
// if they want users to control it. The true default is safe - actual gating
// happens via the per-generator showProgress option passed by consumers.
const config = vscode.workspace.getConfiguration();
const settingEnabled = config.get<boolean>(
"ApplicationWizard.showGeneratorProgress",
true
);

// Only show if both the setting is enabled AND the generator opts in
if (!settingEnabled || !showProgress) {
return; // Don't show progress notification if disabled or not opted in
}

// Map phases to localized messages
const phaseMessages = {
writing: this.messages.progress_writing_files,
install: this.messages.progress_installing,
end: this.messages.progress_finalising,
};

// Minimum duration for each phase (milliseconds)
const MIN_DURATIONS = {
writing: 2000, // 2 seconds
install: 0, // No minimum - let npm install take as long as it needs
end: 1000, // 1 second
};

const message = phaseMessages[phase];

// If this is the first phase (writing) AND no progress notification exists yet
if (phase === "writing" && !this.progressReporter) {
// Close the webview panel (showing the question form) before showing progress
this.doClose();
this.currentPhase = phase;
this.phaseStartTime = Date.now();
this.showInstallMessage(projectName, message);
} else if (this.progressReporter) {
// Calculate time elapsed in current phase
const elapsed = Date.now() - this.phaseStartTime;
const minDuration = this.currentPhase
? MIN_DURATIONS[this.currentPhase]
: 0;
const remainingTime = Math.max(0, minDuration - elapsed);

if (remainingTime > 0) {
// Wait for minimum duration before showing next phase
setTimeout(() => {
if (this.progressReporter) {
this.progressReporter.report({ message });
this.currentPhase = phase;
this.phaseStartTime = Date.now();
}
}, remainingTime);
} else {
// Minimum duration already elapsed, update immediately
this.progressReporter.report({ message });
this.currentPhase = phase;
this.phaseStartTime = Date.now();
}
}
public doGeneratorInstall(): void {
this.doClose();
this.showInstallMessage();
}

public getAppWizard(): AppWizard {
Expand Down Expand Up @@ -265,36 +182,16 @@ export class VSCodeYouiEvents implements YouiEvents {
}
}

private showInstallMessage(
projectName?: string,
initialMessage: string = this.messages.progress_preparing
): void {
// Store project name for later use in success message
this.currentProjectName = projectName;

// Use "Generating {projectName}" as the title
const title = projectName
? `Generating ${projectName}`
: "Application Generator";

private showInstallMessage(): void {
void vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: title,
cancellable: false,
title: "Installing dependencies...",
},
async (progress) => {
// Store the progress reporter so we can update it
this.progressReporter = progress;
progress.report({ message: initialMessage });

// Keep the notification open until generation completes
await new Promise<void>((resolve) => {
async () => {
await new Promise((resolve) => {
this.resolveFunc = resolve;
});

// Clean up the progress reporter
this.progressReporter = null;
}
);
}
Expand All @@ -310,12 +207,9 @@ export class VSCodeYouiEvents implements YouiEvents {
errorMmessage: string,
selectedWorkspace: string,
type: string,
targetFolderPath?: string,
skipResolve: boolean = false
targetFolderPath?: string
): Thenable<any> {
if (!skipResolve) {
this.resolveInstallingProgress();
}
this.resolveInstallingProgress();

if (success) {
if (!isNil(targetFolderPath)) {
Expand Down Expand Up @@ -425,33 +319,17 @@ export class VSCodeYouiEvents implements YouiEvents {
selectedWorkspace: string,
type: string
): string {
// Default message with project name if available
let successInfoMessage: string = this.currentProjectName
? `Project ${this.currentProjectName} has been generated.`
: this.messages.artifact_generated_files;

let successInfoMessage: string = this.messages.artifact_generated_files;
if (type === "project") {
// For project type, use project name and add workspace-specific detail
if (this.currentProjectName) {
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage = `Project ${this.currentProjectName} has been generated. The project will be opened in a new workspace.`;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage = `Project ${this.currentProjectName} has been generated. The project has been added to workspace.`;
} else {
successInfoMessage = `Project ${this.currentProjectName} has been generated.`;
}
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_open_in_a_new_workspace;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_add_to_workspace;
} else {
// Fallback to original messages if no project name
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_open_in_a_new_workspace;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_add_to_workspace;
} else {
successInfoMessage =
this.messages.artifact_generated_project_saved_for_future;
}
successInfoMessage =
this.messages.artifact_generated_project_saved_for_future;
}
} else if (type === "module") {
successInfoMessage = this.messages.artifact_generated_module;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,18 @@ export class ServerYouiEvents implements YouiEvents {
selectedWorkspace: string,
type: string,
targetPath = ""
): Promise<void> {
return this.rpc.invoke("generatorDone", [
): void {
void this.rpc.invoke("generatorDone", [
suceeded,
message,
selectedWorkspace,
type,
targetPath,
]) as Promise<void>;
]);
}

public doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end",
showProgress: boolean = false
): void {
// Only invoke if generator opts in (WebSocket doesn't have VS Code settings)
if (!showProgress) {
return;
}
// WebSocket implementation - invoke RPC method with progress info
void this.rpc.invoke("generatorProgress", [projectName, phase]);
public doGeneratorInstall(): void {
void this.rpc.invoke("generatorInstall");
}

public showProgress(): void {
Expand Down
47 changes: 3 additions & 44 deletions projects/yeoman-ui/packages/backend/src/yeomanui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ export class YeomanUI {
);
AnalyticsWrapper.updateGeneratorEnded(generatorName);
// when targetFolderPath is undefined and no files are generated, send type = '' to get the empty toast message
void this.youiEvents.doGeneratorDone(
this.youiEvents.doGeneratorDone(
true,
message,
selectedWorkspace,
Expand All @@ -567,7 +567,7 @@ export class YeomanUI {
const messagePrefix = `${generatorName} generator failed`;
const errorMsg = error?.message || error;
this.logError(error, messagePrefix);
void this.youiEvents.doGeneratorDone(
this.youiEvents.doGeneratorDone(
false,
`${messagePrefix} - ${errorMsg}`,
"",
Expand All @@ -579,49 +579,8 @@ export class YeomanUI {
}

private onGenInstall(gen: any) {
// Extract project name
const getProjectName = () => {
return (
_.get(gen, "state.project.name") ||
_.get(gen, "options.projectName") ||
_.get(gen, "answers.projectName") ||
_.get(gen, "answers.app.name") ||
_.get(gen, "props.projectName") ||
_.get(gen, "props.app.name")
);
};

// Check if generator opts in to progress notifications
const showProgress = _.get(gen, "options.showGeneratorProgress", false);

// Listen to writing phase
gen.on("method:writing", () => {
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(
projectName,
"writing",
showProgress
);
});

// Listen to install phase
gen.on("method:install", () => {
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(
projectName,
"install",
showProgress
);
});

// Listen to end phase
gen.on("method:end", () => {
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(
projectName,
"end",
showProgress
);
this.youiEvents.doGeneratorInstall();
});
}

Expand Down
2 changes: 1 addition & 1 deletion projects/yeoman-ui/packages/backend/src/youi-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class YouiAdapter {
try {
return await cb(result); // eslint-disable-line @typescript-eslint/await-thenable
} catch (err) {
void this.youiEvents.doGeneratorDone(
this.youiEvents.doGeneratorDone(
false,
get(err, "message", "Template Wizard detected an error"),
"",
Expand Down
6 changes: 1 addition & 5 deletions projects/yeoman-ui/packages/backend/src/youi-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ export interface YouiEvents {
selectedWorkspace: string,
type: string,
targetFolderPath?: string
): Thenable<any>;
doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end",
showProgress?: boolean
): void;
doGeneratorInstall(): void;
showProgress(message?: string): void;
getAppWizard(): AppWizard;
executeCommand(id: string, ...args: any[]): Thenable<any>;
Expand Down
Loading
Loading