Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Avatar Picker] Browser window resize should not make image go out of bounds #15898

Merged
merged 4 commits into from
Mar 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/components/AvatarCropModal/AvatarCropModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ const AvatarCropModal = (props) => {
const translateSlider = useSharedValue(0);
const isPressableEnabled = useSharedValue(true);

// The previous offset values are maintained to recalculate the offset value in proportion
// to the container size, especially when the window size is first decreased and then increased
const prevMaxOffsetX = useSharedValue(0);
const prevMaxOffsetY = useSharedValue(0);

const [imageContainerSize, setImageContainerSize] = useState(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
const [sliderContainerSize, setSliderContainerSize] = useState(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
const [isImageContainerInitialized, setIsImageContainerInitialized] = useState(false);
Expand All @@ -82,7 +87,10 @@ const AvatarCropModal = (props) => {
const initializeImageContainer = useCallback((event) => {
setIsImageContainerInitialized(true);
const {height, width} = event.nativeEvent.layout;
setImageContainerSize(Math.floor(Math.min(height - styles.imageCropRotateButton.height, width)));

// Even if the browser height is reduced too much, the relative height should not be negative
const relativeHeight = Math.max(height - styles.imageCropRotateButton.height, CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
setImageContainerSize(Math.floor(Math.min(relativeHeight, width)));
}, [props.isSmallScreenWidth]);

// An onLayout callback, that initializes the slider container size, for proper render of a slider
Expand All @@ -99,6 +107,8 @@ const AvatarCropModal = (props) => {
scale.value = CONST.AVATAR_CROP_MODAL.MIN_SCALE;
rotation.value = 0;
translateSlider.value = 0;
prevMaxOffsetX.value = 0;
prevMaxOffsetY.value = 0;
setImageContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
setSliderContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
setIsImageContainerInitialized(false);
Expand Down Expand Up @@ -166,6 +176,8 @@ const AvatarCropModal = (props) => {
const maxOffsetY = (height - imageContainerSize) / 2;
translateX.value = clamp(offsetX, [maxOffsetX * -1, maxOffsetX]);
translateY.value = clamp(offsetY, [maxOffsetY * -1, maxOffsetY]);
prevMaxOffsetX.value = maxOffsetX;
prevMaxOffsetY.value = maxOffsetY;
}, [imageContainerSize, scale, clamp]);

/**
Expand Down Expand Up @@ -199,6 +211,38 @@ const AvatarCropModal = (props) => {
},
}, [imageContainerSize, updateImageOffset, translateX, translateY]);

// This effect is needed to recalculate the maximum offset values
// when the browser window is resized.
useEffect(() => {
// If no panning has happened and the value is 0, do an early return.
if (!prevMaxOffsetX.value && !prevMaxOffsetY.value) {
return;
}
const {height, width} = getDisplayedImageSize();
const maxOffsetX = (width - imageContainerSize) / 2;
const maxOffsetY = (height - imageContainerSize) / 2;

// Since interpolation is expensive, we only want to do it if
// image has been panned across X or Y axis by the user.
if (prevMaxOffsetX) {
translateX.value = interpolate(
translateX.value,
[prevMaxOffsetX.value * -1, prevMaxOffsetX.value],
[maxOffsetX * -1, maxOffsetX],
);
}

if (prevMaxOffsetY) {
translateY.value = interpolate(
translateY.value,
[prevMaxOffsetY.value * -1, prevMaxOffsetY.value],
[maxOffsetY * -1, maxOffsetY],
);
}
prevMaxOffsetX.value = maxOffsetX;
prevMaxOffsetY.value = maxOffsetY;
}, [imageContainerSize]);

/**
* Calculates new scale value and updates images offset to ensure
* that image stays in the center of the container after changing scale.
Expand Down