mirror of
https://github.com/dualshock-tools/dualshock-tools.github.io.git
synced 2026-03-01 11:19:54 +03:00
Bug fixes and tweaks
This commit is contained in:
committed by
dualshock-tools
parent
0e8e7fc281
commit
fea0e9c3fa
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import { sleep, la } from './utils.js';
|
||||
import { l } from './translations.js'
|
||||
|
||||
/**
|
||||
* Controller Manager - Manages the current controller instance and provides unified interface
|
||||
@@ -8,7 +9,6 @@ import { sleep, la } from './utils.js';
|
||||
class ControllerManager {
|
||||
constructor(uiDependencies = {}) {
|
||||
this.currentController = null;
|
||||
this.l = uiDependencies.l;
|
||||
this.handleNvStatusUpdate = uiDependencies.handleNvStatusUpdate;
|
||||
this.has_changes_to_write = null;
|
||||
this.inputHandler = null; // Callback function for input processing
|
||||
@@ -109,6 +109,17 @@ class ControllerManager {
|
||||
return this.currentController.getModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of supported quick tests for the current controller
|
||||
* @returns {Array<string>} Array of supported test types
|
||||
*/
|
||||
getSupportedQuickTests() {
|
||||
if (!this.currentController) {
|
||||
return [];
|
||||
}
|
||||
return this.currentController.getSupportedQuickTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a controller is connected
|
||||
* @returns {boolean} True if controller is connected
|
||||
@@ -183,7 +194,7 @@ class ControllerManager {
|
||||
async nvsLock() {
|
||||
const res = await this.currentController.nvsLock();
|
||||
if (!res.ok) {
|
||||
throw new Error(this.l("NVS Lock failed"), { cause: res.error });
|
||||
throw new Error(l("NVS Lock failed"), { cause: res.error });
|
||||
}
|
||||
|
||||
await this.queryNvStatus(); // Refresh NVS status
|
||||
@@ -196,7 +207,7 @@ class ControllerManager {
|
||||
async calibrateSticksBegin() {
|
||||
const res = await this.currentController.calibrateSticksBegin();
|
||||
if (!res.ok) {
|
||||
throw new Error(`${this.l("Stick calibration failed")}. ${res.error?.message}`, { cause: res.error });
|
||||
throw new Error(`${l("Stick calibration failed")}. ${res.error?.message}`, { cause: res.error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +218,7 @@ class ControllerManager {
|
||||
const res = await this.currentController.calibrateSticksSample();
|
||||
if (!res.ok) {
|
||||
await sleep(500);
|
||||
throw new Error(this.l("Stick calibration failed"), { cause: res.error });
|
||||
throw new Error(l("Stick calibration failed"), { cause: res.error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +229,7 @@ class ControllerManager {
|
||||
const res = await this.currentController.calibrateSticksEnd();
|
||||
if (!res.ok) {
|
||||
await sleep(500);
|
||||
throw new Error(this.l("Stick calibration failed"), { cause: res.error });
|
||||
throw new Error(l("Stick calibration failed"), { cause: res.error });
|
||||
}
|
||||
|
||||
this.setHasChangesToWrite(true);
|
||||
@@ -230,7 +241,7 @@ class ControllerManager {
|
||||
async calibrateRangeBegin() {
|
||||
const res = await this.currentController.calibrateRangeBegin();
|
||||
if (!res.ok) {
|
||||
throw new Error(`${this.l("Stick calibration failed")}. ${res.error?.message}`, { cause: res.error });
|
||||
throw new Error(`${l("Stick calibration failed")}. ${res.error?.message}`, { cause: res.error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +252,7 @@ class ControllerManager {
|
||||
const res = await this.currentController.calibrateRangeEnd();
|
||||
if(res?.ok) {
|
||||
this.setHasChangesToWrite(true);
|
||||
return { success: true, message: this.l("Range calibration completed") };
|
||||
return { success: true, message: l("Range calibration completed") };
|
||||
} else {
|
||||
// Check if the error is code 3 (DS4/DS5) or codes 4/5 (DS5 Edge), which typically means
|
||||
// the calibration was already ended or the controller is not in range calibration mode
|
||||
@@ -254,7 +265,7 @@ class ControllerManager {
|
||||
|
||||
console.log("Range calibration end failed with unexpected error:", res);
|
||||
await sleep(500);
|
||||
const msg = res?.code ? (`${this.l("Range calibration failed")}. ${this.l("Error")} ${res.code}`) : (`${this.l("Range calibration failed")}. ${res?.error || ""}`);
|
||||
const msg = res?.code ? (`${l("Range calibration failed")}. ${l("Error")} ${res.code}`) : (`${l("Range calibration failed")}. ${res?.error || ""}`);
|
||||
return { success: false, message: msg, error: res?.error };
|
||||
}
|
||||
}
|
||||
@@ -286,7 +297,7 @@ class ControllerManager {
|
||||
await this.calibrateSticksEnd();
|
||||
progressCallback(100);
|
||||
|
||||
return { success: true, message: this.l("Stick calibration completed") };
|
||||
return { success: true, message: l("Stick calibration completed") };
|
||||
} catch (e) {
|
||||
la("multi_calibrate_sticks_failed", {"r": e});
|
||||
throw e;
|
||||
@@ -299,24 +310,24 @@ class ControllerManager {
|
||||
*/
|
||||
async disableLeftAdaptiveTrigger() {
|
||||
if (!this.currentController) {
|
||||
throw new Error(this.l("No controller connected"));
|
||||
throw new Error(l("No controller connected"));
|
||||
}
|
||||
|
||||
// Check if the controller supports adaptive triggers (DS5 only)
|
||||
if (this.getModel() !== "DS5") {
|
||||
throw new Error(this.l("Adaptive triggers are only supported on DualSense controllers"));
|
||||
throw new Error(l("Adaptive triggers are only supported on DualSense controllers"));
|
||||
}
|
||||
|
||||
// Check if the controller has the disableLeftAdaptiveTrigger method
|
||||
if (typeof this.currentController.disableLeftAdaptiveTrigger !== 'function') {
|
||||
throw new Error(this.l("Controller does not support adaptive trigger control"));
|
||||
throw new Error(l("Controller does not support adaptive trigger control"));
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.currentController.disableLeftAdaptiveTrigger();
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new Error(this.l("Failed to disable adaptive trigger"), { cause: error });
|
||||
throw new Error(l("Failed to disable adaptive trigger"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +356,7 @@ class ControllerManager {
|
||||
// if (preset === 'custom') {
|
||||
// // Validate custom parameters
|
||||
// if (typeof start !== 'number' || typeof end !== 'number' || typeof force !== 'number') {
|
||||
// throw new Error(this.l("Custom preset requires start, end, and force parameters"));
|
||||
// throw new Error(l("Custom preset requires start, end, and force parameters"));
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -374,7 +385,7 @@ class ControllerManager {
|
||||
}
|
||||
} catch (error) {
|
||||
if(duration) doneCb({ success: false});
|
||||
throw new Error(this.l("Failed to set vibration"), { cause: error });
|
||||
throw new Error(l("Failed to set vibration"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +398,7 @@ class ControllerManager {
|
||||
async setSpeakerTone(duration = 1000, doneCb = ({success}) => {}, output = "speaker") {
|
||||
try {
|
||||
if (!this.currentController.setSpeakerTone) {
|
||||
throw new Error(this.l("Speaker tone not supported on this controller"));
|
||||
throw new Error(l("Speaker tone not supported on this controller"));
|
||||
}
|
||||
|
||||
await this.currentController.setSpeakerTone(output);
|
||||
@@ -409,7 +420,7 @@ class ControllerManager {
|
||||
}
|
||||
} catch (error) {
|
||||
if(duration) doneCb({ success: false});
|
||||
throw new Error(this.l("Failed to set speaker tone"), { cause: error });
|
||||
throw new Error(l("Failed to set speaker tone"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,7 +575,7 @@ class ControllerManager {
|
||||
*/
|
||||
_batteryPercentToText({bat_capacity, is_charging, is_error}) {
|
||||
if (is_error) {
|
||||
return '<font color="red">' + this.l("error") + '</font>';
|
||||
return '<font color="red">' + l("error") + '</font>';
|
||||
}
|
||||
|
||||
const batteryIcons = [
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
* Base Controller class that provides common functionality for all controller types
|
||||
*/
|
||||
class BaseController {
|
||||
constructor(device, uiDependencies = {}) {
|
||||
constructor(device) {
|
||||
this.device = device;
|
||||
this.model = "undefined"; // to be set by subclasses
|
||||
this.finetuneMaxValue; // to be set by subclasses
|
||||
|
||||
// UI dependencies injected from core
|
||||
this.l = uiDependencies.l;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
@@ -184,6 +181,15 @@ class BaseController {
|
||||
// Default no-op implementation for controllers that don't support player indicators
|
||||
return { success: true, message: "This controller does not support player indicators" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of supported quick tests for this controller
|
||||
* @returns {Array<string>} Array of supported test types
|
||||
*/
|
||||
getSupportedQuickTests() {
|
||||
// Default implementation - supports all tests
|
||||
return ['usb', 'buttons', 'adaptive', 'haptic', 'lights', 'speaker', 'headphone', 'microphone'];
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseController;
|
||||
|
||||
@@ -21,20 +21,19 @@ class ControllerFactory {
|
||||
/**
|
||||
* Create a controller instance based on the HID device product ID
|
||||
* @param {HIDDevice} device The HID device
|
||||
* @param {Object} uiDependencies Optional UI dependencies (l function, etc.)
|
||||
* @returns {BaseController} The appropriate controller instance
|
||||
*/
|
||||
static createControllerInstance(device, uiDependencies = {}) {
|
||||
static createControllerInstance(device) {
|
||||
switch (device.productId) {
|
||||
case 0x05c4: // DS4 v1
|
||||
case 0x09cc: // DS4 v2
|
||||
return new DS4Controller(device, uiDependencies);
|
||||
return new DS4Controller(device);
|
||||
|
||||
case 0x0ce6: // DS5
|
||||
return new DS5Controller(device, uiDependencies);
|
||||
return new DS5Controller(device);
|
||||
|
||||
case 0x0df2: // DS5 Edge
|
||||
return new DS5EdgeController(device, uiDependencies);
|
||||
return new DS5EdgeController(device);
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported device: ${dec2hex(device.vendorId)}:${dec2hex(device.productId)}`);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
lf,
|
||||
la
|
||||
} from '../utils.js';
|
||||
import { l } from '../translations.js';
|
||||
|
||||
const NOT_GENUINE_SONY_CONTROLLER_MSG = "Your device might not be a genuine Sony controller. If it is not a clone then please report this issue.";
|
||||
|
||||
@@ -111,8 +112,8 @@ class DS4OutputStruct {
|
||||
* DualShock 4 Controller implementation
|
||||
*/
|
||||
class DS4Controller extends BaseController {
|
||||
constructor(device, uiDependencies = {}) {
|
||||
super(device, uiDependencies);
|
||||
constructor(device) {
|
||||
super(device);
|
||||
this.model = "DS4";
|
||||
|
||||
// Initialize current output state to track controller settings
|
||||
@@ -134,8 +135,6 @@ class DS4Controller extends BaseController {
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
const { l } = this;
|
||||
|
||||
// Device-only: collect info and return a common structure; do not touch the DOM
|
||||
try {
|
||||
let deviceTypeText = l("unknown");
|
||||
@@ -204,9 +203,9 @@ class DS4Controller extends BaseController {
|
||||
const lockRes = await this.nvsLock();
|
||||
if(!lockRes.ok) throw (lockRes.error || new Error("NVS lock failed"));
|
||||
|
||||
return { success: true, message: this.l("Changes saved successfully") };
|
||||
return { success: true, message: l("Changes saved successfully") };
|
||||
} catch(error) {
|
||||
throw new Error(this.l("Error while saving changes"), { cause: error });
|
||||
throw new Error(l("Error while saving changes"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +257,7 @@ class DS4Controller extends BaseController {
|
||||
la("ds4_calibrate_range_begin_failed", {"d1": d1, "d2": d2});
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(this.l(NOT_GENUINE_SONY_CONTROLLER_MSG)),
|
||||
error: new Error(l(NOT_GENUINE_SONY_CONTROLLER_MSG)),
|
||||
code: 1, d1, d2
|
||||
};
|
||||
}
|
||||
@@ -306,7 +305,7 @@ class DS4Controller extends BaseController {
|
||||
la("ds4_calibrate_sticks_begin_failed", {"d1": d1, "d2": d2});
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(this.l(NOT_GENUINE_SONY_CONTROLLER_MSG)),
|
||||
error: new Error(l(NOT_GENUINE_SONY_CONTROLLER_MSG)),
|
||||
code: 1, d1, d2,
|
||||
};
|
||||
}
|
||||
@@ -401,7 +400,7 @@ class DS4Controller extends BaseController {
|
||||
} else {
|
||||
if(this.isRare(hw_ver))
|
||||
return "WOW!";
|
||||
return this.l("Unknown");
|
||||
return l("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,6 +688,15 @@ class DS4Controller extends BaseController {
|
||||
throw new Error('WebRTC getUserMedia API or mediaDevices enumeration not available.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of supported quick tests for DS4 controller
|
||||
* DS4 does not support adaptive triggers, speaker, or microphone
|
||||
* @returns {Array<string>} Array of supported test types
|
||||
*/
|
||||
getSupportedQuickTests() {
|
||||
return ['usb', 'buttons', 'haptic', 'lights', 'headphone'];
|
||||
}
|
||||
}
|
||||
|
||||
export default DS4Controller;
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
la,
|
||||
lf
|
||||
} from '../utils.js';
|
||||
import { l } from '../translations.js';
|
||||
|
||||
// DS5 Button mapping configuration
|
||||
const DS5_BUTTON_MAP = [
|
||||
@@ -225,8 +226,8 @@ function ds5_color(x) {
|
||||
* DualSense (DS5) Controller implementation
|
||||
*/
|
||||
class DS5Controller extends BaseController {
|
||||
constructor(device, uiDependencies = {}) {
|
||||
super(device, uiDependencies);
|
||||
constructor(device) {
|
||||
super(device);
|
||||
this.model = "DS5";
|
||||
this.finetuneMaxValue = 65535; // 16-bit max value for DS5
|
||||
|
||||
@@ -271,7 +272,6 @@ class DS5Controller extends BaseController {
|
||||
}
|
||||
|
||||
async _getInfo(is_edge) {
|
||||
const { l } = this;
|
||||
// Device-only: collect info and return a common structure; do not touch the DOM
|
||||
try {
|
||||
console.log("Fetching DS5 info...");
|
||||
@@ -352,9 +352,9 @@ class DS5Controller extends BaseController {
|
||||
const lockRes = await this.nvsLock();
|
||||
if(!lockRes.ok) throw (lockRes.error || new Error("NVS lock failed"));
|
||||
|
||||
return { success: true, message: this.l("Changes saved successfully") };
|
||||
return { success: true, message: l("Changes saved successfully") };
|
||||
} catch(error) {
|
||||
throw new Error(this.l("Error while saving changes"), { cause: error });
|
||||
throw new Error(l("Error while saving changes"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ class DS5Controller extends BaseController {
|
||||
const data = await this.receiveFeatureReport(0x81);
|
||||
} catch(error) {
|
||||
await sleep(500);
|
||||
throw new Error(this.l("NVS Unlock failed"), { cause: error });
|
||||
throw new Error(l("NVS Unlock failed"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ class DS5Controller extends BaseController {
|
||||
await this.sendFeatureReport(128, [base,num])
|
||||
const pcba_id = lf("ds5_pcba_id", await this.receiveFeatureReport(129));
|
||||
if(pcba_id.getUint8(1) != base || pcba_id.getUint8(2) != num || pcba_id.getUint8(3) != 2) {
|
||||
return this.l("error");
|
||||
return l("error");
|
||||
}
|
||||
if(decode)
|
||||
return new TextDecoder().decode(pcba_id.buffer.slice(4, 4+length));
|
||||
@@ -539,7 +539,7 @@ class DS5Controller extends BaseController {
|
||||
if(a == 0x05) return "BDM-030";
|
||||
if(a == 0x06) return "BDM-040";
|
||||
if(a == 0x07 || a == 0x08) return "BDM-050";
|
||||
return this.l("Unknown");
|
||||
return l("Unknown");
|
||||
}
|
||||
|
||||
async getInMemoryModuleData() {
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
|
||||
import DS5Controller from './ds5-controller.js';
|
||||
import { sleep, dec2hex32, la, lf } from '../utils.js';
|
||||
import { l } from "../translations.js";
|
||||
|
||||
/**
|
||||
* DualSense Edge (DS5 Edge) Controller implementation
|
||||
*/
|
||||
class DS5EdgeController extends DS5Controller {
|
||||
constructor(device, uiDependencies = {}) {
|
||||
super(device, uiDependencies);
|
||||
constructor(device) {
|
||||
super(device);
|
||||
this.model = "DS5_Edge";
|
||||
this.finetuneMaxValue = 4095; // 12-bit max value for DS5 Edge
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
const { l } = this;
|
||||
|
||||
// DS5 Edge uses the same info structure as DS5 but with is_edge=true
|
||||
const result = await this._getInfo(true);
|
||||
|
||||
@@ -41,12 +40,12 @@ class DS5EdgeController extends DS5Controller {
|
||||
if(ret) {
|
||||
return {
|
||||
success: true,
|
||||
message: "<b>" + this.l("Changes saved successfully") + "</b>.<br><br>" + this.l("If the calibration is not stored permanently, please double-check the wirings of the hardware mod."),
|
||||
message: "<b>" + l("Changes saved successfully") + "</b>.<br><br>" + l("If the calibration is not stored permanently, please double-check the wirings of the hardware mod."),
|
||||
isHtml: true
|
||||
};
|
||||
}
|
||||
} catch(error) {
|
||||
throw new Error(this.l("Error while saving changes"), { cause: error });
|
||||
throw new Error(l("Error while saving changes"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +67,7 @@ class DS5EdgeController extends DS5Controller {
|
||||
await sleep(200);
|
||||
const ret = await this.waitUntilWritten([21, 6, 2]);
|
||||
if(!ret) {
|
||||
throw new Error(this.l("Cannot unlock") + " " + this.l(m_name));
|
||||
throw new Error(l("Cannot unlock") + " " + l(m_name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +78,7 @@ class DS5EdgeController extends DS5Controller {
|
||||
await sleep(200);
|
||||
const ret = await this.waitUntilWritten([21, 4, 2]);
|
||||
if(!ret) {
|
||||
throw new Error(this.l("Cannot lock") + " " + this.l(m_name));
|
||||
throw new Error(l("Cannot lock") + " " + l(m_name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ class DS5EdgeController extends DS5Controller {
|
||||
await sleep(200);
|
||||
const ret = await this.waitUntilWritten([21, 3, 2]);
|
||||
if(!ret) {
|
||||
throw new Error(this.l("Cannot store data into") + " " + this.l(m_name));
|
||||
throw new Error(l("Cannot store data into") + " " + l(m_name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ async function connect() {
|
||||
initAnalyticsApi(app); // init with gu and jg
|
||||
|
||||
// Initialize controller manager with translation function
|
||||
controller = initControllerManager({ l, handleNvStatusUpdate });
|
||||
controller = initControllerManager({ handleNvStatusUpdate });
|
||||
controller.setInputHandler(handleControllerInput);
|
||||
|
||||
la("begin");
|
||||
@@ -224,7 +224,7 @@ async function continue_connection({data, device}) {
|
||||
|
||||
try {
|
||||
// Create controller instance using factory
|
||||
controllerInstance = ControllerFactory.createControllerInstance(device, { l });
|
||||
controllerInstance = ControllerFactory.createControllerInstance(device);
|
||||
controller.setControllerInstance(controllerInstance);
|
||||
|
||||
info = await controllerInstance.getInfo();
|
||||
@@ -1058,7 +1058,7 @@ window.show_donate_modal = show_donate_modal;
|
||||
window.board_model_info = board_model_info;
|
||||
window.edge_color_info = edge_color_info;
|
||||
window.show_quick_test_modal = () => {
|
||||
show_quick_test_modal(controller, { l }).catch(error => {
|
||||
show_quick_test_modal(controller).catch(error => {
|
||||
throw new Error("Failed to show quick test modal", { cause: error });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import { l } from '../translations.js';
|
||||
|
||||
const TEST_SEQUENCE = ['usb', 'buttons', 'adaptive', 'haptic', 'lights', 'speaker', 'headphone', 'microphone'];
|
||||
const TEST_NAMES = {
|
||||
'usb': 'USB Connector',
|
||||
@@ -39,9 +41,8 @@ const BUTTON_INFILL_MAPPING = {
|
||||
* Handles controller feature testing including haptic feedback, adaptive triggers, speaker, and microphone functionality
|
||||
*/
|
||||
export class QuickTestModal {
|
||||
constructor(controllerInstance, { l }) {
|
||||
constructor(controllerInstance) {
|
||||
this.controller = controllerInstance;
|
||||
this.l = l;
|
||||
|
||||
this.resetAllTests();
|
||||
|
||||
@@ -119,8 +120,9 @@ export class QuickTestModal {
|
||||
/**
|
||||
* Apply skipped tests to the UI (rebuild accordion with non-skipped tests)
|
||||
*/
|
||||
_applySkippedTestsToUI() {
|
||||
async _applySkippedTestsToUI() {
|
||||
this._buildDynamicAccordion();
|
||||
await this._initSvgController();
|
||||
this._updateSkippedTestsDropdown();
|
||||
}
|
||||
|
||||
@@ -131,14 +133,13 @@ export class QuickTestModal {
|
||||
const $accordion = $('#quickTestAccordion');
|
||||
$accordion.empty();
|
||||
|
||||
// Get non-skipped tests in order
|
||||
let activeTests = TEST_SEQUENCE.filter(testType => !this.state.skippedTests.includes(testType));
|
||||
// Get supported tests from the controller
|
||||
const supportedTests = this.controller.getSupportedQuickTests();
|
||||
|
||||
// Filter out unsupported tests for DS4 controllers
|
||||
if (this.controller.getModel() === 'DS4') {
|
||||
const ds4UnsupportedTests = ['adaptive', 'speaker', 'microphone'];
|
||||
activeTests = activeTests.filter(testType => !ds4UnsupportedTests.includes(testType));
|
||||
}
|
||||
// Get non-skipped tests in order, filtered by what the controller supports
|
||||
let activeTests = TEST_SEQUENCE.filter(testType =>
|
||||
!this.state.skippedTests.includes(testType) && supportedTests.includes(testType)
|
||||
);
|
||||
|
||||
activeTests.forEach(testType => {
|
||||
const accordionItem = this._createAccordionItem(testType);
|
||||
@@ -223,7 +224,7 @@ export class QuickTestModal {
|
||||
</div>
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<span class="ds-i18n">The test will automatically pass when all buttons have turned green.</span>
|
||||
<span class="ds-i18n">Long-press <kbd>Circle</kbd> to skip ahead.</span>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button type="button" class="btn btn-success" id="buttons-pass-btn" onclick="markTestResult('buttons', true)">
|
||||
@@ -239,7 +240,7 @@ export class QuickTestModal {
|
||||
`;
|
||||
case 'haptic':
|
||||
return `
|
||||
<p class="ds-i18n">This test will activate the controller's vibration motors for 3 seconds.</p>
|
||||
<p class="ds-i18n">This test will activate the controller's vibration motors, first the heavy one, and then the light one.</p>
|
||||
<p class="ds-i18n"><strong>Instructions:</strong> Feel for vibration in the controller.</p>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button type="button" class="btn btn-success" id="haptic-pass-btn" onclick="markTestResult('haptic', true)">
|
||||
@@ -267,10 +268,6 @@ export class QuickTestModal {
|
||||
return `
|
||||
<p class="ds-i18n">This test will cycle through red, green, and blue colors on the controller lightbar, animate the player indicator lights, and flash the mute button.</p>
|
||||
<p class="ds-i18n"><strong>Instructions:</strong> Watch the controller lights change colors, the player lights animate, and the mute button flash.</p>
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<span class="ds-i18n">The lights will automatically cycle through colors and patterns until you mark the test as passed or failed.</span>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button type="button" class="btn btn-success" id="lights-pass-btn" onclick="markTestResult('lights', true)">
|
||||
<i class="fas fa-check me-1"></i><span class="ds-i18n">Pass</span>
|
||||
@@ -375,13 +372,13 @@ export class QuickTestModal {
|
||||
const allTestsCompleted = this._areAllTestsCompleted();
|
||||
|
||||
if (activeTest === 'buttons') {
|
||||
$instructionsText.html(this.l('Test all buttons, or long-press <kbd>Square</kbd> to Pass and <kbd>Cross</kbd> to Fail, or <kbd>Circle</kbd> to skip.'));
|
||||
$instructionsText.html(l('Test all buttons, or long-press <kbd>Square</kbd> to Pass and <kbd>Cross</kbd> to Fail, or <kbd>Circle</kbd> to skip.'));
|
||||
} else if (activeTest) {
|
||||
$instructionsText.html(this.l('Press <kbd>Square</kbd> to Pass, <kbd>Cross</kbd> to Fail, or <kbd>Circle</kbd> to skip.'));
|
||||
$instructionsText.html(l('Press <kbd>Square</kbd> to Pass, <kbd>Cross</kbd> to Fail, or <kbd>Circle</kbd> to skip.'));
|
||||
} else if (allTestsCompleted) {
|
||||
$instructionsText.html(this.l('Press <kbd>Circle</kbd> to close, or <kbd>Square</kbd> to start over'));
|
||||
$instructionsText.html(l('Press <kbd>Circle</kbd> to close, or <kbd>Square</kbd> to start over'));
|
||||
} else {
|
||||
$instructionsText.html(this.l('Press <kbd>Square</kbd> to begin or <kbd>Circle</kbd> to close'));
|
||||
$instructionsText.html(l('Press <kbd>Square</kbd> to begin or <kbd>Circle</kbd> to close'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,6 +414,8 @@ export class QuickTestModal {
|
||||
$modal.on('hidden.bs.modal', this._boundModalHidden);
|
||||
$modal.on('shown.bs.modal', () => {
|
||||
this._updateInstructions();
|
||||
// Automatically start the test sequence when modal opens
|
||||
this._startTestSequence();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -518,6 +517,10 @@ export class QuickTestModal {
|
||||
});
|
||||
|
||||
this._resetButtonColors();
|
||||
|
||||
// Hide mute button for DS4 controllers
|
||||
const model = this.controller.getModel();
|
||||
this._setMuteVisibility(model !== 'DS4');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -530,6 +533,28 @@ export class QuickTestModal {
|
||||
return this.svgContainer.querySelector(`#${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of buttons to test based on controller model
|
||||
* DS4 controllers don't have a mute button
|
||||
*/
|
||||
_getAvailableButtons() {
|
||||
const model = this.controller.getModel();
|
||||
if (model === 'DS4') {
|
||||
return BUTTONS.filter(button => button !== 'mute');
|
||||
}
|
||||
return BUTTONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mute button visibility in the quick test SVG
|
||||
*/
|
||||
_setMuteVisibility(show) {
|
||||
const muteOutline = this._getQuickTestElement('qt-Mute_outline');
|
||||
const muteInfill = this._getQuickTestElement('qt-Mute_infill');
|
||||
if (muteOutline) muteOutline.style.display = show ? '' : 'none';
|
||||
if (muteInfill) muteInfill.style.display = show ? '' : 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color for SVG group elements
|
||||
*/
|
||||
@@ -633,14 +658,14 @@ export class QuickTestModal {
|
||||
// Initialize button press counts only if not already initialized
|
||||
if (!this.state.buttonPressCount || Object.keys(this.state.buttonPressCount).length === 0) {
|
||||
this.state.buttonPressCount = {};
|
||||
BUTTONS.forEach(button => {
|
||||
this._getAvailableButtons().forEach(button => {
|
||||
this.state.buttonPressCount[button] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Check for any buttons that are already stuck pressed when the test starts
|
||||
// and draw them as pressed
|
||||
BUTTONS.forEach(button => {
|
||||
this._getAvailableButtons().forEach(button => {
|
||||
if (this.controller.button_states[button] === true) {
|
||||
this._setButtonPressed(button, true);
|
||||
}
|
||||
@@ -686,7 +711,7 @@ export class QuickTestModal {
|
||||
* Check if all buttons have been pressed the required number of times
|
||||
*/
|
||||
_checkButtonsTestComplete() {
|
||||
const allPressed = BUTTONS.every(button => {
|
||||
const allPressed = this._getAvailableButtons().every(button => {
|
||||
const count = this.state.buttonPressCount[button] || 0;
|
||||
// Special buttons (create, options, mute, ps) only need 1 press
|
||||
const checkOnce = ['create', 'touchpad', 'options', 'l3', 'ps', 'mute', 'r3'].includes(button);
|
||||
@@ -706,7 +731,7 @@ export class QuickTestModal {
|
||||
resetButtonsTest() {
|
||||
// Reset button press counts
|
||||
this.state.buttonPressCount = {};
|
||||
BUTTONS.forEach(button => {
|
||||
this._getAvailableButtons().forEach(button => {
|
||||
this.state.buttonPressCount[button] = 0;
|
||||
});
|
||||
|
||||
@@ -717,7 +742,7 @@ export class QuickTestModal {
|
||||
this._resetButtonColors();
|
||||
|
||||
// Check for any buttons that are already stuck pressed and draw them as pressed
|
||||
BUTTONS.forEach(button => {
|
||||
this._getAvailableButtons().forEach(button => {
|
||||
if (this.controller.button_states[button] === true) {
|
||||
this._setButtonPressed(button, true);
|
||||
}
|
||||
@@ -730,9 +755,11 @@ export class QuickTestModal {
|
||||
async _startHapticTest() {
|
||||
this._startIconAnimation('haptic');
|
||||
await this.controller.setVibration({ heavyLeft: 255, lightRight: 0, duration: 500 }, async () => {
|
||||
await this.controller.setVibration({ heavyLeft: 0, lightRight: 255, duration: 500 });
|
||||
await setTimeout(async () => {
|
||||
await this.controller.setVibration({ heavyLeft: 0, lightRight: 255, duration: 500 });
|
||||
}, 500);
|
||||
});
|
||||
setTimeout(() => { this._stopIconAnimation('haptic'); }, 1000); }
|
||||
setTimeout(() => { this._stopIconAnimation('haptic'); }, 1500); }
|
||||
|
||||
/**
|
||||
* Start adaptive trigger test
|
||||
@@ -984,12 +1011,12 @@ export class QuickTestModal {
|
||||
|
||||
if (passed) {
|
||||
$statusBadge.attr('class', 'badge bg-success me-2');
|
||||
$statusBadge.text(this.l('Passed'));
|
||||
$statusBadge.text(l('Passed'));
|
||||
$accordionItem.addClass('border-success');
|
||||
$accordionButton.css('backgroundColor', 'rgba(25, 135, 84, 0.1)'); // Light green background
|
||||
} else {
|
||||
$statusBadge.attr('class', 'badge bg-danger me-2');
|
||||
$statusBadge.text(this.l('Failed'));
|
||||
$statusBadge.text(l('Failed'));
|
||||
$accordionItem.addClass('border-danger');
|
||||
$accordionButton.css('backgroundColor', 'rgba(220, 53, 69, 0.1)'); // Light red background
|
||||
}
|
||||
@@ -1010,7 +1037,7 @@ export class QuickTestModal {
|
||||
/**
|
||||
* Skip a test and remove it from the accordion
|
||||
*/
|
||||
skipTest(testType) {
|
||||
async skipTest(testType) {
|
||||
// Add to skipped tests if not already there
|
||||
if (!this.state.skippedTests.includes(testType)) {
|
||||
this.state.skippedTests.push(testType);
|
||||
@@ -1031,6 +1058,7 @@ export class QuickTestModal {
|
||||
|
||||
// Rebuild the accordion without the skipped test
|
||||
this._buildDynamicAccordion();
|
||||
await this._initSvgController();
|
||||
|
||||
this._updateSkippedTestsDropdown();
|
||||
this._updateTestSummary();
|
||||
@@ -1041,7 +1069,7 @@ export class QuickTestModal {
|
||||
/**
|
||||
* Add a test back from the skipped list
|
||||
*/
|
||||
addTestBack(testType) {
|
||||
async addTestBack(testType) {
|
||||
// Remove from skipped tests
|
||||
const index = this.state.skippedTests.indexOf(testType);
|
||||
if (index > -1) {
|
||||
@@ -1055,6 +1083,7 @@ export class QuickTestModal {
|
||||
|
||||
// Rebuild the accordion with the restored test
|
||||
this._buildDynamicAccordion();
|
||||
await this._initSvgController();
|
||||
|
||||
this._updateSkippedTestsDropdown();
|
||||
this._updateTestSummary();
|
||||
@@ -1077,7 +1106,7 @@ export class QuickTestModal {
|
||||
$list.empty();
|
||||
|
||||
this.state.skippedTests.forEach(testType => {
|
||||
const testName = this.l(TEST_NAMES[testType]);
|
||||
const testName = l(TEST_NAMES[testType]);
|
||||
const $item = $(`
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" onclick="addTestBack('${testType}'); return false;">
|
||||
@@ -1099,14 +1128,13 @@ export class QuickTestModal {
|
||||
let passed = 0;
|
||||
let skipped = this.state.skippedTests.length;
|
||||
|
||||
// Get active tests for this controller model
|
||||
let activeTests = TEST_SEQUENCE.filter(testType => !this.state.skippedTests.includes(testType));
|
||||
// Get supported tests from the controller
|
||||
const supportedTests = this.controller.getSupportedQuickTests();
|
||||
|
||||
// Filter out unsupported tests for DS4 controllers
|
||||
if (this.controller.getModel() === 'DS4') {
|
||||
const ds4UnsupportedTests = ['adaptive', 'speaker', 'microphone'];
|
||||
activeTests = activeTests.filter(testType => !ds4UnsupportedTests.includes(testType));
|
||||
}
|
||||
// Get active tests for this controller model (non-skipped and supported)
|
||||
let activeTests = TEST_SEQUENCE.filter(testType =>
|
||||
!this.state.skippedTests.includes(testType) && supportedTests.includes(testType)
|
||||
);
|
||||
|
||||
activeTests.forEach(test => {
|
||||
if (this.state[test] !== null) {
|
||||
@@ -1119,12 +1147,12 @@ export class QuickTestModal {
|
||||
const totalProcessed = completed + skipped;
|
||||
|
||||
if (totalProcessed === 0) {
|
||||
$summary.text(this.l('No tests completed yet.'));
|
||||
$summary.text(l('No tests completed yet.'));
|
||||
$summary.attr('class', 'text-muted ds-i18n');
|
||||
} else {
|
||||
let summaryText = this.l(`${completed}/${numTests} tests completed. ${passed} passed, ${completed - passed} failed.`);
|
||||
let summaryText = `${completed}/${numTests} ${l("tests completed")}. ${passed} ${"passed"}, ${completed - passed} ${"failed"}.`;
|
||||
if (skipped > 0) {
|
||||
summaryText += this.l(` ${skipped} skipped.`);
|
||||
summaryText += ` ${skipped} l({"skipped"}).`;
|
||||
}
|
||||
$summary.text(summaryText);
|
||||
$summary.attr('class', totalProcessed === numTests ? 'text-success' : 'text-info');
|
||||
@@ -1239,7 +1267,7 @@ export class QuickTestModal {
|
||||
* Track button presses for the buttons test
|
||||
*/
|
||||
_trackButtonPresses(changes) {
|
||||
BUTTONS.forEach(button => {
|
||||
this._getAvailableButtons().forEach(button => {
|
||||
const handleLongpress = ['cross', 'square', 'triangle', 'circle'].includes(button);
|
||||
if (changes[button] === true) {
|
||||
// Button pressed - increment count and show dark blue infill
|
||||
@@ -1343,9 +1371,9 @@ export class QuickTestModal {
|
||||
/**
|
||||
* Start the test sequence from the beginning
|
||||
*/
|
||||
_startTestSequence() {
|
||||
async _startTestSequence() {
|
||||
// First, reset all tests to ensure clean state
|
||||
this.resetAllTests();
|
||||
await this.resetAllTests();
|
||||
|
||||
// After a short delay, start with the first non-skipped test
|
||||
setTimeout(() => {
|
||||
@@ -1354,7 +1382,10 @@ export class QuickTestModal {
|
||||
|
||||
if (firstAvailableTest) {
|
||||
const $firstCollapse = $(`#${firstAvailableTest}-test-collapse`);
|
||||
bootstrap.Collapse.getOrCreateInstance($firstCollapse[0]).show();
|
||||
// Check if the element exists in the DOM before trying to create a Collapse instance
|
||||
if ($firstCollapse.length > 0 && $firstCollapse[0]) {
|
||||
bootstrap.Collapse.getOrCreateInstance($firstCollapse[0]).show();
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
@@ -1367,8 +1398,18 @@ export class QuickTestModal {
|
||||
if (!activeTest) return;
|
||||
|
||||
const currentIndex = TEST_SEQUENCE.indexOf(activeTest);
|
||||
const previousIndex = currentIndex > 0 ? currentIndex - 1 : 0;
|
||||
if(previousIndex == currentIndex) return;
|
||||
|
||||
// Find the previous non-skipped test
|
||||
let previousIndex = -1;
|
||||
for (let i = currentIndex - 1; i >= 0; i--) {
|
||||
if (!this.state.skippedTests.includes(TEST_SEQUENCE[i])) {
|
||||
previousIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no previous test found, stay on current
|
||||
if (previousIndex === -1) return;
|
||||
|
||||
const previousTest = TEST_SEQUENCE[previousIndex];
|
||||
|
||||
@@ -1380,14 +1421,17 @@ export class QuickTestModal {
|
||||
// Expand previous test after a short delay
|
||||
setTimeout(() => {
|
||||
const $previousCollapse = $(`#${previousTest}-test-collapse`);
|
||||
bootstrap.Collapse.getOrCreateInstance($previousCollapse[0]).show();
|
||||
// Check if the element exists in the DOM before trying to create a Collapse instance
|
||||
if ($previousCollapse.length > 0 && $previousCollapse[0]) {
|
||||
bootstrap.Collapse.getOrCreateInstance($previousCollapse[0]).show();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all tests to initial state
|
||||
*/
|
||||
resetAllTests() {
|
||||
async resetAllTests() {
|
||||
// Clear any active long-press timers before resetting state
|
||||
this._clearAllLongPressTimers();
|
||||
|
||||
@@ -1409,7 +1453,7 @@ export class QuickTestModal {
|
||||
const $accordionButton = $accordionItem.find('.accordion-button');
|
||||
|
||||
$statusBadge.attr('class', 'badge bg-secondary me-2');
|
||||
$statusBadge.text(this.l('Not tested'));
|
||||
$statusBadge.text(l('Not tested'));
|
||||
$accordionItem.removeClass('border-success border-danger');
|
||||
$accordionButton.css('backgroundColor', ''); // Clear background color
|
||||
|
||||
@@ -1423,7 +1467,7 @@ export class QuickTestModal {
|
||||
});
|
||||
|
||||
// Apply skipped tests to UI (hide skipped items)
|
||||
this._applySkippedTestsToUI();
|
||||
await this._applySkippedTestsToUI();
|
||||
|
||||
this._updateTestSummary();
|
||||
|
||||
@@ -1472,12 +1516,12 @@ export function quicktest_handle_controller_input(changes) {
|
||||
/**
|
||||
* Show the Quick Test modal (legacy function for backward compatibility)
|
||||
*/
|
||||
export async function show_quick_test_modal(controller, { l } = {}) {
|
||||
export async function show_quick_test_modal(controller) {
|
||||
// Destroy any existing instance
|
||||
destroyCurrentInstance();
|
||||
|
||||
// Create new instance
|
||||
currentQuickTestInstance = new QuickTestModal(controller, { l });
|
||||
currentQuickTestInstance = new QuickTestModal(controller);
|
||||
await currentQuickTestInstance.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ export function l(text) {
|
||||
const [out] = translationState.lang_cur[text] || [];
|
||||
if(out) return out;
|
||||
|
||||
console.log("Missing translation for: '" + text + "'");
|
||||
console.log(`Missing translation for "${text}"`);
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ function lang_translate(target_file, target_lang, target_direction) {
|
||||
if (translatedText) {
|
||||
$(item).html(translatedText);
|
||||
} else {
|
||||
console.log("Cannot find mapping for " + originalText);
|
||||
console.log(`Cannot find mapping for "${originalText}"`);
|
||||
$(item).html(originalText);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user