├── .gitmodules
├── CNAME
├── LICENSE
├── README.md
├── buildhtml.cmd
├── buildhtml.py
├── html
├── about.html
├── block-xml
│ ├── divisibility.xml
│ ├── function-limit.xml
│ ├── prime.xml
│ ├── sequence-limit-q1-correct.xml
│ ├── sequence-limit-q1-pieces.xml
│ └── sequence-limit.xml
├── builder.html
├── images
│ ├── ICME poster.pdf
│ ├── logo-large.png
│ ├── logo-transparent.png
│ ├── logo.png
│ ├── logo2.png
│ └── video-poster.png
├── limit-exercise.html
├── logic-exercise.html
├── logo.html
├── main-html.css
├── math-blockly-old.css
├── math-blockly.css
├── mathjax-test.html
├── templates
│ ├── header-index.html
│ └── header.html
├── test.html
├── translation-exercise.html
├── trig-functions.html
├── vector-exercise.html
├── vectors.html
└── videos
│ ├── addfades.cmd
│ ├── blocks1.mp4
│ ├── blocks2.mp4
│ ├── combined.mp4
│ ├── concatlist.txt
│ ├── ffmpeg notes.txt
│ ├── getframecount.cmd
│ ├── gif2mp4.cmd
│ ├── latex1.mp4
│ ├── vectors1.mp4
│ ├── vectors2.mp4
│ └── video-test.html
├── index.html
└── js
├── exercise-utils.js
├── expression-generator.js
├── field_mathjax.js
├── field_mathvariable.js
├── field_mathvariable_old.js
├── latex-generator.js
├── math-blocks-limit-exercise.js
├── math-blocks-logic-exercise.js
├── math-blocks-old.js
├── math-blocks-translation-exercise.js
├── math-blocks-trig.js
├── math-blocks-vectors.js
├── math-blocks.js
├── shaped-connectors.js
├── utils.js
└── video-utils.js
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "closure-library"]
2 | path = closure-library
3 | url = https://github.com/google/closure-library.git
4 | [submodule "blockly"]
5 | path = blockly
6 | url = https://github.com/awmorp/blockly.git
7 | [submodule "blockly-type-indicator"]
8 | path = blockly-type-indicator
9 | url = https://github.com/awmorp/blockly-type-indicator.git
10 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | mathsblocks.com
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A visual mathematical expression builder using Blockly.
--------------------------------------------------------------------------------
/buildhtml.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal
3 |
4 | echo Building index.html
5 | python buildhtml.py index.html
6 |
7 | cd html
8 | for /d %%i in ( builder.html logic-exercise.html limit-exercise.html translation-exercise.html vectors.html ) do echo Building %%i & python ..\buildhtml.py %%i
9 |
10 |
--------------------------------------------------------------------------------
/buildhtml.py:
--------------------------------------------------------------------------------
1 | # Simple template replacement engine
2 | #
3 | # Usage: buildhtml inputfile.html
4 | #
5 | # Replaces any text in inputfile.html between tags with the content of file.html
6 |
7 | import sys # for sys.argv
8 | import shutil # for shutil.move
9 |
10 | # regexp module
11 | import re
12 |
13 | beginRE = r'' # matches
14 | endRE = r'' # matches or
15 |
16 | filename = sys.argv[1]
17 |
18 | inputFile = open( filename, "r" )
19 | outputFile = open( filename + ".tmp", "w" )
20 |
21 | def output(str):
22 | outputFile.write(str)
23 |
24 | STATE_NORMAL = 0
25 | STATE_INCLUDING = 1
26 | state = STATE_NORMAL
27 |
28 | for line in inputFile:
29 | if state == STATE_NORMAL:
30 | matchResult = re.search( beginRE, line )
31 | if matchResult:
32 | output( line[0:matchResult.end()] + "\n" ) # Output to end of
33 | includeFileName = matchResult.group( "fname" )
34 | includeFile = open( includeFileName, "r" )
35 | for line2 in includeFile:
36 | output(line2)
37 | # print "MATCH: file '" + includeFile + "'"
38 | includeFile.close()
39 | state = STATE_INCLUDING
40 | else:
41 | output( line )
42 | elif state == STATE_INCLUDING:
43 | matchResult = re.search( endRE, line )
44 | if matchResult:
45 | output( "\n" + line[matchResult.start():] ) # Output from start of
46 | state = STATE_NORMAL
47 |
48 | inputFile.close()
49 | outputFile.close()
50 | shutil.move( filename, filename + ".bak" )
51 | shutil.move( filename + ".tmp", filename )
52 |
--------------------------------------------------------------------------------
/html/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Use the blocks to build mathematical expressions.
102 | Click the categories 'Logic', 'Number' etc to show the different kinds of blocks. Drag a block from the toolbox into the main workspace to use the block. You can drag a block into a free space in another block to connect them - but only if the shapes match!
103 | Load a pre-built example:
105 |
106 | -- choose one --
107 | Limit of a sequence
108 | Limit of a function
109 | _ is divisible by _
110 | p is prime
111 |
112 |
Load
113 |
Replace existing blocks
114 |
115 |
123 |
124 |
127 |
128 |
129 |
130 |
131 |
137 |
138 |
139 | ⇒
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | <
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | ℝ
180 |
181 |
185 |
186 | ℤ
187 |
188 |
189 | ℕ
190 |
191 |
192 |
193 |
194 |
195 | =
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
217 |
218 |
219 |
220 |
--------------------------------------------------------------------------------
/html/images/ICME poster.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/ICME poster.pdf
--------------------------------------------------------------------------------
/html/images/logo-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo-large.png
--------------------------------------------------------------------------------
/html/images/logo-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo-transparent.png
--------------------------------------------------------------------------------
/html/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo.png
--------------------------------------------------------------------------------
/html/images/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo2.png
--------------------------------------------------------------------------------
/html/images/video-poster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/video-poster.png
--------------------------------------------------------------------------------
/html/limit-exercise.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Limit definitions exercise
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
31 |
32 |
45 |
46 |
47 |
48 |
73 |
74 |
Limit definition exercises
75 |
76 |
Q1. Use the blocks provided to give the definition of \(\displaystyle \lim_{n\to\infty} f(n) = L \).
77 |
Q2. Use the blocks provided to give the definition of `the sequence \(\displaystyle f_n \) converges '.
78 |
Q3. Use the blocks provided to give the definition of `the sequence \(\displaystyle f_n \) does not converge '.
79 |
80 |
81 |
82 |
Not quite there yet...
83 |
84 |
Correct!
85 |
88 |
89 | Finished.
90 |
91 |
92 |
93 |
94 |
97 |
98 |
112 |
113 |
114 | FORALL
115 | BLANK
116 | >
117 | BLANK
118 |
119 |
120 | FORALL
121 | BLANK
122 | >
123 | BLANK
124 |
125 |
126 | EXISTS
127 | BLANK
128 | REAL
129 |
130 |
131 | <
132 |
133 |
134 | ADD
135 |
136 |
137 |
138 |
139 | EPSILON
140 |
141 |
142 | L
143 |
144 |
145 |
146 |
147 |
148 | FORALL
149 | EPSILON
150 | >
151 | 0
152 |
153 |
154 | EXISTS
155 | M
156 | NATURALS
157 |
158 |
159 | FORALL
160 | n
161 | >
162 | M
163 |
164 |
165 | <
166 |
167 |
168 |
169 |
170 | MINUS
171 |
172 |
173 |
174 |
175 |
176 | L
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | EPSILON
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | EXISTS
199 | BLANK
200 | BLANK
201 |
202 |
203 |
204 |
205 | EXISTS
206 | L
207 | REAL
208 |
209 |
210 | FORALL
211 | EPSILON
212 | >
213 | 0
214 |
215 |
216 | EXISTS
217 | M
218 | NATURALS
219 |
220 |
221 | FORALL
222 | n
223 | >
224 | M
225 |
226 |
227 | <
228 |
229 |
230 |
231 |
232 | MINUS
233 |
234 |
235 |
236 |
237 |
238 | L
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | EPSILON
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 | FORALL
263 | L
264 | REAL
265 |
266 |
267 | EXISTS
268 | EPSILON
269 | >
270 | 0
271 |
272 |
273 | FORALL
274 | M
275 | NATURALS
276 |
277 |
278 | EXISTS
279 | n
280 | >
281 | M
282 |
283 |
284 | ≥
285 |
286 |
287 |
288 |
289 | MINUS
290 |
291 |
292 |
293 |
294 |
295 | L
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 | EPSILON
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
330 |
331 |
332 |
--------------------------------------------------------------------------------
/html/logo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks logo
6 |
7 |
8 |
9 |
26 |
27 |
28 |
39 |
40 |
41 |
42 |
43 |
44 |
95 |
96 |
97 |
98 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/html/main-html.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin-top: 0;
4 | }
5 |
6 | li {
7 | margin: 6px 6px;
8 | }
9 |
10 | a {
11 | text-decoration: none;
12 | /* border-bottom: 1px solid; */
13 | }
14 |
15 | a:hover {
16 | text-decoration: underline;
17 | }
18 |
19 | .textcontent {
20 | margin-left: 20px;
21 | margin-right: 20px;
22 | }
23 |
24 | .medspace p {
25 | margin-top: 0.3em;
26 | margin-bottom: 0.3em;
27 | }
28 |
29 | h4 {
30 | margin-top: 2em;
31 | margin-bottom: 0.5em;
32 | }
33 |
34 | #headerbar {
35 | padding: 5px;
36 | background: #eeeeee;
37 | /* border: 1px solid red; */
38 | position: relative;
39 | }
40 |
41 | #tagline {
42 | display: inline;
43 | /* margin-left: 30px; */
44 | font-style: italic;
45 | }
46 |
47 | #tagline.largeheader {
48 | display: block;
49 | margin-left: 30px;
50 | font-style: italic;
51 | font-size: large;
52 | }
53 |
54 | #titlebar {
55 | display: inline-block;
56 | }
57 |
58 | #navbar {
59 | /* float: right; */
60 | /* border: 1px solid green; */
61 | }
62 |
63 | #menulist {
64 | text-align: justify;
65 | padding-top: 6px;
66 | vertical-align: top;
67 | position: absolute;
68 | right: 14px;
69 | top: 0;
70 | }
71 |
72 | .menuitem {
73 | display: inline-block;
74 | /* border: 1px solid blue; */
75 | padding: 6px;
76 | /* width: 30%;*/
77 | white-space: nowrap;
78 | border-radius: 4px;
79 | border: 1px solid grey;
80 | background: #d2d2d2;
81 | vertical-align: top;
82 | }
83 | .menuitem:hover {
84 | background: #ebdff8;
85 | }
86 |
87 | .submenulist {
88 | display: block;
89 | height: 0;
90 | }
91 |
92 | .submenulist .menuitem {
93 | display: block;
94 | visibility: collapse;
95 | margin-top: 0;
96 | }
97 |
98 | .menuitem:hover + .submenulist, .menuitem + .submenulist:hover,
99 | .menuitem:hover > .submenulist, .menuitem > .submenulist:hover {
100 | height: 100%;
101 | }
102 |
103 | .menuitem:hover + .submenulist .menuitem, .menuitem + .submenulist:hover .menuitem,
104 | .menuitem:hover > .submenulist .menuitem, .menuitem > .submenulist:hover .menuitem {
105 | visibility: visible;
106 | margin-top: 5px;
107 | // position: relative;
108 | }
109 |
110 | .submenulist .menuitem:hover {
111 | background: #c8bdd2;
112 | }
--------------------------------------------------------------------------------
/html/math-blockly-old.css:
--------------------------------------------------------------------------------
1 | /* Obsolete rules from older versions */
2 | .modal-dialog-bg {
3 | position: absolute;
4 | top: 0px;
5 | left: 0px;
6 | background-color: #FFF;
7 | }
8 |
9 | .modal-dialog {
10 | position: absolute;
11 | top: 0px;
12 | left: 0px;
13 | width: 300px;
14 | background-color: #eee;
15 | border: 2px solid #999;
16 | }
17 |
18 | .modal-dialog-title {
19 | position:relative;
20 | /* background: #C3D9FF; */
21 | padding: 4px;
22 | font: bold 11px verdana;
23 | cursor: default;
24 | }
25 |
26 | .modal-dialog-content {
27 | /* background: #E8EEF7; */
28 | padding: 12px 18px 12px 18px;
29 | font: normal 12px verdana;
30 | }
31 |
32 | .modal-dialog-userInput {
33 | font: normal 12px verdana;
34 | width: 90%;
35 | }
36 |
37 | .modal-dialog-buttons {
38 | /* background: #E8EEF7; */
39 | padding: 4px;
40 | font: normal 12px verdana;
41 | text-align: right;
42 | }
43 |
44 | .modal-dialog-buttons button {
45 | margin: 5px;
46 | }
47 |
--------------------------------------------------------------------------------
/html/math-blockly.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | font-family: sans-serif;
4 | }
5 |
6 | h1 {
7 | font-weight: normal;
8 | font-size: 140%;
9 | }
10 |
11 | /* Hide block highlight SVG elements while hacking */
12 | /*.blocklyPathLight {
13 | display: none;
14 | }
15 | */
16 |
17 | #blocklyDiv, .blocklyWorkspace {
18 | margin-top: 1em;
19 | margin-bottom: 1em;
20 | }
21 |
22 | .question {
23 | display: none;
24 | }
25 |
26 | /* One question in multi-question page eg logic-exercise.html */
27 | .q {
28 | margin-bottom: 1em;
29 | padding-top: 1em;
30 | }
31 |
32 | .red {
33 | color: red;
34 | }
35 |
36 | xml {
37 | display: none;
38 | }
39 |
40 | .debug {
41 | display: none;
42 | float: right;
43 | font-size: small;
44 | }
45 |
46 | /* The following are rules for SVG so use different properties than HTML, eg 'fill' rather than 'background' */
47 | .blocklyFlydown {
48 | fill: rgb(191,191,191);
49 | fill-opacity: 0.8;
50 | }
51 |
52 | /* Force blue background colour for number variable in quantifier block */
53 | .blocklyEditableText.blocklyQuantifierVarField > rect {
54 | fill: rgb(189, 194, 219);
55 | fill-opacity: 1;
56 | /* Give the field a darker blue border */
57 | stroke-width: 1.5;
58 | stroke: rgb(100, 113, 177); /* Number block colour is rgb(91, 103, 160) */
59 | }
60 |
61 | /* Red background for clashing variables */
62 | .blocklyMathVariableError > rect {
63 | fill: rgb(255, 192, 192) !important;
64 | fill-opacity: 1 !important;
65 | }
--------------------------------------------------------------------------------
/html/mathjax-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Blockly testing
6 |
7 |
8 |
9 |
26 |
27 |
28 |
39 |
40 |
41 |
42 |
43 |
44 |
93 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | \lim_{n \to \infty} ( 1 + x/n )^n = e^x
108 |
109 |
110 |
111 |
112 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/html/templates/header-index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/html/templates/header.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/html/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Blockly testing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
127 |
128 |
129 |
130 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/html/translation-exercise.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Translation from English to mathematics
6 |
7 |
8 |
9 |
10 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
49 |
50 |
51 |
52 |
77 |
78 |
Translation from English to mathematics
79 |
80 |
Q1. Use the blocks provided to express the statement `Every multiple of 6 is also a multiple of 3 '.
81 |
Q2. Use the blocks provided to express the statement `Every multiple of 6 is also a multiple of 3 ', but using only mathematical notation .
82 |
Q3. Use the blocks provided to express the statement `Some multiple of 3 is also a multiple of 6 '.
83 |
84 |
85 |
86 |
87 |
88 |
89 |
Not quite there yet...
90 |
91 |
Correct!
92 |
95 |
96 | Finished.
97 |
98 |
99 |
100 |
101 |
104 |
105 |
119 |
120 |
121 | FORALL
122 | a
123 |
124 |
125 | ⇒
126 |
127 |
128 |
129 |
130 | a
131 |
132 |
133 | a
134 |
135 |
136 |
137 |
138 |
139 | FORALL
140 | a
141 |
142 |
143 | ⇒
144 |
145 |
146 |
147 |
148 | a
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | a
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | FORALL
169 | b
170 |
171 |
172 | ⇒
173 |
174 |
175 |
176 |
177 | b
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | b
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | FORALL
198 | c
199 |
200 |
201 | ⇒
202 |
203 |
204 |
205 |
206 | c
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | c
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | BLANK
227 | BLANK
228 |
229 |
230 | BLANK
231 | BLANK
232 |
233 |
234 | =
235 |
236 |
237 | =
238 |
239 |
240 |
241 |
242 |
243 |
244 | BLANK
245 |
246 |
247 | BLANK
248 |
249 |
250 | 6
251 |
252 |
253 | BLANK
254 |
255 |
256 | BLANK
257 |
258 |
259 | 3
260 |
261 |
262 |
263 |
264 |
265 | FORALL
266 | a
267 |
268 |
269 | ⇒
270 |
271 |
272 | EXISTS
273 | b
274 |
275 |
276 | =
277 |
278 |
279 | a
280 |
281 |
282 |
283 |
284 |
285 |
286 | 6
287 |
288 |
289 |
290 |
291 | b
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 | EXISTS
303 | c
304 |
305 |
306 | =
307 |
308 |
309 | a
310 |
311 |
312 |
313 |
314 |
315 |
316 | 3
317 |
318 |
319 |
320 |
321 | c
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 | EXISTS
338 | a
339 |
340 |
341 | ∧
342 |
343 |
344 | EXISTS
345 | b
346 |
347 |
348 | =
349 |
350 |
351 | a
352 |
353 |
354 |
355 |
356 |
357 |
358 | 6
359 |
360 |
361 |
362 |
363 | b
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 | EXISTS
375 | c
376 |
377 |
378 | =
379 |
380 |
381 | a
382 |
383 |
384 |
385 |
386 |
387 |
388 | 3
389 |
390 |
391 |
392 |
393 | c
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
422 |
423 |
424 |
425 |
--------------------------------------------------------------------------------
/html/trig-functions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Trigonometric functions and inverses demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
34 |
35 |
39 |
40 |
41 |
42 |
67 |
68 |
Demo of trigonometric functions and inverses
69 |
70 |
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/html/vector-exercise.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Vector and scalar syntax exercises
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 |
28 |
32 |
33 |
34 |
Generate a random expression debug
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/html/vectors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Vectors and scalars demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
34 |
35 |
39 |
40 |
41 |
42 |
67 |
68 |
Demo of vector and scalar arithmetic
69 |
70 |
This page will not display properly in Internet Explorer, sorry. Some blocks will appear blank. Try using another web browser.
71 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | 2
86 |
87 |
88 |
89 |
90 | 1
91 |
92 |
93 |
94 |
95 | 3
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | CROSS
105 |
106 |
107 |
108 |
109 | 0
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 0
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/html/videos/addfades.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | REM Add a fadein/fadeout at start/end of video
3 | setlocal enabledelayedexpansion
4 |
5 | rem Length of fade (# of frames)
6 | set FADELENGTH=15
7 |
8 | for %%I in (%*) do (
9 | setlocal enabledelayedexpansion
10 | title Adding fadein/fadeout %%~nI
11 | for /f %%J in ('getframecount %%I') do set ABC=%%J
12 | echo Frames: !ABC!
13 | set /a STARTFADE=!ABC!-15
14 | rem echo Start fadeout: !STARTFADE!
15 |
16 | color 2f
17 | echo ffmpeg -i %%I -filter:v "fade=in:0:!FADELENGTH!:color=white,fade=out:!STARTFADE!:!FADELENGTH!:color=white" "%%~nI-fades%%~xI"
18 | ffmpeg -i %%I -filter:v "fade=in:0:!FADELENGTH!:color=white,fade=out:!STARTFADE!:!FADELENGTH!:color=white" "%%~nI-fades-temp%%~xI"
19 | ffmpeg -i "%%~nI-fades-temp%%~xI" -movflags faststart -pix_fmt yuv420p "%%~nI-fades%%~xI"
20 | del "%%~nI-fades-temp%%~xI"
21 | )
22 | title Command prompt
23 | color
24 |
--------------------------------------------------------------------------------
/html/videos/blocks1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/blocks1.mp4
--------------------------------------------------------------------------------
/html/videos/blocks2.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/blocks2.mp4
--------------------------------------------------------------------------------
/html/videos/combined.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/combined.mp4
--------------------------------------------------------------------------------
/html/videos/concatlist.txt:
--------------------------------------------------------------------------------
1 | file 'blocks1-fades.mp4'
2 | file 'vectors1-fades.mp4'
3 | file 'latex1-fades.mp4'
4 |
--------------------------------------------------------------------------------
/html/videos/ffmpeg notes.txt:
--------------------------------------------------------------------------------
1 | These animation videos were created by:
2 | 1. Recording video using LICEcap (http://www.cockos.com/licecap/). Dimensions: 600x280
3 | 2. Converted to mp4 using ffmpeg: ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 output.mp4
4 |
5 | Fade-in/out to white:
6 | 1. Convert .gif to .mp4 as above:
7 | > ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 output.mp4
8 | 2. Determine total # frames:
9 | > ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 output.mp4
10 | [Source: https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg]
11 | 3. Add fade-in/out:
12 | > ffmpeg -i output.mp4 -filter:v "fade=in:0:15:color=white,fade=out:160:15:color=white" output2.mp4
13 | NOTE: replace 160 with 15 frames before end of video
14 | [Source: https://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction , https://superuser.com/questions/386065/is-there-a-way-to-add-a-fade-to-black-effect-to-a-video-from-the-command-line ]
15 | 4. Recode to make file readable by Windows Media Player:
16 | > ffmpeg -i output2.mp4 -movflags faststart -pix_fmt yuv420p output3.mp4
17 |
18 |
19 |
20 | To concatenate videos:
21 | 1. list files to be concatenated in concatlist.txt
22 | 2. > ffmpeg -f concat -safe 0 -i concatlist.txt -c copy combined.mp4
23 |
24 |
25 |
26 | To repeat the last frame for XXX seconds:
27 | > ffmpeg -f lavfi -i nullsrc=s=600x280:d=ZZZZZ:r=25 -i input.mp4 -filter_complex "[0:v][1:v]overlay[video]" -map "[video]" -shortest output.mp4
28 | where ZZZZZ is the desired new length of video. Note: Update resolution and frame rate accordingly.
29 |
30 | To extract the last frame of a video:
31 | > ffmpeg -i input.mp4 -ss TIME lastframe.jpg
32 | where TIME is the length of the video (as reported by ffprobe), minus 1/25 = 0.04 seconds (duration of one frame)
33 |
34 | To repeat a still frame for XXXX seconds:
35 | > ffmpeg -f lavfi -i aevalsrc=0 -i lastframe.jpg -loop 1 -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 -t 0:0:XXXX lastframe.mp4
36 |
37 |
38 |
39 | To crop the first XX seconds of a video:
40 | > ffmpeg -i input.mp4 -ss XX blocks-demo-1-cut.mp4
41 | where XX is in the form h:mm:ss.ss
42 |
43 |
44 | To add text to a video:
45 | > ffmpeg -i input.mp4 -vf drawtext="text='Text here':fontcolor=blue:fontsize=20:x=(w-text_w)/2:y=(h-2.5*text_h):fontfile=/Windows/Fonts/arial.ttf" output.mp4
--------------------------------------------------------------------------------
/html/videos/getframecount.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 %1
--------------------------------------------------------------------------------
/html/videos/gif2mp4.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | REM Encode an animated GIF into HTML5-compatible mp4
3 | setlocal
4 |
5 | for %%I in (%*) do (
6 | title Encoding %%~nI
7 | color 2f
8 | ffmpeg -i "%%~dpnI%%~xI" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 "%%~dpnI.mp4"
9 | )
10 | title Command prompt
11 | color
12 |
--------------------------------------------------------------------------------
/html/videos/latex1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/latex1.mp4
--------------------------------------------------------------------------------
/html/videos/vectors1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/vectors1.mp4
--------------------------------------------------------------------------------
/html/videos/vectors2.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/vectors2.mp4
--------------------------------------------------------------------------------
/html/videos/video-test.html:
--------------------------------------------------------------------------------
1 |
2 |
HTML5 video test
3 |
4 |
5 |
6 | TODO: show still image here
7 | Define 'poster' attribute
8 |
9 |
10 |
11 |
67 |
Play/pause Next video
68 |
69 |
70 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Maths Blocks - Home
6 |
16 |
17 |
18 |
19 |
44 |
45 |
Maths Blocks : A visual mathematical expression builder
46 |
47 |
48 | Maths Blocks is a system for constructing and manipulating mathematical expressions from visual blocks representing syntactical elements.
49 |
50 |
51 | Try it out:
52 |
53 |
61 |
62 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/js/exercise-utils.js:
--------------------------------------------------------------------------------
1 | /* Javascript for English to mathematics translation exercise */
2 |
3 | /* Animation functions */
4 | function pause( t ) {
5 | return( function(continuation) {
6 | // console.log( "Pausing for " + t );
7 | setTimeout( continuation, t );
8 | } );
9 | }
10 |
11 | function animateBlock( block, x, y, time ) {
12 | return( function(continuation) {
13 | // console.log( "Animating block " + block.id );
14 | block.select();
15 | var startPos = block.getRelativeToSurfaceXY();
16 | var dX = (x - startPos.x)/50;
17 | var dY = (y - startPos.y)/50;
18 | var i = 0;
19 | var timer;
20 | timer = window.setInterval( function() {
21 | block.moveBy( dX, dY );
22 | if( i++ == 50 ) {
23 | window.clearInterval( timer );
24 | continuation();
25 | }
26 | }, time/50);
27 | });
28 | }
29 |
30 | function fixBlock( block ) {
31 | return( function(continuation) {
32 | // console.log( "Fixing block " + block.id );
33 | block.setMovable( false );
34 | block.setEditable( false );
35 | block.setDisabled( true );
36 | // block.updateDisabled();
37 |
38 | continuation();
39 | });
40 | }
41 |
42 | function deleteBlock( block ) {
43 | return( function(continuation) {
44 | // console.log( "Deleting block " + block.id );
45 | block.dispose(false, true); // Delete block with animation
46 | continuation();
47 | });
48 | }
49 |
50 |
51 | function unplug( block ) {
52 | return( function(continuation) {
53 | // console.log( "Unplugging block " + block.id );
54 | block.unplug();
55 | continuation();
56 | });
57 | }
58 |
59 | function loadXml( nodeId ) {
60 | return( function(continuation) {
61 | // console.log( "loadXml( " + nodeId + " )" );
62 | Blockly.Xml.domToWorkspace( workspace, document.getElementById( nodeId ) );
63 | continuation();
64 | });
65 | }
66 |
67 | /* Run an animation sequence */
68 | function runSequence( queue ) {
69 | if( queue ) {
70 | var next = queue.shift();
71 | if( goog.isFunction( next ) ) next( function() { runSequence( queue ) } );
72 | else if( goog.isNumber( next ) ) setTimeout( next, function() { runSequence( queue ) } );
73 | }
74 | }
75 |
76 | /* Load question 2 - run some animations, manipulate some blocks */
77 | function loadQ2() {
78 | var rootBlock = workspace.getBlockById("6/8T!gQ*,D?*RUNRynp3");
79 | var impBlock = workspace.getBlockById(":]LON,k-!D|CGQb$A3FB");
80 | var mult3Block = workspace.getBlockById("fX(^g7xz/h`iw/Q!X8W=");
81 | var mult6Block = workspace.getBlockById("$O{41OtB{O`4$8%M:2E}");
82 | var v1Block = workspace.getBlockById("mtgBEc]uuk$k=.EoQh,h");
83 | var v2Block = workspace.getBlockById("Jr{b*jBHteQRO#N;X3,6");
84 |
85 | workspace.getAllBlocks().forEach( function(x) { x.setEditable( false ); } );
86 |
87 | Blockly.Events.disable();
88 | var queue = [animateBlock( rootBlock, workspace.getWidth()/2 - rootBlock.width/2 + 150, 20, 300 ), pause( 500 ),
89 | unplug( mult6Block ), animateBlock( mult6Block, workspace.getWidth()/3 - mult6Block.width/2, 100, 600 ),
90 | unplug( mult3Block ), animateBlock( mult3Block, 2*workspace.getWidth()/3 - mult6Block.width/2, 100, 600 ),
91 | // fixBlock( rootBlock ), fixBlock( impBlock ),
92 | deleteBlock( mult3Block ), deleteBlock( mult6Block ), pause( 250 ),
93 | loadXml( "q2addition" ),
94 | function(cont) {
95 | workspace.getAllBlocks().forEach( function(x) {
96 | x.setDisabled( false );
97 | x.setMovable( true );
98 | x.setEditable( true );
99 | x.updateDisabled();
100 | } );
101 | Blockly.Events.enable();
102 | cont();
103 | }
104 | ];
105 |
106 | // Start sequence.
107 | runSequence( queue );
108 | }
109 |
110 | /* Compare two XML workspace representations, ignoring block id's and positions */
111 | /* a, b should be XML text */
112 | function compareXML(a,b) {
113 | /* Strip out x="..", y="..", id=".." attributes and whitespace */
114 | a = a.replace(/[xy]="[0-9]+"/g,'').replace(/id="[^"]+"/g,'').replace(/[ \n]/g,'');
115 | b = b.replace(/[xy]="[0-9]+"/g,'').replace(/id="[^"]+"/g,'').replace(/[ \n]/g,'');
116 | return( a == b);
117 | }
118 |
119 | function checkAnswer() {
120 | // var startTime1 = Date.now();
121 |
122 | var correct = false;
123 | var topBlocks = workspace.getTopBlocks();
124 | if( topBlocks.length == 1 ) {
125 | Blockly.Events.disable();
126 | correct = questionConfig[currentQuestion-1].solnsBlock.some( function(x) { /* Does user's response match one of the allowable solutions? */
127 | // var startTime = Date.now();
128 | var result = compareBlocks( topBlocks[0], x, !questionConfig[currentQuestion-1].strictVars );
129 | // console.log( "compareBlocks took " + (Date.now() - startTime) + "ms" );
130 | return( result );
131 | } );
132 | Blockly.Events.enable();
133 | }
134 |
135 | if( correct ) {
136 | goog.dom.getElement( "resultCorrect" ).style.display = "inline";
137 | goog.dom.getElement( "resultIncorrect" ).style.display = "none";
138 | if( currentQuestion == questionConfig.length ) { /* Last question finished? */
139 | goog.dom.getElement( "finished" ).style.display = "inline";
140 | } else {
141 | goog.dom.getElement( "nextButton" ).style.display = "inline";
142 | }
143 | } else {
144 | goog.dom.getElement( "resultCorrect" ).style.display = "none";
145 | goog.dom.getElement( "resultIncorrect" ).style.display = "inline";
146 | goog.dom.getElement( "nextButton" ).style.display = "none";
147 | goog.dom.getElement( "finished" ).style.display = "none";
148 | }
149 | // console.log( "checkAnswer took " + (Date.now() - startTime1) + "ms" );
150 | }
151 |
152 | var currentQuestion = 0;
153 |
154 | /* Load next question */
155 | function nextQuestion() {
156 | if( currentQuestion > 0 ) goog.dom.getElement( "q" + currentQuestion ).style.display = "none";
157 | goog.dom.getElement( "q" + (currentQuestion + 1) ).style.display = "inline";
158 | currentQuestion++;
159 | if( currentQuestion <= questionConfig.length ) { /* Note: questions numbered from 1, array indexing from 0 */
160 | /* Not up to last question yet. Load next question */
161 | Blockly.Events.disable();
162 | var loadData = questionConfig[currentQuestion-1].loadData; /* Questions numbered from 1, array indexing from 0 */
163 | if( goog.typeOf( loadData ) == "string" ) {
164 | // console.log( loadData, document.getElementById( loadData ) );
165 | Blockly.Xml.domToWorkspace( workspace, document.getElementById( loadData ) );
166 | } else if( goog.typeOf( loadData ) == "function" ) {
167 | loadData();
168 | }
169 | Blockly.Events.enable();
170 | }
171 | goog.dom.getElement( "resultCorrect" ).style.display = "none";
172 | goog.dom.getElement( "resultIncorrect" ).style.display = "inline";
173 | goog.dom.getElement( "nextButton" ).style.display = "none";
174 | goog.dom.getElement( "finished" ).style.display = "none";
175 | }
176 |
177 | var answerWorkspace;
178 | function setupAnswers() {
179 | // var time = Date.now();
180 | if( answerWorkspace ) answerWorkspace.dispose();
181 | /* For efficiency, we pre-load all answer blocks into a headless workspace */
182 | answerWorkspace = new Blockly.Workspace();
183 | for( i in questionConfig ) {
184 | questionConfig[i].solnsBlock = [];
185 | for( j in questionConfig[i].solns ) {
186 | var child = goog.dom.getFirstElementChild( goog.dom.getElement( questionConfig[i].solns[j] ) );
187 | var block = Blockly.Xml.domToBlockHeadless_( child, answerWorkspace ); /* Assuming that answer is stored as an XML node with a single block child */
188 | questionConfig[i].solnsBlock[j] = block;
189 | }
190 | }
191 | // console.log( "setupAnswers() took " + (Date.now() - time) + "ms" );
192 | }
193 |
194 | function cheat() {
195 | Blockly.Events.disable();
196 | workspace.clear();
197 | var xmlNode = goog.dom.getElement( questionConfig[currentQuestion-1].solns[0] ); /* Questions numbered from 1, array indexing from 0 */
198 | // console.log( "Cheat: ", xmlNode )
199 | Blockly.Xml.domToWorkspace(workspace,xmlNode);
200 | Blockly.Events.enable();
201 | checkAnswer();
202 | }
203 |
204 | /* Compare two blocks for logical equivalence. */
205 | function compareBlocks( b1, b2, doSubst ) {
206 | var varCount = 0;
207 | var compareBlocksR = function( b1, b2, subst1, subst2 ) /* Recursive helper function */
208 | {
209 | if( !b1 && !b2 ) return( true ); /* Both blocks are null */
210 | else if( !b1 || !b2 ) return( false ); /* One but not both are null */
211 |
212 | /* Compare the top block */
213 | if( b1.type != b2.type ) return( false ); /* Different type blocks can't match */
214 | switch( b1.type ) {
215 | case "logic_quantifier_condition_restricted":
216 | if( (b1.getFieldValue( "COMPARISON_OPERATOR" ) != b2.getFieldValue( "COMPARISON_OPERATOR" )) || (b1.getFieldValue( "BOUND" ) != b2.getFieldValue( "BOUND" )) ) return( false );
217 | /* Fall through to next case */
218 | case "logic_quantifier_set_restricted":
219 | case "logic_quantifier_set_restricted_1":
220 | case "logic_quantifier_set_restricted_2":
221 | case "logic_quantifier_condition_restricted":
222 | if( b1.getField("QUANTIFIER").getValue() != b2.getField("QUANTIFIER").getValue() ) return( false ); /* Different quantifiers */
223 | var p1 = b1.getInputTargetBlock( "PREDICATE" );
224 | var p2 = b2.getInputTargetBlock( "PREDICATE" );
225 | var newsubst1 = subst1; /* Must make copies as direct modifications to substX will propagate back up call chain due to Javascript passing arguments by reference */
226 | var newsubst2 = subst2;
227 | if( doSubst ) {
228 | newsubst1[b1.getFieldValue("VAR")] = newsubst2[b2.getFieldValue("VAR")] = "_v"+varCount; /* Substitute variables with unique identifier */
229 | varCount++;
230 | } else {
231 | if( b1.getFieldValue("VAR") != b2.getFieldValue("VAR") ) return( false );
232 | }
233 | // console.log( "Comparing quantifiers. varCount = " + varCount, newsubst1, newsubst2 );
234 | return( compareBlocksR( p1, p2, newsubst1, newsubst2 ) );
235 |
236 | case "number_comparison":
237 | var op1 = b1.getFieldValue( "COMPARISON_OPERATOR" );
238 | var op2 = b2.getFieldValue( "COMPARISON_OPERATOR" );
239 | var l1 = b1.getInputTargetBlock( "LEFTINPUT" );
240 | var l2 = b2.getInputTargetBlock( "LEFTINPUT" );
241 | var r1 = b1.getInputTargetBlock( "RIGHTINPUT" );
242 | var r2 = b2.getInputTargetBlock( "RIGHTINPUT" );
243 | var reverseOp = {"=":"=", "≠":"≠","<":">", ">":"<", "≤":"≥", "≥":"≤"}; /* Mapping of operator to reversed operator */
244 | return(
245 | (op2 == op1 && (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ))) ||
246 | (op2 == reverseOp[op1] && (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )))
247 | );
248 |
249 | case "math_arithmetic":
250 | var op1 = b1.getFieldValue( "OP" );
251 | var op2 = b2.getFieldValue( "OP" );
252 | var l1 = b1.getInputTargetBlock( "A" );
253 | var l2 = b2.getInputTargetBlock( "A" );
254 | var r1 = b1.getInputTargetBlock( "B" );
255 | var r2 = b2.getInputTargetBlock( "B" );
256 | if( op1 != op2 ) return( false );
257 | if( op1 == "MULTIPLY" || op1 == "ADD" ) {
258 | /* Transitive ops */
259 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) );
260 | } else {
261 | return( compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ) );
262 | }
263 |
264 | case "number_multiplication":
265 | var l1 = b1.getInputTargetBlock( "A" );
266 | var l2 = b2.getInputTargetBlock( "A" );
267 | var r1 = b1.getInputTargetBlock( "B" );
268 | var r2 = b2.getInputTargetBlock( "B" );
269 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) );
270 |
271 | case "number_variable_restricted":
272 | var v1 = b1.getFieldValue( "VAR" );
273 | var v2 = b2.getFieldValue( "VAR" );
274 | // console.log( "Comparing variables v1 = " + v1 + " v2 = " + v2, subst1, subst2 );
275 | if( subst1[v1] ) v1 = subst1[v1];
276 | if( subst2[v2] ) v2 = subst2[v2];
277 | // console.log( " after subst: v1 = " + v1 + " v2 = " + v2 );
278 | return( v1 == v2 );
279 |
280 | case "math_number":
281 | return( b1.getField("NUM").getValue() == b2.getField("NUM").getValue() );
282 |
283 | case "number_abs":
284 | return( compareBlocksR( b1.getInputTargetBlock( "NUM" ), b1.getInputTargetBlock( "NUM" ), subst1, subst2 ) );
285 |
286 | case "logic_connective":
287 | var op1 = b1.getFieldValue( "CONNECTIVE" );
288 | var op2 = b2.getFieldValue( "CONNECTIVE" );
289 | var l1 = b1.getInputTargetBlock( "LEFTINPUT" );
290 | var l2 = b2.getInputTargetBlock( "LEFTINPUT" );
291 | var r1 = b1.getInputTargetBlock( "RIGHTINPUT" );
292 | var r2 = b2.getInputTargetBlock( "RIGHTINPUT" );
293 | if( op1 != op2 ) return( false );
294 | if( op1 == "⇒" ) { /* Intransitive */
295 | return( compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ) );
296 | } else { /* Transitive */
297 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) );
298 | }
299 | /* Note: No attempt to take into account other logical equivalences, eg ~(A and B) == ~A or ~B */
300 |
301 | case "predicate_multiple_of_3":
302 | case "predicate_multiple_of_6":
303 | return( compareBlocksR( b1.getInputTargetBlock("NUM"), b2.getInputTargetBlock("NUM"), subst1, subst2 ) );
304 |
305 | case "set_dropdown":
306 | return( b1.getFieldValue( "SET" ) == b2.getFieldValue( "SET" ) );
307 |
308 | case "function_fn":
309 | return( true );
310 |
311 | default:
312 | console.warn( "Trying to compare unsupported block type '" + b1.type + "'" );
313 | /* TODO: use generic fieldwise comparison */
314 | return( false );
315 | }
316 | }
317 | return( compareBlocksR( b1, b2, {}, {} ) );
318 | }
319 |
--------------------------------------------------------------------------------
/js/expression-generator.js:
--------------------------------------------------------------------------------
1 | /* Routines for generating random mathematical expressions */
2 |
3 | /***************************************/
4 | /* Abstract binary tree data structure */
5 |
6 | function Node(data, left, right) {
7 | this.data = data;
8 | this.left = left;
9 | this.right = right;
10 | this.isLeaf = (left == null && right == null);
11 | }
12 |
13 | Node.prototype.toString = function() {
14 | if( this.isLeaf ) {
15 | return( "Leaf" + (this.data?"("+this.data+")":"") );
16 | } else {
17 | return( "Node" + (this.data?"("+this.data+")":"") + "[" + this.left.toString() + ", " + this.right.toString() + "]" );
18 | }
19 | };
20 |
21 | function fullBinaryTree(depth) {
22 | if( depth == 0 ) {
23 | return new Node(null,null,null); // leaf
24 | } else {
25 | return new Node(null, fullBinaryTree(depth - 1), fullBinaryTree(depth - 1));
26 | }
27 | }
28 |
29 | function getAllNodes(tree) {
30 | if( tree == null ) {
31 | return( [] );
32 | } else {
33 | return( [tree].concat(getAllNodes(tree.left), getAllNodes(tree.right)) );
34 | }
35 | }
36 |
37 | function getLeaves(tree) {
38 | return( getAllNodes(tree).filter(function(x) {return x.isLeaf;}) );
39 | }
40 |
41 | function getNonLeaves(tree) {
42 | return( getAllNodes(tree).filter(function(x) {return !x.isLeaf;}) );
43 | }
44 |
45 | function countLeaves(tree) {
46 | return( getLeaves(tree).length );
47 | }
48 |
49 | function pruneChildren(tree) {
50 | //Note: this modifies the original object!
51 | tree.left = null;
52 | tree.right = null;
53 | tree.isLeaf = true;
54 | return( tree );
55 | }
56 |
57 | /***
58 | * randomPrune: generate a random tree by pruning branches from a given tree until a desired level of complexity is reached.
59 | * arguments:
60 | * tree: a tree to be pruned. (Suggestion: start with a complete binary tree of suitable depth.)
61 | * leaftarget: the desired number of leaf nodes in the pruned tree
62 | *
63 | * Algorithm:
64 | * Choose a random non-leaf node. Count the number of leaves below it.
65 | * If we can prune those leaves without going below the desired number of leaves (leaftarget), then do so.
66 | * Otherwise, try another node.
67 | */
68 |
69 | function randomPrune(tree, leaftarget) {
70 | var currentLeafCount = countLeaves(tree);
71 | // console.log( "randomPrune( " + tree + " (" + currentLeafCount + "), " + leaftarget + ")" );
72 |
73 | if( currentLeafCount <= leaftarget ) return( tree ); // Already reached target. Nothing to do.
74 |
75 | // Otherwise, choose a random non-leaf node to prune (maybe).
76 | var nodes = getNonLeaves(tree);
77 | var i = Math.floor(Math.random()*nodes.length);
78 | var leafChange = countLeaves( nodes[i] ) - 1; // Net change in number of leaves that would result from pruning this node
79 | if( currentLeafCount - leafChange >= leaftarget ) {
80 | // Found an acceptable node. Prune it.
81 | pruneChildren( nodes[i] );
82 | if( currentLeafCount - leafChange == leaftarget ) {
83 | return( tree ); // Finished pruning.
84 | } else {
85 | return( randomPrune( tree, leaftarget ) ); // Continue pruning.
86 | }
87 | } else {
88 | // Pruning this node would result in too few leaves remaining. Try again.
89 | return( randomPrune( tree, leaftarget ) );
90 | }
91 | }
92 | /* Note: The distribution of trees produced by this algorithm is not uniform. Balanced trees occur half as frequently.
93 | Run this line in the browser console to demonstrate:
94 | counts={}; "a".repeat(1000).split("").map(function(x) {return randomPrune(fullBinaryTree(3),4).toString();}).sort().forEach(function(x) { counts[x] = (counts[x] || 0)+1; }); counts
95 | */
96 |
97 |
98 | function addTally( tallies, key ) {
99 | tallies[key] = (tallies[key] || 0) + 1;
100 | }
101 |
102 |
103 | /* Order of operations constants */
104 | //**** TODO: Re-use latex generator enums.
105 | ORDER_ATOMIC = 0;
106 | ORDER_MULTIPLICATION = 5;
107 | ORDER_DOTPRODUCT = 7;
108 | ORDER_ADDITION = 10;
109 |
110 |
111 | /* Functions to assign syntax elements to a given tree */
112 | function OpRule( name, outputType, valid, render, order, leftOp, rightOp, leftAtom, rightAtom ) {
113 | this.name = name; // human-readable name for debugging
114 | this.outputType = outputType; // output type eg "vector"
115 | this.valid = valid; // Does this construct produce valid or invalid syntax?
116 | this.render = render; // Latex code for rendering this op's symbol
117 | this.order = order; // Order of operations priority, for bracketing
118 | this.leftOp = leftOp; // List of allowable operations for LHS of expression
119 | this.rightOp = rightOp;
120 | this.leftAtom = leftAtom; // List of allowable atoms for LHS of expression
121 | this.rightAtom = rightAtom;
122 | }
123 | OpRule.prototype.toString = function() {
124 | return( this.name );
125 | }
126 |
127 | function Atom( outputType, render ) {
128 | this.name = render;
129 | this.outputType = outputType;
130 | this.render = render;
131 | this.order = ORDER_ATOMIC;
132 | this.valid = true;
133 | }
134 | Atom.prototype.toString = function() {
135 | return( this.name );
136 | }
137 |
138 | var syntaxConfig = {
139 | opScalarPlus: new OpRule( "Scalar +", "scalar", true, "+", ORDER_ADDITION, ["scalarOps"], ["scalarOps"], ["scalarVars","scalarConsts"],["scalarVars","scalarConsts"]),
140 |
141 | opVectorPlus: new OpRule( "Vector +", "vector",true,"+", ORDER_ADDITION, ["vectorOps"],["vectorOps"],["vectorTerms"],["vectorTerms"]),
142 |
143 | opVectorDot: new OpRule( "Vector dot", "scalar",true,"\\cdot", ORDER_DOTPRODUCT,["vectorOps"],["vectorOps"],["vectorTerms"],["vectorTerms"]),
144 |
145 | opScalarMult: new OpRule( "Scalar × ", "scalar",true,"",ORDER_MULTIPLICATION,["scalarOps"],["scalarOps"],["scalarVars","scalarConsts"],["scalarVars","scalarConsts"]),
146 |
147 | opScalarVectorMult: new OpRule( "Scalar × vector", "vector",true,"",ORDER_MULTIPLICATION,["scalarOps"],["vectorOps"],["scalarVars","scalarConsts"],["vectorTerms"]),
148 |
149 | opVectorPlusScalar: new OpRule( "Vector + scalar", "invalid",false,"+",ORDER_ADDITION,["vectorOps"],["scalarOps"],["vectorTerms"],["scalarVars","scalarConsts"]),
150 |
151 | scalarVars: {outputType: "scalar", atoms:["x","y","z","a","b","α","λ"]},
152 | scalarConsts: {outputType: "scalar", atoms:["1","2","3","4","5","12","0.5","1.2","0","\\sqrt{2}","\\sqrt{5}","\\frac{1}{2}","\\frac{\\sqrt{2}}{2}"]},
153 | vectorTerms: {outputType: "vector", atoms: ["\\mathbf{u}","\\mathbf{v}","\\mathbf{w}","\\overrightarrow{AB}","\\overrightarrow{OA}","\\overrightarrow{PQ}","\\mathbf{0}"]},
154 | vectorOps: ["opVectorPlus", "opScalarVectorMult"],
155 | scalarOps: ["opScalarPlus","opVectorDot", "opScalarMult"],
156 |
157 | };
158 |
159 | function allocateNode( node, types, tallies ) {
160 | // debugger;
161 | console.log( "allocateNode( " + node.toString() + ", " + types + " )" );
162 |
163 | if( !types ) {
164 | // No types specified (must be root node). Choose one at random.
165 | if( node.isLeaf ) {
166 | types = ["scalarVars", "scalarConsts", "vectorTerms"];
167 | } else {
168 | types = ["vectorOps", "scalarOps"];
169 | }
170 | }
171 |
172 | if( !tallies ) tallies = {};
173 |
174 | // Choose a type at random from the list of allowed types
175 | var type = types[Math.floor(Math.random()*types.length)];
176 |
177 | if( node.isLeaf ) {
178 | // type should be the name of a list of atoms.
179 | var atomsList = syntaxConfig[type];
180 | var atom = atomsList.atoms[Math.floor(Math.random()*atomsList.atoms.length)];
181 | if( typeof atom === "function" ) atom = atom(); // TODO: use goog.isFunction XXXXXXXXXXXXXXXXXXXXXXXXXX
182 | node.data = new Atom( atomsList.outputType, atom );
183 | console.log( " Allocated atom " + node.data.toString() );
184 | addTally( tallies, atomsList.outputType );
185 | addTally( tallies, node.data.valid?"valid":"invalid" );
186 | return( node.data );
187 | } else {
188 | // type should be the name of a list of operations.
189 | var ops = syntaxConfig[type];
190 | var op = syntaxConfig[ops[Math.floor(Math.random()*ops.length)]];
191 | node.data = op;
192 | console.log( " Allocated op " + node.data.toString() );
193 | addTally( tallies, node.data.outputType );
194 | addTally( tallies, node.data.valid?"valid":"invalid" );
195 | if( node.left.isLeaf ) {
196 | allocateNode( node.left, op.leftAtom, tallies );
197 | } else {
198 | allocateNode( node.left, op.leftOp, tallies );
199 | }
200 | if( node.right.isLeaf ) {
201 | allocateNode( node.right, op.rightAtom, tallies );
202 | } else {
203 | allocateNode( node.right, op.rightOp, tallies );
204 | }
205 | return( node.data );
206 | }
207 | }
208 |
209 | function renderNode( node ) {
210 | console.log( "renderNode( " + node.toString() + " )" );
211 | if( node.isLeaf ) {
212 | return( [node.data.render, node.data.order] );
213 | } else {
214 | var leftRender = renderNode( node.left );
215 | var rightRender = renderNode( node.right );
216 | var leftOutput = leftRender[0];
217 | if( leftRender[1] > node.data.order ) {
218 | leftOutput = "\\left( " + leftOutput + " \\right)";
219 | }
220 | var rightOutput = rightRender[0];
221 | if( rightRender[1] > node.data.order ) {
222 | rightOutput = "\\left( " + rightOutput + " \\right)";
223 | }
224 | return( [leftOutput + " " + node.data.render + " " + rightOutput, node.data.order] );
225 | }
226 | }
227 |
228 |
229 | var tree;
230 | function go() {
231 | tree = randomPrune( fullBinaryTree( targetComplexity ), targetComplexity );
232 | var tally = {};
233 | allocateNode( tree, null, tally );
234 | setContentById( "debug-output", "tally: " + JSON.stringify( tally ) + "\n" + tree.toString() );
235 | var renderResult = renderNode(tree)
236 | var output = "\\[ " + renderResult[0] + " \\]";
237 | renderLatex( output );
238 | }
239 |
240 | function debug1() {
241 | debugger;
242 | renderNode( tree );
243 | }
244 |
245 |
246 |
247 | /* The desired complexity of the randomly generated expression, measured as the # of atomic terms in the expression. */
248 | var targetComplexity = 4;
249 |
250 |
251 |
252 | /* For debugging convenience */
253 | var t2 = fullBinaryTree(2);
254 | var t3 = fullBinaryTree(3);
255 | var rt = randomPrune(fullBinaryTree(3),4);
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 | /*
264 | Notes:
265 | * global 'complexity level' variable - determining size of tree (# of leaves)
266 |
267 | Two possible approaches for generating valid/invalid expressions:
268 | 1. Decide initially whether expression should be valid or invalid. Decide at which leaf the error should lie. Pass this to the generator.
269 | Advantage: can produce any desired distribution of valid/invalid, or where the error is.
270 | 2. At each generation step, randomly decide whether to make it valid or invalid. Once one invalid leaf is produced, all others should be valid. Tune the probability so that we get a desired overall valid/invalid rate (binomial distribution).
271 |
272 | Approach 1 probably better?
273 |
274 |
275 |
276 |
277 |
278 | */
--------------------------------------------------------------------------------
/js/field_mathjax.js:
--------------------------------------------------------------------------------
1 | /** Based on Blockly field_image.js **/
2 |
3 | /* This does not work in Internet Explorer, due to lack of support for SVG foreignObject element. */
4 |
5 | 'use strict';
6 |
7 | goog.provide('Blockly.FieldMathJax');
8 |
9 | goog.require('Blockly.Field');
10 | goog.require('goog.dom');
11 | goog.require('goog.math.Size');
12 | goog.require('goog.userAgent');
13 |
14 |
15 | /**
16 | * Class for displaying a mathematical expression rendered by MathJax.
17 | * @param {string} src A mathematical expression, in latex, asciimath or any other format supported by MathJax
18 | * @param {string=} opt_alt Optional alt text for when block is collapsed.
19 | * @param {bool=} opt_initialsource If true, show the alt text during initial rendering.
20 | * @extends {Blockly.Field}
21 | * @constructor
22 | */
23 | Blockly.FieldMathJax = function(src, opt_alt, opt_initial) {
24 | this.sourceBlock_ = null;
25 | this.text_ = opt_alt || '';
26 | this.src_ = src;
27 | this.size_ = new goog.math.Size(0, 0); /* Size cannot be determined until after rendering */
28 | this.initialSource_ = (opt_initial == true);
29 | };
30 | goog.inherits(Blockly.FieldMathJax, Blockly.Field);
31 |
32 | Blockly.FieldMathJax.svgCache_ = {};
33 |
34 | /* TODO: cache clean-up? */
35 |
36 | Blockly.FieldMathJax.addToCache = function( src, node ) {
37 | Blockly.FieldMathJax.svgCache_[src] = {node: node.cloneNode(true), width: node.offsetWidth, height: node.offsetHeight};
38 | }
39 |
40 | /**
41 | * Rectangular mask used by Firefox.
42 | * @type {Element}
43 | * @private
44 | */
45 | Blockly.FieldMathJax.prototype.rectElement_ = null;
46 |
47 | /**
48 | * Editable fields are saved by the XML renderer, non-editable fields are not.
49 | */
50 | Blockly.FieldMathJax.prototype.EDITABLE = false;
51 |
52 | /**
53 | * Install this field on a block.
54 | * @param {!Blockly.Block} block The block containing this text.
55 | */
56 | Blockly.FieldMathJax.prototype.init = function() {
57 | if (this.foreignObject_) {
58 | // Field has already been initialized once.
59 | return;
60 | }
61 | // Build the DOM.
62 | this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
63 | if (!this.visible_) {
64 | this.fieldGroup_.style.display = 'none';
65 | }
66 |
67 | this.foreignElement_ = Blockly.createSvgElement('foreignObject',
68 | {}, this.fieldGroup_);
69 | this.setValue(this.src_);
70 | if (goog.userAgent.GECKO) {
71 | // Due to a Firefox bug which eats mouse events on image elements,
72 | // a transparent rectangle needs to be placed on top of the image.
73 | // TODO: Check if this bug holds for foreignelement also.
74 | this.rectElement_ = Blockly.createSvgElement('rect',
75 | {'fill-opacity': 0}, this.fieldGroup_);
76 | }
77 | this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
78 |
79 | // Configure the field to be transparent with respect to tooltips.
80 | var topElement = this.rectElement_ || this.foreignElement_;
81 | topElement.tooltip = this.sourceBlock_;
82 | Blockly.Tooltip.bindMouseEvents(topElement);
83 | };
84 |
85 | Blockly.FieldMathJax.prototype.setSize_ = function(width, height) {
86 | this.width_ = width;
87 | this.height_ = height;
88 | this.size_ = new goog.math.Size(this.width_,
89 | this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y);
90 |
91 | this.foreignElement_.setAttribute("width", width + "px");
92 | this.foreignElement_.setAttribute("height", height + "px");
93 |
94 | if( this.rectElement_ ) {
95 | this.rectElement_.setAttribute("width", width + "px");
96 | this.rectElement_.setAttribute("height", height + "px");
97 | }
98 | };
99 |
100 | /**
101 | * Set the latex/asciimath source of the expression.
102 | * @param {?string} src New source.
103 | * @override
104 | */
105 | Blockly.FieldMathJax.prototype.setValue = function(src) {
106 | if (src === null) {
107 | // No change if null.
108 | return;
109 | }
110 | this.src_ = src;
111 | if( !this.foreignElement_ ) {
112 | /* Block hasn't been initialised yet. Store string for later. */
113 | return;
114 | }
115 |
116 | /* If this src has been rendered before, re-use svg from cache */
117 | if( Blockly.FieldMathJax.svgCache_[src] ) {
118 | goog.dom.removeNode( this.mathDiv_ );
119 | var cacheData = Blockly.FieldMathJax.svgCache_[src];
120 | var node = cacheData.node.cloneNode(true);
121 | this.mathDiv_ = node;
122 | this.setSize_( cacheData.width, cacheData.height );
123 |
124 | this.foreignElement_.appendChild( node );
125 | node.style.position = "relative";
126 | node.style.visibility = "visible";
127 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */
128 | if( goog.userAgent.WEBKIT ) {
129 | node.style.position = "fixed";
130 | }
131 | return;
132 | }
133 |
134 | /* Otherwise, render the math using MathJax */
135 |
136 | if( !this.mathDiv_ && this.initialSource_ ) {
137 | /* Temporarily display latex source */
138 | this.mathDiv_ = document.createElement("div");
139 | this.mathDiv_.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
140 | this.mathDiv_.innerHTML = this.text_;
141 | this.mathDiv_.style.visibility = "hidden";
142 | this.mathDiv_.style.position = "absolute";
143 | this.mathDiv_.style.margin = "0";
144 | this.mathDiv_.style.fontWeight = "bold";
145 | document.body.appendChild( this.mathDiv_ );
146 | this.setSize_( this.mathDiv_.offsetWidth, this.mathDiv_.offsetHeight );
147 | this.foreignElement_.appendChild( this.mathDiv_ );
148 | this.mathDiv_.style.visibility = "visible";
149 | this.mathDiv_.style.position = "relative";
150 |
151 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */
152 | if( goog.userAgent.WEBKIT ) {
153 | this.mathDiv_.style.position = "fixed";
154 | }
155 | }
156 |
157 | var newDiv = document.createElement("div");
158 | newDiv.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
159 | newDiv.innerHTML = "\\(\\displaystyle " + src + " \\)"; /* Allow Latex only */
160 | // newDiv.innerHTML = "\\/( " + src + " \\/)"; /* Allow Latex and asciimath */
161 |
162 | // Temporarily add div to document so that we can get its size.
163 | // Set visibility to hidden so it will not display.
164 | newDiv.style.visibility = "hidden";
165 | newDiv.style.position = "absolute";
166 | document.body.appendChild( newDiv );
167 |
168 | var t = this;
169 | var callback = function() {
170 | /* Called when MathJax has finished rendering math expression */
171 | t.setSize_( newDiv.offsetWidth, newDiv.offsetHeight );
172 |
173 | goog.dom.removeNode( t.mathDiv_ );
174 | t.foreignElement_.appendChild( newDiv );
175 | newDiv.style.position = "relative";
176 | newDiv.style.visibility = "visible";
177 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */
178 | if( goog.userAgent.WEBKIT ) {
179 | newDiv.style.position = "fixed";
180 | }
181 |
182 | t.mathDiv_ = newDiv;
183 |
184 | /* Re-render block in case size has changed */
185 | t.sourceBlock_.render();
186 |
187 | /* If block is in a flyout, re-flow the flyout due to block size change */
188 | if( t.sourceBlock_.workspace.isFlyout ) {
189 | t.sourceBlock_.workspace.targetWorkspace.flyout_.reflow(); // A bit hackish
190 | }
191 |
192 | /* Add rendered svg to cache */
193 | Blockly.FieldMathJax.addToCache( src, newDiv );
194 |
195 | };
196 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, newDiv, callback]);
197 | };
198 |
199 | /**
200 | * Get the latex/asciimath source of this expression.
201 | * @return {string} Current text.
202 | * @override
203 | */
204 | Blockly.FieldMathJax.prototype.getValue = function() {
205 | return this.src_;
206 | };
207 |
208 | /**
209 | * Dispose of all DOM objects belonging to this text.
210 | */
211 | Blockly.FieldMathJax.prototype.dispose = function() {
212 | goog.dom.removeNode(this.fieldGroup_);
213 | this.fieldGroup_ = null;
214 | this.foreignElement_ = null;
215 | this.mathDiv_ = null;
216 | this.rectElement_ = null;
217 | };
218 |
219 | /**
220 | * Change the tooltip text for this field.
221 | * @param {string|!Element} newTip Text for tooltip or a parent element to
222 | * link to for its tooltip.
223 | */
224 | Blockly.FieldMathJax.prototype.setTooltip = function(newTip) {
225 | var topElement = this.rectElement_ || this.foreignElement_;
226 | topElement.tooltip = newTip;
227 | };
228 |
229 | /**
230 | * Set the alt text of this field.
231 | * @param {?string} alt New alt text.
232 | * @override
233 | */
234 | Blockly.FieldMathJax.prototype.setText = function(alt) {
235 | if (alt === null) {
236 | // No change if null.
237 | return;
238 | }
239 | this.text_ = alt;
240 | };
241 |
242 | /**
243 | * Field is fixed width, no need to render.
244 | * @private
245 | */
246 | // TODO: Implement this?
247 | Blockly.FieldMathJax.prototype.render_ = function() {
248 | // NOP
249 | };
250 |
--------------------------------------------------------------------------------
/js/field_mathvariable.js:
--------------------------------------------------------------------------------
1 | /**** Code for custom variable dropdowns ****/
2 | 'use strict';
3 |
4 | /* Set up inheritance */
5 | goog.provide('Blockly.FieldMathVariable');
6 |
7 | goog.require('Blockly.FieldFlydown');
8 | goog.require('Blockly.Msg');
9 | goog.require('Blockly.Variables');
10 | goog.require('goog.string');
11 |
12 | /**
13 | * A text input for a math variable. Notable characteristics:
14 | * - variable names are restricted to one alphabetic character
15 | * - variables are typed
16 | * - hovering over the field will (optionally) produce a flydown with variable block for easy access
17 | * Based on FieldFlydown from App Inventor.
18 | * @param {?bool} opt_flydown If true, on mouseover a flyout will appear with a block for this variable, for easy access.
19 | * @param {?bool} opt_strict If true, field highlights out-of-scope variables which are not quantified or predefined global variables.
20 | */
21 | Blockly.FieldMathVariable = function(varname, opt_type, opt_validator, opt_flydown, opt_strict) {
22 | Blockly.FieldMathVariable.superClass_.constructor.call(this, varname,
23 | true, /* isEditable */
24 | Blockly.FieldFlydown.DISPLAY_BELOW, /* displayLocation */
25 | (opt_validator != null ? opt_validator : Blockly.FieldMathVariable.validator_),
26 | 1 /* opt_maxlength - maximum allowed input length */
27 | );
28 | this.enforceScope_ = !!opt_strict;
29 | this.useFlydown_ = !!opt_flydown;
30 | this.type_ = opt_type;
31 | this.onchange = function() {console.log("onchange", this.getValue()); };
32 | };
33 | goog.inherits(Blockly.FieldMathVariable, Blockly.FieldFlydown);
34 |
35 | Blockly.FieldMathVariable.prototype.isVariable_ = true;
36 |
37 | /* CSS class for the flydown */
38 | Blockly.FieldMathVariable.prototype.flyoutCSSClassName = 'blocklyFieldMathVariableFlydown';
39 |
40 | // Called when field is installed on a block.
41 | Blockly.FieldMathVariable.prototype.init = function() {
42 | if( this.useFlydown_ ) {
43 | Blockly.FieldMathVariable.superClass_.init.call( this );
44 | } else {
45 | /* Skip FieldFlydown init and go straight to FieldTextInput init */
46 | Blockly.FieldFlydown.superClass_.init.call( this );
47 | }
48 | var workspace = this.sourceBlock_.workspace;
49 | if( !workspace.isFlyout && !workspace.fieldMathVariableEventHandler_ ) {
50 | /* Install onchange handler to detect variable conflicts on block addition/removal */
51 | workspace.fieldMathVariableEventHandler_ = function(event) {
52 | if( event.type == Blockly.Events.CREATE || event.type == Blockly.Events.DELETE )
53 | {
54 | Blockly.FieldMathVariable.checkVars_( workspace );
55 | }
56 | }
57 | workspace.addChangeListener( workspace.fieldMathVariableEventHandler_ );
58 | }
59 | };
60 |
61 |
62 | /* Restrict input to one character alphabetic */
63 | Blockly.FieldMathVariable.validator_ = function(text) {
64 | return( (text.length == 1 && text.search(/^[a-zA-Zα-ωΑ-Ωϵ]$/) == 0) ? text : null ); // Regexp: string consists of a single alphabetic Latin or Greek character.
65 | }
66 |
67 | Blockly.FieldMathVariable.prototype.setValue = function(text) {
68 | var oldvar = this.getValue();
69 | Blockly.FieldMathVariable.superClass_.setValue.call(this, text);
70 | if( this.sourceBlock_ && !this.sourceBlock_.isInFlyout ) {
71 | /* Check for variable collisions */
72 | Blockly.FieldMathVariable.checkVars_( this.sourceBlock_.workspace );
73 | }
74 | }
75 |
76 | Blockly.FieldMathVariable.checkVars_ = function(workspace) {
77 | // debugger;
78 | var varDB = {};
79 | /* Iterate over all variables from the workspace, and check if their types match */
80 | var blocks = workspace.getAllBlocks();
81 | for( var i in blocks ) {
82 | var b = blocks[i];
83 | for( var j in b.inputList ) {
84 | var input = b.inputList[j];
85 | for( var k in input.fieldRow ) {
86 | var field = input.fieldRow[k];
87 | if( field instanceof Blockly.FieldMathVariable ) {
88 | var vname = field.getValue();
89 | if( varDB[vname] ) {
90 | varDB[vname][2].push(field);
91 | if( varDB[vname][0] != field.type_ ) varDB[vname][1] = true;
92 | } else {
93 | varDB[vname] = [field.type_, false, [field]]; /* type, conflicting, list of fields */
94 | }
95 | }
96 | }
97 | }
98 | }
99 | // console.log( "checkVars_", varDB );
100 | for( var i in varDB ) {
101 | if( varDB[i][1] ) {
102 | /* Highlight fields in red */
103 | varDB[i][2].map( function(f) {f.addCSSClass("blocklyMathVariableError");} );
104 | } else {
105 | /* Remove red highlight */
106 | varDB[i][2].map( function(f) {f.removeCSSClass("blocklyMathVariableError");} );
107 | }
108 | }
109 | }
110 |
111 | Blockly.FieldMathVariable.prototype.flydownBlocksXML_ = function() {
112 | /* Mapping from type to corresponding variable block name */
113 | var typemap = {
114 | "Number": "number_variable",
115 | "Set": "set_variable",
116 | "Boolean": "logic_prop_variable",
117 | "Function": "function_variable",
118 | "Abstract": "abstract_variable"
119 | };
120 |
121 | var blocktype = typemap[this.type_];
122 |
123 | var getterSetterXML =
124 | '
' +
125 | '' +
126 | '' +
127 | this.getText() +
128 | ' ' +
129 | ' ' +
130 | ' ';
131 | return getterSetterXML;
132 | }
133 |
--------------------------------------------------------------------------------
/js/field_mathvariable_old.js:
--------------------------------------------------------------------------------
1 | /**** Code for custom variable dropdowns ****/
2 | /* This version uses a dropdown menu based on Blockly's FieldVariable. */
3 | 'use strict';
4 |
5 | /* Set up inheritance */
6 | goog.provide('Blockly.FieldMathVariable');
7 |
8 | goog.require('Blockly.FieldDropdown');
9 | goog.require('Blockly.Flydown');
10 | goog.require('Blockly.Msg');
11 | goog.require('Blockly.Variables');
12 | goog.require('goog.string');
13 |
14 | /**
15 | * A dropdown menu for a math variable. Differences to standard FieldVariable:
16 | * - variable names are restricted to one alphabetic character
17 | * - variables are typed
18 | * @param {?bool} opt_strict If true, dropdown shows only variables which are either quantified, or predefined global variables.
19 | * @param {?bool} opt_flydown If true, on mouseover a flyout will appear with a block for this variable, for easy access.
20 | */
21 | Blockly.FieldMathVariable = function(varname, opt_type, opt_validator, opt_flydown, opt_strict) {
22 | Blockly.FieldMathVariable.superClass_.constructor.call(this,
23 | varname, opt_validator, opt_type);
24 | this.menuGenerator_ = Blockly.FieldMathVariable.dropdownCreate;
25 | this.enforceScope_ = !!opt_strict;
26 | this.useFlydown_ = !!opt_flydown;
27 | };
28 | goog.inherits(Blockly.FieldMathVariable, Blockly.FieldVariable);
29 |
30 | // Called when field is installed on a block.
31 | Blockly.FieldMathVariable.prototype.init = function(block) {
32 | Blockly.FieldMathVariable.superClass_.init.call( this, block );
33 |
34 | if( this.useFlydown_ ) {
35 | Blockly.Flydown.workspaceInit( block.workspace ); // Set up Flydown for this workspace
36 | this.mouseOverWrapper_ =
37 | Blockly.bindEvent_(this.fieldGroup_, 'mouseover', this, this.onMouseOver_);
38 | this.mouseOutWrapper_ =
39 | Blockly.bindEvent_(this.fieldGroup_, 'mouseout', this, this.onMouseOut_);
40 | }
41 | };
42 |
43 | /**
44 | * Milliseconds to wait before showing flydown after mouseover event on flydown field.
45 | */
46 | Blockly.FieldMathVariable.flydownTimeout = 750;
47 |
48 | /**
49 | * Process ID for timer event to show flydown (scheduled by mouseover event)
50 | * @type {number}
51 | */
52 | Blockly.FieldMathVariable.showPid_ = 0;
53 |
54 | // Note: To be correct, the next two should be per-workspace. App Inventor code assumes only one (non-flyout) Blockly workspace is present.
55 | /**
56 | * The flydown which is currently active (if any)
57 | */
58 | Blockly.FieldMathVariable.activeFlydown_ = null;
59 |
60 | /**
61 | * Which instance of FieldMathVariable (or a subclass) is an open flydown attached to?
62 | * @type {Blockly.FieldMathVariable (or subclass)}
63 | * @private
64 | */
65 | Blockly.FieldMathVariable.flydownOwner_ = null;
66 |
67 | Blockly.FieldMathVariable.prototype.onMouseOver_ = function(e) {
68 | console.log( "FieldMathVariable onmouseover" );
69 | if( !this.sourceBlock_.isInFlyout ) { // [lyn, 10/22/13] No flydowns in a flyout!
70 | var field = this;
71 | var callback = function() {
72 | Blockly.FieldMathVariable.showPid_ = 0;
73 | field.showFlydown_();
74 | };
75 | if( Blockly.FieldMathVariable.showPid_ ) window.clearTimeout( Blockly.FieldMathVariable.showPid_ );
76 | Blockly.FieldMathVariable.showPid_ = window.setTimeout( callback, Blockly.FieldMathVariable.flydownTimeout );
77 | // This event has been handled. No need to bubble up to the document.
78 | }
79 | e.stopPropagation();
80 | };
81 |
82 | Blockly.FieldMathVariable.prototype.onMouseOut_ = function(e) {
83 | console.log( "FieldMathVariable onmouseout" );
84 | // Clear any pending timer event to show flydown
85 | window.clearTimeout(Blockly.FieldMathVariable.showPid_);
86 | Blockly.FieldMathVariable.showPid_ = 0;
87 | e.stopPropagation();
88 | };
89 |
90 | Blockly.FieldMathVariable.prototype.showEditor_ = function() {
91 | console.log( "FieldMathVariable showEditor_" );
92 | Blockly.FieldMathVariable.hideFlydown();
93 | Blockly.FieldMathVariable.superClass_.showEditor_.call( this );
94 | }
95 |
96 | /**
97 | * Creates a Flydown containing a block for the current variable.
98 | */
99 | Blockly.FieldMathVariable.prototype.showFlydown_ = function() {
100 | console.log("FieldMathVariable show Flydown");
101 | if( !this.getValue() || this.getValue() == "" ) return; // No flydown if no variable currently selected
102 |
103 | Blockly.hideChaff(); // Hide open context menus, dropDowns, flyouts, and other flydowns
104 | Blockly.FieldMathVariable.flydownOwner_ = this; // Remember field to which flydown is attached
105 | var flydown = this.sourceBlock_.workspace.flydown_;
106 | Blockly.FieldMathVariable.activeFlydown_ = flydown;
107 | var blocksXMLText = '
' + this.getValue() + ' ';
108 | var blocksDom = Blockly.Xml.textToDom(blocksXMLText);
109 | var blocksXMLList = goog.dom.getChildren(blocksDom); // List of blocks for flydown
110 | var xy = Blockly.getSvgXY_(this.borderRect_, this.sourceBlock_.workspace);
111 | var borderBBox = this.borderRect_.getBBox();
112 | var x = xy.x;
113 | var y = xy.y;
114 | y = y + borderBBox.height;
115 | flydown.showAt(blocksXMLList, x, y);
116 | };
117 |
118 | /**
119 | * Hide the flydown menu and squash any timer-scheduled flyout creation
120 | */
121 | Blockly.FieldMathVariable.hideFlydown = function() {
122 | console.log( "hideFlydown_: ", Blockly.FieldMathVariable.showPid_, Blockly.FieldMathVariable.activeFlydown_ );
123 | // Clear any pending timer event to show flydown
124 | if( Blockly.FieldMathVariable.showPid_ != 0 ) window.clearTimeout(Blockly.FieldMathVariable.showPid_);
125 | // Clear any displayed flydown
126 | if( Blockly.FieldMathVariable.activeFlydown_ ) Blockly.FieldMathVariable.activeFlydown_.hide();
127 | if( Blockly.FieldMathVariable.flydownOwner_ ) Blockly.FieldMathVariable.flydownOwner_ = null;
128 | };
129 |
130 | /**
131 | * Close the flydown and dispose of all UI.
132 | */
133 | Blockly.FieldMathVariable.prototype.dispose = function() {
134 | if (Blockly.FieldMathVariable.flydownOwner_ == this) {
135 | Blockly.FieldMathVariable.hideFlydown();
136 | }
137 | // Call parent's destructor.
138 | Blockly.FieldMathVariable.superClass_.dispose.call( this );
139 | };
140 |
141 |
142 | /**
143 | * Return a sorted list of variable names for variable dropdown menus.
144 | * Include a special option at the end for creating a new variable name.
145 | * @return {!Array.
} Array of variable names.
146 | */
147 | Blockly.FieldMathVariable.dropdownCreate = function() {
148 | var variables = [];
149 | if (this.sourceBlock_ && this.sourceBlock_.workspace) {
150 | if( this.enforceScope_ ) {
151 | /* Recursively collect variables from parents */
152 | var parent = this.sourceBlock_;
153 | while( parent = parent.parentBlock_ ) {
154 | if( parent.getVars &&
155 | !(parent.isQuantifier &&
156 | parent.getInput( "SCOPE" ) && parent.getInput( "SCOPE" ).connection.targetBlock() &&
157 | parent.getInput( "SCOPE" ).connection.targetBlock().isParentOf( this.sourceBlock_ )) )
158 | {
159 | // Variable defined by a quantifier is not available to blocks in the 'SCOPE' input
160 | // ie, you can't have "Forall x > x" or "Exists x in { y: y != x}"
161 | variables = variables.concat( parent.getVars() );
162 | }
163 | }
164 | } else {
165 | var blocks = this.sourceBlock_.workspace.getAllBlocks();
166 | for (var x = 0; x < blocks.length; x++) {
167 | if (blocks[x].getVars) {
168 | variables = variables.concat( blocks[x].getVars() );
169 | }
170 | }
171 | }
172 | // Add predefined global variables
173 | var workspace = this.sourceBlock_.workspace;
174 | if( workspace.globalVariables ) {
175 | variables = variables.concat( workspace.globalVariables );
176 | }
177 | } else {
178 | variables = [];
179 | }
180 |
181 | var variableList = [];
182 | for (var y = 0; y < variables.length; y++) {
183 | var v = variables[y];
184 | if( goog.isString(v) ) {
185 | /* Variable is untyped */
186 | if( !this.type_ ) {
187 | var varName = v;
188 | // Variable name may be null if the block is only half-built.
189 | if (varName && variableList.indexOf(varName) == -1) {
190 | variableList.push( varName );
191 | }
192 | }
193 | } else if( goog.isArray(v) ) {
194 | /* Variable is typed - v is an array [name, type] */
195 | var varName = v[0];
196 | var varType = v[1];
197 | // Variable name may be null if the block is only half-built.
198 | if (varName && (!this.type_ || (this.type_ == varType)) && variableList.indexOf(varName) == -1) {
199 | variableList.push( varName );
200 | }
201 | }
202 | }
203 |
204 | if( !this.enforceScope_ ) {
205 | // Ensure that the currently selected variable is an option.
206 | var name = this.getText();
207 | if (name && variableList.indexOf(name) == -1) {
208 | variableList.push(name);
209 | }
210 | }
211 | variableList.sort(goog.string.caseInsensitiveCompare);
212 | if( !this.enforceScope_ ) {
213 | // variableList.push(Blockly.Msg.RENAME_VARIABLE);
214 | variableList.push(Blockly.Msg.NEW_VARIABLE);
215 | }
216 | // Variables are not language-specific, use the name as both the user-facing
217 | // text and the internal representation.
218 | var options = [];
219 | for (var x = 0; x < variableList.length; x++) {
220 | options[x] = [variableList[x], variableList[x]];
221 | }
222 | return options;
223 | };
224 |
225 | /* We want to use our own 'new variable' dialog but the normal FieldVariable dropdownChange is a static member function so we have to hook it in the hard way. */
226 | Blockly.FieldMathVariable.prototype.setValidator = function(handler) {
227 | var wrappedHandler;
228 | if (handler) {
229 | // Wrap the user's change handler together with the variable rename handler.
230 | wrappedHandler = function(value) {
231 | var v1 = handler.call(this, value);
232 | if (v1 === null) {
233 | var v2 = v1;
234 | } else {
235 | if (v1 === undefined) {
236 | v1 = value;
237 | }
238 | var v2 = Blockly.FieldMathVariable.dropdownChange.call(this, v1);
239 | if (v2 === undefined) {
240 | v2 = v1;
241 | }
242 | }
243 | return v2 === value ? undefined : v2;
244 | };
245 | } else {
246 | wrappedHandler = Blockly.FieldMathVariable.dropdownChange;
247 | }
248 | Blockly.FieldVariable.superClass_.setValidator.call(this, wrappedHandler); // Skipping FieldMathVariable setValidator as it will use default dropdownChange instead
249 | };
250 |
251 | Blockly.FieldMathVariable.promptCallback_ = function(text) {
252 | if( text ) Blockly.FieldMathVariable.promptOwner_.setValue( text );
253 | };
254 | Blockly.FieldMathVariable.promptValidator_ = function(text) {
255 | return( text.length == 1 && text.search(/^[a-zA-Z]$/) == 0 ); // Regexp: string consists of a single alphabetic character. TODO: support Greek letters.
256 | };
257 | Blockly.FieldMathVariable.promptOwner_ = null;
258 | Blockly.FieldMathVariable.prompt_ = new goog.ui.Prompt(Blockly.Msg.NEW_VARIABLE_TITLE, "", Blockly.FieldMathVariable.promptCallback_);
259 | Blockly.FieldMathVariable.prompt_.setValidationFunction( Blockly.FieldMathVariable.promptValidator_ );
260 |
261 | Blockly.FieldMathVariable.dropdownChange = function(text) {
262 | if( text == Blockly.Msg.NEW_VARIABLE ) {
263 | Blockly.FieldMathVariable.promptOwner_ = this;
264 | var t = this;
265 | Blockly.hideChaff();
266 | Blockly.FieldMathVariable.prompt_.setDefaultValue( this.getValue() );
267 | Blockly.FieldMathVariable.prompt_.setVisible(true);
268 | Blockly.FieldMathVariable.prompt_.getInputElement().setAttribute( "maxlength", 1 );
269 | return( null );
270 | } else {
271 | return( Blockly.FieldVariable.dropdownChange.call(this, text) );
272 | }
273 | }
--------------------------------------------------------------------------------
/js/latex-generator.js:
--------------------------------------------------------------------------------
1 | /* Code generator to produce latex from math blocks */
2 | /* Adapted from blockly/generators/javascript.js */
3 |
4 | Blockly.Latex = new Blockly.Generator('Latex');
5 |
6 |
7 | /**
8 | * Order of operation ENUMs.
9 | */
10 | Blockly.Latex.ORDER_ATOMIC = 0; // Literals, variables
11 |
12 | /* Logic/boolean operations */
13 | Blockly.Latex.ORDER_FORALL = 1; // forall X in __ ...
14 | Blockly.Latex.ORDER_EXISTS = 1; // exists X in __ ...
15 | Blockly.Latex.ORDER_QUANTIFIER_SET = 1; // exists __ in XXX ...
16 | Blockly.Latex.ORDER_QUANTIFIER_NUM = 1; // exists __ > XXX ...
17 | Blockly.Latex.ORDER_QUANTIFIER_SCOPE = 1; // exists __ _ XXX ...
18 | Blockly.Latex.ORDER_NOT = 2; // NOT a
19 | Blockly.Latex.ORDER_IFF = 4; // a IFF b
20 | Blockly.Latex.ORDER_IMPLIES = 4; // a IMPLIES b
21 | Blockly.Latex.ORDER_AND = 3; // a AND b
22 | Blockly.Latex.ORDER_OR = 4; // a OR b
23 |
24 | /* Comparisons - all have equal precedence */
25 | Blockly.Latex.ORDER_EQUAL = 10; // a = b
26 | Blockly.Latex.ORDER_NOTEQUAL = 10; // a = b
27 | Blockly.Latex.ORDER_SUBSET = 10; // A subset B
28 | Blockly.Latex.ORDER_SUBSETEQ = 10; // A subseteq B
29 | Blockly.Latex.ORDER_LESS = 10; // A < B
30 | Blockly.Latex.ORDER_LESSEQ = 10; // A <= B
31 | Blockly.Latex.ORDER_GREATER = 10; // A > B
32 | Blockly.Latex.ORDER_GREATEREQ = 10; // A >= B
33 | Blockly.Latex.ORDER_COMPARISON = 10; // covers all comparisons: =, <, subseteq, etc
34 |
35 |
36 | /* Set operations */
37 | Blockly.Latex.ORDER_SET_COMPLEMENT = 2; // set complement A^c
38 | Blockly.Latex.ORDER_SET_EXCLUSION = 3; // set exclusion A \ B
39 | Blockly.Latex.ORDER_INTERSECTION = 4; // set intersection
40 | Blockly.Latex.ORDER_UNION = 5; // set union
41 | Blockly.Latex.ORDER_SET_ELEMENT = 20; // X element of __
42 | Blockly.Latex.ORDER_SET_SET = 20; // __ element of X
43 | Blockly.Latex.ORDER_SET_MEMBERSHIP = 21;// (XX element of YY)
44 |
45 | /* Set bounding operations - all have equal precedence */
46 | Blockly.Latex.ORDER_SUP = 1;
47 | Blockly.Latex.ORDER_INF = 1;
48 | Blockly.Latex.ORDER_MIN = 1;
49 | Blockly.Latex.ORDER_MAX = 1;
50 |
51 | /* Number operations */
52 | Blockly.Latex.ORDER_FUNCTION = 1; // Function application eg sin(...)
53 | Blockly.Latex.ORDER_MULT_INVERSE = 2;
54 | Blockly.Latex.ORDER_ADD_INVERSE = 3;
55 | Blockly.Latex.ORDER_POWER = 4;
56 | Blockly.Latex.ORDER_MULTIPLICATION = 5;
57 | Blockly.Latex.ORDER_DIVISION = 5;
58 | Blockly.Latex.ORDER_ADDITION = 6;
59 | Blockly.Latex.ORDER_SUBTRACTION = 6;
60 |
61 | Blockly.Latex.ORDER_NONE = 99; // (...)
62 |
63 | /* Text to appear as a placeholder for any empty slots */
64 | Blockly.Latex.blank = "\\text{[blank]}";
65 |
66 | /* Code for left & right parentheses */
67 | Blockly.Latex.leftParen = "\\left(";
68 | Blockly.Latex.rightParen = "\\right)";
69 |
70 | /* Latex uses \\ for line break */
71 | Blockly.Latex.lineSeparator = "\\\\\n";
72 |
73 | /* Adds comments or other annotations to code. Not needed in this case. */
74 | Blockly.Latex.scrub_ = function(block, code) {
75 | return( code );
76 | }
77 |
78 | /* Adds semicolons, linebreaks etc at end of line. Not needed in this case. */
79 | Blockly.Latex.scrubNakedValue = function(line) {
80 | return( line );
81 | }
82 |
83 | /* Add any prelude, eg variable definitions. Not needed in this case. */
84 | Blockly.Latex.finish = function(code) {
85 | return( code );
86 | }
87 |
88 | /* Mapping from field names (Unicode symbols or text labels) to latex command and precedence value */
89 | Blockly.Latex.symbolToLatex = {
90 | '∧': ["\\wedge",Blockly.Latex.ORDER_AND],
91 | '∨': ["\\vee",Blockly.Latex.ORDER_OR],
92 | '⇒': ["\\implies",Blockly.Latex.ORDER_IMPLIES],
93 | '⇔': ["\\iff",Blockly.Latex.ORDER_IFF],
94 | '∨': ["\\vee",Blockly.Latex.ORDER_OR],
95 | '∨': ["\\vee",Blockly.Latex.ORDER_OR],
96 | '∪': ["\\cup", Blockly.Latex.ORDER_UNION],
97 | '∩': ["\\cap", Blockly.Latex.ORDER_INTERSECTION],
98 | '\\': ["\\setminus", Blockly.Latex.ORDER_SET_EXCLUSION],
99 | 'SUBSET': ["\\subset", Blockly.Latex.ORDER_SUBSET],
100 | 'SUBSETEQ': ["\\subseteq", Blockly.Latex.ORDER_SUBSETEQ],
101 | '=': ["=", Blockly.Latex.ORDER_EQUAL],
102 | '≠': ["\\not=", Blockly.Latex.ORDER_NOTEQUAL],
103 | '<': ["<", Blockly.Latex.ORDER_LESS],
104 | '>': [">", Blockly.Latex.ORDER_GREATER],
105 | '≤': ["\\leq", Blockly.Latex.ORDER_LESSEQ],
106 | '≥': ["\\geq", Blockly.Latex.ORDER_GREATEREQ],
107 | 'SUPREMUM': ["\\sup", Blockly.Latex.ORDER_SUP],
108 | 'INFIMUM': ["\\inf", Blockly.Latex.ORDER_INF],
109 | 'MINIMUM': ["\\min", Blockly.Latex.ORDER_MIN],
110 | 'MAXIMUM': ["\\max", Blockly.Latex.ORDER_MAX],
111 | 'ADD': ["+", Blockly.Latex.ORDER_ADDITION],
112 | 'MINUS': ["-", Blockly.Latex.ORDER_SUBTRACTION],
113 | 'MULTIPLY': ["\\times", Blockly.Latex.ORDER_MULTIPLICATION],
114 | 'DIVIDE': ["/", Blockly.Latex.ORDER_DIVISION],
115 | 'POWER': ["^", Blockly.Latex.ORDER_POWER],
116 | '∀': ["\\forall", Blockly.Latex.ORDER_FORALL],
117 | '∃': ["\\exists", Blockly.Latex.ORDER_EXISTS],
118 | 'FORALL': ["\\forall", Blockly.Latex.ORDER_FORALL],
119 | 'EXISTS': ["\\exists", Blockly.Latex.ORDER_EXISTS],
120 | '∈': ["\\in", Blockly.Latex.ORDER_SET_MEMBERSHIP],
121 | 'BLANK': [Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC], // For dropdowns with nothing selection
122 | };
123 |
124 |
125 | Blockly.Latex.init = function(workspace) {
126 | };
127 |
128 | /* Quantifiers: forall x in set P(x), forall x > n P(x) etc */
129 | Blockly.Latex['logic_quantifier'] = function(block) {
130 | var order = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][1]
131 | var str = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][0] + " ";
132 | for( var i = 1; i <= block.varCount_; i++ ) {
133 | if( i > 1 ) str += ", ";
134 | str += (block.getFieldValue( "VAR"+i ) || Blockly.Latex.blank);
135 | }
136 | str += " " + Blockly.Latex.symbolToLatex[block.getFieldValue( "OPERATOR" )][0] + " " +
137 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SCOPE) || Blockly.Latex.blank ) + " " +
138 | // (block.getInput( "STLABEL" ).isVisible() ? "\\text{ s.t. }" : "\\; " ) +
139 | (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank );
140 | return [str, order];
141 | };
142 |
143 | Blockly.Latex['logic_forall'] = function(block) {
144 | var str = "\\forall " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " \\in " +
145 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SET) || Blockly.Latex.blank ) +
146 | " \\; " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank );
147 | return [str, Blockly.Latex.ORDER_FORALL];
148 | };
149 |
150 | Blockly.Latex['logic_forall_condition'] = function(block) {
151 | var str = "\\forall " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " " + block.getFieldValue( "COMPARISON_OPERATOR" ) + " " +
152 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_NUM) || Blockly.Latex.blank ) +
153 | " \\; " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_FORALL ) || Blockly.Latex.blank );
154 | return [str, Blockly.Latex.ORDER_FORALL];
155 | };
156 |
157 | Blockly.Latex['logic_exists'] = function(block) {
158 | var str = "\\exists " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " \\in " +
159 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SET) || Blockly.Latex.blank) +
160 | " \\text{ s.t. } " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank );
161 | return [str, Blockly.Latex.ORDER_EXISTS];
162 | };
163 |
164 | Blockly.Latex['logic_exists_condition'] = function(block) {
165 | var str = "\\exists " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " " + block.getFieldValue( "COMPARISON_OPERATOR" ) + " " +
166 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_NUM) || Blockly.Latex.blank ) +
167 | " \\text{ s.t. } " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_FORALL ) || Blockly.Latex.blank );
168 | return [str, Blockly.Latex.ORDER_EXISTS];
169 | };
170 |
171 | Blockly.Latex['logic_connective'] = function(block) {
172 | /* Logic connective: AND, OR, etc */
173 | var connective = block.getFieldValue('CONNECTIVE');
174 | var operator = Blockly.Latex.symbolToLatex[connective][0];
175 | var order = Blockly.Latex.symbolToLatex[connective][1];
176 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank;
177 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank;
178 | var code = argument0 + ' ' + operator + ' ' + argument1;
179 | return [code, order];
180 | };
181 |
182 | Blockly.Latex['logic_negation'] = function(block) {
183 | var str = "\\sim " +
184 | (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_NOT) || Blockly.Latex.blank);
185 | return [str, Blockly.Latex.ORDER_NOT];
186 | };
187 |
188 | Blockly.Latex['logic_prop_variable'] = function(block) {
189 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC];
190 | };
191 |
192 | Blockly.Latex['set_number'] = function(block) {
193 | return( [block.getFieldValue("SET") || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC] );
194 | };
195 |
196 | Blockly.Latex['set_comprehension'] = function(block) {
197 | var string = "\\left\\{ ";
198 | string += block.getFieldValue( "VARNAME" );
199 | string += " ∈ ";
200 | string += (Blockly.Latex.valueToCode(block, "DOMAIN", Blockly.Latex.ORDER_NONE) || Blockly.Latex.blank);
201 | string += " : ";
202 | string += (Blockly.Latex.valueToCode(block, "CONDITION", Blockly.Latex.ORDER_NONE) || Blockly.Latex.blank);
203 | string += "\\right\\}";
204 | return( [string, Blockly.Latex.ORDER_ATOMIC] );
205 | };
206 |
207 | Blockly.Latex['set_r'] = function(block) {
208 | return ["\\mathbb{R}", Blockly.Latex.ORDER_ATOMIC]; /* Or use Unicode? */
209 | };
210 |
211 | Blockly.Latex['set_c'] = function(block) {
212 | return ["\\mathbb{C}", Blockly.Latex.ORDER_ATOMIC];
213 | };
214 |
215 | Blockly.Latex['set_q'] = function(block) {
216 | return ["\\mathbb{Q}", Blockly.Latex.ORDER_ATOMIC];
217 | };
218 |
219 | Blockly.Latex['set_z'] = function(block) {
220 | return ["\\mathbb{Z}", Blockly.Latex.ORDER_ATOMIC];
221 | };
222 |
223 | Blockly.Latex['set_n'] = function(block) {
224 | return ["\\mathbb{N}", Blockly.Latex.ORDER_ATOMIC];
225 | };
226 |
227 | Blockly.Latex['set_nullset'] = function(block) {
228 | return ["\\emptyset", Blockly.Latex.ORDER_ATOMIC];
229 | };
230 |
231 | Blockly.Latex['set_variable'] = function(block) {
232 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC];
233 | };
234 |
235 | Blockly.Latex['set_membership'] = function(block) {
236 | var str = (Blockly.Latex.valueToCode(block, "ELEMENT", Blockly.Latex.ORDER_SET_ELEMENT) || Blockly.Latex.blank)
237 | + " \\in " +
238 | (Blockly.Latex.valueToCode(block, "SET", Blockly.Latex.ORDER_SET_SET) || Blockly.Latex.blank);
239 | return [str, Blockly.Latex.ORDER_SET_MEMBERSHIP];
240 | };
241 |
242 | Blockly.Latex['set_operations'] = function(block) {
243 | /* Set operations: union, intersection, exclusion */
244 | var connective = block.getFieldValue('OPERATION');
245 | var operator = Blockly.Latex.symbolToLatex[connective][0];
246 | var order = Blockly.Latex.symbolToLatex[connective][1];
247 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank;
248 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank;
249 | var code = argument0 + ' ' + operator + ' ' + argument1;
250 | return [code, order];
251 |
252 | };
253 |
254 | Blockly.Latex['set_complement'] = function(block) {
255 | var str = (Blockly.Latex.valueToCode(block, "SET", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^c";
256 | return [str, Blockly.Latex.ORDER_SET_COMPLEMENT];
257 | };
258 |
259 | Blockly.Latex['set_comparison'] = function(block) {
260 | var connective = block.getFieldValue('OPERATOR');
261 | var operator = Blockly.Latex.symbolToLatex[connective][0];
262 | var order = Blockly.Latex.symbolToLatex[connective][1];
263 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank;
264 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank;
265 | var code = argument0 + ' ' + operator + ' ' + argument1;
266 | return [code, order];
267 | };
268 |
269 | Blockly.Latex['set_bounds'] = function(block) {
270 | /* Set bounds: sup, inf, min, max */
271 | var connective = block.getFieldValue('OPERATOR');
272 | var operator = Blockly.Latex.symbolToLatex[connective][0];
273 | var order = Blockly.Latex.symbolToLatex[connective][1];
274 | var code = operator + " " + (Blockly.Latex.valueToCode(block, 'SET', order) || Blockly.Latex.blank);
275 | return [code, order];
276 | };
277 |
278 | /* Could re-use function for set, propositional variable? */
279 | Blockly.Latex['number_variable'] = function(block) {
280 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC];
281 | };
282 |
283 | Blockly.Latex['number_0'] = function(block) {
284 | return ["0", Blockly.Latex.ORDER_ATOMIC];
285 | };
286 |
287 | Blockly.Latex['number_1'] = function(block) {
288 | return ["1", Blockly.Latex.ORDER_ATOMIC];
289 | };
290 |
291 | Blockly.Latex['number_pi'] = function(block) {
292 | return ["\\pi", Blockly.Latex.ORDER_ATOMIC];
293 | };
294 |
295 | Blockly.Latex['number_e'] = function(block) {
296 | return ["e", Blockly.Latex.ORDER_ATOMIC];
297 | };
298 |
299 | Blockly.Latex['number_add_inv'] = function(block) {
300 | var str = "-" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank);
301 | return [str, Blockly.Latex.ORDER_ADD_INVERSE];
302 | };
303 |
304 | Blockly.Latex['number_mult_inv'] = function(block) {
305 | var str = (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^{-1}";
306 | return [str, Blockly.Latex.ORDER_MULT_INVERSE];
307 | };
308 |
309 | Blockly.Latex['number_squared'] = function(block) {
310 | var str = (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^{2}";
311 | return [str, Blockly.Latex.ORDER_POWER];
312 | };
313 |
314 | Blockly.Latex['number_abs'] = function(block) {
315 | var str = "\\left|" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right|";
316 | return [str, Blockly.Latex.ORDER_ATOMIC]; /* Using ORDER_ATOMIC as |...| act as brackets themselves. */ /* TODO: is this correct? Should be ORDER_NONE? */
317 | };
318 |
319 | Blockly.Latex['number_log_function'] = function(block) {
320 | var str = "\\log\\left(" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right)";
321 | return [str, Blockly.Latex.ORDER_ATOMIC];
322 | };
323 |
324 | Blockly.Latex['number_trig_functions'] = function(block) {
325 | var str = "\\" + block.getFieldValue( "FUNCTION" )
326 | + "\\left(" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right)";
327 | return [str, Blockly.Latex.ORDER_ATOMIC];
328 | };
329 |
330 | Blockly.Latex['function_variable'] = function(block) {
331 | var str = block.getFieldValue( "FUNCNAME" ) + "\\left( " +
332 | (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + " \\right)";
333 | return [str, Blockly.Latex.ORDER_ATOMIC];
334 | }
335 |
336 | Blockly.Latex['function_fn'] = function(block) {
337 | return ["f(n)", Blockly.Latex.ORDER_ATOMIC];
338 | }
339 |
340 | Blockly.Latex['number_comparison'] = function(block) {
341 | /* Number comparisons: equal, not equal, less than, etc */
342 | var connective = block.getFieldValue('COMPARISON_OPERATOR');
343 | var operator = Blockly.Latex.symbolToLatex[connective][0];
344 | var order = Blockly.Latex.symbolToLatex[connective][1];
345 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank;
346 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank;
347 | var code = argument0 + ' ' + operator + ' ' + argument1;
348 | return [code, order];
349 | };
350 |
351 | Blockly.Latex['number_comparison_3'] = function(block) {
352 | /* Number comparisons: equal, not equal, less than, etc */
353 | var comparison0 = block.getFieldValue('COMPARISON_OPERATOR0');
354 | var comparison1 = block.getFieldValue('COMPARISON_OPERATOR1');
355 | var compsymbol0 = Blockly.Latex.symbolToLatex[comparison0][0];
356 | var compsymbol1 = Blockly.Latex.symbolToLatex[comparison1][0];
357 | var order = Blockly.Latex.symbolToLatex[comparison0][1];
358 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank;
359 | var argument1 = Blockly.Latex.valueToCode(block, 'MIDDLEINPUT', order) || Blockly.Latex.blank;
360 | var argument2 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank;
361 | var code = argument0 + ' ' + compsymbol0 + ' ' + argument1 + ' ' + compsymbol1 + ' ' + argument2;
362 | return [code, order];
363 | };
364 |
365 |
366 | /* Generator for Blockly standard number block. Replace with our own block? */
367 | Blockly.Latex['math_number'] = function(block) {
368 | return [block.getFieldValue( "NUM" ), Blockly.Latex.ORDER_ATOMIC];
369 | };
370 |
371 | /* Generator for Blockly standard arithmetic block. Replace with our own block? */
372 | Blockly.Latex['math_arithmetic'] = function(block) {
373 | /* Number comparisons: equal, not equal, less than, etc */
374 | var op = block.getFieldValue('OP');
375 | var operator = Blockly.Latex.symbolToLatex[op][0];
376 | var order = Blockly.Latex.symbolToLatex[op][1];
377 | var argument0 = Blockly.Latex.valueToCode(block, 'A', order) || Blockly.Latex.blank;
378 | var argument1 = Blockly.Latex.valueToCode(block, 'B', order) || Blockly.Latex.blank; /* TODO: need to use different order for exponent */
379 | var code;
380 | if( op == "POWER" ) {
381 | code = argument0 + "^{" + argument1 + "}"; /* Need braces around argument */
382 | } else {
383 | code = argument0 + ' ' + operator + ' ' + argument1;
384 | }
385 | return [code, order];
386 | };
387 |
388 |
389 |
390 | /* Blocks for translation exercise */
391 | Blockly.Latex['logic_quantifier_set_restricted_1'] = Blockly.Latex['logic_quantifier_set_restricted_2'] = function(block) {
392 | // console.log( block.getFieldValue("QUANTIFIER") );
393 | var order = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][1]
394 | var varname = block.getFieldValue( "VAR" );
395 | if( !varname || varname == "BLANK" ) varname = Blockly.Latex.blank;
396 | var str = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][0] + " " + varname +
397 | " ∈ ℤ " +
398 | // (block.getFieldValue( "QUANTIFIER" ) == "∃" ? "\\text{ s.t. }" : "\\; " ) +
399 | (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank );
400 | return [str, order];
401 | };
402 |
403 | Blockly.Latex['predicate_multiple_of_3'] = Blockly.Latex['predicate_multiple_of_6'] = function(block) {
404 | var str = (Blockly.Latex.valueToCode( block, "NUM", Blockly.Latex.ORDER_ATOMIC ) || Blockly.Latex.blank )
405 | + " \\text{ is a multiple of " + (block.type == "predicate_multiple_of_3" ? "3" : "6") + "}";
406 | return [str, Blockly.Latex.ORDER_ATOMIC];
407 | };
408 |
409 | Blockly.Latex['number_multiplication'] = function(block) {
410 | var argument0 = Blockly.Latex.valueToCode(block, 'A', Blockly.Latex.ORDER_MULTIPLICATION) || Blockly.Latex.blank;
411 | var argument1 = Blockly.Latex.valueToCode(block, 'B', Blockly.Latex.ORDER_MULTIPLICATION) || Blockly.Latex.blank;
412 | var code = argument0 + " \\times " + argument1;
413 | return [code, Blockly.Latex.ORDER_MULTIPLICATION];
414 | };
415 |
416 | Blockly.Latex['number_variable_restricted'] = function(block) {
417 | var varname = block.getFieldValue("VAR");
418 | if( varname == "BLANK" ) varname = Blockly.Latex.blank;
419 | return [varname, Blockly.Latex.ORDER_ATOMIC];
420 | };
421 |
422 | Blockly.Latex['set_dropdown'] = function(block) {
423 | var setname = block.getFieldValue("SET");
424 | if( setname == " " ) varname = Blockly.Latex.blank;
425 | return [setname, Blockly.Latex.ORDER_ATOMIC];
426 | };
427 |
--------------------------------------------------------------------------------
/js/math-blocks-limit-exercise.js:
--------------------------------------------------------------------------------
1 | /*** Blockly block definitions for mathematical expression construction ***/
2 | /* By Anthony Morphett, awmorp@gmail.com */
3 |
4 | /*
5 | Blocks for online activity 1
6 |
7 | Assumes that math-blocks.js is loaded.
8 | */
9 |
10 |
11 | var varlist = [[" ", "BLANK"],["ϵ", "EPSILON"], ["δ", "DELTA"], ["L","L"], ["M", "M"], ["n", "n"], ["x", "x"]];
12 | var valuelist = [[" ", "BLANK"],["0","0"],["ϵ", "EPSILON"], ["δ", "DELTA"], ["L","L"], ["M", "M"], ["n", "n"], ["x", "x"]];
13 | var setlist = [[" ", "BLANK"], ["ℝ", "REAL"], ["ℚ", "RATIONAL"], ["ℤ", "INTEGERS"], ["ℕ", "NATURALS"]];
14 | var quantifierlist = [[" ", "BLANK"],["∀", "FORALL"],["∃","EXISTS"]];
15 |
16 | /****** Quantifiers ******/
17 | Blockly.Blocks['logic_quantifier_set_restricted'] = {
18 | init: function() {
19 | this.appendValueInput("PREDICATE")
20 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER")
21 | .appendField(new Blockly.FieldDropdown(varlist), "VAR")
22 | .appendField("∈")
23 | .appendField(new Blockly.FieldDropdown(setlist), "SET")
24 | .setCheck("Boolean");
25 | this.setInputsInline(true);
26 | this.setOutput(true, "Boolean");
27 | this.setColour(booleanQuantifierHue);
28 | this.setTooltip('Quantifier');
29 | }
30 | };
31 |
32 | Blockly.Blocks['logic_quantifier_condition_restricted'] = {
33 | init: function() {
34 | this.appendDummyInput()
35 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER")
36 | .appendField(new Blockly.FieldDropdown(varlist), "VAR")
37 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["≥", "≥"], ["<", "<"], ["≤", "v"], ["≠", "≠"]]), "COMPARISON_OPERATOR")
38 | .appendField(new Blockly.FieldDropdown(valuelist), "BOUND");
39 | this.appendValueInput("PREDICATE")
40 | .setCheck("Boolean");
41 | this.setInputsInline(true);
42 | this.setOutput(true, "Boolean");
43 | this.setColour(booleanQuantifierHue);
44 | this.setTooltip('Quantifier with condition');
45 | }
46 | };
47 |
48 | Blockly.Blocks['function_fn'] = {
49 | init: function() {
50 | this.appendDummyInput()
51 | .appendField("f(n)");
52 | this.setInputsInline(true);
53 | this.setOutput(true, "Number");
54 | this.setColour(numberHue);
55 | this.setTooltip('');
56 | }
57 | };
58 |
59 | Blockly.Blocks['number_variable_restricted'] = {
60 | init: function() {
61 | this.appendDummyInput()
62 | .appendField(new Blockly.FieldDropdown(valuelist), "VAR");
63 | this.setInputsInline(true);
64 | this.setOutput(true, "Number");
65 | this.setColour(numberHue);
66 | this.setTooltip('');
67 | }
68 | };
69 |
70 | Blockly.Blocks['set_dropdown'] = {
71 | init: function() {
72 | this.appendDummyInput()
73 | .appendField(new Blockly.FieldDropdown(setlist), "SET");
74 | this.setInputsInline(true);
75 | this.setOutput(true, "Set");
76 | this.setColour(setHue);
77 | this.setTooltip('');
78 | }
79 | };
80 |
81 |
--------------------------------------------------------------------------------
/js/math-blocks-logic-exercise.js:
--------------------------------------------------------------------------------
1 | /* Propositional variables with negation */
2 | Blockly.Blocks['logic_p_notp'] = {
3 | init: function() {
4 | this.appendDummyInput()
5 | .appendField( new Blockly.FieldDropdown([["P", "P"], ["~P", "NOTP"]]), "VAR");
6 | this.setInputsInline(true);
7 | this.setOutput(true, "Boolean");
8 | this.setColour(booleanHue);
9 | this.setTooltip('Propositional variable P');
10 | this.setHelpUrl();
11 | }
12 | };
13 |
14 | Blockly.Blocks['logic_q_notq'] = {
15 | init: function() {
16 | this.appendDummyInput()
17 | .appendField( new Blockly.FieldDropdown([["Q", "Q"], ["~Q", "NOTQ"]]), "VAR");
18 | this.setInputsInline(true);
19 | this.setOutput(true, "Boolean");
20 | this.setColour(booleanHue);
21 | this.setTooltip('Propositional variable Q');
22 | this.setHelpUrl();
23 | }
24 | };
25 |
26 | Blockly.Blocks['logic_r_notr'] = {
27 | init: function() {
28 | this.appendDummyInput()
29 | .appendField( new Blockly.FieldDropdown([["R", "R"], ["~R", "NOTR"]]), "VAR");
30 | this.setInputsInline(true);
31 | this.setOutput(true, "Boolean");
32 | this.setColour(booleanHue);
33 | this.setTooltip('Propositional variable R');
34 | this.setHelpUrl();
35 | }
36 | };
37 |
38 | /* Implication */
39 | Blockly.Blocks['logic_implies'] = {
40 | init: function() {
41 | this.appendValueInput("LEFTINPUT")
42 | .setCheck("Boolean");
43 | this.appendValueInput("RIGHTINPUT")
44 | .setCheck("Boolean")
45 | .appendField( "⇒" );
46 | this.setInputsInline(true);
47 | this.setOutput(true, "Boolean");
48 | this.setColour(booleanHue);
49 | this.setTooltip('Logical implication');
50 | this.setHelpUrl();
51 | }
52 | };
53 |
54 | /* Square of number */
55 | Blockly.Blocks['number_square'] = {
56 | init: function() {
57 | this.appendValueInput("NUM")
58 | .setCheck("Number");
59 | this.appendDummyInput()
60 | .appendField( "²" );
61 | this.setInputsInline(true);
62 | this.setOutput(true, "Number");
63 | this.setColour(numberHue);
64 | this.setTooltip('Square');
65 | this.setHelpUrl();
66 | }
67 | };
68 |
69 | Blockly.Blocks['number_cube'] = {
70 | init: function() {
71 | this.appendValueInput("NUM")
72 | .setCheck("Number");
73 | this.appendDummyInput()
74 | .appendField( "³" );
75 | this.setInputsInline(true);
76 | this.setOutput(true, "Number");
77 | this.setColour(numberHue);
78 | this.setTooltip('Cube');
79 | this.setHelpUrl();
80 | }
81 | };
82 |
83 | Blockly.Blocks['function_fn'] = {
84 | init: function() {
85 | this.appendDummyInput()
86 | .appendField( "f(" )
87 | .appendField(new Blockly.FieldMathVariable("n", "Number"), "VARNAME")
88 | .appendField(")");
89 | this.setInputsInline(true);
90 | this.setOutput(true, "Number");
91 | this.setColour(numberHue);
92 | this.setTooltip('f(n)');
93 | this.setHelpUrl();
94 | }
95 | };
96 |
97 | Blockly.Blocks['function_fn+1'] = {
98 | init: function() {
99 | this.appendDummyInput()
100 | .appendField( "f(" )
101 | .appendField(new Blockly.FieldMathVariable("n", "Number"), "VARNAME")
102 | .appendField("+1)");
103 | this.setInputsInline(true);
104 | this.setOutput(true, "Number");
105 | this.setColour(numberHue);
106 | this.setTooltip('f(n+1)');
107 | this.setHelpUrl();
108 | }
109 | };
110 |
111 | Blockly.Blocks['quantifier_abstract'] = {
112 | init: function() {
113 | var varField = new Blockly.FieldMathVariable("x", "Abstract", null, true);
114 | /* Override CSS so that this field is displayed in number colour rather than boolean colour */
115 | varField.addCSSClass( "blocklyQuantifierVarField" );
116 | this.appendDummyInput("VARINPUT")
117 | .appendField(new Blockly.FieldDropdown([["∀", "∀"], ["∃", "∃"]]), "QUANTIFIER")
118 | .appendField(varField, "VAR")
119 | .appendField(" ");
120 | // this.appendDummyInput("STLABEL")
121 | // .appendField("s.t.", "ST");
122 | this.appendValueInput("PREDICATE")
123 | .setCheck("Boolean");
124 | this.setInputsInline(true);
125 | this.setOutput(true, "Boolean");
126 | this.setColourByType();
127 | this.setTooltip('Quantifier');
128 | this.setHelpUrl();
129 | this.isQuantifier = true;
130 | },
131 | getVars: function() {
132 | var varlist = [[this.getFieldValue( 'VAR' ), "Abstract"]];
133 | return varlist;
134 | }
135 | };
136 |
137 | Blockly.Blocks['abstract_variable'] = {
138 | init: function() {
139 | this.appendDummyInput()
140 | .appendField(new Blockly.FieldMathVariable("x", "Abstract"), "VARNAME");
141 | this.setInputsInline(true);
142 | this.setOutput(true, "Abstract");
143 | this.setColourByType();
144 | this.setTooltip('A variable representing an unspecified object');
145 | this.setHelpUrl();
146 | },
147 | getVars: function() {
148 | return [[this.getFieldValue('VARNAME'), "Abstract"]];
149 | }
150 | };
151 |
152 |
153 | Blockly.Blocks['predicate_glitters'] = {
154 | init: function() {
155 | this.appendValueInput( "VALUE" )
156 | .setCheck( "Abstract" );
157 | this.appendDummyInput()
158 | .appendField( "glitters" );
159 | this.setInputsInline(true);
160 | this.setOutput(true,"Boolean");
161 | this.setColourByType();
162 | this.setTooltip( "'glitters' predicate" );
163 | }
164 | };
165 |
166 | Blockly.Blocks['predicate_gold'] = {
167 | init: function() {
168 | this.appendValueInput( "VALUE" )
169 | .setCheck( "Abstract" );
170 | this.appendDummyInput()
171 | .appendField( "is gold" );
172 | this.setInputsInline(true);
173 | this.setOutput(true,"Boolean");
174 | this.setColourByType();
175 | this.setTooltip( "'gold' predicate" );
176 | }
177 | };
--------------------------------------------------------------------------------
/js/math-blocks-old.js:
--------------------------------------------------------------------------------
1 | /* Variant math blocks not currently used. Removed from main math-blocks.js to reduce code size. */
2 |
3 | Blockly.Blocks['logic_forall_condition'] = {
4 | init: function() {
5 | this.appendValueInput("SCOPE")
6 | .setCheck("Number")
7 | .appendField("?")
8 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR")
9 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["=", "="], ["<", "<"], ["=", "="], ["?", "?"]]), "COMPARISON_OPERATOR")
10 | .parentVarsInScope_ = false;
11 | /* Note: using the mathematical symbol as the blockly 'language neutral' identifier (as maths is a universal language :) */
12 | this.appendValueInput("PREDICATE")
13 | .setCheck("Boolean");
14 | this.setInputsInline(true);
15 | this.setOutput(true, "Boolean");
16 | this.setColour(booleanQuantifierHue);
17 | this.setTooltip('Universal (\'for all\') quantifier with condition');
18 | this.setHelpUrl();
19 | this.isQuantifier = true;
20 | },
21 | getVars: function() {
22 | return [[this.getFieldValue('VAR'),"Number"]];
23 | }
24 | };
25 |
26 |
27 | /* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#hkxkys */
28 | Blockly.Blocks['logic_exists'] = {
29 | init: function() {
30 | this.appendValueInput("SCOPE")
31 | .setCheck("Set")
32 | .appendField("?")
33 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR")
34 | .appendField("?")
35 | .parentVarsInScope_ = false;
36 | this.appendValueInput("PREDICATE")
37 | // .appendField(" s.t.")
38 | .setCheck("Boolean");
39 | this.setInputsInline(true);
40 | this.setOutput(true, "Boolean");
41 | this.setColour(booleanQuantifierHue);
42 | this.setTooltip('Existential (\'exists\') quantifier');
43 | this.setHelpUrl();
44 | this.isQuantifier = true;
45 | },
46 | getVars: function() {
47 | return [[this.getFieldValue('VAR'),"Number"]];
48 | }
49 | };
50 |
51 | /* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#yt9arv */
52 | Blockly.Blocks['logic_exists_condition'] = {
53 | init: function() {
54 | this.appendValueInput("SCOPE")
55 | .setCheck("Number")
56 | .appendField("?")
57 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR")
58 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["=", "="], ["<", "<"], ["=", "v"], ["?", "?"]]), "COMPARISON_OPERATOR")
59 | .parentVarsInScope_ = false;
60 | this.appendValueInput("PREDICATE")
61 | // .appendField(" s.t.")
62 | .setCheck("Boolean");
63 | this.setInputsInline(true);
64 | this.setOutput(true, "Boolean");
65 | this.setColour(booleanQuantifierHue);
66 | this.setTooltip('');
67 | this.setHelpUrl();
68 | this.isQuantifier = true;
69 | },
70 | getVars: function() {
71 | return [[this.getFieldValue('VAR'),"Number"]];
72 | },
73 | };
74 |
--------------------------------------------------------------------------------
/js/math-blocks-translation-exercise.js:
--------------------------------------------------------------------------------
1 | /*** Blockly block definitions for mathematical expression construction ***/
2 | /* By Anthony Morphett, awmorp@gmail.com */
3 |
4 | /*
5 | Blocks for English -> Mathematics translation activity
6 |
7 | Assumes that math-blocks.js is loaded.
8 | */
9 |
10 |
11 | var varlist1 = [[" ", "BLANK"],["a", "a"]];
12 | var varlist2 = [[" ", "BLANK"],["b", "b"],["c","c"]];
13 | var valuelist = [[" ", "BLANK"],["a", "a"], ["b", "b"],["c","c"]];;
14 | var setlist = [[" ", "BLANK"], ["ℝ", "REAL"], ["ℚ", "RATIONAL"], ["ℤ", "INTEGERS"], ["ℕ", "NATURALS"]];
15 | var quantifierlist = [[" ", "BLANK"],["∀", "FORALL"],["∃","EXISTS"]];
16 |
17 | /****** Quantifiers ******/
18 | Blockly.Blocks['logic_quantifier_set_restricted_1'] = {
19 | init: function() {
20 | this.appendValueInput("PREDICATE")
21 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER")
22 | .appendField(new Blockly.FieldDropdown(varlist1), "VAR")
23 | .appendField("∈ ℤ")
24 | .setCheck("Boolean");
25 | this.setInputsInline(true);
26 | this.setOutput(true, "Boolean");
27 | this.setColour(booleanQuantifierHue);
28 | this.setTooltip('Quantifier');
29 | }
30 | };
31 |
32 | Blockly.Blocks['logic_quantifier_set_restricted_2'] = {
33 | init: function() {
34 | this.appendValueInput("PREDICATE")
35 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER")
36 | .appendField(new Blockly.FieldDropdown(varlist2), "VAR")
37 | .appendField("∈ ℤ")
38 | .setCheck("Boolean");
39 | this.setInputsInline(true);
40 | this.setOutput(true, "Boolean");
41 | this.setColour(booleanQuantifierHue);
42 | this.setTooltip('Quantifier');
43 | }
44 | };
45 |
46 | Blockly.Blocks['predicate_multiple_of_3'] = {
47 | init: function() {
48 | this.appendValueInput( "NUM" )
49 | .setCheck("Number");
50 | this.appendDummyInput()
51 | .appendField("is a multiple of 3");
52 | this.setInputsInline(true);
53 | this.setOutput(true, "Boolean");
54 | this.setColour(booleanHue);
55 | this.setTooltip('');
56 | }
57 | };
58 |
59 | Blockly.Blocks['predicate_multiple_of_6'] = {
60 | init: function() {
61 | this.appendValueInput( "NUM" )
62 | .setCheck("Number");
63 | this.appendDummyInput()
64 | .appendField("is a multiple of 6");
65 | this.setInputsInline(true);
66 | this.setOutput(true, "Boolean");
67 | this.setColour(booleanHue);
68 | this.setTooltip('');
69 | }
70 | };
71 |
72 | Blockly.Blocks['number_multiplication'] = {
73 | init: function() {
74 | this.appendValueInput( "A" )
75 | .setCheck("Number");
76 | this.appendValueInput( "B" )
77 | .setCheck("Number")
78 | .appendField("×");
79 | this.setInputsInline(true);
80 | this.setOutput(true, "Number");
81 | this.setColourByType();
82 | this.setTooltip('');
83 | }
84 | };
85 |
86 | Blockly.Blocks['number_variable_restricted'] = {
87 | init: function() {
88 | this.appendDummyInput()
89 | .appendField(new Blockly.FieldDropdown(valuelist), "VAR");
90 | this.setInputsInline(true);
91 | this.setOutput(true, "Number");
92 | this.setColour(numberHue);
93 | this.setTooltip('');
94 | }
95 | };
96 |
97 | Blockly.Blocks['set_dropdown'] = {
98 | init: function() {
99 | this.appendDummyInput()
100 | .appendField(new Blockly.FieldDropdown(setlist), "SET");
101 | this.setInputsInline(true);
102 | this.setOutput(true, "Set");
103 | this.setColour(setHue);
104 | this.setTooltip('');
105 | }
106 | };
107 |
108 |
--------------------------------------------------------------------------------
/js/math-blocks-trig.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/js/math-blocks-trig.js
--------------------------------------------------------------------------------
/js/math-blocks-vectors.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/js/math-blocks-vectors.js
--------------------------------------------------------------------------------
/js/shaped-connectors.js:
--------------------------------------------------------------------------------
1 | /** Adds angled, rounded and brace-shaped connectors, and chooses connector shape based on type **/
2 | /* Author: Anthony Morphett, awmorp@gmail.com */
3 |
4 | alert( "Warning: shaped-connectors.js was loaded. This file is obsolete and shouldn't be used." );
5 |
6 | /**
7 | * SVG path for drawing an angled tab from top to bottom.
8 | * @const
9 | */
10 | Blockly.BlockSvg.TAB_PATH_DOWN_ANGLE = 'v 5 l -' + Blockly.BlockSvg.TAB_WIDTH + ',' + 7.5 +
11 | ' l ' + Blockly.BlockSvg.TAB_WIDTH + ',' + 7.5;
12 |
13 | /**
14 | * SVG path for drawing a round-shaped tab from top to bottom.
15 | * @const
16 | */
17 | Blockly.BlockSvg.TAB_PATH_DOWN_ROUND = 'v 5 a ' + Blockly.BlockSvg.TAB_WIDTH + ' ' + 7.5 +
18 | ' 0 0 0 0 ' + 1.75*Blockly.BlockSvg.TAB_WIDTH;
19 |
20 | /**
21 | * SVG path for drawing a brace-shaped tab from top to bottom.
22 | * @const
23 | */
24 | /*Blockly.BlockSvg.TAB_PATH_DOWN_BRACE = 'v 2.5 c -5,0 -2,9 -10,10 c 8,1 5,10 10,10'; */
25 | Blockly.BlockSvg.TAB_PATH_DOWN_BRACE = 'v 5 c -' + (0.5*Blockly.BlockSvg.TAB_WIDTH) + ',0' +
26 | ' -' + (0.2*Blockly.BlockSvg.TAB_WIDTH) + ',' + (0.9*7.5) +
27 | ' -' + (0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + 7.5 +
28 | ' c ' + (0.55*Blockly.BlockSvg.TAB_WIDTH) + ',' + (0.1*7.5) +
29 | ' ' + (0.5*0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + (7.5) + /* Not sure this is quite symmetric? */
30 | ' ' + (0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + (7.5);
31 |
32 | /**
33 | * SVG path for drawing an arrow-shaped tab from top to bottom.
34 | * @const
35 | */
36 | Blockly.BlockSvg.TAB_PATH_DOWN_ARROW = 'v 5 h -3 v -3 l -3 7 l 3 7 v -3 h 3 v 5';
37 |
38 | /* Define new getConnector function.
39 | Determine shape based on connector type.
40 | */
41 | Blockly.BlockSvg.getConnectorPath = function(connector) {
42 | alert( "Warning: obsolete shaped-connector.js getConnectorPath called!" );
43 | var typeArray = connector.check_;
44 | if( typeArray && typeArray.length == 1 ) {
45 | switch( typeArray[0] ) {
46 | case "Number": return( Blockly.BlockSvg.TAB_PATH_DOWN_ROUND );
47 | case "Boolean": return( Blockly.BlockSvg.TAB_PATH_DOWN_ANGLE );
48 | case "Set": return( Blockly.BlockSvg.TAB_PATH_DOWN_BRACE );
49 | case "Vector": return( Blockly.BlockSvg.TAB_PATH_DOWN_ARROW );
50 | default: return( Blockly.BlockSvg.TAB_PATH_DOWN_PUZZLE );
51 | }
52 | } else {
53 | return( Blockly.BlockSvg.TAB_PATH_DOWN_PUZZLE );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | /* Set content of element 'id' to 'text' */
2 | function setContentById( id, text )
3 | {
4 | var e = document.getElementById( id );
5 | if( e ) {
6 | e.innerHTML = "";
7 | e.appendChild( document.createTextNode( text ) );
8 | }
9 | }
10 |
11 | function dumpXML()
12 | {
13 | var xml = Blockly.Xml.workspaceToDom(workspace);
14 | var xml_text = Blockly.Xml.domToPrettyText(xml);
15 | console.log( xml );
16 | console.log( xml_text );
17 | }
18 |
19 | function debug() {
20 | var nodes = goog.dom.getElementsByClass( "debug" );
21 | Array.prototype.forEach.call(nodes, function(x) {x.style.display = "block";} ); // nodes is a NodeList rather than Array
22 | }
23 |
24 |
25 | /* Load local MathJax if viewing page locally, otherwise use remote MathJax */
26 | function loadMathjax() {
27 | var scriptNode = document.createElement( "script" );
28 | scriptNode.type = "text/javascript";
29 | if( window.location.protocol == "file:" ) {
30 | scriptNode.src = "../MathJax/unpacked/MathJax.js";
31 | } else {
32 | scriptNode.src = "https://cdn.mathjax.org/mathjax/latest/MathJax.js";
33 | }
34 | document.getElementsByTagName('head')[0].appendChild( scriptNode );
35 | }
36 |
37 | /* Render block expression on workspace change */
38 | var gPreviousLatex;
39 | function displayLatex(workspace) {
40 | var code = Blockly.Latex.workspaceToCode( workspace );
41 | if( code != gPreviousLatex ) {
42 | gPreviousLatex = code;
43 | renderLatex( "\\( " + code + " \\)" );
44 | }
45 | }
46 |
47 | function renderLatex( code ) {
48 | /* Display latex source, if desired */
49 | var latexNode = document.getElementById( "latex-output" );
50 | if( latexNode ) setContentById( 'latex-output', code );
51 | /* Render source into new div asynchronously */
52 | var newNode = document.createElement( "div" );
53 | newNode.innerHTML = code;
54 | var callback = function() {
55 | var container = document.getElementById( "mathjax-output" );
56 | /* Clear old Mathjax */
57 | while( container.firstChild ) container.removeChild(container.firstChild);
58 | /* Add newly rendered node */
59 | container.appendChild( newNode );
60 | };
61 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, newNode, callback]);
62 | };
63 |
64 | function setupAutoLatex( workspace )
65 | {
66 | workspace.addChangeListener( function() { displayLatex( workspace ); } );
67 | if( window.MathJax ) {
68 | /* Do initial render, if MathJax loaded */
69 | displayLatex( workspace );
70 | }
71 | }
72 |
73 |
74 | /* We must wait until MathJax has loaded itself (at least to the point that MathJax.Hub.queue exists) before loading Blockly.
75 | * This function will be called by a MathJax signal handler once MathJax is loaded.
76 | */
77 | var workspace;
78 | function loadBlockly() {
79 | if( window.MathJax ) {
80 | console.warn( "Warning: loadBlockly() must be called before MathJax is loaded!" );
81 | return;
82 | }
83 |
84 | var injectBlockly = function() {
85 | workspace = Blockly.inject('blocklyDiv',
86 | {
87 | media: '../blockly/media/',
88 | disable: false,
89 | toggleInline: false,
90 | toolbox: document.getElementById('toolbox'),
91 | trashcan: true,
92 | zoom: {controls: true, wheel: true, startScale: 1}
93 | });
94 | // Blockly.Xml.domToWorkspace( workspace, document.getElementById("workspace-initial") );
95 | }
96 |
97 | /* Set up a hook to load Blockly */
98 | window.MathJax = { AuthorInit: function() {
99 | MathJax.Hub.Register.StartupHook( "End", injectBlockly );
100 | }};
101 | }
102 |
103 |
104 | if( window.location.search.search("debug") >= 0 ) {
105 | if( window.addEventListener ){
106 | window.addEventListener( 'load', debug )
107 | } else {
108 | window.attachEvent( 'onload', debug )
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/js/video-utils.js:
--------------------------------------------------------------------------------
1 | var videoState = 0;
2 | var handle;
3 |
4 | function playPause() {
5 | if( videoState == 2 ) { // Fading out
6 | if( handle ) {
7 | window.clearTimeout( handle );
8 | handle = null;
9 | } else {
10 | fadeoutStep();
11 | }
12 | } else if( videoState == 3 ) {
13 | if( handle ) {
14 | window.clearTimeout( handle );
15 | handle = null;
16 | } else {
17 | fadeinStep();
18 | }
19 | } else {
20 | if( videoElement.paused ) {
21 | videoElement.play();
22 | } else {
23 | videoElement.pause();
24 | }
25 | }
26 | }
27 |
28 | function nextVideo() {
29 | // console.log( "nextVideo" );
30 | videoIndex++;
31 | if( videoIndex >= videoSources.length ) videoIndex = 0;
32 | var pauseState = videoElement.paused;
33 | videoElement.src = videoSources[videoIndex];
34 | videoState = 3; // Fading in
35 | handle = window.setTimeout( fadeinStep, 30 );
36 | // if( pauseState ) {
37 | // videoElement.pause();
38 | // } else {
39 | // videoElement.play();
40 | // }
41 | }
42 |
43 | function videoEnded() {
44 | // console.log( "videoEnded" );
45 | videoState = 2; // Fading out
46 | handle = window.setTimeout( fadeoutStep, 500 );
47 | }
48 |
49 | function fadeinStep() {
50 | // console.log( "fadein" );
51 | if( Number( videoElement.style.opacity ) < 0.95 ) {
52 | videoElement.style.opacity = Number( videoElement.style.opacity ) + 0.05;
53 | handle = window.setTimeout( fadeinStep, 30 );
54 | } else {
55 | videoElement.style.opacity = 1;
56 | videoState = 1; // Playing
57 | videoElement.play();
58 | }
59 | }
60 |
61 | function fadeoutStep() {
62 | // console.log( "fadeout" );
63 | if( Number( videoElement.style.opacity ) > 0.05 ) {
64 | videoElement.style.opacity = Number( videoElement.style.opacity ) - 0.05;
65 | handle = window.setTimeout( fadeoutStep, 30 );
66 | } else {
67 | videoElement.style.opacity = 0;
68 | nextVideo();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------