@nitro-bolt/scratchblocks 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +205 -0
  3. package/browser.es.js +8 -0
  4. package/browser.js +8 -0
  5. package/build/scratchblocks.min.es.js +11 -0
  6. package/build/scratchblocks.min.es.js.map +1 -0
  7. package/build/scratchblocks.min.js +11 -0
  8. package/build/scratchblocks.min.js.map +1 -0
  9. package/build/translations-all-es.js +10 -0
  10. package/build/translations-all.js +10 -0
  11. package/build/translations-es.js +10 -0
  12. package/build/translations.js +10 -0
  13. package/index.js +163 -0
  14. package/locales/ab.json +261 -0
  15. package/locales/af.json +261 -0
  16. package/locales/all.js +161 -0
  17. package/locales/am.json +262 -0
  18. package/locales/an.json +261 -0
  19. package/locales/ar.json +261 -0
  20. package/locales/ast.json +261 -0
  21. package/locales/az.json +262 -0
  22. package/locales/be.json +261 -0
  23. package/locales/bg.json +261 -0
  24. package/locales/bn.json +261 -0
  25. package/locales/ca.json +267 -0
  26. package/locales/ckb.json +261 -0
  27. package/locales/cs.json +267 -0
  28. package/locales/cy.json +266 -0
  29. package/locales/da.json +261 -0
  30. package/locales/de.json +266 -0
  31. package/locales/el.json +268 -0
  32. package/locales/eo.json +261 -0
  33. package/locales/es-419.json +261 -0
  34. package/locales/es.json +266 -0
  35. package/locales/et.json +261 -0
  36. package/locales/eu.json +261 -0
  37. package/locales/fa.json +266 -0
  38. package/locales/fi.json +261 -0
  39. package/locales/fil.json +262 -0
  40. package/locales/forums.js +37 -0
  41. package/locales/fr.json +266 -0
  42. package/locales/fy.json +261 -0
  43. package/locales/ga.json +261 -0
  44. package/locales/gd.json +266 -0
  45. package/locales/gl.json +261 -0
  46. package/locales/ha.json +261 -0
  47. package/locales/he.json +266 -0
  48. package/locales/hi.json +266 -0
  49. package/locales/hr.json +266 -0
  50. package/locales/ht.json +261 -0
  51. package/locales/hu.json +267 -0
  52. package/locales/hy.json +261 -0
  53. package/locales/id.json +266 -0
  54. package/locales/is.json +261 -0
  55. package/locales/it.json +266 -0
  56. package/locales/ja-Hira.json +268 -0
  57. package/locales/ja.json +268 -0
  58. package/locales/ka.json +261 -0
  59. package/locales/kk.json +263 -0
  60. package/locales/km.json +261 -0
  61. package/locales/ko.json +261 -0
  62. package/locales/ku.json +263 -0
  63. package/locales/lt.json +261 -0
  64. package/locales/lv.json +261 -0
  65. package/locales/mi.json +261 -0
  66. package/locales/mn.json +262 -0
  67. package/locales/nb.json +266 -0
  68. package/locales/nl.json +266 -0
  69. package/locales/nn.json +261 -0
  70. package/locales/nso.json +261 -0
  71. package/locales/oc.json +261 -0
  72. package/locales/or.json +262 -0
  73. package/locales/pl.json +266 -0
  74. package/locales/pt-br.json +261 -0
  75. package/locales/pt.json +266 -0
  76. package/locales/qu.json +261 -0
  77. package/locales/rap.json +263 -0
  78. package/locales/ro.json +266 -0
  79. package/locales/ru.json +266 -0
  80. package/locales/sk.json +261 -0
  81. package/locales/sl.json +266 -0
  82. package/locales/sr.json +261 -0
  83. package/locales/sv.json +261 -0
  84. package/locales/sw.json +261 -0
  85. package/locales/th.json +261 -0
  86. package/locales/tn.json +261 -0
  87. package/locales/tr.json +269 -0
  88. package/locales/uk.json +261 -0
  89. package/locales/uz.json +262 -0
  90. package/locales/vi.json +262 -0
  91. package/locales/xh.json +261 -0
  92. package/locales/zh-cn.json +266 -0
  93. package/locales/zh-tw.json +266 -0
  94. package/locales/zu.json +261 -0
  95. package/package.json +77 -0
  96. package/scratch3/blocks.js +959 -0
  97. package/scratch3/draw.js +404 -0
  98. package/scratch3/index.js +12 -0
  99. package/scratch3/style.css.js +226 -0
  100. package/scratch3/style.js +807 -0
  101. package/syntax/blocks.js +467 -0
  102. package/syntax/commands.js +1692 -0
  103. package/syntax/extensions.js +33 -0
  104. package/syntax/index.js +17 -0
  105. package/syntax/model.js +427 -0
  106. package/syntax/syntax.js +941 -0
@@ -0,0 +1,467 @@
1
+ import { extensions, aliasExtensions } from "./extensions.js"
2
+
3
+ // List of classes we're allowed to override.
4
+
5
+ const overrideCategories = [
6
+ "motion",
7
+ "looks",
8
+ "sound",
9
+ "variables",
10
+ "list",
11
+ "events",
12
+ "control",
13
+ "sensing",
14
+ "operators",
15
+ "custom",
16
+ "custom-arg",
17
+ "extension",
18
+ "grey",
19
+ "obsolete",
20
+ ...Object.keys(extensions),
21
+ ...Object.keys(aliasExtensions),
22
+ ]
23
+
24
+ const overrideShapes = [
25
+ "hat",
26
+ "cap",
27
+ "stack",
28
+ "boolean",
29
+ "reporter",
30
+ "object",
31
+ "array",
32
+ "ring",
33
+ "cat",
34
+ ]
35
+
36
+ // languages that should be displayed right to left
37
+ export const rtlLanguages = ["ar", "ckb", "fa", "he"]
38
+
39
+ // List of commands taken from Scratch
40
+ import scratchCommands from "./commands.js"
41
+
42
+ const inputNumberPat = /%([0-9]+)/
43
+ export const inputPat = /(%[a-zA-Z0-9](?:\.[a-zA-Z0-9]+)?)/
44
+ const inputPatGlobal = new RegExp(inputPat.source, "g")
45
+ export const iconPat = /(@[a-zA-Z]+)/
46
+ const splitPat = new RegExp(`${inputPat.source}|${iconPat.source}| +`, "g")
47
+
48
+ export const hexColorPat = /^#(?:[0-9a-fA-F]{3}){1,2}?$/
49
+
50
+ export function parseInputNumber(part) {
51
+ const m = inputNumberPat.exec(part)
52
+ return m ? +m[1] : 0
53
+ }
54
+
55
+ // used for procDefs
56
+ export function parseSpec(spec) {
57
+ const parts = spec.split(splitPat).filter(x => x)
58
+ const inputs = parts.filter(p => inputPat.test(p))
59
+ return {
60
+ spec: spec,
61
+ parts: parts,
62
+ inputs: inputs,
63
+ hash: hashSpec(spec),
64
+ }
65
+ }
66
+
67
+ export function hashSpec(spec) {
68
+ return minifyHash(spec.replace(inputPatGlobal, " _ "))
69
+ }
70
+
71
+ export function minifyHash(hash) {
72
+ return hash
73
+ .replace(/_/g, " _ ")
74
+ .replace(/ +/g, " ")
75
+ .replace(/[,%?:]/g, "")
76
+ .replace(/ß/g, "ss")
77
+ .replace(/ä/g, "a")
78
+ .replace(/ö/g, "o")
79
+ .replace(/ü/g, "u")
80
+ .replace(". . .", "...")
81
+ .replace(/^…$/, "...")
82
+ .trim()
83
+ .toLowerCase()
84
+ }
85
+
86
+ export const blocksById = {}
87
+ const allBlocks = scratchCommands.map(def => {
88
+ if (!def.id) {
89
+ if (!def.selector) {
90
+ throw new Error(`Missing ID: ${def.spec}`)
91
+ }
92
+ def.id = `sb2:${def.selector}`
93
+ }
94
+ if (!def.spec) {
95
+ throw new Error(`Missing spec: ${def.id}`)
96
+ }
97
+
98
+ const info = {
99
+ id: def.id, // Used for Scratch 3 translations
100
+ spec: def.spec, // Used for legacy translations.
101
+ parts: def.spec.split(splitPat).filter(x => x),
102
+ selector: def.selector || `sb3:${def.id}`, // Used for JSON marshalling
103
+ inputs: def.inputs == null ? [] : def.inputs,
104
+ shape: def.shape,
105
+ category: def.category,
106
+ hasLoopArrow: !!def.hasLoopArrow,
107
+ }
108
+ if (blocksById[info.id]) {
109
+ throw new Error(`Duplicate ID: ${info.id}`)
110
+ }
111
+ blocksById[info.id] = info
112
+ return info
113
+ })
114
+
115
+ export const unicodeIcons = {
116
+ "@greenFlag": "⚑",
117
+ "@turnRight": "↻",
118
+ "@turnLeft": "↺",
119
+ "@addInput": "▸",
120
+ "@delInput": "◂",
121
+ }
122
+
123
+ export const allLanguages = {}
124
+ function loadLanguage(code, language) {
125
+ const blocksByHash = (language.blocksByHash = {})
126
+
127
+ Object.keys(language.commands).forEach(blockId => {
128
+ const nativeSpec = language.commands[blockId]
129
+ const block = blocksById[blockId]
130
+
131
+ const nativeHash = hashSpec(nativeSpec)
132
+ if (!blocksByHash[nativeHash]) {
133
+ blocksByHash[nativeHash] = []
134
+ }
135
+ blocksByHash[nativeHash].push(block)
136
+
137
+ // fallback image replacement, for languages without aliases
138
+ const m = iconPat.exec(block.spec)
139
+ if (m) {
140
+ const image = m[0]
141
+ const hash = nativeHash.replace(hashSpec(image), unicodeIcons[image])
142
+ if (!blocksByHash[hash]) {
143
+ blocksByHash[hash] = []
144
+ }
145
+ blocksByHash[hash].push(block)
146
+ }
147
+ })
148
+
149
+ language.nativeAliases = {}
150
+ Object.keys(language.aliases).forEach(alias => {
151
+ const blockId = language.aliases[alias]
152
+ const block = blocksById[blockId]
153
+ if (block === undefined) {
154
+ throw new Error(`Invalid alias '${blockId}'`)
155
+ }
156
+ const aliasHash = hashSpec(alias)
157
+ if (!blocksByHash[aliasHash]) {
158
+ blocksByHash[aliasHash] = []
159
+ }
160
+ blocksByHash[aliasHash].push(block)
161
+
162
+ if (!language.nativeAliases[blockId]) {
163
+ language.nativeAliases[blockId] = []
164
+ }
165
+ language.nativeAliases[blockId].push(alias)
166
+ })
167
+
168
+ // Some English blocks have legacy names. Wire them
169
+ // into language.blocksByHash
170
+ Object.keys(language.renamedBlocks || {}).forEach(alt => {
171
+ const id = language.renamedBlocks[alt]
172
+ if (!blocksById[id]) {
173
+ throw new Error(`Unknown ID: ${id}`)
174
+ }
175
+ const block = blocksById[id]
176
+ const hash = hashSpec(alt)
177
+ if (!english.blocksByHash[hash]) {
178
+ english.blocksByHash[hash] = []
179
+ }
180
+ english.blocksByHash[hash].push(block)
181
+ })
182
+
183
+ language.nativeDropdowns = {}
184
+ Object.keys(language.dropdowns).forEach(name => {
185
+ const nativeName = language.dropdowns[name]
186
+ language.nativeDropdowns[nativeName] = name
187
+ })
188
+
189
+ language.code = code
190
+ allLanguages[code] = language
191
+ }
192
+ export function loadLanguages(languages) {
193
+ Object.keys(languages).forEach(code => loadLanguage(code, languages[code]))
194
+ }
195
+
196
+ export const english = {
197
+ aliases: {
198
+ "turn ccw %1 degrees": "MOTION_TURNLEFT",
199
+ "turn left %1 degrees": "MOTION_TURNLEFT",
200
+ "turn cw %1 degrees": "MOTION_TURNRIGHT",
201
+ "turn right %1 degrees": "MOTION_TURNRIGHT",
202
+ "when flag clicked": "EVENT_WHENFLAGCLICKED",
203
+ "when gf clicked": "EVENT_WHENFLAGCLICKED",
204
+ "when green flag clicked": "EVENT_WHENFLAGCLICKED",
205
+ },
206
+
207
+ renamedBlocks: {
208
+ "say %1 for %2 secs": "LOOKS_SAYFORSECS",
209
+ "think %1 for %2 secs": "LOOKS_THINKFORSECS",
210
+ "play sound %1": "SOUND_PLAY",
211
+ "wait %1 secs": "CONTROL_WAIT",
212
+ clear: "pen.clear",
213
+ },
214
+
215
+ definePrefix: ["define"],
216
+ defineSuffix: [],
217
+
218
+ // For ignoring the lt sign in the "when distance < _" block
219
+ ignorelt: ["when distance"],
220
+
221
+ // Valid arguments to "of" dropdown, for resolving ambiguous situations
222
+ math: [
223
+ "abs",
224
+ "floor",
225
+ "ceiling",
226
+ "sqrt",
227
+ "sin",
228
+ "cos",
229
+ "tan",
230
+ "asin",
231
+ "acos",
232
+ "atan",
233
+ "ln",
234
+ "log",
235
+ "e ^",
236
+ "10 ^",
237
+ ],
238
+
239
+ // Language name is needed for the English locale as well
240
+ name: "English",
241
+
242
+ // Valid arguments to "sound effect" dropdown, for resolving ambiguous situations
243
+ soundEffects: ["pitch", "pan left/right"],
244
+
245
+ // Valid arguments to "microbit when" dropdown
246
+ microbitWhen: ["moved", "shaken", "jumped"],
247
+
248
+ // For detecting the "stop" cap / stack block
249
+ osis: ["other scripts in sprite", "other scripts in stage"],
250
+
251
+ dropdowns: {},
252
+
253
+ commands: {},
254
+ }
255
+ allBlocks.forEach(info => {
256
+ english.commands[info.id] = info.spec
257
+ })
258
+ loadLanguages({
259
+ en: english,
260
+ })
261
+
262
+ /*****************************************************************************/
263
+
264
+ function registerCheck(id, func) {
265
+ if (!blocksById[id]) {
266
+ throw new Error(`Unknown ID: ${id}`)
267
+ }
268
+ blocksById[id].accepts = func
269
+ }
270
+
271
+ function specialCase(id, func) {
272
+ if (!blocksById[id]) {
273
+ throw new Error(`Unknown ID: ${id}`)
274
+ }
275
+ blocksById[id].specialCase = func
276
+ }
277
+
278
+ function disambig(id1, id2, test) {
279
+ registerCheck(id1, (_, children, lang) => {
280
+ return test(children, lang)
281
+ })
282
+ registerCheck(id2, (_, children, lang) => {
283
+ return !test(children, lang)
284
+ })
285
+ }
286
+
287
+ disambig("OPERATORS_MATHOP", "SENSING_OF", (children, lang) => {
288
+ // Operators if math function, otherwise sensing "attribute of" block
289
+ const first = children[0]
290
+ if (!first.isInput) {
291
+ return
292
+ }
293
+ const name = first.value
294
+ return lang.math.includes(name)
295
+ })
296
+
297
+ disambig("SOUND_CHANGEEFFECTBY", "LOOKS_CHANGEEFFECTBY", (children, lang) => {
298
+ // Sound if sound effect, otherwise default to graphic effect
299
+ for (const child of children) {
300
+ if (child.shape === "dropdown") {
301
+ const name = child.value
302
+ for (const effect of lang.soundEffects) {
303
+ if (minifyHash(effect) === minifyHash(name)) {
304
+ return true
305
+ }
306
+ }
307
+ }
308
+ }
309
+ return false
310
+ })
311
+
312
+ disambig("SOUND_SETEFFECTO", "LOOKS_SETEFFECTTO", (children, lang) => {
313
+ // Sound if sound effect, otherwise default to graphic effect
314
+ for (const child of children) {
315
+ if (child.shape === "dropdown") {
316
+ const name = child.value
317
+ for (const effect of lang.soundEffects) {
318
+ if (minifyHash(effect) === minifyHash(name)) {
319
+ return true
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return false
325
+ })
326
+
327
+ disambig("DATA_LENGTHOFLIST", "OPERATORS_LENGTH", (children, _lang) => {
328
+ // List block if dropdown, otherwise operators
329
+ const last = children[children.length - 1]
330
+ if (!last.isInput) {
331
+ return
332
+ }
333
+ return last.shape === "dropdown"
334
+ })
335
+
336
+ disambig("DATA_LISTCONTAINSITEM", "OPERATORS_CONTAINS", (children, _lang) => {
337
+ // List block if dropdown, otherwise operators
338
+ const first = children[0]
339
+ if (!first.isInput) {
340
+ return
341
+ }
342
+ return first.shape === "dropdown"
343
+ })
344
+
345
+ disambig("pen.setColor", "pen.setHue", (children, _lang) => {
346
+ // Color block if color input, otherwise numeric
347
+ const last = children[children.length - 1]
348
+ // If variable, assume color input, since the RGBA hack is common.
349
+ // TODO fix Scratch :P
350
+ return (last.isInput && last.isColor) || last.isBlock
351
+ })
352
+
353
+ disambig("microbit.whenGesture", "gdxfor.whenGesture", (children, lang) => {
354
+ for (const child of children) {
355
+ if (child.shape === "dropdown") {
356
+ const name = child.value
357
+ // Yes, "when shaken" gdxfor block exists. But microbit is more common.
358
+ for (const effect of lang.microbitWhen) {
359
+ if (minifyHash(effect) === minifyHash(name)) {
360
+ return true
361
+ }
362
+ }
363
+ }
364
+ }
365
+ return false
366
+ })
367
+
368
+ // This block does not need disambiguation in English;
369
+ // however, many other languages do require that.
370
+ disambig("ev3.buttonPressed", "microbit.isButtonPressed", (children, _lang) => {
371
+ for (const child of children) {
372
+ if (child.shape === "dropdown") {
373
+ // EV3 "button pressed" block uses numeric identifier
374
+ // and does not support "any".
375
+ switch (minifyHash(child.value)) {
376
+ case "1":
377
+ case "2":
378
+ case "3":
379
+ case "4":
380
+ return true
381
+ default:
382
+ }
383
+ }
384
+ }
385
+ return false
386
+ })
387
+
388
+ specialCase("CONTROL_STOP", (_, children, lang) => {
389
+ // Cap block unless argument is "other scripts in sprite"
390
+ const last = children[children.length - 1]
391
+ if (!last.isInput) {
392
+ return
393
+ }
394
+ const value = last.value
395
+ if (lang.osis.includes(value)) {
396
+ return { ...blocksById.CONTROL_STOP, shape: "stack" }
397
+ }
398
+ })
399
+
400
+ export function lookupHash(hash, info, children, languages) {
401
+ for (const lang of languages) {
402
+ if (Object.prototype.hasOwnProperty.call(lang.blocksByHash, hash)) {
403
+ const collisions = lang.blocksByHash[hash]
404
+ for (let block of collisions) {
405
+ if (
406
+ info.shape === "reporter" &&
407
+ block.shape !== "reporter" &&
408
+ block.shape !== "ring"
409
+ ) {
410
+ continue
411
+ }
412
+ if (info.shape === "boolean" && block.shape !== "boolean") {
413
+ continue
414
+ }
415
+ if (collisions.length > 1) {
416
+ // Only check in case of collision;
417
+ // perform "disambiguation"
418
+ if (block.accepts && !block.accepts(info, children, lang)) {
419
+ continue
420
+ }
421
+ }
422
+ if (block.specialCase) {
423
+ block = block.specialCase(info, children, lang) || block
424
+ }
425
+ return { type: block, lang: lang }
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ export function lookupDropdown(name, languages) {
432
+ for (const lang of languages) {
433
+ if (Object.prototype.hasOwnProperty.call(lang.nativeDropdowns, name)) {
434
+ return lang.nativeDropdowns[name]
435
+ }
436
+ }
437
+ }
438
+
439
+ export function applyOverrides(info, overrides) {
440
+ for (const name of overrides) {
441
+ if (hexColorPat.test(name)) {
442
+ info.color = name
443
+ info.category = ""
444
+ info.categoryIsDefault = false
445
+ } else if (overrideCategories.includes(name)) {
446
+ info.category = name
447
+ info.categoryIsDefault = false
448
+ } else if (overrideShapes.includes(name)) {
449
+ info.shape = name
450
+ } else if (name === "loop") {
451
+ info.hasLoopArrow = true
452
+ } else if (name === "+" || name === "-") {
453
+ info.diff = name
454
+ }
455
+ }
456
+ }
457
+
458
+ export function blockName(block) {
459
+ const words = []
460
+ for (const child of block.children) {
461
+ if (!child.isLabel) {
462
+ return
463
+ }
464
+ words.push(child.value)
465
+ }
466
+ return words.join(" ")
467
+ }