[extension] More cleanup

This commit is contained in:
orosmatthew 2023-10-16 13:11:54 -04:00
parent 88be84bb62
commit 6fb6f57ece
4 changed files with 284 additions and 316 deletions

View File

@ -4,6 +4,20 @@ import { cloneAndOpenRepo } from './extension';
import { BWPanel } from './problemPanel';
import urlJoin from 'url-join';
export type TeamData = {
teamId: number;
contestId: number;
};
export type WebviewMessageType = { msg: 'onLogin'; data: TeamData } | { msg: 'onLogout' };
export type MessageType =
| { msg: 'onTestAndSubmit' }
| { msg: 'onStartup' }
| { msg: 'onClone'; data: { contestId: number; teamId: number } }
| { msg: 'onLogin'; data: { teamName: string; password: string } }
| { msg: 'onLogout' };
export class SidebarProvider implements vscode.WebviewViewProvider {
constructor(
private readonly extensionUri: vscode.Uri,
@ -19,43 +33,40 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
};
webview.html = this.getHtmlForWebview(webview);
webview.onDidReceiveMessage(async (data: { type: string; value: any }) => {
switch (data.type) {
const webviewPostMessage = (m: WebviewMessageType) => {
webview.postMessage(m);
};
webview.onDidReceiveMessage(async (m: MessageType) => {
switch (m.msg) {
case 'onTestAndSubmit': {
if (this.context) {
BWPanel.createOrShow(this.context, this.webUrl);
BWPanel.show(this.context, this.webUrl);
}
break;
}
case 'onStartup': {
const token: string | undefined = this.context.globalState.get('token');
const teamData = this.context.globalState.get('teamData');
const teamData = this.context.globalState.get('teamData') as TeamData | undefined;
if (token && teamData !== undefined) {
webview.postMessage({
type: 'onLogin',
value: teamData
webviewPostMessage({
msg: 'onLogin',
data: teamData
});
}
webview.postMessage({
type: 'onWebUrl',
value: this.webUrl
});
break;
}
case 'onClone': {
if (!data.value || !data.value.contestId || !data.value.teamId) {
return;
}
await cloneAndOpenRepo(parseInt(data.value.contestId), parseInt(data.value.teamId));
await cloneAndOpenRepo(m.data.contestId, m.data.teamId);
break;
}
case 'requestLogin': {
case 'onLogin': {
const res = await fetch(urlJoin(this.webUrl, '/api/team/login'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
teamname: data.value.teamname,
password: data.value.password
teamname: m.data.teamName,
password: m.data.password
})
});
const thing = await res.json();
@ -72,13 +83,13 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
return;
}
this.context.globalState.update('teamData', data2.data);
webview.postMessage({ type: 'onLogin', value: data2.data });
webviewPostMessage({ msg: 'onLogout' });
break;
}
case 'requestLogout': {
case 'onLogout': {
const sessionToken = this.context.globalState.get<string>('token');
if (sessionToken === undefined) {
webview.postMessage({ type: 'onLogout' });
webviewPostMessage({ msg: 'onLogout' });
}
const res = await fetch(urlJoin(this.webUrl, '/api/team/logout'), {
method: 'POST',
@ -92,29 +103,29 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
}
const data2 = await res.json();
if (data2.success === true) {
webview.postMessage({ type: 'onLogout' });
webviewPostMessage({ msg: 'onLogout' });
this.context.globalState.update('token', undefined);
}
break;
}
case 'onLogout': {
this.context.globalState.update('token', null);
break;
}
case 'onInfo': {
if (!data.value) {
return;
}
vscode.window.showInformationMessage(data.value);
break;
}
case 'onError': {
if (!data.value) {
return;
}
vscode.window.showErrorMessage(data.value);
break;
}
// case 'onLogout': {
// this.context.globalState.update('token', null);
// break;
// }
// case 'onInfo': {
// if (!data.value) {
// return;
// }
// vscode.window.showInformationMessage(data.value);
// break;
// }
// case 'onError': {
// if (!data.value) {
// return;
// }
// vscode.window.showErrorMessage(data.value);
// break;
// }
}
});
}

View File

@ -4,46 +4,56 @@ import { runJava } from './run/java';
import { extensionSettings } from './extension';
import { join } from 'path';
import { submitProblem } from './submit';
import urlJoin from 'url-join';
export type ProblemData = {
id: number;
name: string;
pascalName: string;
sampleInput: string;
sampleOutput: string;
}[];
export type MessageType = { msg: 'onRequestProblemData' };
export type WebviewMessageType = { msg: 'onProblemData'; data: ProblemData };
/**
* Singleton class for problem panel
*/
export class BWPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: BWPanel | undefined;
public static readonly viewType = 'bwpanel';
private running: boolean = false;
private kill: Function | null = null;
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = [];
private static _context?: vscode.ExtensionContext;
private static _running: boolean;
private static _kill: Function | null;
private constructor(
private readonly context: vscode.ExtensionContext,
private readonly panel: vscode.WebviewPanel,
private readonly extensionUri: vscode.Uri,
private readonly webUrl: string
) {
this.update();
}
public static createOrShow(context: vscode.ExtensionContext, webUrl: string) {
this._context = context;
public static show(context: vscode.ExtensionContext, webUrl: string) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
// If we already have a panel, show it.
// Show panel if exists
if (BWPanel.currentPanel) {
BWPanel.currentPanel._panel.reveal(column);
BWPanel.currentPanel.panel.reveal(column);
return;
}
// Otherwise, create a new panel.
// Otherwise create new panel
const panel = vscode.window.createWebviewPanel(
BWPanel.viewType,
'bwpanel',
'BWContest',
column || vscode.ViewColumn.One,
{
// Enable javascript in the webview
enableScripts: true,
retainContextWhenHidden: true,
// And restrict the webview to only loading content from our extension's `media` directory.
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, 'media'),
vscode.Uri.joinPath(context.extensionUri, 'out/compiled')
@ -51,7 +61,7 @@ export class BWPanel {
}
);
BWPanel.currentPanel = new BWPanel(panel, context.extensionUri, webUrl);
BWPanel.currentPanel = new BWPanel(context, panel, context.extensionUri, webUrl);
}
public static kill() {
@ -59,173 +69,155 @@ export class BWPanel {
BWPanel.currentPanel = undefined;
}
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, webUrl: string) {
BWPanel.currentPanel = new BWPanel(panel, extensionUri, webUrl);
}
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, webUrl: string) {
this._panel = panel;
this._extensionUri = extensionUri;
this._update();
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
}
public dispose() {
this.panel.dispose();
BWPanel.currentPanel = undefined;
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private async _update() {
const webview = this._panel.webview;
private webviewPostMessage(m: WebviewMessageType) {
this.panel.webview.postMessage(m);
}
this._panel.webview.html = this._getHtmlForWebview(webview);
webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case 'onKill': {
if (!BWPanel._running || !BWPanel._kill) {
break;
}
BWPanel._kill();
}
case 'onSubmit': {
await vscode.workspace.saveAll();
if (!data.value) {
return;
}
const ans = await vscode.window.showInformationMessage(
`Are you sure you want to submit ${data.value.problemName}?`,
'Yes',
'No'
);
if (ans !== 'Yes') {
break;
}
try {
await submitProblem(
data.value.sessionToken,
data.value.contestId,
data.value.teamId,
data.value.problemId
);
} catch (err: any) {
vscode.window.showErrorMessage(err.message ?? 'Submission unsuccessful');
break;
}
vscode.window.showInformationMessage('Submitted!');
break;
}
case 'onRun': {
if (BWPanel._running === true) {
vscode.window.showErrorMessage('Already running');
break;
}
await vscode.workspace.saveAll();
if (!data.value) {
break;
}
const repoDir = extensionSettings().repoClonePath;
BWPanel._running = true;
const process = await runJava(
join(
repoDir,
'BWContest',
data.value.contestId.toString(),
data.value.teamId.toString(),
data.value.problemPascalName.toString()
),
join(
repoDir,
'BWContest',
data.value.contestId.toString(),
data.value.teamId.toString(),
data.value.problemPascalName.toString(),
`${data.value.problemPascalName}.java`
),
data.value.problemPascalName,
data.value.input
);
if (!process) {
this._panel.webview.postMessage({
type: 'onOutput',
value: '[An error occurred while running]'
});
break;
}
process.output
.then((output) => {
this._panel.webview.postMessage({ type: 'onOutput', value: output });
BWPanel._running = false;
BWPanel._kill = null;
})
.catch(() => {
this._panel.webview.postMessage({
type: 'onOutput',
value: '[An error occurred while running]'
private async update() {
const webview = this.panel.webview;
this.panel.webview.html = this._getHtmlForWebview(webview);
webview.onDidReceiveMessage(async (m: MessageType) => {
switch (m.msg) {
// case 'onKill': {
// if (!this.running || !this.kill) {
// break;
// }
// this.kill();
// }
// case 'onSubmit': {
// await vscode.workspace.saveAll();
// if (!data.value) {
// return;
// }
// const ans = await vscode.window.showInformationMessage(
// `Are you sure you want to submit ${data.value.problemName}?`,
// 'Yes',
// 'No'
// );
// if (ans !== 'Yes') {
// break;
// }
// try {
// await submitProblem(
// data.value.sessionToken,
// data.value.contestId,
// data.value.teamId,
// data.value.problemId
// );
// } catch (err: any) {
// vscode.window.showErrorMessage(err.message ?? 'Submission unsuccessful');
// break;
// }
// vscode.window.showInformationMessage('Submitted!');
// break;
// }
// case 'onRun': {
// if (this.running === true) {
// vscode.window.showErrorMessage('Already running');
// break;
// }
// await vscode.workspace.saveAll();
// if (!data.value) {
// break;
// }
// const repoDir = extensionSettings().repoClonePath;
// this.running = true;
// const process = await runJava(
// join(
// repoDir,
// 'BWContest',
// data.value.contestId.toString(),
// data.value.teamId.toString(),
// data.value.problemPascalName.toString()
// ),
// join(
// repoDir,
// 'BWContest',
// data.value.contestId.toString(),
// data.value.teamId.toString(),
// data.value.problemPascalName.toString(),
// `${data.value.problemPascalName}.java`
// ),
// data.value.problemPascalName,
// data.value.input
// );
// if (!process) {
// this.panel.webview.postMessage({
// type: 'onOutput',
// value: '[An error occurred while running]'
// });
// break;
// }
// process.output
// .then((output) => {
// this.panel.webview.postMessage({ type: 'onOutput', value: output });
// this.running = false;
// this.kill = null;
// })
// .catch(() => {
// this.panel.webview.postMessage({
// type: 'onOutput',
// value: '[An error occurred while running]'
// });
// this.running = false;
// this.kill = null;
// });
// this.kill = process.kill;
// break;
// }
case 'onRequestProblemData': {
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.webviewPostMessage({
msg: 'onProblemData',
data: data.problems
});
BWPanel._running = false;
BWPanel._kill = null;
});
BWPanel._kill = process.kill;
break;
}
case 'onStartup': {
const token: string | undefined = BWPanel._context?.globalState.get('token');
if (token) {
this._panel.webview.postMessage({
type: 'onSession',
value: token
});
}
}
break;
}
case 'onInfo': {
if (!data.value) {
return;
}
vscode.window.showInformationMessage(data.value);
break;
}
case 'onError': {
if (!data.value) {
return;
}
vscode.window.showErrorMessage(data.value);
break;
}
// case "tokens": {
// await Util.globalState.update(accessTokenKey, data.accessToken);
// await Util.globalState.update(refreshTokenKey, data.refreshToken);
// break;
// case 'onInfo': {
// if (!data.value) {
// return;
// }
// vscode.window.showInformationMessage(data.value);
// break;
// }
// case 'onError': {
// if (!data.value) {
// return;
// }
// vscode.window.showErrorMessage(data.value);
// break;
// }
}
});
}
private _getHtmlForWebview(webview: vscode.Webview) {
// // And the uri we use to load this script in the webview
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.js')
vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.js')
);
// Uri to load styles into webview
const stylesResetUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css')
vscode.Uri.joinPath(this.extensionUri, 'media', 'reset.css')
);
const stylesMainUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
vscode.Uri.joinPath(this.extensionUri, 'media', 'vscode.css')
);
const cssUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.css')
vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.css')
);
// // Use a nonce to only allow specific scripts to be run
const nonce = getNonce();
return `<!DOCTYPE html>

View File

@ -1,120 +1,89 @@
<script lang="ts">
import { onMount } from 'svelte';
import urlJoin from 'url-join';
import type { WebviewMessageType, MessageType, ProblemData } from '../../src/problemPanel';
function postMessage(message: any) {
function postMessage(message: MessageType) {
vscode.postMessage(message);
}
type ProblemData = {
id: number;
name: string;
pascalName: string;
sampleInput: string;
sampleOutput: string;
}[];
// let savedInputs: Map<number, { input: string; output: string }> = new Map();
let savedInputs: Map<number, { input: string; output: string }> = new Map();
let activeProblem: ProblemData[0];
let sessionToken: string | undefined;
let activeProblemIndex = 0;
let problemData: ProblemData | undefined;
let sampleInputText: HTMLTextAreaElement;
let outputText: HTMLTextAreaElement;
let sampleInputValue: string;
let outputValue: string;
let running = false;
let webUrl: string | undefined;
$: if (problemData && problemData.length !== 0) {
let first = problemData.at(0);
if (first) {
activeProblem = first;
}
activeProblemIndex = 0;
}
function resetInput() {
sampleInputText.value = activeProblem.sampleInput;
if (problemData) {
sampleInputValue = problemData[activeProblemIndex].sampleInput;
} else {
sampleInputValue = '';
}
}
let contestId: number | undefined;
let teamId: number | undefined;
function onRun() {
if (!running && contestId && teamId) {
postMessage({
type: 'onRun',
value: {
problemPascalName: activeProblem.pascalName,
contestId: contestId,
teamId: teamId,
input: sampleInputText.value
}
});
running = true;
}
// if (problemData !== undefined && running === false) {
// postMessage({
// type: 'requestRun',
// value: {
// problemId: problemData[activeProblemIndex].id,
// input: sampleInputValue
// }
// });
// }
}
function updateTextBoxes() {
if (savedInputs.has(activeProblem.id)) {
sampleInputText.value = savedInputs.get(activeProblem.id)!.input;
outputText.value = savedInputs.get(activeProblem.id)!.output;
} else {
sampleInputText.value = activeProblem.sampleInput;
outputText.value = '[Run to get output]';
// if (savedInputs.has(activeProblem.id)) {
// sampleInputText.value = savedInputs.get(activeProblem.id)!.input;
// outputText.value = savedInputs.get(activeProblem.id)!.output;
// } else {
if (problemData !== undefined) {
sampleInputValue = problemData[activeProblemIndex].sampleInput;
}
outputValue = '[Run to get output]';
// }
}
function onSubmit() {
if (teamId && contestId && sessionToken) {
postMessage({
type: 'onSubmit',
value: {
sessionToken: sessionToken,
contestId: contestId,
teamId: teamId,
problemId: activeProblem.id,
problemName: activeProblem.pascalName
}
});
}
// if (teamId && contestId && sessionToken) {
// postMessage({
// type: 'onSubmit',
// value: {
// sessionToken: sessionToken,
// contestId: contestId,
// teamId: teamId,
// problemId: activeProblem.id,
// problemName: activeProblem.pascalName
// }
// });
// }
}
function onKill() {
postMessage({ type: 'onKill' });
// postMessage({ type: 'onKill' });
}
async function fetchProblemData() {
if (sessionToken && webUrl) {
const res = await fetch(urlJoin(webUrl, `/api/contest/${sessionToken}`));
const data = await res.json();
if (data.success === true) {
problemData = data.problems as ProblemData;
contestId = data.contestId;
teamId = data.teamId;
}
}
}
window.addEventListener('message', async (event) => {
const message = (event as MessageEvent).data;
if (message.type === 'onSession') {
if (message.value !== '') {
sessionToken = message.value;
await fetchProblemData();
updateTextBoxes();
}
} else if (message.type === 'onOutput') {
outputText.value = message.value;
running = false;
} else if (message.type === 'onWebUrl') {
webUrl = message.value;
}
});
onMount(() => {
postMessage({ type: 'onStartup' });
postMessage({ msg: 'onRequestProblemData' });
});
window.addEventListener('message', async (event) => {
const m = (event as MessageEvent).data as WebviewMessageType;
// if (message.msg === 'onOutput') {
// outputValue = message.value;
// running = false;
if (m.msg === 'onProblemData') {
problemData = m.data;
updateTextBoxes();
}
});
</script>
@ -122,32 +91,32 @@
{#if problemData}
<div class="tab-container">
{#each problemData as problem}
{#each problemData as problem, i}
<button
on:click={() => {
if (!running) {
savedInputs.set(activeProblem.id, {
input: sampleInputText.value,
output: outputText.value
});
activeProblem = problem;
// savedInputs.set(activeProblem.id, {
// input: sampleInputText.value,
// output: outputText.value
// });
activeProblemIndex = i;
updateTextBoxes();
}
}}
id={`problem_${problem.id}`}
type="button"
class={'tab ' + (activeProblem.id == problem.id ? 'active' : '')}>{problem.name}</button
class={'tab ' + (activeProblemIndex === i ? 'active' : '')}>{problem.name}</button
>
{/each}
</div>
{/if}
{#if activeProblem}
<h2>{activeProblem.name}</h2>
{#if problemData !== undefined}
<h2>{problemData[activeProblemIndex].name}</h2>
<div style="display:flex">
<div style="flex:1; margin-right:20px">
<h3>Sample Input (You can edit this!)</h3>
<textarea bind:this={sampleInputText} />
<textarea bind:value={sampleInputValue} />
<button style="margin-top:5px" on:click={resetInput} type="button">Reset Input</button>
</div>
<div style="flex:1">
@ -157,7 +126,7 @@
<span class="loader"></span>
{/if}
</div>
<textarea bind:this={outputText} disabled />
<textarea bind:value={outputValue} readonly />
{#if !running}
<button style="margin-top:5px" on:click={onRun} type="button">Run</button>
{:else}

View File

@ -1,7 +1,8 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { WebviewMessageType, MessageType, TeamData } from '../../src/SidebarProvider';
function postMessage(message: any) {
function postMessage(message: MessageType) {
vscode.postMessage(message);
}
@ -10,49 +11,44 @@
let loggedIn = false;
interface TeamData {
teamId: number;
contestId: number;
}
let teamData: TeamData | undefined;
function onClone() {
if (teamData) {
postMessage({
type: 'onClone',
value: { contestId: teamData.contestId, teamId: teamData.teamId }
msg: 'onClone',
data: { contestId: teamData.contestId, teamId: teamData.teamId }
});
}
}
function onLogin() {
postMessage({
type: 'requestLogin',
value: { teamname: teamname, password: password }
msg: 'onLogin',
data: { teamName: teamname, password: password }
});
}
function onLogout() {
postMessage({
type: 'requestLogout'
msg: 'onLogout'
});
}
function onTestAndSubmit() {
postMessage({ type: 'onTestAndSubmit' });
postMessage({ msg: 'onTestAndSubmit' });
}
onMount(() => {
postMessage({ type: 'onStartup' });
postMessage({ msg: 'onStartup' });
});
window.addEventListener('message', (event) => {
const message = (event as MessageEvent).data;
if (message.type === 'onLogin') {
const m = (event as MessageEvent).data as WebviewMessageType;
if (m.msg === 'onLogin') {
loggedIn = true;
teamData = message.value;
} else if (message.type === 'onLogout') {
teamData = m.data;
} else if (m.msg === 'onLogout') {
loggedIn = false;
teamData = undefined;
}