@ -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 |