mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-20 19:02:45 +01:00
Refactor towards modular structure
This commit is contained in:
91
src/shell/AppState.ts
Normal file
91
src/shell/AppState.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import log from 'loglevel';
|
||||
|
||||
const APP_VERSION = 5;
|
||||
|
||||
export type PersistedAppData = {
|
||||
emphasizeBytes: boolean;
|
||||
uiTheme: string;
|
||||
version: number;
|
||||
debugMode: boolean | null;
|
||||
}
|
||||
|
||||
export type CommandResultView = {
|
||||
key: number,
|
||||
input: string,
|
||||
view: JSX.Element
|
||||
};
|
||||
|
||||
export type AppStateChangeHandler = (state: AppState) => void;
|
||||
|
||||
export default class AppState {
|
||||
|
||||
version: number = APP_VERSION;
|
||||
emphasizeBytes: boolean;
|
||||
debugMode: boolean = false;
|
||||
uiTheme: string;
|
||||
changeHandlers: AppStateChangeHandler[];
|
||||
commandResults: CommandResultView[];
|
||||
persistedVersion: number;
|
||||
wasOldVersion: boolean;
|
||||
env: string;
|
||||
|
||||
constructor(persistData : PersistedAppData, env: string) {
|
||||
this.commandResults = [];
|
||||
this.changeHandlers = [];
|
||||
this.uiTheme = persistData.uiTheme || 'midnight';
|
||||
this.env = env;
|
||||
|
||||
this.emphasizeBytes = persistData.emphasizeBytes || true;
|
||||
this.persistedVersion = persistData.version || 0.1;
|
||||
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
|
||||
this.debugMode = env !== 'prod' || persistData.debugMode === true;
|
||||
}
|
||||
|
||||
addCommandResult(input : string, view : JSX.Element) {
|
||||
const key = generateKey();
|
||||
this.commandResults.unshift({key, input, view});
|
||||
log.debug(`command result added: ${input}`);
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
clearCommandResults() {
|
||||
this.commandResults = [];
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleEmphasizeBytes() {
|
||||
this.emphasizeBytes = !this.emphasizeBytes;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
onChange(handler : AppStateChangeHandler) {
|
||||
this.changeHandlers.push(handler);
|
||||
}
|
||||
|
||||
triggerChanged() {
|
||||
this.changeHandlers.forEach(h => h(this));
|
||||
}
|
||||
|
||||
setUiTheme(theme: string) {
|
||||
this.uiTheme = theme;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleDebugMode() {
|
||||
this.debugMode = !this.debugMode;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
getPersistData() : PersistedAppData {
|
||||
return {
|
||||
emphasizeBytes: this.emphasizeBytes,
|
||||
uiTheme: this.uiTheme,
|
||||
version: this.version,
|
||||
debugMode: this.debugMode
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function generateKey() : number {
|
||||
return Math.ceil(Math.random()*10000000) ^ Date.now(); // Because why the hell not...
|
||||
}
|
||||
28
src/shell/appStateStore.ts
Normal file
28
src/shell/appStateStore.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import AppState, { PersistedAppData } from "./AppState";
|
||||
|
||||
const storeKey = 'AppState';
|
||||
|
||||
export default {
|
||||
getPersistedData() : PersistedAppData {
|
||||
var json = window.localStorage.getItem(storeKey);
|
||||
if(!json) {
|
||||
return {} as PersistedAppData;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(json) as PersistedAppData;
|
||||
}
|
||||
catch(ex) {
|
||||
console.error('Failed to parse AppState json. Json Value: \n' + json, ex);
|
||||
return {} as PersistedAppData;;
|
||||
}
|
||||
},
|
||||
|
||||
watch (appState: AppState) {
|
||||
appState.onChange(() => this.persistData(appState));
|
||||
},
|
||||
|
||||
persistData(appState: AppState) {
|
||||
localStorage.setItem(storeKey, JSON.stringify(appState.getPersistData()));
|
||||
}
|
||||
}
|
||||
48
src/shell/cmd.test.ts
Normal file
48
src/shell/cmd.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { CmdShell, ICommandHandler } from "./cmd";
|
||||
|
||||
describe("CmdShell", () => {
|
||||
it("simple command", () => {
|
||||
|
||||
var handler = {
|
||||
test1() { },
|
||||
test2() { }
|
||||
};
|
||||
|
||||
spyOn(handler, "test1");
|
||||
spyOn(handler, "test2");
|
||||
|
||||
var sut = new CmdShell();
|
||||
sut.command("test1", handler.test1);
|
||||
|
||||
sut.execute("test1");
|
||||
|
||||
expect(handler.test1).toHaveBeenCalled();
|
||||
expect(handler.test2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("unknown command", () => {
|
||||
var sut = new CmdShell();
|
||||
sut.execute("test1");
|
||||
});
|
||||
|
||||
it("object handler", () => {
|
||||
|
||||
var handler = {
|
||||
canHandle: function(input: string) : boolean { return input === "test2"; },
|
||||
handle: function (input: string) { }
|
||||
};
|
||||
|
||||
spyOn(handler, "handle");
|
||||
|
||||
var sut = new CmdShell();
|
||||
sut.command(handler);
|
||||
|
||||
sut.execute("test1");
|
||||
|
||||
expect(handler.handle).not.toHaveBeenCalled();
|
||||
|
||||
sut.execute("test2");
|
||||
|
||||
expect(handler.handle).toHaveBeenCalled();
|
||||
});
|
||||
})
|
||||
107
src/shell/cmd.ts
Normal file
107
src/shell/cmd.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import is from '../core/is';
|
||||
import log from 'loglevel';
|
||||
|
||||
export type CommandInput = {
|
||||
input: string;
|
||||
}
|
||||
|
||||
type HandleFunction = (input: CommandInput) => void;
|
||||
type InputErrorHandler = (input:string, error: Error) => void;
|
||||
|
||||
export interface ICommandHandler {
|
||||
canHandle (input:string) : boolean;
|
||||
handle: HandleFunction;
|
||||
}
|
||||
|
||||
export class CmdShell {
|
||||
debugMode: boolean;
|
||||
handlers: ICommandHandler[];
|
||||
errorHandler: InputErrorHandler | null;
|
||||
constructor() {
|
||||
this.handlers = [];
|
||||
this.debugMode = false;
|
||||
this.errorHandler = null;
|
||||
};
|
||||
|
||||
execute (rawInput: string) {
|
||||
|
||||
log.debug(`Executing command: ${rawInput}`)
|
||||
|
||||
var input = rawInput.trim().toLowerCase();
|
||||
var handler = this.findHandler(input);
|
||||
|
||||
if(handler != null) {
|
||||
if(this.debugMode) {
|
||||
this.invokeHandler(input, handler);
|
||||
} else {
|
||||
try {
|
||||
this.invokeHandler(input, handler);
|
||||
} catch (e) {
|
||||
this.handleError(input, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug(`Handled is not found for command: ${rawInput}`)
|
||||
this.handleError(input, new Error("Unsupported expression: " + input.trim()));
|
||||
}
|
||||
};
|
||||
|
||||
onError(h: InputErrorHandler) {
|
||||
this.errorHandler = h;
|
||||
}
|
||||
|
||||
command (cmd : string | object, handler? : any) {
|
||||
var h = this.createHandler(cmd, handler);
|
||||
if(h == null){
|
||||
console.warn('unexpected set of arguments: ', JSON.stringify(arguments));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!is.aFunction(h.canHandle)) {
|
||||
console.warn('handler is missing "canHandle" function. registration denied.');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!is.aFunction(h.handle)) {
|
||||
console.warn('handler is missing "handle" function. registration denied.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlers.push(h);
|
||||
};
|
||||
|
||||
createHandler (cmd : string | object, handler : HandleFunction) : ICommandHandler | null {
|
||||
if(is.plainObject(cmd)) {
|
||||
return cmd as ICommandHandler;
|
||||
}
|
||||
|
||||
if(is.string(cmd)) {
|
||||
return { canHandle: function (input) { return input === cmd; }, handle: handler };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
findHandler (input: string) : ICommandHandler | null {
|
||||
return this.handlers.filter(h => h.canHandle(input))[0];
|
||||
};
|
||||
|
||||
invokeHandler (input : string, handler : ICommandHandler) {
|
||||
|
||||
var cmdResult = handler.handle({ input: input});
|
||||
if(cmdResult != null) {
|
||||
log.debug(cmdResult);
|
||||
}
|
||||
};
|
||||
|
||||
handleError (input: string, err: Error) {
|
||||
if(this.debugMode)
|
||||
console.error(input, err);
|
||||
|
||||
if(this.errorHandler != null)
|
||||
this.errorHandler(input, err);
|
||||
}
|
||||
}
|
||||
|
||||
export default new CmdShell();
|
||||
12
src/shell/components/AboutResultView.tsx
Normal file
12
src/shell/components/AboutResultView.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
function AboutResultView() {
|
||||
|
||||
return <div className="aboutTpl" data-result-type="help">
|
||||
<p> Created by <a href="http://boryslevytskyi.github.io/">Borys Levytskyi</a>. Please give it a like if BitwiseCmd has helped you in your work.</p>
|
||||
<p>If you have an idea, suggestion or you've spotted a bug here, please send it to <a href="mailto:bitwisecmd@gmail.com?subject=Feedback">bitwisecmd@gmail.com</a> or tweet on <a href="http://twitter.com/BitwiseCmd">@BitwiseCmd</a>. Your feedback is greatly appreciated.</p>
|
||||
<p><a href="https://github.com/BorisLevitskiy/BitwiseCmd">Project on <strong>GitHub</strong></a></p>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default AboutResultView;
|
||||
80
src/shell/components/AppRoot.tsx
Normal file
80
src/shell/components/AppRoot.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import InputBox from './InputBox';
|
||||
import DisplayResultView from './DisplayResultView';
|
||||
import AppState, { CommandResultView } from '../AppState';
|
||||
import cmd from '../cmd';
|
||||
import log from 'loglevel';
|
||||
import Indicators from './Indicators';
|
||||
import hash from '../../core/hash';
|
||||
|
||||
type AppRootProps = {
|
||||
appState: AppState,
|
||||
};
|
||||
|
||||
type AppRootState = {
|
||||
uiTheme: string,
|
||||
emphasizeBytes: boolean,
|
||||
commandResults: CommandResultView[]
|
||||
}
|
||||
|
||||
export default class AppRoot extends React.Component<AppRootProps, AppRootState> {
|
||||
|
||||
componentWillMount() {
|
||||
this.refresh();
|
||||
this.props.appState.onChange(() => this.refresh());
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.setState(this.props.appState);
|
||||
}
|
||||
|
||||
getIndicator(value : boolean) {
|
||||
return value ? 'on' : 'off';
|
||||
}
|
||||
|
||||
getResultViews() : JSX.Element[] {
|
||||
|
||||
var results = this.state.commandResults.map((r, i) =>
|
||||
<DisplayResultView key={r.key} input={r.input} inputHash={hash.encodeHash(r.input)} appState={this.props.appState}>
|
||||
{r.view}
|
||||
</DisplayResultView>);
|
||||
return results;
|
||||
}
|
||||
|
||||
toggleEmphasizeBytes() {
|
||||
this.props.appState.toggleEmphasizeBytes();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className={`app-root ${this.state.uiTheme}`}>
|
||||
<Indicators appState={this.props.appState} />
|
||||
<div className="header">
|
||||
<h1>Bitwise<span className="header-cmd">Cmd</span>
|
||||
</h1>
|
||||
<ul className="top-links">
|
||||
<li>
|
||||
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><i className="icon github"> </i><span className="link-text">Project on GitHub</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/BitwiseCmd"><i className="icon twitter"> </i><span className="link-text">Twitter</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:bitwisecmd@gmail.com?subject=Feedback"><i className="icon feedback"> </i><span className="link-text">Send Feedback</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="expressionInput-container">
|
||||
<InputBox onCommandEntered={(input) => cmd.execute(input)} />
|
||||
|
||||
<span className="configPnl">
|
||||
<span id="emphasizeBytes" data-cmd="em" className={"indicator " + this.getIndicator(this.state.emphasizeBytes)} title="Toggle Emphasize Bytes" onClick={() => this.toggleEmphasizeBytes()}>[em]</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="output">
|
||||
{this.getResultViews()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
23
src/shell/components/DisplayResultView.tsx
Normal file
23
src/shell/components/DisplayResultView.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import AppState from '../AppState';
|
||||
|
||||
|
||||
type DisplayResultProps = {
|
||||
appState: AppState,
|
||||
inputHash: string,
|
||||
input: string,
|
||||
key: number
|
||||
}
|
||||
|
||||
export default class DisplayResultView extends React.Component<DisplayResultProps> {
|
||||
render() {
|
||||
|
||||
return <div className="result">
|
||||
<div className="input mono"><span className="cur">></span>{this.props.input}<a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + this.props.inputHash}>#</a></div>
|
||||
<div className="content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
10
src/shell/components/ErrorResultView.tsx
Normal file
10
src/shell/components/ErrorResultView.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
function ErrorResultView(props : {errorMessage:string}) {
|
||||
|
||||
return <div className="result">
|
||||
<div className="error">{props.errorMessage}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default ErrorResultView;
|
||||
4
src/shell/components/HelpResultView.css
Normal file
4
src/shell/components/HelpResultView.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.help .section {margin-bottom:10px;}
|
||||
.help .panel-container {overflow: hidden;}
|
||||
.help .left-panel {float:left; margin-right: 20px;}
|
||||
.help .right-panel {float:left;}
|
||||
73
src/shell/components/HelpResultView.tsx
Normal file
73
src/shell/components/HelpResultView.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import CommandLink from '../../core/components/CommandLink';
|
||||
import './HelpResultView.css';
|
||||
|
||||
function HelpResultView() {
|
||||
|
||||
return <div className="help helpResultTpl">
|
||||
<div className="panel-container">
|
||||
<div className="left-panel">
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Bitiwse Calculation Commands</strong>
|
||||
<ul>
|
||||
<li><code><CommandLink text="23 | 34" /></code> — type bitwise expression to see result in binary (only positive integers are supported now)</li>
|
||||
<li><code><CommandLink text="23 34" /></code> — type one or more numbers to see their binary representations</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">IP Address Commands</strong>
|
||||
<ul>
|
||||
<li><code><CommandLink text="127.0.0.1" /></code> — enter single or multiple ip addresses (separated by space) to see their binary represenation</li>
|
||||
<li><code><CommandLink text="192.168.0.1/8" /></code> — subnet mask notiations are support as well</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Color Theme Commands</strong>
|
||||
<ul>
|
||||
<li><code><CommandLink text="light" /></code> — set Light theme</li>
|
||||
<li><code><CommandLink text="dark" /></code> — set Dark theme</li>
|
||||
<li><code><CommandLink text="midnight" /></code> — set Midnight theme</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Other Commands</strong>
|
||||
<ul>
|
||||
<li><code><CommandLink text="clear" /></code> — clear output pane</li>
|
||||
<li><code><CommandLink text="help" /></code> — display this help</li>
|
||||
<li><code><CommandLink text="whatsnew" /></code> — display changelog</li>
|
||||
<li><code><CommandLink text="em" /></code> — turn On/Off Emphasize Bytes</li>
|
||||
<li><code><CommandLink text="about" /></code> — about the app</li>
|
||||
<li><code><CommandLink text="guid" /></code> — generate <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29">v4</a> GUID</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-panel">
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Supported Bitwise Operations</strong><br/>
|
||||
<small>
|
||||
<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators">
|
||||
as implemented in JavaScript engine of your browser
|
||||
</a>
|
||||
</small>
|
||||
<ul>
|
||||
<li><code>&</code> — bitwise AND</li>
|
||||
<li><code>|</code> — bitwise inclusive OR</li>
|
||||
<li><code>^</code> — bitwise exclusive XOR</li>
|
||||
<li><code>~</code> — bitwise NOT</li>
|
||||
<li><code><<</code> — left shift</li>
|
||||
<li><code>>></code> — sign propagating right shift</li>
|
||||
<li><code>>>></code> — zero-fill right shift</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Tip</strong>
|
||||
<p>
|
||||
You can click on bits to flip them in number inputs (e.g. <CommandLink text="2 4" />) or IP addresses (e.g. <CommandLink text="192.168.0.0/8"/>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default HelpResultView;
|
||||
33
src/shell/components/Indicators.tsx
Normal file
33
src/shell/components/Indicators.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import AppState from "../AppState";
|
||||
import React from "react";
|
||||
|
||||
type IndicatorsProps = {
|
||||
appState: AppState
|
||||
};
|
||||
|
||||
function Indicators(props: IndicatorsProps) {
|
||||
|
||||
const list = [];
|
||||
const state = props.appState;
|
||||
|
||||
if(props.appState.env != 'prod') {
|
||||
list.push(state.env);
|
||||
}
|
||||
|
||||
if(props.appState.debugMode) {
|
||||
list.push("debug");
|
||||
}
|
||||
|
||||
if(localStorage.getItem('TrackAnalytics') === 'false') {
|
||||
list.push("notrack");
|
||||
}
|
||||
|
||||
if(list.length == 0)
|
||||
return null;
|
||||
|
||||
return <div>
|
||||
{list.map(i => <span>{i} </span>)}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Indicators;
|
||||
71
src/shell/components/InputBox.tsx
Normal file
71
src/shell/components/InputBox.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import log from 'loglevel';
|
||||
|
||||
export interface IInputBoxProps
|
||||
{
|
||||
onCommandEntered: (input :string) => void;
|
||||
}
|
||||
|
||||
export default class InputBox extends React.Component<IInputBoxProps> {
|
||||
history: string[];
|
||||
historyIndex: number;
|
||||
nameInput: HTMLInputElement | null;
|
||||
|
||||
constructor(props: IInputBoxProps) {
|
||||
super(props);
|
||||
this.nameInput = null;
|
||||
this.history = [];
|
||||
this.historyIndex = -1;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if(this.nameInput != null)
|
||||
this.nameInput.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input id="in" type="text"
|
||||
ref={(input) => { this.nameInput = input; }}
|
||||
onKeyUp={e => this.onKeyUp(e)}
|
||||
onKeyDown={e => this.onKeyDown(e)}
|
||||
className="expressionInput mono"
|
||||
placeholder="type expression like '1>>2' or 'help' "/>;
|
||||
}
|
||||
|
||||
onKeyUp(e: any) {
|
||||
var input = e.target;
|
||||
if (e.keyCode != 13 || input.value.trim().length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var commandInput = input.value;
|
||||
this.history.unshift(commandInput);
|
||||
this.historyIndex = -1;
|
||||
|
||||
input.value = '';
|
||||
this.props.onCommandEntered(commandInput);
|
||||
}
|
||||
|
||||
onKeyDown(args: any) {
|
||||
|
||||
if(args.keyCode == 38) {
|
||||
var newIndex = this.historyIndex+1;
|
||||
|
||||
if (this.history.length > newIndex) { // up
|
||||
args.target.value = this.history[newIndex];
|
||||
this.historyIndex = newIndex;
|
||||
}
|
||||
|
||||
args.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.keyCode == 40) {
|
||||
if(this.historyIndex > 0) { // down
|
||||
args.target.value = this.history[--this.historyIndex];
|
||||
}
|
||||
|
||||
args.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/shell/components/TextResultView.tsx
Normal file
7
src/shell/components/TextResultView.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
function TextResultView(props : { text: string }) {
|
||||
return <p>{props.text}</p>;
|
||||
}
|
||||
|
||||
export default TextResultView;
|
||||
10
src/shell/components/UnknownInputResultView.tsx
Normal file
10
src/shell/components/UnknownInputResultView.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
function UnknownInputResultView(props : {input:string}) {
|
||||
|
||||
return <div className="result">
|
||||
<div className="error">¯\_(ツ)_/¯ Sorry, i don′t know what <strong>{props.input}</strong> is</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default UnknownInputResultView;
|
||||
2
src/shell/components/WhatsNewResultView.css
Normal file
2
src/shell/components/WhatsNewResultView.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.changelog .item { margin-top: 2em; }
|
||||
.changelog .item-new .date { font-weight: bold; text-decoration: underline;}
|
||||
38
src/shell/components/WhatsNewResultView.tsx
Normal file
38
src/shell/components/WhatsNewResultView.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import CommandLink from '../../core/components/CommandLink';
|
||||
import './WhatsNewResultView.css';
|
||||
|
||||
function WhatsnewResultView() {
|
||||
|
||||
return <div className="changelog">
|
||||
<h3>Changelog</h3>
|
||||
<div className="item item-new">
|
||||
<p><span className="soft date">Jun 14th, 2021</span> <br/>
|
||||
Added support of ip addresses and subnet masks notatioans. Try them out:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Single IP address <CommandLink text="127.0.0.1" /></li>
|
||||
<li>Multiple IP addresses and subnet mask notations <CommandLink text="127.0.0.1 192.168.0.0/24" /></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">Jun 6th, 2017</span> <br/>
|
||||
Added <code><CommandLink text="guid" /></code> command. Use it for generating v4 GUIDs </p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">May 27th, 2017</span> <br/>
|
||||
Added support of binary number notation (e.g. <code><CommandLink text="0b10101" /></code>). </p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft">May 20th, 2017</span> <br/>
|
||||
New <CommandLink text="Midnight" /> theme added. </p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft">May 16th, 2017</span> <br/>
|
||||
Complete rewrite using React. Old implementation is available at <a href="http://bitwisecmd.com/old">http://bitwisecmd.com/old</a>. Please let me know if you have problems with this release by <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues">creating issue</a> in Github Repo.</p>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default WhatsnewResultView;
|
||||
8
src/shell/interfaces.ts
Normal file
8
src/shell/interfaces.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import AppState from "./AppState";
|
||||
import { CmdShell } from "./cmd";
|
||||
|
||||
export type Env = 'prod' | 'stage';
|
||||
|
||||
export type AppModule = {
|
||||
setup: (appState: AppState, cmd: CmdShell) => void;
|
||||
};
|
||||
36
src/shell/module.tsx
Normal file
36
src/shell/module.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import AppState from './AppState';
|
||||
import { CmdShell, CommandInput } from './cmd';
|
||||
import AboutResultView from './components/AboutResultView';
|
||||
import ErrorResultView from './components/ErrorResultView';
|
||||
import HelpResultView from './components/HelpResultView';
|
||||
import TextResultView from './components/TextResultView';
|
||||
import WhatsnewResultView from './components/WhatsNewResultView';
|
||||
|
||||
const shellModule = {
|
||||
setup: function(appState: AppState, cmd: CmdShell) {
|
||||
|
||||
cmd.debugMode = appState.debugMode;
|
||||
appState.onChange(() => cmd.debugMode = appState.debugMode);
|
||||
|
||||
cmd.command("help", (c: CommandInput) => appState.addCommandResult(c.input, <HelpResultView />));
|
||||
cmd.command("clear", () => appState.clearCommandResults());
|
||||
cmd.command("em", () => appState.toggleEmphasizeBytes());
|
||||
cmd.command("dark", () => appState.setUiTheme('dark'));
|
||||
cmd.command("light", () => appState.setUiTheme('light'));
|
||||
cmd.command("midnight", () => appState.setUiTheme('midnight'));
|
||||
cmd.command("about", (c: CommandInput) => appState.addCommandResult(c.input, <AboutResultView />));
|
||||
cmd.command("whatsnew", (c: CommandInput) => appState.addCommandResult(c.input, <WhatsnewResultView />));
|
||||
cmd.command("guid", (c: CommandInput) => appState.addCommandResult(c.input, <TextResultView text={uuid()} />));
|
||||
cmd.command("-notrack", () => {});
|
||||
cmd.command("-debug", (c: CommandInput) => {
|
||||
appState.toggleDebugMode();
|
||||
appState.addCommandResult(c.input, <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>);
|
||||
});
|
||||
|
||||
cmd.onError((input: string, err: Error) => appState.addCommandResult(input, <ErrorResultView errorMessage={err.toString()} />));
|
||||
}
|
||||
}
|
||||
|
||||
export default shellModule;
|
||||
62
src/shell/startup.ts
Normal file
62
src/shell/startup.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import log from 'loglevel';
|
||||
import hash from '../core/hash';
|
||||
import AppState from './AppState';
|
||||
import { Env } from './interfaces';
|
||||
import appStateStore from './appStateStore';
|
||||
|
||||
export type StartupAppData = {
|
||||
appState: AppState,
|
||||
startupCommands: string[]
|
||||
}
|
||||
|
||||
function bootstrapAppData() : StartupAppData {
|
||||
const env = window.location.host === "bitwisecmd.com" ? 'prod' : 'stage';
|
||||
|
||||
setupLogger(env);
|
||||
|
||||
const appState = createAppState(env);
|
||||
const startupCommands = getStartupCommands(appState);
|
||||
|
||||
return {
|
||||
appState,
|
||||
startupCommands
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createAppState(env:string) {
|
||||
var stateData = appStateStore.getPersistedData();
|
||||
const appState = new AppState(stateData, env);
|
||||
appStateStore.watch(appState);
|
||||
log.debug("appState initialized", appState);
|
||||
return appState;
|
||||
}
|
||||
|
||||
function getStartupCommands(appState : AppState) : string[] {
|
||||
var hashArgs = hash.getArgs(window.location.hash);
|
||||
|
||||
var startupCommands = ['help', '127.0.0.1 192.168.0.0/8', '1|2&6','4 0b1000000 0x80'];
|
||||
|
||||
if(appState.wasOldVersion) {
|
||||
startupCommands = ["whatsnew"];
|
||||
}
|
||||
|
||||
if(hashArgs.length > 0) {
|
||||
startupCommands = hashArgs;
|
||||
}
|
||||
|
||||
log.debug('Executing startup commands', startupCommands);
|
||||
|
||||
return startupCommands;
|
||||
}
|
||||
|
||||
function setupLogger(env: Env) {
|
||||
if(env != 'prod'){
|
||||
log.setLevel("debug");
|
||||
log.debug(`Log level is set to debug. Env: ${env}`)
|
||||
} else {
|
||||
log.setLevel("warn");
|
||||
}
|
||||
}
|
||||
|
||||
export default bootstrapAppData;
|
||||
Reference in New Issue
Block a user