mumuki-puzzle-runner 0.4.0 → 1.0.2
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 +45 -2
- data/lib/public/js/muzzle-editor.js +16 -14
- data/lib/public/js/muzzle.js +247 -59
- data/lib/public/vendor/headbreaker.d.ts +39 -7
- 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: babd3a8ed60a9345983363242f5e1fc1bee0bb36a471d136c314ba5fba8c4ed1
|
4
|
+
data.tar.gz: 62d411ca5bfeca771b9b78e113be587c72fdbea23d811e91635cae4a9c703e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5baff4c0cdac71f1d0038d3cced0c45fd852be5e6712cb470cdb601981d01dbf0f230bea99c07cca85d914bbcff2a84a042ee853f18ed94f7e1583559142861f
|
7
|
+
data.tar.gz: fa0b70e5c7ce9529c28fda9bb9b1f464c62bcc2f768fa4bd082fe2193684267e2c3e239361c7a4645b18bbbf7f9ab27dd374be0978711e53634e6858d83135a7
|
@@ -1,3 +1,19 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* ============
|
4
|
+
* Initial size
|
5
|
+
* ============
|
6
|
+
*/
|
7
|
+
|
8
|
+
.mu-exercise-content {
|
9
|
+
border-radius: 10px;
|
10
|
+
border: 1px solid #dddddd;
|
11
|
+
}
|
12
|
+
|
13
|
+
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-submit-button {
|
14
|
+
display: none;
|
15
|
+
}
|
16
|
+
|
1
17
|
.mu-kids-state-image img {
|
2
18
|
height: 100%;
|
3
19
|
width: auto;
|
@@ -8,6 +24,33 @@
|
|
8
24
|
width: 100%;
|
9
25
|
}
|
10
26
|
|
11
|
-
|
12
|
-
|
27
|
+
/*
|
28
|
+
* ====================
|
29
|
+
* Submit button hiding
|
30
|
+
* ====================
|
31
|
+
*/
|
32
|
+
|
33
|
+
/*
|
34
|
+
* ===========
|
35
|
+
* Blur effect
|
36
|
+
* ===========
|
37
|
+
*/
|
38
|
+
|
39
|
+
.mu-kids-exercise-workspace.mu-full-workspace .mu-kids-submit-button {
|
40
|
+
margin: 0 30px 30px;
|
41
|
+
}
|
42
|
+
|
43
|
+
.mu-kids-exercise-workspace .mu-kids-blocks {
|
44
|
+
overflow: hidden;
|
45
|
+
}
|
46
|
+
|
47
|
+
.mu-kids-exercise-workspace .mu-kids-blocks:after {
|
48
|
+
content: ' ';
|
49
|
+
box-shadow: inset 0 0 30px 30px #FFFFFF;
|
50
|
+
position: absolute;
|
51
|
+
top: 0;
|
52
|
+
left: 0;
|
53
|
+
right: 0;
|
54
|
+
bottom: 0;
|
55
|
+
pointer-events: none;
|
13
56
|
}
|
@@ -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,7 +60,7 @@ $(() => {
|
|
60
60
|
// Submit button hiding
|
61
61
|
// ====================
|
62
62
|
|
63
|
-
register('onReady', () => {
|
63
|
+
Muzzle.register('onReady', () => {
|
64
64
|
if (Muzzle.simple) {
|
65
65
|
$('.mu-kids-exercise-workspace').addClass('muzzle-simple');
|
66
66
|
}
|
@@ -70,7 +70,9 @@ $(() => {
|
|
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
|
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,
|
@@ -93,7 +95,7 @@ class MuzzleCanvas {
|
|
93
95
|
*
|
94
96
|
* @type {number}
|
95
97
|
*/
|
96
|
-
this.strokeWidth =
|
98
|
+
this.strokeWidth = 3;
|
97
99
|
|
98
100
|
/**
|
99
101
|
* Piece size
|
@@ -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,12 +157,24 @@ 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
|
*/
|
149
164
|
this.simple = null;
|
150
165
|
|
166
|
+
this.spiky = false;
|
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
|
+
|
151
178
|
/**
|
152
179
|
* Callback to be executed when submitting puzzle.
|
153
180
|
*
|
@@ -166,32 +193,81 @@ class MuzzleCanvas {
|
|
166
193
|
* property with any code you need the be called here
|
167
194
|
*/
|
168
195
|
this.onValid = () => {};
|
196
|
+
|
197
|
+
/**
|
198
|
+
* @private
|
199
|
+
*/
|
200
|
+
this._ready = false;
|
201
|
+
}
|
202
|
+
|
203
|
+
get painter() {
|
204
|
+
return new MuzzlePainter();
|
169
205
|
}
|
170
206
|
|
171
207
|
/**
|
172
208
|
*/
|
173
209
|
get baseConfig() {
|
174
|
-
|
175
|
-
|
176
|
-
return {
|
210
|
+
return Object.assign({
|
211
|
+
preventOffstageDrag: true,
|
177
212
|
width: this.canvasWidth,
|
178
213
|
height: this.canvasHeight,
|
179
|
-
pieceSize:
|
180
|
-
proximity: Math.min(
|
181
|
-
borderFill: this.borderFill === null ? headbreaker.Vector.divide(pieceSize, 10) : this.borderFill,
|
214
|
+
pieceSize: this.adjustedPieceSize,
|
215
|
+
proximity: Math.min(this.adjustedPieceSize.x, this.adjustedPieceSize.y) / 5,
|
182
216
|
strokeWidth: this.strokeWidth,
|
183
|
-
lineSoftness: 0.18
|
184
|
-
|
217
|
+
lineSoftness: 0.18,
|
218
|
+
painter: this.painter
|
219
|
+
}, this.outlineConfig);
|
220
|
+
}
|
221
|
+
|
222
|
+
/**
|
223
|
+
*/
|
224
|
+
get outlineConfig() {
|
225
|
+
if (this.spiky) {
|
226
|
+
return {
|
227
|
+
borderFill: this.borderFill === null ? headbreaker.Vector.divide(this.adjustedPieceSize, 10) : this.borderFill,
|
228
|
+
}
|
229
|
+
} else {
|
230
|
+
return {
|
231
|
+
borderFill: 0,
|
232
|
+
outline: new headbreaker.outline.Rounded({
|
233
|
+
bezelize: true,
|
234
|
+
insertDepth: 3/5,
|
235
|
+
bezelDepth: 9/10,
|
236
|
+
referenceInsertAxis: this.referenceInsertAxis
|
237
|
+
}),
|
238
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* The piece size, adjusted to the aspect ratio
|
244
|
+
*
|
245
|
+
* @returns {Vector}
|
246
|
+
*/
|
247
|
+
get adjustedPieceSize() {
|
248
|
+
if (!this._adjustedPieceSize) {
|
249
|
+
const aspectRatio = this.effectiveAspectRatio;
|
250
|
+
this._adjustedPieceSize = headbreaker.vector(this.pieceSize * aspectRatio, this.pieceSize);
|
251
|
+
}
|
252
|
+
return this._adjustedPieceSize;
|
185
253
|
}
|
186
254
|
|
187
255
|
/**
|
188
256
|
* @type {Axis}
|
189
257
|
*/
|
190
258
|
get imageAdjustmentAxis() {
|
191
|
-
console.log(this.fitImagesVertically)
|
192
259
|
return this.fitImagesVertically ? headbreaker.Vertical : headbreaker.Horizontal;
|
193
260
|
}
|
194
261
|
|
262
|
+
/**
|
263
|
+
* The configured aspect ratio, or 1
|
264
|
+
*
|
265
|
+
* @type {number}
|
266
|
+
*/
|
267
|
+
get effectiveAspectRatio() {
|
268
|
+
return this.aspectRatio || 1;
|
269
|
+
}
|
270
|
+
|
195
271
|
/**
|
196
272
|
* The currently active canvas, or null if
|
197
273
|
* it has not yet initialized
|
@@ -205,7 +281,7 @@ class MuzzleCanvas {
|
|
205
281
|
/**
|
206
282
|
* Draws the - previusly built - current canvas.
|
207
283
|
*
|
208
|
-
* Prefer
|
284
|
+
* Prefer `this.currentCanvas.redraw()` when performing
|
209
285
|
* small updates to the pieces.
|
210
286
|
*/
|
211
287
|
draw() {
|
@@ -234,13 +310,9 @@ class MuzzleCanvas {
|
|
234
310
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
235
311
|
*/
|
236
312
|
async basic(x, y, imagePath) {
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
if (this.simple === null) {
|
242
|
-
this.simple = true;
|
243
|
-
}
|
313
|
+
this._config('aspectRatio', y / x);
|
314
|
+
this._config('simple', true);
|
315
|
+
this._config('shuffler', Muzzle.Shuffler.grid);
|
244
316
|
|
245
317
|
/**
|
246
318
|
* @todo take all container size
|
@@ -252,7 +324,6 @@ class MuzzleCanvas {
|
|
252
324
|
canvas.adjustImagesToPuzzle(this.imageAdjustmentAxis);
|
253
325
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y });
|
254
326
|
this._attachBasicValidator(canvas);
|
255
|
-
canvas.shuffleGrid(0.8);
|
256
327
|
this._configCanvas(canvas);
|
257
328
|
canvas.onValid(() => {
|
258
329
|
setTimeout(() => {
|
@@ -265,52 +336,94 @@ class MuzzleCanvas {
|
|
265
336
|
}
|
266
337
|
|
267
338
|
/**
|
268
|
-
*
|
269
|
-
*
|
270
|
-
*
|
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
|
+
*
|
271
350
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
272
351
|
*/
|
273
|
-
async
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
const canvas = this._createCanvas();
|
278
|
-
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y * count });
|
279
|
-
|
280
|
-
// todo validate
|
281
|
-
// todo set images
|
282
|
-
|
283
|
-
this._configCanvas(canvas);
|
284
|
-
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});
|
285
355
|
}
|
286
356
|
|
287
357
|
/**
|
288
|
-
*
|
289
|
-
* 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.
|
290
361
|
*
|
291
362
|
* @param {string[]} leftUrls
|
292
363
|
* @param {string[]} rightUrls must be of the same size of lefts
|
293
|
-
* @param {
|
294
|
-
* @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
|
295
368
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
296
369
|
*/
|
297
|
-
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
|
+
|
298
378
|
/** @private @type {(Promise<Template>)[]} */
|
299
379
|
const templatePromises = [];
|
300
|
-
|
301
|
-
|
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
|
+
});
|
302
400
|
|
303
401
|
const last = leftUrls.length - 1;
|
304
402
|
for (let i = 0; i <= last; i++) {
|
305
403
|
const leftId = `l${i}`;
|
306
404
|
const rightId = `r${i}`;
|
307
405
|
|
308
|
-
|
309
|
-
|
406
|
+
pushLeftTemplate(i + 1, leftUrls[i], {
|
407
|
+
id: leftId,
|
408
|
+
rightTargetId: rightId
|
409
|
+
});
|
410
|
+
pushRightTemplate(i + 1, rightUrls[i], {
|
411
|
+
id: rightId
|
412
|
+
});
|
310
413
|
}
|
311
414
|
|
312
|
-
leftOddUrls.forEach((it, i) =>
|
313
|
-
|
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
|
+
);
|
314
427
|
|
315
428
|
// + Math.max(leftOddUrls.length, rightOddUrls.length)
|
316
429
|
const templates = await Promise.all(templatePromises);
|
@@ -318,7 +431,6 @@ class MuzzleCanvas {
|
|
318
431
|
const canvas = this._createCanvas({ maxPiecesCount: {x: 2, y: leftUrls.length} });
|
319
432
|
canvas.adjustImagesToPiece(this.imageAdjustmentAxis);
|
320
433
|
templates.forEach(it => canvas.sketchPiece(it));
|
321
|
-
canvas.shuffleColumns(0.8);
|
322
434
|
this._attachMatchValidator(canvas);
|
323
435
|
this._configCanvas(canvas);
|
324
436
|
return canvas;
|
@@ -383,11 +495,11 @@ class MuzzleCanvas {
|
|
383
495
|
* @param {object} options
|
384
496
|
* @returns {Promise<object>}
|
385
497
|
*/
|
386
|
-
_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}) {
|
387
499
|
const structure = left ? 'T-N-' : `N-S-`;
|
388
|
-
|
389
500
|
return this._loadImage(imagePath).then((image) => {
|
390
501
|
return {
|
502
|
+
...(size ? {size} : {}),
|
391
503
|
structure,
|
392
504
|
metadata: { id, left, odd, rightTargetId, image, targetPosition }
|
393
505
|
}
|
@@ -400,6 +512,7 @@ class MuzzleCanvas {
|
|
400
512
|
*/
|
401
513
|
_configCanvas(canvas) {
|
402
514
|
this._canvas = canvas;
|
515
|
+
this._canvas.shuffleWith(0.8, this.shuffler);
|
403
516
|
this._canvas.onValid(() => {
|
404
517
|
setTimeout(() => this.onValid(), 0);
|
405
518
|
});
|
@@ -412,14 +525,23 @@ class MuzzleCanvas {
|
|
412
525
|
|
413
526
|
['resize', 'load'].forEach((event) => {
|
414
527
|
window.addEventListener(event, () => {
|
528
|
+
console.debug("Scaler event fired:", event);
|
415
529
|
var container = document.getElementById(this.canvasId);
|
416
530
|
this.scale(container.offsetWidth, container.scrollHeight);
|
417
531
|
});
|
418
532
|
});
|
419
533
|
}
|
420
534
|
|
535
|
+
/**
|
536
|
+
* Scales the canvas to the given width and height
|
537
|
+
*
|
538
|
+
* @param {number} width
|
539
|
+
* @param {number} height
|
540
|
+
*/
|
421
541
|
scale(width, height) {
|
422
542
|
if (this.fixedDimensions || !this.canvas) return;
|
543
|
+
|
544
|
+
console.debug("Scaling:", {width, height})
|
423
545
|
const factor = this.optimalScaleFactor(width, height);
|
424
546
|
this.canvas.resize(width, height);
|
425
547
|
this.canvas.scale(factor);
|
@@ -427,6 +549,9 @@ class MuzzleCanvas {
|
|
427
549
|
this.focus();
|
428
550
|
}
|
429
551
|
|
552
|
+
/**
|
553
|
+
* Focuses the stage around the canvas center
|
554
|
+
*/
|
430
555
|
focus() {
|
431
556
|
const stage = this.canvas['__konvaLayer__'].getStage();
|
432
557
|
|
@@ -440,7 +565,7 @@ class MuzzleCanvas {
|
|
440
565
|
const maxX = Math.max(...xs);
|
441
566
|
const maxY = Math.max(...ys);
|
442
567
|
|
443
|
-
return headbreaker.
|
568
|
+
return headbreaker.vector(maxX - minX, maxY - minY);
|
444
569
|
})();
|
445
570
|
const diff = headbreaker.Vector.minus(area, realDiameter);
|
446
571
|
const semi = headbreaker.Vector.divide(diff, -2);
|
@@ -449,11 +574,19 @@ class MuzzleCanvas {
|
|
449
574
|
stage.draw();
|
450
575
|
}
|
451
576
|
|
577
|
+
/**
|
578
|
+
* @private
|
579
|
+
*/
|
452
580
|
get coordinates() {
|
453
581
|
const points = this.canvas.puzzle.points;
|
454
582
|
return [points.map(([x, _y]) => x), points.map(([_x, y]) => y)];
|
455
583
|
}
|
456
584
|
|
585
|
+
/**
|
586
|
+
* @private
|
587
|
+
* @param {number} width
|
588
|
+
* @param {number} height
|
589
|
+
*/
|
457
590
|
optimalScaleFactor(width, height) {
|
458
591
|
const factors = headbreaker.Vector.divide(headbreaker.vector(width, height), this.canvas.puzzleDiameter);
|
459
592
|
return Math.min(factors.x, factors.y) / 1.75;
|
@@ -467,9 +600,14 @@ class MuzzleCanvas {
|
|
467
600
|
this.loadPreviousSolution();
|
468
601
|
this.resetCoordinates();
|
469
602
|
this.draw();
|
603
|
+
this._ready = true;
|
470
604
|
this.onReady();
|
471
605
|
}
|
472
606
|
|
607
|
+
isReady() {
|
608
|
+
return this._ready;
|
609
|
+
}
|
610
|
+
|
473
611
|
// ===========
|
474
612
|
// Persistence
|
475
613
|
// ===========
|
@@ -508,6 +646,10 @@ class MuzzleCanvas {
|
|
508
646
|
}
|
509
647
|
}
|
510
648
|
|
649
|
+
/**
|
650
|
+
* Translates the pieces so that
|
651
|
+
* they start at canvas' coordinates origin
|
652
|
+
*/
|
511
653
|
resetCoordinates() {
|
512
654
|
const [xs, ys] = this.coordinates;
|
513
655
|
const minX = Math.min(...xs);
|
@@ -554,12 +696,58 @@ class MuzzleCanvas {
|
|
554
696
|
};
|
555
697
|
}
|
556
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
|
+
}
|
557
743
|
}
|
558
744
|
|
559
745
|
const Muzzle = new class extends MuzzleCanvas {
|
560
746
|
constructor() {
|
561
747
|
super();
|
562
748
|
this.aux = {};
|
749
|
+
|
750
|
+
this.Shuffler = headbreaker.Shuffler;
|
563
751
|
}
|
564
752
|
|
565
753
|
/**
|