mumuki-puzzle-runner 0.5.0 → 1.0.3
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.
- checksums.yaml +4 -4
- data/lib/public/css/muzzle-editor.css +41 -2
- data/lib/public/js/muzzle-editor.js +32 -15
- data/lib/public/js/muzzle.js +193 -55
- data/lib/public/vendor/headbreaker.d.ts +19 -3
- data/lib/public/vendor/headbreaker.js +1 -1
- data/lib/public/vendor/headbreaker.js.map +1 -1
- data/lib/version_hook.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 969e3f9fc0351a8ef3700be527ff4a977df0d5be6f49a55d9857255ffda6b9af
|
4
|
+
data.tar.gz: f388dce7952f3abd843b27a2f82744d5519716a0a6c5d461fc9842d3f8c0562e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fb7208972a9c6efa975a6272bb95c9b62613f63a5fea7c6b115058b708d44b199455e2b90f3ebffca01d41076c8a178191e0e960f0889938356253fc91b64af
|
7
|
+
data.tar.gz: 8e8f8c653af72c67e7199ff72d6be49f1c790f4a9cd4c700c699f66f8aa913b01ab0b7725ae1c71efca6bb062471e8b799471e3078e26ab32daa457dce186e79
|
@@ -1,3 +1,15 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* ============
|
4
|
+
* Initial size
|
5
|
+
* ============
|
6
|
+
*/
|
7
|
+
|
8
|
+
.mu-exercise-content {
|
9
|
+
border-radius: 10px;
|
10
|
+
border: 1px solid #dddddd;
|
11
|
+
}
|
12
|
+
|
1
13
|
.mu-kids-state-image img {
|
2
14
|
height: 100%;
|
3
15
|
width: auto;
|
@@ -8,6 +20,33 @@
|
|
8
20
|
width: 100%;
|
9
21
|
}
|
10
22
|
|
11
|
-
|
12
|
-
|
23
|
+
/*
|
24
|
+
* ====================
|
25
|
+
* Submit button hiding
|
26
|
+
* ====================
|
27
|
+
*/
|
28
|
+
|
29
|
+
/*
|
30
|
+
* ===========
|
31
|
+
* Blur effect
|
32
|
+
* ===========
|
33
|
+
*/
|
34
|
+
|
35
|
+
.mu-kids-exercise-workspace.mu-full-workspace .mu-kids-submit-button {
|
36
|
+
margin: 0 30px 30px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.mu-kids-exercise-workspace .mu-kids-blocks {
|
40
|
+
overflow: hidden;
|
41
|
+
}
|
42
|
+
|
43
|
+
.mu-kids-exercise-workspace .mu-kids-blocks:after {
|
44
|
+
content: ' ';
|
45
|
+
box-shadow: inset 0 0 30px 30px #FFFFFF;
|
46
|
+
position: absolute;
|
47
|
+
top: 0;
|
48
|
+
left: 0;
|
49
|
+
right: 0;
|
50
|
+
bottom: 0;
|
51
|
+
pointer-events: none;
|
13
52
|
}
|
@@ -1,12 +1,5 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
$(() => {
|
3
|
-
function register(event, callback) {
|
4
|
-
const _event = Muzzle[event];
|
5
|
-
Muzzle[event] = (...args) => {
|
6
|
-
callback(...args);
|
7
|
-
_event(...args);
|
8
|
-
}
|
9
|
-
}
|
10
3
|
|
11
4
|
// ================
|
12
5
|
// Muzzle rendering
|
@@ -35,9 +28,9 @@ $(() => {
|
|
35
28
|
getContent() { return { name: "client_result[status]", value: Muzzle.clientResultStatus }; }
|
36
29
|
})
|
37
30
|
|
38
|
-
//
|
31
|
+
// Required to actually bind Muzzle's submit to
|
39
32
|
// mumuki's solution processing
|
40
|
-
register('onSubmit', (submission) => {
|
33
|
+
Muzzle.register('onSubmit', (submission) => {
|
41
34
|
mumuki.submission.processSolution(submission);
|
42
35
|
});
|
43
36
|
|
@@ -45,13 +38,20 @@ $(() => {
|
|
45
38
|
// Kids config
|
46
39
|
// ===========
|
47
40
|
|
48
|
-
mumuki.kids.registerStateScaler(($state, fullMargin
|
49
|
-
|
41
|
+
mumuki.kids.registerStateScaler(($state, fullMargin) => {
|
42
|
+
const $image = $state.find('img');
|
43
|
+
if (!$image.length) return;
|
44
|
+
|
45
|
+
$image.css('transform', 'scale(1)');
|
46
|
+
const width = ($state.width() - fullMargin) / $image.width();
|
47
|
+
const height = ($state.height() - fullMargin) / $image.height();
|
48
|
+
$image.css('transform', 'scale(' + Math.min(width, height) + ')');
|
50
49
|
});
|
51
50
|
|
52
51
|
mumuki.kids.registerBlocksAreaScaler(($blocks) => {
|
52
|
+
console.debug("Scaler fired");
|
53
53
|
const maxHeight = $('.mu-kids-exercise').height() - $('.mu-kids-exercise-description').height();
|
54
|
-
Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight));
|
54
|
+
Muzzle.run(() => Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight)));
|
55
55
|
});
|
56
56
|
|
57
57
|
Muzzle.manualScale = true;
|
@@ -60,9 +60,9 @@ $(() => {
|
|
60
60
|
// Submit button hiding
|
61
61
|
// ====================
|
62
62
|
|
63
|
-
register('onReady', () => {
|
63
|
+
Muzzle.register('onReady', () => {
|
64
64
|
if (Muzzle.simple) {
|
65
|
-
$('.mu-kids-exercise-workspace').addClass('
|
65
|
+
$('.mu-kids-exercise-workspace').addClass('mu-submitless-exercise');
|
66
66
|
}
|
67
67
|
});
|
68
68
|
|
@@ -70,11 +70,28 @@ $(() => {
|
|
70
70
|
// Assets loading
|
71
71
|
// ==============
|
72
72
|
|
73
|
-
register('onReady', () => {
|
73
|
+
Muzzle.register('onReady', () => {
|
74
|
+
console.debug("Muzzle is ready");
|
75
|
+
|
74
76
|
mumuki.assetsLoadedFor('editor');
|
75
77
|
// although layout assets
|
76
78
|
// are actually loaded before this script, puzzle runner is not aimed
|
77
79
|
// to be used without a custom editor
|
78
80
|
mumuki.assetsLoadedFor('layout');
|
81
|
+
|
82
|
+
mumuki.I18n.register({
|
83
|
+
'es': {
|
84
|
+
'kindergarten_passed': '¡Muy bien! Armaste el rompecabezas'
|
85
|
+
},
|
86
|
+
'es-CL': {
|
87
|
+
'kindergarten_passed': '¡Muy bien! Armaste el rompecabezas'
|
88
|
+
},
|
89
|
+
'en': {
|
90
|
+
'kindergarten_passed': 'Very well! You solve the puzzle'
|
91
|
+
},
|
92
|
+
'pt': {
|
93
|
+
'kindergarten_passed': 'Muito bom! Você mentou o quebra-cabeça'
|
94
|
+
},
|
95
|
+
});
|
79
96
|
});
|
80
97
|
});
|
data/lib/public/js/muzzle.js
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* @typedef {object} PieceConfig
|
3
|
-
* @property {string} imagePath
|
4
|
-
* @property {string} structure
|
5
|
-
*/
|
6
|
-
|
7
1
|
/**
|
8
2
|
* @typedef {number[]} Point
|
9
3
|
*/
|
@@ -14,6 +8,14 @@
|
|
14
8
|
*/
|
15
9
|
|
16
10
|
|
11
|
+
class MuzzlePainter extends headbreaker.painters.Konva {
|
12
|
+
_newLine(options) {
|
13
|
+
|
14
|
+
const line = super._newLine(options);
|
15
|
+
line.strokeScaleEnabled(false);
|
16
|
+
return line;
|
17
|
+
}
|
18
|
+
}
|
17
19
|
|
18
20
|
/**
|
19
21
|
* Facade for referencing and creating a global puzzle canvas,
|
@@ -103,7 +105,7 @@ class MuzzleCanvas {
|
|
103
105
|
this.pieceSize = 100;
|
104
106
|
|
105
107
|
/**
|
106
|
-
* The x:y aspect ratio of the piece. Set null for automatic
|
108
|
+
* The `x:y` aspect ratio of the piece. Set null for automatic
|
107
109
|
* aspectRatio
|
108
110
|
*
|
109
111
|
* @type {number}
|
@@ -112,14 +114,27 @@ class MuzzleCanvas {
|
|
112
114
|
|
113
115
|
/**
|
114
116
|
* If the images should be adjusted vertically instead of horizontally
|
115
|
-
* to puzzle dimensions.
|
117
|
+
* to puzzle dimensions.
|
118
|
+
*
|
119
|
+
* Set null for automatic fit.
|
116
120
|
*
|
117
121
|
* @type {boolean}
|
118
122
|
*/
|
119
|
-
this.fitImagesVertically =
|
123
|
+
this.fitImagesVertically = null;
|
120
124
|
|
125
|
+
/**
|
126
|
+
* Wether the scaling should ignore the scaler
|
127
|
+
* rise events
|
128
|
+
*/
|
121
129
|
this.manualScale = false;
|
122
130
|
|
131
|
+
/**
|
132
|
+
* The canvas shuffler.
|
133
|
+
*
|
134
|
+
* Set it null to automatic shuffling algorithm selection.
|
135
|
+
*/
|
136
|
+
this.shuffler = null;
|
137
|
+
|
123
138
|
/**
|
124
139
|
* Callback that will be executed
|
125
140
|
* when muzzle has fully loaded and rendered its first
|
@@ -142,7 +157,7 @@ class MuzzleCanvas {
|
|
142
157
|
* Whether the current puzzle can be solved in very few tries.
|
143
158
|
*
|
144
159
|
* Set null for automatic configuration of this property. Basic puzzles will be considered
|
145
|
-
* basic and match puzzles will be considered non-
|
160
|
+
* basic and match puzzles will be considered non-simple.
|
146
161
|
*
|
147
162
|
* @type {boolean}
|
148
163
|
*/
|
@@ -150,6 +165,16 @@ class MuzzleCanvas {
|
|
150
165
|
|
151
166
|
this.spiky = false;
|
152
167
|
|
168
|
+
/**
|
169
|
+
* The reference insert axis, used at rounded outline to compute insert internal and external diameters
|
170
|
+
*
|
171
|
+
* Set null for default computation of axis - no axis reference for basic boards
|
172
|
+
* and vertical axis for match
|
173
|
+
*
|
174
|
+
* @type {Axis}
|
175
|
+
* */
|
176
|
+
this.referenceInsertAxis = null;
|
177
|
+
|
153
178
|
/**
|
154
179
|
* Callback to be executed when submitting puzzle.
|
155
180
|
*
|
@@ -168,20 +193,29 @@ class MuzzleCanvas {
|
|
168
193
|
* property with any code you need the be called here
|
169
194
|
*/
|
170
195
|
this.onValid = () => {};
|
171
|
-
}
|
172
196
|
|
197
|
+
/**
|
198
|
+
* @private
|
199
|
+
*/
|
200
|
+
this._ready = false;
|
201
|
+
}
|
173
202
|
|
203
|
+
get painter() {
|
204
|
+
return new MuzzlePainter();
|
205
|
+
}
|
174
206
|
|
175
207
|
/**
|
176
208
|
*/
|
177
209
|
get baseConfig() {
|
178
210
|
return Object.assign({
|
211
|
+
preventOffstageDrag: true,
|
179
212
|
width: this.canvasWidth,
|
180
213
|
height: this.canvasHeight,
|
181
214
|
pieceSize: this.adjustedPieceSize,
|
182
215
|
proximity: Math.min(this.adjustedPieceSize.x, this.adjustedPieceSize.y) / 5,
|
183
216
|
strokeWidth: this.strokeWidth,
|
184
|
-
lineSoftness: 0.18
|
217
|
+
lineSoftness: 0.18,
|
218
|
+
painter: this.painter
|
185
219
|
}, this.outlineConfig);
|
186
220
|
}
|
187
221
|
|
@@ -195,7 +229,12 @@ class MuzzleCanvas {
|
|
195
229
|
} else {
|
196
230
|
return {
|
197
231
|
borderFill: 0,
|
198
|
-
outline: new headbreaker.outline.Rounded({
|
232
|
+
outline: new headbreaker.outline.Rounded({
|
233
|
+
bezelize: true,
|
234
|
+
insertDepth: 3/5,
|
235
|
+
bezelDepth: 9/10,
|
236
|
+
referenceInsertAxis: this.referenceInsertAxis
|
237
|
+
}),
|
199
238
|
}
|
200
239
|
}
|
201
240
|
}
|
@@ -207,8 +246,8 @@ class MuzzleCanvas {
|
|
207
246
|
*/
|
208
247
|
get adjustedPieceSize() {
|
209
248
|
if (!this._adjustedPieceSize) {
|
210
|
-
const aspectRatio = this.
|
211
|
-
this._adjustedPieceSize = headbreaker.vector(this.pieceSize
|
249
|
+
const aspectRatio = this.effectiveAspectRatio;
|
250
|
+
this._adjustedPieceSize = headbreaker.vector(this.pieceSize * aspectRatio, this.pieceSize);
|
212
251
|
}
|
213
252
|
return this._adjustedPieceSize;
|
214
253
|
}
|
@@ -217,10 +256,18 @@ class MuzzleCanvas {
|
|
217
256
|
* @type {Axis}
|
218
257
|
*/
|
219
258
|
get imageAdjustmentAxis() {
|
220
|
-
console.log(this.fitImagesVertically)
|
221
259
|
return this.fitImagesVertically ? headbreaker.Vertical : headbreaker.Horizontal;
|
222
260
|
}
|
223
261
|
|
262
|
+
/**
|
263
|
+
* The configured aspect ratio, or 1
|
264
|
+
*
|
265
|
+
* @type {number}
|
266
|
+
*/
|
267
|
+
get effectiveAspectRatio() {
|
268
|
+
return this.aspectRatio || 1;
|
269
|
+
}
|
270
|
+
|
224
271
|
/**
|
225
272
|
* The currently active canvas, or null if
|
226
273
|
* it has not yet initialized
|
@@ -234,7 +281,7 @@ class MuzzleCanvas {
|
|
234
281
|
/**
|
235
282
|
* Draws the - previusly built - current canvas.
|
236
283
|
*
|
237
|
-
* Prefer
|
284
|
+
* Prefer `this.currentCanvas.redraw()` when performing
|
238
285
|
* small updates to the pieces.
|
239
286
|
*/
|
240
287
|
draw() {
|
@@ -263,13 +310,9 @@ class MuzzleCanvas {
|
|
263
310
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
264
311
|
*/
|
265
312
|
async basic(x, y, imagePath) {
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
if (this.simple === null) {
|
271
|
-
this.simple = true;
|
272
|
-
}
|
313
|
+
this._config('aspectRatio', y / x);
|
314
|
+
this._config('simple', true);
|
315
|
+
this._config('shuffler', Muzzle.Shuffler.grid);
|
273
316
|
|
274
317
|
/**
|
275
318
|
* @todo take all container size
|
@@ -281,7 +324,6 @@ class MuzzleCanvas {
|
|
281
324
|
canvas.adjustImagesToPuzzle(this.imageAdjustmentAxis);
|
282
325
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y });
|
283
326
|
this._attachBasicValidator(canvas);
|
284
|
-
canvas.shuffleGrid(0.8);
|
285
327
|
this._configCanvas(canvas);
|
286
328
|
canvas.onValid(() => {
|
287
329
|
setTimeout(() => {
|
@@ -294,52 +336,94 @@ class MuzzleCanvas {
|
|
294
336
|
}
|
295
337
|
|
296
338
|
/**
|
297
|
-
*
|
298
|
-
*
|
299
|
-
*
|
339
|
+
* Creates a choose puzzle, where a single right piece must match the single left piece,
|
340
|
+
* choosing the latter from a bunch of other left odd pieces. By default, `Muzzle.Shuffler.line` shuffling is used.
|
341
|
+
*
|
342
|
+
* This is a particular case of a match puzzle with line
|
343
|
+
*
|
344
|
+
* @param {string} leftUrl the url of the left piece
|
345
|
+
* @param {string} rightUrl the url of the right piece
|
346
|
+
* @param {string[]} leftOddUrls the urls of the off left urls
|
347
|
+
* @param {number} [rightAspectRatio] the `x:y` ratio of the right pieces, that override the general `aspectRatio` of the puzzle.
|
348
|
+
* Use null to have the same aspect ratio as left pieces
|
349
|
+
*
|
300
350
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
301
351
|
*/
|
302
|
-
async
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
const canvas = this._createCanvas();
|
307
|
-
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y * count });
|
308
|
-
|
309
|
-
// todo validate
|
310
|
-
// todo set images
|
311
|
-
|
312
|
-
this._configCanvas(canvas);
|
313
|
-
return canvas;
|
352
|
+
async choose(leftUrl, rightUrl, leftOddUrls, rightAspectRatio = null) {
|
353
|
+
this._config('shuffler', Muzzle.Shuffler.line);
|
354
|
+
return this.match([leftUrl], [rightUrl], {leftOddUrls, rightAspectRatio});
|
314
355
|
}
|
315
356
|
|
316
357
|
/**
|
317
|
-
*
|
318
|
-
* with optional odd left and right pieces that don't match
|
358
|
+
* Creates a match puzzle, where left pieces are matched against right pieces,
|
359
|
+
* with optional odd left and right pieces that don't match. By default, `Muzzle.Shuffler.columns`
|
360
|
+
* shuffling is used.
|
319
361
|
*
|
320
362
|
* @param {string[]} leftUrls
|
321
363
|
* @param {string[]} rightUrls must be of the same size of lefts
|
322
|
-
* @param {
|
323
|
-
* @param {string[]}
|
364
|
+
* @param {object} [options]
|
365
|
+
* @param {string[]} [options.leftOddUrls]
|
366
|
+
* @param {string[]} [options.rightOddUrls]
|
367
|
+
* @param {number?} [options.rightAspectRatio] the aspect ratio of the right pieces. Use null to have the same aspect ratio as left pieces
|
324
368
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
325
369
|
*/
|
326
|
-
async match(leftUrls, rightUrls, leftOddUrls = [], rightOddUrls = []) {
|
370
|
+
async match(leftUrls, rightUrls, {leftOddUrls = [], rightOddUrls = [], rightAspectRatio = this.effectiveAspectRatio} = {}) {
|
371
|
+
const rightWidthRatio = rightAspectRatio / this.effectiveAspectRatio;
|
372
|
+
|
373
|
+
this._config('simple', false);
|
374
|
+
this._config('shuffler', Muzzle.Shuffler.columns);
|
375
|
+
this._config('fitImagesVertically', rightWidthRatio > 1);
|
376
|
+
this._config('referenceInsertAxis', headbreaker.Vertical);
|
377
|
+
|
327
378
|
/** @private @type {(Promise<Template>)[]} */
|
328
379
|
const templatePromises = [];
|
329
|
-
|
330
|
-
|
380
|
+
|
381
|
+
const rightSize = headbreaker.diameter(
|
382
|
+
headbreaker.Vector.multiply(this.adjustedPieceSize, headbreaker.vector(rightWidthRatio, 1)));
|
383
|
+
|
384
|
+
const pushTemplate = (path, options) =>
|
385
|
+
templatePromises.push(this._createMatchTemplate(path, options));
|
386
|
+
|
387
|
+
const pushLeftTemplate = (index, path, options) =>
|
388
|
+
pushTemplate(path, {
|
389
|
+
left: true,
|
390
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(1, index)),
|
391
|
+
...options
|
392
|
+
});
|
393
|
+
|
394
|
+
const pushRightTemplate = (index, path, options) =>
|
395
|
+
pushTemplate(path, {
|
396
|
+
size: rightSize,
|
397
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(2, index)),
|
398
|
+
...options
|
399
|
+
});
|
331
400
|
|
332
401
|
const last = leftUrls.length - 1;
|
333
402
|
for (let i = 0; i <= last; i++) {
|
334
403
|
const leftId = `l${i}`;
|
335
404
|
const rightId = `r${i}`;
|
336
405
|
|
337
|
-
|
338
|
-
|
406
|
+
pushLeftTemplate(i + 1, leftUrls[i], {
|
407
|
+
id: leftId,
|
408
|
+
rightTargetId: rightId
|
409
|
+
});
|
410
|
+
pushRightTemplate(i + 1, rightUrls[i], {
|
411
|
+
id: rightId
|
412
|
+
});
|
339
413
|
}
|
340
414
|
|
341
|
-
leftOddUrls.forEach((it, i) =>
|
342
|
-
|
415
|
+
leftOddUrls.forEach((it, i) =>
|
416
|
+
pushLeftTemplate(i + leftUrls.length, it, {
|
417
|
+
id: `lo${i}`,
|
418
|
+
odd: true
|
419
|
+
})
|
420
|
+
);
|
421
|
+
rightOddUrls.forEach((it, i) =>
|
422
|
+
pushRightTemplate(i + rightUrls.length, it, {
|
423
|
+
id: `ro${i}`,
|
424
|
+
odd: true
|
425
|
+
})
|
426
|
+
);
|
343
427
|
|
344
428
|
// + Math.max(leftOddUrls.length, rightOddUrls.length)
|
345
429
|
const templates = await Promise.all(templatePromises);
|
@@ -347,7 +431,6 @@ class MuzzleCanvas {
|
|
347
431
|
const canvas = this._createCanvas({ maxPiecesCount: {x: 2, y: leftUrls.length} });
|
348
432
|
canvas.adjustImagesToPiece(this.imageAdjustmentAxis);
|
349
433
|
templates.forEach(it => canvas.sketchPiece(it));
|
350
|
-
canvas.shuffleColumns(0.8);
|
351
434
|
this._attachMatchValidator(canvas);
|
352
435
|
this._configCanvas(canvas);
|
353
436
|
return canvas;
|
@@ -412,11 +495,11 @@ class MuzzleCanvas {
|
|
412
495
|
* @param {object} options
|
413
496
|
* @returns {Promise<object>}
|
414
497
|
*/
|
415
|
-
_createMatchTemplate(imagePath, {id, left = false, targetPosition = null, rightTargetId = null, odd = false}) {
|
498
|
+
_createMatchTemplate(imagePath, {id, left = false, targetPosition = null, rightTargetId = null, odd = false, size = null}) {
|
416
499
|
const structure = left ? 'T-N-' : `N-S-`;
|
417
|
-
|
418
500
|
return this._loadImage(imagePath).then((image) => {
|
419
501
|
return {
|
502
|
+
...(size ? {size} : {}),
|
420
503
|
structure,
|
421
504
|
metadata: { id, left, odd, rightTargetId, image, targetPosition }
|
422
505
|
}
|
@@ -429,6 +512,7 @@ class MuzzleCanvas {
|
|
429
512
|
*/
|
430
513
|
_configCanvas(canvas) {
|
431
514
|
this._canvas = canvas;
|
515
|
+
this._canvas.shuffleWith(0.8, this.shuffler);
|
432
516
|
this._canvas.onValid(() => {
|
433
517
|
setTimeout(() => this.onValid(), 0);
|
434
518
|
});
|
@@ -441,6 +525,7 @@ class MuzzleCanvas {
|
|
441
525
|
|
442
526
|
['resize', 'load'].forEach((event) => {
|
443
527
|
window.addEventListener(event, () => {
|
528
|
+
console.debug("Scaler event fired:", event);
|
444
529
|
var container = document.getElementById(this.canvasId);
|
445
530
|
this.scale(container.offsetWidth, container.scrollHeight);
|
446
531
|
});
|
@@ -455,6 +540,8 @@ class MuzzleCanvas {
|
|
455
540
|
*/
|
456
541
|
scale(width, height) {
|
457
542
|
if (this.fixedDimensions || !this.canvas) return;
|
543
|
+
|
544
|
+
console.debug("Scaling:", {width, height})
|
458
545
|
const factor = this.optimalScaleFactor(width, height);
|
459
546
|
this.canvas.resize(width, height);
|
460
547
|
this.canvas.scale(factor);
|
@@ -478,7 +565,7 @@ class MuzzleCanvas {
|
|
478
565
|
const maxX = Math.max(...xs);
|
479
566
|
const maxY = Math.max(...ys);
|
480
567
|
|
481
|
-
return headbreaker.
|
568
|
+
return headbreaker.vector(maxX - minX, maxY - minY);
|
482
569
|
})();
|
483
570
|
const diff = headbreaker.Vector.minus(area, realDiameter);
|
484
571
|
const semi = headbreaker.Vector.divide(diff, -2);
|
@@ -513,9 +600,14 @@ class MuzzleCanvas {
|
|
513
600
|
this.loadPreviousSolution();
|
514
601
|
this.resetCoordinates();
|
515
602
|
this.draw();
|
603
|
+
this._ready = true;
|
516
604
|
this.onReady();
|
517
605
|
}
|
518
606
|
|
607
|
+
isReady() {
|
608
|
+
return this._ready;
|
609
|
+
}
|
610
|
+
|
519
611
|
// ===========
|
520
612
|
// Persistence
|
521
613
|
// ===========
|
@@ -604,12 +696,58 @@ class MuzzleCanvas {
|
|
604
696
|
};
|
605
697
|
}
|
606
698
|
|
699
|
+
/**
|
700
|
+
* @param {string} key
|
701
|
+
* @param {any} value
|
702
|
+
*/
|
703
|
+
_config(key, value) {
|
704
|
+
const current = this[key];
|
705
|
+
console.debug("Setting config: ", [key, value])
|
706
|
+
|
707
|
+
if (current === null) {
|
708
|
+
this[key] = value;
|
709
|
+
}
|
710
|
+
}
|
711
|
+
|
712
|
+
// ==============
|
713
|
+
// Event handling
|
714
|
+
// ==============
|
715
|
+
|
716
|
+
|
717
|
+
/**
|
718
|
+
* Registers an event handler
|
719
|
+
*
|
720
|
+
* @param {string} event
|
721
|
+
* @param {(...args: any) => void} callback
|
722
|
+
*/
|
723
|
+
register(event, callback) {
|
724
|
+
const _event = this[event];
|
725
|
+
this[event] = (...args) => {
|
726
|
+
callback(...args);
|
727
|
+
_event(...args);
|
728
|
+
}
|
729
|
+
}
|
730
|
+
|
731
|
+
/**
|
732
|
+
* Runs the given action if muzzle is ready,
|
733
|
+
* queueing it otherwise
|
734
|
+
* @param {() => void} callback
|
735
|
+
*/
|
736
|
+
run(callback) {
|
737
|
+
if (this.isReady()) {
|
738
|
+
callback();
|
739
|
+
} else {
|
740
|
+
this.register('onReady', callback);
|
741
|
+
}
|
742
|
+
}
|
607
743
|
}
|
608
744
|
|
609
745
|
const Muzzle = new class extends MuzzleCanvas {
|
610
746
|
constructor() {
|
611
747
|
super();
|
612
748
|
this.aux = {};
|
749
|
+
|
750
|
+
this.Shuffler = headbreaker.Shuffler;
|
613
751
|
}
|
614
752
|
|
615
753
|
/**
|