Merge pull request #5 from BorysLevytskyi/Move_to_react

Move to react
This commit is contained in:
Borys Levytskyi
2017-05-16 23:45:26 +03:00
committed by GitHub
83 changed files with 1751 additions and 416 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,9 @@
.idea/
node_modules
build
BitwiseCmdPages/
.DS_Store
bundle.js
bundle.js.map
npm-debug.log
react/

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.check.workspaceVersion": false
}

View File

@@ -1,85 +0,0 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
files: {
'build/js/bitwisecmd.js': [
'src/js/core/core.js',
'src/js/core/is.js',
'src/js/core/should.js',
'src/js/core/di.js',
'src/js/core/appShell.js',
'src/js/core/htmlBuilder.js',
'src/js/core/observable.js',
'src/js/app.js',
// TODO: Make components to put their extensions to AppShell instead of app
'src/js/components/*.*',
'src/js/app/**/*.*'
]
}
}
},
cssmin: {
options: {
shorthandCompacting: false,
roundingPrecision: -1
},
target: {
files: {
'build/css/styles.css': ['src/css/styles.css']
}
}
},
copy: {
main: {
files: [{
src: 'src/*.*',
dest: 'build/',
flatten: true,
expand: true
}, {
src: 'src/img/*.*',
dest: 'build/img/',
flatten: true,
expand: true
}, {
src: 'src/js/analytics.js',
dest:'build/js/',
flatten: true,
expand: true
}]
}
},
processhtml: {
build: {
files: {
'build/index.html' : ['build/index.html']
}
}
},
clean: ['build/**']
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-processhtml');
grunt.loadNpmTasks('grunt-contrib-clean');
// Default task(s).
grunt.registerTask('default', ['uglify','cssmin','copy', 'processhtml']);
};

View File

@@ -1,6 +1,4 @@
# BitwiseCmd
[Bitwise Calculator Online](http://bitwisecmd.com/)
Web App that rolls on wheels that I most certainly had to reinvent myself. Nuff said.
It helps better understand how bitwise operations are pefromed by displaying bytes in a way you can actually see what is going on there during AND, OR, XOR or shift operations.
Web App that helps better understand how bitwise operations are pefromed by displaying bytes in a way you can actually see what is going on there during AND, OR, XOR or shift operations.

17
e2e.bat
View File

@@ -1,17 +0,0 @@
@echo off
SET appUrl="http://localhost:63342/BitwiseCmd/src/#clear"
SET confFile=e2e.chrome.js
FOR %%p IN (%*) DO (
IF [%%p] == [all] SET confFile=e2e.all.js
IF [%%p] == [build] SET appUrl="http://localhost:63342/BitwiseCmd/build/#clear"
IF [%%p] == [deploy] SET appUrl="http://bitwisecmd.com/#clear"
)
@echo on
echo "appUrl: %appUrl%"
echo "confFile: %confFile%"
protractor tests\%confFile% --params.appUrl=%appUrl%

View File

@@ -1,20 +1,40 @@
module.exports = function(config) {
config.set({
frameworks: ['jasmine'],
files: [
'src/js/core/core.js',
'src/js/core/is.js',
'src/js/core/di.js',
'src/js/core/should.js',
'src/js/core/htmlBuilder.js',
'src/js/core/should.js',
'src/js/core/appShell.js',
'src/js/core/observable.js',
'src/js/app.js',
'src/js/components/*.js',
'src/js/app/**/*.js',
'tests/unit/**/*.js'
]
});
};
module.exports = function (config) {
config.set({
browsers: [ 'Chrome' ],
// karma only needs to know about the test bundle
files: [
'./tests/unit/**/*.js'
],
frameworks: [ 'jasmine' ],
plugins: [
'karma-chrome-launcher',
'karma-jasmine',
'karma-webpack',
],
// run the bundle through the webpack and sourcemap plugins
preprocessors: {
'./tests/unit/**/*.js': [ 'webpack']
},
reporters: [ 'dots' ],
singleRun: true,
// webpack config object
webpack: {
module: {
loaders: [
{
exclude: /node_modules/,
loader: 'babel-loader',
test: /\.jsx?$/,
query: {
presets: [require.resolve('babel-preset-es2015'), require.resolve('babel-preset-react')],
plugins: [require.resolve('babel-plugin-transform-class-properties')]
},
}
],
}
},
webpackMiddleware: {
noInfo: true,
}
});
};

View File

@@ -4,7 +4,16 @@
"description": "Bitwise Operations Console",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "webpack -p && rm -rf ./BitwiseCmdPages/* && cp ./src/index.html ./BitwiseCmdPages && cp ./src/*.js ./BitwiseCmdPages && cp ./src/bundle.js.map ./BitwiseCmdPages && cp -r ./src/css ./BitwiseCmdPages && cp -r ./src/img ./BitwiseCmdPages && cp -r ./src/css ./BitwiseCmdPages",
"stage": "npm run build && cp -r ./BitwiseCmdPages/* ../BitwiseCmdPages",
"stage:react": "npm run build && cp -r ./BitwiseCmdPages/* ../BitwiseCmdPages/react",
"serv": "webpack-dev-server --content-base ./src",
"e2e": "protractor ./tests/e2e.chrome.js --params.appUrl='http://localhost:8080/#clear'",
"e2e:build": "protractor ./tests/e2e.chrome.js --params.appUrl='http://localhost:3000/#clear'",
"e2e:remote:react": "protractor ./tests/e2e.chrome.js --params.appUrl='http://bitwisecmd.com/react/#clear||-notrack'",
"e2e:remote:old": "protractor ./tests/e2e.chrome.js --params.appUrl='http://bitwisecmd.com/old/#clear||-notrack'",
"e2e:remote": "protractor ./tests/e2e.chrome.js --params.appUrl='http://bitwisecmd.com//#clear||-notrack'",
"test": "karma start"
},
"repository": {
"type": "git",
@@ -17,6 +26,12 @@
},
"homepage": "https://github.com/BorysLevytskyi/BitwiseCmd",
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"grunt-contrib-clean": "latest",
"grunt-contrib-copy": "latest",
"grunt-contrib-cssmin": "latest",
@@ -24,6 +39,22 @@
"grunt-processhtml": "latest",
"jasmine": "latest",
"karma": "latest",
"karma-jasmine": "latest"
"karma-jasmine": "latest",
"karma-webpack": "latest",
"source-map-loader": "^0.1.5",
"ts-loader": "^1.0.0"
},
"dependencies": {
"@types/react": "^0.14.44",
"@types/react-dom": "^0.14.18",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"body-parser": "^1.15.2",
"loglevel": "^1.4.1",
"react": "^15.4.0",
"react-dom": "^15.4.0"
}
}

103
src.old/css/styles.css Normal file
View File

@@ -0,0 +1,103 @@
body { font-family: Verdana; font-size: 0.8em; margin: 0; padding: 20px 100px 0 100px; }
code { font-size: 1.2em; font-weight: bold; }
.top-links { position: absolute; right: 10px; top: 10px; list-style-type: none; margin: 0 }
.top-links li { float: left; }
.top-links a { display: inline-block; padding: 10px 15px}
.top-links .icon { margin-right: 5px; }
.mono { font-family: monospace; font-size: 1.3em }
.expressionInput { width: 500px; padding: 3px; border: solid 1px lightgray; }
.result { margin: 10px 10px 30px; }
.result .input { margin-bottom: 10px; }
.result .content { padding-left: 10px}
.result .cur { margin-right: 5px; }
.hashLink { text-decoration: none; margin-left: 5px; visibility: hidden }
.hashLink:hover { text-decoration: underline; margin-left: 5px; }
.input:hover .hashLink { visibility: visible }
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
.expression .bin { letter-spacing: 3px; }
.expression .flipable { cursor: pointer; opacity: 1 }
.expression .flipable:hover { opacity: 0.8 }
.expression .byte { margin: 0 3px; }
.expression .flipable { cursor: pointer; opacity: 1 }
.expression .flipable:hover { opacity: 0.8 }
.expression-result td { border-top: dotted 1px gray; }
.expression { font-size: 1.5em; font-family: monospace }
.expression .prefix { font-weight: normal; display: none; font-size: 0.9em }
.expression .other { font-size: 0.9em}
.expression .sign { text-align: right}
.hex .prefix { display: inline; }
.help { padding: 10px; }
.help ul { list-style-type: none; margin: 0; padding: 0; }
.help p { margin-top: 0 }
.indicator { padding: 2px 5px; font-family: monospace; font-size: 1.3em; background: transparent; border: none; cursor: pointer }
.error { color: maroon; }
#view { padding: 10px}
.cur { color: lightgray; }
.icon { width: 16px; height: 16px; display: inline-block; }
.light .twitter { background: url('../img/twitter-light.png') }
.dark .twitter { background: url('../img/twitter-dark.png') }
.light .feedback { background: url('../img/feedback-light.png') }
.dark .feedback { background: url('../img/feedback-dark.png') }
.dark .github { background: url('../img/github-dark.png') }
.light .github { background: url('../img/github-light.png') }
/* Light */
.light { background: #fafafa; }
.light a, .light a:visited { color: #222; }
.light .one { color: black; }
.light .zero { color: #888; }
.light .indicator { color: #ddd; }
.light .on { color: #121212; }
.light .prefix { color: #888}
.light .other { color: #bbb }
.light .hashLink, .light .hashLink:visited { color: #ddd }
.light .hashLink:hover { color: #888 }
.light ul.top-links li:hover { background: #ddd }
/* Dark */
.dark { background: #121212; color: white;}
.dark .expressionInput { background: #121212; color: white; }
.dark a, .dark a:visited { color: white; }
.dark .indicator { color: #555; }
.dark .on { color: white; }
.dark .zero { color: #999;}
.dark .prefix { color: #999}
.dark .other { color: #444;}
.dark .hashLink, .dark .hashLink:visited { color: #333 }
.dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 }
/* Top Links Shrink */
@media (max-width: 800px) {
.top-links .link-text { display: none }
}
/* Remove margin space on body. Inline top links with header */
@media (max-width: 700px) {
body { padding: 10px; }
.expressionInput { width: 500px; }
}
/* Further shrink */
@media (max-width: 500px) {
.expressionInput { width: 350px; }
.top-links a { display: inline-block; padding: 5px 10px}
}
@media (max-width: 350px) {
.expressionInput { width: 200px; }
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src.old/img/github-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

182
src.old/index.html Normal file
View File

@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="description" content="Bitwise Calculator. Visualised Bitwise Operations.">
<title>BitwiseCmd</title>
<link rel="shortcut icon" href="http://bitwisecmd.com/favicon.ico">
<!-- build:js js/bitwisecmd.js -->
<script type="text/javascript" src="js/core/core.js"></script>
<script type="text/javascript" src="js/core/is.js"></script>
<script type="text/javascript" src="js/core/should.js"></script>
<script type="text/javascript" src="js/core/di.js"></script>
<script type="text/javascript" src="js/core/appShell.js"></script>
<script type="text/javascript" src="js/core/htmlBuilder.js"></script>
<script type="text/javascript" src="js/core/observable.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/components/controllersFeature.js"></script>
<script type="text/javascript" src="js/components/viewsFeature.js"></script>
<script type="text/javascript" src="js/components/templatesFeature.js"></script>
<script type="text/javascript" src="js/app/bitwise/calc.js"></script>
<script type="text/javascript" src="js/app/bitwise/expression.js"></script>
<script type="text/javascript" src="js/app/bitwise/formatter.js"></script>
<script type="text/javascript" src="js/app/models.js"></script>
<script type="text/javascript" src="js/app/modelViews.js"></script>
<script type="text/javascript" src="js/app/cmd/cmd.js"></script>
<script type="text/javascript" src="js/app/services.js"></script>
<script type="text/javascript" src="js/app/controllers.js"></script>
<script type="text/javascript" src="js/app/cmd/commands.js"></script>
<script type="text/javascript" src="js/app/run.js"></script>
<!-- /build -->
<link rel="stylesheet" type="text/css" href="css/styles.css" />
</head>
<body id="rootView" class="dark">
<header>
<div class="header">
<h1>Bitwise<span style="color: #c5c5c5;">Cmd</span></h1>
<ul class="top-links">
<li>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><i class="icon github">&nbsp;</i><span class="link-text">Project on GitHub</span></a>
</li>
<li>
<a href="https://twitter.com/BitwiseCmd"><i class="icon twitter">&nbsp;</i><span class="link-text">Twitter</span></a>
</li>
<li>
<a href="mailto:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback"><i class="icon feedback">&nbsp;</i><span class="link-text">Send Feedback</span></a>
</li>
</ul>
</div>
<div class="expressionInput-container">
<input id="in" type="text" class="expressionInput mono" data-controller="expressionInputCtrl" placeholder="type expression like '1>>2' or 'help' "/>
<span data-controller="configPanelCtrl" class="configPnl">
<span id="emphasizeBytes" data-cmd="em" class="indicator on" title="Emphasize Bytes">[em]</span>
</span>
</div>
</header>
<div id="output" data-controller="cmdController">
</div>
<script data-template="helpResultTpl" type="text/template">
<div class="help helpResultTpl">
<div style="overflow: hidden">
<div style="float: left; margin-right: 20px">
<p class="section">
<strong>Supported Commands</strong>
<ul>
<li><code>23 ^ 34</code> type bitwise expression to see result in binary (only positive integers are supported now)</li>
<li><code>23 34</code> type one or more numbers to see their binary representations</li>
<li><code>clear</code> clear output pane</li>
<li><code>help</code> display this help</li>
<li><code>em</code> turn On/Off Emphasize Bytes</li>
<li><code>dark</code> set Dark theme</li>
<li><code>light</code> set Light theme</li>
<li><code>about</code> about the app</li>
</ul>
</p>
</div>
<div class="float:left">
<p class="section">
<strong>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
</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>&lt;<</code> left shift</li>
<li><code>&gt;&gt;</code> sign propagating right shift</li>
<li><code>&gt;&gt;&gt;</code> zero-fill right shift</li>
</ul>
</p>
</div>
</div>
<p style="font-style: italic">
<strong>Tip:</strong> Use Up and Down keys to navigate trough executed commands.
</p>
</div>
</script>
<script data-template="aboutTpl" type="text/template">
<div class="aboutTpl">
<p> Created by <a href="http://boryslevytskyi.github.io/">Borys Levytskyi</a></p>
<p>If you have an idea, suggestion or you've spotted a bug here, please send it to <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">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</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>
</script>
<script data-template="resultView" data-compiled="" type="text/template">
<div class="result">
<div class="input mono"><span class="cur">&gt;</span>{m.input}<a class="hashLink" title="Link for this expression" href="{window.location.pathname}#{m.inputHash}">#</a></div>
<div class="content"></div>
</div>
</script>
<script data-template="bitwiseExpressionView" data-compiled="" type="text/template">
<table class="expression" cellspacing="0">
{each itm in m.items}
<tr class="{itm.css}">
<td class="sign">{itm.sign}</td>
<td class="label">{itm.label}</td>
<td class="bin">{itm.bin.padLeft(m.maxNumberOfBits, '0')}</td>
<td class="other">{itm.other}</td>
</tr>
{/}
</table>
</script>
<script data-template="numbersList" data-compiled="" type="text/template">
<table class="expression" cellspacing="0">
{each op in m.operands}
<tr data-kind="{op.kind}">
<td class="label">{op.input}</td>
<td class="bin">{op.bin.padLeft(m.bitsSize, '0')}</td>
<td class="other">{op.other}</td>
</tr>
{/}
</table>
</script>
<script type="text/javascript">
var app = window.app;
app.bootstrap(document.getElementById('rootView'));
var cmd = app.get('cmd');
var hashArgs = app.get('hashArgs');
if(hashArgs.commands.length > 0) {
hashArgs.commands.forEach(cmd.execute.bind(cmd));
}
else {
cmd.execute('help');
cmd.execute('1|2&6');
cmd.execute('1<<0x2a');
cmd.execute('2 4 8 16 32');
}
</script>
<!-- build:js js/analytics.js -->
<!-- /build -->
</body>
</html>

View File

@@ -6,12 +6,13 @@ app.set('cmd', function() {
var cmdController = app.controller('cmdController');
return {
debugMode: true,
execute: function(rawInput) {
var input = rawInput.trim().toLowerCase();
var handler = findHandler(input);
if(handler != null) {
if(app.debugMode) {
if(this.debugMode) {
invokeHandler(input, handler);
} else {
try {
@@ -52,13 +53,12 @@ app.set('cmd', function() {
handlers.push(h);
},
clear: function() {
cmdController.clear();
console.error('[displayCommandError] not implemented');
}
};
function displayCommandError(input, message) {
var error = new app.models.ErrorResult(message);
cmdController.display(new app.models.DisplayResult(input, error));
console.error('[displayCommandError] not implemented');
}
function invokeHandler (input, handler) {

View File

@@ -6,42 +6,6 @@ app.run(function() {
var rootView = app.get('rootView');
var expression = app.get('expression');
cmd.commands({
'help': function() {
var helpResult = document.querySelector('.result .helpResultTpl');
if(helpResult != null) {
moveResultUp(helpResult);
return;
}
return new app.models.ViewResult('helpResultTpl');
},
'clear': function() {
cmd.clear();
},
'em': function() {
cmdConfig.emphasizeBytes = !cmdConfig.emphasizeBytes;
},
'dark': function() {
cmdConfig.theme = 'dark';
},
light: function () {
cmdConfig.theme = 'light';
},
about: function() {
var aboutResult = document.querySelector('.result .aboutTpl');
if(aboutResult != null) {
moveResultUp(aboutResult);
return;
}
return new app.models.ViewResult('aboutTpl');
},
'-debug': function() {
app.debugMode = true;
console.log('debug is on');
},
'-notrack': function () {}
});
// TODO: Make as function
cmd.command({
canHandle: function(input) { return app.get('expression').canParse(input); },

15
src/analytics.js Normal file
View File

@@ -0,0 +1,15 @@
(function() {
if(window.location.host != 'bitwisecmd.com' || window.location.hash.indexOf('-notrack') > -1) {
console.log("Analytics not tracked")
return;
}
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-61569164-1', 'auto');
ga('send', 'pageview');
})();

51
src/app/appState.js Normal file
View File

@@ -0,0 +1,51 @@
export default class AppState {
constructor(persistData) {
this.emphasizeBytes = persistData.emphasizeBytes || true;
this.commandResults = [];
this.handlers = [];
this.uiTheme = persistData.uiTheme || 'dark';
this.debugMode = false;
this.version = 1;
this.persistedVersion = persistData.version || 0.9;
this.wasOldVersion = this.version > this.persistedVersion;
}
addCommandResult(result) {
this.commandResults.unshift(result);
this.triggerChanged();
}
clearCommmandResults() {
this.commandResults = [];
this.triggerChanged();
}
toggleEmphasizeBytes() {
this.emphasizeBytes = !this.emphasizeBytes;
this.triggerChanged();
}
onChange(handler) {
this.handlers.push(handler);
}
triggerChanged() {
for(var h of this.handlers) {
h();
}
}
setUiTheme(theme) {
this.uiTheme = theme;
this.triggerChanged();
}
getPersistData() {
return {
emphasizeBytes: this.emphasizeBytes,
uiTheme: this.uiTheme,
version: this.version
}
}
};

26
src/app/appStateStore.js Normal file
View File

@@ -0,0 +1,26 @@
const storeKey = 'AppState';
export default {
getPersistedData() {
var json = window.localStorage.getItem(storeKey);
if(!json) {
return {};
}
try {
return JSON.parse(json);
}
catch(ex) {
console.error('Failed to parse AppState json. Json Value: \n' + json, ex);
return {};
}
},
watch (appState) {
appState.onChange(() => this.persistData(appState));
},
persistData(appState) {
localStorage.setItem(storeKey, JSON.stringify(appState.getPersistData()));
}
}

26
src/app/calc.js Normal file
View File

@@ -0,0 +1,26 @@
import should from './should';
export default calc = {
numberOfBits: function (num) {
if(num < 0) {
return 32;
}
should.bePositiveInteger(num);
return Math.floor(Math.log(num) / Math.log(2)) + 1;
},
maxNumberOfBits: function (arr) {
var counts = [], num;
for (var i = 0; i < arr.length; i++) {
num = arr[i];
counts.push(this.numberOfBits(num));
}
return Math.max.apply(null, counts);
},
calcExpression: function (expr) {
return eval(expr.expressionString);
}
};

96
src/app/cmd.js Normal file
View File

@@ -0,0 +1,96 @@
import is from './is';
var handlers = [];
var config = {
errorHandler: (input, err) => logError(err)
}
var cmd = {
debugMode: false,
execute: function(rawInput) {
var input = rawInput.trim().toLowerCase();
var handler = findHandler(input);
if(handler != null) {
if(this.debugMode) {
invokeHandler(input, handler);
} else {
try {
invokeHandler(input, handler);
} catch (e) {
config.errorHandler(input, e);
}
}
}
else {
logError(input, new Error("Unsupported expression: " + input.trim()));
}
},
commands: function(catalog) {
for(var key in catalog) {
if(catalog.hasOwnProperty(key)) {
this.command(key, catalog[key]);
}
}
},
command: function(cmd, handler) {
var h = createHandler(cmd, handler);
if(h == null){
console.warn('unexpected set of arguments: ', Array.prototype.splice.call(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;
}
handlers.push(h);
},
clear: function() {
console.log('clear');
},
onError: function(handler) {
config.errorHandler = handler;
}
};
function logError(err) {
console.error(err)
}
function invokeHandler (input, handler) {
var cmdResult = handler.handle({ input: input});
if(cmdResult != null) {
console.log(cmdResult);
}
}
function createHandler (cmd, handler) {
if(is.plainObject(cmd)) {
return cmd;
}
if(is.string(cmd)) {
return { canHandle: function (input) { return input === cmd; }, handle: handler };
}
return null;
}
function findHandler (input) {
var i= 0;
for(i;i<handlers.length; i++) {
if(handlers[i].canHandle(input)) {
return handlers[i];
}
}
};
export default cmd;

55
src/app/commands.js Normal file
View File

@@ -0,0 +1,55 @@
import HelpResult from './models/HelpResult';
import AboutResult from './models/AboutResult';
import UnknownCommandResult from './models/UnknownCommandResult';
import ExpressionResult from './models/ExpressionResult';
import ErrorResult from './models/ErrorResult';
import WahtsnewResult from './models/WhatsnewResult';
import * as expression from './expression';
var cmdConfig = {};
export default {
initialize (cmd, appState) {
cmd.command({
canHandle: (input) => expression.parser.canParse(input),
handle: function(c) {
var expr = expression.parser.parse(c.input);
appState.addCommandResult(new ExpressionResult(c.input, expr));
}
})
cmd.commands({
'help': function(c) {
appState.addCommandResult(new HelpResult(c.input));
},
'clear': function() {
appState.clearCommmandResults();
},
'em': function() {
appState.toggleEmphasizeBytes();
},
'dark': function() {
appState.setUiTheme('dark');
},
'light': function () {
appState.setUiTheme('light');
},
'about': function(c) {
appState.addCommandResult(new AboutResult(c.input));
},
'whatsnew': function(c) {
appState.addCommandResult(new WahtsnewResult(c.input));
},
'-notrack': function () {}
});
// Last command handler reports that input is unknown
cmd.command({
canHandle: () => true,
handle: (c) => appState.addCommandResult(new UnknownCommandResult(c.input))
});
cmd.onError((input, err) => appState.addCommandResult(new ErrorResult(input, err)));
}
}

View File

@@ -0,0 +1,58 @@
import React from 'react';
import InputBox from './InputBox';
import DisplayResultView from './DisplayResultView';
export default class AppRoot extends React.Component {
componentWillMount() {
this.refresh();
this.props.appState.onChange(() => this.refresh());
}
refresh() {
this.setState(this.props.appState);
}
getIndicator(value) {
return value === true ? 'on' : 'off';
}
getResultViews() {
var results = this.state.commandResults.map((r, i) => <DisplayResultView key={i} content={r} input={r.input} inputHash={r.inputHash} appState={this.props.appState} />);
return results;
}
toggleEmphasizeBytes() {
console.log(this.props.appState);
this.props.appState.toggleEmphasizeBytes();
}
render() {
return <div className={`app-root ${this.state.uiTheme}`}>
<div className="header">
<h1>Bitwise<span style={{color: "#c5c5c5"}}>Cmd</span></h1>
<ul className="top-links">
<li>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><i className="icon github">&nbsp;</i><span className="link-text">Project on GitHub</span></a>
</li>
<li>
<a href="https://twitter.com/BitwiseCmd"><i className="icon twitter">&nbsp;</i><span className="link-text">Twitter</span></a>
</li>
<li>
<a href="mailto:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback"><i className="icon feedback">&nbsp;</i><span className="link-text">Send Feedback</span></a>
</li>
</ul>
</div>
<div className="expressionInput-container">
<InputBox />
<span className="configPnl">
<span id="emphasizeBytes" data-cmd="em" className={"indicator " + this.getIndicator(this.state.emphasizeBytes)} title="Toggle Emphasize Bytes" onClick={e => this.toggleEmphasizeBytes()}>[em]</span>
</span>
</div>
<div id="output">
{this.getResultViews()}
</div>
</div>;
}
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import HelpResult from '../models/HelpResult';
import AboutResult from '../models/AboutResult';
import UnknownCommandResult from '../models/UnknownCommandResult';
import HelpResultView from './results/HelpResultView';
import AboutResultView from './results/AboutResultView';
import ExpressionResult from '../models/ExpressionResult';
import ExpressionResultView from './results/ExpressionResultView';
import WhatsnewResult from '../models/WhatsnewResult';
import WhatsnewResultView from './results/WhatsnewResultView';
import ErrorResult from '../models/ErrorResult';
export default class DisplayResult extends React.Component {
render() {
if(this.props.content instanceof UnknownCommandResult) {
return this.renderUnknown();
}
if(this.props.content instanceof ErrorResult) {
return this.renderError(this.props.content.error.message);
}
return <div className="result">
<div className="input mono"><span className="cur">&gt;</span>{this.props.content.input}<a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + this.props.inputHash}>#</a></div>
<div className="content">
{this.findResultComponent(this.props.content)}
</div>
</div>;
}
renderUnknown() {
return <div className="result">
<div className="error">¯\_()_/¯ Sorry, i don&prime;t know what <strong>{this.props.input}</strong> is</div>
</div>
}
renderError(message) {
return <div className="result">
<div className="error">(X_X) Error occurred: <strong>{message}</strong></div>
</div>
}
findResultComponent(result) {
if(result instanceof HelpResult) {
return <HelpResultView content={result} />
}
if(result instanceof AboutResult) {
return <AboutResultView />
}
if(result instanceof ExpressionResult) {
return <ExpressionResultView result={result} emphasizeBytes={this.props.appState.emphasizeBytes} />
}
if(result instanceof WhatsnewResult) {
return <WhatsnewResultView />
}
console.warn('Unknown result:', result);
return <span>Unknown result: {typeof result}</span>
}
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import cmd from '../cmd';
export default class InputBox extends React.Component {
constructor() {
super();
this.history = [];
this.historyIndex = -1;
}
componentDidMount(){
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) {
var input = e.target;
if (e.keyCode != 13 || input.value.trim().length == 0) {
return;
}
var value = input.value;
this.history.unshift(value);
this.historyIndex = -1;
input.value = '';
cmd.execute(value);
console.log(this.history);
}
onKeyDown(args) {
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();
}
}
}

View File

@@ -0,0 +1,11 @@
import React from 'react'
export default class AboutResultView extends React.Component {
render() {
return <div className="aboutTpl">
<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:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</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>;
}
}

View File

@@ -0,0 +1,46 @@
import React from 'react';
export default class BinaryStringView extends React.Component {
render() {
return <span>{this.getChildren()}</span>
}
onBitClick(index, e) {
if(!this.props.allowFlipBits) {
return;
}
if(this.props.onFlipBit) {
this.props.onFlipBit(index);
}
}
getChildren() {
var bits = this.createBits(this.props.binaryString.split(''));
if(this.props.emphasizeBytes) {
return this.splitIntoBytes(bits);
}
return bits;
}
createBits(bitChars) {
const allowFlipBits = this.props.allowFlipBits || false;
const css = allowFlipBits ? ' flipable' : ''
const classNames = { '0': `zero${css}`, '1' : `one ${css}` };
return bitChars.map((c, i) => <span className={classNames[c]} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>);
}
splitIntoBytes(bits) {
const bytes = [];
var key = 0;
while(bits.length > 0) {
bytes.push(<span key={key++} className="byte">{bits.splice(0, 8)}</span>);
}
return bytes;
}
}

View File

@@ -0,0 +1,54 @@
import React from 'react';
import * as expression from '../../expression';
import formatter from '../../formatter';
import BinaryStringView from './BinaryStringView';
import BitwiseExpressionViewModel from './models/BitwiseExpressionViewModel';
import log from 'loglevel';
export default class BitwiseOperationEpxressionView extends React.Component {
render() {
var rows = this.getRows();
if(!rows) {
return null;
}
return <table className="expression">
<tbody>
{rows}
</tbody>
</table>
}
getRows() {
const expr = this.props.expression;
if(expr instanceof expression.SingleOperandExpression) {
const m = BitwiseExpressionViewModel.buildNot(expr, { emphasizeBytes: this.props.emphasizeBytes });
log.info('Render model', m);
return m.items.map((itm, i) => <ExpressionRow key={i} {...itm} emphasizeBytes={this.props.emphasizeBytes} maxNumberOfBits={m.maxNumberOfBits} />);
}
if(expr instanceof expression.MultipleOperandsExpression) {
const m = BitwiseExpressionViewModel.buildMultiple(expr, { emphasizeBytes: this.props.emphasizeBytes });
log.info('Render model', m);
return m.items.map((itm, i) => <ExpressionRow key={i} {...itm} emphasizeBytes={this.props.emphasizeBytes} maxNumberOfBits={m.maxNumberOfBits} />);
}
return null;
}
}
class ExpressionRow extends React.Component {
render() {
const { sign, label, bin, other, css, maxNumberOfBits, emphasizeBytes } = this.props;
return <tr className={css}>
<td className="sign">{sign}</td>
<td className="label">{label}</td>
<td className="bin">
<BinaryStringView emphasizeBytes={emphasizeBytes} binaryString={formatter.padLeft(bin, maxNumberOfBits, '0')} allowFlipBits={false}/>
</td>
<td className="other">{other}</td>
</tr>;
}
}

View File

@@ -0,0 +1,26 @@
import React from 'react'
import ListOfNumbersExpressionView from './ListOfNumbersExpressionView';
import BitwiseOperationExpressionView from './BitwiseOperationExpressionView';
import * as expression from '../../expression';
export default class ExpressionResultView extends React.Component {
render() {
var expr = this.props.result.expression;
if(expr instanceof expression.ListOfNumbersExpression) {
return <div>
<ListOfNumbersExpressionView expression={expr} emphasizeBytes={this.props.emphasizeBytes} />
</div>
}
if(expr instanceof expression.SingleOperandExpression || expr instanceof expression.MultipleOperandsExpression) {
return <div>
<BitwiseOperationExpressionView expression={expr} emphasizeBytes={this.props.emphasizeBytes} />
</div>
}
console.log('[BitwiseOperationExpressionView] render()', expr);
return <b>Expression: {expr.expressionString}</b>;
}
}

View File

@@ -0,0 +1,45 @@
import React from 'react'
export default class HelpResultView extends React.Component {
render() {
return <div className="help helpResultTpl">
<div style={{overflow: "hidden"}}>
<div style={{float: "left", "marginRight": "20px"}}>
<div className="section">
<strong>Supported Commands</strong>
<ul>
<li><code>23 ^ 34</code> type bitwise expression to see result in binary (only positive integers are supported now)</li>
<li><code>23 34</code> type one or more numbers to see their binary representations</li>
<li><code>clear</code> clear output pane</li>
<li><code>help</code> display this help</li>
<li><code>whatsnew</code> display changelog</li>
<li><code>em</code> turn On/Off Emphasize Bytes</li>
<li><code>dark</code> set Dark theme</li>
<li><code>light</code> set Light theme</li>
<li><code>about</code> about the app</li>
</ul>
</div>
</div>
<div style={{"float":"left"}}>
<div className="section">
<strong>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
</a>
</small>
<ul>
<li><code>&amp;</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>&lt;&lt;</code> left shift</li>
<li><code>&gt;&gt;</code> sign propagating right shift</li>
<li><code>&gt;&gt;&gt;</code> zero-fill right shift</li>
</ul>
</div>
</div>
</div>
</div>;
}
}

View File

@@ -0,0 +1,47 @@
import React from 'react';
import formatter from '../../formatter';
import BinaryStringView from './BinaryStringView';
import BitwiseExpressionViewModel from './models/BitwiseExpressionViewModel'
export default class ListOfNumersExpressionView extends React.Component {
render() {
const expr = this.props.expression;
const maxBitsLegnth = BitwiseExpressionViewModel.getNumberOfBits(expr.maxBitsLegnth, this.props.emphasizeBytes);
const numberRows = expr.numbers.map((n, i) => <OperandView key={i} operand={n} maxBitsLegnth={maxBitsLegnth} emphasizeBytes={this.props.emphasizeBytes} />);
return <table className="expression">
<tbody>
{numberRows}
</tbody>
</table>
}
}
class OperandView extends React.Component {
constructor() {
super();
this.state = { operand: null };
}
render() {
const op = this.props.operand;
const binaryString = formatter.padLeft(op.bin, this.props.maxBitsLegnth, '0');
return <tr data-kind={op.kind}>
<td className="label">{op.input}</td>
<td className="bin"><BinaryStringView emphasizeBytes={this.props.emphasizeBytes} binaryString={binaryString} allowFlipBits={true} onFlipBit={e => this.flipBit(e)} /></td>
<td className="other">{op.other}</td>
</tr>;
};
flipBit(index) {
var op = this.props.operand;
const binaryString = formatter.padLeft(op.bin, this.props.maxBitsLegnth, '0');
var arr = binaryString.split('');
// TODO: this code looks ugly
arr[index] = arr[index] == '0' ? '1' : '0';
var bin = arr.join('');
op.setValue(parseInt(bin, 2));
this.setState({ operand: op });
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react'
export default class WhatsnewResultView extends React.Component {
render() {
return <div class="changelog">
<h3>BitwiseCmd Changelog</h3>
<div class="item">
<p><strong>May 16th, 2017</strong> Complete rewrite using React. Old implementation is available at <a href="http://bitwisecmd.com/old">http://bitwisecmd.com/old</a>.</p>
<p>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>;
}
}

View File

@@ -0,0 +1,80 @@
export default class BitwiseExpressionViewModel {
constructor({ emphasizeBytes = false } = {}) {
this.emphasizeBytes = emphasizeBytes;
this.items = [];
this.maxNumberOfBits = 0;
}
static buildMultiple (expr, config) {
var op = expr.expressions[0],
i = 1, l = expr.expressions.length,
ex, m = new BitwiseExpressionViewModel(config);
m.addOperand(op);
for (;i<l;i++) {
ex = expr.expressions[i];
op = ex.apply(op.value);
if(ex.isShiftExpression()){
m.addShiftExpressionResult(ex, op);
} else {
m.addExpression(ex);
m.addExpressionResult(op);
}
}
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
static buildNot (expression, config) {
var m = new BitwiseExpressionViewModel(config);
m.addExpression(expression);
m.addExpressionResult(expression.apply());
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
addOperand(operand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({ sign:'', label: operand.toString(), bin: operand.bin, other: operand.other, css: ''});
};
addExpression(expression) {
this.maxNumberOfBits = Math.max(expression.operand1.getLengthInBits(), this.maxNumberOfBits);
this.items.push({ sign: expression.sign, label: expression.operand1.toString(), bin: expression.operand1.bin, other: expression.operand1.other, css: ''});
};
addShiftExpressionResult(expression, resultOperand) {
this.maxNumberOfBits = Math.max(resultOperand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign: expression.sign + expression.operand1.input,
label: resultOperand.toString(),
bin: resultOperand.bin,
other: resultOperand.other,
css: 'expression-result'});
};
addExpressionResult(operand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({ sign:'=', label: operand.toString(), bin: operand.bin, other: operand.other, css: 'expression-result'});
};
// TODO: move this method elsewhere. It is also used in LisOfNumbersExpressionView.js
static getNumberOfBits = function (bits, emphasizeBytes) {
if(emphasizeBytes && bits % 8 != 0) {
if(bits < 8) {
return 8;
}
var n = bits - (bits % 8);
return n + 8;
}
return bits;
};
}

273
src/app/expression.js Normal file
View File

@@ -0,0 +1,273 @@
var expression = {
factories:[],
canParse: function(string) {
var trimmed = string.replace(/^\s+|\s+$/, '');
var i = this.factories.length-1;
for(;i>=0;i--) {
if(this.factories[i].canCreate(trimmed) === true){
return true;
}
}
return false;
},
parse: function(string) {
var trimmed = string.replace(/^\s+|\s+$/, '');
var i = 0, l = this.factories.length, factory;
for(;i<l;i++) {
factory = this.factories[i];
if(factory.canCreate(trimmed) == true){
return factory.create(trimmed);
}
}
return null;
},
parseOperand: function(input) {
return new Operand(input);
},
createOperand: function(number, kind) {
return Operand.create(number, kind);
},
addFactory: function(factory) {
this.factories.push(factory);
}
};
// List of numbers
expression.addFactory({
regex: /^(-?(?:\d+|0x[\d,a-f]+)\s?)+$/,
canCreate: function(string) {
return this.regex.test(string);
},
create: function (string) {
var matches = this.regex.exec(string),
numbers = [],
input = matches.input;
input.split(' ').forEach(function(n){
if(n.trim().length > 0) {
numbers.push(new Operand(n.trim()));
}
});
return new ListOfNumbersExpression(input, numbers);
}
});
// Not Expression
expression.addFactory({
regex: /^(~)(-?(?:\d+|0x[\d,a-f]+))$/,
canCreate: function(string) {
return this.regex.test(string);
},
create: function (string) {
var matches = this.regex.exec(string),
operand = new Operand(matches[2]);
return new SingleOperandExpression(matches.input, operand, matches[1]);
}
});
// Multiple operands expression
expression.addFactory({
fullRegex: /^((<<|>>|>>>|\||\&|\^)?(-?((?:\d+(?!x))|(?:0x[\d,a-f]+))))+$/,
regex: /(<<|>>|>>>|\||\&|\^)?(-?((?:\d+(?!x))|(?:0x[\d,a-f]+)))/g,
canCreate: function(string) {
this.fullRegex.lastIndex = 0;
return this.fullRegex.test(this.normalizeString(string));
},
create: function (string) {
var m, operands = [],
normalizedString = this.normalizeString(string);
while ((m = this.regex.exec(normalizedString)) != null) {
operands.push(this.parseMatch(m));
}
return new MultipleOperandsExpression(normalizedString, operands)
},
parseMatch: function (m) {
var input = m[0],
sign = m[1],
num = m[2];
if(sign == null) {
return new Operand(num);
} else {
return new SingleOperandExpression(input, new Operand(num), sign);
}
},
normalizeString: function (string) {
return string.replace(/\s+/g,'');
}
});
// Represents numeric value
export class Operand {
constructor(input) {
this.input = input;
this.value = parseInt(input);
this.hex = Operand.toHexString(this.value.toString(16));
this.dec = this.value.toString(10);
// >>> 0 makes negative numbers like -1 to be displayed as '11111111111111111111111111111111' in binary instead of -1
this.bin = this.value < 0 ? (this.value >>> 0).toString(2) : this.value.toString(2);
this.kind = this.input.indexOf('0x') > -1 ? 'hex' : 'dec';
this.other = this.kind == 'dec' ? this.hex : this.dec;
this.lengthInBits = Operand.getBitLength(this.value);
}
getLengthInBits() {
if(this.value < 0) {
return 32;
}
return Math.floor(Math.log(this.value) / Math.log(2)) + 1;
};
getOtherKind(kind) {
switch(kind || this.kind) {
case 'dec': return 'hex';
case 'hex': return 'dec';
default : throw new Error(kind + " kind doesn't have opposite kind")
}
};
toString() {
return this.input;
}
setValue(value) {
console.log('Before ' + value, this);
this.value = value;
this.bin = Operand.toKindString(this.value, 'bin');
this.dec = Operand.toKindString(this.value, 'dec');
this.hex = Operand.toKindString(this.value, 'hex');
this.other = Operand.toKindString(this.value, this.getOtherKind());
this.input = Operand.toKindString(this.value, this.kind);
console.log('After ' + value, this);
}
static getBitLength(num) {
return Math.floor(Math.log(num) / Math.log(2)) + 1
}
static getBase(kind){
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
};
static create(number, kind) {
var str = number.toString(Operand.getBase(kind));
if(kind == 'hex') {
str = Operand.toHexString(str);
}
return new Operand(str);
};
static toKindString(value, kind) {
switch(kind) {
case 'hex':
var hexVal = Math.abs(value).toString(16);
return value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
case 'bin':
return (value>>>0).toString(2);
case 'dec':
return value.toString(10);
default:
throw new Error("Unexpected kind: " + kind)
}
};
static toHexString (hex) {
return hex.indexOf('-') == 0 ? '-0x' + hex.substr(1) : '0x' + hex;
};
}
// Expressions like ~1
export class SingleOperandExpression {
constructor(expressionString, operand, sign) {
this.expressionString = expressionString;
this.operand1 = operand;
this.sign = sign;
}
apply(value) {
var str = '';
if(this.sign == '~'){
str = '~' + this.operand1.value;
} else {
str = value + this.sign + this.operand1.value
}
return Operand.create(eval(str), this.operand1.kind);
};
isShiftExpression() {
return this.sign.indexOf('<') >= 0 || this.sign.indexOf('>')>= 0;
};
toString() {
return this.sign + this.operand1.toString();
}
}
// Expression like 1|2 or 4^5
export class TwoOperandExpression {
constructor(expressionString, operand1, operand2, sign) {
this.expressionString = expressionString;
this.operand1 = operand1;
this.operand2 = operand2;
this.sign = sign;
}
}
export class MultipleOperandsExpression {
constructor(expressionString, expressions) {
this.expressionString = expressionString;
this.expressions = expressions;
}
}
export class ListOfNumbersExpression {
constructor(expressionString, numbers) {
this.expressionString = expressionString;
this.numbers = numbers;
this.maxBitsLegnth = numbers.map(n => n.lengthInBits).reduce((n , c) => n >= c ? n : c, 0);
}
toString() {
return this.numbers.map(n => n.value.toString()).join(' ');
}
}
export class Expression {
toString() {
return this.expressionString ? "Expression: " + this.expressionString : this.toString();
};
}
export var parser = expression;
export class Parser {
constructor(input, pos) {
this.input = input;
this.pos = pos || 0;
this.buffer = [];
}
parse() {
console.log(this.input.length);
while(this.pos<this.input.length) {
this.buffer.push(this.input[this.pos]);
this.pos++;
}
console.log('exit');
}
}

26
src/app/formatter.js Normal file
View File

@@ -0,0 +1,26 @@
export default {
formatString: function(num, kind) {
return num.toString(getBase(kind || "bin"));
},
padLeft: function (str, length, symbol) {
var sb = Array.prototype.slice.call(str), symbol = symbol || "0";
if(length == null) {
return str;
}
while(length > sb.length) {
sb.unshift(symbol);
}
return sb.join('');
}
};
function getBase(kind) {
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
}

41
src/app/hash.js Normal file
View File

@@ -0,0 +1,41 @@
export default {
encodeHash: function(string) {
return encodeURI(string.trim().replace(/\s/g,','));
},
decodeHash: function(hashValue) {
return decodeURI(hashValue).replace(/^\#/, '').replace(/,/g,' ');
},
getArgs: function (hashValue) {
var decodedHash = this.decodeHash(hashValue),
args = { commands: [] };
splitHashList(decodedHash).forEach(function(value) {
// Support for -debur or -notrack properties
if(/^\-[a-zA-Z]+$/.test(value)) {
args[value.substr(1)] = true;
return;
}
args.commands.push(value);
});
return Object.freeze(args);
}
};
function splitHashList(str) {
var values = [];
if(str.indexOf('||')) {
str.split('||').forEach(function (v) {
if (v.length > 0) {
values.push(v);
}
});
} else {
values.push(str);
}
return values;
}

55
src/app/index.jsx Normal file
View File

@@ -0,0 +1,55 @@
import React from 'react';
import ReactDOM from 'react-dom';
import InputBox from './components/InputBox.jsx';
import AppState from './AppState';
import appStateStore from './appStateStore';
import cmd from './cmd';
import commands from './commands';
import AppRoot from './components/AppRoot';
import hash from './hash';
import log from 'loglevel';
setupLogger();
const appState = createAppState();
commands.initialize(cmd, appState);
executeStartupCommands();
var root = <AppRoot appState={appState} />;
ReactDOM.render(root, document.getElementById('root'));
log.debug("started");
function createAppState() {
var stateData = appStateStore.getPersistedData();
const appState = new AppState(stateData);
appStateStore.watch(appState);
log.debug("appState", appState);
return appState;
}
function setupLogger() {
if(window.location.host != 'bitwisecmd.com' || window.location.hash.indexOf('-debug') > -1) {
log.setLevel("trace");
} else {
log.setLevel("warn");
}
}
function executeStartupCommands() {
var hashArgs = hash.getArgs(window.location.hash);
var startupCommands = ['help', '1|2&6','1<<0x2a','2 4 8 16 32'];
if(appState.wasOldVersion) {
startupCommands = ["whatsnew"];
}
if(hashArgs.commands.length > 0) {
startupCommands = hashArgs.commands;
}
startupCommands.forEach(cmd.execute.bind(cmd));
}

33
src/app/is.js Normal file
View File

@@ -0,0 +1,33 @@
export default {
plainObject: function(obj) {
return typeof obj == "object" && obj instanceof Object;
},
aFunction: function (obj) {
return typeof obj == "function";
},
string: function (obj) {
return typeof obj == "string";
},
regex: function (obj) {
return typeof obj == "object" && this.constructedFrom(RegExp);
},
constructedFrom: function (obj, ctor) {
return obj instanceof ctor;
},
htmlElement: function(obj) {
return obj instanceof HtmlElement;
},
array: function(obj) {
return obj instanceof Array;
},
number: function(num) {
return typeof num == "number" && !isNaN(num)
}
}

View File

@@ -0,0 +1,7 @@
import CommandResult from './CommandResult';
export default class AboutResult extends CommandResult {
constructor(input) {
super(input);
}
}

View File

@@ -0,0 +1,10 @@
export default class CommandResult {
constructor(input) {
this.input = input;
this.inputHash = this.encodeHash(input);
}
encodeHash (string) {
return encodeURI(string.trim().replace(/\s/g,','));
}
}

View File

@@ -0,0 +1,8 @@
import CommandResult from './CommandResult';
export default class ErrorResult extends CommandResult {
constructor(input, error) {
super(input);
this.error = error;
}
}

View File

@@ -0,0 +1,8 @@
import CommandResult from './CommandResult';
export default class ExpressionResult extends CommandResult {
constructor(input, expression) {
super(input);
this.expression = expression;
}
}

View File

@@ -0,0 +1,7 @@
import CommandResult from './CommandResult';
export default class HelpResult extends CommandResult {
constructor(input) {
super(input);
}
}

View File

@@ -0,0 +1,8 @@
import CommandResult from './CommandResult';
export default class UnknownCommandResult extends CommandResult {
constructor(input) {
super(input);
this.message = `Sorry, i don''t know what ${input} is :(`;
}
}

View File

@@ -0,0 +1,7 @@
import CommandResult from './CommandResult';
export default class WhatsnewResult extends CommandResult {
constructor(input) {
super(input);
}
}

View File

@@ -1,4 +1,7 @@
body { font-family: Verdana; font-size: 0.8em; margin: 0; padding: 20px 100px 0 100px; }
body { padding:0; margin:0; height: 100%; overflow: hidden; }
html { height: 100% }
.app-root { font-family: Verdana; font-size: 0.8em; margin: 0; padding: 20px 100px 0 100px; height: 100%; overflow: auto; }
code { font-size: 1.2em; font-weight: bold; }
.top-links { position: absolute; right: 10px; top: 10px; list-style-type: none; margin: 0 }
@@ -69,6 +72,7 @@ code { font-size: 1.2em; font-weight: bold; }
/* Dark */
.dark { background: #121212; color: white;}
.dark .expression { color: white;}
.dark .expressionInput { background: #121212; color: white; }
.dark a, .dark a:visited { color: white; }
.dark .indicator { color: #555; }
@@ -79,6 +83,7 @@ code { font-size: 1.2em; font-weight: bold; }
.dark .hashLink, .dark .hashLink:visited { color: #333 }
.dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 }
.dark .error { color: mediumvioletred}
/* Top Links Shrink */
@media (max-width: 800px) {
@@ -86,6 +91,8 @@ code { font-size: 1.2em; font-weight: bold; }
.top-links .link-text { display: none }
}
.social-container{ position:fixed; bottom:10px; right:10px }
/* Remove margin space on body. Inline top links with header */
@media (max-width: 700px) {
body { padding: 10px; }

BIN
src/img/social-avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,182 +1,39 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="description" content="Bitwise Calculator. Visualised Bitwise Operations.">
<title>BitwiseCmd</title>
<link rel="shortcut icon" href="http://bitwisecmd.com/favicon.ico">
<!-- build:js js/bitwisecmd.js -->
<script type="text/javascript" src="js/core/core.js"></script>
<script type="text/javascript" src="js/core/is.js"></script>
<script type="text/javascript" src="js/core/should.js"></script>
<script type="text/javascript" src="js/core/di.js"></script>
<script type="text/javascript" src="js/core/appShell.js"></script>
<script type="text/javascript" src="js/core/htmlBuilder.js"></script>
<script type="text/javascript" src="js/core/observable.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/components/controllersFeature.js"></script>
<script type="text/javascript" src="js/components/viewsFeature.js"></script>
<script type="text/javascript" src="js/components/templatesFeature.js"></script>
<script type="text/javascript" src="js/app/bitwise/calc.js"></script>
<script type="text/javascript" src="js/app/bitwise/expression.js"></script>
<script type="text/javascript" src="js/app/bitwise/formatter.js"></script>
<script type="text/javascript" src="js/app/models.js"></script>
<script type="text/javascript" src="js/app/modelViews.js"></script>
<script type="text/javascript" src="js/app/cmd/cmd.js"></script>
<script type="text/javascript" src="js/app/services.js"></script>
<script type="text/javascript" src="js/app/controllers.js"></script>
<script type="text/javascript" src="js/app/cmd/commands.js"></script>
<script type="text/javascript" src="js/app/run.js"></script>
<!-- /build -->
<link rel="stylesheet" type="text/css" href="css/styles.css" />
</head>
<body id="rootView" class="dark">
<header>
<div class="header">
<h1>Bitwise<span style="color: #c5c5c5;">Cmd</span></h1>
<ul class="top-links">
<li>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><i class="icon github">&nbsp;</i><span class="link-text">Project on GitHub</span></a>
</li>
<li>
<a href="https://twitter.com/BitwiseCmd"><i class="icon twitter">&nbsp;</i><span class="link-text">Twitter</span></a>
</li>
<li>
<a href="mailto:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback"><i class="icon feedback">&nbsp;</i><span class="link-text">Send Feedback</span></a>
</li>
</ul>
</div>
<div class="expressionInput-container">
<input id="in" type="text" class="expressionInput mono" data-controller="expressionInputCtrl" placeholder="type expression like '1>>2' or 'help' "/>
<span data-controller="configPanelCtrl" class="configPnl">
<span id="emphasizeBytes" data-cmd="em" class="indicator on" title="Emphasize Bytes">[em]</span>
</span>
</div>
</header>
<div id="output" data-controller="cmdController">
</div>
<script data-template="helpResultTpl" type="text/template">
<div class="help helpResultTpl">
<div style="overflow: hidden">
<div style="float: left; margin-right: 20px">
<p class="section">
<strong>Supported Commands</strong>
<ul>
<li><code>23 ^ 34</code> type bitwise expression to see result in binary (only positive integers are supported now)</li>
<li><code>23 34</code> type one or more numbers to see their binary representations</li>
<li><code>clear</code> clear output pane</li>
<li><code>help</code> display this help</li>
<li><code>em</code> turn On/Off Emphasize Bytes</li>
<li><code>dark</code> set Dark theme</li>
<li><code>light</code> set Light theme</li>
<li><code>about</code> about the app</li>
</ul>
</p>
</div>
<div class="float:left">
<p class="section">
<strong>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
</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>&lt;<</code> left shift</li>
<li><code>&gt;&gt;</code> sign propagating right shift</li>
<li><code>&gt;&gt;&gt;</code> zero-fill right shift</li>
</ul>
</p>
</div>
</div>
<p style="font-style: italic">
<strong>Tip:</strong> Use Up and Down keys to navigate trough executed commands.
</p>
</div>
</script>
<script data-template="aboutTpl" type="text/template">
<div class="aboutTpl">
<p> Created by <a href="http://boryslevytskyi.github.io/">Borys Levytskyi</a></p>
<p>If you have an idea, suggestion or you've spotted a bug here, please send it to <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">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</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>
</script>
<script data-template="resultView" data-compiled="" type="text/template">
<div class="result">
<div class="input mono"><span class="cur">&gt;</span>{m.input}<a class="hashLink" title="Link for this expression" href="{window.location.pathname}#{m.inputHash}">#</a></div>
<div class="content"></div>
</div>
</script>
<script data-template="bitwiseExpressionView" data-compiled="" type="text/template">
<table class="expression" cellspacing="0">
{each itm in m.items}
<tr class="{itm.css}">
<td class="sign">{itm.sign}</td>
<td class="label">{itm.label}</td>
<td class="bin">{itm.bin.padLeft(m.maxNumberOfBits, '0')}</td>
<td class="other">{itm.other}</td>
</tr>
{/}
</table>
</script>
<script data-template="numbersList" data-compiled="" type="text/template">
<table class="expression" cellspacing="0">
{each op in m.operands}
<tr data-kind="{op.kind}">
<td class="label">{op.input}</td>
<td class="bin">{op.bin.padLeft(m.bitsSize, '0')}</td>
<td class="other">{op.other}</td>
</tr>
{/}
</table>
</script>
<script type="text/javascript">
var app = window.app;
app.bootstrap(document.getElementById('rootView'));
var cmd = app.get('cmd');
var hashArgs = app.get('hashArgs');
if(hashArgs.commands.length > 0) {
hashArgs.commands.forEach(cmd.execute.bind(cmd));
}
else {
cmd.execute('help');
cmd.execute('1|2&6');
cmd.execute('1<<0x2a');
cmd.execute('2 4 8 16 32');
}
</script>
<!-- build:js js/analytics.js -->
<!-- /build -->
</body>
<!DOCTYPE>
<html>
<head>
<meta charset="UTF-8">
<meta name="description" content="Free Text Online Bitwise Calculator">
<title>BitwiseCmd</title>
<link rel="shortcut icon" href="http://bitwisecmd.com/favicon.ico">
<link rel="stylesheet" type="text/css" href="css/styles.css" />
<meta property="og:url" content="http://bitwisecmd.com" />
<meta property="og:type" content="website" />
<meta property="og:title" content="BitwiseCmd" />
<meta property="og:description" content="Free Text Online Bitwise Calculator" />
<meta property="og:image" content="http://bitwisecmd.com/img/social-avatar.png" />
</head>
<body>
<div id="root">
<p>Ooops... Something went wrong.</p>
<p>Either you have an old browser or I screwed something up ¯\_(ツ)_/¯</p>
<p>No worries! There is a still old version avaliable at <a href="http://bitwisecmd.com/old">http://bitwisecmd.com/old</a>.</p>
</div>
<script src="bundle.js"></script>
<script src="analytics.js"></script>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div class="social-container">
<div class="fb-like" data-href="http://bitwisecmd.com" data-layout="box_count" data-action="like" data-show-faces="false" data-share="true"></div>
</div>
</body>
</html>

View File

@@ -1,4 +0,0 @@
rem /S Copy folders and subfolders
rem /Y Suppress prompt to confirm overwriting a file.
xcopy .\build\*.* ..\BitwiseCmdPages\ /S /Y

View File

@@ -7,7 +7,7 @@ var driver = browser.driver;
var appUrl = browser.params.appUrl || 'http://localhost:63342/BitwiseCmd/src/#clear';
var sutPage = new BitwiseCmdPage(driver, appUrl);
describe('launch of application', function() {
describe('when application starts', function() {
it('should have title', function() {
sutPage.goToApp().then(function() {
expect(driver.getTitle()).toEqual('BitwiseCmd');
@@ -149,15 +149,15 @@ describe('launch of application', function() {
});
xit('should emphasize bytes', function() {
it('should emphasize bytes', function() {
goToApp()
sutPage.goToApp()
.then(function() { return sutPage.executeExpression('1')})
.then(function() {
return assertExpressionResult([{ label: '1', bin:'00000001', other: '0x1'}])
})
.then(function() { return sutPage.executeExpression('clear')})
// .then(function() { return sendCommand('em')})
.then(function() { return sutPage.executeExpression('em')})
.then(function() { return sutPage.shouldHaveNoErrors(); })
.then(function() { return sutPage.executeExpression('1 3')})
.then(function() {

View File

@@ -1,20 +0,0 @@
describe("cloned container", function() {
var objA = { id: "a"};
var objB = { id: "b" };
var objC = { id: 'c'};
var parent = new core.Container();
parent.register('a', objA);
parent.register('b', objB);
var cloned = parent.clone();
cloned.register('a', objC);
it("should be independent from source container", function() {
expect(parent.resolve('a')).toBe(objA);
expect(cloned.resolve('a')).toBe(objC);
expect(parent.resolve('b')).toBe(objB);
expect(cloned.resolve('b')).toBe(objB);
});
});

View File

@@ -1,18 +0,0 @@
describe('html templates', function () {
var html = core.html;
it('should compile template', function() {
var t = "<div>{m.name}</div>";
var compiled = html.compileTemplate(t);
expect(typeof compiled).toBe("function");
expect(compiled({name: 'test'})).toBe('<div>test</div>');
});
it('should support each', function () {
var t = '{each n in m.lst}{each c in m.lst2}{n}{c}{/}{/}';
var compiled = html.compileTemplate(t);
var result = compiled({lst:[1,2,3], lst2:['a','b']});
console.log(result);
expect(result).toBe('1a1b2a2b3a3b');
});
});

View File

@@ -1,13 +1,12 @@
var app = window.app;
var expression = app.get('expression');
var expression = require('../../src/app/expression');
var parser = expression.parser;
describe("expression parse", function() {
describe("expression parser", function() {
var shouldParse = ['0x2>>1', '1 2 3', '0x1 1 2 3 5', '0x1>>0x2', '1|2', '-9', '-1|-2', '~3'];
it("should be able to parse", function() {
shouldParse.forEach(function(expr) {
expect(expression.canParse(expr)).toBe(true, 'expr: ' + expr);
expect(parser.canParse(expr)).toBe(true, 'expr: ' + expr);
})
});
@@ -29,7 +28,7 @@ describe("expression parse", function() {
for(input in expressionCases) {
console.log('case: ' + input);
var actual = expression.parse(input);
var actual = parser.parse(input);
var expected = expressionCases[input];
expect(actual).toBeDefined();
expect(actual).not.toBe(null);
@@ -58,7 +57,7 @@ describe("expression parse", function() {
it("should parse hexadecimal expressions", function() {
var input, i;
for(input in listCases) {
var actual = expression.parse(input);
var actual = parser.parse(input);
var expected = listCases[input];
for(i =0; i<expected.length;i++) {
@@ -69,26 +68,26 @@ describe("expression parse", function() {
});
it ("should parse multiple operands expression", function () {
var actual = expression.parse("1|2&3");
var actual = parser.parse("1|2&3");
})
});
describe('parse operands', function() {
var hexOperand = expression.parseOperand('0x10');
var decOperand = expression.parseOperand('10');
var hexOperand = parser.parseOperand('0x10');
var decOperand = parser.parseOperand('10');
rundOperandsTest(hexOperand, decOperand);
});
describe('create operands', function() {
var hexOperand = expression.createOperand(0x10, 'hex');
var decOperand = expression.createOperand(10, 'dec');
var hexOperand = parser.createOperand(0x10, 'hex');
var decOperand = parser.createOperand(10, 'dec');
rundOperandsTest(hexOperand, decOperand);
});
describe('negative operands', function () {
var op = expression.parseOperand('-0xa');
var op = parser.parseOperand('-0xa');
it('shoold have correct values', function() {
expect(op.value).toBe(-10);
expect(op.hex).toBe('-0xa');
@@ -112,8 +111,6 @@ describe('should format to kind strings', function() {
});
});
function rundOperandsTest(hexOperand, decOperand) {
it('should remember input form', function() {
expect(hexOperand.input).toBe('0x10');

View File

@@ -1,6 +1,6 @@
var formatter = require('../../src/app/formatter');
describe('expression formatter', function () {
var di = app.di.clone();
var formatter = di.resolve('formatter');
xit('should format number to binary by default', function() {
expect(formatter.formatString(10)).toBe("1010");

View File

@@ -1,5 +1,6 @@
var hash = require('../../src/app/hash').default;
describe('hash arguments parser', function() {
var hash = app.get('hash');
it('should parse empty', function() {
var args = hash.getArgs('');
@@ -53,7 +54,4 @@ describe('hash arguments parser', function() {
expect(args.notrack).toBe(true);
expect(args.debug).toBe(true);
});
});

11
tests/unit/parserSpec.js Normal file
View File

@@ -0,0 +1,11 @@
var expression = require('../../src/app/expression');
var parser = new expression.Parser();
describe("Parser", function() {
it("should be able to parse from start", function() {
var sut = new expression.Parser("test", 0);
sut.parse();
console.log(sut);
});
});

2
todo.txt Normal file
View File

@@ -0,0 +1,2 @@
Unparsable expressions:
~2|11

14
tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react"
},
"files": [
"./src.old/components/Hello.tsx",
"./src.old/index.tsx"
]
}

29
webpack.config.js Normal file
View File

@@ -0,0 +1,29 @@
module.exports = {
entry: __dirname + "/src/app/index.jsx",
output: {
filename: 'bundle.js',
path: __dirname + '/src',
publicPath: 'http://localhost:8080/'
},
devtool: 'source-maps',
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node-modules/,
//output: { path: __dirname + '/src', filename: 'bundle.js' },
query: {
presets: [require.resolve('babel-preset-es2015'), require.resolve('babel-preset-react')],
plugins: [require.resolve('babel-plugin-transform-class-properties')]
},
}]
},
// externals: {
// //don't bundle the 'react' npm package with our bundle.js
// //but get it from a global 'React' variable
// 'react': 'React'
// },
resolve: {
extensions: ['.js', '.jsx']
}
};