Web: Combining avatar Editor and IconEditor components into one component and removing avatarEditor and IconEditor components

This commit is contained in:
Akmal Isomadinov 2023-01-04 15:28:52 +05:00
parent 11e7a777f6
commit 49bfcf917a
14 changed files with 369 additions and 722 deletions

View File

@ -1,210 +0,0 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import throttle from "lodash/throttle";
import AvatarEditor from "react-avatar-editor";
import Slider from "@docspace/components/slider";
import IconButton from "@docspace/components/icon-button";
import { Base } from "@docspace/components/themes";
const StyledAvatarCropper = styled.div`
max-width: 216px;
.icon_cropper-crop_area {
width: 216px;
height: 216px;
margin-bottom: 4px;
position: relative;
.icon_cropper-grid {
pointer-events: none;
position: absolute;
width: 216px;
height: 216px;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
opacity: 0.2;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.gridColor};
}
}
}
}
.icon_cropper-delete_button {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 6px 0;
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor};
border-radius: 3px;
margin-bottom: 12px;
transition: all 0.2s ease;
&:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBackground};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBorderColor};
}
&-text {
user-select: none;
font-weight: 600;
line-height: 20px;
color: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.color};
}
svg {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor};
}
}
}
.icon_cropper-zoom-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 20px;
&-slider {
margin: 0;
}
&-button {
user-select: none;
}
}
`;
StyledAvatarCropper.defaultProps = { theme: Base };
const AvatarCropper = ({
t,
avatar,
onChangeAvatar,
uploadedFile,
setUploadedFile,
setPreviewAvatar,
}) => {
let editorRef = null;
const setEditorRef = (editor) => (editorRef = editor);
const handlePositionChange = (position) =>
onChangeAvatar({ ...avatar, x: position.x, y: position.y });
const handleSliderChange = (e, newZoom = null) =>
onChangeAvatar({ ...avatar, zoom: newZoom ? newZoom : +e.target.value });
const handleZoomInClick = () =>
handleSliderChange({}, avatar.zoom <= 4.5 ? avatar.zoom + 0.5 : 5);
const handleZoomOutClick = () =>
handleSliderChange({}, avatar.zoom >= 1.5 ? avatar.zoom - 0.5 : 1);
const handleDeleteImage = () => setUploadedFile(null);
const handleImageChange = throttle(() => {
try {
if (!editorRef) return;
const newPreveiwImage = editorRef.getImageScaledToCanvas()?.toDataURL();
setPreviewAvatar(newPreveiwImage);
} catch (e) {
console.error(e);
}
}, 300);
useEffect(() => {
handleImageChange();
return () => {
setPreviewAvatar("");
};
}, [avatar]);
return (
<StyledAvatarCropper className="icon_cropper">
<div className="icon_cropper-crop_area">
<ReactSVG
className="icon_cropper-grid"
src="images/icon-cropper-grid.svg"
/>
<AvatarEditor
ref={setEditorRef}
image={uploadedFile}
width={216}
height={216}
position={{ x: avatar.x, y: avatar.y }}
scale={avatar.zoom}
color={[6, 22, 38, 0.2]}
border={0}
rotate={0}
borderRadius={108}
onPositionChange={handlePositionChange}
onImageReady={handleImageChange}
disableHiDPIScaling
crossOrigin="anonymous"
/>
</div>
<div
className="icon_cropper-delete_button"
onClick={handleDeleteImage}
title={t("Common:Delete")}
>
<ReactSVG src={"images/trash.react.svg"} />
<div className="icon_cropper-delete_button-text">
{t("Common:Delete")}
</div>
</div>
<div className="icon_cropper-zoom-container">
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomOutClick}
iconName={"/static/images/zoom-minus.react.svg"}
isFill={true}
isClickable={false}
/>
<Slider
className="icon_cropper-zoom-container-slider"
max={5}
min={1}
onChange={handleSliderChange}
step={0.01}
value={avatar.zoom}
/>
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomInClick}
iconName={"/static/images/zoom-plus.react.svg"}
isFill={true}
isClickable={false}
/>
</div>
</StyledAvatarCropper>
);
};
export default AvatarCropper;

View File

@ -1,56 +0,0 @@
import React, { useState } from "react";
import styled from "styled-components";
import AvatarCropper from "./avatar-cropper";
import AvatarPreview from "./preview";
import Dropzone from "./dropzone";
const StyledWrapper = styled.div`
width: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.avatar-editor {
display: flex;
gap: 16px;
align-items: center;
}
`;
const AvatarEditor = ({
t,
profile,
avatar,
onChangeAvatar,
preview,
setPreview,
}) => {
const setUploadedFile = (file) =>
onChangeAvatar({ ...avatar, uploadedFile: file });
const isDefaultAvatar =
typeof avatar.uploadedFile === "string" &&
avatar.uploadedFile.includes("default_user_photo");
return (
<StyledWrapper>
{avatar.uploadedFile && !isDefaultAvatar && (
<div className="avatar-editor">
<AvatarCropper
t={t}
avatar={avatar}
onChangeAvatar={onChangeAvatar}
uploadedFile={avatar.uploadedFile}
setUploadedFile={setUploadedFile}
setPreviewAvatar={setPreview}
/>
<AvatarPreview avatar={preview} userName={profile.displayName} />
</div>
)}
<Dropzone t={t} setUploadedFile={setUploadedFile} />
</StyledWrapper>
);
};
export default AvatarEditor;

View File

@ -13,12 +13,32 @@ import {
deleteAvatar,
} from "@docspace/common/api/people";
import { dataUrlToFile } from "@docspace/common/utils/dataUrlToFile";
import AvatarEditor from "./editor";
import ImageEditor from "@docspace/components/ImageEditor";
import AvatarPreview from "@docspace/components/ImageEditor/AvatarPreview";
const StyledModalDialog = styled(ModalDialog)``;
const StyledModalDialog = styled(ModalDialog)`
.wrapper-image-editor {
width: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.avatar-editor {
display: flex;
gap: 16px;
align-items: center;
}
}
`;
const AvatarEditorDialog = (props) => {
const { t } = useTranslation(["Profile", "PeopleTranslations", "ProfileAction", "Common"]);
const { t } = useTranslation([
"Profile",
"PeopleTranslations",
"ProfileAction",
"Common",
"CreateEditRoomDialog",
]);
const {
visible,
onClose,
@ -89,13 +109,16 @@ const AvatarEditorDialog = (props) => {
</Text>
</ModalDialog.Header>
<ModalDialog.Body>
<AvatarEditor
<ImageEditor
t={t}
avatar={avatar}
onChangeAvatar={onChangeAvatar}
profile={profile}
preview={preview}
className="wrapper-image-editor"
classNameWrapperImageCropper="avatar-editor"
image={avatar}
setPreview={setPreview}
onChangeImage={onChangeAvatar}
Preview={
<AvatarPreview avatar={preview} userName={profile.displayName} />
}
/>
</ModalDialog.Body>
<ModalDialog.Footer>

View File

@ -1,118 +0,0 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { useDropzone } from "react-dropzone";
import resizeImage from "resize-image";
import { Base } from "@docspace/components/themes";
import { ColorTheme, ThemeType } from "@docspace/common/components/ColorTheme";
const StyledDropzone = styled.div`
cursor: pointer;
box-sizing: border-box;
width: 100%;
height: 150px;
border: 2px dashed
${(props) => props.theme.createEditRoomDialog.dropzone.borderColor};
border-radius: 6px;
.dropzone {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
user-select: none;
&-link {
display: flex;
flex-direction: row;
gap: 4px;
font-size: 13px;
line-height: 20px;
&-main {
font-weight: 600;
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 1px;
}
&-secondary {
font-weight: 400;
color: ${(props) =>
props.theme.createEditRoomDialog.dropzone.linkSecondaryColor};
}
}
&-exsts {
font-weight: 600;
font-size: 12px;
line-height: 16px;
color: ${(props) => props.theme.createEditRoomDialog.dropzone.exstsColor};
}
}
`;
StyledDropzone.defaultProps = { theme: Base };
const Dropzone = ({ t, setUploadedFile, isDisabled }) => {
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
maxFiles: 1,
noClick: isDisabled,
noKeyboard: isDisabled,
// maxSize: 1000000,
accept: ["image/png", "image/jpeg"],
});
useEffect(() => {
if (acceptedFiles.length) {
const fr = new FileReader();
fr.readAsDataURL(acceptedFiles[0]);
fr.onload = () => {
const img = new Image();
img.onload = () => {
const canvas = resizeImage.resize2Canvas(img, img.width, img.height);
const data = resizeImage.resize(
canvas,
img.width / 4,
img.height / 4,
resizeImage.JPEG
);
fetch(data)
.then((res) => res.blob())
.then((blob) => {
const file = new File([blob], "File name", {
type: "image/jpg",
});
setUploadedFile(file);
});
};
img.src = fr.result;
};
}
}, [acceptedFiles]);
return (
<StyledDropzone>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className="dropzone-link">
<ColorTheme className="dropzone-link-main" themeId={ThemeType.Link}>
{t("DropzoneTitleLink")}
</ColorTheme>
<span className="dropzone-link-secondary">
{t("DropzoneTitleSecondary")}
</span>
</div>
<div className="dropzone-exsts">{t("DropzoneTitleExsts")}</div>
</div>
</StyledDropzone>
);
};
export default Dropzone;

View File

@ -1,227 +0,0 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import throttle from "lodash/throttle";
import AvatarEditor from "react-avatar-editor";
import Slider from "@docspace/components/slider";
import IconButton from "@docspace/components/icon-button";
import { Base } from "@docspace/components/themes";
const StyledIconCropper = styled.div`
max-width: 216px;
.icon_cropper-crop_area {
width: 216px;
height: 216px;
margin-bottom: 4px;
position: relative;
.icon_cropper-grid {
pointer-events: none;
position: absolute;
width: 216px;
height: 216px;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
opacity: 0.2;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.gridColor};
}
}
}
}
.icon_cropper-delete_button {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 6px 0;
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor};
border-radius: 3px;
margin-bottom: 12px;
transition: all 0.2s ease;
&:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBackground};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBorderColor};
}
&-text {
user-select: none;
font-weight: 600;
line-height: 20px;
color: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.color};
}
svg {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor};
}
}
}
.icon_cropper-zoom-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 20px;
&-slider {
margin: 0;
}
&-button {
user-select: none;
}
}
`;
StyledIconCropper.defaultProps = { theme: Base };
const IconCropper = ({
t,
icon,
onChangeIcon,
uploadedFile,
setUploadedFile,
setPreviewIcon,
isDisabled,
}) => {
let editorRef = null;
const setEditorRef = (editor) => (editorRef = editor);
const handlePositionChange = (position) => {
if (isDisabled) return;
onChangeIcon({ ...icon, x: position.x, y: position.y });
};
const handleSliderChange = (e, newZoom = null) => {
if (isDisabled) return;
onChangeIcon({ ...icon, zoom: newZoom ? newZoom : +e.target.value });
};
const handleZoomInClick = () => {
if (isDisabled) return;
handleSliderChange({}, icon.zoom <= 4.5 ? icon.zoom + 0.5 : 5);
};
const handleZoomOutClick = () => {
if (isDisabled) return;
handleSliderChange({}, icon.zoom >= 1.5 ? icon.zoom - 0.5 : 1);
};
const handleDeleteImage = () => {
if (isDisabled) return;
setUploadedFile(null);
};
const handleImageChange = throttle(() => {
try {
if (!editorRef) return;
const newPreveiwImage = editorRef.getImageScaledToCanvas()?.toDataURL();
setPreviewIcon(newPreveiwImage);
} catch (e) {
console.error(e);
}
}, 300);
useEffect(() => {
handleImageChange();
return () => {
setPreviewIcon("");
};
}, [icon]);
return (
<StyledIconCropper className="icon_cropper">
<div className="icon_cropper-crop_area">
<ReactSVG
className="icon_cropper-grid"
src="images/icon-cropper-grid.svg"
/>
<AvatarEditor
ref={setEditorRef}
image={uploadedFile}
width={216}
height={216}
position={{ x: icon.x, y: icon.y }}
scale={icon.zoom}
color={[6, 22, 38, 0.2]}
border={0}
rotate={0}
borderRadius={108}
onPositionChange={handlePositionChange}
onImageReady={handleImageChange}
disableHiDPIScaling
crossOrigin="anonymous"
/>
</div>
<div
className="icon_cropper-delete_button"
onClick={handleDeleteImage}
title={t("Common:Delete")}
>
<ReactSVG src={"images/trash.react.svg"} />
<div className="icon_cropper-delete_button-text">
{t("Common:Delete")}
</div>
</div>
<div className="icon_cropper-zoom-container">
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomOutClick}
iconName={"/static/images/zoom-minus.react.svg"}
isFill={true}
isClickable={false}
isDisabled={isDisabled}
/>
<Slider
className="icon_cropper-zoom-container-slider"
max={5}
min={1}
onChange={handleSliderChange}
step={0.01}
value={icon.zoom}
isDisabled={isDisabled}
/>
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomInClick}
iconName={"/static/images/zoom-plus.react.svg"}
isFill={true}
isClickable={false}
isDisabled={isDisabled}
/>
</div>
</StyledIconCropper>
);
};
export default IconCropper;

View File

@ -1,67 +0,0 @@
import React, { useState } from "react";
import styled from "styled-components";
import Dropzone from "./Dropzone";
const StyledIconEditor = styled.div`
.icon-editor {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: start;
gap: 16px;
}
`;
import IconCropper from "./IconCropper";
import PreviewTile from "./PreviewTile";
const IconEditor = ({
t,
isEdit,
title,
tags,
defaultTagLabel,
icon,
onChangeIcon,
isDisabled,
}) => {
const [previewIcon, setPreviewIcon] = useState(null);
const setUploadedFile = (uploadedFile) =>
onChangeIcon({ ...icon, uploadedFile });
return (
<StyledIconEditor>
{icon.uploadedFile && (
<div className="icon-editor">
<IconCropper
t={t}
icon={icon}
onChangeIcon={onChangeIcon}
uploadedFile={icon.uploadedFile}
setUploadedFile={setUploadedFile}
setPreviewIcon={setPreviewIcon}
isDisabled={isDisabled}
/>
<PreviewTile
t={t}
title={title || t("Files:NewRoom")}
previewIcon={previewIcon}
tags={tags.map((tag) => tag.name)}
defaultTagLabel={defaultTagLabel}
isDisabled={isDisabled}
/>
</div>
)}
<Dropzone
t={t}
setUploadedFile={setUploadedFile}
isDisabled={isDisabled}
/>
</StyledIconEditor>
);
};
export default IconEditor;

View File

@ -6,7 +6,7 @@ import RoomTypeDropdown from "./RoomTypeDropdown";
import ThirdPartyStorage from "./ThirdPartyStorage";
import TagInput from "./TagInput";
import RoomType from "./RoomType";
import IconEditor from "./IconEditor";
import PermanentSettings from "./PermanentSettings";
import InputParam from "./Params/InputParam";
import IsPrivateParam from "./IsPrivateParam";
@ -15,11 +15,22 @@ import withLoader from "@docspace/client/src/HOCs/withLoader";
import Loaders from "@docspace/common/components/Loaders";
import { getRoomTypeDefaultTagTranslation } from "../data";
import ImageEditor from "@docspace/components/ImageEditor";
import PreviewTile from "@docspace/components/ImageEditor/PreviewTile";
const StyledSetRoomParams = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 22px;
.icon-editor {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: start;
gap: 16px;
}
`;
const SetRoomParams = ({
@ -35,6 +46,8 @@ const SetRoomParams = ({
isValidTitle,
setIsValidTitle,
}) => {
const [previewIcon, setPreviewIcon] = React.useState(null);
const onChangeName = (e) => {
setIsValidTitle(true);
setRoomParams({ ...roomParams, title: e.target.value });
@ -111,14 +124,26 @@ const SetRoomParams = ({
/>
)}
<IconEditor
<ImageEditor
t={t}
title={roomParams.title}
tags={roomParams.tags}
defaultTagLabel={getRoomTypeDefaultTagTranslation(roomParams.type, t)}
icon={roomParams.icon}
onChangeIcon={onChangeIcon}
isDisabled={isDisabled}
image={roomParams.icon}
setPreview={setPreviewIcon}
onChangeImage={onChangeIcon}
classNameWrapperImageCropper={"icon-editor"}
Preview={
<PreviewTile
t={t}
title={roomParams.title || t("Files:NewRoom")}
previewIcon={previewIcon}
tags={roomParams.tags.map((tag) => tag.name)}
isDisabled={isDisabled}
defaultTagLabel={getRoomTypeDefaultTagTranslation(
roomParams.type,
t
)}
/>
}
/>
</StyledSetRoomParams>
);

View File

@ -1,10 +1,10 @@
import React from "react";
import styled from "styled-components";
import Avatar from "@docspace/components/avatar";
import Text from "@docspace/components/text";
import Avatar from "../../avatar";
import Text from "../../text";
import { hugeMobile } from "@docspace/components/utils/device";
import { hugeMobile } from "../../utils/device";
const StyledWrapper = styled.div`
width: 100%;

View File

@ -1,7 +1,7 @@
import styled from "styled-components";
import { hugeMobile } from "@docspace/components/utils/device";
import { hugeMobile } from "../../utils/device";
import { Base } from "@docspace/components/themes";
import { Base } from "../../themes";
const StyledDropzone = styled.div`
cursor: pointer;
@ -16,10 +16,8 @@ const StyledDropzone = styled.div`
.dropzone_loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@ -43,8 +41,6 @@ const StyledDropzone = styled.div`
font-size: 13px;
line-height: 20px;
&-main {
color: ${(props) =>
props.theme.createEditRoomDialog.dropzone.linkMainColor};
font-weight: 600;
text-decoration: underline;
text-decoration-style: dashed;

View File

@ -1,24 +1,22 @@
import React, { useState, useRef, useMemo, useEffect } from "react";
import React, { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useDropzone } from "react-dropzone";
import StyledDropzone from "./StyledDropzone";
import Loader from "@docspace/components/loader";
import { toastr } from "@docspace/components";
import resizeImage from "resize-image";
import Loader from "../../loader";
import { toastr } from "../../";
import { ColorTheme, ThemeType } from "@docspace/common/components/ColorTheme";
import StyledDropzone from "./StyledDropzone";
const ONE_MEGABYTE = 1024 * 1024;
const COMPRESSION_RATIO = 2;
const NO_COMPRESSION_RATIO = 1;
const Dropzone = ({ setUploadedFile }) => {
const Dropzone = ({ t, setUploadedFile, isDisabled }) => {
const [loadingFile, setLoadingFile] = useState(false);
const { t } = useTranslation("CreateEditRoomDialog");
const mount = useRef(false);
const timer = useRef(null);
useEffect(() => {
@ -99,7 +97,7 @@ const Dropzone = ({ setUploadedFile }) => {
error instanceof Error &&
error.message === "recursion depth exceeded"
) {
toastr.error(t("SizeImageLarge"));
toastr.error(t("CreateEditRoomDialog:SizeImageLarge"));
}
console.error(error);
})
@ -113,6 +111,8 @@ const Dropzone = ({ setUploadedFile }) => {
const { getRootProps, getInputProps } = useDropzone({
maxFiles: 1,
noClick: isDisabled,
noKeyboard: isDisabled,
// maxSize: 1000000,
accept: ["image/png", "image/jpeg"],
onDrop,
@ -126,12 +126,16 @@ const Dropzone = ({ setUploadedFile }) => {
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className="dropzone-link">
<span className="dropzone-link-main">{t("DropzoneTitleLink")}</span>
<ColorTheme className="dropzone-link-main" themeId={ThemeType.Link}>
{t("CreateEditRoomDialog:DropzoneTitleLink")}
</ColorTheme>
<span className="dropzone-link-secondary">
{t("DropzoneTitleSecondary")}
{t("CreateEditRoomDialog:DropzoneTitleSecondary")}
</span>
</div>
<div className="dropzone-exsts">{t("DropzoneTitleExsts")}</div>
<div className="dropzone-exsts">
{t("CreateEditRoomDialog:DropzoneTitleExsts")}
</div>
</div>
</StyledDropzone>
);

View File

@ -0,0 +1,94 @@
import styled from "styled-components";
import { Base } from "../../themes";
const StyledImageCropper = styled.div`
max-width: 216px;
.icon_cropper-crop_area {
width: 216px;
height: 216px;
margin-bottom: 4px;
position: relative;
.icon_cropper-grid {
pointer-events: none;
position: absolute;
width: 216px;
height: 216px;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
opacity: 0.2;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.gridColor};
}
}
}
}
.icon_cropper-delete_button {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 6px 0;
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor};
border-radius: 3px;
margin-bottom: 12px;
transition: all 0.2s ease;
&:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBackground};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBorderColor};
}
&-text {
user-select: none;
font-weight: 600;
line-height: 20px;
color: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.color};
}
svg {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor};
}
}
}
.icon_cropper-zoom-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 20px;
&-slider {
margin: 0;
}
&-button {
user-select: none;
}
}
`;
StyledImageCropper.defaultProps = { theme: Base };
export default StyledImageCropper;

View File

@ -0,0 +1,136 @@
import React, { useEffect } from "react";
import { ReactSVG } from "react-svg";
import throttle from "lodash/throttle";
import AvatarEditor from "react-avatar-editor";
import Slider from "../../slider";
import IconButton from "../../icon-button";
import StyledImageCropper from "./StyledImageCropper";
const ImageCropper = ({
t,
image,
onChangeImage,
uploadedFile,
setUploadedFile,
setPreviewImage,
isDisabled,
}) => {
let editorRef = null;
const setEditorRef = (editor) => (editorRef = editor);
const handlePositionChange = (position) => {
if (isDisabled) return;
onChangeImage({ ...image, x: position.x, y: position.y });
};
const handleSliderChange = (e, newZoom = null) => {
if (isDisabled) return;
onChangeImage({ ...image, zoom: newZoom ? newZoom : +e.target.value });
};
const handleZoomInClick = () => {
if (isDisabled) return;
handleSliderChange({}, image.zoom <= 4.5 ? image.zoom + 0.5 : 5);
};
const handleZoomOutClick = () => {
if (isDisabled) return;
handleSliderChange({}, image.zoom >= 1.5 ? image.zoom - 0.5 : 1);
};
const handleDeleteImage = () => {
if (isDisabled) return;
setUploadedFile(null);
};
const handleImageChange = throttle(() => {
try {
if (!editorRef) return;
const newPreveiwImage = editorRef.getImageScaledToCanvas()?.toDataURL();
setPreviewImage(newPreveiwImage);
} catch (e) {
console.error(e);
}
}, 300);
useEffect(() => {
handleImageChange();
return () => {
setPreviewImage("");
};
}, [image]);
return (
<StyledImageCropper className="icon_cropper">
<div className="icon_cropper-crop_area">
<ReactSVG
className="icon_cropper-grid"
src="images/icon-cropper-grid.svg"
/>
<AvatarEditor
ref={setEditorRef}
image={uploadedFile}
width={216}
height={216}
position={{ x: image.x, y: image.y }}
scale={image.zoom}
color={[6, 22, 38, 0.2]}
border={0}
rotate={0}
borderRadius={108}
onPositionChange={handlePositionChange}
onImageReady={handleImageChange}
disableHiDPIScaling
crossOrigin="anonymous"
/>
</div>
<div
className="icon_cropper-delete_button"
onClick={handleDeleteImage}
title={t("Common:Delete")}
>
<ReactSVG src={"images/trash.react.svg"} />
<div className="icon_cropper-delete_button-text">
{t("Common:Delete")}
</div>
</div>
<div className="icon_cropper-zoom-container">
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomOutClick}
iconName={"/static/images/zoom-minus.react.svg"}
isFill={true}
isClickable={false}
isDisabled={isDisabled}
/>
<Slider
className="icon_cropper-zoom-container-slider"
max={5}
min={1}
onChange={handleSliderChange}
step={0.01}
value={image.zoom}
isDisabled={isDisabled}
/>
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomInClick}
iconName={"/static/images/zoom-plus.react.svg"}
isFill={true}
isClickable={false}
isDisabled={isDisabled}
/>
</div>
</StyledImageCropper>
);
};
export default ImageCropper;

View File

@ -1,11 +1,11 @@
import React from "react";
import styled from "styled-components";
import { smallTablet } from "@docspace/components/utils/device";
import Tags from "@docspace/common/components/Tags";
import Tag from "@docspace/components/tag";
import { Base } from "@docspace/components/themes";
import { smallTablet } from "../../utils/device";
import Tag from "../../tag";
import { Base } from "../../themes";
const StyledPreviewTile = styled.div`
background: ${(props) =>
@ -73,7 +73,7 @@ const StyledPreviewTile = styled.div`
`;
StyledPreviewTile.defaultProps = { theme: Base };
const PreviewTile = ({ t, title, previewIcon, tags, defaultTagLabel }) => {
const PreviewTile = ({ title, previewIcon, tags, defaultTagLabel }) => {
return (
<StyledPreviewTile>
<div className="tile-header">

View File

@ -0,0 +1,47 @@
import React from "react";
import Dropzone from "./Dropzone";
import ImageCropper from "./ImageCropper";
const ImageEditor = ({
t,
image,
onChangeImage,
Preview,
setPreview,
isDisabled,
classNameWrapperImageCropper,
className,
}) => {
const setUploadedFile = (uploadedFile) =>
onChangeImage({ ...image, uploadedFile });
const isDefaultAvatar =
typeof image.uploadedFile === "string" &&
image.uploadedFile.includes("default_user_photo");
return (
<div className={className}>
{image.uploadedFile && !isDefaultAvatar && (
<div className={classNameWrapperImageCropper}>
<ImageCropper
t={t}
image={image}
onChangeImage={onChangeImage}
uploadedFile={image.uploadedFile}
setUploadedFile={setUploadedFile}
setPreviewImage={setPreview}
isDisabled={isDisabled}
/>
{Preview}
</div>
)}
<Dropzone
t={t}
setUploadedFile={setUploadedFile}
isDisabled={isDisabled}
/>
</div>
);
};
export default ImageEditor;