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 && alt} 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; +} +