bw-hspc-contest-env/extension/bwcontest/src/problemPanel.ts

369 lines
10 KiB
TypeScript
Raw Normal View History

2023-05-06 00:01:27 -04:00
import * as vscode from 'vscode';
import { getNonce } from './getNonce';
2023-10-16 13:11:54 -04:00
import urlJoin from 'url-join';
2023-10-16 15:32:26 -04:00
import { extensionSettings } from './extension';
import { runJava } from './run/java';
import { join } from 'path';
2023-10-17 12:50:13 -04:00
import { submitProblem } from './submit';
2023-11-15 21:01:12 -05:00
import { runCSharp } from './run/csharp';
2024-02-17 15:02:33 -05:00
import { runCpp } from './run/cpp';
VSCode Extension: Sidebar UI showing team's submissions, automatically updating and showing alerts as submissions are judged (#14) * Add an Output Panel channel named "BWContest Log" * Allow client logout when no contest And make login/logout error messages clearer * Show contest name & team name in Code extension side panel * submission icons for sidebar panel * Start VSCode extension "onStartupFinished" instead of waiting for Sidebar to be opened * VSCode: Sidebar UI for up-to-date problem/submissions status - VSCode: poll API every 30 seconds to get contest metadata and all submission metadata for the logged in team - The Sidebar now shows all problems in the contest, along with their submissions and overall status, which automatically updates as submissions are submitted & judged - Web: "contestState" API to get all info for an activeTeam via their token - Update submit API to return the submission id, allowing the VSCode UI to immediately render it as Pending without waiting for a polling cycle - * Add "Compilation Failed" message to submissions that fail to build * Contest Import - Option to create repos & immediately activate the imported contest Useful for testing with old contests (including the submissions) * Test/Submit panel, use fixed-width font in input/output areas * Fix build error for 'pluralize' * Clear all state & halt polling loops on logout, restart them on login * Improve the debug fastPolling option - Toggleable via package.json config - Setting the option changes the initial state as well as ability to toggle states * Web project 'npm run format'
2024-03-05 17:50:16 -05:00
import { TeamData } from './sharedTypes';
import outputPanelLog from './outputPanelLog';
import { recordInitialSubmission } from './contestMonitor/contestStateSyncManager';
2023-10-16 13:11:54 -04:00
export type ProblemData = {
id: number;
name: string;
pascalName: string;
sampleInput: string;
sampleOutput: string;
}[];
2023-10-16 15:32:26 -04:00
export type MessageType =
| { msg: 'onRequestProblemData' }
| { msg: 'onRun'; data: { problemId: number; input: string } }
2023-10-17 12:50:13 -04:00
| { msg: 'onKill' }
| { msg: 'onSubmit'; data: { problemId: number } };
2023-10-16 15:32:26 -04:00
export type WebviewMessageType =
| { msg: 'onProblemData'; data: ProblemData }
| { msg: 'onRunning' }
| { msg: 'onRunningDone' }
| { msg: 'onRunningOutput'; data: string };
type RunningProgram = {
problemId: number;
outputBuffer: string[];
kill: () => void;
};
2023-10-16 13:11:54 -04:00
/**
* Singleton class for problem panel
*/
2023-05-06 00:01:27 -04:00
export class BWPanel {
public static currentPanel: BWPanel | undefined;
2023-10-16 15:32:26 -04:00
private runningProgram: RunningProgram | undefined;
private problemData: ProblemData | undefined;
2023-05-06 00:01:27 -04:00
2023-10-16 13:11:54 -04:00
private constructor(
private readonly context: vscode.ExtensionContext,
private readonly panel: vscode.WebviewPanel,
private readonly extensionUri: vscode.Uri,
private readonly webUrl: string
) {
this.update();
2023-10-16 15:32:26 -04:00
panel.onDidDispose(() => this.dispose());
2023-10-16 13:11:54 -04:00
}
2023-05-06 00:01:27 -04:00
2023-10-16 13:11:54 -04:00
public static show(context: vscode.ExtensionContext, webUrl: string) {
2024-03-05 19:31:11 -05:00
outputPanelLog.info('Showing BWPanel');
2023-05-06 00:01:27 -04:00
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
2023-10-16 13:11:54 -04:00
// Show panel if exists
2023-10-16 15:32:26 -04:00
if (BWPanel.currentPanel !== undefined) {
2023-10-16 13:11:54 -04:00
BWPanel.currentPanel.panel.reveal(column);
2023-05-06 00:01:27 -04:00
return;
}
2023-10-16 13:11:54 -04:00
// Otherwise create new panel
2023-05-06 00:01:27 -04:00
const panel = vscode.window.createWebviewPanel(
2023-10-16 13:11:54 -04:00
'bwpanel',
'BWContest',
2023-05-06 00:01:27 -04:00
column || vscode.ViewColumn.One,
{
enableScripts: true,
2023-05-07 16:30:42 -04:00
retainContextWhenHidden: true,
2023-05-06 00:01:27 -04:00
localResourceRoots: [
2023-05-08 14:37:52 -04:00
vscode.Uri.joinPath(context.extensionUri, 'media'),
vscode.Uri.joinPath(context.extensionUri, 'out/compiled')
2023-05-06 00:01:27 -04:00
]
}
);
2023-10-16 13:11:54 -04:00
BWPanel.currentPanel = new BWPanel(context, panel, context.extensionUri, webUrl);
2023-05-06 00:01:27 -04:00
}
public static kill() {
BWPanel.currentPanel?.dispose();
BWPanel.currentPanel = undefined;
}
public dispose() {
BWPanel.currentPanel = undefined;
}
2023-10-16 13:11:54 -04:00
private webviewPostMessage(m: WebviewMessageType) {
this.panel.webview.postMessage(m);
}
2023-05-06 00:01:27 -04:00
2023-10-17 12:50:13 -04:00
private async handleSubmit(problemId: number) {
if (this.problemData === undefined) {
console.error('Problem data undefined');
return;
}
const problem = this.problemData.find((p) => p.id === problemId);
if (problem === undefined) {
console.error('Invalid problem Id');
return;
}
const sessionToken = this.context.globalState.get<string>('token');
if (sessionToken === undefined) {
console.error('No session token');
return;
}
const teamData = this.context.globalState.get<TeamData>('teamData');
if (teamData === undefined) {
console.error('No team data');
return;
}
await vscode.workspace.saveAll();
const ans = await vscode.window.showInformationMessage(
VSCode Extension: Sidebar UI showing team's submissions, automatically updating and showing alerts as submissions are judged (#14) * Add an Output Panel channel named "BWContest Log" * Allow client logout when no contest And make login/logout error messages clearer * Show contest name & team name in Code extension side panel * submission icons for sidebar panel * Start VSCode extension "onStartupFinished" instead of waiting for Sidebar to be opened * VSCode: Sidebar UI for up-to-date problem/submissions status - VSCode: poll API every 30 seconds to get contest metadata and all submission metadata for the logged in team - The Sidebar now shows all problems in the contest, along with their submissions and overall status, which automatically updates as submissions are submitted & judged - Web: "contestState" API to get all info for an activeTeam via their token - Update submit API to return the submission id, allowing the VSCode UI to immediately render it as Pending without waiting for a polling cycle - * Add "Compilation Failed" message to submissions that fail to build * Contest Import - Option to create repos & immediately activate the imported contest Useful for testing with old contests (including the submissions) * Test/Submit panel, use fixed-width font in input/output areas * Fix build error for 'pluralize' * Clear all state & halt polling loops on logout, restart them on login * Improve the debug fastPolling option - Toggleable via package.json config - Setting the option changes the initial state as well as ability to toggle states * Web project 'npm run format'
2024-03-05 17:50:16 -05:00
`Are you sure you want to submit '${problem.name}'?`,
2023-10-17 12:50:13 -04:00
'Yes',
'No'
);
if (ans !== 'Yes') {
return;
}
VSCode Extension: Sidebar UI showing team's submissions, automatically updating and showing alerts as submissions are judged (#14) * Add an Output Panel channel named "BWContest Log" * Allow client logout when no contest And make login/logout error messages clearer * Show contest name & team name in Code extension side panel * submission icons for sidebar panel * Start VSCode extension "onStartupFinished" instead of waiting for Sidebar to be opened * VSCode: Sidebar UI for up-to-date problem/submissions status - VSCode: poll API every 30 seconds to get contest metadata and all submission metadata for the logged in team - The Sidebar now shows all problems in the contest, along with their submissions and overall status, which automatically updates as submissions are submitted & judged - Web: "contestState" API to get all info for an activeTeam via their token - Update submit API to return the submission id, allowing the VSCode UI to immediately render it as Pending without waiting for a polling cycle - * Add "Compilation Failed" message to submissions that fail to build * Contest Import - Option to create repos & immediately activate the imported contest Useful for testing with old contests (including the submissions) * Test/Submit panel, use fixed-width font in input/output areas * Fix build error for 'pluralize' * Clear all state & halt polling loops on logout, restart them on login * Improve the debug fastPolling option - Toggleable via package.json config - Setting the option changes the initial state as well as ability to toggle states * Web project 'npm run format'
2024-03-05 17:50:16 -05:00
try {
2024-03-05 19:31:11 -05:00
const submissionResult = await submitProblem(
sessionToken,
teamData.contestId,
teamData.teamId,
problemId
);
VSCode Extension: Sidebar UI showing team's submissions, automatically updating and showing alerts as submissions are judged (#14) * Add an Output Panel channel named "BWContest Log" * Allow client logout when no contest And make login/logout error messages clearer * Show contest name & team name in Code extension side panel * submission icons for sidebar panel * Start VSCode extension "onStartupFinished" instead of waiting for Sidebar to be opened * VSCode: Sidebar UI for up-to-date problem/submissions status - VSCode: poll API every 30 seconds to get contest metadata and all submission metadata for the logged in team - The Sidebar now shows all problems in the contest, along with their submissions and overall status, which automatically updates as submissions are submitted & judged - Web: "contestState" API to get all info for an activeTeam via their token - Update submit API to return the submission id, allowing the VSCode UI to immediately render it as Pending without waiting for a polling cycle - * Add "Compilation Failed" message to submissions that fail to build * Contest Import - Option to create repos & immediately activate the imported contest Useful for testing with old contests (including the submissions) * Test/Submit panel, use fixed-width font in input/output areas * Fix build error for 'pluralize' * Clear all state & halt polling loops on logout, restart them on login * Improve the debug fastPolling option - Toggleable via package.json config - Setting the option changes the initial state as well as ability to toggle states * Web project 'npm run format'
2024-03-05 17:50:16 -05:00
if (submissionResult.success === true) {
recordInitialSubmission(submissionResult.submission);
vscode.window.showInformationMessage(`Submitted '${problem.name}'!`);
2023-10-17 12:50:13 -04:00
} else {
2024-03-05 19:31:11 -05:00
vscode.window.showErrorMessage(
`Error submitting '${problem.name}': ${submissionResult.message}`
);
2023-10-17 12:50:13 -04:00
}
2024-03-05 19:31:11 -05:00
} catch (error) {
VSCode Extension: Sidebar UI showing team's submissions, automatically updating and showing alerts as submissions are judged (#14) * Add an Output Panel channel named "BWContest Log" * Allow client logout when no contest And make login/logout error messages clearer * Show contest name & team name in Code extension side panel * submission icons for sidebar panel * Start VSCode extension "onStartupFinished" instead of waiting for Sidebar to be opened * VSCode: Sidebar UI for up-to-date problem/submissions status - VSCode: poll API every 30 seconds to get contest metadata and all submission metadata for the logged in team - The Sidebar now shows all problems in the contest, along with their submissions and overall status, which automatically updates as submissions are submitted & judged - Web: "contestState" API to get all info for an activeTeam via their token - Update submit API to return the submission id, allowing the VSCode UI to immediately render it as Pending without waiting for a polling cycle - * Add "Compilation Failed" message to submissions that fail to build * Contest Import - Option to create repos & immediately activate the imported contest Useful for testing with old contests (including the submissions) * Test/Submit panel, use fixed-width font in input/output areas * Fix build error for 'pluralize' * Clear all state & halt polling loops on logout, restart them on login * Improve the debug fastPolling option - Toggleable via package.json config - Setting the option changes the initial state as well as ability to toggle states * Web project 'npm run format'
2024-03-05 17:50:16 -05:00
vscode.window.showErrorMessage(`Web error submitting '${problem.name}'`);
outputPanelLog.error(`Web error submitting '${problem.name}': ${error}`);
}
2023-10-17 12:50:13 -04:00
}
private async handleRun(problemId: number, input: string) {
const teamData: TeamData | undefined = this.context.globalState.get('teamData');
if (teamData === undefined) {
return;
}
if (this.problemData === undefined) {
return;
}
if (this.runningProgram !== undefined) {
vscode.window.showErrorMessage('Already Running');
return;
}
2023-11-15 21:01:12 -05:00
const problem = this.problemData.find((p) => p.id === problemId);
2023-10-17 12:50:13 -04:00
if (problem === undefined) {
return;
}
await vscode.workspace.saveAll();
const repoDir = extensionSettings().repoClonePath;
const outputBuffer: string[] = [];
this.webviewPostMessage({ msg: 'onRunning' });
this.webviewPostMessage({ msg: 'onRunningOutput', data: '[Compiling...]' });
2023-11-15 21:01:12 -05:00
let killFunc: (() => void) | undefined;
if (teamData.language === 'Java') {
2024-02-17 15:43:38 -05:00
const res = await runJava({
input,
mainClass: problem.pascalName,
mainFile: join(
2023-11-15 21:01:12 -05:00
repoDir,
'BWContest',
teamData.contestId.toString(),
teamData.teamId.toString(),
2024-02-17 15:43:38 -05:00
problem.pascalName,
`${problem.pascalName}.java`
2023-11-15 21:01:12 -05:00
),
2024-02-17 15:43:38 -05:00
srcDir: join(
2023-11-15 21:01:12 -05:00
repoDir,
'BWContest',
teamData.contestId.toString(),
teamData.teamId.toString(),
2024-02-17 15:43:38 -05:00
problem.pascalName
2023-11-15 21:01:12 -05:00
),
2024-02-17 15:43:38 -05:00
outputCallback: (data) => {
2023-11-15 21:01:12 -05:00
outputBuffer.push(data);
this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') });
2024-02-17 15:43:38 -05:00
}
});
if (res.success === true) {
killFunc = res.killFunc;
res.runResult.then(() => {
2023-11-15 21:01:12 -05:00
this.runningProgram = undefined;
this.webviewPostMessage({ msg: 'onRunningDone' });
2024-03-05 19:31:11 -05:00
});
2024-02-17 15:43:38 -05:00
} else {
this.runningProgram = undefined;
this.webviewPostMessage({
msg: 'onRunningOutput',
data: `${res.runResult.kind}:\n${res.runResult.output}`
});
this.webviewPostMessage({ msg: 'onRunningDone' });
}
2023-11-15 21:01:12 -05:00
} else if (teamData.language === 'CSharp') {
2024-02-17 15:43:38 -05:00
const res = await runCSharp({
2024-03-05 19:31:11 -05:00
input,
2024-02-17 15:43:38 -05:00
srcDir: join(
2023-11-15 21:01:12 -05:00
repoDir,
'BWContest',
teamData.contestId.toString(),
teamData.teamId.toString(),
problem.pascalName
),
2024-02-17 15:43:38 -05:00
outputCallback: (data) => {
2023-11-15 21:01:12 -05:00
outputBuffer.push(data);
this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') });
2024-02-17 15:43:38 -05:00
}
2024-03-05 19:31:11 -05:00
});
2024-02-17 15:43:38 -05:00
if (res.success === true) {
killFunc = res.killFunc;
res.runResult.then(() => {
2024-02-17 15:02:33 -05:00
this.runningProgram = undefined;
this.webviewPostMessage({ msg: 'onRunningDone' });
2024-03-05 19:31:11 -05:00
});
2024-02-17 15:43:38 -05:00
} else {
this.runningProgram = undefined;
this.webviewPostMessage({
msg: 'onRunningOutput',
data: `${res.runResult.kind}:\n${res.runResult.output}`
});
this.webviewPostMessage({ msg: 'onRunningDone' });
}
2024-02-17 15:02:33 -05:00
} else if (teamData.language === 'CPP') {
2024-02-17 15:43:38 -05:00
const res = await runCpp({
2024-02-17 15:02:33 -05:00
input,
2024-02-17 15:43:38 -05:00
cppPlatform: process.platform === 'win32' ? 'VisualStudio' : 'GCC',
problemName: problem.pascalName,
2024-03-05 19:31:11 -05:00
srcDir: join(
repoDir,
'BWContest',
teamData.contestId.toString(),
teamData.teamId.toString()
),
2024-02-17 15:43:38 -05:00
outputCallback: (data) => {
2024-02-17 15:02:33 -05:00
outputBuffer.push(data);
this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') });
2024-02-17 15:43:38 -05:00
}
2024-03-05 19:31:11 -05:00
});
2024-02-17 15:43:38 -05:00
if (res.success === true) {
killFunc = res.killFunc;
res.runResult.then(() => {
2023-11-15 21:01:12 -05:00
this.runningProgram = undefined;
this.webviewPostMessage({ msg: 'onRunningDone' });
2024-03-05 19:31:11 -05:00
});
2024-02-17 15:43:38 -05:00
} else {
this.runningProgram = undefined;
this.webviewPostMessage({
msg: 'onRunningOutput',
data: `${res.runResult.kind}:\n${res.runResult.output}`
});
this.webviewPostMessage({ msg: 'onRunningDone' });
}
2023-11-15 21:01:12 -05:00
}
2023-10-17 12:50:13 -04:00
if (killFunc !== undefined) {
this.runningProgram = {
problemId: problemId,
outputBuffer: outputBuffer,
kill: killFunc
};
} else {
this.webviewPostMessage({ msg: 'onRunningDone' });
}
}
private async handleRequestProblemData() {
const token: string | undefined = this.context.globalState.get('token');
if (token !== undefined) {
const res = await fetch(urlJoin(this.webUrl, `/api/contest/${token}`));
const data = await res.json();
if (data.success === true) {
this.problemData = data.problems;
this.webviewPostMessage({
msg: 'onProblemData',
data: data.problems
});
}
}
}
2023-10-16 13:11:54 -04:00
private async update() {
const webview = this.panel.webview;
this.panel.webview.html = this._getHtmlForWebview(webview);
2023-10-17 12:50:13 -04:00
webview.onDidReceiveMessage((m: MessageType) => {
2023-10-16 13:11:54 -04:00
switch (m.msg) {
2023-10-16 15:32:26 -04:00
case 'onKill': {
if (this.runningProgram !== undefined) {
this.runningProgram.kill();
return;
}
break;
}
2023-10-17 12:50:13 -04:00
case 'onSubmit': {
this.handleSubmit(m.data.problemId);
break;
}
2023-10-16 15:32:26 -04:00
case 'onRun': {
2023-10-17 12:50:13 -04:00
this.handleRun(m.data.problemId, m.data.input);
2023-10-16 15:32:26 -04:00
break;
}
2023-10-16 13:11:54 -04:00
case 'onRequestProblemData': {
2023-10-17 12:50:13 -04:00
this.handleRequestProblemData();
2023-05-06 00:01:27 -04:00
break;
}
}
});
}
private _getHtmlForWebview(webview: vscode.Webview) {
const scriptUri = webview.asWebviewUri(
2023-10-16 13:11:54 -04:00
vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.js')
2023-05-06 00:01:27 -04:00
);
const stylesResetUri = webview.asWebviewUri(
2023-10-16 13:11:54 -04:00
vscode.Uri.joinPath(this.extensionUri, 'media', 'reset.css')
2023-05-06 00:01:27 -04:00
);
const stylesMainUri = webview.asWebviewUri(
2023-10-16 13:11:54 -04:00
vscode.Uri.joinPath(this.extensionUri, 'media', 'vscode.css')
2023-05-06 00:01:27 -04:00
);
2023-05-07 11:01:27 -04:00
const cssUri = webview.asWebviewUri(
2023-10-16 13:11:54 -04:00
vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.css')
2023-05-07 11:01:27 -04:00
);
2023-05-06 00:01:27 -04:00
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="img-src https: data:; style-src 'unsafe-inline' ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${stylesResetUri}" rel="stylesheet">
<link href="${stylesMainUri}" rel="stylesheet">
2023-05-07 11:01:27 -04:00
<link href="${cssUri}" rel="stylesheet">
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
</script>
2023-05-06 00:01:27 -04:00
</head>
<body>
</body>
<script src=${scriptUri} nonce="${nonce}">
</script>
</html>`;
}
}