mirror of
https://gitlab.com/aviortheking/code-stats-vscode.git
synced 2025-06-07 15:59:54 +00:00
feat: Allow the plugin to work under the browser context
Signed-off-by: Avior <f.bouillon@aptatio.com>
This commit is contained in:
parent
e42e5271db
commit
cef14d1038
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
out
|
out
|
||||||
node_modules
|
node_modules
|
||||||
.vscode-test/
|
.vscode-test*/
|
||||||
*.vsix
|
*.vsix
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -6,5 +6,8 @@
|
|||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"out": true // set this to false to include "out" folder in search results
|
"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
14
esbuild.js
Normal 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
3268
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -31,6 +31,7 @@
|
|||||||
"*"
|
"*"
|
||||||
],
|
],
|
||||||
"main": "./out/src/code-stats",
|
"main": "./out/src/code-stats",
|
||||||
|
"browser": "./out/browser",
|
||||||
"extensionKind": [
|
"extensionKind": [
|
||||||
"ui",
|
"ui",
|
||||||
"workspace"
|
"workspace"
|
||||||
@ -66,19 +67,25 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "tsc -p ./",
|
"vscode:prepublish": "tsc -p ./",
|
||||||
"compile": "tsc -watch -p ./"
|
"compile": "tsc -watch -p ./",
|
||||||
|
"test:browser": "vscode-test-web --extensionDevelopmentPath=. .",
|
||||||
|
"compile:browser": "node esbuild.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
||||||
"@types/mocha": "^7.0.2",
|
"@types/mocha": "^7.0.2",
|
||||||
"@types/node": "^13.9.8",
|
"@types/node": "^13.9.8",
|
||||||
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/vscode": "^1.43.2",
|
"@types/vscode": "^1.43.2",
|
||||||
|
"@vscode/test-web": "^0.0.34",
|
||||||
|
"esbuild": "^0.17.3",
|
||||||
"growl": "^1.10.5",
|
"growl": "^1.10.5",
|
||||||
"mocha": "^7.1.1",
|
"mocha": "^7.1.1",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"vscode-test": "^1.5.2"
|
"vscode-test": "^1.5.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.2",
|
"lodash.template": "^4.5.0",
|
||||||
"lodash.template": "^4.5.0"
|
"node-fetch": "^2.6.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
import { Pulse } from "./pulse";
|
import type fetch from 'node-fetch'
|
||||||
import { getISOTimestamp, getLanguageName } from "./utils";
|
import { Pulse } from "./pulse"
|
||||||
import * as axios from "axios";
|
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 {
|
export class CodeStatsAPI {
|
||||||
private API_KEY = null;
|
private API_KEY = null;
|
||||||
private USER_NAME = null;
|
private USER_NAME = null;
|
||||||
private UPDATE_URL = "https://codestats.net/api/";
|
private UPDATE_URL = "https://codestats.net/api";
|
||||||
|
private headers: Record<string, string> = {
|
||||||
private axios = null;
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
constructor(apiKey: string, apiURL: string, userName: string) {
|
constructor(apiKey: string, apiURL: string, userName: string) {
|
||||||
this.updateSettings(apiKey, apiURL, userName);
|
this.updateSettings(apiKey, apiURL, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateSettings( apiKey: string, apiURL: string, userName: string) {
|
public updateSettings(apiKey: string, apiURL: string, userName: string) {
|
||||||
|
|
||||||
this.API_KEY = apiKey;
|
this.API_KEY = apiKey;
|
||||||
this.UPDATE_URL = apiURL;
|
this.UPDATE_URL = apiURL;
|
||||||
@ -27,19 +38,12 @@ export class CodeStatsAPI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.axios = axios.default.create({
|
this.headers["X-API-Token"] = this.API_KEY
|
||||||
baseURL: this.UPDATE_URL,
|
|
||||||
timeout: 10000,
|
|
||||||
headers: {
|
|
||||||
"X-API-Token": this.API_KEY,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 we did not have API key, don't try to update
|
||||||
if (this.axios === null) {
|
if (this.API_KEY === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,29 +59,28 @@ export class CodeStatsAPI {
|
|||||||
let json: string = JSON.stringify(data);
|
let json: string = JSON.stringify(data);
|
||||||
console.log(`JSON: ${json}`);
|
console.log(`JSON: ${json}`);
|
||||||
|
|
||||||
return this.axios
|
try {
|
||||||
.post("my/pulses", json)
|
const response = await realFetch(`${this.UPDATE_URL}/my/pulses`, {
|
||||||
.then(response => {
|
method: 'POST',
|
||||||
console.log(response);
|
body: json,
|
||||||
|
headers: this.headers
|
||||||
})
|
})
|
||||||
.then(() => {
|
console.log(response)
|
||||||
pulse.reset();
|
pulse.reset()
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
console.log(error)
|
||||||
console.log(error);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProfile(): axios.AxiosPromise {
|
public async getProfile(): Promise<any> {
|
||||||
return this.axios
|
try {
|
||||||
.get(`users/${this.USER_NAME}`)
|
const resp = await realFetch(`${this.UPDATE_URL}/users/${this.USER_NAME}`)
|
||||||
.then(response => {
|
const response = await resp.json()
|
||||||
return response.data;
|
return response
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
console.log(error)
|
||||||
console.log(error);
|
return null
|
||||||
return null;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
import { ExtensionContext } from "vscode"
|
||||||
import { ExtensionContext } from "vscode";
|
import { XpCounter } from "./xp-counter"
|
||||||
import { XpCounter } from "./xp-counter";
|
|
||||||
|
|
||||||
export function activate(context: ExtensionContext): void {
|
export function activate(context: ExtensionContext): void {
|
||||||
let controller: XpCounter = new XpCounter(context);
|
let controller: XpCounter = new XpCounter(context);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
import * as fs from 'fs';
|
import { CancellationToken, Event, ExtensionContext, TextDocumentContentProvider, Uri, workspace } from "vscode"
|
||||||
import { CancellationToken, Event, ExtensionContext, TextDocumentContentProvider, Uri } from "vscode";
|
import { CodeStatsAPI } from "./code-stats-api"
|
||||||
import { CodeStatsAPI } from "./code-stats-api";
|
import profileHtmlEex from './profile.html.eex'
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import template = require('lodash.template');
|
import template = require('lodash.template');
|
||||||
|
|
||||||
@ -19,8 +18,7 @@ export class ProfileProvider implements TextDocumentContentProvider {
|
|||||||
|
|
||||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
|
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
|
||||||
|
|
||||||
if( token.isCancellationRequested )
|
if (token.isCancellationRequested) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const LEVEL_FACTOR = 0.025;
|
const LEVEL_FACTOR = 0.025;
|
||||||
|
|
||||||
@ -47,10 +45,9 @@ export class ProfileProvider implements TextDocumentContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSortedArray(obj: any): any[] {
|
function getSortedArray(obj: any): any[] {
|
||||||
|
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
||||||
for( let prop in obj) {
|
for (const prop in obj) {
|
||||||
let item = obj[prop];
|
let item = obj[prop];
|
||||||
let percents = getLevelProgress(item.xps, item.new_xps);
|
let percents = getLevelProgress(item.xps, item.new_xps);
|
||||||
items.push(
|
items.push(
|
||||||
@ -68,14 +65,15 @@ export class ProfileProvider implements TextDocumentContentProvider {
|
|||||||
return items.sort( (a,b) => {return b.xp - a.xp;});
|
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 )
|
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.`;
|
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"]);
|
profile["level"] = getLevel(profile["total_xp"]);
|
||||||
|
|
||||||
@ -89,10 +87,7 @@ export class ProfileProvider implements TextDocumentContentProvider {
|
|||||||
|
|
||||||
let html = template(htmlTemplate);
|
let html = template(htmlTemplate);
|
||||||
|
|
||||||
const stylePath = Uri.file(path.join(this.context.extensionPath, 'assets', 'profile.css'));
|
return html({profile: profile, languages: languages, machines: machines});
|
||||||
const styleSrc = stylePath.with({scheme: 'vscode-resource'});
|
|
||||||
|
|
||||||
return html({profile: profile, languages: languages, machines: machines, style: styleSrc});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
191
src/profile.html.eex.ts
Normal file
191
src/profile.html.eex.ts
Normal 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>`
|
@ -1,22 +1,12 @@
|
|||||||
|
import * as path from 'path'
|
||||||
import {
|
import {
|
||||||
Disposable,
|
commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem,
|
||||||
workspace,
|
TextDocument, TextDocumentChangeEvent, Uri,
|
||||||
window,
|
ViewColumn, window, workspace, WorkspaceConfiguration
|
||||||
Uri,
|
} from "vscode"
|
||||||
ViewColumn,
|
import { CodeStatsAPI } from "./code-stats-api"
|
||||||
commands,
|
import { ProfileProvider } from "./profile-provider"
|
||||||
StatusBarItem,
|
import { Pulse } from "./pulse"
|
||||||
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';
|
|
||||||
|
|
||||||
export class XpCounter {
|
export class XpCounter {
|
||||||
private combinedDisposable: Disposable;
|
private combinedDisposable: Disposable;
|
||||||
private statusBarItem: StatusBarItem;
|
private statusBarItem: StatusBarItem;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user