mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-05-02 06:26:23 +00:00
Commit retro-b5500 release 1.06:
1. Redesign and simplify delay and time deviation adjustment algorithm in global SetCallback function; reinstate alpha parameter to control adjustment decay rate. 2. Correct memory stores during tape drive backward read. 3. Compare tape image file extensions in case-insensitive manner. 4. Detect dropouts (no flux change) in magnetic tape images. 5. Suppress printed SPO greeting after power-on. 6. Disable (for now) the ConsolePanel lamp test. 7. Update stacker progress meter and stacker-full annunciator when emptying a stacker. 8. Supply (benign) terminating semicolons in ConsolePanel method declarations. 9. Optimize extraction of MSCW address during Character Mode exit. 10. Miscellaneous minor additions and enhancements to scripts in tools/ directory.
This commit is contained in:
@@ -97,6 +97,16 @@ B5500CardPunch.prototype.copyStacker = function copyStacker(ev) {
|
||||
});
|
||||
|
||||
this.emptyStacker(stacker);
|
||||
if (stacker == this.stacker1) {
|
||||
this.stacker1Count = 0;
|
||||
this.$$("CPStacker1Bar").value = 0;
|
||||
this.$$("CPStacker1Full").classList.remove("annunciatorLit");
|
||||
} else if (stacker == this.stacker2) {
|
||||
this.stacker2Count = 0;
|
||||
this.$$("CPStacker2Bar").value = 0;
|
||||
this.$$("CPStacker2Full").classList.remove("annunciatorLit");
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ function B5500ConsolePanel(global, autoPowerUp, shutDown) {
|
||||
this.cc; // B5500CentralControl object
|
||||
this.ccLatches = [0, 0, 0]; // I/O- & interrupt-reporting latches
|
||||
this.ccLightsMap = new Array(6); // Misc annunciator DOM objects
|
||||
this.enableLampTest = false; // Perform lamp test at power-on
|
||||
this.global = global; // Global window object
|
||||
this.intLightsMap = new Array(48); // Interrupt annunciator DOM objects
|
||||
this.lastInterruptMask = 0; // Prior mask of interrupt annunciator lights
|
||||
@@ -67,7 +68,7 @@ B5500ConsolePanel.annOffColor = "#333"; // annunciator lamp off color
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.$$ = function $$(id) {
|
||||
return this.doc.getElementById(id);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.setAnnunciators = function setAnnunciators(showEm) {
|
||||
@@ -78,7 +79,7 @@ B5500ConsolePanel.prototype.setAnnunciators = function setAnnunciators(showEm) {
|
||||
this.$$("RetroLogoImage").style.display = (showEm ? "inline" : "none");
|
||||
this.$$("B5500LogoImage").style.display = (showEm ? "none" : "inline");
|
||||
this.$$("ConfigLabel").style.display = (showEm ? "inline" : "none");
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.evaluateNotReady = function evaluateNotReady(config) {
|
||||
@@ -87,7 +88,7 @@ B5500ConsolePanel.prototype.evaluateNotReady = function evaluateNotReady(config)
|
||||
var lampClass = "whiteButton";
|
||||
|
||||
switch (false) {
|
||||
case config.PA.enabled || config.PA.enabled:
|
||||
case config.PA.enabled || config.PB.enabled:
|
||||
case (config.PA.enabled && !config.PB1L) || (config.PB.enabled && config.PB1L):
|
||||
case config.IO1.enabled || config.IO2.enabled || config.IO3.enabled || config.IO4.enabled:
|
||||
case config.memMod[0].enabled:
|
||||
@@ -97,7 +98,7 @@ B5500ConsolePanel.prototype.evaluateNotReady = function evaluateNotReady(config)
|
||||
}
|
||||
|
||||
this.$$("NotReadyBtn").className = lampClass;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.focusConsole = function focusConsole() {
|
||||
@@ -105,7 +106,7 @@ B5500ConsolePanel.prototype.focusConsole = function focusConsole() {
|
||||
|
||||
this.window.focus();
|
||||
this.$$("LoadBtn").focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.BurroughsLogo_Click = function BurroughsLogo_Click(ev) {
|
||||
@@ -113,7 +114,7 @@ B5500ConsolePanel.prototype.BurroughsLogo_Click = function BurroughsLogo_Click(e
|
||||
|
||||
this.showAnnunciators = !this.showAnnunciators;
|
||||
this.setAnnunciators(this.showAnnunciators);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.B5500Logo_Click = function B5500Logo_Click(ev) {
|
||||
@@ -127,7 +128,7 @@ B5500ConsolePanel.prototype.B5500Logo_Click = function B5500Logo_Click(ev) {
|
||||
this.$$("ConfigLabel").style.display = "none";
|
||||
sysConfig.openConfigUI();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.PowerOnBtn_Click = function PowerOnBtn_Click(ev) {
|
||||
@@ -159,7 +160,7 @@ B5500ConsolePanel.prototype.PowerOnBtn_Click = function PowerOnBtn_Click(ev) {
|
||||
that.$$("PowerOnBtn").className = "greenButton greenLit";
|
||||
that.$$("SysConfigName").textContent = config.configName;
|
||||
that.$$("StorageName").textContent = config.units.DKA.storageName;
|
||||
if (that.showAnnunciators) {
|
||||
if (that.enableLampTest && that.showAnnunciators) {
|
||||
that.lampTest(applyPower.bind(that), config);
|
||||
} else {
|
||||
applyPower(config);
|
||||
@@ -169,7 +170,7 @@ B5500ConsolePanel.prototype.PowerOnBtn_Click = function PowerOnBtn_Click(ev) {
|
||||
|
||||
sysConfig.getSystemConfig(null, youMayPowerOnWhenReady_Gridley); // get current system config
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.PowerOffBtn_Click = function PowerOffBtn_Click(ev) {
|
||||
@@ -198,7 +199,7 @@ B5500ConsolePanel.prototype.PowerOffBtn_Click = function PowerOffBtn_Click(ev) {
|
||||
this.timer = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.HaltBtn_Click = function HaltBtn_Click(ev) {
|
||||
@@ -213,7 +214,7 @@ B5500ConsolePanel.prototype.HaltBtn_Click = function HaltBtn_Click(ev) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.LoadBtn_Click = function LoadBtn_Click(ev) {
|
||||
@@ -250,7 +251,7 @@ B5500ConsolePanel.prototype.LoadBtn_Click = function LoadBtn_Click(ev) {
|
||||
this.window.alert("cc.load() result = " + result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.LoadSelectBtn_Click = function LoadSelectBtn_Click(ev) {
|
||||
@@ -263,7 +264,7 @@ B5500ConsolePanel.prototype.LoadSelectBtn_Click = function LoadSelectBtn_Click(e
|
||||
this.cc.cardLoadSelect = 1;
|
||||
this.$$("LoadSelectBtn").className = "yellowButton yellowLit";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.dumpState = function dumpState(caption) {
|
||||
@@ -354,7 +355,7 @@ B5500ConsolePanel.prototype.dumpState = function dumpState(caption) {
|
||||
B5500Util.openPopup(this.window, "./B5500FramePaper.html", "",
|
||||
"location=no,resizable,scrollbars,status",
|
||||
this, dumpStateOnLoad);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.dumpTape = function dumpTape(caption) {
|
||||
@@ -430,7 +431,7 @@ B5500ConsolePanel.prototype.dumpTape = function dumpTape(caption) {
|
||||
B5500Util.openPopup(this.window, "./B5500FramePaper.html", "",
|
||||
"location=no,resizable,scrollbars,status",
|
||||
this, dumpTapeOnLoad);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.displayCallbackState = function displayCallbackState() {
|
||||
@@ -487,7 +488,7 @@ B5500ConsolePanel.prototype.displayCallbackState = function displayCallbackState
|
||||
|
||||
body.id = oldBody.id;
|
||||
oldBody.parentNode.replaceChild(body, oldBody);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.displayCentralControl = function displayCentralControl() {
|
||||
@@ -550,7 +551,7 @@ B5500ConsolePanel.prototype.displayCentralControl = function displayCentralContr
|
||||
unitBusyChange >>>= 1;
|
||||
x--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.dasBlinkenlichten = function dasBlinkenlichten() {
|
||||
@@ -706,7 +707,7 @@ B5500ConsolePanel.prototype.dasBlinkenlichten = function dasBlinkenlichten() {
|
||||
this.displayCentralControl();
|
||||
}
|
||||
//this.displayCallbackState();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.buildLightMaps = function buildLightMaps() {
|
||||
@@ -730,7 +731,7 @@ B5500ConsolePanel.prototype.buildLightMaps = function buildLightMaps() {
|
||||
spec = B5500CentralControl.unitSpecs[mnem];
|
||||
this.perLightsMap[spec.unitIndex] = this.$$(mnem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.lampTest = function lampTest(callback, callbackParam) {
|
||||
@@ -780,7 +781,7 @@ B5500ConsolePanel.prototype.lampTest = function lampTest(callback, callbackParam
|
||||
this.$$("CentralControl").style.display = "block"; // overrides if !this.cc.poweredUp
|
||||
switchEm(1);
|
||||
setCallback(null, this, 2000, switchEm, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.beforeUnload = function beforeUnload(ev) {
|
||||
@@ -819,7 +820,7 @@ B5500ConsolePanel.prototype.clearStatusLabel = function clearStatusLabel(inSecon
|
||||
this.$$("StatusLabel").textContent = "";
|
||||
this.statusLabelTimer = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500ConsolePanel.prototype.consoleOnload = function consoleOnload(ev) {
|
||||
|
||||
@@ -376,7 +376,7 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
file = ev.target.files[0];
|
||||
fileName = file.name;
|
||||
x = fileName.lastIndexOf(".");
|
||||
fileExt = (x > 0 ? fileName.substring(x) : "");
|
||||
fileExt = (x > 0 ? fileName.substring(x) : "").toLowerCase();
|
||||
writeRingCheck.checked = false;
|
||||
tapeLengthSelect.disabled = true;
|
||||
|
||||
@@ -977,8 +977,8 @@ B5500MagTapeDrive.prototype.bcdReadForward = function bcdReadForward(oddParity)
|
||||
var buffer = this.buffer; // IOUnit buffer
|
||||
var bufLength = this.bufLength // IOUnit buffer length
|
||||
var bufIndex = 0; // current IOUnit buffer offset
|
||||
var c; // current character (tape image frame)
|
||||
var cx; // current character translated to ASCII
|
||||
var c = 0; // current character (tape image frame)
|
||||
var cx = 0; // current character translated to ASCII
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
@@ -998,6 +998,10 @@ B5500MagTapeDrive.prototype.bcdReadForward = function bcdReadForward(oddParity)
|
||||
c &= 0x7F; // zap the start-of-block bit
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (bufIndex > 0) {
|
||||
this.errorMask |= 0x10; // dropout detected: no flux change
|
||||
}
|
||||
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
@@ -1057,8 +1061,8 @@ B5500MagTapeDrive.prototype.bcdReadBackward = function bcdReadBackward(oddParity
|
||||
var buffer = this.buffer; // IOUnit buffer
|
||||
var bufLength = this.bufLength // IOUnit buffer length
|
||||
var bufIndex = 0; // current IOUnit buffer offset
|
||||
var c; // current character (tape image frame)
|
||||
var cx; // current character translated to ASCII
|
||||
var c = 0; // current character (tape image frame)
|
||||
var cx = 0; // current character translated to ASCII
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
@@ -1081,13 +1085,17 @@ B5500MagTapeDrive.prototype.bcdReadBackward = function bcdReadBackward(oddParity
|
||||
} else {
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (bufIndex > 0) {
|
||||
this.errorMask |= 0x10; // dropout detected: no flux change
|
||||
}
|
||||
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
} else if (imgIndex > 0) {
|
||||
c = image[--imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at end of tape, kill the read loop
|
||||
break; // at beginning of tape, kill the read loop
|
||||
}
|
||||
} else {
|
||||
blankCount = 0;
|
||||
@@ -1137,6 +1145,7 @@ B5500MagTapeDrive.prototype.bcdWrite = function bcdWrite(oddParity) {
|
||||
var buffer = this.buffer; // IOUnit buffer
|
||||
var bufLength = this.bufLength // IOUnit buffer length
|
||||
var bufIndex = 0; // current IOUnit buffer offset
|
||||
var c = 0; // current tape character code
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
@@ -1148,13 +1157,24 @@ B5500MagTapeDrive.prototype.bcdWrite = function bcdWrite(oddParity) {
|
||||
if (this.atBOT) {
|
||||
this.setAtBOT(false);
|
||||
}
|
||||
image[imgIndex++] = xlate[buffer[bufIndex++] & 0x7F] | 0x80;
|
||||
|
||||
c = xlate[buffer[bufIndex++] & 0x7F];
|
||||
if (c == 0) {
|
||||
this.errorMask |= 0x10; // dropout detected: attempt to write no flux changes
|
||||
}
|
||||
|
||||
image[imgIndex++] = c | 0x80;
|
||||
while (bufIndex < bufLength) {
|
||||
if (imgIndex >= imgLength) {
|
||||
this.errorMask |= 0x04; // report not ready beyond end of tape
|
||||
break;
|
||||
} else {
|
||||
image[imgIndex++] = xlate[buffer[bufIndex++] & 0x7F];
|
||||
c = xlate[buffer[bufIndex++] & 0x7F];
|
||||
if (c == 0) {
|
||||
this.errorMask |= 0x10; // dropout detected: attempt to write no flux changes
|
||||
}
|
||||
|
||||
image[imgIndex++] = c;
|
||||
}
|
||||
} // while
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal, opti
|
||||
|
||||
this.maxScrollLines = 5000; // Maximum amount of printer scrollback
|
||||
this.charPeriod = 100; // Printer speed, milliseconds per character
|
||||
this.printGreeting = false; // Print initial greeting message
|
||||
|
||||
this.mnemonic = mnemonic; // Unit mnemonic
|
||||
this.unitIndex = unitIndex; // Ready-mask bit number
|
||||
@@ -471,6 +472,16 @@ B5500SPOUnit.prototype.printText = function printText(msg, finish) {
|
||||
this.endOfPaper.scrollIntoView();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.spoInitialize = function spoInitialize() {
|
||||
/* Initializes the SPO after power-on */
|
||||
|
||||
this.setRemote();
|
||||
this.appendEmptyLine("\xA0");
|
||||
this.endOfPaper.scrollIntoView();
|
||||
this.signal(-1); // re-focus the Console window
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.spoOnload = function spoOnload(ev) {
|
||||
/* Initializes the SPO window and user interface */
|
||||
@@ -513,19 +524,14 @@ B5500SPOUnit.prototype.spoOnload = function spoOnload(ev) {
|
||||
B5500SPOUnit.prototype.SPOAlgolGlyphsBtn_onclick.bind(this), false);
|
||||
|
||||
this.window.focus();
|
||||
this.printText("retro-B5500 Emulator Version " + B5500CentralControl.version,
|
||||
function initFinish() {
|
||||
this.setRemote();
|
||||
this.appendEmptyLine("\xA0");
|
||||
this.endOfPaper.scrollIntoView();
|
||||
this.signal(-1); // re-focus the Console window
|
||||
}.bind(this));
|
||||
|
||||
// Kludge for Chrome window.outerWidth/Height timing bug
|
||||
setCallback(null, this, 100, function chromeBug() {
|
||||
this.window.moveTo(screen.availWidth-this.window.outerWidth,
|
||||
screen.availHeight-this.window.outerHeight);
|
||||
});
|
||||
this.window.moveTo(screen.availWidth-this.window.outerWidth,
|
||||
screen.availHeight-this.window.outerHeight);
|
||||
if (this.printGreeting) {
|
||||
this.printText("retro-B5500 Emulator Version " + B5500CentralControl.version,
|
||||
B5500SPOUnit.prototype.spoInitialize);
|
||||
} else {
|
||||
this.spoInitialize();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
|
||||
@@ -86,12 +86,13 @@
|
||||
* Redesign yet again the delay adjustment mechanism with one from the
|
||||
* retro-205 project. Replace window.postMessage yield mechanism with one
|
||||
* from the retro-220 project based on Promise().
|
||||
* 2018-07-05 P.Kimpel
|
||||
* Simplify delay and deviation adjustment algorithm.
|
||||
***********************************************************************/
|
||||
"use strict";
|
||||
|
||||
(function (global) {
|
||||
/* Define a closure for the setCallback() mechanism */
|
||||
var alpha = 0.25; // decay factor for delay deviation adjustment
|
||||
var delayDev = {NUL: 0}; // hash of delay time deviations by category
|
||||
var minTimeout = 4; // minimum setTimeout() threshold, milliseconds
|
||||
var lastTokenNr = 0; // last setCallback token return value
|
||||
@@ -151,11 +152,10 @@
|
||||
to "fcn". If the delay is less than "minTimeout", a setImmediate-like mechanism
|
||||
based on DOM Promise() will be used; otherwise the environment's standard
|
||||
setTimeout mechanism will be used */
|
||||
var adj = 0; // adjustment to delay and delayDev[]
|
||||
var categoryName = (category || "NUL").toString();
|
||||
var delay = callbackDelay || 0; // actual delay to be generated
|
||||
var delayBias; // current amount of delay deviation
|
||||
var thisCallback; // call-back object to be used
|
||||
var delayBias = 0; // current amount of delay deviation
|
||||
var thisCallback = null; // call-back object to be used
|
||||
var token = ++lastTokenNr; // call-back token number
|
||||
var tokenName = token.toString(); // call-back token ID
|
||||
|
||||
@@ -167,7 +167,14 @@
|
||||
pool[poolLength] = null;
|
||||
}
|
||||
|
||||
// Fill in the call-back object and tank it in pendingCallbacks.
|
||||
thisCallback.startStamp = perf.now();
|
||||
thisCallback.category = categoryName;
|
||||
thisCallback.delay = delay;
|
||||
thisCallback.context = context || this;
|
||||
thisCallback.fcn = fcn;
|
||||
thisCallback.arg = arg;
|
||||
pendingCallbacks[tokenName] = thisCallback;
|
||||
|
||||
// Adjust the requested delay based on the current delay deviation
|
||||
// for this category.
|
||||
@@ -177,42 +184,19 @@
|
||||
} else {
|
||||
if (delayBias > 0) {
|
||||
// We are delaying too much and should try to delay less.
|
||||
if (delay < 0) {
|
||||
adj = 0; // don't make delay any more negative
|
||||
} else {
|
||||
adj = -Math.min(delay, delayBias, minTimeout)*alpha;
|
||||
if (delay > 0) { // don't make a negative delay any more so
|
||||
delay -= Math.min(delay, delayBias, minTimeout);
|
||||
}
|
||||
} else { // delayBias < 0
|
||||
} else { // delayBias <= 0
|
||||
// We are delaying too little and should try to delay more.
|
||||
if (delay < 0) {
|
||||
if (delay - minTimeout < delayBias) {
|
||||
adj = -delayBias;
|
||||
} else {
|
||||
adj = minTimeout - delay;
|
||||
}
|
||||
delay -= Math.max(delay, delayBias);
|
||||
} else {
|
||||
if (delay > minTimeout) {
|
||||
adj = 0;
|
||||
} else if (delay - minTimeout < delayBias) {
|
||||
adj = -delayBias;
|
||||
} else {
|
||||
adj = minTimeout - delay;
|
||||
}
|
||||
delay += Math.min(-delayBias, minTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
delay += adj;
|
||||
delayDev[categoryName] += adj;
|
||||
}
|
||||
|
||||
// Fill in the call-back object and tank it in pendingCallbacks.
|
||||
thisCallback.category = categoryName;
|
||||
thisCallback.delay = delay;
|
||||
thisCallback.context = context || this;
|
||||
thisCallback.fcn = fcn;
|
||||
thisCallback.arg = arg;
|
||||
pendingCallbacks[tokenName] = thisCallback;
|
||||
|
||||
// Decide whether to do a time wait or just a yield.
|
||||
if (delay > minTimeout) {
|
||||
thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
|
||||
|
||||
BIN
webUI/resources/B-NORMAL.png
Normal file
BIN
webUI/resources/B-NORMAL.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 892 B |
Binary file not shown.
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 618 B |
Reference in New Issue
Block a user