@@ -0,0 +1,61 @@
|
||||
class ActionBase {
|
||||
constructor($UD, context) {
|
||||
this.$UD = $UD;
|
||||
this.context = context;
|
||||
this.refreshTimer = null;
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
|
||||
onClear() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
startTimer(fetchFn, refreshRate) {
|
||||
this.onClear();
|
||||
fetchFn();
|
||||
const duration = this.getRefreshDuration(refreshRate);
|
||||
if (duration > 0) {
|
||||
this.refreshTimer = setInterval(() => fetchFn(), duration * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
debounce(fn, delay = 300) {
|
||||
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(() => fn(), delay);
|
||||
}
|
||||
|
||||
getRefreshDuration(refreshRate) {
|
||||
switch (refreshRate) {
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '3': return 5;
|
||||
case '4': return 10;
|
||||
case '5': return 30;
|
||||
case '6': return 60;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
createCanvas() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 196;
|
||||
canvas.height = 196;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 196, 196);
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
return { canvas, ctx };
|
||||
}
|
||||
|
||||
setIcon(canvas) {
|
||||
this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png'));
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,42 @@
|
||||
class CopilotAction {
|
||||
class CopilotAction extends ActionBase {
|
||||
constructor($UD, context) {
|
||||
this.$UD = $UD;
|
||||
this.context = context;
|
||||
super($UD, context);
|
||||
this.config = {
|
||||
url: '',
|
||||
refreshRate: '4',
|
||||
};
|
||||
this.refreshTimer = null;
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
|
||||
setActive() {}
|
||||
|
||||
onClear() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setParams(jsn) {
|
||||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||||
this.start();
|
||||
this.startTimer(() => this.fetchData(), this.config.refreshRate);
|
||||
}
|
||||
|
||||
onRun() {
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
start() {
|
||||
this.onClear();
|
||||
this.fetchData();
|
||||
const duration = this.getRefreshDuration();
|
||||
if (duration > 0) {
|
||||
this.refreshTimer = setInterval(() => this.fetchData(), duration * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(async () => {
|
||||
fetchData() {
|
||||
this.debounce(async () => {
|
||||
if (!this.config.url) {
|
||||
this.renderGauge(null);
|
||||
this.renderGauge(null, false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(this.config.url);
|
||||
if (!response.ok) {
|
||||
this.renderGauge(null, true);
|
||||
return;
|
||||
}
|
||||
const text = await response.text();
|
||||
const value = this.parsePrometheus(text);
|
||||
this.renderGauge(value !== null ? parseFloat(value) : null);
|
||||
this.renderGauge(value !== null ? parseFloat(value) : null, false);
|
||||
} catch (e) {
|
||||
this.renderGauge(null);
|
||||
this.renderGauge(null, true);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
parsePrometheus(text) {
|
||||
@@ -66,7 +46,6 @@ class CopilotAction {
|
||||
if (trimmed.startsWith('github_copilot_usage_percentage')) {
|
||||
const closingBrace = trimmed.indexOf('}');
|
||||
if (closingBrace !== -1) return trimmed.slice(closingBrace + 1).trim();
|
||||
// no labels
|
||||
const parts = trimmed.split(/\s+/);
|
||||
if (parts.length >= 2) return parts[1];
|
||||
}
|
||||
@@ -74,25 +53,18 @@ class CopilotAction {
|
||||
return null;
|
||||
}
|
||||
|
||||
renderGauge(value) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 196;
|
||||
canvas.height = 196;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 196, 196);
|
||||
renderGauge(value, isError) {
|
||||
const { canvas, ctx } = this.createCanvas();
|
||||
|
||||
const cx = 98, cy = 100;
|
||||
const r = 68;
|
||||
const lineWidth = 14;
|
||||
|
||||
// Gauge runs from 225° to 315° clockwise (270° sweep)
|
||||
const startAngle = 135 * Math.PI / 180;
|
||||
const totalSweep = 270 * Math.PI / 180;
|
||||
const greenEnd = startAngle + 0.50 * totalSweep; // 50%
|
||||
const yellowEnd = startAngle + 0.80 * totalSweep; // 80%
|
||||
const endAngle = startAngle + totalSweep; // 100%
|
||||
const greenEnd = startAngle + 0.50 * totalSweep;
|
||||
const yellowEnd = startAngle + 0.80 * totalSweep;
|
||||
const endAngle = startAngle + totalSweep;
|
||||
|
||||
// Background track
|
||||
ctx.beginPath();
|
||||
@@ -106,7 +78,6 @@ class CopilotAction {
|
||||
const pct = Math.max(0, Math.min(100, value)) / 100;
|
||||
const valueAngle = startAngle + pct * totalSweep;
|
||||
|
||||
// Green segment (0–50%)
|
||||
if (pct > 0) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, startAngle, Math.min(valueAngle, greenEnd), false);
|
||||
@@ -115,8 +86,6 @@ class CopilotAction {
|
||||
ctx.lineCap = 'round';
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Yellow segment (50–80%)
|
||||
if (pct > 0.5) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, greenEnd, Math.min(valueAngle, yellowEnd), false);
|
||||
@@ -125,8 +94,6 @@ class CopilotAction {
|
||||
ctx.lineCap = 'butt';
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Red segment (80–100%)
|
||||
if (pct > 0.8) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, yellowEnd, valueAngle, false);
|
||||
@@ -136,21 +103,16 @@ class CopilotAction {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Value text — centered, colored
|
||||
const color = pct <= 0.5 ? '#4caf50' : pct <= 0.8 ? '#ffeb3b' : '#f44336';
|
||||
const displayText = value.toFixed(1) + '%';
|
||||
const fSize = displayText.length > 6 ? 28 : 34;
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = `bold ${fSize}px "Source Han Sans SC"`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(displayText, cx, cy + 8);
|
||||
} else {
|
||||
ctx.fillStyle = '#666666';
|
||||
ctx.fillStyle = isError ? '#ff6b6b' : '#666666';
|
||||
ctx.font = 'bold 28px "Source Han Sans SC"';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('N/A', cx, cy + 8);
|
||||
ctx.fillText(isError ? 'ERR' : 'N/A', cx, cy + 8);
|
||||
}
|
||||
|
||||
// Label — bottom
|
||||
@@ -158,18 +120,6 @@ class CopilotAction {
|
||||
ctx.font = 'bold 20px "Source Han Sans SC"';
|
||||
ctx.fillText('Copilot', cx, 165);
|
||||
|
||||
this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png'));
|
||||
}
|
||||
|
||||
getRefreshDuration() {
|
||||
switch (this.config.refreshRate) {
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '3': return 5;
|
||||
case '4': return 10;
|
||||
case '5': return 30;
|
||||
case '6': return 60;
|
||||
default: return 0;
|
||||
}
|
||||
this.setIcon(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class GiteaAction {
|
||||
class GiteaAction extends ActionBase {
|
||||
constructor($UD, context) {
|
||||
this.$UD = $UD;
|
||||
this.context = context;
|
||||
super($UD, context);
|
||||
this.config = {
|
||||
url: '',
|
||||
owner: '',
|
||||
@@ -9,26 +8,13 @@ class GiteaAction {
|
||||
token: '',
|
||||
refreshRate: '4',
|
||||
};
|
||||
this.refreshTimer = null;
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
|
||||
setActive() {}
|
||||
|
||||
onClear() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setParams(jsn) {
|
||||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||||
this.start();
|
||||
this.startTimer(() => this.fetchRun(), this.config.refreshRate);
|
||||
}
|
||||
|
||||
onRun() {
|
||||
@@ -37,18 +23,8 @@ class GiteaAction {
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.onClear();
|
||||
this.fetchRun();
|
||||
const duration = this.getRefreshDuration();
|
||||
if (duration > 0) {
|
||||
this.refreshTimer = setInterval(() => this.fetchRun(), duration * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchRun() {
|
||||
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(async () => {
|
||||
fetchRun() {
|
||||
this.debounce(async () => {
|
||||
if (!this.config.url || !this.config.owner || !this.config.repo) {
|
||||
this.renderButton(null);
|
||||
return;
|
||||
@@ -69,20 +45,11 @@ class GiteaAction {
|
||||
} catch (e) {
|
||||
this.renderButton({ error: 'ERR' });
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
renderButton(run) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 196;
|
||||
canvas.height = 196;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 196, 196);
|
||||
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
const { canvas, ctx } = this.createCanvas();
|
||||
|
||||
// Owner — top
|
||||
const ownerLabel = this.config.owner || '?';
|
||||
@@ -135,18 +102,6 @@ class GiteaAction {
|
||||
ctx.fillText(info.length > 16 ? info.slice(0, 15) + '…' : info, 98, 172);
|
||||
}
|
||||
|
||||
this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png'));
|
||||
}
|
||||
|
||||
getRefreshDuration() {
|
||||
switch (this.config.refreshRate) {
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '3': return 5;
|
||||
case '4': return 10;
|
||||
case '5': return 30;
|
||||
case '6': return 60;
|
||||
default: return 0;
|
||||
}
|
||||
this.setIcon(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
class PetrolAction {
|
||||
class PetrolAction extends ActionBase {
|
||||
constructor($UD, context) {
|
||||
this.$UD = $UD;
|
||||
this.context = context;
|
||||
super($UD, context);
|
||||
this.config = {
|
||||
url: '',
|
||||
stationUuid: '',
|
||||
fuelType: '',
|
||||
refreshRate: '1',
|
||||
};
|
||||
this.refreshTimer = null;
|
||||
this.debounceTimer = 0;
|
||||
this.previousPrice = null;
|
||||
this.lastDelta = null;
|
||||
|
||||
@@ -24,40 +21,18 @@ class PetrolAction {
|
||||
|
||||
setActive() {}
|
||||
|
||||
onClear() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setParams(jsn) {
|
||||
const params = (jsn && jsn.param) || {};
|
||||
this.config = Object.assign(this.config, params);
|
||||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||||
this.$UD.getSettings(this.context);
|
||||
this.start();
|
||||
this.startTimer(() => this.fetchPrice(), this.config.refreshRate);
|
||||
}
|
||||
|
||||
onRun() {
|
||||
this.fetchPrice();
|
||||
}
|
||||
|
||||
start() {
|
||||
this.onClear();
|
||||
this.fetchPrice();
|
||||
const duration = this.getRefreshDuration();
|
||||
if (duration > 0) {
|
||||
this.refreshTimer = setInterval(() => this.fetchPrice(), duration * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchPrice() {
|
||||
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(async () => {
|
||||
fetchPrice() {
|
||||
this.debounce(async () => {
|
||||
if (!this.config.url || !this.config.stationUuid || !this.config.fuelType) {
|
||||
this.renderButton('?', null);
|
||||
return;
|
||||
@@ -73,7 +48,7 @@ class PetrolAction {
|
||||
if (delta !== 0) this.lastDelta = delta;
|
||||
}
|
||||
this.previousPrice = current;
|
||||
this.renderButton(`${raw}€`, this.lastDelta);
|
||||
this.renderButton(`${parseFloat(raw).toFixed(3)}€`, this.lastDelta);
|
||||
this.$UD.setSettings({ ...this.config, previousPrice: this.previousPrice, lastDelta: this.lastDelta }, this.context);
|
||||
} else {
|
||||
this.renderButton('N/A', null);
|
||||
@@ -81,7 +56,7 @@ class PetrolAction {
|
||||
} catch (e) {
|
||||
this.renderButton('ERR', null);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
parsePrometheus(text) {
|
||||
@@ -97,55 +72,28 @@ class PetrolAction {
|
||||
}
|
||||
|
||||
renderButton(priceText, delta) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 196;
|
||||
canvas.height = 196;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 196, 196);
|
||||
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
const { canvas, ctx } = this.createCanvas();
|
||||
|
||||
// Fuel type — top, light blue
|
||||
ctx.fillStyle = '#7ec8e3';
|
||||
ctx.font = 'bold 28px "Source Han Sans SC"';
|
||||
ctx.strokeText(this.config.fuelType, 98, 45);
|
||||
ctx.fillText(this.config.fuelType, 98, 45);
|
||||
|
||||
// Price — middle, yellow
|
||||
const fSize = priceText.length > 6 ? 34 - priceText.length : 40;
|
||||
ctx.fillStyle = '#f0c040';
|
||||
ctx.font = `bold ${fSize}px "Source Han Sans SC"`;
|
||||
ctx.strokeText(priceText, 98, 105);
|
||||
ctx.fillText(priceText, 98, 105);
|
||||
|
||||
// Delta — bottom
|
||||
if (delta !== null) {
|
||||
const arrow = delta > 0 ? '▲' : '▼';
|
||||
const color = delta > 0 ? '#ff6b6b' : '#6bff6b';
|
||||
const deltaText = `${arrow} ${Math.abs(delta).toFixed(3)}`;
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = '18px "Source Han Sans SC"';
|
||||
ctx.strokeText(deltaText, 98, 142);
|
||||
ctx.fillText(deltaText, 98, 142);
|
||||
ctx.fillText(`${arrow} ${Math.abs(delta).toFixed(3)}`, 98, 142);
|
||||
}
|
||||
|
||||
this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png'));
|
||||
}
|
||||
|
||||
getRefreshDuration() {
|
||||
switch (this.config.refreshRate) {
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '3': return 5;
|
||||
case '4': return 10;
|
||||
case '5': return 30;
|
||||
case '6': return 60;
|
||||
default: return 0;
|
||||
}
|
||||
this.setIcon(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<script src="../libs/js/utils.js"></script>
|
||||
<script src="../libs/js/ulanzideckApi.js"></script>
|
||||
|
||||
<script src="./ActionBase.js"></script>
|
||||
<script src="./actions/PetrolAction.js"></script>
|
||||
<script src="./actions/CopilotAction.js"></script>
|
||||
<script src="./actions/GiteaAction.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user