mumuki-puzzle-runner 0.5.0 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
/**
|