Merge branch 'main' of https://github.com/orosmatthew/bw-hspc-contest-env
This commit is contained in:
commit
87dd37c6a6
@ -24,6 +24,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"description": "The path where the repos are cloned to"
|
"description": "The path where the repos are cloned to"
|
||||||
|
},
|
||||||
|
"BWContest.javaPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Path of java bin folder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getNonce } from './getNonce';
|
import { getNonce } from './getNonce';
|
||||||
import { cloneAndOpenRepo } from './extension';
|
import { cloneAndOpenRepo } from './extension';
|
||||||
|
import { BWPanel } from './problemPanel';
|
||||||
|
|
||||||
export class SidebarProvider implements vscode.WebviewViewProvider {
|
export class SidebarProvider implements vscode.WebviewViewProvider {
|
||||||
_view?: vscode.WebviewView;
|
_view?: vscode.WebviewView;
|
||||||
@ -24,6 +25,12 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
webviewView.webview.onDidReceiveMessage(async (data) => {
|
webviewView.webview.onDidReceiveMessage(async (data) => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
case 'onTestAndSubmit': {
|
||||||
|
if (this._context) {
|
||||||
|
BWPanel.createOrShow(this._context?.extensionUri, this._context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'onStartup': {
|
case 'onStartup': {
|
||||||
const token: string | undefined = this._context?.globalState.get('token');
|
const token: string | undefined = this._context?.globalState.get('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
|
@ -2,10 +2,16 @@ import * as vscode from 'vscode';
|
|||||||
import { SidebarProvider } from './SidebarProvider';
|
import { SidebarProvider } from './SidebarProvider';
|
||||||
import * as child_process from 'child_process';
|
import * as child_process from 'child_process';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
|
import { BWPanel } from './problemPanel';
|
||||||
|
|
||||||
interface BWContestSettings {
|
export interface BWContestSettings {
|
||||||
repoBaseUrl: string;
|
repoBaseUrl: string;
|
||||||
repoClonePath: string;
|
repoClonePath: string;
|
||||||
|
javaPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extensionSettings(): BWContestSettings {
|
||||||
|
return vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllWorkspaces() {
|
function closeAllWorkspaces() {
|
||||||
@ -42,7 +48,9 @@ export async function cloneAndOpenRepo(contestId: number, teamId: number) {
|
|||||||
fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`);
|
fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clonedRepoPath = `${currentSettings.repoClonePath}/BWContest/${contestId.toString()}/${repoName}`;
|
const clonedRepoPath = `${
|
||||||
|
currentSettings.repoClonePath
|
||||||
|
}/BWContest/${contestId.toString()}/${repoName}`;
|
||||||
|
|
||||||
if (fs.existsSync(clonedRepoPath)) {
|
if (fs.existsSync(clonedRepoPath)) {
|
||||||
const confirm = await vscode.window.showWarningMessage(
|
const confirm = await vscode.window.showWarningMessage(
|
||||||
@ -88,7 +96,11 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider)
|
vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider)
|
||||||
);
|
);
|
||||||
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('bwcontest.helloWorld', () => {}));
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('bwcontest.helloWorld', () => {
|
||||||
|
BWPanel.createOrShow(context.extensionUri, context);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('bwcontest.askQuestion', async () => {
|
vscode.commands.registerCommand('bwcontest.askQuestion', async () => {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getNonce } from './getNonce';
|
import { getNonce } from './getNonce';
|
||||||
|
import { runJava } from './run/java';
|
||||||
|
import { extensionSettings } from './extension';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export class BWPanel {
|
export class BWPanel {
|
||||||
/**
|
/**
|
||||||
@ -12,8 +15,10 @@ export class BWPanel {
|
|||||||
private readonly _panel: vscode.WebviewPanel;
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
private readonly _extensionUri: vscode.Uri;
|
private readonly _extensionUri: vscode.Uri;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
private static _context?: vscode.ExtensionContext;
|
||||||
|
|
||||||
public static createOrShow(extensionUri: vscode.Uri) {
|
public static createOrShow(extensionUri: vscode.Uri, context: vscode.ExtensionContext) {
|
||||||
|
this._context = context;
|
||||||
const column = vscode.window.activeTextEditor
|
const column = vscode.window.activeTextEditor
|
||||||
? vscode.window.activeTextEditor.viewColumn
|
? vscode.window.activeTextEditor.viewColumn
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -34,6 +39,8 @@ export class BWPanel {
|
|||||||
// Enable javascript in the webview
|
// Enable javascript in the webview
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
|
|
||||||
|
retainContextWhenHidden: true,
|
||||||
|
|
||||||
// And restrict the webview to only loading content from our extension's `media` directory.
|
// And restrict the webview to only loading content from our extension's `media` directory.
|
||||||
localResourceRoots: [
|
localResourceRoots: [
|
||||||
vscode.Uri.joinPath(extensionUri, 'media'),
|
vscode.Uri.joinPath(extensionUri, 'media'),
|
||||||
@ -99,6 +106,44 @@ export class BWPanel {
|
|||||||
this._panel.webview.html = this._getHtmlForWebview(webview);
|
this._panel.webview.html = this._getHtmlForWebview(webview);
|
||||||
webview.onDidReceiveMessage(async (data) => {
|
webview.onDidReceiveMessage(async (data) => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
case 'onRun': {
|
||||||
|
if (!data.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const repoDir = extensionSettings().repoClonePath;
|
||||||
|
const output = 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
|
||||||
|
);
|
||||||
|
this._panel.webview.postMessage({ type: 'onOutput', value: output });
|
||||||
|
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': {
|
case 'onInfo': {
|
||||||
if (!data.value) {
|
if (!data.value) {
|
||||||
return;
|
return;
|
||||||
@ -125,7 +170,7 @@ export class BWPanel {
|
|||||||
private _getHtmlForWebview(webview: vscode.Webview) {
|
private _getHtmlForWebview(webview: vscode.Webview) {
|
||||||
// // And the uri we use to load this script in the webview
|
// // And the uri we use to load this script in the webview
|
||||||
const scriptUri = webview.asWebviewUri(
|
const scriptUri = webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'HelloWorld.js')
|
vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.js')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Uri to load styles into webview
|
// Uri to load styles into webview
|
||||||
@ -135,9 +180,9 @@ export class BWPanel {
|
|||||||
const stylesMainUri = webview.asWebviewUri(
|
const stylesMainUri = webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
|
||||||
);
|
);
|
||||||
// const cssUri = webview.asWebviewUri(
|
const cssUri = webview.asWebviewUri(
|
||||||
// vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/swiper.css')
|
vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.css')
|
||||||
// );
|
);
|
||||||
|
|
||||||
// // Use a nonce to only allow specific scripts to be run
|
// // Use a nonce to only allow specific scripts to be run
|
||||||
const nonce = getNonce();
|
const nonce = getNonce();
|
||||||
@ -154,6 +199,10 @@ export class BWPanel {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="${stylesResetUri}" rel="stylesheet">
|
<link href="${stylesResetUri}" rel="stylesheet">
|
||||||
<link href="${stylesMainUri}" rel="stylesheet">
|
<link href="${stylesMainUri}" rel="stylesheet">
|
||||||
|
<link href="${cssUri}" rel="stylesheet">
|
||||||
|
<script nonce="${nonce}">
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
51
extension/bwcontest/src/run/java.ts
Normal file
51
extension/bwcontest/src/run/java.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import { join } from 'path';
|
||||||
|
import os = require('os');
|
||||||
|
import { exec, spawn } from 'child_process';
|
||||||
|
import { extensionSettings } from '../extension';
|
||||||
|
import { error } from 'console';
|
||||||
|
import util = require('node:util');
|
||||||
|
|
||||||
|
const execPromise = util.promisify(exec);
|
||||||
|
|
||||||
|
export async function runJava(
|
||||||
|
srcDir: string,
|
||||||
|
mainFile: string,
|
||||||
|
mainClass: string,
|
||||||
|
input: string
|
||||||
|
): Promise<string> {
|
||||||
|
const javaPath = extensionSettings().javaPath;
|
||||||
|
if (javaPath == '') {
|
||||||
|
throw error('Java path not set');
|
||||||
|
}
|
||||||
|
const tempDir = os.tmpdir();
|
||||||
|
const buildDir = join(tempDir, 'bwcontest_java');
|
||||||
|
if (fs.existsSync(buildDir)) {
|
||||||
|
fs.removeSync(buildDir);
|
||||||
|
}
|
||||||
|
fs.mkdirSync(buildDir);
|
||||||
|
|
||||||
|
const compileCommand = `${join(javaPath, 'javac')} -cp ${srcDir} ${mainFile} -d ${buildDir}`;
|
||||||
|
await execPromise(compileCommand);
|
||||||
|
|
||||||
|
const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let outputBuffer = '';
|
||||||
|
const child = spawn(runCommand, { shell: true });
|
||||||
|
child.stdout.setEncoding('utf8');
|
||||||
|
child.stdout.on('data', (data) => {
|
||||||
|
outputBuffer += data.toString();
|
||||||
|
});
|
||||||
|
child.stderr.setEncoding('utf8');
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
outputBuffer += data.toString();
|
||||||
|
});
|
||||||
|
child.stdin.write(input);
|
||||||
|
child.stdin.end();
|
||||||
|
|
||||||
|
child.on('close', () => {
|
||||||
|
resolve(outputBuffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Test!</h1>
|
|
172
extension/bwcontest/webviews/components/ProblemPanel.svelte
Normal file
172
extension/bwcontest/webviews/components/ProblemPanel.svelte
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
function postMessage(message: any) {
|
||||||
|
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 activeProblem: ProblemData[0];
|
||||||
|
let sessionToken: string | undefined;
|
||||||
|
let problemData: ProblemData | undefined;
|
||||||
|
|
||||||
|
let sampleInputText: HTMLTextAreaElement;
|
||||||
|
let outputText: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
let running = false;
|
||||||
|
|
||||||
|
$: if (problemData && problemData.length !== 0) {
|
||||||
|
let first = problemData.at(0);
|
||||||
|
if (first) {
|
||||||
|
activeProblem = first;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetInput() {
|
||||||
|
sampleInputText.value = activeProblem.sampleInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProblemData() {
|
||||||
|
if (sessionToken) {
|
||||||
|
const res = await fetch(`http://localhost:5173/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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
postMessage({type: "onStartup"});
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Test & Submit Problems</h1>
|
||||||
|
|
||||||
|
{#if problemData}
|
||||||
|
<div class="tab-container">
|
||||||
|
{#each problemData as problem}
|
||||||
|
<button on:click={() => {
|
||||||
|
if (!running) {
|
||||||
|
savedInputs.set(activeProblem.id, {input: sampleInputText.value, output: outputText.value});
|
||||||
|
activeProblem = problem;
|
||||||
|
updateTextBoxes();
|
||||||
|
}
|
||||||
|
}} id={`problem_${problem.id}`} type="button" class={"tab " + (activeProblem.id == problem.id ? "active" : "")}>{problem.name}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeProblem}
|
||||||
|
<h2>{activeProblem.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} />
|
||||||
|
<button style="margin-top:5px" on:click={resetInput} type="button">Reset Input</button>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1">
|
||||||
|
<div style="display:flex">
|
||||||
|
<h3 style="margin-right:5px">Output</h3>
|
||||||
|
{#if running}
|
||||||
|
<span class="loader"></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<textarea bind:this={outputText} disabled />
|
||||||
|
<button style="margin-top:5px" on:click={onRun} type="button">Run</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
background-color: rgb(95, 103, 118);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 3px solid #FFF;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: rotation 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotation {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -86,6 +86,10 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onTestAndSubmit() {
|
||||||
|
postMessage({type: 'onTestAndSubmit'});
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
postMessage({type: "onStartup"});
|
postMessage({type: "onStartup"});
|
||||||
})
|
})
|
||||||
@ -107,5 +111,6 @@
|
|||||||
<p>TeamID: {teamData.teamId}</p>
|
<p>TeamID: {teamData.teamId}</p>
|
||||||
<p>ContestID: {teamData.contestId}</p>
|
<p>ContestID: {teamData.contestId}</p>
|
||||||
<button on:click={onClone}>Clone and Open Repo</button>
|
<button on:click={onClone}>Clone and Open Repo</button>
|
||||||
|
<button on:click={onTestAndSubmit}>Test & Submit</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
@ -1,4 +1,4 @@
|
|||||||
import App from '../components/HelloWorld.svelte';
|
import App from '../components/ProblemPanel.svelte';
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.body
|
target: document.body
|
Loading…
Reference in New Issue
Block a user