state of the mqtt
This commit is contained in:
11
src/App.vue
11
src/App.vue
@@ -178,16 +178,19 @@ onMounted(() => {
|
||||
// Example: Sync with OS theme only if no theme saved (requires store modification)
|
||||
// syncWithOsThemeIfNeeded();
|
||||
|
||||
// *** Auto-connect MQTT if URL is stored and not already connected/connecting ***
|
||||
// Auto-connect MQTT only if URL is stored AND user desires connection
|
||||
const storedBrokerUrl = store.getters.mqttBrokerUrl;
|
||||
const connectDesired = store.getters.mqttConnectDesired; // Get the flag
|
||||
|
||||
if (storedBrokerUrl &&
|
||||
connectDesired && // Check the flag
|
||||
MqttService.connectionStatus.value !== 'connected' &&
|
||||
MqttService.connectionStatus.value !== 'connecting') {
|
||||
console.log("App.vue: Auto-connecting to stored MQTT broker:", storedBrokerUrl);
|
||||
console.log("App.vue: Auto-connecting to stored MQTT broker (user desired):", storedBrokerUrl);
|
||||
MqttService.connect(storedBrokerUrl);
|
||||
} else if (storedBrokerUrl && !connectDesired) {
|
||||
console.log("App.vue: MQTT Broker URL is stored, but user previously disconnected. Not auto-connecting.");
|
||||
}
|
||||
// *** End Auto-connect MQTT ***
|
||||
|
||||
}).catch(error => {
|
||||
console.error("App.vue: Error during store.dispatch('loadState'):", error);
|
||||
});
|
||||
|
||||
@@ -114,15 +114,33 @@ const connect = async (brokerUrl = 'ws://localhost:9001') => {
|
||||
}
|
||||
};
|
||||
|
||||
const disconnect = () => { /* ... existing disconnect logic ... */
|
||||
if (client.value && typeof client.value.end === 'function') {
|
||||
console.log('MQTT: Disconnecting...');
|
||||
const disconnect = () => {
|
||||
if (client.value) {
|
||||
console.log('MQTT: Disconnecting/Stopping connection attempt...');
|
||||
// Set status immediately to give user feedback, 'close' event will confirm
|
||||
// but if it was 'connecting', it might not emit 'close' if it never truly connected.
|
||||
const wasConnecting = connectionStatus.value === 'connecting';
|
||||
|
||||
client.value.end(true, () => { // true forces close and stops reconnect attempts
|
||||
console.log('MQTT: client.end() callback executed.');
|
||||
// The 'close' event listener on the client should handle final cleanup
|
||||
// like setting client.value = null and connectionStatus.value = 'disconnected'.
|
||||
// If it was just 'connecting' and never connected, 'close' might not fire reliably.
|
||||
if (wasConnecting && connectionStatus.value !== 'disconnected') {
|
||||
connectionStatus.value = 'disconnected';
|
||||
client.value.end(true, () => {
|
||||
console.log('MQTT: Disconnected callback triggered.');
|
||||
client.value = null; // Ensure cleanup if 'close' doesn't fire from 'connecting' state
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If it was 'connecting', we might want to immediately reflect disconnected state
|
||||
// as 'end(true)' stops further attempts.
|
||||
if (wasConnecting) {
|
||||
connectionStatus.value = 'disconnected';
|
||||
// Note: client.value will be fully nulled on the 'close' event or after end() callback.
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('MQTT: No active client to disconnect.');
|
||||
connectionStatus.value = 'disconnected'; // Ensure status is correct
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const initialState = {
|
||||
globalMqttStopPause: null, // New
|
||||
globalMqttRunAll: null, // New
|
||||
mqttBrokerUrl: 'ws://localhost:9001', // Default, user can change
|
||||
mqttConnectDesired: false,
|
||||
currentPlayerIndex: 0,
|
||||
gameMode: 'normal',
|
||||
isMuted: false,
|
||||
@@ -80,6 +81,7 @@ export default createStore({
|
||||
globalMqttStopPause: persistedState.globalMqttStopPause || initialState.globalMqttStopPause,
|
||||
globalMqttRunAll: persistedState.globalMqttRunAll || initialState.globalMqttRunAll,
|
||||
mqttBrokerUrl: persistedState.mqttBrokerUrl || initialState.mqttBrokerUrl,
|
||||
mqttConnectDesired: persistedState.hasOwnProperty('mqttConnectDesired') ? persistedState.mqttConnectDesired : initialState.mqttConnectDesired,
|
||||
gameRunning: false, // Always start non-running
|
||||
currentPlayerIndex: persistedState.currentPlayerIndex !== undefined && persistedState.currentPlayerIndex < playersToUse.length ? persistedState.currentPlayerIndex : 0,
|
||||
};
|
||||
@@ -113,6 +115,10 @@ export default createStore({
|
||||
},
|
||||
SET_MQTT_BROKER_URL(state, url) {
|
||||
state.mqttBrokerUrl = url;
|
||||
state.mqttConnectDesired = true;
|
||||
},
|
||||
SET_MQTT_CONNECT_DESIRED(state, desired) {
|
||||
state.mqttConnectDesired = desired;
|
||||
},
|
||||
SET_GLOBAL_MQTT_STOP_PAUSE(state, char) {
|
||||
state.globalMqttStopPause = char;
|
||||
@@ -251,9 +257,10 @@ export default createStore({
|
||||
})),
|
||||
globalHotkeyStopPause: state.globalHotkeyStopPause,
|
||||
globalHotkeyRunAll: state.globalHotkeyRunAll,
|
||||
globalMqttStopPause: state.globalMqttStopPause, // Save
|
||||
globalMqttRunAll: state.globalMqttRunAll, // Save
|
||||
mqttBrokerUrl: state.mqttBrokerUrl, // Save
|
||||
globalMqttStopPause: state.globalMqttStopPause,
|
||||
globalMqttRunAll: state.globalMqttRunAll,
|
||||
mqttBrokerUrl: state.mqttBrokerUrl,
|
||||
mqttConnectDesired: state.mqttConnectDesired,
|
||||
currentPlayerIndex: state.currentPlayerIndex,
|
||||
gameMode: state.gameMode,
|
||||
isMuted: state.isMuted,
|
||||
@@ -268,6 +275,10 @@ export default createStore({
|
||||
commit('SET_MQTT_BROKER_URL', url);
|
||||
dispatch('saveState');
|
||||
},
|
||||
setMqttConnectDesired({ commit, dispatch }, desired) {
|
||||
commit('SET_MQTT_CONNECT_DESIRED', desired);
|
||||
dispatch('saveState');
|
||||
},
|
||||
setGlobalMqttStopPause({ commit, dispatch }, char) {
|
||||
commit('SET_GLOBAL_MQTT_STOP_PAUSE', char);
|
||||
dispatch('saveState');
|
||||
@@ -333,6 +344,7 @@ export default createStore({
|
||||
commit('SET_GLOBAL_HOTKEY_STOP_PAUSE', freshInitialState.globalHotkeyStopPause);
|
||||
commit('SET_GLOBAL_HOTKEY_RUN_ALL', freshInitialState.globalHotkeyRunAll);
|
||||
commit('SET_MQTT_BROKER_URL', freshInitialState.mqttBrokerUrl); // Reset
|
||||
commit('SET_MQTT_CONNECT_DESIRED', freshInitialState.mqttConnectDesired); // Reset
|
||||
commit('SET_GLOBAL_MQTT_STOP_PAUSE', freshInitialState.globalMqttStopPause); // Reset
|
||||
commit('SET_GLOBAL_MQTT_RUN_ALL', freshInitialState.globalMqttRunAll); // Reset
|
||||
|
||||
@@ -486,6 +498,7 @@ export default createStore({
|
||||
globalHotkeyStopPause: state => state.globalHotkeyStopPause,
|
||||
globalHotkeyRunAll: state => state.globalHotkeyRunAll,
|
||||
mqttBrokerUrl: state => state.mqttBrokerUrl,
|
||||
mqttConnectDesired: state => state.mqttConnectDesired,
|
||||
globalMqttStopPause: state => state.globalMqttStopPause,
|
||||
globalMqttRunAll: state => state.globalMqttRunAll,
|
||||
totalPlayers: state => state.players.length,
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
<h1 class="text-4xl font-bold text-blue-600 dark:text-blue-400">Nexus Timer Setup</h1>
|
||||
</header>
|
||||
|
||||
<!-- Player Management -->
|
||||
<!-- Player Management (no changes) -->
|
||||
<section class="w-full max-w-3xl bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md mb-6">
|
||||
<!-- ... Player list ... -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-semibold">Players ({{ players.length }})</h2>
|
||||
<button @click="openAddPlayerModal" class="btn btn-primary" :disabled="players.length >= 99">
|
||||
@@ -52,22 +53,40 @@
|
||||
<div class="mb-6">
|
||||
<label for="mqttBrokerUrl" class="block text-sm font-medium text-gray-700 dark:text-gray-300">MQTT Broker URL</label>
|
||||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<input type="text" id="mqttBrokerUrl" v-model="localMqttBrokerUrl" placeholder="ws://localhost:9001" class="input-base flex-1 rounded-none rounded-l-md" :disabled="mqttConnectionStatus === 'connected' || mqttConnectionStatus === 'connecting'">
|
||||
<button @click="toggleMqttConnection"
|
||||
:class="['btn inline-flex items-center px-3 rounded-l-none rounded-r-md border border-l-0',
|
||||
mqttConnectionStatus === 'connected' ? 'bg-green-500 hover:bg-green-600 border-green-600' :
|
||||
mqttConnectionStatus === 'connecting' ? 'bg-yellow-500 border-yellow-600 cursor-not-allowed' :
|
||||
mqttConnectionStatus === 'error' ? 'bg-red-500 hover:bg-red-600 border-red-600' :
|
||||
'btn-secondary border-gray-300 dark:border-gray-500']"
|
||||
:disabled="mqttConnectionStatus === 'connecting'">
|
||||
{{ mqttConnectionStatus === 'connected' ? 'Disconnect' : (mqttConnectionStatus === 'connecting' ? 'Connecting...' : 'Connect') }}
|
||||
<input
|
||||
type="text"
|
||||
id="mqttBrokerUrl"
|
||||
v-model="localMqttBrokerUrl"
|
||||
placeholder="ws://broker.example.com:9001"
|
||||
class="input-base flex-1 rounded-none rounded-l-md"
|
||||
:disabled="mqttConnectionStatus === 'connected' || mqttConnectionStatus === 'connecting'"
|
||||
>
|
||||
<button
|
||||
@click="toggleMqttConnection"
|
||||
:class="['btn inline-flex items-center px-4 rounded-l-none rounded-r-md border border-l-0 text-white',
|
||||
mqttConnectionStatus === 'connected' ? 'bg-red-500 hover:bg-red-600 border-red-600' :
|
||||
mqttConnectionStatus === 'connecting' ? 'bg-yellow-500 hover:bg-yellow-600 border-yellow-600 !text-black' : // Stop button is yellow
|
||||
'btn-primary border-blue-500']"
|
||||
>
|
||||
{{
|
||||
mqttConnectionStatus === 'connected' ? 'Disconnect' :
|
||||
(mqttConnectionStatus === 'connecting' ? 'Stop' : 'Connect')
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="mqttError" class="text-xs text-red-500 mt-1">{{ mqttError }}</p>
|
||||
<p v-else-if="mqttConnectionStatus === 'connected'" class="text-xs text-green-500 mt-1">Connected to {{ store.getters.mqttBrokerUrl }}. Subscribed to '{{ MqttService.MQTT_TOPIC_GAME }}'.</p>
|
||||
<p v-else-if="mqttConnectionStatus === 'disconnected' && !mqttError" class="text-xs text-gray-500 mt-1">Not connected to MQTT broker.</p>
|
||||
<p v-else-if="mqttConnectionStatus === 'connected'" class="text-xs text-green-500 mt-1">
|
||||
Connected to {{ store.getters.mqttBrokerUrl }}. Subscribed to '{{ MqttService.MQTT_TOPIC_GAME }}'.
|
||||
</p>
|
||||
<p v-else-if="mqttConnectionStatus === 'connecting'" class="text-xs text-yellow-600 mt-1">
|
||||
Connecting to {{ localMqttBrokerUrl }}... (Click "Stop" to cancel)
|
||||
</p>
|
||||
<p v-else-if="mqttConnectionStatus === 'disconnected'" class="text-xs text-gray-500 mt-1">
|
||||
Not connected.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Global Triggers (no changes to this section) -->
|
||||
<div>
|
||||
<p class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Global "Stop/Pause All" Trigger:</p>
|
||||
<div class="flex items-center justify-between space-x-4 mb-3">
|
||||
@@ -87,7 +106,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Global "Run All Timers" Trigger:</p>
|
||||
<div class="flex items-center justify-between space-x-4 mb-6">
|
||||
@@ -141,6 +159,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.trigger-clear-btn {
|
||||
@apply text-xs text-blue-500 hover:underline ml-2 px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-600;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onUnmounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
@@ -255,9 +279,8 @@ const handleGlobalHotkeyCaptured = (key) => {
|
||||
isCapturingGlobalHotkey.value = false;
|
||||
const actionType = currentGlobalActionType.value;
|
||||
if (!actionType || key.length !== 1) { currentGlobalActionType.value = null; return; }
|
||||
|
||||
let conflictMessage = '';
|
||||
if (store.state.players.some(p => p.hotkey === key)) { // Only check player hotkeys
|
||||
if (store.state.players.some(p => p.hotkey === key)) {
|
||||
conflictMessage = `Hotkey "${key.toUpperCase()}" is already used by a player.`;
|
||||
}
|
||||
else if (actionType === 'stopPause' && store.state.globalHotkeyRunAll === key) {
|
||||
@@ -265,9 +288,7 @@ const handleGlobalHotkeyCaptured = (key) => {
|
||||
} else if (actionType === 'runAll' && store.state.globalHotkeyStopPause === key) {
|
||||
conflictMessage = `Hotkey "${key.toUpperCase()}" is already the Global Stop/Pause All Hotkey.`;
|
||||
}
|
||||
|
||||
if (conflictMessage) { alert(conflictMessage); currentGlobalActionType.value = null; return; }
|
||||
|
||||
if (actionType === 'stopPause') store.dispatch('setGlobalHotkeyStopPause', key);
|
||||
else if (actionType === 'runAll') store.dispatch('setGlobalHotkeyRunAll', key);
|
||||
currentGlobalActionType.value = null;
|
||||
@@ -296,9 +317,8 @@ const handleGlobalMqttCharCapturedDirect = (charKey) => {
|
||||
isCapturingGlobalMqttChar.value = false;
|
||||
const actionType = currentGlobalActionType.value;
|
||||
if (!actionType || charKey.length !== 1) { currentGlobalActionType.value = null; return; }
|
||||
|
||||
let conflictMessage = '';
|
||||
if (store.state.players.some(p => p.mqttChar === charKey)) { // Only check player MQTT chars
|
||||
if (store.state.players.some(p => p.mqttChar === charKey)) {
|
||||
conflictMessage = `MQTT Char "${charKey.toUpperCase()}" is already used by a player.`;
|
||||
}
|
||||
else if (actionType === 'stopPause' && store.state.globalMqttRunAll === charKey) {
|
||||
@@ -306,26 +326,38 @@ const handleGlobalMqttCharCapturedDirect = (charKey) => {
|
||||
} else if (actionType === 'runAll' && store.state.globalMqttStopPause === charKey) {
|
||||
conflictMessage = `MQTT Char "${charKey.toUpperCase()}" is already the Global Stop/Pause All MQTT Char.`;
|
||||
}
|
||||
|
||||
if (conflictMessage) { alert(conflictMessage); currentGlobalActionType.value = null; return; }
|
||||
|
||||
if (actionType === 'stopPause') store.dispatch('setGlobalMqttStopPause', charKey);
|
||||
else if (actionType === 'runAll') store.dispatch('setGlobalMqttRunAll', charKey);
|
||||
currentGlobalActionType.value = null;
|
||||
};
|
||||
|
||||
const toggleMqttConnection = async () => {
|
||||
if (MqttService.connectionStatus.value === 'connected') {
|
||||
if (MqttService.connectionStatus.value === 'connected' || MqttService.connectionStatus.value === 'connecting') {
|
||||
MqttService.disconnect();
|
||||
} else {
|
||||
// User explicitly disconnected or stopped connecting
|
||||
await store.dispatch('setMqttConnectDesired', false);
|
||||
} else { // Status is 'disconnected' or 'error'
|
||||
if (!localMqttBrokerUrl.value.trim()) {
|
||||
alert("Please enter MQTT Broker URL.");
|
||||
alert("Please enter MQTT Broker URL (e.g., ws://host:port).");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const url = new URL(localMqttBrokerUrl.value);
|
||||
if(!url.protocol.startsWith('ws')) {
|
||||
throw new Error("URL must start with ws:// or wss://");
|
||||
}
|
||||
} catch (e) {
|
||||
alert(`Invalid MQTT Broker URL: ${e.message}. Please use format ws://host:port or wss://host:port.`);
|
||||
return;
|
||||
}
|
||||
// Saving URL also sets mqttConnectDesired to true in the store
|
||||
await store.dispatch('setMqttBrokerUrl', localMqttBrokerUrl.value);
|
||||
// await store.dispatch('setMqttConnectDesired', true); // This is now handled by setMqttBrokerUrl action
|
||||
MqttService.connect(localMqttBrokerUrl.value);
|
||||
}
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
if (MqttService.isCapturingMqttChar.value) {
|
||||
MqttService.stopMqttCharCapture();
|
||||
|
||||
@@ -6,29 +6,21 @@ import { resolve } from 'path';
|
||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const appVersion = packageJson.version;
|
||||
|
||||
// Get current date (this will be the server's local time where the build runs)
|
||||
const now = new Date();
|
||||
const dateTimeFormat = new Intl.DateTimeFormat('sk-SK', {
|
||||
|
||||
// Options for date formatting, targeting CET/CEST
|
||||
const dateTimeFormatOptionsCEST = {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
hour12: false, // Use 24-hour format
|
||||
timeZone: 'Europe/Bratislava' // Or 'Europe/Prague', 'Europe/Berlin', etc. (any major CET/CEST city)
|
||||
// This will automatically handle Daylight Saving Time (CEST vs CET)
|
||||
};
|
||||
|
||||
const parts = dateTimeFormat.formatToParts(now);
|
||||
let day = '', month = '', year = '', hour = '', minute = '', second = '';
|
||||
// Generate build time string using a specific time zone that observes CET/CEST
|
||||
const appBuildTime = now.toLocaleString('sk-SK', dateTimeFormatOptionsCEST) + " CEST/CET"; // Add timezone indicator for clarity
|
||||
|
||||
parts.forEach(part => {
|
||||
switch (part.type) {
|
||||
case 'day': day = part.value; break;
|
||||
case 'month': month = part.value; break;
|
||||
case 'year': year = part.value; break;
|
||||
case 'hour': hour = part.value; break;
|
||||
case 'minute': minute = part.value; break;
|
||||
case 'second': second = part.value; break;
|
||||
}
|
||||
});
|
||||
|
||||
// Assemble the date part without unwanted spaces
|
||||
const appBuildTime = `${day}.${month}.${year} ${hour}:${minute}:${second}`;
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
|
||||
Reference in New Issue
Block a user