Skip to content

Commit 4c5e48d

Browse files
committed
move NestedWizard logic to Wizzard class
1 parent 4d8ff98 commit 4c5e48d

File tree

5 files changed

+58
-84
lines changed

5 files changed

+58
-84
lines changed

packages/core/src/shared/ui/nestedWizardPrompter.ts

Lines changed: 0 additions & 63 deletions
This file was deleted.

packages/core/src/shared/ui/wizardPrompter.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,12 @@ import { StepEstimator, Wizard, WIZARD_BACK, WIZARD_SKIP } from '../wizards/wiza
88
import { Prompter, PromptResult } from './prompter'
99

1010
/**
11-
* Wraps {@link Wizard} object into its own {@link Prompter}, allowing wizards to use other
12-
* wizards in their flows.
13-
*
11+
* Wraps {@link Wizard} object into its own {@link Prompter}, allowing wizards to use other wizards in their flows.
12+
* This is meant to be used exclusively in createWizardPrompter() method of {@link Wizard} class.
1413
* @remarks
15-
* - This class should only be used inside wizard classes that extend {@link NestedWizard}.
16-
* - See examples:
17-
* - {@link SingleNestedWizard}
18-
* - {@link DoubleNestedWizard}
14+
* - Use createWizardPrompter() method of {@link Wizard} when creating a nested wizard prompter for proper state management.
15+
* - The WizardPrompter class should never be instantiated with directly.
1916
*/
20-
21-
// eslint-disable-next-line @typescript-eslint/naming-convention
22-
export const WIZARD_PROMPTER = 'WIZARD_PROMPTER'
23-
2417
export class WizardPrompter<T> extends Prompter<T> {
2518
public get recentItem(): any {
2619
return undefined
@@ -64,7 +57,7 @@ export class WizardPrompter<T> extends Prompter<T> {
6457

6558
if (this.response === undefined) {
6659
return WIZARD_BACK as PromptResult<T>
67-
} else if (_.isEmpty(this.response)) {
60+
} else if (JSON.stringify(this.response) === '{}') {
6861
return WIZARD_SKIP as PromptResult<T>
6962
}
7063

packages/core/src/shared/wizards/stateController.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export enum ControlSignal {
1616
* Value for indicating current direction of the wizard
1717
* Created mainly to support skipping prompters
1818
*/
19-
export enum DIRECTION {
19+
export enum Direction {
2020
Forward,
2121
Backward,
2222
}
@@ -48,11 +48,11 @@ export class StateMachineController<TState> {
4848
private extraSteps = new Map<number, Branch<TState>>()
4949
private steps: Branch<TState> = []
5050
private internalStep: number = 0
51-
private direction: DIRECTION
51+
private direction: Direction
5252

5353
public constructor(private state: TState = {} as TState) {
5454
this.previousStates = [_.cloneDeep(state)]
55-
this.direction = DIRECTION.Forward
55+
this.direction = Direction.Forward
5656
}
5757

5858
public addStep(step: StepFunction<TState>): void {
@@ -82,14 +82,14 @@ export class StateMachineController<TState> {
8282

8383
this.state = this.previousStates.pop()!
8484
this.internalStep -= 1
85-
this.direction = DIRECTION.Backward
85+
this.direction = Direction.Backward
8686
}
8787

8888
protected advanceState(nextState: TState): void {
8989
this.previousStates.push(this.state)
9090
this.state = nextState
9191
this.internalStep += 1
92-
this.direction = DIRECTION.Forward
92+
this.direction = Direction.Forward
9393
}
9494

9595
protected detectCycle(step: StepFunction<TState>): TState | undefined {
@@ -122,7 +122,7 @@ export class StateMachineController<TState> {
122122
/**
123123
* Depending on current wizard direction, skip signal get converted to forward or backward control signal
124124
*/
125-
result.controlSignal = this.direction === DIRECTION.Forward ? undefined : ControlSignal.Back
125+
result.controlSignal = this.direction === Direction.Forward ? undefined : ControlSignal.Back
126126
}
127127
return result
128128
} else {

packages/core/src/shared/wizards/wizard.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Branch, ControlSignal, StateMachineController, StepFunction } from './s
77
import * as _ from 'lodash'
88
import { Prompter, PromptResult } from '../../shared/ui/prompter'
99
import { PrompterProvider, WizardForm } from './wizardForm'
10+
import { createHash } from 'crypto'
11+
import { WizardPrompter } from '../ui/wizardPrompter'
1012

1113
/** Checks if the user response is valid (i.e. not undefined and not a control signal) */
1214
export function isValidResponse<T>(response: PromptResult<T>): response is T {
@@ -89,6 +91,10 @@ export class Wizard<TState extends Partial<Record<keyof TState, unknown>>> {
8991
private _exitStep?: StepFunction<TState>
9092
/** Guards against accidental use of the Wizard before `init()`. */
9193
private _ready: boolean
94+
/**
95+
* Map to store memoized wizard instances using SHA-256 hashed keys
96+
*/
97+
private childWizards: Map<string, any> = new Map()
9298

9399
/** Checks that `init()` was performed (if it was defined). */
94100
private assertReady() {
@@ -99,6 +105,45 @@ export class Wizard<TState extends Partial<Record<keyof TState, unknown>>> {
99105
}
100106
}
101107

108+
/**
109+
* Creates a prompter for a wizard instance with memoization.
110+
*
111+
* @template TWizard - The type of wizard, must extend Wizard<TState>
112+
* @template TState - The type of state managed by the wizard
113+
*
114+
* @param wizardClass - The wizard class constructor
115+
* @param args - Constructor arguments for the wizard instance
116+
*
117+
* @returns A wizard prompter to be used as prompter
118+
*
119+
* @example
120+
* // Create a prompter for SyncWizard
121+
* const prompter = this.createWizardPrompter<SyncWizard, SyncParams>(
122+
* SyncWizard,
123+
* template.uri,
124+
* syncUrl
125+
* )
126+
*
127+
* @remarks
128+
* - Instances are memoized using a SHA-256 hash of the wizard class name and arguments
129+
* - The same wizard instance is reused for identical constructor parameters for restoring wizard prompter
130+
* states during back button click event
131+
*/
132+
protected createWizardPrompter<TWizard extends Wizard<TState>, TState>(
133+
wizardClass: new (...args: any[]) => TWizard,
134+
...args: ConstructorParameters<new (...args: any[]) => TWizard>
135+
): Prompter<TState> {
136+
const memoizeKey = createHash('sha256')
137+
.update(wizardClass.name + JSON.stringify(args))
138+
.digest('hex')
139+
140+
if (!this.childWizards.get(memoizeKey)) {
141+
this.childWizards.set(memoizeKey, new wizardClass(...args))
142+
}
143+
144+
return new WizardPrompter(this.childWizards.get(memoizeKey)) as Prompter<TState>
145+
}
146+
102147
/**
103148
* The offset is applied to both the current step and total number of steps. Useful if the wizard is
104149
* apart of some overarching flow.

packages/core/src/test/shared/wizards/nestedWizard.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
import * as vscode from 'vscode'
66
import { createCommonButtons } from '../../../shared/ui/buttons'
7-
import { NestedWizard } from '../../../shared/ui/nestedWizardPrompter'
87
import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter'
98
import { Wizard } from '../../../shared/wizards/wizard'
109
import * as assert from 'assert'
@@ -56,7 +55,7 @@ class ChildWizard extends Wizard<ChildWizardForm> {
5655
}
5756
}
5857

59-
class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
58+
class SingleNestedWizard extends Wizard<SingleNestedWizardForm> {
6059
constructor() {
6160
super()
6261

@@ -75,7 +74,7 @@ class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
7574
}
7675
}
7776

78-
class DoubleNestedWizard extends NestedWizard<DoubleNestedWizardForm> {
77+
class DoubleNestedWizard extends Wizard<DoubleNestedWizardForm> {
7978
constructor() {
8079
super()
8180

0 commit comments

Comments
 (0)