upgrade dependencies and introduce ci workflow (#59)

This commit is contained in:
Borys Levytskyi
2025-11-05 15:37:06 -05:00
committed by GitHub
parent 7b05214267
commit 0a7f85c3e4
42 changed files with 343 additions and 30265 deletions

68
.github/workflows/pr-ci.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: PR CI
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
e2e-tests:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
BITWISECMD_TESTS_TOKEN: ${{ secrets.BITWISECMD_TESTS_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm install --no-audit --no-fund
- name: Build application
run: npx react-scripts build
env:
CI: true
- name: Start static web server
run: |
npx --yes http-server ./build -p 3030 --silent </dev/null > server.log 2>&1 &
echo $! > server.pid
npx --yes wait-on --timeout=120000 http-get://127.0.0.1:3030
- name: Ensure BitwiseCmdTests access token is configured
if: ${{ env.BITWISECMD_TESTS_TOKEN == '' }}
run: |
echo "::error::Add a personal access token with repo scope as the BITWISECMD_TESTS_TOKEN repository secret so the CI job can clone BitwiseCmdTests."
exit 1
- name: Checkout BitwiseCmdTests repository
if: ${{ env.BITWISECMD_TESTS_TOKEN != '' }}
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/BitwiseCmdTests
path: BitwiseCmdTests
token: ${{ env.BITWISECMD_TESTS_TOKEN }}
- name: Install BitwiseCmdTests dependencies
working-directory: BitwiseCmdTests
run: npm install --no-audit --no-fund
- name: Run webdriver tests
working-directory: BitwiseCmdTests
env:
BASE_URL: http://localhost:3030
CHROME_ARGS: --headless=new --disable-gpu --no-sandbox --disable-dev-shm-usage --window-size=1280,720
run: |
xvfb-run --auto-servernum -- npm run test-build
- name: Stop static web server
if: always()
run: |
if [ -f server.pid ]; then
kill $(cat server.pid) || true
fi

9
docs/ci.md Normal file
View File

@@ -0,0 +1,9 @@
# Continuous Integration Setup
The pull request workflow checks out a private test suite from `BitwiseCmdTests`. To allow the workflow to clone that repository, add a repository secret named `BITWISECMD_TESTS_TOKEN` that contains a GitHub personal access token with at least `repo` scope.
1. Generate a classic personal access token from your GitHub account with `repo` scope.
2. In the BitwiseCmd repository settings, open **Secrets and variables → Actions**.
3. Create a new repository secret named `BITWISECMD_TESTS_TOKEN` and paste the token value.
Once the secret is configured, rerun the pull request workflow so it can authenticate and complete the checkout step.

30037
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,26 +3,26 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/fontawesome-free": "^6.7.2",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.25",
"@types/react": "^18.2.4",
"@types/react-dom": "^18.2.3",
"@types/uuid": "^9.0.1",
"loglevel": "^1.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/node": "^22.10.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.7",
"@types/uuid": "^9.0.7",
"loglevel": "^1.9.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"
"uuid": "^9.0.1",
"web-vitals": "^4.2.4"
},
"scripts": {
"start": "react-scripts start",
@@ -52,7 +52,7 @@
]
},
"devDependencies": {
"gh-pages": "^5.0.0",
"gh-pages": "^6.1.1",
"http-server": "^14.1.1"
}
}

View File

@@ -1,6 +1,5 @@
import { type } from "os";
import { INT32_MAX_VALUE, INT32_MIN_VALUE, UINT32_MAX_VALUE } from "./const";
import { asIntN, logLines as ln } from "./utils";
import { asIntN } from "./utils";
import formatter from "./formatter";
import calc from "./calc";
@@ -15,9 +14,10 @@ export class Integer {
constructor(value: IntegerInput, maxBitSize?: number, signed? : boolean) {
this.value = typeof value == "bigint" ? value : BigInt(value);
this.signed = signed == null ? true : signed == true;
this.maxBitSize = maxBitSize != null ? maxBitSize : detectSize(this.value, this.signed);
this.value = typeof value === "bigint" ? value : BigInt(value);
const resolvedSigned = signed ?? true;
this.signed = resolvedSigned;
this.maxBitSize = maxBitSize ?? detectSize(this.value, this.signed);
if(!this.signed && this.value < 0)
throw new Error("Value " + value + " cannot be negative if the type is unsigned");
@@ -54,7 +54,7 @@ export class Integer {
}
isTheSame (other : Integer) : boolean {
return this.value == other.value && this.signed == other.signed && this.maxBitSize == other.maxBitSize;
return this.value === other.value && this.signed === other.signed && this.maxBitSize === other.maxBitSize;
}
toUnsigned() {
@@ -71,9 +71,9 @@ export class Integer {
const orig = this.toString(2).padStart(this.maxBitSize, '0');
const inverted = orig[0] == '1' ? calc.engine.applyTwosComplement(orig) : orig;
const inverted = orig[0] === '1' ? calc.engine.applyTwosComplement(orig) : orig;
const n = BigInt("0b"+inverted);
const negative = orig[0] == '1';
const negative = orig[0] === '1';
return new Integer(negative ? -n : n, this.maxBitSize, true)
}
@@ -118,24 +118,24 @@ export class Integer {
export function asInteger(num: JsNumber | Integer | string): Integer {
if(typeof num == "string")
if(typeof num === "string")
return asInteger(BigInt(num));
if(isInteger(num))
return num;
if(typeof num == "number" && isNaN(num)) {
if(typeof num === "number" && isNaN(num)) {
throw new Error("Cannot create BoundedNumber from NaN");
}
const size = num > INT32_MAX_VALUE || num < INT32_MIN_VALUE ? 64 : 32;
const n = typeof num == "bigint" ? num : BigInt(num);
const n = typeof num === "bigint" ? num : BigInt(num);
return new Integer(n, size);
}
export function isInteger(num: JsNumber | Integer): num is Integer {
return (<Integer>num).maxBitSize !== undefined;
return (num as Integer).maxBitSize !== undefined;
}
function detectSize(value: bigint, signed: boolean): number {

View File

@@ -1,8 +1,8 @@
function flipBitsToZero(byte: number, numberOfBits : number) : number {
if(numberOfBits == 0)
if(numberOfBits === 0)
return byte;
const zerouOutMask = Math.pow(2, 8-numberOfBits)-1<<numberOfBits; // E.g. 11111000 for flipping first three bits
const zerouOutMask = (Math.pow(2, 8 - numberOfBits) - 1) << numberOfBits; // E.g. 11111000 for flipping first three bits
const result = byte & zerouOutMask;
return result;
@@ -11,16 +11,16 @@ function flipBitsToZero(byte: number, numberOfBits : number) : number {
// TODO: continue here to implement getting broadcast address
function flipBitsToOne(byte : number, numberOfBits : number) : number {
if(numberOfBits == 0) return byte;
if(numberOfBits === 0) return byte;
const zerouOutMask = Math.pow(2, numberOfBits)-1; // E.g. 00000111 for flipping first three bits
const zerouOutMask = Math.pow(2, numberOfBits) - 1; // E.g. 00000111 for flipping first three bits
const result = byte | zerouOutMask;
return result;
}
function createSubnetMaskByte(numberOfBits: number) {
return 255<<(8-numberOfBits)&255;;
return (255 << (8 - numberOfBits)) & 255;
}
export {flipBitsToZero, createSubnetMaskByte, flipBitsToOne};

View File

@@ -1,12 +1,12 @@
import { Integer, JsNumber, asInteger } from "./Integer";
import { asIntN, logLines } from "./utils";
import { asIntN } from "./utils";
export default {
const calc = {
numberOfBitsDisplayed: function (num: Integer | JsNumber) : number {
const n = asInteger(num);
const len = this.toBinaryString(n).length;
return (len+1) == n.maxBitSize ? n.maxBitSize : len; // Include sign bit if it is all that left
return (len + 1) === n.maxBitSize ? n.maxBitSize : len; // Include sign bit if it is all that left
},
flipBit: function(num: Integer | JsNumber, bitIndex: number): Integer {
@@ -51,14 +51,14 @@ export default {
? this.engine.applyTwosComplement(bin)
: bin;
return bin.length != bitSize ? r.substring(r.length-bin.length) : r;
return bin.length !== bitSize ? r.substring(r.length-bin.length) : r;
},
lshift (num: Integer, numBytes : JsNumber) : Integer {
let bytes = asIntN(numBytes);
if(num.maxBitSize == numBytes)
if(num.maxBitSize === bytes)
return num; // Preserve C undefined behavior
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
@@ -70,7 +70,7 @@ export default {
let bytes = asIntN(numBytes);
if(num.maxBitSize == numBytes)
if(num.maxBitSize === bytes)
return num; // Preserve C undefined behavior
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
@@ -82,7 +82,7 @@ export default {
let bytes = asIntN(numBytes);
if(num.maxBitSize == numBytes)
if(num.maxBitSize === bytes)
return num; // Preserve C undefined behavior
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
@@ -114,18 +114,18 @@ export default {
let negative = false;
if(num.signed && bin['0'] == '1') {
if(num.signed && bin[0] === '1') {
bin = this.engine.applyTwosComplement(bin);
negative = true;
}
const result = BigInt("0b" + bin) * BigInt(negative ? -1 : 1);
return new Integer(typeof num.value == "bigint" ? result : asIntN(result), num.maxBitSize, num.signed);
return new Integer(typeof num.value === "bigint" ? result : asIntN(result), num.maxBitSize, num.signed);
},
_applyTwo(op1: Integer, op2: Integer, operation: (bin1:string, bin2:string) => string) : Integer {
if(op1.maxBitSize == op2.maxBitSize && op1.signed != op2.signed)
if(op1.maxBitSize === op2.maxBitSize && op1.signed !== op2.signed)
throw new Error("This operation cannot be applied to signed and unsigned operands of the same size");
const [num1, num2] = equalizeSize(op1, op2);
@@ -137,7 +137,7 @@ export default {
let m = BigInt(1);
if(resultBin['0'] == '1') {
if(resultBin[0] === '1') {
resultBin = this.engine.applyTwosComplement(resultBin);
m = BigInt(-1);
}
@@ -204,7 +204,7 @@ export default {
const b1 = bin1[i] === "1";
const b2 = bin2[i] === "1";
result.push(b1 != b2 ? "1" : "0");
result.push(b1 !== b2 ? "1" : "0");
}
return result.join('');
@@ -217,7 +217,7 @@ export default {
// If there exists no '1' concat 1 at the
// starting of string
if (lastIndex == -1)
if (lastIndex === -1)
return "1" + bin;
// Continue traversal backward after the position of
@@ -225,7 +225,7 @@ export default {
var flipped =[];
for (var i = lastIndex - 1; i >= 0; i--) {
// Just flip the values
flipped.unshift(bin.charAt(i) == "1" ? "0" : "1");
flipped.unshift(bin.charAt(i) === "1" ? "0" : "1");
}
const result = flipped.join('') + bin.substring(lastIndex);
@@ -235,8 +235,10 @@ export default {
}
};
export default calc;
function checkSameLength(bin1: string, bin2: string) {
if (bin1.length != bin2.length)
if (bin1.length !== bin2.length)
throw new Error("Binary strings must have the same length");
}
@@ -251,7 +253,7 @@ function nextPowOfTwo(num: number) : number {
}
function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
if(n1.maxBitSize == n2.maxBitSize)
if(n1.maxBitSize === n2.maxBitSize)
{
if(n1.signed === n2.signed) return [n1,n2];

View File

@@ -1,6 +1,5 @@
import React from 'react';
import './BinaryString.css';
import loglevel from 'loglevel';
export type BinaryStringViewProps = {
allowFlipBits?: boolean;
@@ -32,7 +31,7 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
}
const arr = this.props.binaryString.split('');
arr[index] = arr[index] == '0' ? '1' : '0';
arr[index] = arr[index] === '0' ? '1' : '0';
const newBinaryString = arr.join('');
this.props.onBitClicked({
@@ -59,13 +58,13 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
const css = allowFlipBits ? ' flipable' : ''
const disableHighlight = this.props.disableHighlight || false;
const firstBitIndex = this.props.valueBitSize != null
const firstBitIndex = this.props.valueBitSize !== null && this.props.valueBitSize !== undefined
? bitChars.length - this.props.valueBitSize
: -1;
return bitChars.map((c, i) => {
var className = c == '1' ? `one${css}` : `zero${css}`;
var className = c === '1' ? `one${css}` : `zero${css}`;
var tooltip = '';

View File

@@ -0,0 +1,18 @@
.command-link {
background: none;
border: none;
color: inherit;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0;
font: inherit;
text-decoration: underline;
}
.command-link:focus {
outline: 2px solid currentColor;
outline-offset: 2px;
}

View File

@@ -2,6 +2,7 @@ import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react';
import cmd from '../../shell/cmd';
import './CommandLink.css';
type CommandLinkProps = {
command?:string;
@@ -12,12 +13,14 @@ type CommandLinkProps = {
function CommandLink({icon, command, text, textClassName}: CommandLinkProps) {
const onClick = () => cmd.execute(command || text);
const onClick = () => cmd.execute(command ?? text);
if(icon != null)
return <a href="javascript:void(0)" onClick={onClick}><FontAwesomeIcon icon={icon} className="icon" /><span className={textClassName}>{text}</span></a>;
return <a href="javascript:void(0)" onClick={onClick}><span className={textClassName}>{text}</span></a>;
return (
<button type="button" className="command-link" onClick={onClick}>
{icon ? <FontAwesomeIcon icon={icon} className="icon" /> : null}
<span className={textClassName}>{text}</span>
</button>
);
}
export default CommandLink;

View File

@@ -6,7 +6,7 @@ const formatter = {
numberToString: function(num: Integer | JsNumber, base: NumberBase | number, padLength?: number) : string {
num = asInteger(num);
base = typeof base == "string" ? getBase(base) : base;
base = typeof base === "string" ? getBase(base) : base;
switch(base) {
case 16:
@@ -15,7 +15,7 @@ const formatter = {
case 2:
const bin = calc.toBinaryString(num);
if(padLength == null)
if(padLength === null || padLength === undefined)
return bin;
const padChar = num.value >= 0 ? '0' : '1';
@@ -27,14 +27,15 @@ const formatter = {
}
},
padLeft: function (str: string, length: number, symbol: string) : string {
var sb = Array.prototype.slice.call(str), symbol = symbol || "0";
const fillCharacter = symbol ?? "0";
const sb: string[] = Array.prototype.slice.call(str);
if(length == null) {
if(length === null || length === undefined) {
return str;
}
while(length > sb.length) {
sb.unshift(symbol);
sb.unshift(fillCharacter);
}
return sb.join('');
@@ -66,7 +67,7 @@ const formatter = {
mask++;
if(mask == b) {
if(mask === b) {
b = mask2;
res.push(tmp.join(''));
tmp = [];

View File

@@ -1,9 +1,9 @@
export default {
const hashUtils = {
encodeHash: function(input:string):string {
return encodeURIComponent(input.trim().replace(/\s/g,','));
},
decodeHash: function(hashValue:string):string {
return decodeURIComponent(hashValue.replace(/^\#/, '')).replace(/,/g,' ');
return decodeURIComponent(hashValue.replace(/^#/, '')).replace(/,/g,' ');
},
getArgs: function (hashValue:string) : string[] {
@@ -18,6 +18,8 @@ export default {
}
};
export default hashUtils;
function splitHashList(str: string) : string[] {
return str.split('||').filter(s => s.length > 0);

View File

@@ -1,14 +1,14 @@
export default {
const typeGuards = {
plainObject: function(obj : any) : boolean {
return typeof obj == "object" && !(obj instanceof Array) && obj instanceof Object;
return typeof obj === "object" && !(obj instanceof Array) && obj instanceof Object;
},
aFunction: function(obj : any) : boolean {
return typeof obj == "function";
return typeof obj === "function";
},
string: function(obj : any) : boolean {
return typeof obj == "string";
return typeof obj === "string";
},
array: function(obj : any) : boolean {
@@ -16,6 +16,8 @@ export default {
},
number: function(obj : any) : boolean {
return typeof obj == "number" && !isNaN(obj)
return typeof obj === "number" && !isNaN(obj);
}
}
};
export default typeGuards;

View File

@@ -15,7 +15,7 @@ function asIntN(num: JsNumber | Integer) : number {
if(isInteger(num))
return asIntN(num.value);
return typeof num == "bigint" ? parseInt(num.toString()): num as number;
return typeof num === "bigint" ? parseInt(num.toString()): num as number;
}
function random(from: number, to: number) {
@@ -23,11 +23,11 @@ function random(from: number, to: number) {
}
function randomBool() {
return random(1, 10000) % 2 == 0;
return random(1, 10000) % 2 === 0;
}
function logLines(...params: any[]) {
console.log(params.join('\n'))
console.log(params.join('\n'));
}
export {chunkifyString, asIntN, random, randomBool, logLines};

View File

@@ -63,7 +63,7 @@ function tokenize(input: string): Token[] {
if (!firstToken.skip)
found.push({value: value, type: firstToken.type});
if (cur.length == value.length)
if (cur.length === value.length)
break;
cur = cur.substring(value.length);

View File

@@ -1,6 +1,6 @@
import calc from "../core/calc";
import Operand from "./Operand";
import { Expression, ExpressionElement } from "./expression-interfaces";
import { Expression } from "./expression-interfaces";
export default class ListOfNumbers implements Expression {
children: Operand[];

View File

@@ -1,4 +1,4 @@
import { ExpressionElement as ExpressionElement } from './expression-interfaces';
import { ExpressionElement } from './expression-interfaces';
import { NumberBase } from '../core/formatter';
import { INT64_MAX_VALUE, INT64_MIN_VALUE, UINT64_MAX_VALUE } from '../core/const';
import { Integer, JsNumber, isInteger, asInteger } from '../core/Integer';

View File

@@ -23,12 +23,12 @@ export default class Operator implements ExpressionElement {
if (operand instanceof Operator)
throw new Error('operand must be scalar value');
if( this.operator != "~" && operand == null)
if( this.operator !== "~" && (operand === undefined || operand === null))
throw new Error("operand is required");
var evaluatedOperand = this.operand.evaluate();
return this.operator == "~"
return this.operator === "~"
? applyNotOperator(this.operand.getUnderlyingOperand())
: applyOperator(operand!, this.operator, evaluatedOperand);
}
@@ -52,7 +52,7 @@ function applyOperator(op1 : Operand, operator: string, op2 : Operand) : Operand
if(!isShift)
{
if(op1.value.maxBitSize == op2.value.maxBitSize && op1.value.signed != op2.value.signed)
if(op1.value.maxBitSize === op2.value.maxBitSize && op1.value.signed !== op2.value.signed)
throw new Error("Operator `" + operator + "` cannot be applied to signed and unsigned operands of the same " + op2.value.maxBitSize + " -bit size");
equalizeSize(op1, op2);

View File

@@ -7,7 +7,7 @@ import { Operator, Operand, ListOfNumbers } from '../expression';
import calc from '../../core/calc';
import { Integer } from '../../core/Integer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle, faTriangleExclamation, faUndo } from '@fortawesome/free-solid-svg-icons';
import { faInfoCircle, faUndo } from '@fortawesome/free-solid-svg-icons';
import loglevel from 'loglevel';
import IconWithToolTip from '../../shell/components/IconWithTooltip';
@@ -191,7 +191,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
}
getLabelString(op: Operand): string {
return formatter.numberToString(op.value, op.base == 'bin' ? 'dec' : op.base);
return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base);
}
undo() {
@@ -201,7 +201,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
onBitClicked(args: BitClickedEventArg) {
const { bitIndex, binaryStringLength: binaryStringLength } = args;
const { bitIndex, binaryStringLength } = args;
const maxBitSize = this.scalar.value.maxBitSize;

View File

@@ -2,7 +2,6 @@ import { Operand, ListOfNumbers, BitwiseOperation, Operator } from '../expressio
import { ExpressionElement, Expression } from '../expression-interfaces';
import calc from '../../core/calc';
import formatter from '../../core/formatter';
import exp from 'constants';
type Config = {
emphasizeBytes: boolean;
@@ -44,26 +43,25 @@ export default class BitwiseResultViewModel {
static buildBitwiseOperation (expr : BitwiseOperation, config : Config) {
var op = expr.children[0],
i = 0, len = expr.children.length,
ex, m = new BitwiseResultViewModel(config);
const len = expr.children.length;
const m = new BitwiseResultViewModel(config);
var prev : Operand | null = null;
let prev : Operand | null = null;
for (;i<len;i++) {
ex = expr.children[i];
for (let i = 0; i < len; i++) {
const ex = expr.children[i];
if(ex instanceof Operand) {
m.addScalarRow(ex);
prev = ex;
continue;
}
var eo = ex as Operator;
const eo = ex as Operator;
// If it a single NOT expression
if(eo.isNotExpression) {
m.addOperatorRow(eo);
var notResult = eo.evaluate();
const notResult = eo.evaluate();
m.addExpressionResultRow(notResult);
prev = notResult;
}
@@ -152,7 +150,7 @@ export default class BitwiseResultViewModel {
static applyEmphasizeBytes (bits : number, emphasizeBytes : boolean) : number {
if(emphasizeBytes && bits % 8 != 0) {
if(emphasizeBytes && bits % 8 !== 0) {
if(bits < 8) {
return 8;
}

View File

@@ -114,7 +114,7 @@ describe("comparison with nodejs engine", () => {
const res = bo.evaluate();
actual = res.value.toString();
if(actual != expected) {
if(actual !== expected) {
const uop = bo.getUnderlyingOperand();
console.log(`Expected:${expectedInput}\nActual:${actualInput}\n${uop.value} ${uop.value.maxBitSize}\n${res.value} ${typeof res.value} ${res.value.maxBitSize}`)
}

View File

@@ -26,7 +26,7 @@ class ExpressionParser {
var trimmed = input.replace(/^\s+|\s+$/, '');
var i = this.factories.length-1;
for(;i>=0;i--) {
if(this.factories[i].canCreate(trimmed) === true){
if(this.factories[i].canCreate(trimmed)){
return true;
}
}
@@ -41,7 +41,7 @@ class ExpressionParser {
factory = this.factories[i];
if(factory.canCreate(trimmed) == true){
if(factory.canCreate(trimmed)){
return factory.create(trimmed);
}
}
@@ -56,17 +56,14 @@ class ExpressionParser {
class ListOfNumbersExpressionFactory implements IExpressionParserFactory
{
constructor() {
}
canCreate (input: string): boolean {
if(input.length == 0) return false;
if(input.length === 0) return false;
return input.split(' ')
.filter(p => p.length > 0)
.map(p => numberParser.caseParse(p))
.filter(n => n == false)
.length == 0;
.filter(n => n === false)
.length === 0;
};
create (input : string) : Expression {
@@ -84,8 +81,8 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
regex: RegExp;
constructor() {
this.fullRegex = /^[-,~,<,>,&,\^\|,b,x,l,s,u,a-f,0-9,\s]+$/i;
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,l,s,u,,a-f,0-9]+))/gi;
this.fullRegex = /^[-,~,<,>,&,^|,b,x,l,s,u,a-f,0-9,\s]+$/i;
this.regex = /(<<|>>|>>>|\||&|\^)?(~?-?(?:[b,x,l,s,u,,a-f,0-9]+))/gi;
}
canCreate (input: string) : boolean {
@@ -100,7 +97,7 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
this.regex.lastIndex = 0;
while ((m = this.regex.exec(normalizedString)) != null) {
while ((m = this.regex.exec(normalizedString)) !== null) {
operands.push(this.parseMatch(m));
}
@@ -109,20 +106,19 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
parseMatch (m:RegExpExecArray): ExpressionElement {
var input = m[0],
operator = m[1],
var operator = m[1],
num = m[2];
var parsed = null;
if(num.indexOf('~') == 0) {
if(num.indexOf('~') === 0) {
parsed = new Operator(parseScalarValue(num.substring(1)), '~');
}
else {
parsed = parseScalarValue(num);
}
if(operator == null) {
if(operator === undefined || operator === null) {
return parsed as Operator;
} else {
return new Operator(parsed as Operand, operator);
@@ -137,7 +133,7 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
function parseScalarValue(input : string) : Operand {
const n = numberParser.parse(input);
var sv = new Operand(n.value, n.base);
if(sv.value.maxBitSize != n.value.maxBitSize) throw new Error("Gotcha!");
if(sv.value.maxBitSize !== n.value.maxBitSize) throw new Error("Gotcha!");
return sv;
}

View File

@@ -27,13 +27,13 @@ class NumberParser {
parse (input : string) : ParsedNumber {
if(input.length == 0) throw new Error("input is null or empty");
if(input.length === 0) throw new Error("input is null or empty");
const regex = new RegExp(numberRegexFullString, "i");
const m = regex.exec(input);
if(m == null || m.length == 0)
if(m === null || m.length === 0)
throw new Error(input + " is not a number");
const value = parseInteger(m[0], m[1], m[2] || '');

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react';
import BinaryStringView from '../../core/components/BinaryString';
import './SubnetView.css';
import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp } from '../subnet-utils';
import { chunkifyString } from '../../core/utils';
import IpAddressBinaryString from './IpAddressBinaryString';
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from '../models';
@@ -41,9 +39,9 @@ function SubnetView(props : {subnet : SubnetCommand}) {
</td>
<td data-test-name="decimal">
<button className="btn" onClick={decrementMask} disabled={subnet.cidr.maskBits === 0} title="Decrease mask size">-</button>
<button type="button" className="btn" onClick={decrementMask} disabled={subnet.cidr.maskBits === 0} title="Decrease mask size">-</button>
<span>{subnet.cidr.maskBits}</span>
<button className="btn"onClick={incrementMask} disabled={subnet.cidr.maskBits === 32} title="Increase mask size">+</button>
<button type="button" className="btn"onClick={incrementMask} disabled={subnet.cidr.maskBits === 32} title="Increase mask size">+</button>
</td>
</tr>
</tbody>

View File

@@ -2,8 +2,7 @@ import React, { useState } from 'react';
import BinaryStringView from '../../core/components/BinaryString';
import './VpcView.css';
import { getNetworkAddress, getAddressSpaceSize } from '../subnet-utils';
import IpAddressBinaryString from './IpAddressBinaryString';
import { IpAddress, IpAddressWithSubnetMask, VpcCommand } from '../models';
import { IpAddressWithSubnetMask, VpcCommand } from '../models';
import formatter from '../../core/formatter';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import IconWithToolTip from '../../shell/components/IconWithTooltip';
@@ -57,9 +56,9 @@ function SubnetView(props: { vpc: VpcCommand }) {
VPC CIDR Mask:
</td>
<td>
<button className="btn" onClick={decrVpc} disabled={vpc.cidr.maskBits <= 1} title="Decrease vpc address bits">-</button>
/{vpc.cidr.maskBits}
<button className="btn" onClick={incrVpc} disabled={subnetMaskSize >= MAX_NON_HOSTS_BITS} title="Increse vpc address bits">+</button>
<button type="button" className="btn" onClick={decrVpc} disabled={vpc.cidr.maskBits <= 1} title="Decrease vpc address bits">-</button>
/{vpc.cidr.maskBits}
<button type="button" className="btn" onClick={incrVpc} disabled={subnetMaskSize >= MAX_NON_HOSTS_BITS} title="Increase vpc address bits">+</button>
</td>
</tr>
<tr>
@@ -67,9 +66,9 @@ function SubnetView(props: { vpc: VpcCommand }) {
Subnet CIDR Mask:
</td>
<td>
<button className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Increase subnet bits">-</button>
/{subnetMaskSize}
<button className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
<button type="button" className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Increase subnet bits">-</button>
/{subnetMaskSize}
<button type="button" className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
</td>
</tr>
<tr>
@@ -77,9 +76,9 @@ function SubnetView(props: { vpc: VpcCommand }) {
Max Subnets in VPC:
</td>
<td>
<button className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Decrease subnet bits">-</button>
<button type="button" className="btn" onClick={decrSubnet} disabled={vpc.subnetBits <= 1} title="Decrease subnet bits">-</button>
{maxSubnets}
<button className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
<button type="button" className="btn" onClick={incrSubnet} disabled={vpc.cidr.maskBits + vpc.subnetBits >= MAX_NON_HOSTS_BITS} title="Increase subnet bits">+</button>
</td>
</tr>
<tr>
@@ -104,21 +103,6 @@ function SubnetView(props: { vpc: VpcCommand }) {
</React.Fragment>;
}
function Indicator2(props: { ip: IpAddress, descr: string }) {
const { ip, descr } = props;
return <tr>
<td className="soft" data-test-name="label">{descr}</td>
<td data-test-name="decimal" className="ip-address-col">
{ip.toString()}
</td>
<td data-test-name="bin">
<IpAddressBinaryString ip={ip} />
</td>
</tr>;
}
export default SubnetView;
class VpcModel {

View File

@@ -17,10 +17,10 @@ const ipAddressParser = {
const result = this.parseCommand(input);
const matches = this.getMaches(result.nextInput);
const correctInputs = matches.filter(m => m.matches != null);
const incorrectInputs = matches.filter(m => m.matches == null);
const correctInputs = matches.filter(m => m.matches !== null && m.matches !== undefined);
const incorrectInputs = matches.filter(m => m.matches === null || m.matches === undefined);
if(correctInputs.length == 0)
if(correctInputs.length === 0)
return null;
if(incorrectInputs.length > 0) {
@@ -34,9 +34,9 @@ const ipAddressParser = {
return parsingErrors[0] as ParsingError;
}
if(result.command != null) {
if(result.command !== null) {
const cmd =
result.command == CMD_SUBNET
result.command === CMD_SUBNET
? this.createSubnetDefinition(parsedObjects as ParsedIpObject[])
: this.createVpcDefinition(parsedObjects as ParsedIpObject[]);
@@ -62,15 +62,15 @@ const ipAddressParser = {
getMaches(input : string) : { matches: RegExpExecArray | null, input: string }[] {
return input.
replace(/[\t\s]+/g, ' ')
.split(' ')
return input
.replace(/[\t\s]+/g, ' ')
.split(' ')
.filter(s => s.length>0)
.map(s => {
const ipV4Regex = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})(\/\d+)?$/;
const matches = ipV4Regex.exec(s);
if(matches == null || matches.length === 0)
if(matches === null || matches.length === 0)
return {matches: null, input: s};
return {matches, input: s};
@@ -105,7 +105,7 @@ const ipAddressParser = {
},
createSubnetDefinition(items: ParsedIpObject[]) : SubnetCommand | ParsingError {
if(items.length != 1)
if(items.length !== 1)
return new ParsingError("Incorrect network definition");
const first = items[0];
@@ -118,7 +118,7 @@ const ipAddressParser = {
createVpcDefinition(items: ParsedIpObject[]) : VpcCommand | ParsingError {
if(items.length != 1)
if(items.length !== 1)
return new ParsingError("Incorrect VPC definition");
const first = items[0];

View File

@@ -9,7 +9,6 @@ import log from 'loglevel';
import SubnetView from './components/SubnetView';
import { createSubnetMaskIp } from './subnet-utils';
import {sendAnalyticsEvent} from '../shell/analytics';
import TextResultView from '../shell/components/TextResultView';
import VpcView from './components/VpcView';
const networkingAppModule = {
@@ -17,11 +16,14 @@ const networkingAppModule = {
// Add Ip Address commands
cmd.command({
canHandle: (input:string) => ipAddressParser.parse(input) != null,
canHandle: (input:string) => {
const parsed = ipAddressParser.parse(input);
return parsed !== null && parsed !== undefined;
},
handle: function(c: CommandInput) {
var result = ipAddressParser.parse(c.input);
if(result == null)
if(result === null || result === undefined)
return;
if(result instanceof ParsingError) {

View File

@@ -1,5 +1,4 @@
import { createSubnetMaskByte } from "../core/byte";
import { flipBitsToOne, flipBitsToZero } from '../core/byte';
import { createSubnetMaskByte, flipBitsToOne, flipBitsToZero } from '../core/byte';
import { IpAddress, IpAddressWithSubnetMask, NetworkClass } from "./models";
function createSubnetMaskIp(ipm: IpAddressWithSubnetMask) : IpAddress {
@@ -59,7 +58,6 @@ function getNetworkClass (ipAddress: IpAddress) : NetworkClass {
const byte = ipAddress.firstByte;
const firstBitOne = (byte & 128) === 128;
const firstBitZero = (byte & 128) === 0;
const secondBitOne = (byte & 64) === 64;
const thirdBitOne = (byte & 32) === 32;

View File

@@ -1,13 +1,41 @@
import { ReportHandler } from 'web-vitals';
import type { Metric } from 'web-vitals';
type ReportHandler = (metric: Metric) => void;
type WebVitalsModule = {
onCLS?: (callback: ReportHandler) => void;
onFID?: (callback: ReportHandler) => void;
onFCP?: (callback: ReportHandler) => void;
onLCP?: (callback: ReportHandler) => void;
onTTFB?: (callback: ReportHandler) => void;
getCLS?: (callback: ReportHandler) => void;
getFID?: (callback: ReportHandler) => void;
getFCP?: (callback: ReportHandler) => void;
getLCP?: (callback: ReportHandler) => void;
getTTFB?: (callback: ReportHandler) => void;
};
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
import('web-vitals').then((module) => {
const {
onCLS,
onFID,
onFCP,
onLCP,
onTTFB,
getCLS,
getFID,
getFCP,
getLCP,
getTTFB,
} = module as WebVitalsModule;
(onCLS ?? getCLS)?.(onPerfEntry);
(onFID ?? getFID)?.(onPerfEntry);
(onFCP ?? getFCP)?.(onPerfEntry);
(onLCP ?? getLCP)?.(onPerfEntry);
(onTTFB ?? getTTFB)?.(onPerfEntry);
});
}
};

View File

@@ -49,7 +49,7 @@ export default class AppState {
this.uiTheme = persistData.uiTheme;
this.emphasizeBytes = !!persistData.emphasizeBytes;
this.persistedVersion = persistData.version || 0.1;
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
this.wasOldVersion = persistData.version !== null && persistData.version !== undefined && this.version > this.persistedVersion;
this.debugMode = persistData.debugMode === true;
this.pageVisitsCount = persistData.pageVisistsCount || 0;
this.donationClicked = persistData.donationClicked;
@@ -79,7 +79,7 @@ export default class AppState {
}
toggleEmphasizeBytes(value?: boolean) {
this.emphasizeBytes = value != null ? value : !this.emphasizeBytes;
this.emphasizeBytes = value !== null && value !== undefined ? value : !this.emphasizeBytes;
this.triggerChanged();
}
@@ -107,7 +107,7 @@ export default class AppState {
}
toggleAnnotateTypes(value?: boolean) {
this.annotateTypes = value != null ? value : !this.annotateTypes;
this.annotateTypes = value !== null && value !== undefined ? value : !this.annotateTypes;
this.triggerChanged();
}

View File

@@ -10,8 +10,9 @@ export type AnalyticsHandler = (evt: AnalyticsEvent) => boolean;
function sendAnalyticsEvent(evt : AnalyticsEvent) {
const handler = (window as any).bitwiseCmdAnalyticsHandler;
if(handler == null) {
if(handler === null || handler === undefined) {
log.debug('ERROR!!!: Analytics event was not sent. Handler not found');
return;
}
const delivered = (handler as AnalyticsHandler)(evt);

View File

@@ -14,7 +14,7 @@ const DEFAULT_DATA : PersistedAppData = {
cookieDisclaimerHidden: false
}
export default {
const appStateStore = {
getPersistedData() : PersistedAppData {
var json = window.localStorage.getItem(storeKey);
if(!json) {
@@ -26,7 +26,7 @@ export default {
}
catch(ex) {
console.error('Failed to parse AppState json. Json Value: \n' + json, ex);
return DEFAULT_DATA;;
return DEFAULT_DATA;
}
},
@@ -37,4 +37,6 @@ export default {
persistData(appState: AppState) {
localStorage.setItem(storeKey, JSON.stringify(appState.getPersistData()));
}
}
};
export default appStateStore;

View File

@@ -36,19 +36,19 @@ export class CmdShell {
log.debug(`Executing command: ${rawInput}`);
ops = ops || Object.assign({}, DEFUALT_COMMAND_OPTIONS);
const resolvedOps = ops ?? Object.assign({}, DEFUALT_COMMAND_OPTIONS);
var input = rawInput.trim().toLowerCase();
var handler = this.findHandler(input);
const input = rawInput.trim().toLowerCase();
const handler = this.findHandler(input);
if(handler != null) {
if(handler !== null) {
// if(this.debugMode) {
// this.invokeHandler(input, handler, ops);
// return
// }
try {
this.invokeHandler(input, handler, ops);
this.invokeHandler(input, handler, resolvedOps);
} catch (e) {
this.handleError(input, e as Error);
}
@@ -64,8 +64,8 @@ export class CmdShell {
}
command (cmd : string | object, handler? : any) {
var h = this.createHandler(cmd, handler);
if(h == null){
const h = this.createHandler(cmd, handler);
if(h === null){
console.warn('unexpected set of arguments: ', JSON.stringify(arguments));
return;
}
@@ -96,13 +96,13 @@ export class CmdShell {
}
findHandler (input: string) : ICommandHandler | null {
return this.handlers.filter(h => h.canHandle(input))[0];
return this.handlers.find(h => h.canHandle(input)) ?? null;
};
invokeHandler (input : string, handler : ICommandHandler, options: CommandOptions) {
var cmdResult = handler.handle({ input: input, options });
if(cmdResult != null) {
const cmdResult = handler.handle({ input: input, options });
if(cmdResult !== null && cmdResult !== undefined) {
log.debug(cmdResult);
}
};
@@ -112,9 +112,11 @@ export class CmdShell {
if(this.debugMode)
console.error(input, err);
if(this.errorHandler != null)
if(this.errorHandler !== null)
this.errorHandler(input, err);
}
}
export default new CmdShell();
const cmdShell = new CmdShell();
export default cmdShell;

View File

@@ -3,7 +3,6 @@ import InputBox from './InputBox';
import DisplayResultView from './DisplayResultView';
import AppState, { CommandResultView } from '../AppState';
import cmd from '../cmd';
import log from 'loglevel';
import DebugIndicators from './DebugIndicators';
import hash from '../../core/hash';
import TopLinks from './TopLinks';
@@ -49,7 +48,7 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
render() {
const enableNewUi = this.props.appState.env != 'prod' || true;
const enableNewUi = this.props.appState.env !== 'prod' || true;
const newUi = enableNewUi ? 'new-ui' : '';
const settingsCss = "settings-button" + (this.props.appState.showSettings ? '' : ' soft');
@@ -64,7 +63,9 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
<div className="expressionInput-container">
<InputBox onCommandEntered={(input) => cmd.execute(input)} />
<button className={settingsCss} title='Toggle Settings'><FontAwesomeIcon icon={faGear} onClick={() => this.props.appState.toggleShowSettings()} /></button>
<button className={settingsCss} title='Toggle Settings' type="button" onClick={() => this.props.appState.toggleShowSettings()}>
<FontAwesomeIcon icon={faGear} />
</button>
</div>
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}

View File

@@ -7,7 +7,7 @@ function DebugIndicators(props: {appState: AppState}) {
const list = [];
const state = props.appState;
if(props.appState.env != 'prod') {
if(props.appState.env !== 'prod') {
list.push(state.env);
}
@@ -19,7 +19,7 @@ function DebugIndicators(props: {appState: AppState}) {
list.push("notrack");
}
if(list.length == 0)
if(list.length === 0)
return null;
return <div className="debug-indicators">

View File

@@ -1,4 +1,4 @@
import { faTrashAlt, faHashtag, faLink, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt, faLink, faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react';
import AppState from '../AppState';

View File

@@ -1,15 +1,15 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState} from 'react';
import { faClipboard, faCoffee} from "@fortawesome/free-solid-svg-icons";
import { faCoffee} from "@fortawesome/free-solid-svg-icons";
import "./DonateResultView.css";
import { sendAnalyticsEvent } from '../analytics';
import { faPaypal } from '@fortawesome/free-brands-svg-icons';
function DonateResultView() {
const copyCss = navigator.clipboard != null ? "" : "hidden";
const copyCss = navigator.clipboard !== null && navigator.clipboard !== undefined ? "" : "hidden";
const [state, setState] = useState('default');
const copiedCss = state == "copied" ? "" : "hidden";
const copiedCss = state === "copied" ? "" : "hidden";
const addr = "bc1qyv08z29776uwdwy2m0c77gpgpupzr78jpcnraq";
@@ -19,13 +19,13 @@ function DonateResultView() {
<div className='section'>
<h3>buymeacoffee.com</h3>
<p>
<a className='button button-large' href='https://bmc.link/boryslevytB' onClick={() => onBuyMeCoffe()} target='_blank'>
<a className='button button-large' href='https://bmc.link/boryslevytB' onClick={() => onBuyMeCoffe()} target='_blank' rel="noreferrer">
<FontAwesomeIcon icon={faCoffee} size='lg' /> Buy Me a Coffee
</a>
</p>
<h3>PayPal</h3>
<p>
<a className='paypal-button button button-large' href='https://www.paypal.com/donate/?hosted_button_id=3GREJYC4T5AJ8' target='_blank'>
<a className='paypal-button button button-large' href='https://www.paypal.com/donate/?hosted_button_id=3GREJYC4T5AJ8' target='_blank' rel="noreferrer">
<FontAwesomeIcon icon={faPaypal} size='lg' />
Donate via PayPal
</a>
@@ -35,14 +35,16 @@ function DonateResultView() {
<div className='section'>
<h3>Bitcoin</h3>
<span>BTC Address:</span> <strong>{addr}</strong>
<button onClick={() => copy()} title="Copy this address into the Cliboard" className={`button copy-button ${copyCss}`}>
<button type="button" onClick={() => copy()} title="Copy this address into the Cliboard" className={`button copy-button ${copyCss}`}>
Copy
</button> <span className={`soft ${copiedCss}`}>copied</span>
</div>
</div>
function copy() {
navigator.clipboard.writeText(addr);
if (navigator.clipboard) {
navigator.clipboard.writeText(addr);
}
setState('copied');
setTimeout(() => setState('default'), 3000);
sendAnalyticsEvent({eventCategory: "Donation", eventAction: "CopyBTCAddressCopyClicked"})

View File

@@ -3,7 +3,7 @@ import CommandLink from '../../core/components/CommandLink';
import './HelpResultView.css';
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from '../../core/const';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircleExclamation, faWarning } from '@fortawesome/free-solid-svg-icons';
import { faCircleExclamation } from '@fortawesome/free-solid-svg-icons';
function HelpResultView() {
@@ -59,7 +59,7 @@ function HelpResultView() {
<li><code>&gt;&gt;&gt;</code> zero-fill right shift</li>
</ul>
<div className='important-note'>
<FontAwesomeIcon icon={faCircleExclamation} size='lg'/> <a target='_blank' href='https://en.cppreference.com/w/c/language/operator_precedence'>Operator precedence</a> is IGNORED. Operators are executed <strong>left-to-right</strong>.
<FontAwesomeIcon icon={faCircleExclamation} size='lg'/> <a target='_blank' href='https://en.cppreference.com/w/c/language/operator_precedence' rel="noreferrer">Operator precedence</a> is IGNORED. Operators are executed <strong>left-to-right</strong>.
</div>
</div>
<div className="section soft-border">

View File

@@ -1,5 +1,4 @@
import React from 'react';
import log from 'loglevel';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
@@ -21,7 +20,7 @@ export default class InputBox extends React.Component<IInputBoxProps> {
}
componentDidMount(){
if(this.nameInput != null)
if(this.nameInput !== null)
this.nameInput.focus();
}
@@ -40,7 +39,7 @@ export default class InputBox extends React.Component<IInputBoxProps> {
onKeyUp(e: any) {
var input = e.target;
if (e.keyCode != 13 || input.value.trim().length == 0) {
if (e.keyCode !== 13 || input.value.trim().length === 0) {
return;
}
@@ -54,7 +53,7 @@ export default class InputBox extends React.Component<IInputBoxProps> {
onKeyDown(args: any) {
if(args.keyCode == 38) {
if(args.keyCode === 38) {
var newIndex = this.historyIndex+1;
if (this.history.length > newIndex) { // up
@@ -66,7 +65,7 @@ export default class InputBox extends React.Component<IInputBoxProps> {
return;
}
if(args.keyCode == 40) {
if(args.keyCode === 40) {
if(this.historyIndex > 0) { // down
args.target.value = this.history[--this.historyIndex];
}

View File

@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import './SettingsPane.css';
import { faGear, faToggleOff, faToggleOn } from '@fortawesome/free-solid-svg-icons';
import { faToggleOff, faToggleOn } from '@fortawesome/free-solid-svg-icons';
import AppState from '../AppState';
type SettingsPaneProps = {
@@ -15,7 +15,7 @@ function SettingsPane(props : SettingsPaneProps) {
<div className="inner solid-border">
<h3>Settings</h3>
<div className='setting'>
<button onClick={() => appState.toggleEmphasizeBytes()}>
<button type="button" onClick={() => appState.toggleEmphasizeBytes()}>
<FontAwesomeIcon size='xl' icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes
</button>
<p className='description'>
@@ -25,7 +25,7 @@ function SettingsPane(props : SettingsPaneProps) {
</p>
</div>
<div className='setting'>
<button onClick={() => appState.toggleDimExtrBits()}>
<button type="button" onClick={() => appState.toggleDimExtrBits()}>
<FontAwesomeIcon size='xl' icon={appState.dimExtraBits ? faToggleOn : faToggleOff} /> Dim Extra Bits
</button>
<p className='description'>
@@ -35,7 +35,7 @@ function SettingsPane(props : SettingsPaneProps) {
</p>
</div>
<div className='setting'>
<button onClick={() => appState.toggleAnnotateTypes()}>
<button type="button" onClick={() => appState.toggleAnnotateTypes()}>
<FontAwesomeIcon size='xl' icon={appState.annotateTypes ? faToggleOn : faToggleOff} /> Annotate Data Types
</button>
<p className='description'>

View File

@@ -9,12 +9,12 @@ function TopLinks() {
return <ul className="top-links">
<li>
<button onClick={onDonate} className='link-button'>
<button type="button" onClick={onDonate} className='link-button'>
<FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span>
</button>
</li>
<li>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd" target='_blank'><FontAwesomeIcon className="icon" icon={faGithub} size="lg" /><span className="link-text">github</span></a>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd" target='_blank' rel="noreferrer"><FontAwesomeIcon className="icon" icon={faGithub} size="lg" /><span className="link-text">github</span></a>
</li>
<li>
<a href="mailto:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback"><FontAwesomeIcon className="icon" icon={faEnvelope} size="lg" /><span className="link-text">idea or feedback</span></a>

View File

@@ -40,7 +40,7 @@ function getStartupCommands(appState : AppState) : string[] {
var startupCommands = loadStoredCommands();
if(startupCommands.length == 0)
if(startupCommands.length === 0)
startupCommands = DEFAULT_COMMANDS;
if(appState.wasOldVersion) {
@@ -49,7 +49,7 @@ function getStartupCommands(appState : AppState) : string[] {
if(hashArgs.length > 0) {
if(hashArgs.indexOf('empty')==-1)
if(hashArgs.indexOf('empty') === -1)
startupCommands = hashArgs;
}
@@ -60,11 +60,11 @@ function getStartupCommands(appState : AppState) : string[] {
function loadStoredCommands() : string[] {
const json = localStorage.getItem(STARTUP_COMMAND_KEY);
return json != null ? [json] : [];
return json !== null && json !== undefined ? [json] : [];
}
function setupLogger(env: Env) {
if(env != 'prod'){
if(env !== 'prod'){
log.setLevel("debug");
log.debug(`Log level is set to debug. Env: ${env}`)
} else {