From 1cd46a5a2fc92be4d1623aeb51a4f50c36533c08 Mon Sep 17 00:00:00 2001
From: JIN-YONG LEE <74762475+jinlee0@users.noreply.github.com>
Date: Thu, 30 Nov 2023 17:05:42 +0900
Subject: [PATCH] =?UTF-8?q?feat:=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=90?=
=?UTF-8?q?=EB=94=94=ED=84=B0=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20?=
=?UTF-8?q?=ED=9A=A8=EA=B3=BC=20API=20=EC=A0=81=EC=9A=A9=20(#324)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 34 +++++++--
package.json | 4 +-
.../preview-effects/PreviewEffectApi.ts | 7 ++
src/component/order/editor/Preview.js | 75 +++++++++----------
.../image-editor/preview-effects/client.ts | 7 ++
5 files changed, 81 insertions(+), 46 deletions(-)
create mode 100644 src/axios/image-editor/preview-effects/PreviewEffectApi.ts
create mode 100644 src/types/image-editor/preview-effects/client.ts
diff --git a/package-lock.json b/package-lock.json
index a62f330..92c50e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,9 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
- "prettier": "^2.8.7"
+ "@types/styled-components": "^5.1.29",
+ "prettier": "^2.8.7",
+ "typescript": "^5.1.6"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -4557,6 +4559,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4785,6 +4797,17 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw=="
},
+ "node_modules/@types/styled-components": {
+ "version": "5.1.32",
+ "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.32.tgz",
+ "integrity": "sha512-DqVpl8R0vbhVSop4120UHtGrFmHuPeoDwF4hDT0kPJTY8ty0SI38RV3VhCMsWigMUXG+kCXu7vMRqMFNy6eQgA==",
+ "dev": true,
+ "dependencies": {
+ "@types/hoist-non-react-statics": "*",
+ "@types/react": "*",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/@types/testing-library__jest-dom": {
"version": "5.14.5",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz",
@@ -18095,16 +18118,15 @@
}
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
- "peer": true,
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
+ "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/unbox-primitive": {
diff --git a/package.json b/package.json
index c069b2d..7a9c6b4 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,9 @@
},
"description": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).",
"main": "index.js",
- "devDependencies": {
+ "devDependencies": {
+ "@types/styled-components": "^5.1.29",
+ "typescript": "^5.1.6",
"prettier": "^2.8.7"
},
"keywords": [],
diff --git a/src/axios/image-editor/preview-effects/PreviewEffectApi.ts b/src/axios/image-editor/preview-effects/PreviewEffectApi.ts
new file mode 100644
index 0000000..e8d1181
--- /dev/null
+++ b/src/axios/image-editor/preview-effects/PreviewEffectApi.ts
@@ -0,0 +1,7 @@
+import { PreviewEffect } from '../../../types/image-editor/preview-effects/client';
+import axios from '../../axios';
+
+export const getAllPreviewEffectImages = async () => {
+ const res = await axios.get('/product/image-editor/preview-effects');
+ return res.data as PreviewEffect[];
+};
diff --git a/src/component/order/editor/Preview.js b/src/component/order/editor/Preview.js
index d802365..ce5a6a9 100644
--- a/src/component/order/editor/Preview.js
+++ b/src/component/order/editor/Preview.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Modal, ModalBody, ModalHeader, ModalButton } from 'baseui/modal';
import { Block } from 'baseui/block';
import { Radio, RadioGroup, ALIGN } from 'baseui/radio';
@@ -6,22 +6,25 @@ import { LabelSmall, HeadingXSmall } from 'baseui/typography';
import { useEditor } from '@layerhub-io/react';
import { ModalFooter } from 'react-bootstrap';
import Frame from '../../../image/icon/frame.png';
-import Glow from '../../../image/icon/glow.png';
-import useAppContext from '../../../hooks/useAppContext';
import mergeImages from 'merge-images';
import { resizedataURL } from '../../../utils';
-import { ADDITIONAL_MATERIAL } from '../../../global/Constants';
+import { getAllPreviewEffectImages } from '../../../axios/image-editor/preview-effects/PreviewEffectApi.ts';
-const generateMergedImageURL = async ({ customImage, isGlow }) => {
+const generateMergedImageURL = async ({ customImage, isGlow, effect }) => {
const images = [{ src: Frame }, { src: await resizeImage(customImage), x: 412, y: 238 }];
- if (isGlow) {
- images.push({ src: await resizeImage(Glow), x: 412, y: 238 });
+ if (effect.src !== '') {
+ images.push({
+ src: await resizeImage(effect.src + '?timestamp=' + new Date().getTime()), // to avoid cors, add timestamp
+ x: 412,
+ y: 238,
+ });
}
return await mergeImages(images);
};
const resizeImage = async (binary) => {
const img = document.createElement('img');
+ img.crossOrigin = 'anonymous'; // to avoid cors
const canvas = document.createElement('canvas');
const resizedImageURL = await resizedataURL(img, canvas, binary, 1750, 1010).finally(
() => img.remove() && canvas.remove(),
@@ -30,13 +33,21 @@ const resizeImage = async (binary) => {
};
const Preview = ({ isOpen, setIsOpen }) => {
- const { frameOption, setFrameOption } = useAppContext();
const editor = useEditor();
const [loading, setLoading] = React.useState(true);
- const [additionalOption, setAdditionalOption] = React.useState(frameOption['기본소재 옵션']);
const [state, setState] = React.useState({
image: '',
});
+ const [previewEffects, updatePreviewEffects] = useState([]);
+ const [selectedEffect, updateSelectedEffect] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ const effects = await getAllPreviewEffectImages();
+ updatePreviewEffects(() => [...effects]);
+ updateSelectedEffect(effects[0]);
+ })();
+ }, []);
const rollbackPreview = React.useCallback(async () => {
if (!editor) return;
@@ -45,37 +56,26 @@ const Preview = ({ isOpen, setIsOpen }) => {
editor.frame.setBackgroundColor('#ffffff');
// rollback opacity of objects to previous state
- editor.objects
- .list()
- .map((obj) =>
- frameOption['기본소재 옵션']?.includes('실버')
- ? (obj.opacity /= 0.65)
- : (obj.opacity /= 0.9),
- );
+ editor.objects.list().map((obj) => (obj.opacity /= selectedEffect.opacity / 100));
setLoading(false);
- }, [editor, frameOption]);
+ }, [editor, selectedEffect]);
const makePreview = async () => {
if (!editor) return;
// set frame background by options
- frameOption['기본소재 옵션']?.includes('실버')
+
+ selectedEffect.name.includes('실버')
? editor.frame.setBackgroundColor('#9B9B9B')
: editor.frame.setBackgroundColor('#ffffff');
- // set opacity of objects
- editor.objects
- .list()
- .map((obj) =>
- frameOption['기본소재 옵션']?.includes('실버')
- ? (obj.opacity *= 0.65)
- : (obj.opacity *= 0.9),
- );
+ editor.objects.list().map((obj) => (obj *= selectedEffect.opacity / 100));
const template = editor.scene.exportToJSON();
const imageURL = await editor.renderer.render(template);
const image = await generateMergedImageURL({
customImage: imageURL,
- isGlow: frameOption['기본소재 옵션']?.includes('유광'),
+ isGlow: selectedEffect.name.includes('유광'),
+ effect: selectedEffect,
});
setState({ image });
@@ -84,7 +84,8 @@ const Preview = ({ isOpen, setIsOpen }) => {
React.useEffect(() => {
makePreview();
- }, [editor, frameOption]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [editor, selectedEffect]);
const handleSave = React.useCallback(async () => {
await rollbackPreview();
@@ -98,17 +99,13 @@ const Preview = ({ isOpen, setIsOpen }) => {
a.click();
a.remove();
setIsOpen(false);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [editor]);
const handleChangeRadio = async (e) => {
const currentTargetValue = e.currentTarget.value;
await rollbackPreview();
- setAdditionalOption(currentTargetValue);
-
- setFrameOption({
- ...frameOption,
- [`기본소재 옵션`]: currentTargetValue,
- });
+ updateSelectedEffect(previewEffects[+currentTargetValue]);
};
return (
@@ -162,13 +159,13 @@ const Preview = ({ isOpen, setIsOpen }) => {
옵션 적용은 주문페이지에서 해주세요
- {ADDITIONAL_MATERIAL.map((option, idx) => (
-
- {option}
+ {previewEffects.map((e, idx) => (
+
+ {e.name}
))}
@@ -182,7 +179,7 @@ const Preview = ({ isOpen, setIsOpen }) => {
padding: '2rem',
}}
>
- {!loading && }
+ {!loading && }
diff --git a/src/types/image-editor/preview-effects/client.ts b/src/types/image-editor/preview-effects/client.ts
new file mode 100644
index 0000000..46d4e07
--- /dev/null
+++ b/src/types/image-editor/preview-effects/client.ts
@@ -0,0 +1,7 @@
+export interface PreviewEffect {
+ id: string;
+ name: string;
+ src: string;
+ opacity?: number;
+}
+