@ -0,0 +1 @@ |
||||
node_modules |
@ -0,0 +1,33 @@ |
||||
module.exports = function (grunt) { |
||||
|
||||
var baseSrc = 'js/src'; |
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-watch'); |
||||
grunt.loadNpmTasks('grunt-contrib-uglify'); |
||||
|
||||
grunt.initConfig({ |
||||
uglify: { |
||||
options: { |
||||
separator: ';' |
||||
}, |
||||
compile: { |
||||
src: [ |
||||
baseSrc + '/Sankore/klass.js',
|
||||
baseSrc + '/**/*.js' |
||||
], |
||||
dest: 'dist/calculator.js' |
||||
} |
||||
}, |
||||
watch: { |
||||
scripts: { |
||||
files: 'js/src/**/*.js', |
||||
tasks: ['scripts:dist'] |
||||
} |
||||
} |
||||
}); |
||||
|
||||
grunt.registerTask('default', ['dist', 'watch']); |
||||
grunt.registerTask('dist', ['scripts:dist']); |
||||
|
||||
grunt.registerTask('scripts:dist', ['uglify:compile']); |
||||
}; |
@ -0,0 +1,14 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<widget xmlns="http://www.w3.org/ns/widgets" |
||||
xmlns:ub="http://uniboard.mnemis.com/widgets" |
||||
id="http://uniboard.mnemis.com/widgets/calculator" |
||||
version="1.1" |
||||
width="460" |
||||
height="418" |
||||
minimum_width="460" |
||||
minimum_height="418" |
||||
ub:resizable="true" |
||||
ub:transparent="true"> |
||||
<name>Calculator</name> |
||||
<content src="index.html"/> |
||||
</widget> |
@ -0,0 +1,380 @@ |
||||
* { |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
body { |
||||
font-size: 12px; |
||||
margin:0; |
||||
} |
||||
.calculator { |
||||
-webkit-user-select: none; |
||||
font-family: Verdana; |
||||
background: -webkit-linear-gradient(top, #f6f6f6 0%, #f7f7f7 25%, #f4f4f4 25%, #e8e8e8 100%); |
||||
background: linear-gradient(to bottom, #f6f6f6 0%, #f7f7f7 25%, #f4f4f4 25%, #e8e8e8 100%); |
||||
padding: 1em; |
||||
width: auto; |
||||
border-radius: 0.66em; |
||||
border: 1px solid #bbb; |
||||
margin: 0.33em; |
||||
box-shadow: 0.16em 0.16em 0.46em rgba(0, 0, 0, 0.35), inset 0 0 0.45em rgba(0, 0, 0, 0.3); |
||||
} |
||||
.calculator .title { |
||||
font-weight:bold; |
||||
color:#333; |
||||
text-shadow:0 1px 1px rgba(0, 0, 0, 0.2); |
||||
display:block; |
||||
margin-top: -1.1em; |
||||
margin-right:25%; |
||||
top: 0.4em; |
||||
position:relative; |
||||
} |
||||
.calculator .controls { |
||||
float: right; |
||||
margin: -1.3em 0.2em 0.25em 0; |
||||
} |
||||
.calculator .controls button { |
||||
background: -webkit-linear-gradient(top, #666666 0%, #444444 100%); |
||||
background: linear-gradient(to bottom, #666666 0%, #444444 100%); |
||||
padding: 0.12em 0.5em; |
||||
border: none; |
||||
color: white; |
||||
outline: none; |
||||
text-shadow: 0 -1px 0 black; |
||||
border-radius: 0 0 0.25em 0.25em; |
||||
box-shadow: inset 0 -1px 1px #000000, 0 1px 1px rgba(0, 0, 0, 0.5); |
||||
} |
||||
.calculator .controls button + button { |
||||
margin-left:2px; |
||||
} |
||||
.calculator .controls button:active { |
||||
background: -webkit-linear-gradient(bottom, #666666 0%, #444444 100%); |
||||
background: linear-gradient(to top, #666666 0%, #444444 100%); |
||||
box-shadow: inset 0 -1px 3px #000000; |
||||
color: #cccccc; |
||||
} |
||||
.calculator table { |
||||
table-layout: fixed; |
||||
line-height:90%; |
||||
width: 100%; |
||||
border-collapse: separate; |
||||
border-spacing: 1px; |
||||
} |
||||
.calculator .screen td { |
||||
height: 1px; |
||||
} |
||||
.calculator .screen ul { |
||||
height: 100%; |
||||
cursor: default; |
||||
box-sizing: border-box; |
||||
margin-bottom: 0.8rem; |
||||
padding: 0.4rem; |
||||
font-size: 1.5em; |
||||
line-height: normal; |
||||
text-shadow: 0 1px 1px #ffffff; |
||||
color: #444f53; |
||||
background: -webkit-linear-gradient(top, #f6f8f9 0%, #d2edf2 70%, #c9e3e7 70%, #d4eff4 100%); |
||||
background: linear-gradient(to bottom, #f6f8f9 0%, #d2edf2 70%, #c9e3e7 70%, #d4eff4 100%); |
||||
border: 1px solid #d3d3d3; |
||||
border-top-color: #d0d0d0; |
||||
border-left-color: #d0d0d0; |
||||
border-radius: 0.33rem; |
||||
box-shadow: 0 0 0.4rem #ffffff, inset 0.15rem 0.15rem 0.4rem rgba(21, 39, 54, 0.5); |
||||
} |
||||
.calculator .screen li { |
||||
list-style-type: none; |
||||
} |
||||
.calculator .screen .expression-row .caret { |
||||
border-left: 1px solid #444f53; |
||||
box-sizing: border-box; |
||||
margin-right: -1px; |
||||
-webkit-animation-name: blinker; |
||||
-webkit-animation-duration: 1.2s; |
||||
-webkit-animation-timing-function: linear; |
||||
-webkit-animation-iteration-count: infinite; |
||||
animation-name: blinker; |
||||
animation-duration: 1.2s; |
||||
animation-timing-function: linear; |
||||
animation-iteration-count: infinite; |
||||
} |
||||
@-webkit-keyframes blinker { |
||||
0% { |
||||
opacity: 1.0; |
||||
} |
||||
40% { |
||||
opacity: 1.0; |
||||
} |
||||
50% { |
||||
opacity: 0.0; |
||||
} |
||||
90% { |
||||
opacity: 0.0; |
||||
} |
||||
} |
||||
@keyframes blinker { |
||||
0% { |
||||
opacity: 1.0; |
||||
} |
||||
40% { |
||||
opacity: 1.0; |
||||
} |
||||
50% { |
||||
opacity: 0.0; |
||||
} |
||||
90% { |
||||
opacity: 0.0; |
||||
} |
||||
} |
||||
.calculator .screen .flag-row { |
||||
font-size: 0.6em; |
||||
min-height: 15px; |
||||
overflow: hidden; |
||||
} |
||||
.calculator .screen .flag-row span { |
||||
margin-right:0.3em; |
||||
display: inline-block; |
||||
} |
||||
.calculator .screen .result-row { |
||||
min-height: 38px; |
||||
font-size: 1.6em; |
||||
text-align: right; |
||||
} |
||||
.calculator .screen .result-row .error { |
||||
color: #4b2525; |
||||
} |
||||
.calculator .screen .euclidean { |
||||
font-size: 0.8em; |
||||
} |
||||
.calculator .screen .euclidean span { |
||||
border-bottom: 1px solid #444f53; |
||||
position: relative; |
||||
margin-bottom: 0.5rem; |
||||
display: block; |
||||
float: right; |
||||
} |
||||
.calculator .screen .euclidean .remainder { |
||||
margin-left: 0.5em; |
||||
} |
||||
.calculator .screen .euclidean span:before { |
||||
font-size: 0.4em; |
||||
display: block; |
||||
position: absolute; |
||||
bottom: -1em; |
||||
width: 100%; |
||||
text-align: center; |
||||
} |
||||
.calculator .screen .euclidean .quotient:before { |
||||
content: 'q'; |
||||
} |
||||
.calculator .screen .euclidean .remainder:before { |
||||
content: 'r'; |
||||
} |
||||
.calculator .screen .front-screen ul { |
||||
overflow: hidden; |
||||
} |
||||
.calculator .screen .front-screen .expression-row { |
||||
min-height: 22px; |
||||
max-height: 22px; |
||||
white-space: nowrap; |
||||
} |
||||
.calculator .screen .rear-screen { |
||||
width: 50%; |
||||
} |
||||
.calculator .screen .rear-screen ul { |
||||
-webkit-user-select: initial; |
||||
height: 103.5%; |
||||
margin-right: 0.8rem; |
||||
margin-bottom: 0; |
||||
font-size: 1.3em; |
||||
background: -webkit-linear-gradient(top, #3b474f 0%, #131f21 51%, #0d1516 100%); |
||||
background: linear-gradient(to bottom, #3b474f 0%, #131f21 51%, #0d1516 100%); |
||||
box-shadow: 0 0 0.4rem #ffffff, inset 0.15rem 0.15rem 0.4rem rgba(21, 39, 54, 0.8); |
||||
text-shadow: 0 -1px 1px #000000; |
||||
color: #cbe7f4; |
||||
overflow-y: scroll; |
||||
} |
||||
.calculator .screen .rear-screen .expression-row { |
||||
word-wrap:break-word; |
||||
} |
||||
.calculator .screen .rear-screen .euclidian span:before { |
||||
font-size: 0.6em; |
||||
} |
||||
.calculator .edit-area { |
||||
height: 100%; |
||||
margin-right: 0.8rem; |
||||
margin-bottom: 0; |
||||
font-size: 1em; |
||||
position: relative; |
||||
} |
||||
.calculator .edit-area hr { |
||||
margin: 0.7em 0 0.5em 0; |
||||
border: none; |
||||
border-top: 1px solid #d0d0d0; |
||||
border-bottom: 1px solid #fcfcfc; |
||||
} |
||||
.calculator .edit-area select { |
||||
width: 100%; |
||||
} |
||||
.calculator .edit-area button.small { |
||||
float: right; |
||||
width: 9.13%; |
||||
height:20px; |
||||
padding: 0; |
||||
font-size:0; |
||||
position:relative; |
||||
} |
||||
.calculator .edit-area button.small:before { |
||||
position: absolute; |
||||
display: block; |
||||
height:100%; |
||||
width: 100%; |
||||
font-size:16px; |
||||
top: 0; |
||||
left: 0; |
||||
font-weight:bold; |
||||
} |
||||
.calculator .edit-area button.small.add:before { |
||||
content: '+'; |
||||
} |
||||
.calculator .edit-area button.small.remove:before { |
||||
content: '-'; |
||||
} |
||||
.calculator .edit-area button.small + button.small { |
||||
margin-right:0.4rem; |
||||
} |
||||
.calculator .edit-area select.layout-select { |
||||
width: 74.77%; |
||||
} |
||||
.calculator .edit-area label { |
||||
width: 100%; |
||||
display: block; |
||||
color: #555555; |
||||
margin: 0.7em 0 0.5em 0; |
||||
} |
||||
.calculator .edit-area input[type=text], |
||||
.calculator .edit-area textarea { |
||||
display: block; |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
border: 1px solid #d0d0d0; |
||||
font-size: 1em; |
||||
font-family: Verdana; |
||||
border-radius: 3px; |
||||
padding: 4px 2px; |
||||
box-shadow: inset 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.2); |
||||
resize: none; |
||||
} |
||||
.calculator .edit-area input[type=text][disabled], |
||||
.calculator .edit-area textarea[disabled] { |
||||
background-color: #eeeeee; |
||||
color: #555555; |
||||
} |
||||
.calculator .edit-area .assignation { |
||||
margin-top: 1em; |
||||
border: 1px solid #d0d0d0; |
||||
padding: 0.5em; |
||||
box-shadow: inset 0 0 0.5em rgba(255, 255, 255, 1); |
||||
border-radius: 3px; |
||||
} |
||||
.calculator .edit-area .assignation em { |
||||
font-size: 1.1em; |
||||
line-height: 1.1em; |
||||
color: #555555; |
||||
text-align: center; |
||||
display: block; |
||||
} |
||||
.calculator .edit-area .assignation label:first-child { |
||||
margin-top: 0; |
||||
} |
||||
.calculator .edit-area .assignation .help { |
||||
display: block; |
||||
margin-top: 0.3em; |
||||
font-size: 0.9em; |
||||
color: #777777; |
||||
} |
||||
.calculator .edit-area .run { |
||||
display: block; |
||||
width: 100%; |
||||
position: absolute; |
||||
padding: 0.5em 0; |
||||
bottom: 0; |
||||
font-weight: bold; |
||||
outline: 0; |
||||
} |
||||
.calculator .buttons td { |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
.calculator .buttons button { |
||||
outline: 0; |
||||
font-family: Verdana; |
||||
font-size: 1.2em; |
||||
font-weight: 500; |
||||
color: #555555; |
||||
background: -webkit-linear-gradient(bottom, #eeeeee 0%, #fdfdfd 100%); |
||||
background: linear-gradient(to top, #eeeeee 0%, #fdfdfd 100%); |
||||
border: 0.4em solid #ffffff; |
||||
border-top-width: 0.3em; |
||||
border-bottom-width: 0.5em; |
||||
border-bottom-color: #eeeeee; |
||||
border-right-color: #eeeeee; |
||||
border-radius: 0.33rem; |
||||
box-shadow: 0.2em 0.2em 0.2em 0.1em rgba(0, 0, 0, 0.2); |
||||
box-sizing: border-box; |
||||
display: block; |
||||
width: 100%; |
||||
height: 2.4rem; |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
.calculator .buttons button:hover { |
||||
color: #29a0b5; |
||||
background: -webkit-linear-gradient(bottom, #d0ecf0 0%, #ffffff 100%); |
||||
background: linear-gradient(to top, #d0ecf0 0%, #ffffff 100%); |
||||
border-bottom-color: #d0ecf0; |
||||
border-right-color: #d0ecf0; |
||||
} |
||||
.calculator .buttons button:active { |
||||
font-size: 1.1em; |
||||
color: #29a0b5; |
||||
border: 1px solid #cccccc; |
||||
background: -webkit-linear-gradient(bottom, #d0ecf0 0%, #ffffff 100%); |
||||
background: linear-gradient(to top, #d0ecf0 0%, #ffffff 100%); |
||||
box-shadow: inset 0 0 0.5em 0.2em rgba(0, 0, 0, 0.1), 0.1em 0.1em 0.2em #ffffff; |
||||
} |
||||
.calculator .buttons .alt button { |
||||
color: #2b8eac; |
||||
font-weight: bold; |
||||
} |
||||
.calculator .buttons .edit button { |
||||
border:1px solid #aac3b2; |
||||
box-shadow: inset 0 0 1.5em 0.2em rgba(48, 114, 71, 0.3); |
||||
color: #678d74; |
||||
} |
||||
.calculator .buttons .danger button { |
||||
font-weight: bold; |
||||
} |
||||
.calculator .buttons .danger button:hover { |
||||
color: #cc5d54; |
||||
background: -webkit-linear-gradient(bottom, #f0dad0 0%, #ffffff 100%); |
||||
background: linear-gradient(to top, #f0dad0 0%, #ffffff 100%); |
||||
border-bottom-color: #f0dad0; |
||||
border-right-color: #f0dad0; |
||||
} |
||||
.calculator .buttons .danger button:active { |
||||
color: #cc5d54; |
||||
background: -webkit-linear-gradient(bottom, #f0dad0 0%, #ffffff 100%); |
||||
background: linear-gradient(to top, #f0dad0 0%, #ffffff 100%); |
||||
} |
||||
.calculator .buttons button[disabled] { |
||||
background: inherit; |
||||
box-shadow: 0.1em 0.1em 0.2em #ffffff; |
||||
border: 1px solid #cccccc; |
||||
color: #cccccc; |
||||
} |
||||
.calculator .buttons button[disabled]:hover { |
||||
background: inherit; |
||||
box-shadow: 0.1em 0.1em 0.2em #ffffff; |
||||
} |
||||
.calculator .buttons button[disabled]:active { |
||||
font-size: 1.2em; |
||||
} |
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,55 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
||||
<title>Unpredictable Calculator</title> |
||||
|
||||
<script src="js/sankore.js" type="text/javascript"></script> |
||||
<script src="dist/calculator.js" type="text/javascript"></script> |
||||
<link rel="stylesheet" type="text/css" href="css/calculator.css" /> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="ubwidget"></div> |
||||
|
||||
<script type="text/javascript"> |
||||
var unpredictable = false; |
||||
|
||||
var c = Sankore.Calculator.create('ubwidget', { |
||||
locale: window.sankore ? window.sankore.locale() : 'fr_FR', |
||||
unpredictableMode: unpredictable, |
||||
ready: function () { |
||||
var self = this, state = {}, timer = null; |
||||
|
||||
if (window.sankore) { |
||||
try { |
||||
state = JSON.parse(window.sankore.preference('state')); |
||||
} catch (e) {} |
||||
|
||||
this.eventDispatcher.addEventSubscriber({ |
||||
events: [ |
||||
'calculator.output_changed', 'calculator.memory_changed', 'calculator.layout_changed', |
||||
'editor.layout_created', 'editor.layout_removed', 'editor.layout_changed', 'editor.button_selected', |
||||
'keystroke_line.changed' |
||||
], |
||||
listener: function () { |
||||
if (null !== timer) { |
||||
clearTimeout(timer); |
||||
} |
||||
|
||||
timer = setTimeout(function() { |
||||
window.sankore.setPreference('state', JSON.stringify(self.getState())); |
||||
timer = null; |
||||
}, 350); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
this.init(state); |
||||
} |
||||
}); |
||||
</script> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,22 @@ |
||||
/*jshint browser:true, devel:true*/ |
||||
if (!('sankore' in window)) { |
||||
window.sankore = { |
||||
preferences: { |
||||
state: '' |
||||
}, |
||||
|
||||
setPreference: function (name, value) { |
||||
console.log('Preference "' + name + '" set to : ' + value); |
||||
this.preferences[name] = value; |
||||
}, |
||||
|
||||
preference: function (name) { |
||||
console.log('Accessing "' + name + '"'); |
||||
return this.preferences[name] || ''; |
||||
}, |
||||
|
||||
locale: function () { |
||||
return window.navigator.language; |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,35 @@ |
||||
/*global klass:true, Sankore:true*/ |
||||
(function() { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore', 'Button', klass.extend({ |
||||
constructor: function (text, command, useLimit, editable) { |
||||
this.text = text; |
||||
this.command = command; |
||||
|
||||
this.useLimit = typeof useLimit === 'undefined' ? -1 : useLimit; |
||||
this.editable = typeof editable === 'undefined' ? true : editable; |
||||
}, |
||||
|
||||
isEditable: function () { |
||||
return this.editable; |
||||
}, |
||||
|
||||
isUsable: function () { |
||||
return this.useLimit === -1; |
||||
}, |
||||
|
||||
isDisabled: function () { |
||||
return this.useLimit === 0; |
||||
}, |
||||
|
||||
clone: function () { |
||||
return Sankore.Button.create( |
||||
this.text, |
||||
this.command, |
||||
this.useLimit, |
||||
this.editable |
||||
); |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,155 @@ |
||||
/*jshint plusplus: true*/ |
||||
/*global klass: true, Sankore: true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Calculus', 'Engine', klass.extend({ |
||||
constructor: function () { |
||||
this.expressions = []; |
||||
this.operators = []; |
||||
}, |
||||
|
||||
init: function () { |
||||
this.expressions = []; |
||||
this.operators = []; |
||||
}, |
||||
|
||||
evaluate: function (expressionString) { |
||||
var tokens = [], |
||||
lastToken, |
||||
penultimateToken, |
||||
item; |
||||
|
||||
expressionString = expressionString.replace(/\s+/g, ''); |
||||
|
||||
for (var i in expressionString) { |
||||
item = expressionString[i]; |
||||
|
||||
if (tokens.length > 0) { |
||||
lastToken = tokens[tokens.length - 1]; |
||||
} else { |
||||
lastToken = undefined; |
||||
} |
||||
|
||||
if (tokens.length > 1) { |
||||
penultimateToken = tokens[tokens.length - 2]; |
||||
} else { |
||||
penultimateToken = undefined; |
||||
} |
||||
|
||||
if ('0123456789.'.indexOf(item) !== -1) { |
||||
if ( |
||||
!isNaN(Number(lastToken)) || |
||||
lastToken === '.' || |
||||
lastToken === '-.' || |
||||
( |
||||
lastToken === '-' &&
|
||||
( |
||||
penultimateToken === '(' ||
|
||||
penultimateToken === undefined |
||||
) |
||||
) |
||||
) { |
||||
tokens[tokens.length - 1] += item; |
||||
} else { |
||||
tokens.push(item); |
||||
} |
||||
} else { |
||||
tokens.push(item); |
||||
} |
||||
} |
||||
|
||||
for (var j in tokens) { |
||||
if (tokens[j].length > 1 && tokens[j].charAt(tokens[j].length - 1) === '.') { |
||||
throw Sankore.Util.Error.create('InvalidExpression', 'Trailing comma');
|
||||
} |
||||
} |
||||
|
||||
return this.computeExpression(tokens); |
||||
}, |
||||
|
||||
computeExpression: function (tokens) { |
||||
var operatorCheck = function (stack, token) { |
||||
var prec = Sankore.Calculus.BinaryOperation.getOperatorPrecedence, |
||||
top; |
||||
|
||||
while (true) { |
||||
top = stack.operators[stack.operators.length - 1]; |
||||
|
||||
if (stack.operators.length === 0 || top === '(' || prec(token) > prec(top)) { |
||||
return stack.operators.push(token); |
||||
} |
||||
|
||||
stack.reduce(); |
||||
} |
||||
}; |
||||
|
||||
this.init(); |
||||
|
||||
for (var i in tokens) { |
||||
switch (tokens[i]) { |
||||
case '(': |
||||
this.operators.push(tokens[i]); |
||||
break; |
||||
|
||||
case ')': |
||||
if (this.operators.length === 0) { |
||||
throw Sankore.Util.Error.create('InvalidExpression', 'Trailing closing brackets'); |
||||
} |
||||
|
||||
while (this.operators.length !== 0 && this.operators[this.operators.length - 1] !== '(') { |
||||
this.reduce(); |
||||
} |
||||
|
||||
if (this.operators[this.operators.length - 1] === '(') { |
||||
this.operators.pop(); // get rid of the extra paren '('
|
||||
} |
||||
|
||||
break; |
||||
|
||||
case '+': |
||||
case '-': |
||||
case '*': |
||||
case '/': |
||||
case ':': |
||||
operatorCheck(this, tokens[i]); |
||||
break; |
||||
|
||||
default: |
||||
this.expressions.push(Sankore.Calculus.Operand.create(tokens[i])); |
||||
} |
||||
} |
||||
|
||||
while (this.operators.length !== 0) { |
||||
this.reduce(); |
||||
} |
||||
|
||||
// if there's not one and only one expression in the stack, the expression is invalid
|
||||
if (this.expressions.length !== 1) { |
||||
throw Sankore.Util.Error.create('InvalidExpression', '"' + tokens.join(' ') + '" is not a valid expression'); |
||||
} |
||||
|
||||
return this.expressions.pop(); |
||||
}, |
||||
|
||||
reduce: function () { |
||||
var right = this.expressions.pop(), |
||||
left = this.expressions.pop(), |
||||
operator = this.operators.pop(), |
||||
operation; |
||||
|
||||
if (typeof operator === 'undefined' || typeof left === 'undefined' || typeof right === 'undefined') { |
||||
throw Sankore.Util.Error.create('InvalidExpression', 'Invalid expression'); |
||||
} |
||||
|
||||
if (operator === ':') { |
||||
operation = Sankore.Calculus.EuclideanDivisionOperation.create(left, right); |
||||
} else { |
||||
operation = Sankore.Calculus.BinaryOperation.create(left, operator, right); |
||||
} |
||||
|
||||
this.expressions.push(operation); |
||||
} |
||||
|
||||
})); |
||||
})(); |
@ -0,0 +1,217 @@ |
||||
/*global klass:true, Sankore:true */ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
/** |
||||
* Base class for expression |
||||
*/ |
||||
klass.define('Sankore.Calculus', 'Expression', klass.extend({ |
||||
constructor: function () { |
||||
|
||||
}, |
||||
|
||||
getValue: function () { |
||||
return null; |
||||
}, |
||||
|
||||
isInteger: function () { |
||||
try { |
||||
var value = this.getValue(); |
||||
|
||||
return value === Math.floor(value); |
||||
} catch (e) { |
||||
return 0; |
||||
} |
||||
}, |
||||
|
||||
toString: function () { |
||||
return ''; |
||||
}, |
||||
|
||||
isCompound: function () { |
||||
return false; |
||||
} |
||||
})); |
||||
|
||||
/** |
||||
* Calculus operand |
||||
*/ |
||||
klass.define('Sankore.Calculus', 'Operand', Sankore.Calculus.Expression.extend({ |
||||
constructor: function (value) { |
||||
this.value = Number(value); |
||||
|
||||
if (isNaN(this.value)) { |
||||
throw Sankore.Util.Error.create('InvalidNumber', '"' + String(value) + '" is a not a number'); |
||||
} |
||||
}, |
||||
|
||||
getValue: function () { |
||||
return this.value; |
||||
}, |
||||
|
||||
toString: function () { |
||||
return String(this.value); |
||||
} |
||||
})); |
||||
|
||||
/** |
||||
* Unary operator (+, -) |
||||
*/ |
||||
klass.define('Sankore.Calculus', 'Operation', Sankore.Calculus.Expression.extend({ |
||||
constructor: function (operator, right) { |
||||
this.operator = operator; |
||||
|
||||
if (!Sankore.Calculus.Expression.isPrototypeOf(right)) { |
||||
right = Sankore.Calculus.Operand.create(right); |
||||
} |
||||
|
||||
this.right = right; |
||||
}, |
||||
|
||||
getPrecedence: function () { |
||||
return Sankore.Calculus.Operation.getOperatorPrecedence(this.operator); |
||||
}, |
||||
|
||||
isLeftAssociative: function () { |
||||
return false; |
||||
}, |
||||
|
||||
isCompound: function () { |
||||
return true; |
||||
}, |
||||
|
||||
getValue: function () { |
||||
var value = Number(this.right.getValue()); |
||||
|
||||
if (this.operator === '-') { |
||||
value *= -1; |
||||
} |
||||
|
||||
return value; |
||||
}, |
||||
|
||||
toString: function () { |
||||
var string = this.right.toString(); |
||||
|
||||
if (this.operator !== '-') { |
||||
return string; |
||||
} |
||||
|
||||
if (this.right.isCompound()) { |
||||
string = '(' + string + ')'; |
||||
} |
||||
|
||||
return '-' + string; |
||||
} |
||||
})); |
||||
|
||||
Sankore.Calculus.Operation.getOperatorPrecedence = function (operator) { |
||||
switch (operator) { |
||||
case '+': |
||||
case '-': |
||||
return 1; |
||||
case '*': |
||||
case '/': |
||||
case ':': |
||||
return 2; |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Binary operator (+, -, *, /) |
||||
*/ |
||||
klass.define('Sankore.Calculus', 'BinaryOperation', Sankore.Calculus.Operation.extend({ |
||||
constructor: function (left, operator, right) { |
||||
Sankore.Calculus.Operation.constructor.call(this, operator, right); |
||||
|
||||
if (!Sankore.Calculus.Expression.isPrototypeOf(left)) { |
||||
left = Sankore.Calculus.Operand.create(left); |
||||
} |
||||
|
||||
this.left = left; |
||||
}, |
||||
|
||||
isLeftAssociative: function () { |
||||
return true; |
||||
}, |
||||
|
||||
getValue: function () { |
||||
var leftValue = this.left.getValue(), |
||||
rightValue = this.right.getValue(); |
||||
|
||||
switch (this.operator) { |
||||
case '+': |
||||
return leftValue + rightValue; |
||||
|
||||
case '-': |
||||
return leftValue - rightValue; |
||||
|
||||
case '*': |
||||
return leftValue * rightValue; |
||||
|
||||
case '/': |
||||
if (0 === rightValue) { |
||||
throw Sankore.Util.Error.create('ZeroDivision', 'Division by zero'); |
||||
} |
||||
|
||||
return leftValue / rightValue; |
||||
|
||||
default: |
||||
throw Sankore.Util.Error.create('InvalidOperator', 'This is not a valid operator : ' + this.operator); |
||||
} |
||||
}, |
||||
|
||||
toString: function () { |
||||
if (this.isInteger()) { |
||||
return String(this.getValue()); |
||||
} |
||||
|
||||
var leftString = this.left.toString(), |
||||
rightString = this.right.toString(), |
||||
string = ''; |
||||
|
||||
if (Sankore.Calculus.Operation.isPrototypeOf(this.left)) { |
||||
if (this.left.getPrecedence() < this.getPrecedence()) { |
||||
leftString = '(' + leftString + ')'; |
||||
} |
||||
} |
||||
|
||||
if (Sankore.Calculus.Operation.isPrototypeOf(this.right)) { |
||||
if (this.right.getPrecedence() < this.getPrecedence()) { |
||||
rightString = '(' + rightString + ')'; |
||||
} |
||||
} |
||||
|
||||
return leftString + String(this.operator) + rightString; |
||||
} |
||||
})); |
||||
|
||||
/** |
||||
* Euclidean division operator |
||||
*/ |
||||
klass.define('Sankore.Calculus', 'EuclideanDivisionOperation', Sankore.Calculus.BinaryOperation.extend({ |
||||
constructor: function (left, right) { |
||||
Sankore.Calculus.BinaryOperation.constructor.call(this, left, ':', right); |
||||
}, |
||||
|
||||
getValue: function () { |
||||
var rightValue = this.right.getValue(); |
||||
|
||||
if (0 === rightValue) { |
||||
throw Sankore.Util.Error.create('ZeroDivision', 'Division by zero'); |
||||
} |
||||
|
||||
return Math.floor(this.left.getValue() / rightValue); |
||||
}, |
||||
|
||||
getRemainder: function () { |
||||
var rightValue = this.right.getValue(); |
||||
|
||||
if (0 === rightValue) { |
||||
throw Sankore.Util.Error.create('ZeroDivision', 'Division by zero'); |
||||
} |
||||
|
||||
return this.left.getValue() % rightValue; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,52 @@ |
||||
/*global klass:true, Sankore:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore', 'Command', klass.extend({ |
||||
constructor: function (id, name, closure) { |
||||
this.id = id; |
||||
this.name = name; |
||||
this.closure = closure; |
||||
}, |
||||
|
||||
getId: function () { |
||||
return this.id; |
||||
}, |
||||
|
||||
getName: function () { |
||||
return this.name; |
||||
}, |
||||
|
||||
exec: function (scope, args) { |
||||
this.closure.call(scope, args); |
||||
}, |
||||
|
||||
isInterrupting: function () { |
||||
return false; |
||||
}, |
||||
|
||||
isInternal: function () { |
||||
return false; |
||||
} |
||||
})); |
||||
|
||||
klass.define('Sankore', 'InterruptingCommand', Sankore.Command.extend({ |
||||
constructor: function (id, name, closure) { |
||||
Sankore.Command.constructor.call(this, id, name, closure); |
||||
}, |
||||
|
||||
isInterrupting: function () { |
||||
return true; |
||||
} |
||||
})); |
||||
|
||||
klass.define('Sankore', 'InternalCommand', Sankore.Command.extend({ |
||||
constructor: function (id, name, closure) { |
||||
Sankore.Command.constructor.call(this, id, name, closure); |
||||
}, |
||||
|
||||
isInternal: function () { |
||||
return true; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,341 @@ |
||||
/*jshint browser:true, devel:true */ |
||||
/*global klass:true, Sankore:true, _:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Editor', 'Editor', klass.extend({ |
||||
constructor: function (calculator) { |
||||
|
||||
// state attributes
|
||||
this.current = null; |
||||
this.activeButton = null; |
||||
this.enabled = false; |
||||
this.layouts = Sankore.Util.Hash.create({ |
||||
'default': Sankore.Editor.Layout.create({ |
||||
id: 'default',
|
||||
name: _('layout.classic_name'), |
||||
buttonMap: { |
||||
a1: Sankore.Button.create('mr', 'memoryRecall'), |
||||
b1: Sankore.Button.create('mc', 'memoryClear'), |
||||
c1: Sankore.Button.create('m+', 'memoryAdd'), |
||||
d1: Sankore.Button.create('m-', 'memorySub'), |
||||
|
||||
a2: Sankore.Button.create('op', 'op'), |
||||
b2: Sankore.Button.create('(', '('), |
||||
c2: Sankore.Button.create(')', ')'), |
||||
d2: Sankore.Button.create(':', ':'), |
||||
|
||||
a3: Sankore.Button.create('7', '7'), |
||||
b3: Sankore.Button.create('8', '8'), |
||||
c3: Sankore.Button.create('9', '9'), |
||||
d3: Sankore.Button.create('/', '/'), |
||||
|
||||
a4: Sankore.Button.create('4', '4'), |
||||
b4: Sankore.Button.create('5', '5'), |
||||
c4: Sankore.Button.create('6', '6'), |
||||
d4: Sankore.Button.create('*', '*'), |
||||
|
||||
a5: Sankore.Button.create('1', '1'), |
||||
b5: Sankore.Button.create('2', '2'), |
||||
c5: Sankore.Button.create('3', '3'), |
||||
d5: Sankore.Button.create('-', '-'), |
||||
|
||||
a6: Sankore.Button.create('0', '0'), |
||||
b6: Sankore.Button.create('.', '.'), |
||||
c6: Sankore.Button.create('=', '='), |
||||
d6: Sankore.Button.create('+', '+') |
||||
} |
||||
}) |
||||
}); |
||||
|
||||
// components
|
||||
this.calculator = calculator; |
||||
this.ui = Sankore.UI.EditorInterface.create(this, this.calculator.eventDispatcher); |
||||
|
||||
this.attachEventHandlers(); |
||||
}, |
||||
|
||||
attachEventHandlers: function () { |
||||
var self = this, ed = this.calculator.eventDispatcher; |
||||
|
||||
// click on Add button
|
||||
ed.addEventListener('editor_interface.add_click', function () { |
||||
var clone = self.createLayout(); |
||||
|
||||
self.setCurrentLayout(clone.id); |
||||
}); |
||||
|
||||
// click on Remove button
|
||||
ed.addEventListener('editor_interface.remove_click', function () { |
||||
self.removeLayout(self.current); |
||||
|
||||
self.setCurrentLayout('default'); |
||||
}); |
||||
|
||||
// click on Run button
|
||||
ed.addEventListener('editor_interface.run_click', this.runCurrentLayout.bind(this)); |
||||
|
||||
// load the new selected layout
|
||||
ed.addEventListener('editor_interface.layout_select', function (layoutId) { |
||||
self.setCurrentLayout(layoutId); |
||||
}); |
||||
|
||||
// the layout name has changed
|
||||
ed.addEventListener('editor_interface.layout_name_change', function (name) { |
||||
if (self.getCurrentLayout().name !== name && name.trim().length > 0) { |
||||
self.getCurrentLayout().name = name; |
||||
ed.notify('editor.layout_changed'); |
||||
} |
||||
}); |
||||
|
||||
// the layout description has changed
|
||||
ed.addEventListener('editor_interface.layout_description_change', function (description) { |
||||
if (self.getCurrentLayout().description !== description) { |
||||
self.getCurrentLayout().description = description; |
||||
ed.notify('editor.layout_changed'); |
||||
} |
||||
}); |
||||
|
||||
// the command of a button has changed
|
||||
ed.addEventListener('editor_interface.button_command_change', function (command) { |
||||
if (self.activeButton) { |
||||
self.getCurrentLayout().getButton(self.activeButton).command = command; |
||||
ed.notify('editor.layout_changed'); |
||||
} |
||||
}); |
||||
|
||||
// the text of a button has changed
|
||||
ed.addEventListener('editor_interface.button_text_change', function (text) { |
||||
if (self.activeButton) { |
||||
var button = self.getCurrentLayout().getButton(self.activeButton); |
||||
button.text = text; |
||||
|
||||
ed.notify('editor.button_renamed', { |
||||
slot: self.activeButton, |
||||
button: button |
||||
}); |
||||
|
||||
ed.notify('editor.layout_changed'); |
||||
} |
||||
}); |
||||
|
||||
// the use limit of a button has changed
|
||||
ed.addEventListener('editor_interface.button_uselimit_change', function (limit) { |
||||
if (self.activeButton) { |
||||
if (!isNaN(Number(limit))) { |
||||
self.getCurrentLayout().getButton(self.activeButton).useLimit = limit.length === 0 ? -1 : Number(limit); |
||||
ed.notify('editor.layout_changed'); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// a button is clicked
|
||||
ed.addEventListener('main_interface.button_click', function (event) { |
||||
if (self.enabled) { |
||||
self.setActiveButton(event.slot); |
||||
} |
||||
}); |
||||
|
||||
// the editor button is click
|
||||
ed.addEventListener('main_interface.editor_click', function () { |
||||
if (self.enabled) { |
||||
self.runCurrentLayout(); |
||||
} else { |
||||
self.enable(); |
||||
} |
||||
}); |
||||
|
||||
// the editor button is click
|
||||
ed.addEventListener('main_interface.reset_click', function () { |
||||
if (self.enabled) { |
||||
self.resetActiveButton(); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
init: function (state) { |
||||
var self = this; |
||||
|
||||
if ('layouts' in state) { |
||||
this.loadLayouts(state.layouts); |
||||
} |
||||
|
||||
if ('enabled' in state) { |
||||
this.enabled = state.enabled; |
||||
} |
||||
|
||||
this.ui.render(this.calculator.ui); |
||||
|
||||
this.setCurrentLayout(('current' in state && state.current) ? state.current : 'default'); |
||||
|
||||
if (this.enabled) { |
||||
this.enable(); |
||||
} |
||||
|
||||
if ('activeButton' in state && this.enabled) { |
||||
this.setActiveButton(state.activeButton); |
||||
} |
||||
}, |
||||
|
||||
getState: function () { |
||||
return { |
||||
current: this.current, |
||||
activeButton: this.activeButton, |
||||
enabled: this.enabled, |
||||
layouts: this.layouts.map(function (id, layout) { |
||||
if (layout.isEditable()) { |
||||
return layout; |
||||
} |
||||
}) |
||||
}; |
||||
}, |
||||
|
||||
loadLayouts: function (layouts) { |
||||
var buttonMap = {}, |
||||
layout; |
||||
|
||||
for (var i in layouts) { |
||||
for (var slot in layouts[i].buttonMap) { |
||||
if (layouts[i].buttonMap.hasOwnProperty(slot)) { |
||||
buttonMap[slot] = Sankore.Button.create( |
||||
layouts[i].buttonMap[slot].text,
|
||||
layouts[i].buttonMap[slot].command,
|
||||
layouts[i].buttonMap[slot].useLimit |
||||
); |
||||
} |
||||
} |
||||
|
||||
layout = Sankore.Editor.Layout.create({ |
||||
id: layouts[i].id, |
||||
name: layouts[i].name, |
||||
description: layouts[i].description, |
||||
buttonMap: buttonMap |
||||
}); |
||||
|
||||
layout.setEditable(true); |
||||
|
||||
this.layouts.add('id', [layout]); |
||||
} |
||||
}, |
||||
|
||||
getCurrentLayout: function () { |
||||
if (null === this.current) { |
||||
return null; |
||||
} |
||||
|
||||
return this.layouts.get(this.current); |
||||
}, |
||||
|
||||
setCurrentLayout: function (id) { |
||||
this.current = id; |
||||
|
||||
this.calculator.eventDispatcher.notify('editor.layout_selected', this.getCurrentLayout()); |
||||
|
||||
this.resetActiveButton(); |
||||
}, |
||||
|
||||
createLayout: function () { |
||||
var clone = this.layouts.get('default').clone(); |
||||
|
||||
clone.id = this.generateId(); |
||||
clone.name = _('layout.new_name'); |
||||
clone.setEditable(true); |
||||
|
||||
this.layouts.set(clone.id, clone); |
||||
this.calculator.eventDispatcher.notify('editor.layout_created'); |
||||
|
||||
return clone; |
||||
}, |
||||
|
||||
/** |
||||
* don't try to understand the purpose of this method, it just generates an unique string based upon the current widget url and current time |
||||
*/ |
||||
generateId: function () { |
||||
var values = '', |
||||
date = new Date(), |
||||
id = 0, |
||||
i; |
||||
|
||||
for (i = 0; i < document.URL.length; i++) { |
||||
values += String(document.URL.charCodeAt(i) * (date.getMilliseconds() + date.getSeconds() + date.getMinutes())); |
||||
} |
||||
|
||||
values = values.match(/.{1,10}/g); |
||||
|
||||
for (i in values) { |
||||
id += Number(values[i]); |
||||
} |
||||
|
||||
return id.toString(36); |
||||
}, |
||||
|
||||
removeLayout: function (id) { |
||||
if (confirm(_('editor.remove_alert'))) { |
||||
this.layouts.remove(id); |
||||
|
||||
this.calculator.eventDispatcher.notify('editor.layout_removed'); |
||||
} |
||||
}, |
||||
|
||||
setActiveButton: function (slot) { |
||||
if (slot && this.getCurrentLayout().isEditable()) { |
||||
var button = this.getCurrentLayout().getButton(slot); |
||||
|
||||
if (button.isEditable()) { |
||||
this.calculator.eventDispatcher.notify('editor.button_selected', { |
||||
slot: slot, |
||||
button: button, |
||||
previousSlot: this.activeButton |
||||
}); |
||||
|
||||
this.activeButton = slot; |
||||
} |
||||
} else { |
||||
this.resetActiveButton(); |
||||
} |
||||
}, |
||||
|
||||
resetActiveButton: function () { |
||||
this.calculator.eventDispatcher.notify('editor.button_selected', { |
||||
slot: null, |
||||
button: null, |
||||
previousSlot: this.activeButton |
||||
}); |
||||
|
||||
this.activeButton = null; |
||||
}, |
||||
|
||||
enable: function () { |
||||
this.enabled = true; |
||||
this.setActiveButton(null); |
||||
this.calculator.eventDispatcher.notify('editor.show'); |
||||
}, |
||||
|
||||
disable: function () { |
||||
this.enabled = false; |
||||
this.setActiveButton(null); |
||||
this.calculator.eventDispatcher.notify('editor.hide'); |
||||
}, |
||||
|
||||
getAssignableCommands: function () { |
||||
return this.calculator.commands.map(function (k, v) { |
||||
if (!v.isInternal()) { |
||||
return v; |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
getAssignableTexts: function () { |
||||
return this.calculator.texts.map(function (k, v) { |
||||
if (v.isEditable()) { |
||||
return v; |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
runCurrentLayout: function () { |
||||
this.disable(); |
||||
} |
||||
|
||||
})); |
||||
})(); |
@ -0,0 +1,44 @@ |
||||
/*global klass:true, Sankore:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Editor', 'Layout', klass.extend({ |
||||
constructor: function (data) { |
||||
this.id = data.id || null; |
||||
this.name = data.name || null; |
||||
this.description = data.description || null; |
||||
this.editable = false; |
||||
this.buttonMap = data.buttonMap || {}; |
||||
}, |
||||
|
||||
setEditable: function (editable) { |
||||
this.editable = !! editable; |
||||
}, |
||||
|
||||
isEditable: function () { |
||||
return this.editable; |
||||
}, |
||||
|
||||
getButton: function (slot) { |
||||
return this.buttonMap[slot] || null; |
||||
}, |
||||
|
||||
clone: function () { |
||||
var clonedMap = {}; |
||||
|
||||
for (var index in this.buttonMap) { |
||||
if (this.buttonMap.hasOwnProperty(index)) { |
||||
clonedMap[index] = this.buttonMap[index].clone(); |
||||
} |
||||
} |
||||
|
||||
return Sankore.Editor.Layout.create({ |
||||
id: this.id, |
||||
name: this.name, |
||||
description: this.description, |
||||
editable: this.editable, |
||||
buttonMap: clonedMap |
||||
}); |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,109 @@ |
||||
/*global klass:true, Sankore:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore', 'KeystrokeLine', klass.extend({ |
||||
constructor: function (dispatcher) { |
||||
this.dispatcher = dispatcher; |
||||
|
||||
this.keystrokes = []; |
||||
this.caret = 0; |
||||
}, |
||||
|
||||
notify: function () { |
||||
this.dispatcher.notify('keystroke_line.changed', this); |
||||
}, |
||||
|
||||
hit: function (keystroke) { |
||||
this.keystrokes.splice(this.caret, 0, keystroke); |
||||
this.caret++; |
||||
this.notify(); |
||||
}, |
||||
|
||||
del: function () { |
||||
if (this.caret > 0) { |
||||
var deleted = this.keystrokes.splice(this.caret - 1, 1)[0]; |
||||
this.caret--; |
||||
this.notify(); |
||||
|
||||
return deleted; |
||||
} |
||||
}, |
||||
|
||||
moveCaretLeft: function () { |
||||
if (this.caret > 0) { |
||||
this.caret--; |
||||
this.notify(); |
||||
} |
||||
}, |
||||
|
||||
moveCaretRight: function () { |
||||
if (this.caret < this.keystrokes.length) { |
||||
this.caret++; |
||||
this.notify(); |
||||
} |
||||
}, |
||||
|
||||
moveCaretToEnd: function () { |
||||
this.caret = this.keystrokes.length; |
||||
this.notify(); |
||||
}, |
||||
|
||||
reset: function () { |
||||
this.caret = 0; |
||||
this.keystrokes = []; |
||||
this.notify(); |
||||
}, |
||||
|
||||
count: function () { |
||||
return this.keystrokes.length; |
||||
}, |
||||
|
||||
at: function (index) { |
||||
if (typeof this.keystrokes[index] !== 'undefined') { |
||||
return this.keystrokes[index]; |
||||
} |
||||
|
||||
throw Sankore.Util.Error.create('OutOfRangeError', 'No keystroke at index ' + index); |
||||
}, |
||||
|
||||
getAsText: function () { |
||||
return [ |
||||
this.getTextAtRange(0, this.caret), |
||||
this.getTextAtRange(this.caret, this.keystrokes.length) |
||||
]; |
||||
}, |
||||
|
||||
getTextAtRange: function (from, to) { |
||||
var i, output = ''; |
||||
|
||||
if (from < 0) { |
||||
throw Sankore.Util.Error.create('OutOfRangeError', 'Cannot get keystroke before index 0'); |
||||
} |
||||
|
||||
if (from > this.keystrokes.length) { |
||||
throw Sankore.Util.Error.create('OutOfRangeError', 'Cannot get keystroke after index ' + this.keystrokes.length); |
||||
} |
||||
|
||||
for (i = from; i < to; i++) { |
||||
output += this.at(i).text; |
||||
} |
||||
|
||||
return output; |
||||
}, |
||||
|
||||
getState: function () { |
||||
return { |
||||
keystrokes: this.keystrokes, |
||||
caret: this.caret |
||||
}; |
||||
}, |
||||
|
||||
loadState: function (state) { |
||||
this.keystrokes = state.keystrokes || {}; |
||||
this.caret = state.caret || 0; |
||||
this.notify(); |
||||
} |
||||
|
||||
})); |
||||
})(); |
@ -0,0 +1,23 @@ |
||||
/*globals klass:true, Sankore:true*/ |
||||
(function() { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore', 'Text', klass.extend({ |
||||
constructor: function (id, screen, button, type, editable) { |
||||
this.id = id; |
||||
this.screen = screen; |
||||
this.button = button; |
||||
|
||||
this.type = typeof type !== 'undefined' ? type : 'normal'; |
||||
this.editable = typeof editable !== 'undefined' ? !!editable : true; |
||||
}, |
||||
|
||||
isEditable: function () { |
||||
return this.editable; |
||||
}, |
||||
|
||||
setEditable: function (editable) { |
||||
this.editable = !!editable; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,323 @@ |
||||
/*jshint browser:true*/ |
||||
/*global klass:true, Sankore: true, _:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.UI', 'EditorInterface', klass.extend({ |
||||
constructor: function (editor, ed) { |
||||
this.editor = editor; |
||||
this.dispatcher = ed; |
||||
this.hidden = true; |
||||
|
||||
this.editArea = null; |
||||
this.layoutSelect = null; |
||||
this.layoutNameInput = null; |
||||
this.layoutDescriptionInput = null; |
||||
this.assignationDiv = null; |
||||
this.runButton = null; |
||||
this.addButton = null; |
||||
this.removeButton = null; |
||||
|
||||
this.attachEventListeners(); |
||||
|
||||
this.rendered = false; |
||||
}, |
||||
|
||||
attachEventListeners: function () { |
||||
var self = this; |
||||
|
||||
this.dispatcher.addEventListener('editor.show', this.show.bind(this)); |
||||
this.dispatcher.addEventListener('editor.hide', this.hide.bind(this)); |
||||
|
||||
this.dispatcher.addEventSubscriber({ |
||||
events: ['editor.layout_changed', 'editor.layout_created', 'editor.layout_removed'], |
||||
listener: this.updateLayoutSelectElement.bind(this) |
||||
}); |
||||
|
||||
this.dispatcher.addEventListener('editor.layout_selected', function (layout) { |
||||
self.loadLayout(layout); |
||||
|
||||
self.selectLayout(layout.id); |
||||
}); |
||||
|
||||
this.dispatcher.addEventListener('editor.button_selected', function (e) { |
||||
var context = e.button; |
||||
|
||||
if (null === context && self.editor.getCurrentLayout()) { |
||||
context = self.editor.getCurrentLayout().isEditable(); |
||||
} |
||||
|
||||
self.renderAssignation(context); |
||||
}); |
||||
}, |
||||
|
||||
_clearElement: function (element) { |
||||
while (element.firstChild) { |
||||
element.removeChild(element.firstChild); |
||||
} |
||||
}, |
||||
|
||||
_map: function (iterable, callback) { |
||||
var mapped = [], |
||||
idx; |
||||
|
||||
for (idx in iterable) { |
||||
if (iterable.hasOwnProperty(idx)) { |
||||
mapped.push(callback.call(iterable, iterable[idx], idx)); |
||||
} |
||||
} |
||||
|
||||
return mapped; |
||||
}, |
||||
|
||||
render: function (mainInterface) { |
||||
var layoutNameLabel, |
||||
layoutDescriptionLabel, |
||||
assignationDiv, |
||||
assignationText, |
||||
self = this; |
||||
|
||||
this.editArea = document.createElement('div'); |
||||
this.editArea.classList.add('edit-area'); |
||||
|
||||
this.layoutSelect = this.createLayoutSelectElement(); |
||||
this.editArea.appendChild(this.layoutSelect); |
||||
|
||||
this.addButton = document.createElement('button'); |
||||
this.addButton.className = 'small'; |
||||
this.addButton.setAttribute('type', 'button'); |
||||
this.addButton.addEventListener('click', function (e) { |
||||
self.dispatcher.notify('editor_interface.add_click'); |
||||
}); |
||||
|
||||
this.removeButton = this.addButton.cloneNode(); |
||||
this.removeButton.classList.add('remove'); |
||||
this.removeButton.addEventListener('click', function (e) { |
||||
self.dispatcher.notify('editor_interface.remove_click'); |
||||
}); |
||||
|
||||
this.addButton.classList.add('add'); |
||||
|
||||
this.editArea.appendChild(this.addButton); |
||||
this.editArea.appendChild(this.removeButton); |
||||
|
||||
this.editArea.appendChild(document.createElement('hr')); |
||||
|
||||
layoutNameLabel = document.createElement('label'); |
||||
layoutNameLabel.appendChild(document.createTextNode(_('editor.layout_name.label'))); |
||||
this.editArea.appendChild(layoutNameLabel); |
||||
|
||||
this.layoutNameInput = document.createElement('input'); |
||||
this.layoutNameInput.setAttribute('name', 'layout_name'); |
||||
this.layoutNameInput.setAttribute('type', 'text'); |
||||
this.layoutNameInput.setAttribute('maxlength', 32); |
||||
this.layoutNameInput.addEventListener('keyup', function (e) { |
||||
self.dispatcher.notify('editor_interface.layout_name_change', this.value); |
||||
}); |
||||
this.layoutNameInput.addEventListener('change', function (e) { |
||||
if (this.value.trim().length === 0) { |
||||
this.value = self.editor.getCurrentLayout().name; |
||||
self.dispatcher.notify('editor_interface.layout_name_change', this.value); |
||||
} |
||||
}); |
||||
this.editArea.appendChild(this.layoutNameInput); |
||||
|
||||
layoutDescriptionLabel = document.createElement('label'); |
||||
layoutDescriptionLabel.appendChild(document.createTextNode(_('editor.layout_description.label'))); |
||||
this.editArea.appendChild(layoutDescriptionLabel); |
||||
|
||||
this.layoutDescriptionInput = document.createElement('textarea'); |
||||
this.layoutDescriptionInput.setAttribute('name', 'layout_description'); |
||||
this.layoutDescriptionInput.setAttribute('maxlength', 140); |
||||
this.layoutDescriptionInput.addEventListener('keyup', function(e) { |
||||
self.dispatcher.notify('editor_interface.layout_description_change', this.value); |
||||
}); |
||||
this.editArea.appendChild(this.layoutDescriptionInput); |
||||
|
||||
this.assignationDiv = document.createElement('div'); |
||||
this.assignationDiv.classList.add('assignation'); |
||||
|
||||
this.editArea.appendChild(this.assignationDiv); |
||||
|
||||
this.runButton = document.createElement('button'); |
||||
this.runButton.classList.add('run'); |
||||
this.runButton.setAttribute('type', 'button'); |
||||
this.runButton.appendChild(document.createTextNode(_('editor.run_button'))); |
||||
this.runButton.addEventListener('click', function (e) { |
||||
self.dispatcher.notify('editor_interface.run_click'); |
||||
}); |
||||
|
||||
this.editArea.appendChild(this.runButton); |
||||
|
||||
mainInterface.rearScreen.parentElement.appendChild(this.editArea); |
||||
|
||||
this.rendered = true; |
||||
|
||||
this.hide(); |
||||
}, |
||||
|
||||
createSelectElement: function (data, name, className, selectedValue) { |
||||
var select = document.createElement('select'), |
||||
option; |
||||
|
||||
select.setAttribute('name', name); |
||||
|
||||
if (className) { |
||||
select.className = className; |
||||
} |
||||
|
||||
for (var i in data) { |
||||
option = document.createElement('option'); |
||||
option.setAttribute('value', data[i].value); |
||||
|
||||
if (typeof selectedValue !== 'undefined' && selectedValue === data[i].value) { |
||||
option.selected = true; |
||||
} |
||||
|
||||
option.appendChild(document.createTextNode(data[i].text)); |
||||
|
||||
select.appendChild(option); |
||||
} |
||||
|
||||
return select; |
||||
}, |
||||
|
||||
createLayoutSelectElement: function () { |
||||
var select = this.createSelectElement( |
||||
this.editor.layouts.map(function (k, layout) { |
||||
return { |
||||
text: layout.name, |
||||
value: layout.id |
||||
}; |
||||
}),
|
||||
'layouts',
|
||||
'layout-select',
|
||||
this.editor.current |
||||
), |
||||
self = this; |
||||
|
||||
select.addEventListener('change', function (e) { |
||||
self.dispatcher.notify('editor_interface.layout_select', this.value); |
||||
}); |
||||
|
||||
return select; |
||||
}, |
||||
|
||||
updateLayoutSelectElement: function () { |
||||
var select = this.createLayoutSelectElement(); |
||||
|
||||
this.editArea.replaceChild(select, this.layoutSelect); |
||||
|
||||
this.layoutSelect = select; |
||||
}, |
||||
|
||||
selectLayout: function (selected) { |
||||
this.layoutSelect.value = selected; |
||||
}, |
||||
|
||||
show: function () { |
||||
if (this.rendered) { |
||||
this.editArea.style.display = 'block'; |
||||
this.hidden = false; |
||||
} |
||||
}, |
||||
|
||||
hide: function () { |
||||
if (this.rendered) { |
||||
this.editArea.style.display = 'none'; |
||||
this.hidden = true; |
||||
} |
||||
}, |
||||
|
||||
loadLayout: function (layout) { |
||||
this.layoutNameInput.value = layout.name; |
||||
this.layoutNameInput.disabled = !layout.isEditable(); |
||||
|
||||
this.layoutDescriptionInput.value = layout.description; |
||||
this.layoutDescriptionInput.disabled = !layout.isEditable(); |
||||
|
||||
this.removeButton.disabled = !layout.isEditable(); |
||||
|
||||
this.renderAssignation(layout.isEditable()); |
||||
}, |
||||
|
||||
renderAssignation: function (context) { |
||||
var innerEl, textLabel, textSelect, commandLabel, commandSelect, useLabel, useInput, useHelp, |
||||
text, |
||||
self = this; |
||||
|
||||
if (false === context) { |
||||
innerEl = document.createElement('em'); |
||||
innerEl.appendChild(document.createTextNode(_('editor.assignation.disabled'))); |
||||
} else if (Sankore.Button.isPrototypeOf(context)) { |
||||
innerEl = document.createDocumentFragment(); |
||||
|
||||
textLabel = document.createElement('label'); |
||||
textLabel.appendChild(document.createTextNode(_('editor.assignation.text.label'))); |
||||
innerEl.appendChild(textLabel); |
||||
|
||||
text = this.editor.calculator.texts.get(context.text); |
||||
|
||||
textSelect = this.createSelectElement( |
||||
this._map(this.editor.getAssignableTexts(), function (text) { |
||||
return { |
||||
text: text.button, |
||||
value: text.id |
||||
};
|
||||
}), |
||||
'button_text',
|
||||
'', |
||||
text.id |
||||
); |
||||
textSelect.addEventListener('change', function (e) { |
||||
self.dispatcher.notify('editor_interface.button_text_change', e.target.value); |
||||
}); |
||||
innerEl.appendChild(textSelect); |
||||
|
||||
commandLabel = document.createElement('label'); |
||||
commandLabel.appendChild(document.createTextNode(_('editor.assignation.command.label'))); |
||||
innerEl.appendChild(commandLabel); |
||||
|
||||
commandSelect = this.createSelectElement( |
||||
this._map(this.editor.getAssignableCommands(), function (command) { |
||||
return { |
||||
text: command.name, |
||||
value: command.id |
||||
}; |
||||
}), |
||||
'button_command', |
||||
'', |
||||
context.command |
||||
); |
||||
commandSelect.addEventListener('change', function (e) { |
||||
self.dispatcher.notify('editor_interface.button_command_change', e.target.value); |
||||
}); |
||||
innerEl.appendChild(commandSelect); |
||||
|
||||
useLabel = document.createElement('label'); |
||||
useLabel.appendChild(document.createTextNode(_('editor.assignation.use_limit.label'))); |
||||
innerEl.appendChild(useLabel); |
||||
|
||||
useInput = document.createElement('input'); |
||||
useInput.setAttribute('type', 'text'); |
||||
useInput.setAttribute('name', 'button_count'); |
||||
useInput.value = (context.useLimit === -1 ? '' : context.useLimit); |
||||
useInput.addEventListener('change', function (e) { |
||||
self.dispatcher.notify('editor_interface.button_uselimit_change', e.target.value); |
||||
}); |
||||
innerEl.appendChild(useInput); |
||||
|
||||
useHelp = document.createElement('span'); |
||||
useHelp.className = 'help'; |
||||
useHelp.appendChild(document.createTextNode(_('editor.assignation.use_limit.help'))); |
||||
innerEl.appendChild(useHelp); |
||||
} else { |
||||
innerEl = document.createElement('em'); |
||||
innerEl.appendChild(document.createTextNode(_('editor.assignation.enabled'))); |
||||
} |
||||
|
||||
this._clearElement(this.assignationDiv); |
||||
this.assignationDiv.appendChild(innerEl); |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,534 @@ |
||||
/*jshint browser:true */ |
||||
/*global klass:true, Sankore:true, _:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.UI', 'MainInterface', klass.extend({ |
||||
constructor: function (id, ed, texts, withEditor) { |
||||
this.id = id; |
||||
this.dispatcher = ed; |
||||
this.texts = texts; |
||||
this.withEditor = withEditor; |
||||
|
||||
this.title = null; |
||||
|
||||
this.caret = document.createElement('i'); |
||||
this.caret.className = 'caret'; |
||||
|
||||
this.frontScreen = null; |
||||
this.expressionRow = null; |
||||
this.resultRow = null; |
||||
this.flagRow = null; |
||||
|
||||
this.flags = []; |
||||
|
||||
this.rearScreen = null; |
||||
|
||||
this.buttons = Sankore.Util.Hash.create(); |
||||
|
||||
this.rendered = false; |
||||
|
||||
this.attachEventListeners(); |
||||
}, |
||||
|
||||
attachEventListeners: function () { |
||||
var self = this; |
||||
|
||||
// the main screen text has changed
|
||||
this.dispatcher.addEventListener('keystroke_line.changed', function (keystrokeLine) { |
||||
self.updateExpressionRow(keystrokeLine.getAsText()); |
||||
}); |
||||
|
||||
// the internal memory has changed
|
||||
this.dispatcher.addEventListener('calculator.memory_changed', function (memory) { |
||||
if (null !== memory) { |
||||
self.addFlag('M'); |
||||
} else { |
||||
self.removeFlag('M'); |
||||
} |
||||
|
||||
self.updateFlagRow(); |
||||
}); |
||||
|
||||
// the op memory has changed
|
||||
this.dispatcher.addEventListener('calculator.op_changed', function (op) { |
||||
if (null !== op) { |
||||
self.addFlag('OP'); |
||||
} else { |
||||
self.removeFlag('OP'); |
||||
} |
||||
|
||||
self.updateFlagRow(); |
||||
}); |
||||
|
||||
// a new result has been computed
|
||||
this.dispatcher.addEventListener('calculator.output_changed', function (event) { |
||||
if (null !== event.output) { |
||||
self.updateResultRow(event.output); |
||||
} else if (null !== event.error) { |
||||
self.updateResultRow(event.error); |
||||
} else { |
||||
self.updateResultRow(''); |
||||
} |
||||
}); |
||||
|
||||
// the history has changed
|
||||
this.dispatcher.addEventListener('calculator.history_changed', this.updateRearScreen.bind(this)); |
||||
|
||||
// a new layout is loaded
|
||||
this.dispatcher.addEventListener('calculator.layout_loaded', function (layout) { |
||||
self.renderButtons(layout.buttonMap); |
||||
|
||||
self.changeTitle(layout.name); |
||||
|
||||
self.clearRearScreen(); |
||||
}); |
||||
|
||||
// a button has been disabled
|
||||
this.dispatcher.addEventListener('calculator.button_disabled', function (event) { |
||||
var buttonEl = self.buttons.get(event.slot); |
||||
|
||||
if (buttonEl) { |
||||
buttonEl.disabled = true; |
||||
} |
||||
}); |
||||
|
||||
// a button has been enabled
|
||||
this.dispatcher.addEventListener('calculator.button_enabled', function (slot) { |
||||
var buttonEl = self.buttons.get(slot); |
||||
|
||||
if (buttonEl) { |
||||
buttonEl.disabled = false; |
||||
} |
||||
}); |
||||
|
||||
// the editor is shown/hidden
|
||||
this.dispatcher.addEventListener('editor.show', this.hideRearScreen.bind(this)); |
||||
this.dispatcher.addEventListener('editor.hide', this.showRearScreen.bind(this)); |
||||
|
||||
// a button has been selected
|
||||
this.dispatcher.addEventListener('editor.button_selected', function (event) { |
||||
var newButtonEl = self.buttons.get(event.slot), |
||||
oldButtonEl = self.buttons.get(event.previousSlot); |
||||
|
||||
if (oldButtonEl) { |
||||
oldButtonEl.parentElement.classList.remove('edit'); |
||||
} |
||||
|
||||
if (newButtonEl) { |
||||
newButtonEl.parentElement.classList.add('edit'); |
||||
} |
||||
}); |
||||
|
||||
// a button has been renamed
|
||||
this.dispatcher.addEventListener('editor.button_renamed', function (event) { |
||||
var buttonEl = self.buttons.get(event.slot), |
||||
text = self.texts.get(event.button.text), |
||||
hasEditClass; |
||||
|
||||
if (buttonEl) { |
||||
buttonEl.innerText = text.button; |
||||
|
||||
hasEditClass = buttonEl.parentElement.classList.contains('edit'); |
||||
buttonEl.parentElement.className = text.type; |
||||
|
||||
if (hasEditClass) { |
||||
buttonEl.parentElement.classList.add('edit'); |
||||
} |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
_clearElement: function (element) { |
||||
while (element.firstChild) { |
||||
element.removeChild(element.firstChild); |
||||
} |
||||
}, |
||||
|
||||
getRootElement: function () { |
||||
return document.getElementById(this.id); |
||||
}, |
||||
|
||||
render: function () { |
||||
var root = this.getRootElement(); |
||||
|
||||
this._clearElement(root); |
||||
|
||||
if (!root.classList.contains('calculator')) { |
||||
root.classList.add('calculator'); |
||||
} |
||||
|
||||
this.createBaseMarkup(root); |
||||
|
||||
this.rearScreen = document.createElement('ul'); |
||||
root |
||||
.getElementsByClassName('rear-screen') |
||||
.item(0) |
||||
.appendChild(this.rearScreen); |
||||
|
||||
this.frontScreen = document.createElement('ul'); |
||||
root |
||||
.getElementsByClassName('front-screen') |
||||
.item(0) |
||||
.appendChild(this.frontScreen); |
||||
|
||||
this.expressionRow = document.createElement('li'); |
||||
this.expressionRow.classList.add('expression-row'); |
||||
this.frontScreen.appendChild(this.expressionRow); |
||||
|
||||
this.flagRow = document.createElement('li'); |
||||
this.flagRow.classList.add('flag-row'); |
||||
this.frontScreen.appendChild(this.flagRow); |
||||
|
||||
this.resultRow = document.createElement('li'); |
||||
this.resultRow.classList.add('result-row'); |
||||
this.frontScreen.appendChild(this.resultRow); |
||||
|
||||
this.rendered = true; |
||||
}, |
||||
|
||||
renderButtons: function (buttonMap) { |
||||
var buttonsTrs, |
||||
map = {}, |
||||
buttons = document.createDocumentFragment(), |
||||
rootElement = this |
||||
.getRootElement() |
||||
.getElementsByClassName('screen') |
||||
.item(0) |
||||
.parentElement; |
||||
|
||||
// map copy in order to prevent alteration of the original map
|
||||
for (var slot in buttonMap) { |
||||
map[slot] = buttonMap[slot]; |
||||
} |
||||
|
||||
// adding constant non-editable buttons
|
||||
map.a0 = Sankore.Button.create('s', 'del', -1, false); |
||||
map.b0 = Sankore.Button.create('l', 'left', -1, false); |
||||
map.c0 = Sankore.Button.create('r', 'right', -1, false); |
||||
map.d0 = Sankore.Button.create('c', 'clear', -1, false); |
||||
|
||||
buttonsTrs = rootElement.getElementsByClassName('buttons'); |
||||
while (buttonsTrs.length > 0) { |
||||
rootElement.removeChild(buttonsTrs[0]); |
||||
} |
||||
|
||||
buttonsTrs = this.createButtons(map); |
||||
|
||||
// we use a DocumentFragment to avoid excessive and useless repaints
|
||||
for (var i in buttonsTrs) { |
||||
buttons.appendChild(buttonsTrs[i]); |
||||
} |
||||
|
||||
rootElement.appendChild(buttons); |
||||
}, |
||||
|
||||
createBaseMarkup: function (element) { |
||||
var table = document.createElement('table'), |
||||
tr = document.createElement('tr'), |
||||
rearTd = document.createElement('td'), |
||||
frontTd = document.createElement('td'); |
||||
|
||||
rearTd.className = 'rear-screen'; |
||||
rearTd.setAttribute('rowspan', '8'); |
||||
tr.appendChild(rearTd); |
||||
|
||||
frontTd.className = 'front-screen'; |
||||
frontTd.setAttribute('colspan', '4'); |
||||
tr.appendChild(frontTd); |
||||
|
||||
tr.className = 'screen'; |
||||
table.appendChild(tr); |
||||
|
||||
element.appendChild(this.createTitle()); |
||||
element.appendChild(this.createControls()); |
||||
element.appendChild(table); |
||||
}, |
||||
|
||||
createTitle: function () { |
||||
this.title = document.createElement('span'); |
||||
|
||||
this.title.appendChild(document.createTextNode('Chargement...')); |
||||
this.title.className = 'title'; |
||||
|
||||
return this.title; |
||||
}, |
||||
|
||||
createControls: function () { |
||||
var controlsDiv = document.createElement('div'), |
||||
editorButton = document.createElement('button'), |
||||
resetButton = document.createElement('button'), |
||||
self = this; |
||||
|
||||
if (this.withEditor) { |
||||
editorButton.setAttribute('type', 'button'); |
||||
editorButton.appendChild(document.createTextNode(_('controls.editor'))); |
||||
editorButton.addEventListener('click', function (e) { |
||||
self.dispatcher.notify('main_interface.editor_click'); |
||||
}); |
||||
controlsDiv.appendChild(editorButton); |
||||
} |
||||
|
||||
resetButton.setAttribute('type', 'button'); |
||||
resetButton.appendChild(document.createTextNode(_('controls.reset'))); |
||||
resetButton.addEventListener('click', function (e) { |
||||
self.dispatcher.notify('main_interface.reset_click'); |
||||
}); |
||||
controlsDiv.appendChild(resetButton); |
||||
|
||||
controlsDiv.className = 'controls'; |
||||
|
||||
return controlsDiv; |
||||
}, |
||||
|
||||
createButtons: function (buttonMap) { |
||||
var trs = [], |
||||
i; |
||||
|
||||
for (i = 0; i < 7; i++) { |
||||
trs.push(this.createButtonRow(i, buttonMap)); |
||||
} |
||||
|
||||
return trs; |
||||
}, |
||||
|
||||
createButtonRow: function (rowNumber, buttonMap) { |
||||
var tr = document.createElement('tr'), |
||||
i; |
||||
|
||||
tr.className = 'buttons'; |
||||
|
||||
for (i = 'a'; i <= 'd'; i = String.fromCharCode(i.charCodeAt(0) + 1)) { |
||||
tr.appendChild(this.createButton(i + rowNumber, buttonMap)); |
||||
} |
||||
|
||||
return tr; |
||||
}, |
||||
|
||||
createButton: function (slot, buttonMap) { |
||||
var td = document.createElement('td'), |
||||
self = this, |
||||
text, |
||||
buttonEl, |
||||
button = buttonMap[slot]; |
||||
|
||||
td.setAttribute('data-slot', slot); |
||||
td.setAttribute('data-editable', button.isEditable()); |
||||
|
||||
if (typeof button !== 'undefined') { |
||||
buttonEl = document.createElement('button'); |
||||
text = this.texts.get(button.text); |
||||
buttonEl.innerText = text.button; |
||||
|
||||
if (text.type !== 'normal') { |
||||
td.classList.add(text.type); |
||||
} |
||||
|
||||
buttonEl.addEventListener('mousedown', function (e) { |
||||
self.handleButtonClick(this, e, button, slot); |
||||
}); |
||||
|
||||
this.buttons.set(slot, buttonEl); |
||||
|
||||
this.dispatcher.notify('main_interface.button_created', { |
||||
button: button, |
||||
slot: slot |
||||
}); |
||||
|
||||
td.appendChild(buttonEl); |
||||
} |
||||
|
||||
return td; |
||||
}, |
||||
|
||||
handleButtonClick: function (el, event, button, slot) { |
||||
this.dispatcher.notify('main_interface.button_click', { |
||||
button: button, |
||||
slot: slot |
||||
}); |
||||
}, |
||||
|
||||
updateFlagRow: function () { |
||||
var span; |
||||
|
||||
this._clearElement(this.flagRow); |
||||
|
||||
for (var i in this.flags) { |
||||
span = document.createElement('span'); |
||||
span.appendChild(document.createTextNode(this.flags[i])); |
||||
|
||||
this.flagRow.appendChild(span); |
||||
} |
||||
}, |
||||
|
||||
updateExpressionRow: function (keystrokeText) { |
||||
this._clearElement(this.expressionRow); |
||||
this.expressionRow.appendChild(document.createTextNode(keystrokeText[0])); |
||||
this.expressionRow.appendChild(this.caret); |
||||
this.expressionRow.appendChild(document.createTextNode(keystrokeText[1])); |
||||
}, |
||||
|
||||
updateResultRow: function (result) { |
||||
this._clearElement(this.resultRow); |
||||
this.resultRow.appendChild(this.createResultRow(result)); |
||||
}, |
||||
|
||||
createResultRow: function (result) { |
||||
try { |
||||
if (Sankore.Calculus.EuclideanDivisionOperation.isPrototypeOf(result)) { |
||||
return this.createResultRowEuclidean(result.getValue(), result.getRemainder()); |
||||
} else if (Sankore.Calculus.Expression.isPrototypeOf(result)) { |
||||
return this.createResultRowExpression(result.getValue()); |
||||
} else if (Sankore.Util.Error.isPrototypeOf(result)) { |
||||
return this.createResultRowError(result); |
||||
} else { |
||||
return this.createResultRowText(String(result)); |
||||
} |
||||
} catch (e) { |
||||
return this.createResultRowError(e); |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
|
||||
createResultRowEuclidean: function (quotient, remainder) { |
||||
var p = document.createElement('p'), |
||||
quotientSpan = document.createElement('span'), |
||||
remainderSpan = document.createElement('span'); |
||||
|
||||
p.classList.add('euclidean'); |
||||
|
||||
remainderSpan.classList.add('remainder'); |
||||
remainderSpan.textContent = this.formatResultValue(remainder); |
||||
p.appendChild(remainderSpan); |
||||
|
||||
quotientSpan.classList.add('quotient'); |
||||
quotientSpan.textContent = this.formatResultValue(quotient); |
||||
p.appendChild(quotientSpan); |
||||
|
||||
return p; |
||||
}, |
||||
|
||||
createResultRowExpression: function (value) { |
||||
return document.createTextNode(this.formatResultValue(value)); |
||||
}, |
||||
|
||||
createResultRowText: function (text) { |
||||
return document.createTextNode(text); |
||||
}, |
||||
|
||||
createResultRowError: function (error) { |
||||
var errorDiv = document.createElement('div'), |
||||
text = _('error.common'); |
||||
|
||||
if (error.name === 'ZeroDivision') { |
||||
text = _('error.zero_division'); |
||||
} |
||||
|
||||
errorDiv.classList.add('error'); |
||||
errorDiv.appendChild(document.createTextNode(text)); |
||||
|
||||
return errorDiv; |
||||
}, |
||||
|
||||
formatResultValue: function (raw) { |
||||
var fixed = raw.toFixed(Math.max(0, 10 - raw.toFixed().length)), |
||||
last; |
||||
|
||||
if (fixed.indexOf('.') !== -1) { |
||||
while (true) { |
||||
last = fixed.charAt(fixed.length - 1); |
||||
|
||||
if (last === "0") { |
||||
fixed = fixed.slice(0, -1); |
||||
continue; |
||||
} |
||||
|
||||
if (last === '.') { |
||||
fixed = fixed.slice(0, -1); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
// si le nombre de chiffres dépassent 10, on tronque à 10 à droite
|
||||
// et on arrondit au dernier (wtf)
|
||||
if (Number(fixed) > (1e10 - 1)) { |
||||
fixed = Number(fixed.substring(0, 11) / 10).toFixed(); |
||||
} |
||||
|
||||
if (fixed.indexOf('.') !== -1) { |
||||
fixed = fixed.replace('.', _('text.comma')); |
||||
} |
||||
|
||||
return fixed; |
||||
}, |
||||
|
||||
updateRearScreen: function (history) { |
||||
var expressionRow, |
||||
resultRow, |
||||
rows = document.createDocumentFragment(); |
||||
|
||||
for (var i in history) { |
||||
expressionRow = document.createElement('li'); |
||||
expressionRow.className = 'expression-row'; |
||||
expressionRow.appendChild(document.createTextNode(history[i].expression)); |
||||
rows.appendChild(expressionRow); |
||||
|
||||
resultRow = document.createElement('li'); |
||||
resultRow.className = 'result-row'; |
||||
resultRow.appendChild(this.createResultRow(history[i].output)); |
||||
rows.appendChild(resultRow); |
||||
} |
||||
|
||||
this.clearRearScreen(); |
||||
this.rearScreen.appendChild(rows); |
||||
this.rearScreen.lastChild.scrollIntoView(); |
||||
}, |
||||
|
||||
clearRearScreen: function () { |
||||
this._clearElement(this.rearScreen); |
||||
}, |
||||
|
||||
showRearScreen: function () { |
||||
this.rearScreen.style.display = 'block'; |
||||
this.showTitle(); |
||||
}, |
||||
|
||||
hideRearScreen: function () { |
||||
this.rearScreen.style.display = 'none'; |
||||
this.hideTitle(); |
||||
}, |
||||
|
||||
addFlag: function (flag) { |
||||
if (this.flags.indexOf(flag) === -1) { |
||||
this.flags.push(flag); |
||||
|
||||
this.flags.sort(); |
||||
} |
||||
}, |
||||
|
||||
removeFlag: function (flag) { |
||||
var idx = this.flags.indexOf(flag); |
||||
if (idx !== -1) { |
||||
this.flags.splice(idx, 1); |
||||
|
||||
this.flags.sort(); |
||||
} |
||||
}, |
||||
|
||||
changeTitle: function (title) { |
||||
this._clearElement(this.title); |
||||
this.title.appendChild(document.createTextNode(title)); |
||||
}, |
||||
|
||||
showTitle: function () { |
||||
this.title.style.visibility = 'visible'; |
||||
}, |
||||
|
||||
hideTitle: function () { |
||||
this.title.style.visibility = 'hidden'; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,15 @@ |
||||
/*globals klass:true Sankore:true*/ |
||||
(function(){ |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Util', 'Error', klass.extend({ |
||||
constructor: function (name, message) { |
||||
this.name = name; |
||||
this.message = message; |
||||
}, |
||||
|
||||
toString: function () { |
||||
return this.name + ': ' + this.message; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,48 @@ |
||||
/*globals klass: true, Sankore:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Util', 'EventDispatcher', klass.extend({ |
||||
constructor: function () { |
||||
this.eventListeners = {}; |
||||
}, |
||||
|
||||
addEventSubscriber: function(subscriber) { |
||||
for (var i in subscriber.events) { |
||||
this.addEventListener(subscriber.events[i], subscriber.listener); |
||||
} |
||||
|
||||
return this; |
||||
}, |
||||
|
||||
addEventListener: function (event, closure, id) { |
||||
if (typeof this.eventListeners[event] === 'undefined') { |
||||
this.eventListeners[event] = []; |
||||
} |
||||
|
||||
if (typeof id === 'undefined') { |
||||
this.eventListeners[event].push(closure); |
||||
} else { |
||||
this.eventListeners[event][id] = closure; |
||||
} |
||||
|
||||
return this; |
||||
}, |
||||
|
||||
removeEventListener: function (event, id) { |
||||
delete this.eventListeners[event][id]; |
||||
}, |
||||
|
||||
removeAllEventListeners: function (event) { |
||||
this.eventListeners[event] = []; |
||||
}, |
||||
|
||||
notify: function (event, obj) { |
||||
var closure; |
||||
|
||||
for (closure in this.eventListeners[event]) { |
||||
this.eventListeners[event][closure](obj); |
||||
} |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,78 @@ |
||||
/*globals klass: true, Sankore: true*/ |
||||
(function() { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Util', 'Hash', klass.extend({ |
||||
constructor: function (elements) { |
||||
this.elements = elements || {}; |
||||
}, |
||||
|
||||
length: function () { |
||||
return this.keys().length; |
||||
}, |
||||
|
||||
keys: function () { |
||||
return Object.keys(this.elements); |
||||
}, |
||||
|
||||
set: function (id, value) { |
||||
this.elements[id] = value; |
||||
}, |
||||
|
||||
add: function (id, values) { |
||||
for (var i in values) { |
||||
this.set(values[i][id], values[i]); |
||||
} |
||||
}, |
||||
|
||||
has: function (id) { |
||||
return this.keys().indexOf(id) !== -1; |
||||
}, |
||||
|
||||
get: function (id, def) { |
||||
if (typeof this.elements[id] !== 'undefined') { |
||||
return this.elements[id]; |
||||
} |
||||
|
||||
if (typeof def !== 'undefined') { |
||||
return def; |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
|
||||
pos: function (id) { |
||||
var pos = 0; |
||||
|
||||
for (var i in this.elements) { |
||||
if (this.elements.hasOwnProperty(i) && i === id) { |
||||
return pos; |
||||
} |
||||
|
||||
pos++; |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
|
||||
remove: function (id) { |
||||
return delete this.elements[id]; |
||||
}, |
||||
|
||||
map: function (closure) { |
||||
var output = [], |
||||
called; |
||||
|
||||
for (var id in this.elements) { |
||||
if (this.elements.hasOwnProperty(id)) { |
||||
called = closure.call(this, id, this.elements[id]); |
||||
if (called) { |
||||
output.push(called); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return output; |
||||
} |
||||
})); |
||||
})(); |
@ -0,0 +1,45 @@ |
||||
/*jshint devel:true, browser:true*/ |
||||
/*globals klass:true, Sankore:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
klass.define('Sankore.Util', 'I18N', klass.extend({ |
||||
|
||||
catalogs: Sankore.Util.Hash.create(), |
||||
|
||||
constructor: function () { |
||||
this.catalog = {}; |
||||
}, |
||||
|
||||
load: function (locale) { |
||||
var localeId = locale.split(/-|_/)[0].toLowerCase(); |
||||
|
||||
if (!Sankore.Util.I18N.catalogs.has(localeId)) { |
||||
localeId = 'en'; |
||||
} |
||||
|
||||
this.catalog = Sankore.Util.I18N.catalogs.get(localeId, {}); |
||||
}, |
||||
|
||||
translate: function (id) { |
||||
return id.split('.').reduce( |
||||
function (root, id) { |
||||
if (root && id in root) { |
||||
return root[id]; |
||||
} |
||||
|
||||
return null; |
||||
},
|
||||
this.catalog |
||||
) || id; |
||||
} |
||||
})); |
||||
|
||||
// global instance
|
||||
Sankore.Util.i18n = Sankore.Util.I18N.create(); |
||||
|
||||
// global helper
|
||||
window._ = function (id) { |
||||
return Sankore.Util.i18n.translate(id); |
||||
}; |
||||
})(); |
@ -0,0 +1,74 @@ |
||||
(function() { |
||||
Sankore.Util.I18N.catalogs.set('en', { |
||||
layout: { |
||||
classic_name: 'Basic calculator', |
||||
new_name: 'New calculator' |
||||
}, |
||||
text: { |
||||
del: 'DEL', |
||||
comma: '.' |
||||
}, |
||||
command: { |
||||
zero: '0 digit', |
||||
one: '1 digit', |
||||
two: '2 digit', |
||||
three: '3 digit', |
||||
four: '4 digit', |
||||
five: '5 digit', |
||||
six: '6 digit', |
||||
seven: '7 digit', |
||||
eight: '8 digit', |
||||
nine: '9 digit', |
||||
plus: 'Addition', |
||||
minus: 'Subtraction', |
||||
times: 'Multiplication', |
||||
divide: 'Division', |
||||
euclidean_divide: 'Euclidean division', |
||||
equal: 'Equal', |
||||
comma: 'Comma', |
||||
open_parenthesis: 'Open parenthesis', |
||||
close_parenthesis: 'Close parenthesis', |
||||
op: 'Constant operator', |
||||
memory_add: 'Memory addition', |
||||
memory_sub: 'Memory substraction', |
||||
memory_recall: 'Memory recall', |
||||
memory_clear: 'Memory clear', |
||||
clear: 'Clear', |
||||
left: 'Move left', |
||||
right: 'Move right', |
||||
del: 'Delete' |
||||
}, |
||||
controls: { |
||||
editor: 'Editor', |
||||
reset: 'RST' |
||||
}, |
||||
editor: { |
||||
run_button: 'Run', |
||||
remove_alert: 'Delete this calculator?', |
||||
layout_name: { |
||||
label: 'Name' |
||||
}, |
||||
layout_description: { |
||||
label: 'Description' |
||||
}, |
||||
assignation: { |
||||
enabled: 'Click on a button to edit it', |
||||
disabled: 'This calculator is not editable', |
||||
text: { |
||||
label: 'Display text' |
||||
}, |
||||
command: { |
||||
label: 'Command' |
||||
}, |
||||
use_limit: { |
||||
label: 'Use limit', |
||||
help: '0 for disabled, empty for unlimited' |
||||
} |
||||
} |
||||
}, |
||||
error: { |
||||
common: 'Error', |
||||
zero_division: 'Div/0 error' |
||||
} |
||||
}); |
||||
})(); |
@ -0,0 +1,74 @@ |
||||
(function() { |
||||
Sankore.Util.I18N.catalogs.set('fr', { |
||||
layout: { |
||||
classic_name: 'Calculatrice standard', |
||||
new_name: 'Nouvelle calculatrice' |
||||
}, |
||||
text: { |
||||
del: 'EFF', |
||||
comma: ',' |
||||
}, |
||||
command: { |
||||
zero: 'Chiffre 0', |
||||
one: 'Chiffre 1', |
||||
two: 'Chiffre 2', |
||||
three: 'Chiffre 3', |
||||
four: 'Chiffre 4', |
||||
five: 'Chiffre 5', |
||||
six: 'Chiffre 6', |
||||
seven: 'Chiffre 7', |
||||
eight: 'Chiffre 8', |
||||
nine: 'Chiffre 9', |
||||
plus: 'Addition', |
||||
minus: 'Soustraction', |
||||
times: 'Multiplication', |
||||
divide: 'Division', |
||||
euclidean_divide: 'Division euclidienne', |
||||
equal: 'Egalité', |
||||
comma: 'Virgule', |
||||
open_parenthesis: 'Parenthèse ouvrante', |
||||
close_parenthesis: 'Parenthèse fermante', |
||||
op: 'Opérateur constant', |
||||
memory_add: 'Addition mémoire', |
||||
memory_sub: 'Soustraction mémoire', |
||||
memory_recall: 'Rappel mémoire', |
||||
memory_clear: 'R.A.Z. mémoire', |
||||
clear: 'R.A.Z.', |
||||
left: 'Déplacement à gauche', |
||||
right: 'Déplacement à droite', |
||||
del: 'Suppression' |
||||
}, |
||||
controls: { |
||||
editor: 'Editeur', |
||||
reset: 'RAZ' |
||||
}, |
||||
editor: { |
||||
run_button: 'Utiliser', |
||||
remove_alert: 'Supprimer cette calculatrice ?', |
||||
layout_name: { |
||||
label: 'Nom' |
||||
}, |
||||
layout_description: { |
||||
label: 'Description' |
||||
}, |
||||
assignation: { |
||||
enabled: 'Cliquez sur une touche pour la modifier', |
||||
disabled: 'Cette calculatrice n\'est pas modifiable', |
||||
text: { |
||||
label: 'Texte à afficher' |
||||
}, |
||||
command: { |
||||
label: 'Commande' |
||||
}, |
||||
use_limit: { |
||||
label: 'Nb d\'utilisation', |
||||
help: '0 pour désactiver, vide pour illimité' |
||||
} |
||||
} |
||||
}, |
||||
error: { |
||||
common: 'Erreur', |
||||
zero_division: 'Erreur div/0' |
||||
} |
||||
}); |
||||
})(); |
@ -0,0 +1,78 @@ |
||||
/*global window:true*/ |
||||
(function () { |
||||
"use strict"; |
||||
|
||||
// polyfill
|
||||
if (!Function.prototype.bind) { |
||||
Function.prototype.bind = function (oThis) { |
||||
if (typeof this !== "function") { |
||||
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); |
||||
} |
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1), |
||||
fToBind = this, |
||||
fNOP = function () {}, |
||||
fBound = function () { |
||||
return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, |
||||
aArgs.concat(Array.prototype.slice.call(arguments))); |
||||
}; |
||||
|
||||
fNOP.prototype = this.prototype; |
||||
fBound.prototype = new fNOP(); |
||||
|
||||
return fBound; |
||||
}; |
||||
} |
||||
|
||||
window.klass = { |
||||
create: function () { |
||||
var self = Object.create(this); |
||||
|
||||
if (typeof self.constructor === "function") { |
||||
self.constructor.apply(self, arguments); |
||||
} |
||||
|
||||
return self; |
||||
}, |
||||
|
||||
extend: function (object) { |
||||
var self = Object.create(this); |
||||
|
||||
if (!object) { |
||||
return self; |
||||
} |
||||
|
||||
Object.keys(object).forEach(function (key) { |
||||
self[key] = object[key]; |
||||
}); |
||||
|
||||
return self; |
||||
}, |
||||
|
||||
define: function (namespace, name, object) { |
||||
var createNamespace = function (namespace, root) { |
||||
var namespaceParts = namespace.split('.'), |
||||
first; |
||||
|
||||
if (namespaceParts.length > 0) { |
||||
first = namespaceParts.shift(); |
||||
|
||||
if (typeof root[first] === 'undefined') { |
||||
root[first] = {}; |
||||
} |
||||
|
||||
if (namespaceParts.length > 0) { |
||||
return createNamespace(namespaceParts.join('.'), root[first]); |
||||
} |
||||
|
||||
return root[first]; |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
ns = createNamespace(namespace, window); |
||||
|
||||
ns[name] = object; |
||||
} |
||||
}; |
||||
})(); |
@ -0,0 +1,102 @@ |
||||
* { |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
body{ |
||||
margin:0px; |
||||
} |
||||
|
||||
.ubw-container{ |
||||
float:left; |
||||
margin:3px; |
||||
margin-right:0px; |
||||
margin-top: 2px; |
||||
background-image:url(../images/back_small.png); |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.ubw-body{ |
||||
margin:5px; |
||||
margin-left: 9px; |
||||
margin-right: 0px; |
||||
} |
||||
|
||||
.ubw-inspector{ |
||||
position:absolute; |
||||
background-color:rgb(252, 252, 252); |
||||
border:1px solid #cccccc; |
||||
line-height:20px; |
||||
font-family:Arial, Helvetica, sans-serif; |
||||
font-weight:normal; |
||||
font-size:20px; |
||||
color:#333333; |
||||
} |
||||
|
||||
.ubw-inpubox{ |
||||
min-width:28px; |
||||
min-height:37px; |
||||
color:#333333; |
||||
background-image: url(../images/button_out.png); |
||||
border-left:1px solid rgb(231, 231, 231); |
||||
border-right:1px solid rgb(231, 231, 231); |
||||
border-bottom:1px solid rgb(221, 221, 221); |
||||
border-top:1px solid rgb(241, 241, 241); |
||||
} |
||||
|
||||
/*BUTTONS*/ |
||||
|
||||
.ubw-button-wrapper{ |
||||
float:left; |
||||
position:relative; |
||||
/*border:solid 1px yellow;*/ |
||||
margin-right:-7px; |
||||
z-index:0; |
||||
font-family:Arial, Helvetica, sans-serif; |
||||
font-weight:normal; |
||||
font-size:30px; |
||||
overflow:visible; |
||||
} |
||||
|
||||
.ubw-button-canvas{ |
||||
width:auto; |
||||
float:left; |
||||
position:relative; |
||||
overflow:visible; |
||||
} |
||||
|
||||
table{ |
||||
line-height:90%; |
||||
} |
||||
|
||||
.ubw-button-body{ |
||||
position:relative; |
||||
float:left; |
||||
|
||||
width:auto; |
||||
height:auto; |
||||
overflow:visible |
||||
|
||||
text-align:center; |
||||
vertical-align:middle; |
||||
|
||||
cursor:pointer; |
||||
} |
||||
|
||||
.ubw-button-content{ |
||||
height:auto; |
||||
width:auto; |
||||
text-align:center; |
||||
overflow:visible; |
||||
} |
||||
|
||||
|
||||
.ubw-button-over{ |
||||
} |
||||
|
||||
.ubw-button-out{ |
||||
} |
||||
|
||||
span.colored{ |
||||
color: #0080ff; |
||||
} |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |