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
15
css/main.css
15
css/main.css
@@ -83,6 +83,16 @@ dl.row dd {
|
||||
animation: pulse 1s ease-in-out infinite !important;
|
||||
}
|
||||
|
||||
/* Quick Test accordion button height reduction */
|
||||
#quickTestAccordion .accordion-button {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
/* Quick Test accordion body tint */
|
||||
#quickTestAccordion .accordion-collapse .accordion-body {
|
||||
background-color: rgba(13, 125, 253, 0.05);
|
||||
}
|
||||
|
||||
/* Skip button hover behavior */
|
||||
.skip-btn {
|
||||
opacity: 0;
|
||||
@@ -102,3 +112,8 @@ dl.row dd {
|
||||
.blink-text {
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
/* Set text color to red for internationalized elements */
|
||||
/* .ds-i18n {
|
||||
color: red;
|
||||
} */
|
||||
|
||||
12
index.html
12
index.html
@@ -138,9 +138,9 @@
|
||||
<br>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-outline-primary ds-btn ds-i18n mb-3" onclick="show_quick_test_modal()" id="quick-test-btn">
|
||||
<button type="button" class="btn btn-outline-primary ds-btn mb-3" onclick="show_quick_test_modal()" id="quick-test-btn">
|
||||
<i class="fas fa-vial" id="quick-test-icon"></i>
|
||||
<span id="quick-test-text">Quick Test</span>
|
||||
<span id="quick-test-text" class="ds-i18n">Quick Test</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -153,13 +153,13 @@
|
||||
<div class="col-md-6 col-sm-12" style="min-width: 330px;">
|
||||
<div class="vstack gap-2 p-2">
|
||||
<button type="button" class="btn btn-primary ds-btn" onclick="calibrate_stick_centers()" id="four-step-center-calib" style="display: none;">
|
||||
1. <span ds-i18n>Calibrate stick center</span>
|
||||
1. <span class="ds-i18n">Calibrate stick center</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary ds-btn" onclick="auto_calibrate_stick_centers()" id="quick-center-calib" style="display: none;">
|
||||
1. <span ds-i18n>Calibrate stick center</span>
|
||||
1. <span class="ds-i18n">Calibrate stick center</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary ds-btn" onclick="calibrate_range()">
|
||||
2. <span ds-i18n>Calibrate stick range</span>
|
||||
2. <span class="ds-i18n">Calibrate stick range</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary ds-btn" onclick="ds5_finetune()" id="ds5finetune">
|
||||
3. <span class="ds-i18n">Finetune stick calibration</span>
|
||||
@@ -318,7 +318,7 @@
|
||||
<footer class="fixed-bottom bg-body-tertiary border-top">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column flex-sm-row justify-content-between py-3" id="footbody">
|
||||
<p class="mb-0"><a target="_blank" href="https://github.com/dualshock-tools/dualshock-tools.github.io/commits/main/"><span class="ds-i18n">Version</span> 2.18 beta 1</a> (2025-10-02) - <a href="#" class="ds-i18n" onclick="show_donate_modal();">Support this project</a> <span id="authorMsg"></span></p>
|
||||
<p class="mb-0"><a target="_blank" href="https://github.com/dualshock-tools/dualshock-tools.github.io/commits/main/"><span class="ds-i18n">Version</span> 2.18 beta 2</a> (2025-10-03) - <a href="#" class="ds-i18n" onclick="show_donate_modal();">Support this project</a> <span id="authorMsg"></span></p>
|
||||
|
||||
<ul class="list-unstyled d-flex mb-0">
|
||||
<li class="ms-3"><a class="link-body-emphasis" href="mailto:ds4@the.al" target="_blank"><svg class="bi" width="24" height="24"><use xlink:href="#mail"/></svg></a></li>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
<div class="alert alert-info finetune-circularity-mode" role="alert">
|
||||
<p>
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span class="ds-i18n">While holding the stick to be adjusted straight up/down/left/right, make adjustments until you see lightblue sectors in all four directions after circling the stick both left and right. Then use the<svg width="24" height="24" style="vertical-align: -6px;"><use xlink:href="#arrows-alt"/></svg>button to increase the non-circularity.</span>
|
||||
<span class="ds-i18n">While holding the stick to be adjusted straight up/down/left/right, make adjustments until you see lightblue sectors in all four directions after circling the stick both left and right. Then use the</span>
|
||||
<svg width="24" height="24" style="vertical-align: -6px;"><use xlink:href="#arrows-alt"/></svg>
|
||||
<span class="ds-i18n">to increase the non-circularity.</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<i class="fas fa-hand-point-up"></i>
|
||||
<span class="ds-i18n"><strong>Average circularity error:</strong> smaller is not always better! Aim for 7-9 %.</span>
|
||||
<a class="btn-link text-decoration-none" href="#" id="learn-more-link">Learn more...</a>
|
||||
<a class="btn-link text-decoration-none ds-i18n" href="#" id="learn-more-link">Learn more...</a>
|
||||
</p>
|
||||
<p class="mb-0" id="learn-more-text" style="display: none;">
|
||||
<span class="ds-i18n">Sony controllers come from the factory calibrated to have an average circularity error of nearly 10 %, and this is now what games expect. Too perfect circularity can make movements and aim feel stiff and unresponsive in some games.</span>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6 class="ds-i18n">Test Summary:</h6>
|
||||
<h6><span class="ds-i18n">Test Summary</span>:</h6>
|
||||
<div id="test-summary" class="text-muted ds-i18n">No tests completed yet.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,20 +13,16 @@
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">
|
||||
<span class="ds-i18n">Progress:</span> <span id="range-progress-text">0%</span>
|
||||
<span class="ds-i18n">Progress</span>: <span id="range-progress-text">0%</span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-3" id="range-calibration-alert" role="alert">
|
||||
<span id="keep-rotating-alert" ><i class="fas fa-info-circle"></i>
|
||||
<small class="ds-i18n">
|
||||
Keep rotating the sticks even if you see no progress!
|
||||
</small>
|
||||
<small class="ds-i18n">Keep rotating the sticks even if you see no progress!</small>
|
||||
</span>
|
||||
<br><br>
|
||||
<small class="ds-i18n">
|
||||
The <b>Done</b> button will unlock after at most 15 seconds. If you press <b>Done</b> without rotating the sticks, the calibration will be incomplete and you will need to repeat it.
|
||||
</small>
|
||||
<small class="ds-i18n">The <b>Done</b> button will unlock after at most 15 seconds. If you press <b>Done</b> without rotating the sticks, the calibration will be incomplete and you will need to repeat it.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
Reference in New Issue
Block a user