Skip to content

Commit 41fcc2f

Browse files
chore: review
1 parent de597af commit 41fcc2f

10 files changed

Lines changed: 99 additions & 92 deletions

File tree

apps/docs/.storybook/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { StorybookConfig } from '@storybook/react-vite';
2-
import { dirname, join, resolve } from 'path';
2+
import { dirname, resolve } from 'path';
33
import { fileURLToPath } from 'url';
44

55
const config: StorybookConfig = {

packages/react/src/components/Flex/Flex.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ const Flex = forwardRef<HTMLElement, FlexProps>(
3939
className,
4040
);
4141

42-
// Cast needed for polymorphic component pattern - props are typed via FlexProps
43-
const Element = Component as React.FC<Record<string, unknown>>;
42+
// Polymorphic component: ElementType resolves props to `never` in JSX.
43+
// External API is typed via FlexProps, so the internal cast is safe.
44+
const Element = Component as React.ElementType;
4445

4546
return (
46-
<Element ref={ref} className={classes} {...restProps}>
47+
<Element
48+
ref={ref as React.Ref<unknown>}
49+
className={classes}
50+
{...restProps}
51+
>
4752
{children}
4853
</Element>
4954
);

packages/react/src/components/Grid/Grid.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ export const Column = ({
6767
as,
6868
...restProps
6969
}: GridColProps) => {
70-
// Cast needed for polymorphic component pattern - props are typed via GridColProps
71-
const Component = (as || 'div') as React.FC<Record<string, unknown>>;
70+
// Polymorphic component: ElementType resolves props to `never` in JSX.
71+
// External API is typed via GridColProps, so the internal cast is safe.
72+
const Component = (as || 'div') as React.ElementType;
7273
const classes = clsx(
7374
{
7475
[`g-col-${sm}`]: sm,

packages/react/src/modules/multimedia/ImageEditor/components/ImageEditor.tsx

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,37 +55,12 @@ const ImageEditor = ({
5555
>(undefined);
5656
// Whether we are saving or not
5757
const [isSaving, setSaving] = useState(false);
58-
// Store the alt text modofied by the input text
58+
// Store the alt text modified by the input text
5959
const [altText, setAltText] = useState(altTextParam ?? '');
60-
// Store the legend text modofied by the input text
60+
// Store the legend text modified by the input text
6161
const [legend, setLegend] = useState(legendParam ?? '');
6262
// Whether the image has been edited or the text has been changed
6363
const [dirty, setDirty] = useState<boolean>(false);
64-
// PIXI app ref for cleanup
65-
const appRef = useRef<PIXI.Application | null>(null);
66-
67-
// Callback ref: initializes PIXI when the container is mounted in the DOM
68-
const containerRef = useCallback((node: HTMLDivElement | null) => {
69-
if (node && !appRef.current) {
70-
const app = new PIXI.Application();
71-
appRef.current = app;
72-
app
73-
.init({
74-
backgroundAlpha: 0,
75-
resolution: 1,
76-
preserveDrawingBuffer: true,
77-
})
78-
.then(() => {
79-
node.appendChild(app.canvas);
80-
setApplication(app);
81-
});
82-
}
83-
if (!node && appRef.current) {
84-
appRef.current.destroy(true);
85-
appRef.current = null;
86-
}
87-
}, []);
88-
8964
// Load Image Editor action
9065
const {
9166
toBlob,
@@ -103,6 +78,42 @@ const ImageEditor = ({
10378
} = useImageEditor({
10479
imageSrc,
10580
});
81+
82+
// PIXI app ref for cleanup
83+
const appRef = useRef<PIXI.Application | null>(null);
84+
85+
// Callback ref: initializes PIXI when the container is mounted in the DOM
86+
const containerRef = useCallback(
87+
(node: HTMLDivElement | null) => {
88+
if (node && !appRef.current) {
89+
const app = new PIXI.Application();
90+
appRef.current = app;
91+
app
92+
.init({
93+
backgroundAlpha: 0,
94+
resolution: 1,
95+
preserveDrawingBuffer: true,
96+
})
97+
.then(() => {
98+
// Guard against unmount during async init
99+
if (appRef.current !== app) return;
100+
node.appendChild(app.canvas);
101+
setApplication(app);
102+
})
103+
.catch(() => {
104+
// Init failed — clean up if still referenced
105+
if (appRef.current === app) {
106+
appRef.current = null;
107+
}
108+
});
109+
}
110+
if (!node && appRef.current) {
111+
appRef.current.destroy(true);
112+
appRef.current = null;
113+
}
114+
},
115+
[setApplication],
116+
);
106117
// A function to remove all opened controllers and backup changes if needed
107118
const stopAll = () => {
108119
stopBlur();
@@ -181,7 +192,7 @@ const ImageEditor = ({
181192
ref={containerRef}
182193
className="d-flex justify-content-center w-100"
183194
/>
184-
{!!loading && (
195+
{loading && (
185196
<div className="position-absolute top-0 start-0 bottom-0 end-0 m-10 d-flex align-items-center justify-content-center bg-black opacity-25">
186197
<LoadingScreen />
187198
</div>

packages/react/src/modules/multimedia/ImageEditor/effects/blur.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ function drawBlurListener(
6262
// Search for sprite
6363
const child = application.stage.getChildByName(spriteName);
6464
const scale = getApplicationScale(application);
65-
if (child === undefined || child === null) return;
65+
if (!child) return;
6666
// Create a sprite by copying texture and apply blurFilter
6767
const newSprite = new PIXI.Sprite((child as PIXI.Sprite).texture);
6868
// Apply blur filter to the new sprite, quality for big image (fix lag issue)
6969

7070
newSprite.filters = [
71-
new PIXI.BlurFilter(
72-
8, // PIXI Default value for strength of the blur effect
73-
(Math.min(Math.round(4 * scale)), 4), // Quality of the blur effect depending on the scale (4 is the PIXI default value)
74-
Math.min(scale, 1), // Resolution of the blur effect depending on the scale
75-
),
71+
new PIXI.BlurFilter({
72+
strength: 8,
73+
quality: Math.min(Math.round(4 * scale), 4),
74+
resolution: Math.min(scale, 1),
75+
}),
7676
];
7777
newSprite.width = (child as PIXI.Sprite).width;
7878
newSprite.height = (child as PIXI.Sprite).height;

packages/react/src/modules/multimedia/ImageEditor/effects/crop.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function drawBackground(
5959
const sprite = application.stage.getChildByName(
6060
spriteName,
6161
) as PIXI.Sprite | null;
62-
if (sprite === null || sprite === undefined) return;
62+
if (!sprite) return;
6363
const spriteBounds = sprite.getBounds();
6464
// Clone the stage
6565
const stageTexture = application.renderer.generateTexture(application.stage);
@@ -156,13 +156,13 @@ function refreshCorners(application: PIXI.Application): void {
156156
CROP_MASK_NAME,
157157
true,
158158
) as PIXI.Graphics | null;
159-
if (mask === undefined || mask === null) return;
159+
if (!mask) return;
160160
CORNERS.forEach((cornerType) => {
161161
const corner = application.stage.getChildByName(
162162
getCornerName(cornerType),
163163
true,
164164
);
165-
if (corner === undefined || corner === null) return;
165+
if (!corner) return;
166166
const position = computeCornerPosition(cornerType, {
167167
height: mask.height,
168168
width: mask.width,
@@ -206,14 +206,7 @@ function drawCorner(
206206
const sprite = application.stage.getChildByName(
207207
spriteName,
208208
) as PIXI.Sprite | null;
209-
if (
210-
sprite === null ||
211-
sprite === undefined ||
212-
background === null ||
213-
background === undefined ||
214-
mask === undefined ||
215-
mask === null
216-
) {
209+
if (!sprite || !background || !mask) {
217210
return;
218211
}
219212
// Compute corner position
@@ -234,27 +227,28 @@ function drawCorner(
234227
corner.interactive = true;
235228
// Add mouse event move => on corner move redraw mask
236229
let enable = false;
237-
application.stage.on('pointermove', (event: PIXI.FederatedMouseEvent) => {
238-
if (enable === false) return;
230+
const handlePointerMove = (event: PIXI.FederatedMouseEvent) => {
231+
if (!enable) return;
239232
const localPosition = background.toLocal(event.global);
240233
corner.position.x = localPosition.x;
241234
corner.position.y = localPosition.y;
242235
moveMask(application, cornerType, localPosition);
243-
});
236+
};
237+
application.stage.on('pointermove', handlePointerMove);
244238
// Add mouse down event => on pointer down start moving mask
245239
const handlePointerDown = () => {
246240
enable = true;
247241
};
248242
corner.on('pointerdown', handlePointerDown);
249-
// Add mouse up event => on pointer down stop moving mask
243+
// Add mouse up event => on pointer up stop moving mask
250244
const handlePointerUp = () => {
251245
enable = false;
252246
};
253247
globalThis.addEventListener('pointerup', handlePointerUp);
254-
// Remove listeners when corner destroyed
248+
// Remove all listeners when corner destroyed
255249
corner.once('destroyed', () => {
256-
// cancel listener
257250
corner.off('pointerdown');
251+
application.stage.off('pointermove', handlePointerMove);
258252
globalThis.removeEventListener('pointerup', handlePointerUp);
259253
});
260254
// Add corner
@@ -277,7 +271,7 @@ function moveMask(
277271
CROP_MASK_NAME,
278272
true,
279273
) as PIXI.Graphics | null;
280-
if (mask === undefined || mask === null) return;
274+
if (!mask) return;
281275
const right = mask.position.x + mask.width;
282276
const bottom = mask.position.y + mask.height;
283277
switch (cornerType) {
@@ -354,8 +348,8 @@ export function save(application: PIXI.Application): PIXI.Sprite | undefined {
354348
CROP_MASK_NAME,
355349
true,
356350
) as PIXI.Graphics | null;
357-
// Stop if mask does not exists
358-
if (mask === undefined || mask === null) return;
351+
// Stop if mask does not exist
352+
if (!mask) return;
359353
// Remove controls before cloning stage
360354
stop(application);
361355
// Clone stage

packages/react/src/modules/multimedia/ImageEditor/effects/misc.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function updateImageFromBlob(
6060
image.src = imageUrl;
6161
return new Promise<PIXI.Sprite | null>((resolve) => {
6262
image.onload = async () => {
63+
URL.revokeObjectURL(imageUrl);
6364
await updateImage(application, {
6465
spriteName,
6566
imgDatasource: image,
@@ -170,7 +171,7 @@ export function autoResize(
170171
sprite: PIXI.Sprite,
171172
): void {
172173
// Get parent html object
173-
const parent = application.view.parentNode as HTMLElement | undefined;
174+
const parent = application.canvas.parentNode as HTMLElement | undefined;
174175
const maxMobileWidth = window.innerWidth - MODAL_HORIZONTAL_PADDING;
175176
const parentWidth = Math.max(parent?.offsetWidth ?? 0, MIN_WIDTH);
176177
const newSize = constraintSize(
@@ -193,9 +194,9 @@ export function autoResize(
193194
const { height: newHeight, width: newWidth } = newSize;
194195

195196
// Anchor the sprite to the middle (for rotation)
196-
if (application.view?.style) {
197-
application.view.style.width = `${newWidth}px`;
198-
application.view.style.height = `${newHeight}px`;
197+
if (application.canvas?.style) {
198+
application.canvas.style.width = `${newWidth}px`;
199+
application.canvas.style.height = `${newHeight}px`;
199200
}
200201
}
201202

@@ -243,8 +244,8 @@ export function constraintSize(
243244
*/
244245
export function saveAsBlob(application: PIXI.Application): Promise<Blob> {
245246
return new Promise<Blob>((resolve, reject) => {
246-
if (application?.view?.toBlob) {
247-
application.view.toBlob(
247+
if (application?.canvas?.toBlob) {
248+
application.canvas.toBlob(
248249
(blob) => {
249250
blob ? resolve(blob) : reject('EXTRACT_FAILED');
250251
},
@@ -265,7 +266,7 @@ export function saveAsBlob(application: PIXI.Application): Promise<Blob> {
265266
export function saveAsDataURL(
266267
application: PIXI.Application,
267268
): string | undefined {
268-
return application.view.toDataURL?.();
269+
return application.canvas.toDataURL?.();
269270
}
270271

271272
/**
@@ -275,17 +276,18 @@ export function saveAsDataURL(
275276
* @returns The scale percentage.
276277
*/
277278
export function getApplicationScale(application: PIXI.Application) {
278-
if (application.view.getBoundingClientRect) {
279+
if (application.canvas.getBoundingClientRect) {
279280
return (
280-
application.view.getBoundingClientRect()!.width / application.view.width
281+
application.canvas.getBoundingClientRect()!.width /
282+
application.canvas.width
281283
);
282284
}
283285
return 1;
284286
}
285287

286288
export function toBlob(application: PIXI.Application) {
287289
return new Promise<Blob>((resolve, reject) => {
288-
application.view.toBlob?.(
290+
application.canvas.toBlob?.(
289291
(blob) => {
290292
if (blob) {
291293
resolve(blob);

packages/react/src/modules/multimedia/ImageEditor/effects/resize.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function resizeContainer(
9494
spriteName,
9595
true,
9696
) as PIXI.Sprite | null;
97-
if (sprite === undefined || sprite === null) return;
97+
if (!sprite) return;
9898
// Check whether sprite has been rotated
9999
const isRotated = sprite.rotation % Math.PI !== 0;
100100
// If sprite is rotated a multiple of 90°=>width and height are swapped
@@ -172,13 +172,7 @@ function drawCorner(
172172
CONTROL_NAME,
173173
true,
174174
) as PIXI.Graphics | null;
175-
if (
176-
sprite === null ||
177-
sprite === undefined ||
178-
container === null ||
179-
container === undefined
180-
)
181-
return;
175+
if (!sprite || !container) return;
182176
// Compute position of the container
183177
const position = computeCornerPosition(cornerType, container);
184178
// Draw and add the corner
@@ -192,32 +186,33 @@ function drawCorner(
192186
// Add listener to redraw container while moving corner
193187
corner.interactive = true;
194188
let enable = false;
195-
application.stage.on('pointermove', (event: PIXI.FederatedMouseEvent) => {
196-
if (enable === false) return;
189+
const handlePointerMove = (event: PIXI.FederatedMouseEvent) => {
190+
if (!enable) return;
197191
const localPosition = application.stage.toLocal(event.global);
198192
resizeContainer(application, {
199193
cornerType,
200194
position: localPosition,
201195
container,
202196
spriteName,
203197
});
204-
});
198+
};
199+
application.stage.on('pointermove', handlePointerMove);
200+
// Enable tracking on pointerdown
201+
const handlePointerDown = () => {
202+
enable = true;
203+
};
204+
corner.on('pointerdown', handlePointerDown);
205205
// stop tracking on pointer up
206206
const handlePointerUp = () => {
207207
enable = false;
208208
};
209209
globalThis.addEventListener('pointerup', handlePointerUp);
210-
// stop listening pointerup when corner is destroyed
210+
// Remove all listeners when corner is destroyed
211211
corner.once('destroyed', () => {
212-
// cancel listener
213212
corner.off('pointerdown');
213+
application.stage.off('pointermove', handlePointerMove);
214214
globalThis.removeEventListener('pointerup', handlePointerUp);
215215
});
216-
// Enable tracking on pointerdown
217-
const handlePointerDown = () => {
218-
enable = true;
219-
};
220-
corner.on('pointerdown', handlePointerDown);
221216
// add to sprite
222217
container.addChild(corner);
223218
}
@@ -238,7 +233,7 @@ function drawContainer(
238233
spriteName,
239234
true,
240235
) as PIXI.Sprite | null;
241-
if (sprite === null || sprite === undefined) return;
236+
if (!sprite) return;
242237
// Clone the stage
243238
const stageTexture = application.renderer.generateTexture(application.stage);
244239
const clonedStage = new PIXI.Sprite(stageTexture);

0 commit comments

Comments
 (0)