mirror of
https://github.com/EmulatorJS/EmulatorJS.git
synced 2026-02-06 11:17:36 +00:00
Merge 22f518792d into 50cb990a87
This commit is contained in:
commit
b87fb2a2fa
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -335,5 +335,6 @@
|
||||
"System Save interval": "System Save interval",
|
||||
"Menu Bar Button": "Menu Bar Button",
|
||||
"visible": "visible",
|
||||
"hidden": "hidden"
|
||||
"hidden": "hidden",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -306,5 +306,6 @@
|
||||
"DPAD_UP": "DPAD ARRIBA",
|
||||
"DPAD_DOWN": "DPAD_ABAJO",
|
||||
"DPAD_LEFT": "DPAD_IZQUIERDA",
|
||||
"DPAD_RIGHT": "DPAD_DERECHO"
|
||||
"DPAD_RIGHT": "DPAD_DERECHO",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -306,5 +306,6 @@
|
||||
"DPAD_UP": "دی پد بالا",
|
||||
"DPAD_DOWN": "دی پد پایین",
|
||||
"DPAD_LEFT": "دی پد چپ",
|
||||
"DPAD_RIGHT": "دی پد راست"
|
||||
"DPAD_RIGHT": "دی پد راست",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -335,5 +335,6 @@
|
||||
"System Save interval": "Intervalle de sauvegarde du système",
|
||||
"Menu Bar Button": "Bouton de la barre de menu",
|
||||
"visible": "visible",
|
||||
"hidden": "masqué"
|
||||
"hidden": "masqué",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "डीपीएडी_यूपी",
|
||||
"DPAD_DOWN": "डीपीएडी_डाउन",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "डीपीएडी_दाएँ"
|
||||
"DPAD_RIGHT": "डीपीएडी_दाएँ",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -298,5 +298,6 @@
|
||||
"DPAD_UP": "DPAD_SU",
|
||||
"DPAD_DOWN": "DPAD_GIU",
|
||||
"DPAD_LEFT": "DPAD_SINISTRA",
|
||||
"DPAD_RIGHT": "DPAD_DESTRA"
|
||||
"DPAD_RIGHT": "DPAD_DESTRA",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "十字キー上",
|
||||
"DPAD_DOWN": "十字キー下",
|
||||
"DPAD_LEFT": "十字キー左",
|
||||
"DPAD_RIGHT": "十字キー右"
|
||||
"DPAD_RIGHT": "十字キー右",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -368,5 +368,6 @@
|
||||
"Screen Recording Audio Bitrate": "화면 녹화 오디오 비트레이트",
|
||||
"customDownload": "상태 파일 다운로드",
|
||||
"customUpload": "상태 파일 업로드",
|
||||
"customScreenshot": "스크린 샷 찍기"
|
||||
"customScreenshot": "스크린 샷 찍기",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -357,5 +357,6 @@
|
||||
"pcsx rearmed gunconadjustx": "pcsx rearmed - gunconadjustx",
|
||||
"pcsx rearmed gunconadjusty": "pcsx rearmed - gunconadjusty",
|
||||
"pcsx rearmed gunconadjustratiox": "pcsx rearmed - gunconadjustratiox",
|
||||
"pcsx rearmed gunconadjustratioy": "pcsx rearmed - gunconadjustratioy"
|
||||
"pcsx rearmed gunconadjustratioy": "pcsx rearmed - gunconadjustratioy",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -306,5 +306,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -297,5 +297,6 @@
|
||||
"DPAD_UP": "DPAD_ВВЕРХ",
|
||||
"DPAD_DOWN": "DPAD_ВНИЗ",
|
||||
"DPAD_LEFT": "DPAD_ВЛЕВО",
|
||||
"DPAD_RIGHT": "DPAD_СПРАВО"
|
||||
"DPAD_RIGHT": "DPAD_СПРАВО",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -306,5 +306,6 @@
|
||||
"DPAD_UP": "DPAD_YUKARI",
|
||||
"DPAD_DOWN": "DPAD_AŞAĞI",
|
||||
"DPAD_LEFT": "DPAD_SOL",
|
||||
"DPAD_RIGHT": "DPAD_SAĞ"
|
||||
"DPAD_RIGHT": "DPAD_SAĞ",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -298,5 +298,6 @@
|
||||
"DPAD_UP": "DPAD_UP",
|
||||
"DPAD_DOWN": "DPAD_DOWN",
|
||||
"DPAD_LEFT": "DPAD_LEFT",
|
||||
"DPAD_RIGHT": "DPAD_RIGHT"
|
||||
"DPAD_RIGHT": "DPAD_RIGHT",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -347,5 +347,6 @@
|
||||
"Movement Anywhere": "Movement Anywhere",
|
||||
"Direct Keyboard Input": "Direct Keyboard Input",
|
||||
"Forward Alt key": "Forward Alt key",
|
||||
"Lock Mouse": "Lock Mouse"
|
||||
"Lock Mouse": "Lock Mouse",
|
||||
"Autofire": "Autofire"
|
||||
}
|
||||
|
||||
@ -238,7 +238,21 @@ class EmulatorJS {
|
||||
this.cheats = [];
|
||||
this.started = false;
|
||||
this.volume = (typeof this.config.volume === "number") ? this.config.volume : 0.5;
|
||||
if (this.config.defaultControllers) this.defaultControllers = this.config.defaultControllers;
|
||||
if (this.config.defaultControllers) {
|
||||
// Merge user config with defaults instead of replacing
|
||||
for (const [player, buttons] of Object.entries(this.config.defaultControllers)) {
|
||||
this.defaultControllers[player] = this.defaultControllers[player] || {};
|
||||
|
||||
for (const [button, config] of Object.entries(buttons)) {
|
||||
this.defaultControllers[player][button] = {
|
||||
...(this.defaultControllers[player][button] || {}),
|
||||
...config
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
this.defaultAutoFireInterval = 100;
|
||||
this.autofireIntervals = {};
|
||||
this.muted = false;
|
||||
this.paused = true;
|
||||
this.missingLang = [];
|
||||
@ -1146,7 +1160,7 @@ class EmulatorJS {
|
||||
if (document.activeElement !== this.elements.parent && this.config.noAutoFocus !== true) this.elements.parent.focus();
|
||||
})
|
||||
this.addEventListener(window, "resize", this.handleResize.bind(this));
|
||||
//this.addEventListener(window, "blur", e => console.log(e), true); //TODO - add "click to make keyboard keys work" message?
|
||||
this.addEventListener(window, "blur", () => this.stopAllAutofire());
|
||||
|
||||
let counter = 0;
|
||||
this.elements.statePopupPanel = this.createPopup("", {}, true);
|
||||
@ -2490,12 +2504,14 @@ class EmulatorJS {
|
||||
this.controls = JSON.parse(JSON.stringify(this.defaultControllers));
|
||||
const body = this.createPopup("Control Settings", {
|
||||
"Reset": () => {
|
||||
this.stopAllAutofire();
|
||||
this.controls = JSON.parse(JSON.stringify(this.defaultControllers));
|
||||
this.setupKeys();
|
||||
this.checkGamepadInputs();
|
||||
this.saveSettings();
|
||||
},
|
||||
"Clear": () => {
|
||||
this.stopAllAutofire();
|
||||
this.controls = { 0: {}, 1: {}, 2: {}, 3: {} };
|
||||
this.setupKeys();
|
||||
this.checkGamepadInputs();
|
||||
@ -2991,7 +3007,7 @@ class EmulatorJS {
|
||||
leftPadding.innerHTML = " ";
|
||||
|
||||
const aboutParent = this.createElement("div");
|
||||
aboutParent.style = "font-size:12px;width:50%;float:left;";
|
||||
aboutParent.style = "font-size:12px;width:40%;float:left;";
|
||||
const gamepad = this.createElement("div");
|
||||
gamepad.style = "text-align:center;width:50%;float:left;";
|
||||
gamepad.innerText = this.localization("Gamepad");
|
||||
@ -3001,12 +3017,22 @@ class EmulatorJS {
|
||||
keyboard.innerText = this.localization("Keyboard");
|
||||
aboutParent.appendChild(keyboard);
|
||||
|
||||
const setHeader = this.createElement("div");
|
||||
setHeader.style = "font-size:12px;width:15%;float:left;text-align:center;";
|
||||
setHeader.innerHTML = " ";
|
||||
|
||||
const autofireHeader = this.createElement("div");
|
||||
autofireHeader.style = "font-size:12px;width:20%;float:left;text-align:center;";
|
||||
autofireHeader.innerText = this.localization("Autofire");
|
||||
|
||||
const headingPadding = this.createElement("div");
|
||||
headingPadding.style = "clear:both;";
|
||||
|
||||
playerTitle.appendChild(gamepadTitle);
|
||||
playerTitle.appendChild(leftPadding);
|
||||
playerTitle.appendChild(aboutParent);
|
||||
playerTitle.appendChild(setHeader);
|
||||
playerTitle.appendChild(autofireHeader);
|
||||
|
||||
if ((this.touch || this.hasTouchScreen) && i === 0) {
|
||||
const vgp = this.createElement("div");
|
||||
@ -3057,7 +3083,7 @@ class EmulatorJS {
|
||||
title.appendChild(label);
|
||||
|
||||
const textBoxes = this.createElement("div");
|
||||
textBoxes.style = "width:50%;float:left;";
|
||||
textBoxes.style = "width:40%;float:left;";
|
||||
|
||||
const textBox1Parent = this.createElement("div");
|
||||
textBox1Parent.style = "width:50%;float:left;padding: 0 5px;";
|
||||
@ -3125,23 +3151,63 @@ class EmulatorJS {
|
||||
textBoxes.appendChild(padding);
|
||||
|
||||
const setButton = this.createElement("div");
|
||||
setButton.style = "width:25%;float:left;";
|
||||
setButton.style = "width:15%;float:left;";
|
||||
const button = this.createElement("a");
|
||||
button.classList.add("ejs_control_set_button");
|
||||
button.innerText = this.localization("Set");
|
||||
setButton.appendChild(button);
|
||||
|
||||
// Autofire checkbox - not available for analog stick axes
|
||||
const autofireColumn = this.createElement("div");
|
||||
autofireColumn.style = "width:20%;float:left;text-align:center;";
|
||||
|
||||
if (!this.analogAxes.includes(k)) {
|
||||
const autofireCheckbox = this.createElement("input");
|
||||
autofireCheckbox.type = "checkbox";
|
||||
autofireCheckbox.style = "cursor:pointer;";
|
||||
autofireCheckbox.checked = this.controls[i][k] && this.controls[i][k].autofire === true;
|
||||
autofireCheckbox.setAttribute("data-player", i);
|
||||
autofireCheckbox.setAttribute("data-button", k);
|
||||
|
||||
// Update checkbox state when controls change
|
||||
buttonListeners.push(() => {
|
||||
autofireCheckbox.checked = this.controls[i][k] && this.controls[i][k].autofire === true;
|
||||
});
|
||||
|
||||
this.addEventListener(autofireCheckbox, "change", (e) => {
|
||||
e.stopPropagation();
|
||||
const playerIdx = parseInt(e.target.getAttribute("data-player"));
|
||||
const buttonIdx = parseInt(e.target.getAttribute("data-button"));
|
||||
if (!this.controls[playerIdx][buttonIdx]) {
|
||||
this.controls[playerIdx][buttonIdx] = {};
|
||||
}
|
||||
this.controls[playerIdx][buttonIdx].autofire = e.target.checked;
|
||||
// Stop any active autofire if unchecked
|
||||
if (!e.target.checked) {
|
||||
this.stopAutofire(playerIdx, buttonIdx);
|
||||
}
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
autofireColumn.appendChild(autofireCheckbox);
|
||||
}
|
||||
|
||||
const padding2 = this.createElement("div");
|
||||
padding2.style = "clear:both;";
|
||||
|
||||
buttonText.appendChild(title);
|
||||
buttonText.appendChild(textBoxes);
|
||||
buttonText.appendChild(setButton);
|
||||
buttonText.appendChild(autofireColumn);
|
||||
buttonText.appendChild(padding2);
|
||||
|
||||
player.appendChild(buttonText);
|
||||
|
||||
this.addEventListener(buttonText, "mousedown", (e) => {
|
||||
// Don't open popup when clicking on the autofire checkbox
|
||||
if (e.target.tagName === "INPUT" && e.target.type === "checkbox") {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.controlPopup.parentElement.parentElement.removeAttribute("hidden");
|
||||
this.controlPopup.innerText = "[ " + controlLabel + " ]\n" + this.localization("Press Keyboard");
|
||||
@ -3308,6 +3374,8 @@ class EmulatorJS {
|
||||
2: {},
|
||||
3: {}
|
||||
}
|
||||
// Analog stick axes - these use 0x7fff values and don't support autofire
|
||||
this.analogAxes = [16, 17, 18, 19, 20, 21, 22, 23];
|
||||
this.keyMap = {
|
||||
0: "",
|
||||
8: "backspace",
|
||||
@ -3435,6 +3503,49 @@ class EmulatorJS {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
getAutofireInterval(playerIndex, buttonIndex) {
|
||||
const control = this.controls[playerIndex] && this.controls[playerIndex][buttonIndex];
|
||||
if (control && typeof control.autoFireInterval === "number") {
|
||||
return control.autoFireInterval;
|
||||
}
|
||||
const settingValue = this.getSettingValue("autofireInterval");
|
||||
return settingValue ? parseInt(settingValue) : this.defaultAutoFireInterval;
|
||||
}
|
||||
isAutofireEnabled(playerIndex, buttonIndex) {
|
||||
const control = this.controls[playerIndex] && this.controls[playerIndex][buttonIndex];
|
||||
return control && control.autofire === true;
|
||||
}
|
||||
startAutofire(playerIndex, buttonIndex, inputValue) {
|
||||
const key = `${playerIndex}-${buttonIndex}`;
|
||||
if (this.autofireIntervals[key]) {
|
||||
return;
|
||||
}
|
||||
let pressed = true;
|
||||
const interval = this.getAutofireInterval(playerIndex, buttonIndex);
|
||||
this.autofireIntervals[key] = setInterval(() => {
|
||||
if (this.paused || !this.gameManager) return;
|
||||
pressed = !pressed;
|
||||
this.gameManager.simulateInput(playerIndex, buttonIndex, pressed ? inputValue : 0);
|
||||
}, interval);
|
||||
}
|
||||
stopAutofire(playerIndex, buttonIndex) {
|
||||
const key = `${playerIndex}-${buttonIndex}`;
|
||||
if (this.autofireIntervals[key]) {
|
||||
clearInterval(this.autofireIntervals[key]);
|
||||
delete this.autofireIntervals[key];
|
||||
this.gameManager.simulateInput(playerIndex, buttonIndex, 0);
|
||||
}
|
||||
}
|
||||
stopAllAutofire() {
|
||||
for (const key in this.autofireIntervals) {
|
||||
clearInterval(this.autofireIntervals[key]);
|
||||
const [playerIndex, buttonIndex] = key.split("-").map(Number);
|
||||
if (this.gameManager) {
|
||||
this.gameManager.simulateInput(playerIndex, buttonIndex, 0);
|
||||
}
|
||||
}
|
||||
this.autofireIntervals = {};
|
||||
}
|
||||
keyChange(e) {
|
||||
if (e.repeat) return;
|
||||
if (!this.started) return;
|
||||
@ -3452,11 +3563,19 @@ class EmulatorJS {
|
||||
}
|
||||
if (this.settingsMenu.style.display !== "none" || this.isPopupOpen() || this.getSettingValue("keyboardInput") === "enabled") return;
|
||||
e.preventDefault();
|
||||
const special = [16, 17, 18, 19, 20, 21, 22, 23];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 30; j++) {
|
||||
if (this.controls[i][j] && this.controls[i][j].value === e.keyCode) {
|
||||
this.gameManager.simulateInput(i, j, (e.type === "keyup" ? 0 : (special.includes(j) ? 0x7fff : 1)));
|
||||
const isAnalog = this.analogAxes.includes(j);
|
||||
const inputValue = isAnalog ? 0x7fff : 1;
|
||||
const isKeyUp = e.type === "keyup";
|
||||
const value = isKeyUp ? 0 : inputValue;
|
||||
|
||||
if (this.isAutofireEnabled(i, j) && !isAnalog) {
|
||||
isKeyUp ? this.stopAutofire(i, j) : this.startAutofire(i, j, inputValue);
|
||||
} else {
|
||||
this.gameManager.simulateInput(i, j, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3489,7 +3608,6 @@ class EmulatorJS {
|
||||
return;
|
||||
}
|
||||
if (this.settingsMenu.style.display !== "none" || this.isPopupOpen()) return;
|
||||
const special = [16, 17, 18, 19, 20, 21, 22, 23];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (gamepadIndex !== i) continue;
|
||||
for (let j = 0; j < 30; j++) {
|
||||
@ -3497,12 +3615,21 @@ class EmulatorJS {
|
||||
continue;
|
||||
}
|
||||
const controlValue = this.controls[i][j].value2;
|
||||
const isAnalog = this.analogAxes.includes(j);
|
||||
|
||||
if (["buttonup", "buttondown"].includes(e.type) && (controlValue === e.label || controlValue === e.index)) {
|
||||
this.gameManager.simulateInput(i, j, (e.type === "buttonup" ? 0 : (special.includes(j) ? 0x7fff : 1)));
|
||||
const inputValue = isAnalog ? 0x7fff : 1;
|
||||
const isButtonUp = e.type === "buttonup";
|
||||
const value = isButtonUp ? 0 : inputValue;
|
||||
|
||||
if (this.isAutofireEnabled(i, j) && !isAnalog) {
|
||||
isButtonUp ? this.stopAutofire(i, j) : this.startAutofire(i, j, inputValue);
|
||||
} else {
|
||||
this.gameManager.simulateInput(i, j, value);
|
||||
}
|
||||
} else if (e.type === "axischanged") {
|
||||
if (typeof controlValue === "string" && controlValue.split(":")[0] === e.axis) {
|
||||
if (special.includes(j)) {
|
||||
if (isAnalog) {
|
||||
if (j === 16 || j === 17) {
|
||||
if (e.value > 0) {
|
||||
this.gameManager.simulateInput(i, 16, 0x7fff * e.value);
|
||||
@ -3915,14 +4042,19 @@ class EmulatorJS {
|
||||
let downValue = info[i].joystickInput === true ? 0x7fff : 1;
|
||||
this.addEventListener(button, "touchstart touchend touchcancel", (e) => {
|
||||
e.preventDefault();
|
||||
const isAnalog = this.analogAxes.includes(value);
|
||||
if (e.type === "touchend" || e.type === "touchcancel") {
|
||||
e.target.classList.remove("ejs_virtualGamepad_button_down");
|
||||
window.setTimeout(() => {
|
||||
this.stopAutofire(0, value);
|
||||
this.gameManager.simulateInput(0, value, 0);
|
||||
})
|
||||
} else {
|
||||
e.target.classList.add("ejs_virtualGamepad_button_down");
|
||||
this.gameManager.simulateInput(0, value, downValue);
|
||||
if (this.isAutofireEnabled(0, value) && !isAnalog) {
|
||||
this.startAutofire(0, value, downValue);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -4433,6 +4565,8 @@ class EmulatorJS {
|
||||
this.gameManager.setAltKeyEnabled(value === "enabled");
|
||||
} else if (option === "lockMouse") {
|
||||
this.enableMouseLock = (value === "enabled");
|
||||
} else if (option === "autofireInterval") {
|
||||
this.defaultAutoFireInterval = parseInt(value);
|
||||
}
|
||||
}
|
||||
menuOptionChanged(option, value) {
|
||||
@ -5098,6 +5232,14 @@ class EmulatorJS {
|
||||
"enabled": this.localization("Enabled"),
|
||||
}, (this.enableMouseLock === true ? "enabled" : "disabled"), inputOptions, true);
|
||||
|
||||
addToMenu(this.localization("Autofire Interval"), "autofireInterval", {
|
||||
"20": "20ms",
|
||||
"50": "50ms",
|
||||
"100": "100ms",
|
||||
"200": "200ms",
|
||||
"500": "500ms",
|
||||
}, "100", inputOptions, true);
|
||||
|
||||
checkForEmptyMenu(inputOptions);
|
||||
|
||||
if (this.saveInBrowserSupported()) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user