207 lines
8.7 KiB
JavaScript
207 lines
8.7 KiB
JavaScript
import { app } from "/scripts/app.js";
|
|
import { api } from "/scripts/api.js";
|
|
|
|
// --- Global Timer & Speed Manager ---
|
|
const GlobalTimer = {
|
|
startTime: 0,
|
|
accumulatedTime: 0,
|
|
intervalId: null,
|
|
isRunning: false,
|
|
isPaused: false,
|
|
activeNodes: new Set(),
|
|
|
|
// Данные для расчета скорости (теперь через Map для точности)
|
|
nodeStartTimes: new Map(),
|
|
|
|
formatTime(ms) {
|
|
if (ms < 0) ms = 0;
|
|
const minutes = String(Math.floor(ms / 60000)).padStart(2, '0');
|
|
const seconds = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0');
|
|
return `${minutes}:${seconds}`;
|
|
},
|
|
|
|
start() {
|
|
this.isRunning = true;
|
|
this.isPaused = false;
|
|
this.accumulatedTime = 0;
|
|
this.startTime = Date.now();
|
|
this.nodeStartTimes.clear();
|
|
|
|
this.activeNodes.forEach(node => {
|
|
if (node.mainContainer) {
|
|
// Красим весь контейнер, чтобы и таймер, и скорость были одного цвета
|
|
node.mainContainer.style.setProperty('--text-color', '#0099ff');
|
|
node.mainContainer.style.setProperty('--glow-color', '#0099ff');
|
|
}
|
|
if (node.timerDisplay) node.timerDisplay.classList.remove('is-paused');
|
|
if (node.speedDisplay) node.speedDisplay.textContent = "";
|
|
});
|
|
this._runInterval();
|
|
},
|
|
|
|
pause() {
|
|
if (!this.isRunning || this.isPaused) return;
|
|
this.isPaused = true;
|
|
this.accumulatedTime += Date.now() - this.startTime;
|
|
clearInterval(this.intervalId);
|
|
this.activeNodes.forEach(node => {
|
|
if (node.timerDisplay) node.timerDisplay.classList.add('is-paused');
|
|
});
|
|
},
|
|
|
|
resume() {
|
|
if (!this.isRunning || !this.isPaused) return;
|
|
this.isPaused = false;
|
|
this.startTime = Date.now();
|
|
this.activeNodes.forEach(node => {
|
|
if (node.timerDisplay) node.timerDisplay.classList.remove('is-paused');
|
|
});
|
|
this._runInterval();
|
|
},
|
|
|
|
_runInterval() {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = setInterval(() => {
|
|
const currentElapsed = Date.now() - this.startTime;
|
|
const totalElapsed = this.accumulatedTime + currentElapsed;
|
|
const timeString = this.formatTime(totalElapsed);
|
|
this.activeNodes.forEach(node => {
|
|
if (node.timerDisplay) node.timerDisplay.textContent = timeString;
|
|
});
|
|
}, 100);
|
|
},
|
|
|
|
stop() {
|
|
if (!this.isRunning) return;
|
|
clearInterval(this.intervalId);
|
|
const finalTime = this.isPaused ? this.accumulatedTime : (this.accumulatedTime + (Date.now() - this.startTime));
|
|
const finalTimeString = this.formatTime(finalTime);
|
|
|
|
this.activeNodes.forEach(node => {
|
|
if (node.mainContainer) {
|
|
// Возвращаем дефолтный фиолетовый после завершения
|
|
node.mainContainer.style.setProperty('--text-color', '#7300ff');
|
|
node.mainContainer.style.setProperty('--glow-color', '#7300ff');
|
|
}
|
|
if (node.timerDisplay) {
|
|
node.timerDisplay.textContent = finalTimeString;
|
|
node.timerDisplay.classList.remove('is-paused');
|
|
}
|
|
if (node.speedDisplay) node.speedDisplay.textContent = "";
|
|
node.properties.elapsed_time_str = finalTimeString;
|
|
});
|
|
this.isRunning = false;
|
|
this.isPaused = false;
|
|
this.nodeStartTimes.clear();
|
|
},
|
|
registerNode(node) { this.activeNodes.add(node); },
|
|
unregisterNode(node) { this.activeNodes.delete(node); },
|
|
};
|
|
|
|
app.registerExtension({
|
|
name: "Neo.Timer",
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name === "NeoTimer") {
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
this.bgcolor = "#000000";
|
|
this.color = "#000000";
|
|
this.title = "Neo_Timer";
|
|
this.properties = this.properties || {};
|
|
this.size = [300, 100];
|
|
|
|
const container = document.createElement("div");
|
|
container.style.cssText = `width: 100%; height: 100%; position: relative; --text-color: #7300ff; --glow-color: #7300ff;`;
|
|
this.mainContainer = container; // Сохраняем ссылку для GlobalTimer
|
|
|
|
this.timerDisplay = document.createElement("div");
|
|
this.timerDisplay.className = "neo-timer-display";
|
|
this.timerDisplay.textContent = this.properties.elapsed_time_str || "00:00";
|
|
|
|
this.speedDisplay = document.createElement("div");
|
|
this.speedDisplay.className = "neo-speed-display";
|
|
this.speedDisplay.textContent = "";
|
|
|
|
container.appendChild(this.timerDisplay);
|
|
container.appendChild(this.speedDisplay);
|
|
|
|
this.addDOMWidget("neoTimer", "Neo Timer", container, { serialize: false });
|
|
GlobalTimer.registerNode(this);
|
|
};
|
|
nodeType.prototype.onRemoved = function() { GlobalTimer.unregisterNode(this); };
|
|
nodeType.prototype.onConfigure = function(info) {
|
|
this.properties = info.properties || {};
|
|
if (this.timerDisplay) this.timerDisplay.textContent = this.properties.elapsed_time_str || "00:00";
|
|
};
|
|
}
|
|
},
|
|
setup() {
|
|
const style = document.createElement("style");
|
|
style.innerText = `
|
|
@keyframes neo-glow-pulse { 0%, 100% { text-shadow: 0 0 15px var(--glow-color); } 50% { text-shadow: 0 0 25px var(--glow-color); } }
|
|
@keyframes neo-pause-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
|
|
.neo-timer-display {
|
|
text-align: center; width: 100%; height: 100%; position: absolute;
|
|
top: 0; left: 0; color: var(--text-color);
|
|
font-family: 'Orbitron', sans-serif;
|
|
display: flex; justify-content: center; align-items: center;
|
|
font-size: 50px; animation: neo-glow-pulse 8s infinite ease-in-out;
|
|
font-weight: bold; font-variant-numeric: tabular-nums;
|
|
transform: translateY(-13px);
|
|
line-height: 1;
|
|
}
|
|
|
|
.neo-speed-display {
|
|
position: absolute; width: 100%; text-align: center;
|
|
bottom: 10px; left: 0;
|
|
font-family: 'Orbitron', sans-serif;
|
|
font-size: 13px; color: var(--text-color);
|
|
opacity: 0.8; font-weight: bold;
|
|
text-shadow: 0 0 5px var(--glow-color);
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.neo-timer-display.is-paused {
|
|
animation: neo-glow-pulse 8s infinite ease-in-out, neo-pause-blink 2s infinite ease-in-out;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
api.addEventListener("execution_start", () => GlobalTimer.start());
|
|
|
|
api.addEventListener("executing", ({ detail }) => {
|
|
if (detail === null) {
|
|
GlobalTimer.stop();
|
|
} else {
|
|
if (GlobalTimer.isPaused) GlobalTimer.resume();
|
|
// Запоминаем время начала работы конкретной ноды
|
|
GlobalTimer.nodeStartTimes.set(detail, Date.now());
|
|
}
|
|
});
|
|
|
|
api.addEventListener("progress", ({ detail }) => {
|
|
const { value, max, node } = detail;
|
|
const nodeStartTime = GlobalTimer.nodeStartTimes.get(node);
|
|
|
|
if (!nodeStartTime) return;
|
|
|
|
const now = Date.now();
|
|
const elapsed = (now - nodeStartTime) / 1000;
|
|
if (elapsed <= 0) return;
|
|
|
|
// Расчет итераций в секунду
|
|
const it_s = value / elapsed;
|
|
if (it_s <= 0) return;
|
|
|
|
let speedText = it_s >= 1 ? `${it_s.toFixed(2)} it/s` : `${(1 / it_s).toFixed(2)} s/it`;
|
|
|
|
GlobalTimer.activeNodes.forEach(n => {
|
|
if (n.speedDisplay) n.speedDisplay.textContent = speedText;
|
|
});
|
|
});
|
|
|
|
api.addEventListener("neo_chooser_show", () => GlobalTimer.pause());
|
|
api.addEventListener("execution_error", () => GlobalTimer.stop());
|
|
api.addEventListener("execution_interrupted", () => GlobalTimer.stop());
|
|
}
|
|
}); |