Skip to content

Commit 77a2505

Browse files
committed
feat: WIP refactored default renderer
1 parent 539cecc commit 77a2505

10 files changed

Lines changed: 448 additions & 69 deletions

File tree

codify.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"homebrew/services"
1414
],
1515
"formulae": [
16-
"asciinema"
16+
"asciinema",
17+
"abduco"
1718
],
1819
"casks": [
1920
"firefox"

src/ui/components/default-component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function DefaultComponent(props: {
9797
return <Box flexDirection="column">
9898
{
9999
([RenderState.APPLY_COMPLETE, RenderState.APPLYING, RenderState.GENERATING_PLAN].includes(state)) && progressState && !hideProgress && (
100-
<ProgressDisplay emitter={spinnerEmitter} eventType="data" progress={progressState}/>
100+
<ProgressDisplay />
101101
)
102102
}
103103
{

src/ui/components/plan/plan.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import { ResourceText } from './resource-text.js';
88
export function PlanComponent(props: {
99
plan: Plan,
1010
}) {
11-
const filteredPlan = props.plan.filterNoopResources();
11+
const { plan } = props;
1212

1313
return <Box flexDirection="column">
1414
<Box borderColor="green" borderStyle="round">
1515
<Text>Codify Plan</Text>
1616
</Box>
17-
<Text>Path: {props.plan.project.path}</Text>
17+
<Text>Path: {plan.project.path}</Text>
1818
<Text>The following actions will be performed: </Text>
1919
<Text> </Text>
2020
<Box flexDirection="column" marginLeft={1}>{
21-
filteredPlan.resources.map((p, idx) =>
21+
plan.resources.map((p, idx) =>
2222
<Box flexDirection="column" key={idx} marginBottom={1}>
2323
<ResourceText plan={p}/>
2424
<Text>{prettyFormatResourcePlan(p)}</Text>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { StatusMessage } from '@inkjs/ui';
2+
import { Box } from 'ink';
3+
import EventEmitter from 'node:events';
4+
import React, { useLayoutEffect, useState } from 'react';
5+
6+
import Spinner from './spinner.js';
7+
import { ctx, Event, ProcessName, SubProcessName } from '../../../events/context.js';
8+
import chalk from 'chalk';
9+
import { createUseStore } from '../../hooks/store.js';
10+
11+
const ProgressLabelMapping = {
12+
[ProcessName.APPLY]: 'Codify apply',
13+
[ProcessName.PLAN]: 'Codify plan',
14+
[ProcessName.DESTROY]: 'Codify destroy',
15+
[ProcessName.IMPORT]: 'Codify import',
16+
[SubProcessName.APPLYING_RESOURCE]: 'Applying resource',
17+
[SubProcessName.GENERATE_PLAN]: 'Refresh states and generating plan',
18+
[SubProcessName.INITIALIZE_PLUGINS]: 'Initializing plugins',
19+
[SubProcessName.PARSE]: 'Parsing configs',
20+
[SubProcessName.VALIDATE]: 'Validating configs',
21+
[SubProcessName.GET_REQUIRED_PARAMETERS]: 'Getting required parameters',
22+
[SubProcessName.IMPORT_RESOURCE]: 'Importing resource'
23+
}
24+
25+
export enum ProgressStatus {
26+
IN_PROGRESS,
27+
FINISHED,
28+
}
29+
30+
export interface ProgressState {
31+
name: string,
32+
label: string;
33+
status: ProgressStatus;
34+
subProgresses: Array<{
35+
name: string,
36+
label: string;
37+
status: ProgressStatus;
38+
}> | null;
39+
}
40+
41+
/**
42+
* This component directly subscribes to the global event bus to listen for progress updates. That is why there are no
43+
* props.
44+
* Note: New process_start events will completely wipe out the old ones.
45+
*/
46+
export function ProgressDisplay() {
47+
const [progressState, setProgressState] = useState({} as ProgressState | undefined);
48+
49+
useLayoutEffect(() => {
50+
ctx.on(Event.PROCESS_START, (name) => onProcessStartEvent(name))
51+
ctx.on(Event.PROCESS_FINISH, (name) => onProcessFinishEvent(name))
52+
ctx.on(Event.SUB_PROCESS_START, (name, additionalName) => onSubprocessStartEvent(name, additionalName));
53+
ctx.on(Event.SUB_PROCESS_FINISH, (name, additionalName) => onSubprocessFinishEvent(name, additionalName))
54+
}, []);
55+
56+
const onProcessStartEvent = (name: ProcessName) => {
57+
const label = ProgressLabelMapping[name];
58+
59+
log(`${label} started`)
60+
setProgressState({
61+
label: label + '...',
62+
name,
63+
status: ProgressStatus.IN_PROGRESS,
64+
subProgresses: [],
65+
});
66+
}
67+
68+
const onProcessFinishEvent = (name: ProcessName) => {
69+
const label = ProgressLabelMapping[name];
70+
71+
log(`${label} finished successfully`)
72+
setProgressState((state) => {
73+
state!.status = ProgressStatus.FINISHED;
74+
return structuredClone(state);
75+
})
76+
}
77+
78+
const onSubprocessStartEvent = (name: SubProcessName, additionalName?: string) => {
79+
const label = ProgressLabelMapping[name] + (additionalName
80+
? ' ' + additionalName
81+
: ''
82+
);
83+
84+
log(`${label} started`)
85+
86+
setProgressState((state) => {
87+
state?.subProgresses?.push({
88+
label,
89+
name: name + additionalName,
90+
status: ProgressStatus.IN_PROGRESS,
91+
});
92+
93+
return structuredClone(state);
94+
})
95+
}
96+
97+
const onSubprocessFinishEvent = (name: SubProcessName, additionalName?: string) => {
98+
const label = ProgressLabelMapping[name] + (additionalName
99+
? ' ' + additionalName
100+
: ''
101+
);
102+
103+
log(`${label} finished successfully`)
104+
setProgressState((state) => {
105+
const subProgress = state
106+
?.subProgresses
107+
?.find((p) => p.name === name + additionalName);
108+
109+
if (!subProgress) {
110+
return state;
111+
}
112+
113+
subProgress.status = ProgressStatus.FINISHED;
114+
115+
return structuredClone(state);
116+
})
117+
}
118+
119+
const log = (log: unknown) => {
120+
console.log(chalk.cyan(log));
121+
}
122+
123+
if (!progressState) {
124+
return <Box></Box>
125+
}
126+
127+
const { label, status, subProgresses } = progressState;
128+
return <Box flexDirection="column">
129+
{
130+
status === ProgressStatus.IN_PROGRESS
131+
? <Spinner type="circleHalves" eventEmitter={ctx.emitter} eventType={Event.OUTPUT} label={label}/>
132+
: <StatusMessage variant="success">{label}</StatusMessage>
133+
}
134+
<Box flexDirection="column" marginLeft={2}>
135+
<SubProgressDisplay subProgresses={subProgresses} />
136+
</Box>
137+
</Box>
138+
}
139+
140+
export function SubProgressDisplay(
141+
props: { subProgresses: ProgressState['subProgresses'] }
142+
) {
143+
const { subProgresses } = props;
144+
145+
return <>{
146+
subProgresses && subProgresses.map((s, idx) =>
147+
s.status === ProgressStatus.IN_PROGRESS
148+
? <Spinner eventEmitter={ctx.emitter} eventType={Event.OUTPUT} key={idx} label={s.label} type="circleHalves"/>
149+
: <StatusMessage key={idx} variant="success">{s.label}</StatusMessage>
150+
)
151+
}</>
152+
}

src/ui/components/progress/progress-display.tsx

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Box, Text } from 'ink';
2+
import React from 'react';
3+
4+
export function CompletionSection() {
5+
return (
6+
<Box flexDirection="column">
7+
<Text> </Text>
8+
<Text>🎉 Finished applying 🎉</Text>
9+
<Text>Open a new terminal or source '.zshrc' for the new changes to be reflected</Text>
10+
</Box>
11+
);
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Box } from 'ink';
2+
import { RenderEvent } from '../../reporters/reporter.js';
3+
import React from 'react';
4+
import { PasswordInput } from '@inkjs/ui';
5+
6+
export function SudoSection() {
7+
return <Box flexDirection="column">
8+
<Text>Password:</Text>
9+
{/* Use sudoAttemptCount as a hack to reset password input between attempts */}
10+
<PasswordInput key={sudoAttemptCount} onSubmit={(password) => {
11+
emitter.emit(RenderEvent.PROMPT_SUDO_RESULT, password);
12+
}}/>
13+
</Box>
14+
15+
}

0 commit comments

Comments
 (0)