mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-24 12:44:12 +01:00
Introduce the Settings pane (#49)
This commit is contained in:
@@ -13,6 +13,7 @@ import loglevel from 'loglevel';
|
||||
type BitwiseResultViewProps = {
|
||||
expression: Expression;
|
||||
emphasizeBytes: boolean;
|
||||
annotateTypes: boolean
|
||||
}
|
||||
|
||||
type BitwiseResultViewState = {
|
||||
@@ -43,12 +44,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
return <div className='error'>Error: {text}</div>
|
||||
}
|
||||
|
||||
const showInfoColumn : boolean = model.items
|
||||
.map(i => willInfoColumnBeVisible(i.expressionElement, model!.maxNumberOfBits, allowSignChange))
|
||||
.filter(p => p == true)
|
||||
.length > 0;
|
||||
|
||||
var rows = this.getRows(model!, showInfoColumn, allowSignChange);
|
||||
var rows = this.getRows(model!, allowSignChange);
|
||||
|
||||
return <table className="expression">
|
||||
<tbody>
|
||||
@@ -57,7 +53,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
</table>
|
||||
}
|
||||
|
||||
getRows(model: BitwiseResultViewModel, showInfoColumn : boolean, allowSignChange : boolean): JSX.Element[] {
|
||||
getRows(model: BitwiseResultViewModel, allowSignChange : boolean): JSX.Element[] {
|
||||
|
||||
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
|
||||
|
||||
@@ -72,7 +68,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
expressionItem={itm.expressionElement}
|
||||
emphasizeBytes={this.props.emphasizeBytes}
|
||||
maxNumberOfBits={this.maxSeenLengthNumberOfBits}
|
||||
showInfoColumn={showInfoColumn}
|
||||
showInfoColumn={this.props.annotateTypes}
|
||||
onValueChanged={() => this.onValueChanged()} />);
|
||||
}
|
||||
|
||||
@@ -92,7 +88,7 @@ type ExpressionElementRowProps = {
|
||||
allowSignChange: boolean,
|
||||
expressionItem: ExpressionElement,
|
||||
onValueChanged: any,
|
||||
showInfoColumn: boolean
|
||||
showInfoColumn: boolean,
|
||||
}
|
||||
|
||||
class ExpressionElementTableRow extends React.Component<ExpressionElementRowProps> {
|
||||
@@ -221,25 +217,23 @@ getLabel(): string {
|
||||
const children = [];
|
||||
let title = `BitwiseCmd treats this number as ${op.value.maxBitSize}-bit integer`;
|
||||
let text = `${op.value.maxBitSize}-bit `;
|
||||
|
||||
const signedStr = op.value.signed ? 'signed' : 'unsigned';
|
||||
const signedOther = op.value.signed ? 'usigned' : 'signed';
|
||||
const signedTitle = `Click to change to ${signedOther} preserving the same bits`;
|
||||
const signedOther = op.value.signed ? 'unsigned' : 'signed';
|
||||
const signedButtonTitle = `Click to change to ${signedOther} preserving the same bits`;
|
||||
|
||||
if(op.label.length > 0)
|
||||
{
|
||||
text += " (converted)";
|
||||
title += ". This number was converted to facilitate bitwise operation with an operand of a different type";
|
||||
title += ". This number was converted to facilitate bitwise operation with an operand of a different type.";
|
||||
}
|
||||
|
||||
children.push(<span title={title} style={{cursor:"help"}}>{text.trim()}</span>);
|
||||
|
||||
if(this.props.maxNumberOfBits >= op.value.maxBitSize)
|
||||
{
|
||||
if(allowSignChange)
|
||||
children.push(<button className='accent1' title={signedTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
|
||||
else if(!op.value.signed)
|
||||
children.push(<span className='accent1'> {signedStr}</span>)
|
||||
}
|
||||
if(allowSignChange)
|
||||
children.push(<button className='accent1' title={signedButtonTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
|
||||
else
|
||||
children.push(<span className='accent1'> {signedStr}</span>)
|
||||
|
||||
return <React.Fragment>{children}</React.Fragment>
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const expressionAppModule = {
|
||||
canHandle: (input:string) => parser.canParse(input),
|
||||
handle: function(c: CommandInput) {
|
||||
var expr = parser.parse(c.input);
|
||||
appState.addCommandResult(c.input, () => <BitwiseResultView expression={expr!} emphasizeBytes={appState.emphasizeBytes} />);
|
||||
appState.addCommandResult(c.input, () => <BitwiseResultView expression={expr!} emphasizeBytes={appState.emphasizeBytes} annotateTypes={appState.annotateTypes} />);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ describe("parser", () => {
|
||||
expect(v?.num()).toBe(1);
|
||||
});
|
||||
|
||||
it('fits usigned int32 max value into 32-bit data type', () => {
|
||||
it('fits unsigned int32 max value into 32-bit data type', () => {
|
||||
const n1 = numberParser.parse("4294967295u");
|
||||
const n2 = numberParser.parse("4294967296u");
|
||||
|
||||
@@ -125,28 +125,28 @@ describe("parser", () => {
|
||||
//expect(v2).toEqual(v);
|
||||
});
|
||||
|
||||
it('parses usigned single', () => {
|
||||
it('parses unsigned single', () => {
|
||||
var v = numberParser.parse('1us')?.value
|
||||
expect(v?.maxBitSize).toBe(16);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned int32', () => {
|
||||
it('parses unsigned int32', () => {
|
||||
var v = numberParser.parse('1u')?.value
|
||||
expect(v?.maxBitSize).toBe(32);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned byte', () => {
|
||||
it('parses unsigned byte', () => {
|
||||
var v = numberParser.parse('1ub')?.value
|
||||
expect(v?.maxBitSize).toBe(8);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned long', () => {
|
||||
it('parses unsigned long', () => {
|
||||
var v = numberParser.parse('1ul')?.value
|
||||
expect(v?.maxBitSize).toBe(64);
|
||||
expect(v?.num()).toBe(1);
|
||||
|
||||
@@ -75,6 +75,10 @@ a.hashLink { font-size: 1.1em;}
|
||||
|
||||
button { border: none; text-decoration: underline;}
|
||||
|
||||
.settings-button {
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.undo button {
|
||||
opacity: 0.4;
|
||||
padding: 0;
|
||||
|
||||
@@ -8,7 +8,8 @@ export type PersistedAppData = {
|
||||
version: number | null;
|
||||
debugMode: boolean | null;
|
||||
pageVisistsCount: number;
|
||||
donationClicked: boolean
|
||||
donationClicked: boolean;
|
||||
annotateTypes: boolean
|
||||
}
|
||||
|
||||
export type CommandResultView = {
|
||||
@@ -26,27 +27,28 @@ export default class AppState {
|
||||
version: number = APP_VERSION;
|
||||
emphasizeBytes: boolean;
|
||||
debugMode: boolean = false;
|
||||
uiTheme: string;
|
||||
changeHandlers: AppStateChangeHandler[];
|
||||
commandResults: CommandResultView[];
|
||||
uiTheme: string = 'midnight';
|
||||
changeHandlers: AppStateChangeHandler[] = [];
|
||||
commandResults: CommandResultView[] = [];
|
||||
persistedVersion: number;
|
||||
wasOldVersion: boolean;
|
||||
env: string;
|
||||
pageVisitsCount: number;
|
||||
donationClicked: boolean;
|
||||
showSettings: boolean = false;
|
||||
annotateTypes: boolean = false;
|
||||
|
||||
constructor(persistData : PersistedAppData, env: string) {
|
||||
this.commandResults = [];
|
||||
this.changeHandlers = [];
|
||||
this.uiTheme = persistData.uiTheme || 'midnight';
|
||||
|
||||
this.env = env;
|
||||
|
||||
this.emphasizeBytes = !!persistData.emphasizeBytes;
|
||||
this.emphasizeBytes = !!persistData.emphasizeBytes;
|
||||
this.persistedVersion = persistData.version || 0.1;
|
||||
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
|
||||
this.debugMode = persistData.debugMode === true;
|
||||
this.pageVisitsCount = persistData.pageVisistsCount || 0;
|
||||
this.donationClicked = persistData.donationClicked;
|
||||
this.annotateTypes = !!persistData.annotateTypes;
|
||||
}
|
||||
|
||||
addCommandResult(input : string, view : ViewFactory) {
|
||||
@@ -92,6 +94,16 @@ export default class AppState {
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleShowSettings() {
|
||||
this.showSettings = !this.showSettings;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleAnnotateTypes() {
|
||||
this.annotateTypes = !this.annotateTypes;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
registerVisit() {
|
||||
this.pageVisitsCount++;
|
||||
this.triggerChanged();
|
||||
@@ -112,7 +124,8 @@ export default class AppState {
|
||||
version: this.version,
|
||||
debugMode: this.debugMode,
|
||||
pageVisistsCount: this.pageVisitsCount,
|
||||
donationClicked: this.donationClicked
|
||||
donationClicked: this.donationClicked,
|
||||
annotateTypes: this.annotateTypes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,8 @@ const DEFAULT_DATA : PersistedAppData = {
|
||||
version: APP_VERSION,
|
||||
debugMode: false,
|
||||
pageVisistsCount: 0,
|
||||
donationClicked: false
|
||||
donationClicked: false,
|
||||
annotateTypes: false
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -8,6 +8,9 @@ import DebugIndicators from './DebugIndicators';
|
||||
import hash from '../../core/hash';
|
||||
import TopLinks from './TopLinks';
|
||||
import Toggle from './Toggle';
|
||||
import SettingsPane from './SettingsPane';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faGear } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
|
||||
type AppRootProps = {
|
||||
@@ -44,15 +47,11 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
|
||||
return results;
|
||||
}
|
||||
|
||||
toggleEmphasizeBytes() {
|
||||
this.props.appState.toggleEmphasizeBytes();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const enableNewUi = this.props.appState.env != 'prod' || true;
|
||||
const newUi = enableNewUi ? 'new-ui' : '';
|
||||
|
||||
const settingsCss = "settings-button" + (this.props.appState.showSettings ? '' : ' soft');
|
||||
return <div className={`app-root ${this.state.uiTheme} ${newUi}`}>
|
||||
<DebugIndicators appState={this.props.appState} />
|
||||
<div className="header">
|
||||
@@ -63,12 +62,11 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
|
||||
|
||||
<div className="expressionInput-container">
|
||||
<InputBox onCommandEntered={(input) => cmd.execute(input)} />
|
||||
|
||||
<button className={settingsCss}><FontAwesomeIcon icon={faGear} onClick={() => this.props.appState.toggleShowSettings()} /></button>
|
||||
|
||||
<span className="configPnl">
|
||||
<Toggle elementId="emphasizeBytes" text="[em]" isOn={this.state.emphasizeBytes} onClick={() => this.toggleEmphasizeBytes()} title="Toggle Emphasize Bytes" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}
|
||||
<div id="output">
|
||||
{this.getResultViews()}
|
||||
</div>
|
||||
|
||||
24
src/shell/components/SettingsPane.css
Normal file
24
src/shell/components/SettingsPane.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.settings-container {
|
||||
padding: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.settings-container button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-container button svg {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.settings-container .description {
|
||||
font-size: 0.85em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-left: 30px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.settings-container .setting {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
42
src/shell/components/SettingsPane.tsx
Normal file
42
src/shell/components/SettingsPane.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import './SettingsPane.css';
|
||||
import { faToggleOff, faToggleOn } from '@fortawesome/free-solid-svg-icons';
|
||||
import AppState from '../AppState';
|
||||
|
||||
type SettingsPaneProps = {
|
||||
appState : AppState
|
||||
}
|
||||
|
||||
function SettingsPane(props : SettingsPaneProps) {
|
||||
|
||||
const {appState} = props;
|
||||
|
||||
return <div id="settings" className='settings-container'>
|
||||
<div className="inner">
|
||||
<h3>Settings</h3>
|
||||
<div className='setting'>
|
||||
<button onClick={() => appState.toggleEmphasizeBytes()}>
|
||||
<FontAwesomeIcon icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes
|
||||
</button>
|
||||
<p className='description'>
|
||||
{appState.emphasizeBytes
|
||||
? "Each binary string is extended to contain at least 8 bits. A white space is added between each group of 8 bits so it easy to tell bytes apart."
|
||||
: "Binary strings are not modified."}
|
||||
</p>
|
||||
</div>
|
||||
<div className='setting'>
|
||||
<button onClick={() => appState.toggleAnnotateTypes()}>
|
||||
<FontAwesomeIcon icon={appState.annotateTypes ? faToggleOn : faToggleOff} /> Annotate Data Types
|
||||
</button>
|
||||
<p className='description'>
|
||||
{appState.annotateTypes
|
||||
? "BitwiseCmd shows the integer size and indicates whether the data type is signed or unsigned."
|
||||
: "Information about the size of integers used in the calculation is hidden."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default SettingsPane;
|
||||
Reference in New Issue
Block a user