strapi-plugin-responsive-image
Version:
Custom responsive image formats for strapi.
357 lines (328 loc) • 10.8 kB
JavaScript
import React, { useEffect, useReducer, useRef } from "react";
import { Helmet } from "react-helmet";
import { useIntl } from "react-intl";
import {
CheckPagePermissions,
LoadingIndicatorPage,
useFocusWhenNavigate,
useNotification,
useOverlayBlocker,
} from "@strapi/helper-plugin";
import Check from "@strapi/icons/Check";
import { Box } from "@strapi/design-system/Box";
import { Flex } from "@strapi/design-system/Flex";
import { ToggleInput } from "@strapi/design-system/ToggleInput";
import { Typography } from "@strapi/design-system/Typography";
import { Button } from "@strapi/design-system/Button";
import { Main } from "@strapi/design-system/Main";
import { Stack } from "@strapi/design-system/Stack";
import { NumberInput } from "@strapi/design-system/NumberInput";
import { Link } from "@strapi/design-system/Link";
import { Grid, GridItem } from "@strapi/design-system/Grid";
import { BaseHeaderLayout } from "@strapi/design-system";
import Trash from "@strapi/icons/Trash";
import Plus from "@strapi/icons/Plus";
import {
ContentLayout,
HeaderLayout,
Layout,
} from "@strapi/design-system/Layout";
import axios from "axios";
import isEqual from "lodash/isEqual";
import { axiosInstance, getRequestUrl, getTrad } from "../../utils";
import init from "./init";
import ImageFormat from "./ImageFormat";
import reducer, { initialState } from "./reducer";
import pluginPermissions from "../../permissions";
export const SettingsPage = () => {
const { formatMessage } = useIntl();
const { lockApp, unlockApp } = useOverlayBlocker();
const toggleNotification = useNotification();
useFocusWhenNavigate();
const [
{ initialData, isLoading, isSubmiting, modifiedData, responsiveDimensions },
dispatch,
] = useReducer(reducer, initialState, init);
const isMounted = useRef(true);
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getData = async () => {
try {
const {
data: { data },
} = await axiosInstance.get(getRequestUrl("settings"), {
cancelToken: source.token,
});
const {
data: { data: uploadSettings },
} = await axiosInstance.get("/upload/settings", {
cancelToken: source.token,
});
dispatch({
type: "GET_DATA_SUCCEEDED",
data: {
...data,
responsiveDimensions: uploadSettings.responsiveDimensions,
},
});
} catch (err) {
console.error(err);
}
};
if (isMounted.current) {
getData();
}
return () => {
source.cancel("Operation canceled by the user.");
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isSaveButtonDisabled = isEqual(initialData, modifiedData);
const handleSubmit = async (e) => {
e.preventDefault();
if (isSaveButtonDisabled) {
return;
}
lockApp();
dispatch({ type: "ON_SUBMIT" });
try {
await axiosInstance.put(getRequestUrl("settings"), modifiedData);
dispatch({
type: "SUBMIT_SUCCEEDED",
});
toggleNotification({
type: "success",
message: { id: "notification.form.success.fields" },
});
} catch (err) {
console.error(err);
try {
toggleNotification({
type: "warning",
message: err.response.data.error.message || err.message,
});
} catch (error) {}
dispatch({ type: "ON_SUBMIT_ERROR" });
}
unlockApp();
};
const handleChange = ({ target: { name, value } }) => {
dispatch({
type: "ON_CHANGE",
keys: name,
value,
});
};
const handleFormatsChange = ({ target: { name, value } }, index) => {
dispatch({
type: "ON_FORMATS_CHANGE",
keys: name,
value,
index,
});
};
const handleAddFormat = () => {
dispatch({
type: "ADD_FORMAT",
});
};
const handleDeleteFormat = (index) => {
dispatch({
type: "DELETE_FORMAT",
index,
});
};
if (!responsiveDimensions) {
return (
<ContentLayout>
<Box
background="neutral0"
padding={6}
shadow="filterShadow"
hasRadius
style={{ textAlign: "center", marginTop: 50, fontSize: "1.2em" }}
>
<Typography variant="beta">
{formatMessage(
{ id: getTrad("settings.section.toActivate.label") },
{
setting: formatMessage({
id: "upload.settings.form.responsiveDimensions.label",
}),
link: (str) => (
<Link to="/settings/media-library" key="settings-link">
{str}
</Link>
),
}
)}
</Typography>
</Box>
</ContentLayout>
);
}
return (
<Main tabIndex={-1}>
<Helmet
title={formatMessage({
id: getTrad("page.title"),
defaultMessage: "Settings - Responsive Image",
})}
/>
<form onSubmit={handleSubmit}>
<HeaderLayout
title={formatMessage({
id: getTrad("settings.header.label"),
defaultMessage: "Responsive image",
})}
primaryAction={
<Button
disabled={isSaveButtonDisabled}
data-testid="save-button"
loading={isSubmiting}
type="submit"
startIcon={<Check />}
size="L"
>
{formatMessage({
id: "app.components.Button.save",
defaultMessage: "Save",
})}
</Button>
}
subtitle={formatMessage({
id: getTrad("settings.sub-header.label"),
defaultMessage: "Configure the settings for the responsive image",
})}
/>
<ContentLayout>
{isLoading ? (
<LoadingIndicatorPage />
) : (
<Layout>
<Stack spacing={12}>
<Box
background="neutral0"
padding={6}
shadow="filterShadow"
hasRadius
>
<Stack spacing={4}>
<Flex>
<Typography variant="delta" as="h2">
{formatMessage({
id: getTrad("settings.section.global.label"),
})}
</Typography>
</Flex>
<Grid gap={6}>
<GridItem col={6} s={12}>
<NumberInput
label={formatMessage({
id: getTrad("settings.form.quality.label"),
})}
name="quality"
validations={{
min: 1,
max: 100,
}}
onValueChange={(value) =>
handleChange({
target: {
name: "quality",
value,
},
})
}
value={modifiedData.quality}
/>
</GridItem>
<GridItem col={6} s={12}>
<ToggleInput
checked={modifiedData.progressive}
label={formatMessage({
id: getTrad("settings.form.progressive.label"),
})}
name="progressive"
offLabel={formatMessage({
id: "app.components.ToggleCheckbox.off-label",
defaultMessage: "Off",
})}
onLabel={formatMessage({
id: "app.components.ToggleCheckbox.on-label",
defaultMessage: "On",
})}
onChange={(e) => {
handleChange({
target: {
name: "progressive",
value: e.target.checked,
},
});
}}
/>
</GridItem>
</Grid>
</Stack>
</Box>
<BaseHeaderLayout
primaryAction={
<Button startIcon={<Plus />} onClick={handleAddFormat}>
{formatMessage({
id: getTrad("settings.section.formats.add.label"),
})}
</Button>
}
title={formatMessage({
id: getTrad("settings.section.formats.label"),
})}
as="h2"
style={{
margin: "0 -56px 0 -56px",
}}
/>
{modifiedData.formats.map((input, index) => (
<Box
key={index}
background="neutral0"
padding={6}
shadow="filterShadow"
hasRadius
style={{ marginTop: 30 }}
>
<ImageFormat
className="row"
format={input}
handleFormatsChange={handleFormatsChange}
index={index}
/>
<Button
variant="danger"
startIcon={<Trash />}
onClick={() => handleDeleteFormat(index)}
style={{
marginTop: 25,
}}
>
{formatMessage({
id: getTrad("settings.section.formats.delete.label"),
})}
</Button>
</Box>
))}
</Stack>
</Layout>
)}
</ContentLayout>
</form>
</Main>
);
};
const ProtectedSettingsPage = () => (
<CheckPagePermissions permissions={pluginPermissions.settings}>
<SettingsPage />
</CheckPagePermissions>
);
export default ProtectedSettingsPage;