expo-dynamic-image-crop
Version:
Dynamic image cropping component for Expo/React Native with free-form and fixed aspect ratio support
100 lines (99 loc) • 4.32 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import * as ImageManipulator from "expo-image-manipulator";
import { useCallback, useEffect } from "react";
import { Modal, StatusBar, StyleSheet, View } from "react-native";
import { EditorContext } from "./context/editor";
import { ControlBar } from "./ControlBar";
import { EditingWindow } from "./EditingWindow";
import { Processing } from "./Processing";
import { useEditorStore } from "./store";
function ImageEditorCore(props) {
const { minimumCropDimensions = { width: 100, height: 100 }, fixedAspectRatio = 0.66666666666, dynamicCrop = false, onEditingCancel, onEditingComplete, imageUri = null, processingComponent, editorOptions, } = props;
const options = useEditorStore((s) => s.editorOptions);
const setOptions = useEditorStore((s) => s.setEditorOptions);
const imageData = useEditorStore((s) => s.imageData);
const setImageData = useEditorStore((s) => s.setImageData);
const setReady = useEditorStore((s) => s.setReady);
const setEditingMode = useEditorStore((s) => s.setEditingMode);
const setProcessing = useEditorStore((s) => s.setProcessing);
const isEdit = useEditorStore((s) => s.isEdit);
const resetEditorStore = useEditorStore((s) => s.resetEditorStore);
const initialize = useCallback(async () => {
setProcessing(true);
if (imageUri) {
const { width: pickerWidth, height: pickerHeight } = await ImageManipulator.manipulateAsync(imageUri, []);
setImageData({
uri: imageUri,
width: pickerWidth,
height: pickerHeight,
});
setReady(true);
setProcessing(false);
}
}, [imageUri, setImageData, setProcessing, setReady]);
const onBackPress = () => {
if (!isEdit) {
onEditingCancel();
}
else {
setProcessing(true);
initialize().then(() => {
setEditingMode("crop");
setProcessing(false);
});
}
};
const onSave = () => {
onEditingComplete(imageData);
resetEditorStore();
};
useEffect(() => {
initialize().then(setCustomStyles).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [imageUri]);
function setCustomStyles() {
if (editorOptions) {
const custom = Object.assign({}, options);
Object.entries(editorOptions).forEach(([key, value]) => {
if (key) {
// @ts-ignore
if (typeof custom[key] === "object" && custom[key] !== null) {
// @ts-ignore
custom[key] = { ...custom[key], ...value };
}
else {
// @ts-ignore
custom[key] = value;
}
}
});
setOptions(custom);
}
}
return (_jsxs(EditorContext.Provider, { value: {
minimumCropDimensions,
fixedAspectRatio,
dynamicCrop,
onBackPress,
onSave,
imageUri,
}, children: [_jsx(StatusBar, { hidden: true }), _jsx(ImageEditorView, { processingComponent: processingComponent })] }));
}
export function ImageEditorView({ processingComponent }) {
const ready = useEditorStore((s) => s.ready);
const processing = useEditorStore((s) => s.processing);
const { backgroundColor, controlBar } = useEditorStore((s) => s.editorOptions);
return (_jsxs(_Fragment, { children: [ready && (_jsxs(View, { style: [styles.container, { backgroundColor }], children: [controlBar?.position === "top" && _jsx(ControlBar, {}), _jsx(EditingWindow, {}), controlBar?.position === "bottom" && _jsx(ControlBar, {})] })), processing && _jsx(Processing, { customComponent: processingComponent })] }));
}
export function ImageEditor({ isVisible, ...props }) {
return (_jsx(Modal, { visible: isVisible, style: styles.modalContainer, children: _jsx(ImageEditorCore, { ...props }) }));
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
modalContainer: {
flex: 1,
zIndex: 1,
},
});