Skip to content

Commit b46ca5b

Browse files
awesome tasks
1 parent e8933a9 commit b46ca5b

File tree

1 file changed

+120
-115
lines changed

1 file changed

+120
-115
lines changed
Lines changed: 120 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
22
Emitter,
3-
IDisposable,
43
type IPitcherClient,
54
type protocol,
65
} from "@codesandbox/pitcher-client";
76

87
import { Disposable } from "../../utils/disposable";
8+
import { DEFAULT_SHELL_SIZE } from "./terminals";
99

1010
export type TaskDefinition = {
1111
name: string;
@@ -17,144 +17,149 @@ export type TaskDefinition = {
1717
};
1818
};
1919

20-
export type Task = TaskDefinition & {
21-
id: string;
22-
unconfigured?: boolean;
23-
shellId: null | string;
24-
ports: protocol.port.Port[];
25-
};
26-
27-
type TaskShellSubscription = {
28-
shellId: string;
29-
disposable: IDisposable;
30-
};
31-
3220
export class Tasks {
3321
private disposable = new Disposable();
34-
private shellOutputListeners: Record<string, TaskShellSubscription> = {};
35-
private onTaskOutputEmitter = this.disposable.addDisposable(
36-
new Emitter<{
37-
taskId: string;
38-
output: string;
39-
}>()
40-
);
41-
public readonly onTaskOutput = this.onTaskOutputEmitter.event;
42-
22+
private tasks: Task[] = [];
4323
constructor(
4424
sessionDisposable: Disposable,
4525
private pitcherClient: IPitcherClient
4626
) {
27+
this.tasks = Object.values(
28+
this.pitcherClient.clients.task.getTasks().tasks
29+
).map((task) => new Task(this.pitcherClient, task));
30+
4731
sessionDisposable.onWillDispose(() => {
4832
this.disposable.dispose();
4933
});
50-
this.disposable.addDisposable(
51-
pitcherClient.clients.task.onTaskUpdate((task) =>
52-
this.listenToShellOutput(task)
53-
)
54-
);
55-
this.disposable.onWillDispose(() => {
56-
Object.values(this.shellOutputListeners).forEach((listener) =>
57-
listener.disposable.dispose()
58-
);
59-
});
60-
}
61-
62-
private async listenToShellOutput(task: protocol.task.TaskDTO) {
63-
const existingListener = this.shellOutputListeners[task.id];
64-
65-
// Already have shell registered
66-
if (existingListener && task.shell?.shellId === existingListener.shellId) {
67-
return;
68-
}
69-
70-
// Has removed shell
71-
if (
72-
existingListener &&
73-
(!task.shell || task.shell.shellId !== existingListener.shellId)
74-
) {
75-
existingListener.disposable.dispose();
76-
}
77-
78-
// No new shell
79-
if (!task.shell) {
80-
return;
81-
}
82-
83-
// Has new shell
84-
const taskShellId = task.shell.shellId;
85-
let listener: TaskShellSubscription = {
86-
shellId: taskShellId,
87-
disposable: this.pitcherClient.clients.shell.onShellOut(
88-
({ shellId, out }) => {
89-
if (shellId === taskShellId) {
90-
this.onTaskOutputEmitter.fire({
91-
taskId: task.id,
92-
output: out,
93-
});
94-
}
95-
}
96-
),
97-
};
98-
99-
this.shellOutputListeners[task.id] = listener;
100-
101-
this.pitcherClient.clients.shell.open(task.shell.shellId, {
102-
cols: 80,
103-
rows: 24,
104-
});
105-
106-
/*
107-
this.onTaskOutputEmitter.fire({
108-
taskId: task.id,
109-
output: shell.buffer.join("\n"),
110-
});
111-
*/
11234
}
11335

11436
/**
11537
* Gets all tasks that are available in the current sandbox.
11638
*/
117-
async getTasks(): Promise<Task[]> {
118-
const tasks = await this.pitcherClient.clients.task.getTasks();
119-
120-
return Object.values(tasks.tasks).map(taskFromDTO);
39+
getTasks(): Task[] {
40+
return this.tasks;
12141
}
12242

12343
/**
12444
* Gets a task by its ID.
12545
*/
126-
async getTask(taskId: string): Promise<Task | undefined> {
127-
const task = await this.pitcherClient.clients.task.getTask(taskId);
128-
129-
if (!task) {
130-
return undefined;
131-
}
46+
getTask(taskId: string): Task | undefined {
47+
return this.tasks.find((task) => task.id === taskId);
48+
}
49+
}
13250

133-
return taskFromDTO(task);
51+
export class Task {
52+
private disposable = new Disposable();
53+
private get shell() {
54+
return this.data.shell;
55+
}
56+
private openedShell?: {
57+
shellId: string;
58+
output: string[];
59+
dimensions: typeof DEFAULT_SHELL_SIZE;
60+
};
61+
private onOutputEmitter = this.disposable.addDisposable(
62+
new Emitter<string>()
63+
);
64+
public readonly onOutput = this.onOutputEmitter.event;
65+
private onStatusChangeEmitter = this.disposable.addDisposable(
66+
new Emitter<protocol.shell.ShellProcessStatus | "IDLE">()
67+
);
68+
public readonly onStatusChange = this.onStatusChangeEmitter.event;
69+
get id() {
70+
return this.data.id;
71+
}
72+
get name() {
73+
return this.data.name;
74+
}
75+
get command() {
76+
return this.data.command;
13477
}
78+
get runAtStart() {
79+
return this.data.runAtStart;
80+
}
81+
get ports() {
82+
return this.data.ports;
83+
}
84+
get status() {
85+
return this.shell?.status || "IDLE";
86+
}
87+
constructor(
88+
private pitcherClient: IPitcherClient,
89+
private data: protocol.task.TaskDTO
90+
) {
91+
pitcherClient.clients.task.onTaskUpdate(async (task) => {
92+
if (task.id !== this.id) {
93+
return;
94+
}
95+
96+
const lastStatus = this.status;
97+
const lastShellId = this.shell?.shellId;
98+
99+
this.data = task;
100+
101+
if (lastStatus !== this.status) {
102+
this.onStatusChangeEmitter.fire(this.status);
103+
}
104+
105+
if (
106+
this.openedShell &&
107+
task.shell &&
108+
task.shell.shellId !== lastShellId
109+
) {
110+
const openedShell = await this.pitcherClient.clients.shell.open(
111+
task.shell.shellId,
112+
this.openedShell.dimensions
113+
);
114+
115+
this.openedShell = {
116+
shellId: openedShell.shellId,
117+
output: openedShell.buffer,
118+
dimensions: this.openedShell.dimensions,
119+
};
120+
121+
this.onOutputEmitter.fire("\x1B[2J\x1B[3J\x1B[1;1H");
122+
openedShell.buffer.forEach((out) => this.onOutputEmitter.fire(out));
123+
}
124+
});
135125

136-
/**
137-
* Runs a task by its ID.
138-
*/
139-
async runTask(taskId: string): Promise<Task> {
140-
const task = await this.pitcherClient.clients.task.runTask(taskId);
126+
pitcherClient.clients.shell.onShellOut(({ shellId, out }) => {
127+
if (!this.shell || this.shell.shellId !== shellId || !this.openedShell) {
128+
return;
129+
}
141130

142-
return taskFromDTO(task);
131+
// Update output for shell
132+
this.openedShell.output.push(out);
133+
this.onOutputEmitter.fire(out);
134+
});
143135
}
136+
async open(dimensions = DEFAULT_SHELL_SIZE) {
137+
if (!this.shell) {
138+
throw new Error("Task is not running");
139+
}
144140

145-
async stopTask(taskId: string) {
146-
await this.pitcherClient.clients.task.stopTask(taskId);
147-
}
148-
}
141+
const openedShell = await this.pitcherClient.clients.shell.open(
142+
this.shell.shellId,
143+
dimensions
144+
);
149145

150-
function taskFromDTO(value: protocol.task.TaskDTO): Task {
151-
return {
152-
id: value.id,
153-
name: value.name,
154-
command: value.command,
155-
runAtStart: value.runAtStart,
156-
preview: value.preview,
157-
shellId: value.shell?.shellId ?? null,
158-
ports: value.ports,
159-
};
146+
this.openedShell = {
147+
shellId: openedShell.shellId,
148+
output: openedShell.buffer,
149+
dimensions,
150+
};
151+
152+
return this.openedShell.output.join("\n");
153+
}
154+
async run() {
155+
await this.pitcherClient.clients.task.runTask(this.id);
156+
}
157+
async restart() {
158+
await this.run();
159+
}
160+
async stop() {
161+
if (this.shell) {
162+
await this.pitcherClient.clients.task.stopTask(this.id);
163+
}
164+
}
160165
}

0 commit comments

Comments
 (0)