mirror of
https://github.com/dualshock-tools/dualshock-tools.github.io.git
synced 2026-03-01 11:19:54 +03:00
Add option to restore previous calibration settings for DS5 and Edge
This commit is contained in:
@@ -209,6 +209,7 @@
|
||||
</button>
|
||||
<hr>
|
||||
<button id="savechanges" type="button" class="btn btn-success ds-btn ds-i18n" onclick="flash_all_changes()" id="resetBtn">Save changes permanently</button>
|
||||
<button id="restore-calibration-btn" type="button" class="btn btn-secondary ds-btn ds-i18n" onclick="openCalibrationHistoryModal()">Restore calibration</button>
|
||||
<button type="button" class="btn btn-danger ds-btn ds-i18n" onclick="reboot_controller()" id="resetBtn">Reboot controller</button>
|
||||
|
||||
<div class="card text-bg-light" >
|
||||
|
||||
@@ -86,9 +86,10 @@ class ControllerFactory {
|
||||
showInfo: false,
|
||||
showFinetune: false,
|
||||
showInfoTab: false,
|
||||
showFourStepCalib: true,
|
||||
showQuickTests: true,
|
||||
showQuickCalib: false
|
||||
showFourStepCalib: true,
|
||||
showQuickCalib: false,
|
||||
showCalibrationHistory: false
|
||||
};
|
||||
|
||||
case 0x0ce6: // DS5
|
||||
@@ -97,9 +98,10 @@ class ControllerFactory {
|
||||
showInfo: true,
|
||||
showFinetune: true,
|
||||
showInfoTab: true,
|
||||
showFourStepCalib: false,
|
||||
showQuickTests: true,
|
||||
showQuickCalib: true
|
||||
showFourStepCalib: false,
|
||||
showQuickCalib: true,
|
||||
showCalibrationHistory: true
|
||||
};
|
||||
|
||||
case 0x0e45: // VR2 Left Controller
|
||||
@@ -108,8 +110,8 @@ class ControllerFactory {
|
||||
showInfo: true,
|
||||
showFinetune: false,
|
||||
showInfoTab: true,
|
||||
showFourStepCalib: true,
|
||||
showQuickTests: false,
|
||||
showFourStepCalib: true,
|
||||
showQuickCalib: false
|
||||
};
|
||||
|
||||
@@ -118,9 +120,10 @@ class ControllerFactory {
|
||||
showInfo: false,
|
||||
showFinetune: false,
|
||||
showInfoTab: false,
|
||||
showFourStepCalib: false,
|
||||
showQuickTests: false,
|
||||
showQuickCalib: false
|
||||
showFourStepCalib: false,
|
||||
showQuickCalib: false,
|
||||
showCalibrationHistory: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
49
js/core.js
49
js/core.js
@@ -14,6 +14,8 @@ import {
|
||||
isQuickTestVisible,
|
||||
quicktest_handle_controller_input
|
||||
} from './modals/quick-test-modal.js';
|
||||
import { FinetuneHistory } from './finetune-history.js';
|
||||
import { CalibrationHistoryModal } from './modals/calibration-history-modal.js';
|
||||
|
||||
// Application State - manages app-wide state and UI
|
||||
const app = {
|
||||
@@ -108,6 +110,7 @@ function gboot() {
|
||||
|
||||
await loadAllTemplates();
|
||||
|
||||
CalibrationHistoryModal.init();
|
||||
initAnalyticsApi(app); // init just with gu for now
|
||||
lang_init(app, handleLanguageChange, show_welcome_modal);
|
||||
show_welcome_modal();
|
||||
@@ -215,14 +218,15 @@ async function continue_connection({data, device}) {
|
||||
}
|
||||
|
||||
// Helper to apply basic UI visibility based on device type
|
||||
function applyDeviceUI({ showInfo, showFinetune, showInfoTab, showFourStepCalib, showQuickTests, showQuickCalib }) {
|
||||
function applyDeviceUI({ showInfo, showFinetune, showInfoTab, showQuickTests, showFourStepCalib, showQuickCalib, showCalibrationHistory }) {
|
||||
$("#infoshowall").toggle(!!showInfo);
|
||||
$("#ds5finetune").toggle(!!showFinetune);
|
||||
$("#info-tab").toggle(!!showInfoTab);
|
||||
$("#four-step-center-calib").toggle(!!showFourStepCalib);
|
||||
$("#quick-tests-div").css("visibility", showQuickTests ? "visible" : "hidden");
|
||||
$("#four-step-center-calib").toggle(!!showFourStepCalib);
|
||||
$("#quick-center-calib").toggle(!!showQuickCalib);
|
||||
$("#quick-center-calib-group").toggle(!!showQuickCalib);
|
||||
$("#restore-calibration-btn").toggle(!!showCalibrationHistory);
|
||||
}
|
||||
|
||||
let controllerInstance = null;
|
||||
@@ -328,6 +332,17 @@ async function continue_connection({data, device}) {
|
||||
if(model == "VR2") {
|
||||
show_popup(l("<p>Support for PS VR2 controllers is <b>minimal and highly experimental</b>.</p><p>I currently don't own these controllers, so I cannot verify the calibration process myself.</p><p>If you'd like to help improve full support, you can contribute with a donation or even send the controllers for testing.</p><p>Feel free to contact me on Discord (the_al) or by email at ds4@the.al .</p><br><p>Thank you for your support!</p>"), true)
|
||||
}
|
||||
|
||||
// Save finetune parameters for DS5 and Edge controllers
|
||||
if (model === "DS5" || model === "DS5_Edge") {
|
||||
const finetuneData = await controllerInstance.getInMemoryModuleData();
|
||||
// Extract serial number from info items
|
||||
const serialNumberItem = info.infoItems?.find(item => item.key === l("Serial Number"));
|
||||
const serialNumber = serialNumberItem?.value;
|
||||
if (serialNumber) {
|
||||
FinetuneHistory.save(finetuneData, serialNumber);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
await disconnect();
|
||||
throw err;
|
||||
@@ -1118,6 +1133,36 @@ window.ds5_finetune = () => ds5_finetune(
|
||||
{ ll_data, rr_data, clear_circularity },
|
||||
(success) => success && switchToRangeMode()
|
||||
);
|
||||
|
||||
window.apply_finetune_revert = async (finetuneData) => {
|
||||
if (!controller || !controller.isConnected()) {
|
||||
throw new Error('Controller not connected');
|
||||
}
|
||||
if (!Array.isArray(finetuneData) || finetuneData.length !== 12) {
|
||||
throw new Error('Invalid finetune data');
|
||||
}
|
||||
await controller.writeFinetuneData(finetuneData);
|
||||
};
|
||||
|
||||
window.openCalibrationHistoryModal = async () => {
|
||||
let currentFinetuneData = null;
|
||||
let controllerSerialNumber = null;
|
||||
try {
|
||||
if (controller && typeof controller.getInMemoryModuleData === 'function') {
|
||||
currentFinetuneData = await controller.getInMemoryModuleData('finetune');
|
||||
}
|
||||
// Get serial number from device info
|
||||
if (controller && typeof controller.getDeviceInfo === 'function') {
|
||||
const info = await controller.getDeviceInfo();
|
||||
const serialNumberItem = info?.infoItems?.find(item => item.key === l("Serial Number"));
|
||||
controllerSerialNumber = serialNumberItem?.value;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not retrieve current finetune data or serial number:', error);
|
||||
}
|
||||
window.show_calibration_history_modal(currentFinetuneData, controllerSerialNumber);
|
||||
};
|
||||
|
||||
window.flash_all_changes = flash_all_changes;
|
||||
window.reboot_controller = reboot_controller;
|
||||
window.refresh_nvstatus = refresh_nvstatus;
|
||||
|
||||
183
js/finetune-history.js
Normal file
183
js/finetune-history.js
Normal file
@@ -0,0 +1,183 @@
|
||||
'use strict';
|
||||
|
||||
const STORAGE_KEY = 'finetuneHistory';
|
||||
const MAX_HISTORY_ENTRIES_PER_CONTROLLER = 10;
|
||||
|
||||
/**
|
||||
* Manages finetune parameter history for DS5 and Edge controllers
|
||||
* Stores entries per controller identified by serial number
|
||||
*/
|
||||
export class FinetuneHistory {
|
||||
/**
|
||||
* Save current finetune settings for a controller
|
||||
* @param {Array} finetuneData - Array of 12 finetune values
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
* @returns {string} The ID of the saved entry
|
||||
*/
|
||||
static save(finetuneData, controllerSerialNumber) {
|
||||
if (!Array.isArray(finetuneData) || finetuneData.length !== 12) {
|
||||
throw new Error(`Finetune data must be an array of 12 values, got "${finetuneData}"`);
|
||||
}
|
||||
|
||||
if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') {
|
||||
throw new Error('Controller serial number is required');
|
||||
}
|
||||
|
||||
const allHistory = this._getAllHistory();
|
||||
const controllerHistory = allHistory[controllerSerialNumber] || [];
|
||||
|
||||
// Check if the most recent entry has the same data
|
||||
if (controllerHistory.length > 0 && this._dataEquals(controllerHistory[0].data, finetuneData)) {
|
||||
// Update the timestamp of the existing entry
|
||||
controllerHistory[0].timestamp = Date.now();
|
||||
allHistory[controllerSerialNumber] = controllerHistory;
|
||||
this._saveAllHistory(allHistory);
|
||||
return controllerHistory[0].id;
|
||||
}
|
||||
|
||||
const entry = {
|
||||
id: this._generateId(),
|
||||
timestamp: Date.now(),
|
||||
data: finetuneData
|
||||
};
|
||||
|
||||
controllerHistory.unshift(entry);
|
||||
|
||||
// Keep only the latest MAX_HISTORY_ENTRIES_PER_CONTROLLER for this controller
|
||||
if (controllerHistory.length > MAX_HISTORY_ENTRIES_PER_CONTROLLER) {
|
||||
controllerHistory.pop();
|
||||
}
|
||||
|
||||
allHistory[controllerSerialNumber] = controllerHistory;
|
||||
this._saveAllHistory(allHistory);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all saved finetune settings for a specific controller
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
* @returns {Array} Array of saved settings entries for the controller
|
||||
*/
|
||||
static getAll(controllerSerialNumber) {
|
||||
if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allHistory = this._getAllHistory();
|
||||
return allHistory[controllerSerialNumber] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get finetune settings by ID
|
||||
* @param {string} id - Entry ID
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
* @returns {Object|null} Entry object or null if not found
|
||||
*/
|
||||
static getById(id, controllerSerialNumber) {
|
||||
if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const history = this.getAll(controllerSerialNumber);
|
||||
return history.find(entry => entry.id === id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a saved entry
|
||||
* @param {string} id - Entry ID
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
* @returns {boolean} True if deleted, false if not found
|
||||
*/
|
||||
static delete(id, controllerSerialNumber) {
|
||||
if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const allHistory = this._getAllHistory();
|
||||
const controllerHistory = allHistory[controllerSerialNumber] || [];
|
||||
const index = controllerHistory.findIndex(entry => entry.id === id);
|
||||
|
||||
if (index >= 0) {
|
||||
controllerHistory.splice(index, 1);
|
||||
allHistory[controllerSerialNumber] = controllerHistory;
|
||||
this._saveAllHistory(allHistory);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all saved finetune settings for a specific controller
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
*/
|
||||
static clearAll(controllerSerialNumber) {
|
||||
if (!controllerSerialNumber || typeof controllerSerialNumber !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
const allHistory = this._getAllHistory();
|
||||
delete allHistory[controllerSerialNumber];
|
||||
this._saveAllHistory(allHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get finetune data from a specific entry
|
||||
* @param {string} id - Entry ID
|
||||
* @param {string} controllerSerialNumber - Serial number of the controller
|
||||
* @returns {Array|null} Finetune data array or null if not found
|
||||
*/
|
||||
static getDataById(id, controllerSerialNumber) {
|
||||
const entry = this.getById(id, controllerSerialNumber);
|
||||
return entry ? entry.data : null;
|
||||
}
|
||||
|
||||
// ==================== PRIVATE METHODS ====================
|
||||
|
||||
/**
|
||||
* Get all history from localStorage (for all controllers)
|
||||
* @private
|
||||
*/
|
||||
static _getAllHistory() {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (e) {
|
||||
console.error('Failed to parse finetune history:', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all history to localStorage
|
||||
* @private
|
||||
*/
|
||||
static _saveAllHistory(allHistory) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(allHistory));
|
||||
} catch (e) {
|
||||
console.error('Failed to save finetune history:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique ID
|
||||
* @private
|
||||
*/
|
||||
static _generateId() {
|
||||
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two data arrays for equality
|
||||
* @private
|
||||
*/
|
||||
static _dataEquals(data1, data2) {
|
||||
if (!Array.isArray(data1) || !Array.isArray(data2)) {
|
||||
return false;
|
||||
}
|
||||
if (data1.length !== data2.length) {
|
||||
return false;
|
||||
}
|
||||
return data1.every((val, idx) => val === data2[idx]);
|
||||
}
|
||||
}
|
||||
175
js/modals/calibration-history-modal.js
Normal file
175
js/modals/calibration-history-modal.js
Normal file
@@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
|
||||
import { FinetuneHistory } from '../finetune-history.js';
|
||||
import { formatLocalizedDate } from '../utils.js';
|
||||
|
||||
export class CalibrationHistoryModal {
|
||||
static modalElement = null;
|
||||
static bootstrapModal = null;
|
||||
static currentFinetuneData = null;
|
||||
static currentControllerSerialNumber = null;
|
||||
|
||||
static init() {
|
||||
this.modalElement = document.getElementById('calibrationHistoryModal');
|
||||
if (this.modalElement) {
|
||||
this.bootstrapModal = new bootstrap.Modal(this.modalElement);
|
||||
}
|
||||
}
|
||||
|
||||
static async show(currentFinetuneData = null, controllerSerialNumber = null) {
|
||||
if (!this.bootstrapModal) {
|
||||
this.init();
|
||||
}
|
||||
this.currentFinetuneData = currentFinetuneData;
|
||||
this.currentControllerSerialNumber = controllerSerialNumber;
|
||||
await this._populateHistory();
|
||||
this.bootstrapModal.show();
|
||||
}
|
||||
|
||||
static hide() {
|
||||
if (this.bootstrapModal) {
|
||||
this.bootstrapModal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the history list
|
||||
* @private
|
||||
*/
|
||||
static async _populateHistory() {
|
||||
const history = FinetuneHistory.getAll(this.currentControllerSerialNumber);
|
||||
const container = document.getElementById('historyListContainer');
|
||||
|
||||
if (!history || history.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted ds-i18n">No saved calibration settings found.</p>';
|
||||
document.getElementById('clearAllBtn').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('clearAllBtn').style.display = 'block';
|
||||
|
||||
let html = '<div class="list-group">';
|
||||
|
||||
history.forEach(entry => {
|
||||
const date = formatLocalizedDate(entry.timestamp);
|
||||
const isCurrent = this.currentFinetuneData && this._dataEquals(entry.data, this.currentFinetuneData);
|
||||
|
||||
html += `
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">${date}</h6>
|
||||
<p class="mb-0 small">Values: ${entry.data.join(', ')}</p>
|
||||
</div>
|
||||
<div class="btn-group-sm" role="group">
|
||||
${isCurrent ?
|
||||
`<button type="button" class="btn btn-sm btn-success ds-i18n" disabled>Current</button>` :
|
||||
`<button type="button" class="btn btn-sm btn-primary ds-i18n" onclick="calibration_history_revert('${entry.id}')">Revert</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger ds-i18n" onclick="calibration_history_delete('${entry.id}')">Delete</button>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two data arrays for equality
|
||||
* @private
|
||||
*/
|
||||
static _dataEquals(data1, data2) {
|
||||
if (!Array.isArray(data1) || !Array.isArray(data2)) {
|
||||
return false;
|
||||
}
|
||||
if (data1.length !== data2.length) {
|
||||
return false;
|
||||
}
|
||||
return data1.every((val, idx) => val === data2[idx]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML special characters
|
||||
* @private
|
||||
*/
|
||||
static _escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to a saved calibration
|
||||
* @param {string} entryId - The ID of the entry to revert to
|
||||
*/
|
||||
static revertTo(entryId) {
|
||||
const entry = FinetuneHistory.getById(entryId, this.currentControllerSerialNumber);
|
||||
if (!entry) {
|
||||
alert('Calibration settings not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Export revert function to window for onclick handlers
|
||||
window.calibration_history_pending_revert_id = entryId;
|
||||
window.calibration_history_pending_revert_data = entry.data;
|
||||
|
||||
// Show confirmation dialog
|
||||
const confirmMsg = `Revert to this version?\n\nThis will restore the stored finetune settings.`;
|
||||
if (confirm(confirmMsg)) {
|
||||
this._executeRevert(entryId, entry.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the revert operation
|
||||
* @private
|
||||
*/
|
||||
static _executeRevert(entryId, finetuneData) {
|
||||
// Call the revert function exposed in core.js
|
||||
if (typeof window.apply_finetune_revert === 'function') {
|
||||
window.apply_finetune_revert(finetuneData).then(() => {
|
||||
this.hide();
|
||||
alert('Calibration reverted successfully. Remember to save changes permanently.');
|
||||
}).catch(err => {
|
||||
alert('Failed to revert calibration: ' + err.message);
|
||||
});
|
||||
} else {
|
||||
alert('Controller not ready. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a saved entry
|
||||
* @param {string} entryId - The ID of the entry to delete
|
||||
*/
|
||||
static async delete(entryId) {
|
||||
const entry = FinetuneHistory.getById(entryId, this.currentControllerSerialNumber);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm(`Delete this calibration entry?`)) {
|
||||
FinetuneHistory.delete(entryId, this.currentControllerSerialNumber);
|
||||
await this._populateHistory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all saved entries
|
||||
*/
|
||||
static async clearAll() {
|
||||
if (confirm('Delete all calibration history for this controller? This cannot be undone.')) {
|
||||
FinetuneHistory.clearAll(this.currentControllerSerialNumber);
|
||||
await this._populateHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions to window for onclick handlers
|
||||
window.calibration_history_revert = (entryId) => CalibrationHistoryModal.revertTo(entryId);
|
||||
window.calibration_history_delete = (entryId) => CalibrationHistoryModal.delete(entryId);
|
||||
window.calibration_history_clear_all = () => CalibrationHistoryModal.clearAll();
|
||||
window.show_calibration_history_modal = (currentFinetuneData = null, controllerSerialNumber = null) => CalibrationHistoryModal.show(currentFinetuneData, controllerSerialNumber);
|
||||
@@ -83,10 +83,11 @@ export async function loadAllTemplates() {
|
||||
const edgeModalHtml = await loadTemplate('edge-modal');
|
||||
const donateModalHtml = await loadTemplate('donate-modal');
|
||||
const quickTestModalHtml = await loadTemplate('quick-test-modal');
|
||||
const calibrationHistoryModalHtml = await loadTemplate('calibration-history-modal');
|
||||
|
||||
// Create modals container
|
||||
const modalsContainer = document.createElement('div');
|
||||
modalsContainer.id = 'modals-container';
|
||||
modalsContainer.innerHTML = faqModalHtml + popupModalHtml + finetuneModalHtml + calibCenterModalHtml + welcomeModalHtml + autoCalibCenterModalHtml + rangeModalHtml + edgeProgressModalHtml + edgeModalHtml + donateModalHtml + quickTestModalHtml;
|
||||
modalsContainer.innerHTML = faqModalHtml + popupModalHtml + finetuneModalHtml + calibCenterModalHtml + welcomeModalHtml + autoCalibCenterModalHtml + rangeModalHtml + edgeProgressModalHtml + edgeModalHtml + donateModalHtml + quickTestModalHtml + calibrationHistoryModalHtml;
|
||||
document.body.appendChild(modalsContainer);
|
||||
}
|
||||
|
||||
26
js/utils.js
26
js/utils.js
@@ -152,4 +152,30 @@ export function readCookie(name) {
|
||||
*/
|
||||
export function eraseCookie(name) {
|
||||
createCookie(name, "", -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate locale for date formatting based on language and timezone
|
||||
* @returns {string} Locale string for use with toLocaleString()
|
||||
*/
|
||||
export function getLocaleForDateFormatting() {
|
||||
let lang = readCookie('force_lang') || navigator.language;
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Replace en_US with en_UK if timezone does not start with "America"
|
||||
if (lang.toLowerCase() === 'en_us' && !timezone.startsWith('America')) {
|
||||
lang = 'en_UK';
|
||||
}
|
||||
|
||||
return lang.replace('_', '-').toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp as a localized date/time string
|
||||
* @param {number|string} timestamp Unix timestamp or date string
|
||||
* @returns {string} Formatted date/time string
|
||||
*/
|
||||
export function formatLocalizedDate(timestamp) {
|
||||
const locale = getLocaleForDateFormatting();
|
||||
return new Date(timestamp).toLocaleString(locale);
|
||||
}
|
||||
19
templates/calibration-history-modal.html
Normal file
19
templates/calibration-history-modal.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="modal fade" id="calibrationHistoryModal" tabindex="-1" aria-labelledby="calibrationHistoryModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5 ds-i18n" id="calibrationHistoryModalLabel">Calibration History</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="historyListContainer">
|
||||
<p class="text-muted ds-i18n">No saved calibration settings found.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary ds-i18n" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger ds-i18n" onclick="calibration_history_clear_all()" id="clearAllBtn" style="display: none;">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user