Login with extension
This commit is contained in:
parent
093ff3af56
commit
d43ffb680a
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"pluginSearchDirs": ["."]
|
"pluginSearchDirs": ["."]
|
||||||
}
|
}
|
||||||
|
2
extension/bwcontest/.vscode/extensions.json
vendored
2
extension/bwcontest/.vscode/extensions.json
vendored
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
|
||||||
// for the documentation about the extensions.json format
|
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"dbaeumer.vscode-eslint"
|
"dbaeumer.vscode-eslint"
|
||||||
]
|
]
|
||||||
|
18
extension/bwcontest/.vscode/launch.json
vendored
18
extension/bwcontest/.vscode/launch.json
vendored
@ -1,7 +1,3 @@
|
|||||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
{
|
{
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
@ -15,20 +11,6 @@
|
|||||||
"outFiles": [
|
"outFiles": [
|
||||||
"${workspaceFolder}/out/**/*.js"
|
"${workspaceFolder}/out/**/*.js"
|
||||||
],
|
],
|
||||||
"preLaunchTask": "${defaultBuildTask}"
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Extension Tests",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"args": [
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
|
||||||
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
|
|
||||||
],
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/out/test/**/*.js"
|
|
||||||
],
|
|
||||||
"preLaunchTask": "${defaultBuildTask}"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
6
extension/bwcontest/.vscode/settings.json
vendored
6
extension/bwcontest/.vscode/settings.json
vendored
@ -1,11 +1,9 @@
|
|||||||
// Place your settings in this file to overwrite default and user settings.
|
|
||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
"out": false
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"out": true // set this to false to include "out" folder in search results
|
"out": true
|
||||||
},
|
},
|
||||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
|
||||||
"typescript.tsc.autoDetect": "off"
|
"typescript.tsc.autoDetect": "off"
|
||||||
}
|
}
|
3
extension/bwcontest/.vscode/tasks.json
vendored
3
extension/bwcontest/.vscode/tasks.json
vendored
@ -1,12 +1,9 @@
|
|||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
// for the documentation about the tasks.json format
|
|
||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "watch",
|
"script": "watch",
|
||||||
"problemMatcher": "$tsc-watch",
|
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "never"
|
"reveal": "never"
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to the "bwcontest" extension will be documented in this file.
|
|
||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
- Initial release
|
|
BIN
extension/bwcontest/media/icon.png
Normal file
BIN
extension/bwcontest/media/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
30
extension/bwcontest/media/reset.css
Normal file
30
extension/bwcontest/media/reset.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
91
extension/bwcontest/media/vscode.css
Normal file
91
extension/bwcontest/media/vscode.css
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
:root {
|
||||||
|
--container-padding: 20px;
|
||||||
|
--input-padding-vertical: 6px;
|
||||||
|
--input-padding-horizontal: 4px;
|
||||||
|
--input-margin-vertical: 4px;
|
||||||
|
--input-margin-horizontal: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 0 var(--container-padding);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
|
font-weight: var(--vscode-font-weight);
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
background-color: var(--vscode-editor-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-left: var(--container-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
body > *,
|
||||||
|
form > * {
|
||||||
|
margin-block-start: var(--input-margin-vertical);
|
||||||
|
margin-block-end: var(--input-margin-vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
*:focus {
|
||||||
|
outline-color: var(--vscode-focusBorder) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:active {
|
||||||
|
color: var(--vscode-textLink-activeForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: var(--vscode-editor-font-size);
|
||||||
|
font-family: var(--vscode-editor-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
outline: 1px solid transparent;
|
||||||
|
outline-offset: 2px !important;
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover {
|
||||||
|
background: var(--vscode-button-secondaryHoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type='checkbox']),
|
||||||
|
textarea {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
outline-color: var(--vscode-input-border);
|
||||||
|
background-color: var(--vscode-input-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: var(--vscode-input-placeholderForeground);
|
||||||
|
}
|
7048
extension/bwcontest/package-lock.json
generated
7048
extension/bwcontest/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,36 +12,74 @@
|
|||||||
"activationEvents": [],
|
"activationEvents": [],
|
||||||
"main": "./out/extension.js",
|
"main": "./out/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
|
"viewsContainers": {
|
||||||
|
"activitybar": [
|
||||||
|
{
|
||||||
|
"id": "bwcontest-sidebar-view",
|
||||||
|
"title": "BWContest",
|
||||||
|
"icon": "media/icon.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"bwcontest-sidebar-view": [
|
||||||
|
{
|
||||||
|
"type": "webview",
|
||||||
|
"id": "bwcontest-sidebar",
|
||||||
|
"name": "BWContest",
|
||||||
|
"icon": "media/icon.png",
|
||||||
|
"contextualTitle": "BWContest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "bwcontest.helloWorld",
|
"command": "bwcontest.helloWorld",
|
||||||
|
"category": "BWContest",
|
||||||
"title": "Hello World"
|
"title": "Hello World"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "bwcontest.testThing",
|
"command": "bwcontest.askQuestion",
|
||||||
"title": "Test Thing"
|
"category": "BWContest",
|
||||||
|
"title": "Ask Question"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "npm run compile",
|
"vscode:prepublish": "npm run compile",
|
||||||
"compile": "tsc -p ./",
|
"compile": "rollup -c && tsc -p ./",
|
||||||
"watch": "tsc -watch -p ./",
|
"watch": "concurrently \"rollup -c -w\" \"tsc -watch -p ./\"",
|
||||||
"pretest": "npm run compile && npm run lint",
|
"pretest": "npm run compile && npm run lint",
|
||||||
"lint": "eslint src --ext ts",
|
"lint": "eslint src --ext ts",
|
||||||
"test": "node ./out/test/runTest.js"
|
"test": "node ./out/test/runTest.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/vscode": "^1.78.0",
|
"@rollup/plugin-commonjs": "^24.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||||
|
"@rollup/plugin-typescript": "^11.1.0",
|
||||||
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node": "16.x",
|
"@types/node": "16.x",
|
||||||
|
"@types/vscode": "^1.78.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||||
"@typescript-eslint/parser": "^5.59.1",
|
"@typescript-eslint/parser": "^5.59.1",
|
||||||
|
"@vscode/test-electron": "^2.3.0",
|
||||||
|
"concurrently": "^8.0.1",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
"glob": "^8.1.0",
|
"glob": "^8.1.0",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"typescript": "^5.0.4",
|
"postcss": "^8.4.23",
|
||||||
"@vscode/test-electron": "^2.3.0"
|
"rollup-plugin-css-only": "^4.3.0",
|
||||||
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"rollup-plugin-svelte": "^7.1.4",
|
||||||
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"svelte": "^3.59.0",
|
||||||
|
"svelte-check": "^3.3.1",
|
||||||
|
"svelte-preprocess": "^5.0.3",
|
||||||
|
"typescript": "^5.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
extension/bwcontest/rollup.config.js
Normal file
49
extension/bwcontest/rollup.config.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import svelte from "rollup-plugin-svelte";
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import { terser } from "rollup-plugin-terser";
|
||||||
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
|
import typescript from "@rollup/plugin-typescript";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import css from "rollup-plugin-css-only";
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default fs
|
||||||
|
.readdirSync(path.join(__dirname, "webviews", "pages"))
|
||||||
|
.map((input) => {
|
||||||
|
const name = input.split(".")[0];
|
||||||
|
return {
|
||||||
|
input: "webviews/pages/" + input,
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: "iife",
|
||||||
|
name: "app",
|
||||||
|
file: "out/compiled/" + name + ".js",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
dev: !production,
|
||||||
|
preprocess: sveltePreprocess(),
|
||||||
|
emitCss: true
|
||||||
|
}),
|
||||||
|
css({ output: name + ".css" }),
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ["svelte"],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
typescript({
|
||||||
|
tsconfig: "webviews/tsconfig.json",
|
||||||
|
sourceMap: !production,
|
||||||
|
inlineSources: !production,
|
||||||
|
}),
|
||||||
|
production && terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
164
extension/bwcontest/src/BWPanel.ts
Normal file
164
extension/bwcontest/src/BWPanel.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { getNonce } from './getNonce';
|
||||||
|
|
||||||
|
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 readonly _panel: vscode.WebviewPanel;
|
||||||
|
private readonly _extensionUri: vscode.Uri;
|
||||||
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
|
public static createOrShow(extensionUri: vscode.Uri) {
|
||||||
|
const column = vscode.window.activeTextEditor
|
||||||
|
? vscode.window.activeTextEditor.viewColumn
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// If we already have a panel, show it.
|
||||||
|
if (BWPanel.currentPanel) {
|
||||||
|
BWPanel.currentPanel._panel.reveal(column);
|
||||||
|
BWPanel.currentPanel._update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create a new panel.
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
BWPanel.viewType,
|
||||||
|
'VSinder',
|
||||||
|
column || vscode.ViewColumn.One,
|
||||||
|
{
|
||||||
|
// Enable javascript in the webview
|
||||||
|
enableScripts: true,
|
||||||
|
|
||||||
|
// And restrict the webview to only loading content from our extension's `media` directory.
|
||||||
|
localResourceRoots: [
|
||||||
|
vscode.Uri.joinPath(extensionUri, 'media'),
|
||||||
|
vscode.Uri.joinPath(extensionUri, 'out/compiled')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BWPanel.currentPanel = new BWPanel(panel, extensionUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static kill() {
|
||||||
|
BWPanel.currentPanel?.dispose();
|
||||||
|
BWPanel.currentPanel = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||||
|
BWPanel.currentPanel = new BWPanel(panel, extensionUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||||
|
this._panel = panel;
|
||||||
|
this._extensionUri = extensionUri;
|
||||||
|
|
||||||
|
// Set the webview's initial html content
|
||||||
|
this._update();
|
||||||
|
|
||||||
|
// Listen for when the panel is disposed
|
||||||
|
// This happens when the user closes the panel or when the panel is closed programatically
|
||||||
|
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||||
|
|
||||||
|
// // Handle messages from the webview
|
||||||
|
// this._panel.webview.onDidReceiveMessage(
|
||||||
|
// (message) => {
|
||||||
|
// switch (message.command) {
|
||||||
|
// case "alert":
|
||||||
|
// vscode.window.showErrorMessage(message.text);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// null,
|
||||||
|
// this._disposables
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
BWPanel.currentPanel = undefined;
|
||||||
|
|
||||||
|
// Clean up our resources
|
||||||
|
this._panel.dispose();
|
||||||
|
|
||||||
|
while (this._disposables.length) {
|
||||||
|
const x = this._disposables.pop();
|
||||||
|
if (x) {
|
||||||
|
x.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _update() {
|
||||||
|
const webview = this._panel.webview;
|
||||||
|
|
||||||
|
this._panel.webview.html = this._getHtmlForWebview(webview);
|
||||||
|
webview.onDidReceiveMessage(async (data) => {
|
||||||
|
switch (data.type) {
|
||||||
|
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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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', 'HelloWorld.js')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Uri to load styles into webview
|
||||||
|
const stylesResetUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css')
|
||||||
|
);
|
||||||
|
const stylesMainUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
|
||||||
|
);
|
||||||
|
// const cssUri = webview.asWebviewUri(
|
||||||
|
// vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/swiper.css')
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // Use a nonce to only allow specific scripts to be run
|
||||||
|
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">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<script src=${scriptUri} nonce="${nonce}">
|
||||||
|
</script>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
}
|
82
extension/bwcontest/src/SidebarProvider.ts
Normal file
82
extension/bwcontest/src/SidebarProvider.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { getNonce } from './getNonce';
|
||||||
|
|
||||||
|
export class SidebarProvider implements vscode.WebviewViewProvider {
|
||||||
|
_view?: vscode.WebviewView;
|
||||||
|
|
||||||
|
constructor(private readonly _extensionUri: vscode.Uri) {}
|
||||||
|
|
||||||
|
public resolveWebviewView(webviewView: vscode.WebviewView) {
|
||||||
|
this._view = webviewView;
|
||||||
|
|
||||||
|
webviewView.webview.options = {
|
||||||
|
// Allow scripts in the webview
|
||||||
|
enableScripts: true,
|
||||||
|
|
||||||
|
localResourceRoots: [this._extensionUri]
|
||||||
|
};
|
||||||
|
|
||||||
|
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
||||||
|
|
||||||
|
webviewView.webview.onDidReceiveMessage(async (data) => {
|
||||||
|
switch (data.type) {
|
||||||
|
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) {
|
||||||
|
const styleResetUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css')
|
||||||
|
);
|
||||||
|
const styleVSCodeUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
|
||||||
|
);
|
||||||
|
|
||||||
|
const scriptUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/sidebar.js')
|
||||||
|
);
|
||||||
|
const styleMainUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/sidebar.css')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use a nonce to only allow a specific script to be run.
|
||||||
|
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="${styleResetUri}" rel="stylesheet">
|
||||||
|
<link href="${styleVSCodeUri}" rel="stylesheet">
|
||||||
|
<link href="${styleMainUri}" rel="stylesheet">
|
||||||
|
<script nonce="${nonce}">
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,29 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { BWPanel } from './BWPanel';
|
||||||
|
import { SidebarProvider } from './SidebarProvider';
|
||||||
|
import { notDeepEqual } from 'assert';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('Congratulations, your extension "bwcontest" is now active!');
|
const sidebarProvider = new SidebarProvider(context.extensionUri);
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider)
|
||||||
|
);
|
||||||
|
|
||||||
let disposable = vscode.commands.registerCommand('bwcontest.helloWorld', () => {
|
context.subscriptions.push(
|
||||||
vscode.window.showInformationMessage('Hello World from BWContest!');
|
vscode.commands.registerCommand('bwcontest.helloWorld', () => {
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
context.subscriptions.push(disposable);
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('bwcontest.askQuestion', async () => {
|
||||||
|
const answer = await vscode.window.showInformationMessage('How was your day?', 'good', 'bad');
|
||||||
|
if (answer === 'bad') {
|
||||||
|
vscode.window.showInformationMessage('Sorry to hear that');
|
||||||
|
} else {
|
||||||
|
console.log(answer);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() {}
|
export function deactivate() {}
|
||||||
|
8
extension/bwcontest/src/getNonce.ts
Normal file
8
extension/bwcontest/src/getNonce.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export function getNonce() {
|
||||||
|
let text = '';
|
||||||
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { runTests } from '@vscode/test-electron';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
try {
|
|
||||||
// The folder containing the Extension Manifest package.json
|
|
||||||
// Passed to `--extensionDevelopmentPath`
|
|
||||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
|
||||||
|
|
||||||
// The path to test runner
|
|
||||||
// Passed to --extensionTestsPath
|
|
||||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
|
||||||
|
|
||||||
// Download VS Code, unzip it and run the integration test
|
|
||||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to run tests', err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
@ -1,15 +0,0 @@
|
|||||||
import * as assert from 'assert';
|
|
||||||
|
|
||||||
// You can import and use all API from the 'vscode' module
|
|
||||||
// as well as import your extension to test it
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
// import * as myExtension from '../../extension';
|
|
||||||
|
|
||||||
suite('Extension Test Suite', () => {
|
|
||||||
vscode.window.showInformationMessage('Start all tests.');
|
|
||||||
|
|
||||||
test('Sample test', () => {
|
|
||||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
|
||||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import * as Mocha from 'mocha';
|
|
||||||
import * as glob from 'glob';
|
|
||||||
|
|
||||||
export function run(): Promise<void> {
|
|
||||||
// Create the mocha test
|
|
||||||
const mocha = new Mocha({
|
|
||||||
ui: 'tdd',
|
|
||||||
color: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const testsRoot = path.resolve(__dirname, '..');
|
|
||||||
|
|
||||||
return new Promise((c, e) => {
|
|
||||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
return e(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add files to the test suite
|
|
||||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Run the mocha test
|
|
||||||
mocha.run(failures => {
|
|
||||||
if (failures > 0) {
|
|
||||||
e(new Error(`${failures} tests failed.`));
|
|
||||||
} else {
|
|
||||||
c();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
e(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -3,15 +3,14 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"outDir": "out",
|
"outDir": "out",
|
||||||
"lib": [
|
"lib": ["ES2020"],
|
||||||
"ES2020"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": true /* enable all strict type-checking options */
|
"strict": true /* enable all strict type-checking options */
|
||||||
/* Additional Checks */
|
/* Additional Checks */
|
||||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
}
|
},
|
||||||
|
"exclude": ["webviews"]
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Test!</h1>
|
61
extension/bwcontest/webviews/components/Sidebar.svelte
Normal file
61
extension/bwcontest/webviews/components/Sidebar.svelte
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
function postMessage(message: any) {
|
||||||
|
vscode.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
let teamname: HTMLInputElement;
|
||||||
|
let password: HTMLInputElement;
|
||||||
|
|
||||||
|
let sessionToken: string | undefined;
|
||||||
|
|
||||||
|
async function onLogin() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("http://localhost:5173/api/team/login", {method: "POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
||||||
|
teamname: teamname.value, password: password.value
|
||||||
|
})});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
postMessage({type: 'onError', value: 'Error logging in'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success === false) {
|
||||||
|
postMessage({type: 'onError', value: data.message ?? "Unknown error logging in"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sessionToken = data.token;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onLogout() {
|
||||||
|
const res = await fetch("http://localhost:5173/api/team/logout", {method: "POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
||||||
|
token: sessionToken,
|
||||||
|
})})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
postMessage({type: 'onError', value: 'Error logging out'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success === true) {
|
||||||
|
postMessage({type: 'onInfo', value: 'BWContest: Logged out'});
|
||||||
|
sessionToken = undefined;
|
||||||
|
} else {
|
||||||
|
postMessage({type: 'onError', value: 'Log out unsuccessful'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Contest</h1>
|
||||||
|
|
||||||
|
{#if sessionToken === undefined}
|
||||||
|
<label for="teamname">Team Name</label>
|
||||||
|
<input bind:this={teamname} id="teamname" type="text"/>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input bind:this={password} id="password" type="password"/>
|
||||||
|
|
||||||
|
<button on:click={onLogin}>Login</button>
|
||||||
|
{:else}
|
||||||
|
<button on:click={onLogout}>Logout</button>
|
||||||
|
{/if}
|
9
extension/bwcontest/webviews/global.d.ts
vendored
Normal file
9
extension/bwcontest/webviews/global.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
|
||||||
|
type VSCode = {
|
||||||
|
postMessage(message: any): void;
|
||||||
|
getState(): any;
|
||||||
|
setState(state: any): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const vscode: VSCode;
|
7
extension/bwcontest/webviews/pages/HelloWorld.ts
Normal file
7
extension/bwcontest/webviews/pages/HelloWorld.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import App from '../components/HelloWorld.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
7
extension/bwcontest/webviews/pages/sidebar.ts
Normal file
7
extension/bwcontest/webviews/pages/sidebar.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import App from '../components/Sidebar.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
6
extension/bwcontest/webviews/tsconfig.json
Normal file
6
extension/bwcontest/webviews/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"include": ["./**/*"],
|
||||||
|
"exclude": ["../node_modules/*"],
|
||||||
|
"compilerOptions": { "strict": true }
|
||||||
|
}
|
@ -56,7 +56,16 @@ model Team {
|
|||||||
name String @unique
|
name String @unique
|
||||||
Submission Submission[]
|
Submission Submission[]
|
||||||
contests Contest[] @relation("TeamContestRelation")
|
contests Contest[] @relation("TeamContestRelation")
|
||||||
password String
|
password String
|
||||||
|
activeTeam ActiveTeam?
|
||||||
|
}
|
||||||
|
|
||||||
|
model ActiveTeam {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
teamId Int @unique
|
||||||
|
team Team @relation(fields: [teamId], references: [id])
|
||||||
|
sessionToken String? @unique
|
||||||
|
sessionCreatedAt DateTime?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Contest {
|
model Contest {
|
||||||
|
@ -21,6 +21,17 @@ async function removeExpiredSessions(userId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const handle = (async ({ event, resolve }) => {
|
export const handle = (async ({ event, resolve }) => {
|
||||||
|
if (event.request.method === 'OPTIONS') {
|
||||||
|
return new Response('ok', {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Credentials': 'true',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (event.url.pathname.startsWith('/login')) {
|
if (event.url.pathname.startsWith('/login')) {
|
||||||
if (event.cookies.get('token')) {
|
if (event.cookies.get('token')) {
|
||||||
const session = await db.session.findUnique({ where: { token: event.cookies.get('token') } });
|
const session = await db.session.findUnique({ where: { token: event.cookies.get('token') } });
|
||||||
|
31
web/src/routes/api/team/login/+server.ts
Normal file
31
web/src/routes/api/team/login/+server.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { db } from '$lib/server/prisma';
|
||||||
|
import * as UUID from 'uuid';
|
||||||
|
|
||||||
|
const loginPostData = z
|
||||||
|
.object({
|
||||||
|
teamname: z.string(),
|
||||||
|
password: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const POST = (async ({ request }) => {
|
||||||
|
const data = loginPostData.safeParse(await request.json());
|
||||||
|
if (!data.success) {
|
||||||
|
throw error(400);
|
||||||
|
}
|
||||||
|
const team = await db.team.findUnique({
|
||||||
|
where: { name: data.data.teamname },
|
||||||
|
include: { activeTeam: true }
|
||||||
|
});
|
||||||
|
if (!team || !team.activeTeam || team.password !== data.data.password) {
|
||||||
|
return json({ success: false, message: 'Invalid login' });
|
||||||
|
}
|
||||||
|
const activeTeam = await db.activeTeam.update({
|
||||||
|
where: { id: team.activeTeam.id },
|
||||||
|
data: { sessionToken: UUID.v4(), sessionCreatedAt: new Date() }
|
||||||
|
});
|
||||||
|
return json({ success: true, token: activeTeam.sessionToken });
|
||||||
|
}) satisfies RequestHandler;
|
22
web/src/routes/api/team/logout/+server.ts
Normal file
22
web/src/routes/api/team/logout/+server.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { db } from '$lib/server/prisma';
|
||||||
|
|
||||||
|
const logoutPostData = z
|
||||||
|
.object({
|
||||||
|
token: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export const POST = (async ({ request }) => {
|
||||||
|
const data = logoutPostData.safeParse(await request.json());
|
||||||
|
if (!data.success) {
|
||||||
|
throw error(400);
|
||||||
|
}
|
||||||
|
await db.activeTeam.update({
|
||||||
|
where: { sessionToken: data.data.token },
|
||||||
|
data: { sessionToken: null, sessionCreatedAt: null }
|
||||||
|
});
|
||||||
|
return json({ success: true });
|
||||||
|
}) satisfies RequestHandler;
|
Loading…
Reference in New Issue
Block a user