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", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^6.7.2",
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^14.5.2",
"@types/jest": "^27.5.2", "@types/jest": "^29.5.12",
"@types/node": "^16.18.25", "@types/node": "^22.10.1",
"@types/react": "^18.2.4", "@types/react": "^18.3.12",
"@types/react-dom": "^18.2.3", "@types/react-dom": "^18.3.7",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.7",
"loglevel": "^1.8.1", "loglevel": "^1.9.1",
"react": "^18.2.0", "react": "^18.3.1",
"react-dom": "^18.2.0", "react-dom": "^18.3.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"uuid": "^9.0.0", "uuid": "^9.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^4.2.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@@ -52,7 +52,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"gh-pages": "^5.0.0", "gh-pages": "^6.1.1",
"http-server": "^14.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 { 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 formatter from "./formatter";
import calc from "./calc"; import calc from "./calc";
@@ -15,9 +14,10 @@ export class Integer {
constructor(value: IntegerInput, maxBitSize?: number, signed? : boolean) { constructor(value: IntegerInput, maxBitSize?: number, signed? : boolean) {
this.value = typeof value == "bigint" ? value : BigInt(value); this.value = typeof value === "bigint" ? value : BigInt(value);
this.signed = signed == null ? true : signed == true; const resolvedSigned = signed ?? true;
this.maxBitSize = maxBitSize != null ? maxBitSize : detectSize(this.value, this.signed); this.signed = resolvedSigned;
this.maxBitSize = maxBitSize ?? detectSize(this.value, this.signed);
if(!this.signed && this.value < 0) if(!this.signed && this.value < 0)
throw new Error("Value " + value + " cannot be negative if the type is unsigned"); throw new Error("Value " + value + " cannot be negative if the type is unsigned");
@@ -54,7 +54,7 @@ export class Integer {
} }
isTheSame (other : Integer) : boolean { 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() { toUnsigned() {
@@ -71,9 +71,9 @@ export class Integer {
const orig = this.toString(2).padStart(this.maxBitSize, '0'); 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 n = BigInt("0b"+inverted);
const negative = orig[0] == '1'; const negative = orig[0] === '1';
return new Integer(negative ? -n : n, this.maxBitSize, true) return new Integer(negative ? -n : n, this.maxBitSize, true)
} }
@@ -118,24 +118,24 @@ export class Integer {
export function asInteger(num: JsNumber | Integer | string): Integer { export function asInteger(num: JsNumber | Integer | string): Integer {
if(typeof num == "string") if(typeof num === "string")
return asInteger(BigInt(num)); return asInteger(BigInt(num));
if(isInteger(num)) if(isInteger(num))
return num; return num;
if(typeof num == "number" && isNaN(num)) { if(typeof num === "number" && isNaN(num)) {
throw new Error("Cannot create BoundedNumber from NaN"); throw new Error("Cannot create BoundedNumber from NaN");
} }
const size = num > INT32_MAX_VALUE || num < INT32_MIN_VALUE ? 64 : 32; 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); return new Integer(n, size);
} }
export function isInteger(num: JsNumber | Integer): num is Integer { 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 { function detectSize(value: bigint, signed: boolean): number {

View File

@@ -1,8 +1,8 @@
function flipBitsToZero(byte: number, numberOfBits : number) : number { function flipBitsToZero(byte: number, numberOfBits : number) : number {
if(numberOfBits == 0) if(numberOfBits === 0)
return byte; 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; const result = byte & zerouOutMask;
return result; return result;
@@ -11,16 +11,16 @@ function flipBitsToZero(byte: number, numberOfBits : number) : number {
// TODO: continue here to implement getting broadcast address // TODO: continue here to implement getting broadcast address
function flipBitsToOne(byte : number, numberOfBits : number) : number { 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; const result = byte | zerouOutMask;
return result; return result;
} }
function createSubnetMaskByte(numberOfBits: number) { function createSubnetMaskByte(numberOfBits: number) {
return 255<<(8-numberOfBits)&255;; return (255 << (8 - numberOfBits)) & 255;
} }
export {flipBitsToZero, createSubnetMaskByte, flipBitsToOne}; export {flipBitsToZero, createSubnetMaskByte, flipBitsToOne};

View File

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

View File

@@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import './BinaryString.css'; import './BinaryString.css';
import loglevel from 'loglevel';
export type BinaryStringViewProps = { export type BinaryStringViewProps = {
allowFlipBits?: boolean; allowFlipBits?: boolean;
@@ -32,7 +31,7 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
} }
const arr = this.props.binaryString.split(''); const arr = this.props.binaryString.split('');
arr[index] = arr[index] == '0' ? '1' : '0'; arr[index] = arr[index] === '0' ? '1' : '0';
const newBinaryString = arr.join(''); const newBinaryString = arr.join('');
this.props.onBitClicked({ this.props.onBitClicked({
@@ -59,13 +58,13 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
const css = allowFlipBits ? ' flipable' : '' const css = allowFlipBits ? ' flipable' : ''
const disableHighlight = this.props.disableHighlight || false; 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 ? bitChars.length - this.props.valueBitSize
: -1; : -1;
return bitChars.map((c, i) => { return bitChars.map((c, i) => {
var className = c == '1' ? `one${css}` : `zero${css}`; var className = c === '1' ? `one${css}` : `zero${css}`;
var tooltip = ''; 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react'; import React from 'react';
import cmd from '../../shell/cmd'; import cmd from '../../shell/cmd';
import './CommandLink.css';
type CommandLinkProps = { type CommandLinkProps = {
command?:string; command?:string;
@@ -12,12 +13,14 @@ type CommandLinkProps = {
function CommandLink({icon, command, text, textClassName}: CommandLinkProps) { function CommandLink({icon, command, text, textClassName}: CommandLinkProps) {
const onClick = () => cmd.execute(command || text); const onClick = () => cmd.execute(command ?? text);
if(icon != null) return (
return <a href="javascript:void(0)" onClick={onClick}><FontAwesomeIcon icon={icon} className="icon" /><span className={textClassName}>{text}</span></a>; <button type="button" className="command-link" onClick={onClick}>
{icon ? <FontAwesomeIcon icon={icon} className="icon" /> : null}
return <a href="javascript:void(0)" onClick={onClick}><span className={textClassName}>{text}</span></a>; <span className={textClassName}>{text}</span>
</button>
);
} }
export default CommandLink; export default CommandLink;

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
export default { const typeGuards = {
plainObject: function(obj : any) : boolean { 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 { aFunction: function(obj : any) : boolean {
return typeof obj == "function"; return typeof obj === "function";
}, },
string: function(obj : any) : boolean { string: function(obj : any) : boolean {
return typeof obj == "string"; return typeof obj === "string";
}, },
array: function(obj : any) : boolean { array: function(obj : any) : boolean {
@@ -16,6 +16,8 @@ export default {
}, },
number: function(obj : any) : boolean { 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)) if(isInteger(num))
return asIntN(num.value); 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) { function random(from: number, to: number) {
@@ -23,11 +23,11 @@ function random(from: number, to: number) {
} }
function randomBool() { function randomBool() {
return random(1, 10000) % 2 == 0; return random(1, 10000) % 2 === 0;
} }
function logLines(...params: any[]) { function logLines(...params: any[]) {
console.log(params.join('\n')) console.log(params.join('\n'));
} }
export {chunkifyString, asIntN, random, randomBool, logLines}; export {chunkifyString, asIntN, random, randomBool, logLines};

View File

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

View File

@@ -1,6 +1,6 @@
import calc from "../core/calc"; import calc from "../core/calc";
import Operand from "./Operand"; import Operand from "./Operand";
import { Expression, ExpressionElement } from "./expression-interfaces"; import { Expression } from "./expression-interfaces";
export default class ListOfNumbers implements Expression { export default class ListOfNumbers implements Expression {
children: Operand[]; 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 { NumberBase } from '../core/formatter';
import { INT64_MAX_VALUE, INT64_MIN_VALUE, UINT64_MAX_VALUE } from '../core/const'; import { INT64_MAX_VALUE, INT64_MIN_VALUE, UINT64_MAX_VALUE } from '../core/const';
import { Integer, JsNumber, isInteger, asInteger } from '../core/Integer'; import { Integer, JsNumber, isInteger, asInteger } from '../core/Integer';

View File

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

View File

@@ -7,7 +7,7 @@ import { Operator, Operand, ListOfNumbers } from '../expression';
import calc from '../../core/calc'; import calc from '../../core/calc';
import { Integer } from '../../core/Integer'; import { Integer } from '../../core/Integer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 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 loglevel from 'loglevel';
import IconWithToolTip from '../../shell/components/IconWithTooltip'; import IconWithToolTip from '../../shell/components/IconWithTooltip';
@@ -191,7 +191,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
} }
getLabelString(op: Operand): string { 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() { undo() {
@@ -201,7 +201,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
onBitClicked(args: BitClickedEventArg) { onBitClicked(args: BitClickedEventArg) {
const { bitIndex, binaryStringLength: binaryStringLength } = args; const { bitIndex, binaryStringLength } = args;
const maxBitSize = this.scalar.value.maxBitSize; 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 { ExpressionElement, Expression } from '../expression-interfaces';
import calc from '../../core/calc'; import calc from '../../core/calc';
import formatter from '../../core/formatter'; import formatter from '../../core/formatter';
import exp from 'constants';
type Config = { type Config = {
emphasizeBytes: boolean; emphasizeBytes: boolean;
@@ -44,26 +43,25 @@ export default class BitwiseResultViewModel {
static buildBitwiseOperation (expr : BitwiseOperation, config : Config) { static buildBitwiseOperation (expr : BitwiseOperation, config : Config) {
var op = expr.children[0], const len = expr.children.length;
i = 0, len = expr.children.length, const m = new BitwiseResultViewModel(config);
ex, m = new BitwiseResultViewModel(config);
var prev : Operand | null = null; let prev : Operand | null = null;
for (;i<len;i++) { for (let i = 0; i < len; i++) {
ex = expr.children[i]; const ex = expr.children[i];
if(ex instanceof Operand) { if(ex instanceof Operand) {
m.addScalarRow(ex); m.addScalarRow(ex);
prev = ex; prev = ex;
continue; continue;
} }
var eo = ex as Operator; const eo = ex as Operator;
// If it a single NOT expression // If it a single NOT expression
if(eo.isNotExpression) { if(eo.isNotExpression) {
m.addOperatorRow(eo); m.addOperatorRow(eo);
var notResult = eo.evaluate(); const notResult = eo.evaluate();
m.addExpressionResultRow(notResult); m.addExpressionResultRow(notResult);
prev = notResult; prev = notResult;
} }
@@ -152,7 +150,7 @@ export default class BitwiseResultViewModel {
static applyEmphasizeBytes (bits : number, emphasizeBytes : boolean) : number { static applyEmphasizeBytes (bits : number, emphasizeBytes : boolean) : number {
if(emphasizeBytes && bits % 8 != 0) { if(emphasizeBytes && bits % 8 !== 0) {
if(bits < 8) { if(bits < 8) {
return 8; return 8;
} }

View File

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

View File

@@ -27,13 +27,13 @@ class NumberParser {
parse (input : string) : ParsedNumber { 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 regex = new RegExp(numberRegexFullString, "i");
const m = regex.exec(input); 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"); throw new Error(input + " is not a number");
const value = parseInteger(m[0], m[1], m[2] || ''); const value = parseInteger(m[0], m[1], m[2] || '');

View File

@@ -1,8 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import BinaryStringView from '../../core/components/BinaryString';
import './SubnetView.css'; import './SubnetView.css';
import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp } from '../subnet-utils'; import { getNetworkAddress, getBroadCastAddress, createSubnetMaskIp } from '../subnet-utils';
import { chunkifyString } from '../../core/utils';
import IpAddressBinaryString from './IpAddressBinaryString'; import IpAddressBinaryString from './IpAddressBinaryString';
import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from '../models'; import { IpAddress, IpAddressWithSubnetMask, SubnetCommand } from '../models';
@@ -41,9 +39,9 @@ function SubnetView(props : {subnet : SubnetCommand}) {
</td> </td>
<td data-test-name="decimal"> <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> <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> </td>
</tr> </tr>
</tbody> </tbody>

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import { createSubnetMaskByte } from "../core/byte"; import { createSubnetMaskByte, flipBitsToOne, flipBitsToZero } from '../core/byte';
import { flipBitsToOne, flipBitsToZero } from '../core/byte';
import { IpAddress, IpAddressWithSubnetMask, NetworkClass } from "./models"; import { IpAddress, IpAddressWithSubnetMask, NetworkClass } from "./models";
function createSubnetMaskIp(ipm: IpAddressWithSubnetMask) : IpAddress { function createSubnetMaskIp(ipm: IpAddressWithSubnetMask) : IpAddress {
@@ -59,7 +58,6 @@ function getNetworkClass (ipAddress: IpAddress) : NetworkClass {
const byte = ipAddress.firstByte; const byte = ipAddress.firstByte;
const firstBitOne = (byte & 128) === 128; const firstBitOne = (byte & 128) === 128;
const firstBitZero = (byte & 128) === 0;
const secondBitOne = (byte & 64) === 64; const secondBitOne = (byte & 64) === 64;
const thirdBitOne = (byte & 32) === 32; 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) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import('web-vitals').then((module) => {
getCLS(onPerfEntry); const {
getFID(onPerfEntry); onCLS,
getFCP(onPerfEntry); onFID,
getLCP(onPerfEntry); onFCP,
getTTFB(onPerfEntry); 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.uiTheme = persistData.uiTheme;
this.emphasizeBytes = !!persistData.emphasizeBytes; this.emphasizeBytes = !!persistData.emphasizeBytes;
this.persistedVersion = persistData.version || 0.1; 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.debugMode = persistData.debugMode === true;
this.pageVisitsCount = persistData.pageVisistsCount || 0; this.pageVisitsCount = persistData.pageVisistsCount || 0;
this.donationClicked = persistData.donationClicked; this.donationClicked = persistData.donationClicked;
@@ -79,7 +79,7 @@ export default class AppState {
} }
toggleEmphasizeBytes(value?: boolean) { toggleEmphasizeBytes(value?: boolean) {
this.emphasizeBytes = value != null ? value : !this.emphasizeBytes; this.emphasizeBytes = value !== null && value !== undefined ? value : !this.emphasizeBytes;
this.triggerChanged(); this.triggerChanged();
} }
@@ -107,7 +107,7 @@ export default class AppState {
} }
toggleAnnotateTypes(value?: boolean) { toggleAnnotateTypes(value?: boolean) {
this.annotateTypes = value != null ? value : !this.annotateTypes; this.annotateTypes = value !== null && value !== undefined ? value : !this.annotateTypes;
this.triggerChanged(); this.triggerChanged();
} }

View File

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

View File

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

View File

@@ -36,19 +36,19 @@ export class CmdShell {
log.debug(`Executing command: ${rawInput}`); 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(); const input = rawInput.trim().toLowerCase();
var handler = this.findHandler(input); const handler = this.findHandler(input);
if(handler != null) { if(handler !== null) {
// if(this.debugMode) { // if(this.debugMode) {
// this.invokeHandler(input, handler, ops); // this.invokeHandler(input, handler, ops);
// return // return
// } // }
try { try {
this.invokeHandler(input, handler, ops); this.invokeHandler(input, handler, resolvedOps);
} catch (e) { } catch (e) {
this.handleError(input, e as Error); this.handleError(input, e as Error);
} }
@@ -64,8 +64,8 @@ export class CmdShell {
} }
command (cmd : string | object, handler? : any) { command (cmd : string | object, handler? : any) {
var h = this.createHandler(cmd, handler); const h = this.createHandler(cmd, handler);
if(h == null){ if(h === null){
console.warn('unexpected set of arguments: ', JSON.stringify(arguments)); console.warn('unexpected set of arguments: ', JSON.stringify(arguments));
return; return;
} }
@@ -96,13 +96,13 @@ export class CmdShell {
} }
findHandler (input: string) : ICommandHandler | null { 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) { invokeHandler (input : string, handler : ICommandHandler, options: CommandOptions) {
var cmdResult = handler.handle({ input: input, options }); const cmdResult = handler.handle({ input: input, options });
if(cmdResult != null) { if(cmdResult !== null && cmdResult !== undefined) {
log.debug(cmdResult); log.debug(cmdResult);
} }
}; };
@@ -112,9 +112,11 @@ export class CmdShell {
if(this.debugMode) if(this.debugMode)
console.error(input, err); console.error(input, err);
if(this.errorHandler != null) if(this.errorHandler !== null)
this.errorHandler(input, err); 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 DisplayResultView from './DisplayResultView';
import AppState, { CommandResultView } from '../AppState'; import AppState, { CommandResultView } from '../AppState';
import cmd from '../cmd'; import cmd from '../cmd';
import log from 'loglevel';
import DebugIndicators from './DebugIndicators'; import DebugIndicators from './DebugIndicators';
import hash from '../../core/hash'; import hash from '../../core/hash';
import TopLinks from './TopLinks'; import TopLinks from './TopLinks';
@@ -49,7 +48,7 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
render() { render() {
const enableNewUi = this.props.appState.env != 'prod' || true; const enableNewUi = this.props.appState.env !== 'prod' || true;
const newUi = enableNewUi ? 'new-ui' : ''; const newUi = enableNewUi ? 'new-ui' : '';
const settingsCss = "settings-button" + (this.props.appState.showSettings ? '' : ' soft'); 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"> <div className="expressionInput-container">
<InputBox onCommandEntered={(input) => cmd.execute(input)} /> <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> </div>
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null} {this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}

View File

@@ -7,7 +7,7 @@ function DebugIndicators(props: {appState: AppState}) {
const list = []; const list = [];
const state = props.appState; const state = props.appState;
if(props.appState.env != 'prod') { if(props.appState.env !== 'prod') {
list.push(state.env); list.push(state.env);
} }
@@ -19,7 +19,7 @@ function DebugIndicators(props: {appState: AppState}) {
list.push("notrack"); list.push("notrack");
} }
if(list.length == 0) if(list.length === 0)
return null; return null;
return <div className="debug-indicators"> 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState } from 'react'; import React, { useState } from 'react';
import AppState from '../AppState'; import AppState from '../AppState';

View File

@@ -1,15 +1,15 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState} from 'react'; 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 "./DonateResultView.css";
import { sendAnalyticsEvent } from '../analytics'; import { sendAnalyticsEvent } from '../analytics';
import { faPaypal } from '@fortawesome/free-brands-svg-icons'; import { faPaypal } from '@fortawesome/free-brands-svg-icons';
function DonateResultView() { function DonateResultView() {
const copyCss = navigator.clipboard != null ? "" : "hidden"; const copyCss = navigator.clipboard !== null && navigator.clipboard !== undefined ? "" : "hidden";
const [state, setState] = useState('default'); const [state, setState] = useState('default');
const copiedCss = state == "copied" ? "" : "hidden"; const copiedCss = state === "copied" ? "" : "hidden";
const addr = "bc1qyv08z29776uwdwy2m0c77gpgpupzr78jpcnraq"; const addr = "bc1qyv08z29776uwdwy2m0c77gpgpupzr78jpcnraq";
@@ -19,13 +19,13 @@ function DonateResultView() {
<div className='section'> <div className='section'>
<h3>buymeacoffee.com</h3> <h3>buymeacoffee.com</h3>
<p> <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 <FontAwesomeIcon icon={faCoffee} size='lg' /> Buy Me a Coffee
</a> </a>
</p> </p>
<h3>PayPal</h3> <h3>PayPal</h3>
<p> <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' /> <FontAwesomeIcon icon={faPaypal} size='lg' />
Donate via PayPal Donate via PayPal
</a> </a>
@@ -35,14 +35,16 @@ function DonateResultView() {
<div className='section'> <div className='section'>
<h3>Bitcoin</h3> <h3>Bitcoin</h3>
<span>BTC Address:</span> <strong>{addr}</strong> <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 Copy
</button> <span className={`soft ${copiedCss}`}>copied</span> </button> <span className={`soft ${copiedCss}`}>copied</span>
</div> </div>
</div> </div>
function copy() { function copy() {
navigator.clipboard.writeText(addr); if (navigator.clipboard) {
navigator.clipboard.writeText(addr);
}
setState('copied'); setState('copied');
setTimeout(() => setState('default'), 3000); setTimeout(() => setState('default'), 3000);
sendAnalyticsEvent({eventCategory: "Donation", eventAction: "CopyBTCAddressCopyClicked"}) sendAnalyticsEvent({eventCategory: "Donation", eventAction: "CopyBTCAddressCopyClicked"})

View File

@@ -3,7 +3,7 @@ import CommandLink from '../../core/components/CommandLink';
import './HelpResultView.css'; import './HelpResultView.css';
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from '../../core/const'; import { INT32_MAX_VALUE, INT32_MIN_VALUE } from '../../core/const';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 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() { function HelpResultView() {
@@ -59,7 +59,7 @@ function HelpResultView() {
<li><code>&gt;&gt;&gt;</code> zero-fill right shift</li> <li><code>&gt;&gt;&gt;</code> zero-fill right shift</li>
</ul> </ul>
<div className='important-note'> <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> </div>
<div className="section soft-border"> <div className="section soft-border">

View File

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

View File

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

View File

@@ -9,12 +9,12 @@ function TopLinks() {
return <ul className="top-links"> return <ul className="top-links">
<li> <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> <FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span>
</button> </button>
</li> </li>
<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>
<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> <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(); var startupCommands = loadStoredCommands();
if(startupCommands.length == 0) if(startupCommands.length === 0)
startupCommands = DEFAULT_COMMANDS; startupCommands = DEFAULT_COMMANDS;
if(appState.wasOldVersion) { if(appState.wasOldVersion) {
@@ -49,7 +49,7 @@ function getStartupCommands(appState : AppState) : string[] {
if(hashArgs.length > 0) { if(hashArgs.length > 0) {
if(hashArgs.indexOf('empty')==-1) if(hashArgs.indexOf('empty') === -1)
startupCommands = hashArgs; startupCommands = hashArgs;
} }
@@ -60,11 +60,11 @@ function getStartupCommands(appState : AppState) : string[] {
function loadStoredCommands() : string[] { function loadStoredCommands() : string[] {
const json = localStorage.getItem(STARTUP_COMMAND_KEY); const json = localStorage.getItem(STARTUP_COMMAND_KEY);
return json != null ? [json] : []; return json !== null && json !== undefined ? [json] : [];
} }
function setupLogger(env: Env) { function setupLogger(env: Env) {
if(env != 'prod'){ if(env !== 'prod'){
log.setLevel("debug"); log.setLevel("debug");
log.debug(`Log level is set to debug. Env: ${env}`) log.debug(`Log level is set to debug. Env: ${env}`)
} else { } else {