feat: Allow the plugin to work under the browser context

Signed-off-by: Avior <f.bouillon@aptatio.com>
This commit is contained in:
Florian Bouillon 2023-01-20 12:14:00 +01:00
parent e42e5271db
commit cef14d1038
Signed by: Florian Bouillon
GPG Key ID: E05B3A94178D3A7C
10 changed files with 3603 additions and 233 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.DS_Store
out
node_modules
.vscode-test/
.vscode-test*/
*.vsix

View File

@ -6,5 +6,8 @@
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
// username for internal browser tests
"codestats.username": "Aviortheking"
}

14
esbuild.js Normal file
View File

@ -0,0 +1,14 @@
const { NodeModulesPolyfillPlugin } = require('@esbuild-plugins/node-modules-polyfill')
const { build } = require('esbuild')
build({
entryPoints: ['./src/code-stats.ts'],
plugins: [NodeModulesPolyfillPlugin()],
bundle: true,
minify: true,
sourcemap: true,
target: ['es2016', 'chrome90', 'firefox78', 'safari14', 'edge90'],
external: ['vscode', 'node-fetch'],
outfile: 'out/browser.js',
format: 'cjs',
platform: 'node'
})

3268
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
"*"
],
"main": "./out/src/code-stats",
"browser": "./out/browser",
"extensionKind": [
"ui",
"workspace"
@ -66,19 +67,25 @@
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./"
"compile": "tsc -watch -p ./",
"test:browser": "vscode-test-web --extensionDevelopmentPath=. .",
"compile:browser": "node esbuild.js"
},
"devDependencies": {
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
"@types/mocha": "^7.0.2",
"@types/node": "^13.9.8",
"@types/node-fetch": "^2.6.2",
"@types/vscode": "^1.43.2",
"@vscode/test-web": "^0.0.34",
"esbuild": "^0.17.3",
"growl": "^1.10.5",
"mocha": "^7.1.1",
"typescript": "^3.8.3",
"vscode-test": "^1.5.2"
},
"dependencies": {
"axios": "^0.19.2",
"lodash.template": "^4.5.0"
"lodash.template": "^4.5.0",
"node-fetch": "^2.6.8"
}
}

View File

@ -1,13 +1,24 @@
import { Pulse } from "./pulse";
import { getISOTimestamp, getLanguageName } from "./utils";
import * as axios from "axios";
import type fetch from 'node-fetch'
import { Pulse } from "./pulse"
import { getISOTimestamp, getLanguageName } from "./utils"
// Please do not look at this
let realFetch: typeof fetch
// @ts-expect-error VSCode is not including fetch everytime
if (typeof fetch === 'undefined') {
realFetch = require('node-fetch')
} else {
// @ts-expect-error VSCode is not including fetch everytime
realFetch = fetch
}
export class CodeStatsAPI {
private API_KEY = null;
private USER_NAME = null;
private UPDATE_URL = "https://codestats.net/api/";
private axios = null;
private UPDATE_URL = "https://codestats.net/api";
private headers: Record<string, string> = {
"Content-Type": "application/json"
}
constructor(apiKey: string, apiURL: string, userName: string) {
this.updateSettings(apiKey, apiURL, userName);
@ -27,19 +38,12 @@ export class CodeStatsAPI {
return;
}
this.axios = axios.default.create({
baseURL: this.UPDATE_URL,
timeout: 10000,
headers: {
"X-API-Token": this.API_KEY,
"Content-Type": "application/json"
}
});
this.headers["X-API-Token"] = this.API_KEY
}
public sendUpdate(pulse: Pulse): axios.AxiosPromise {
public async sendUpdate(pulse: Pulse): Promise<any> {
// If we did not have API key, don't try to update
if (this.axios === null) {
if (this.API_KEY === null) {
return null;
}
@ -55,29 +59,28 @@ export class CodeStatsAPI {
let json: string = JSON.stringify(data);
console.log(`JSON: ${json}`);
return this.axios
.post("my/pulses", json)
.then(response => {
console.log(response);
try {
const response = await realFetch(`${this.UPDATE_URL}/my/pulses`, {
method: 'POST',
body: json,
headers: this.headers
})
.then(() => {
pulse.reset();
})
.catch(error => {
console.log(error);
});
console.log(response)
pulse.reset()
} catch (error) {
console.log(error)
}
}
public getProfile(): axios.AxiosPromise {
return this.axios
.get(`users/${this.USER_NAME}`)
.then(response => {
return response.data;
})
.catch(error => {
console.log(error);
return null;
});
public async getProfile(): Promise<any> {
try {
const resp = await realFetch(`${this.UPDATE_URL}/users/${this.USER_NAME}`)
const response = await resp.json()
return response
} catch (error) {
console.log(error)
return null
}
}
}

View File

@ -1,6 +1,5 @@
"use strict";
import { ExtensionContext } from "vscode";
import { XpCounter } from "./xp-counter";
import { ExtensionContext } from "vscode"
import { XpCounter } from "./xp-counter"
export function activate(context: ExtensionContext): void {
let controller: XpCounter = new XpCounter(context);

View File

@ -1,8 +1,7 @@
import * as fs from 'fs';
import { CancellationToken, Event, ExtensionContext, TextDocumentContentProvider, Uri } from "vscode";
import { CodeStatsAPI } from "./code-stats-api";
import * as path from 'path';
import { CancellationToken, Event, ExtensionContext, TextDocumentContentProvider, Uri, workspace } from "vscode"
import { CodeStatsAPI } from "./code-stats-api"
import profileHtmlEex from './profile.html.eex'
import template = require('lodash.template');
@ -19,8 +18,7 @@ export class ProfileProvider implements TextDocumentContentProvider {
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
if( token.isCancellationRequested )
return;
if (token.isCancellationRequested) return;
const LEVEL_FACTOR = 0.025;
@ -47,10 +45,9 @@ export class ProfileProvider implements TextDocumentContentProvider {
}
function getSortedArray(obj: any): any[] {
let items = [];
for( let prop in obj) {
for (const prop in obj) {
let item = obj[prop];
let percents = getLevelProgress(item.xps, item.new_xps);
items.push(
@ -68,14 +65,15 @@ export class ProfileProvider implements TextDocumentContentProvider {
return items.sort( (a,b) => {return b.xp - a.xp;});
}
return this.api.getProfile().then(profile => {
return this.api.getProfile().then(async (profile) => {
if( profile === null )
{
return `<h1>Can't fetch profile. Please try again later</h1> Make sure <strong>codestats.username</strong> setting is set to correct user name.`;
}
let htmlTemplate = fs.readFileSync(this.context.asAbsolutePath("assets/profile.html.eex"));
// don't look at this file please
let htmlTemplate = profileHtmlEex
profile["level"] = getLevel(profile["total_xp"]);
@ -89,10 +87,7 @@ export class ProfileProvider implements TextDocumentContentProvider {
let html = template(htmlTemplate);
const stylePath = Uri.file(path.join(this.context.extensionPath, 'assets', 'profile.css'));
const styleSrc = stylePath.with({scheme: 'vscode-resource'});
return html({profile: profile, languages: languages, machines: machines, style: styleSrc});
return html({profile: profile, languages: languages, machines: machines});
});
}

191
src/profile.html.eex.ts Normal file
View File

@ -0,0 +1,191 @@
export default `
<style>
/* Dark theme color palette */
.vscode-dark {
--color-language: #dbd5b9;
--color-primary: #E3C23D;
--color-secondary: #5D5535;
--color-bar-background: #303030;
--color-bar-border: #424242;
}
/* Light theme color palette */
.vscode-light {
--color-language: #1B2334;
--color-primary: #5D78B3;
--color-secondary: #354567;
--color-bar-background: #939DB3;
--color-bar-border: #697080;
}
.profile {
color: var(--color-language);
}
h3 {
text-align: center;
}
sup {
top: -.5em;
font-size: 75%;
color: var(--color-primary);
}
.language-progress {
float: left;
position: relative;
width: 6rem;
height: 6rem;
}
.language-progress .tooltiptext {
visibility: hidden;
width: 120px;
background-color: var(--color-bar-border);
color: var(--color-language);
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
}
.language-progress:hover .tooltiptext {
visibility: visible;
}
.language-progress svg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transform: rotate(-90deg);
}
circle {
stroke-width: 5;
fill:transparent;
}
circle.backg {
stroke: var(--color-bar-background);
}
circle.newxp {
stroke: var(--color-primary);
}
circle.oldxp {
stroke: var(--color-secondary);
}
.language {
padding-top: 2rem;
margin: auto;
text-align: center;
color: var(--color-language);
}
.language span {
display: block;
font-weight: bold;
font-size: 1.1em;
}
.machines {
clear: both;
margin-top: 1rem;
padding-top: 1rem;
}
.machine {
float: none;
}
.progress {
height: 20px;
margin-bottom: 20px;
background-color: var(--color-bar-background);
border-radius: 4px;
border: solid 1px;
border-color: var(--color-bar-border);
}
.progress-bar {
float: left;
width: 0%;
height: 100%;
font-size: 12px;
line-height: 20px;
text-align: center;
background-color: var(--color-secondary);
}
.progress-bar-new {
background-color: var(--color-primary);
}
</style>
<div class="profile">
<h3> \${profile.user} <sup>\${profile.level}</sup> \${profile.total_xp} xp
<% if( profile.new_xp > 0 ) { %>
<sup>+<%= profile.new_xp %></sup>
<% } %>
</h3>
<div class="progress">
<div class="progress-bar" style='width:\${profile.progress}%;'>
</div>
<div class="progress-bar progress-bar-new" style='width:\${profile.new_progress}%;'>
</div>
</div>
<div class="languages">
<% for( let l in languages) { %>
<div class="language-progress">
<span class="tooltiptext">
<strong><%=languages[l].xp %> xp</strong>
<% if( languages[l].new_xp > 0 ) { %>
<sup>+<%= languages[l].new_xp %></sup>
<% } %>
</span>
<svg viewBox="0 0 100 100">
<circle class="backg" cx="50" cy="50" r="45" ></circle>
<circle class="newxp" cx="50" cy="50" r="45" stroke-dasharray="282.6" stroke-dashoffset='\${ ((100-languages[l].progress) * 282.6 / 100) }'></circle>
<circle class="oldxp" cx="50" cy="50" r="45" stroke-dasharray="282.6" stroke-dashoffset='\${ ((100-languages[l].progress + languages[l].new_progress) * 282.6 / 100) }'></circle>
</svg>
<div class="language">
<%= languages[l].name %>
<span>
<%= languages[l].level %>
</span>
</div>
</div>
<% } %>
</div>
<p/>
<div class="machines">
<% for( let m in machines) { %>
<div class="machine">
<strong>
<%= machines[m].name %>
</strong>
<sup> <%= machines[m].level %> </sup>
<%= machines[m].xp %> xp
<% if( machines[m].new_xp > 0 ) { %>
<sup>+<%= machines[m].new_xp %></sup>
<% } %>
<div class="progress">
<div class="progress-bar" style='width:\${machines[m].progress}%;'>
</div>
<div class="progress-bar progress-bar-new" style='width:\${machines[m].new_progress}%;'>
</div>
</div>
</div>
<% } %>
</div>
<div>`

View File

@ -1,22 +1,12 @@
import * as path from 'path'
import {
Disposable,
workspace,
window,
Uri,
ViewColumn,
commands,
StatusBarItem,
TextDocument,
StatusBarAlignment,
TextDocumentChangeEvent,
WorkspaceConfiguration,
ExtensionContext
} from "vscode";
import { Pulse } from "./pulse";
import { CodeStatsAPI } from "./code-stats-api";
import { ProfileProvider } from "./profile-provider";
import * as path from 'path';
commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem,
TextDocument, TextDocumentChangeEvent, Uri,
ViewColumn, window, workspace, WorkspaceConfiguration
} from "vscode"
import { CodeStatsAPI } from "./code-stats-api"
import { ProfileProvider } from "./profile-provider"
import { Pulse } from "./pulse"
export class XpCounter {
private combinedDisposable: Disposable;
private statusBarItem: StatusBarItem;