@sequent-org/moodboard 1.4.49 → 1.4.52
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.
- package/package.json +1 -1
- package/src/core/bootstrap/CoreInitializer.js +3 -0
- package/src/core/commands/ApplyCropCommand.js +90 -0
- package/src/core/commands/UpdateFramePropertiesCommand.js +9 -2
- package/src/core/flows/ObjectLifecycleFlow.js +29 -8
- package/src/objects/FrameObject.js +242 -21
- package/src/objects/ImageObject.js +23 -7
- package/src/services/CropController.js +242 -0
- package/src/services/FrameService.js +87 -9
- package/src/services/ImageAttachmentService.js +260 -0
- package/src/tools/manager/ToolManagerLifecycle.js +4 -0
- package/src/tools/object-tools/DrawingTool.js +1 -0
- package/src/tools/object-tools/placement/GhostController.js +2 -2
- package/src/tools/object-tools/placement/PlacementPayloadFactory.js +9 -1
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +13 -4
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +15 -10
- package/src/tools/object-tools/selection/TransformInteractionController.js +50 -0
- package/src/ui/CropOverlay.js +654 -0
- package/src/ui/FramePropertiesPanel.js +1184 -405
- package/src/ui/HtmlHandlesLayer.js +2 -0
- package/src/ui/ImagePropertiesPanel.js +85 -41
- package/src/ui/connectors/ConnectionAnchorsLayer.js +4 -0
- package/src/ui/handles/HandlesDomRenderer.js +9 -2
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/styles/panels.css +402 -27
- package/src/ui/styles/workspace.css +20 -0
- package/src/utils/applyCrop.js +92 -0
package/src/ui/styles/panels.css
CHANGED
|
@@ -206,49 +206,119 @@
|
|
|
206
206
|
height: 36px;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
/* Frame properties
|
|
209
|
+
/* Frame properties toolbar — mirror image toolbar look */
|
|
210
210
|
.frame-properties-panel {
|
|
211
211
|
position: absolute;
|
|
212
212
|
pointer-events: auto;
|
|
213
|
-
display:
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-direction: row;
|
|
214
215
|
align-items: center;
|
|
215
|
-
gap:
|
|
216
|
-
padding:
|
|
217
|
-
background
|
|
218
|
-
border: 1px solid #
|
|
219
|
-
border-radius:
|
|
220
|
-
box-shadow: 0
|
|
216
|
+
gap: 2px;
|
|
217
|
+
padding: 4px 8px;
|
|
218
|
+
background: #ffffff;
|
|
219
|
+
border: 1px solid #E5E7EB;
|
|
220
|
+
border-radius: 8px;
|
|
221
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
222
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
221
223
|
font-size: 13px;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
z-index: 3000;
|
|
225
|
+
user-select: none;
|
|
226
|
+
white-space: nowrap;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Ratio split-dropdown (двухстрочная кнопка «Ratio / значение») */
|
|
230
|
+
.fpp-ratio {
|
|
231
|
+
position: relative;
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.fpp-ratio-btn {
|
|
237
|
+
display: inline-flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 6px;
|
|
224
240
|
height: 36px;
|
|
241
|
+
padding: 0 8px;
|
|
242
|
+
border: none;
|
|
243
|
+
background: none;
|
|
244
|
+
border-radius: 6px;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
color: #374151;
|
|
247
|
+
font-family: inherit;
|
|
248
|
+
transition: background 0.15s;
|
|
225
249
|
}
|
|
226
250
|
|
|
227
|
-
.
|
|
228
|
-
|
|
229
|
-
font-size: 12px;
|
|
230
|
-
color: #666;
|
|
231
|
-
font-weight: 500;
|
|
251
|
+
.fpp-ratio-btn:hover {
|
|
252
|
+
background: #F3F4F6;
|
|
232
253
|
}
|
|
233
254
|
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
255
|
+
.fpp-ratio-text {
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
align-items: flex-start;
|
|
259
|
+
line-height: 1.15;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.fpp-ratio-caption {
|
|
263
|
+
font-size: 10px;
|
|
264
|
+
color: #9CA3AF;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.fpp-ratio-value {
|
|
239
268
|
font-size: 13px;
|
|
240
|
-
|
|
269
|
+
color: #374151;
|
|
270
|
+
font-weight: 500;
|
|
241
271
|
}
|
|
242
272
|
|
|
243
|
-
.
|
|
244
|
-
|
|
245
|
-
|
|
273
|
+
.fpp-ratio-btn svg {
|
|
274
|
+
color: #6B7280;
|
|
275
|
+
transition: transform 0.2s ease;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.fpp-ratio-btn.is-active svg {
|
|
279
|
+
transform: rotate(180deg);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.fpp-ratio-dropdown {
|
|
283
|
+
position: absolute;
|
|
284
|
+
top: calc(100% + 6px);
|
|
285
|
+
left: 0;
|
|
286
|
+
background: #ffffff;
|
|
287
|
+
border: 1px solid #E5E7EB;
|
|
288
|
+
border-radius: 8px;
|
|
289
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
290
|
+
padding: 6px;
|
|
291
|
+
display: none;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
min-width: 180px;
|
|
294
|
+
z-index: 1001;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.fpp-ratio-dropdown.is-open {
|
|
298
|
+
display: flex;
|
|
246
299
|
}
|
|
247
300
|
|
|
248
|
-
|
|
301
|
+
/* Кружок цвета фона */
|
|
302
|
+
.fpp-color-wrap {
|
|
303
|
+
display: inline-flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
justify-content: center;
|
|
249
306
|
width: 28px;
|
|
250
307
|
height: 28px;
|
|
251
|
-
border:
|
|
308
|
+
border-radius: 6px;
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
flex-shrink: 0;
|
|
311
|
+
transition: background 0.15s;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.fpp-color-wrap:hover {
|
|
315
|
+
background: #F3F4F6;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.fpp-color-button {
|
|
319
|
+
width: 20px;
|
|
320
|
+
height: 20px;
|
|
321
|
+
border: 1px solid #D1D5DB;
|
|
252
322
|
border-radius: 50%;
|
|
253
323
|
background-color: #ffffff;
|
|
254
324
|
cursor: pointer;
|
|
@@ -256,9 +326,274 @@
|
|
|
256
326
|
padding: 0;
|
|
257
327
|
display: block;
|
|
258
328
|
box-sizing: border-box;
|
|
329
|
+
position: relative;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* transparent indicator — diagonal slash via pseudo */
|
|
333
|
+
.fpp-color-button--none {
|
|
334
|
+
background-color: #ffffff !important;
|
|
335
|
+
border-color: #D1D5DB;
|
|
336
|
+
}
|
|
337
|
+
.fpp-color-button--none::after {
|
|
338
|
+
content: '';
|
|
339
|
+
position: absolute;
|
|
340
|
+
inset: 0;
|
|
341
|
+
background: linear-gradient(to top-right,
|
|
342
|
+
transparent calc(50% - 0.75px),
|
|
343
|
+
#EF4444 calc(50% - 0.75px),
|
|
344
|
+
#EF4444 calc(50% + 0.75px),
|
|
345
|
+
transparent calc(50% + 0.75px)
|
|
346
|
+
);
|
|
347
|
+
border-radius: 50%;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
351
|
+
Colour popup dropdown
|
|
352
|
+
═══════════════════════════════════════════════════════════════ */
|
|
353
|
+
|
|
354
|
+
.fpp-color-popup {
|
|
355
|
+
position: absolute;
|
|
356
|
+
background: #ffffff;
|
|
357
|
+
border: 1px solid #E5E7EB;
|
|
358
|
+
border-radius: 12px;
|
|
359
|
+
box-shadow: 0 10px 25px rgba(0,0,0,0.10), 0 4px 10px rgba(0,0,0,0.05);
|
|
360
|
+
padding: 12px;
|
|
361
|
+
display: none;
|
|
362
|
+
flex-direction: column;
|
|
363
|
+
gap: 14px;
|
|
364
|
+
min-width: 196px;
|
|
365
|
+
z-index: 1001;
|
|
366
|
+
box-sizing: border-box;
|
|
367
|
+
}
|
|
368
|
+
.fpp-color-popup.is-open { display: flex; }
|
|
369
|
+
|
|
370
|
+
.fpp-section { display: flex; flex-direction: column; gap: 8px; }
|
|
371
|
+
|
|
372
|
+
.fpp-section-label {
|
|
373
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
374
|
+
font-size: 11px;
|
|
375
|
+
font-weight: 500;
|
|
376
|
+
color: #9CA3AF;
|
|
377
|
+
letter-spacing: 0.02em;
|
|
378
|
+
user-select: none;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Background type row */
|
|
382
|
+
.fpp-bg-type-row { display: flex; gap: 4px; }
|
|
383
|
+
|
|
384
|
+
.fpp-bg-type-btn {
|
|
385
|
+
display: inline-flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: center;
|
|
388
|
+
width: 36px;
|
|
389
|
+
height: 32px;
|
|
390
|
+
border: 1.5px solid transparent;
|
|
391
|
+
border-radius: 6px;
|
|
392
|
+
background: #F9FAFB;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
color: #374151;
|
|
395
|
+
padding: 0;
|
|
396
|
+
transition: background 0.12s, border-color 0.12s;
|
|
397
|
+
}
|
|
398
|
+
.fpp-bg-type-btn:hover { background: #F3F4F6; border-color: #E5E7EB; }
|
|
399
|
+
.fpp-bg-type-btn.is-active { border-color: #374151; background: #F3F4F6; }
|
|
400
|
+
|
|
401
|
+
/* Color swatch grid */
|
|
402
|
+
.fpp-color-grid {
|
|
403
|
+
display: grid;
|
|
404
|
+
grid-template-columns: repeat(6, 28px);
|
|
405
|
+
gap: 6px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.fpp-color-swatch {
|
|
409
|
+
width: 28px;
|
|
410
|
+
height: 28px;
|
|
411
|
+
border-radius: 50%;
|
|
412
|
+
border: 1.5px solid rgba(0,0,0,0.12);
|
|
413
|
+
background-color: var(--swatch-color, #ffffff);
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
padding: 0;
|
|
416
|
+
margin: 0;
|
|
417
|
+
display: flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
justify-content: center;
|
|
420
|
+
transition: transform 0.10s, box-shadow 0.10s;
|
|
421
|
+
flex-shrink: 0;
|
|
422
|
+
}
|
|
423
|
+
.fpp-color-swatch:hover { transform: scale(1.12); box-shadow: 0 2px 8px rgba(0,0,0,0.18); }
|
|
424
|
+
.fpp-color-swatch.is-selected { box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #374151; }
|
|
425
|
+
|
|
426
|
+
.fpp-color-swatch--none {
|
|
427
|
+
background-color: #ffffff;
|
|
428
|
+
border-color: #D1D5DB;
|
|
429
|
+
}
|
|
430
|
+
.fpp-color-swatch--none svg { pointer-events: none; }
|
|
431
|
+
|
|
432
|
+
/* Disabled colour section when transparent is selected (removed) */
|
|
433
|
+
/* .fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
|
|
434
|
+
opacity: 0.32;
|
|
435
|
+
pointer-events: none;
|
|
436
|
+
cursor: default;
|
|
437
|
+
} */
|
|
438
|
+
|
|
439
|
+
/* Custom colours row */
|
|
440
|
+
.fpp-custom-row {
|
|
441
|
+
display: flex;
|
|
442
|
+
flex-wrap: wrap;
|
|
443
|
+
gap: 6px;
|
|
444
|
+
align-items: center;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.fpp-add-color-btn {
|
|
448
|
+
width: 28px;
|
|
449
|
+
height: 28px;
|
|
450
|
+
border-radius: 50%;
|
|
451
|
+
border: 1.5px dashed #D1D5DB;
|
|
452
|
+
background: transparent;
|
|
453
|
+
cursor: pointer;
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
justify-content: center;
|
|
457
|
+
color: #9CA3AF;
|
|
458
|
+
padding: 0;
|
|
459
|
+
flex-shrink: 0;
|
|
460
|
+
transition: border-color 0.12s, color 0.12s;
|
|
461
|
+
}
|
|
462
|
+
.fpp-add-color-btn:hover { border-color: #9CA3AF; color: #374151; }
|
|
463
|
+
|
|
464
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
465
|
+
HSV colour picker
|
|
466
|
+
═══════════════════════════════════════════════════════════════ */
|
|
467
|
+
|
|
468
|
+
.fpp-color-picker {
|
|
469
|
+
position: absolute;
|
|
470
|
+
background: #ffffff;
|
|
471
|
+
border: 1px solid #E5E7EB;
|
|
472
|
+
border-radius: 12px;
|
|
473
|
+
box-shadow: 0 10px 25px rgba(0,0,0,0.12);
|
|
474
|
+
padding: 12px;
|
|
475
|
+
display: none;
|
|
476
|
+
flex-direction: column;
|
|
477
|
+
gap: 10px;
|
|
478
|
+
z-index: 1002;
|
|
479
|
+
width: 236px;
|
|
480
|
+
box-sizing: border-box;
|
|
481
|
+
}
|
|
482
|
+
.fpp-color-picker.is-open { display: flex; }
|
|
483
|
+
|
|
484
|
+
.fpp-color-picker__canvas-wrap {
|
|
485
|
+
position: relative;
|
|
486
|
+
width: 212px;
|
|
487
|
+
height: 148px;
|
|
488
|
+
border-radius: 6px;
|
|
489
|
+
overflow: hidden;
|
|
490
|
+
cursor: crosshair;
|
|
491
|
+
flex-shrink: 0;
|
|
492
|
+
}
|
|
493
|
+
.fpp-color-picker__canvas {
|
|
494
|
+
display: block;
|
|
495
|
+
width: 212px;
|
|
496
|
+
height: 148px;
|
|
497
|
+
}
|
|
498
|
+
.fpp-color-picker__cursor {
|
|
499
|
+
position: absolute;
|
|
500
|
+
width: 14px;
|
|
501
|
+
height: 14px;
|
|
502
|
+
border-radius: 50%;
|
|
503
|
+
border: 2px solid #ffffff;
|
|
504
|
+
box-shadow: 0 0 0 1px rgba(0,0,0,0.30);
|
|
505
|
+
transform: translate(-50%, -50%);
|
|
506
|
+
pointer-events: none;
|
|
507
|
+
top: 0;
|
|
508
|
+
left: 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.fpp-color-picker__hue-wrap { width: 100%; }
|
|
512
|
+
|
|
513
|
+
.fpp-color-picker__hue-slider {
|
|
514
|
+
-webkit-appearance: none;
|
|
515
|
+
appearance: none;
|
|
516
|
+
width: 100%;
|
|
517
|
+
height: 10px;
|
|
518
|
+
border-radius: 5px;
|
|
519
|
+
background: linear-gradient(to right,
|
|
520
|
+
hsl(0,100%,50%), hsl(30,100%,50%), hsl(60,100%,50%), hsl(90,100%,50%),
|
|
521
|
+
hsl(120,100%,50%), hsl(150,100%,50%), hsl(180,100%,50%), hsl(210,100%,50%),
|
|
522
|
+
hsl(240,100%,50%), hsl(270,100%,50%), hsl(300,100%,50%), hsl(330,100%,50%),
|
|
523
|
+
hsl(360,100%,50%));
|
|
524
|
+
outline: none;
|
|
525
|
+
cursor: pointer;
|
|
526
|
+
border: none;
|
|
527
|
+
}
|
|
528
|
+
.fpp-color-picker__hue-slider::-webkit-slider-thumb {
|
|
529
|
+
-webkit-appearance: none;
|
|
530
|
+
width: 16px;
|
|
531
|
+
height: 16px;
|
|
532
|
+
border-radius: 50%;
|
|
533
|
+
background: #ffffff;
|
|
534
|
+
border: 2px solid rgba(0,0,0,0.28);
|
|
535
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.20);
|
|
536
|
+
cursor: pointer;
|
|
537
|
+
}
|
|
538
|
+
.fpp-color-picker__hue-slider::-moz-range-thumb {
|
|
539
|
+
width: 16px;
|
|
540
|
+
height: 16px;
|
|
541
|
+
border-radius: 50%;
|
|
542
|
+
background: #ffffff;
|
|
543
|
+
border: 2px solid rgba(0,0,0,0.28);
|
|
544
|
+
cursor: pointer;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.fpp-color-picker__bottom {
|
|
548
|
+
display: flex;
|
|
549
|
+
align-items: center;
|
|
550
|
+
gap: 8px;
|
|
551
|
+
}
|
|
552
|
+
.fpp-color-picker__eyedropper {
|
|
553
|
+
width: 30px;
|
|
554
|
+
height: 30px;
|
|
555
|
+
border: 1px solid #E5E7EB;
|
|
556
|
+
border-radius: 6px;
|
|
557
|
+
background: transparent;
|
|
558
|
+
display: flex;
|
|
559
|
+
align-items: center;
|
|
560
|
+
justify-content: center;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
color: #6B7280;
|
|
563
|
+
padding: 0;
|
|
564
|
+
flex-shrink: 0;
|
|
565
|
+
transition: background 0.12s, color 0.12s;
|
|
566
|
+
}
|
|
567
|
+
.fpp-color-picker__eyedropper:hover { background: #F3F4F6; color: #374151; }
|
|
568
|
+
|
|
569
|
+
.fpp-color-picker__hex-input {
|
|
570
|
+
flex: 1;
|
|
571
|
+
height: 30px;
|
|
572
|
+
border: 1px solid #E5E7EB;
|
|
573
|
+
border-radius: 6px;
|
|
574
|
+
padding: 0 8px;
|
|
575
|
+
font-size: 13px;
|
|
576
|
+
font-family: 'Roboto Mono', 'Courier New', monospace;
|
|
577
|
+
color: #374151;
|
|
578
|
+
outline: none;
|
|
579
|
+
background: #ffffff;
|
|
259
580
|
}
|
|
581
|
+
.fpp-color-picker__hex-input:focus { border-color: #9CA3AF; }
|
|
260
582
|
|
|
261
|
-
.
|
|
583
|
+
.fpp-color-picker__apply {
|
|
584
|
+
width: 100%;
|
|
585
|
+
height: 30px;
|
|
586
|
+
border: none;
|
|
587
|
+
border-radius: 6px;
|
|
588
|
+
background: #374151;
|
|
589
|
+
color: #ffffff;
|
|
590
|
+
font-size: 13px;
|
|
591
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
592
|
+
font-weight: 500;
|
|
593
|
+
cursor: pointer;
|
|
594
|
+
transition: background 0.12s;
|
|
595
|
+
}
|
|
596
|
+
.fpp-color-picker__apply:hover { background: #1F2937; }
|
|
262
597
|
|
|
263
598
|
.text-properties-panel .tpp-label {
|
|
264
599
|
font-family: 'Roboto', Arial, sans-serif;
|
|
@@ -991,3 +1326,43 @@
|
|
|
991
1326
|
color: #6B7280;
|
|
992
1327
|
}
|
|
993
1328
|
|
|
1329
|
+
/* Lock Mode Dropdown */
|
|
1330
|
+
.fpp-lock-wrap {
|
|
1331
|
+
position: relative;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.fpp-lock-dropdown {
|
|
1335
|
+
position: absolute;
|
|
1336
|
+
top: calc(100% + 6px);
|
|
1337
|
+
left: 50%;
|
|
1338
|
+
transform: translateX(-50%);
|
|
1339
|
+
background: #ffffff;
|
|
1340
|
+
border: 1px solid #E5E7EB;
|
|
1341
|
+
border-radius: 8px;
|
|
1342
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
1343
|
+
padding: 6px;
|
|
1344
|
+
display: none;
|
|
1345
|
+
flex-direction: column;
|
|
1346
|
+
min-width: 190px;
|
|
1347
|
+
z-index: 1001;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.fpp-lock-dropdown.is-open {
|
|
1351
|
+
display: flex;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
.fpp-lock-mode-check {
|
|
1355
|
+
width: 14px;
|
|
1356
|
+
height: 14px;
|
|
1357
|
+
display: inline-flex;
|
|
1358
|
+
align-items: center;
|
|
1359
|
+
justify-content: center;
|
|
1360
|
+
color: #374151;
|
|
1361
|
+
flex-shrink: 0;
|
|
1362
|
+
opacity: 0;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.ipp-dropdown-item.is-active .fpp-lock-mode-check {
|
|
1366
|
+
opacity: 1;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
@@ -1622,3 +1622,23 @@
|
|
|
1622
1622
|
}
|
|
1623
1623
|
}
|
|
1624
1624
|
|
|
1625
|
+
/* ── Crop Overlay ─────────────────────────────────────────────────────────── */
|
|
1626
|
+
|
|
1627
|
+
.mb-crop-overlay {
|
|
1628
|
+
position: absolute;
|
|
1629
|
+
inset: 0;
|
|
1630
|
+
z-index: 2000;
|
|
1631
|
+
user-select: none;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.mb-crop-img-area {
|
|
1635
|
+
position: absolute;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/* Стили ручек задаются инлайн в CropOverlay.js (уголки и засечки) */
|
|
1639
|
+
.mb-crop-handle {
|
|
1640
|
+
position: absolute;
|
|
1641
|
+
box-sizing: border-box;
|
|
1642
|
+
z-index: 1;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
import { applyRoundedMask } from './applyRoundedMask.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Применяет или снимает кроп на PIXI-спрайте изображения.
|
|
6
|
+
*
|
|
7
|
+
* @param {PIXI.Sprite} sprite
|
|
8
|
+
* @param {object|null} cropRect - { x, y, w, h } нормализованные 0..1 от оригинальной текстуры, или null
|
|
9
|
+
* @param {string|null} cropShape - 'circle' | 'rect' | null
|
|
10
|
+
* @param {{ width: number, height: number }} displaySize - размер отображения в мировых пикселях
|
|
11
|
+
* @param {{ x: number, y: number }} position - top-left позиция в мировых координатах
|
|
12
|
+
* @param {number} [borderRadius=0] - радиус скругления (переприменяем после снятия кропа)
|
|
13
|
+
*/
|
|
14
|
+
export function applyCropToSprite(sprite, cropRect, cropShape, displaySize, position, borderRadius = 0) {
|
|
15
|
+
if (!sprite || !sprite.texture?.baseTexture) return;
|
|
16
|
+
|
|
17
|
+
const baseTex = sprite.texture.baseTexture;
|
|
18
|
+
// Оригинальные размеры текстуры (до любого кропа)
|
|
19
|
+
const baseW = sprite._mb?.properties?.baseW || baseTex.realWidth || baseTex.width || 1;
|
|
20
|
+
const baseH = sprite._mb?.properties?.baseH || baseTex.realHeight || baseTex.height || 1;
|
|
21
|
+
const src = sprite._mb?.properties?.src;
|
|
22
|
+
|
|
23
|
+
// Снять circle-маску кропа
|
|
24
|
+
if (sprite._cropCircleMask) {
|
|
25
|
+
if (sprite.mask === sprite._cropCircleMask) sprite.mask = null;
|
|
26
|
+
try { sprite._cropCircleMask.destroy({ children: true }); } catch (_) {}
|
|
27
|
+
sprite._cropCircleMask = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { width: dW, height: dH } = displaySize;
|
|
31
|
+
|
|
32
|
+
if (cropRect) {
|
|
33
|
+
const { x: nx, y: ny, w: nw, h: nh } = cropRect;
|
|
34
|
+
const frameX = Math.max(0, Math.round(nx * baseW));
|
|
35
|
+
const frameY = Math.max(0, Math.round(ny * baseH));
|
|
36
|
+
const frameW = Math.max(1, Math.min(Math.round(nw * baseW), baseW - frameX));
|
|
37
|
+
const frameH = Math.max(1, Math.min(Math.round(nh * baseH), baseH - frameY));
|
|
38
|
+
|
|
39
|
+
// Новая текстура с кадрированием
|
|
40
|
+
const croppedTex = new PIXI.Texture(baseTex, new PIXI.Rectangle(frameX, frameY, frameW, frameH));
|
|
41
|
+
sprite.texture = croppedTex;
|
|
42
|
+
sprite.scale.set(dW / frameW, dH / frameH);
|
|
43
|
+
sprite.x = Math.round(position.x + dW / 2);
|
|
44
|
+
sprite.y = Math.round(position.y + dH / 2);
|
|
45
|
+
|
|
46
|
+
// Сбросить borderRadius-маску — circle или radius переустановит её
|
|
47
|
+
if (sprite._borderRadiusMask) {
|
|
48
|
+
if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
|
|
49
|
+
try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
|
|
50
|
+
sprite._borderRadiusMask = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (cropShape === 'circle') {
|
|
54
|
+
// Вписанная окружность в локальных координатах спрайта (local = texture frame px)
|
|
55
|
+
const r = Math.min(frameW, frameH) / 2;
|
|
56
|
+
const g = new PIXI.Graphics();
|
|
57
|
+
g.beginFill(0xffffff);
|
|
58
|
+
g.drawCircle(0, 0, r);
|
|
59
|
+
g.endFill();
|
|
60
|
+
sprite.addChild(g);
|
|
61
|
+
sprite._cropCircleMask = g;
|
|
62
|
+
sprite.mask = g;
|
|
63
|
+
} else if (borderRadius > 0) {
|
|
64
|
+
applyRoundedMask(sprite, borderRadius);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
} else {
|
|
68
|
+
// Восстановить оригинальную текстуру
|
|
69
|
+
if (src) {
|
|
70
|
+
try {
|
|
71
|
+
const origTex = PIXI.Texture.from(src);
|
|
72
|
+
if (origTex) sprite.texture = origTex;
|
|
73
|
+
} catch (_) {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const texW = sprite.texture.width || 1;
|
|
77
|
+
const texH = sprite.texture.height || 1;
|
|
78
|
+
sprite.scale.set(dW / texW, dH / texH);
|
|
79
|
+
sprite.x = Math.round(position.x + dW / 2);
|
|
80
|
+
sprite.y = Math.round(position.y + dH / 2);
|
|
81
|
+
|
|
82
|
+
// Сбросить borderRadius-маску и переприменить если нужно
|
|
83
|
+
if (sprite._borderRadiusMask) {
|
|
84
|
+
if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
|
|
85
|
+
try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
|
|
86
|
+
sprite._borderRadiusMask = null;
|
|
87
|
+
}
|
|
88
|
+
if (borderRadius > 0) {
|
|
89
|
+
applyRoundedMask(sprite, borderRadius);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|