Spaces:
Running
Running
Upload 172 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +2 -0
- Dockerfile +31 -0
- README.md +6 -4
- assets/images/avatar_bot.png +0 -0
- assets/images/avatars/default-avatar-2.svg +79 -0
- assets/images/avatars/default-avatar.svg +85 -0
- assets/images/banner_footer.svg +118 -0
- assets/images/clyde/affraid.svg +1 -0
- assets/images/clyde/wave.svg +39 -0
- assets/images/color-picker.svg +9 -0
- assets/images/editor/transparent_bg.svg +11 -0
- assets/images/header/ananas.svg +32 -0
- assets/images/header/background.svg +31 -0
- assets/images/header/icons-slider/crown.svg +3 -0
- assets/images/header/icons-slider/hammer.svg +8 -0
- assets/images/header/icons-slider/leaf.svg +9 -0
- assets/images/header/icons-slider/partner.svg +9 -0
- assets/images/header/icons-slider/shape.svg +3 -0
- assets/images/header/icons-slider/shield.svg +5 -0
- assets/images/header/icons-slider/star.svg +9 -0
- assets/images/header/pizza.svg +31 -0
- assets/images/header/search.svg +9 -0
- assets/images/header/stars.svg +12 -0
- assets/images/premium-modal/modal-illustration.webp +0 -0
- components/clyde/clyde.tsx +296 -0
- components/clyde/message.tsx +54 -0
- components/collapse/index.tsx +47 -0
- components/color-picker/index.tsx +104 -0
- components/editor-badge/badge-editor.constants.ts +39 -0
- components/editor-badge/comps/badge.tsx +163 -0
- components/editor-badge/comps/download_button.tsx +60 -0
- components/editor-badge/comps/header/comps/icons-slider.tsx +152 -0
- components/editor-badge/comps/header/form/advanced.tsx +100 -0
- components/editor-badge/comps/header/form/main.tsx +267 -0
- components/editor-badge/comps/header/index.tsx +84 -0
- components/editor-badge/comps/preview.tsx +39 -0
- components/editor-badge/comps/select-shapes.tsx +74 -0
- components/editor-badge/comps/shine_effect.tsx +38 -0
- components/editor-badge/comps/tabs.tsx +53 -0
- components/editor-badge/comps/user_card.tsx +114 -0
- components/editor-badge/index.tsx +94 -0
- components/editor-icons/comps/header/comps/icons-slider.tsx +108 -0
- components/editor-icons/comps/header/index.tsx +84 -0
- components/editor-icons/comps/icons/icon-selected.tsx +603 -0
- components/editor-icons/comps/icons/index.tsx +107 -0
- components/editor-icons/comps/list/index.tsx +151 -0
- components/editor-icons/comps/list/list-item.tsx +86 -0
- components/editor-icons/comps/shapes/index.tsx +98 -0
- components/editor-icons/comps/shapes/shape-selected.tsx +307 -0
- components/editor-icons/comps/tabs.tsx +65 -0
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
public/badge-editor.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
public/banner.png filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile
|
2 |
+
|
3 |
+
# Use an official Node.js runtime as the base image
|
4 |
+
FROM node:18
|
5 |
+
|
6 |
+
USER 1000
|
7 |
+
|
8 |
+
# Set the working directory in the container
|
9 |
+
WORKDIR /usr/src/app
|
10 |
+
|
11 |
+
# Copy package.json and package-lock.json to the container
|
12 |
+
COPY --chown=1000 package.json package-lock.json ./
|
13 |
+
|
14 |
+
# Install dependencies
|
15 |
+
RUN npm install
|
16 |
+
|
17 |
+
VOLUME /data
|
18 |
+
|
19 |
+
# Copy the rest of the application files to the container
|
20 |
+
COPY --chown=1000 . .
|
21 |
+
RUN chmod +x entrypoint.sh
|
22 |
+
|
23 |
+
# Build the Next.js application for production
|
24 |
+
# RUN npm run build
|
25 |
+
|
26 |
+
# Expose the application port (assuming your app runs on port 3000)
|
27 |
+
EXPOSE 3000
|
28 |
+
|
29 |
+
RUN npm run build
|
30 |
+
|
31 |
+
CMD ["npm", "run", "start"]
|
README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1 |
---
|
2 |
title: Discotools
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: docker
|
7 |
pinned: false
|
|
|
|
|
8 |
license: mit
|
9 |
---
|
10 |
|
11 |
-
|
|
|
1 |
---
|
2 |
title: Discotools
|
3 |
+
emoji: ✨
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: pink
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
+
app_port: 3000
|
9 |
+
short_description: Generate discord role icon and badge
|
10 |
license: mit
|
11 |
---
|
12 |
|
13 |
+
## This is an old project.
|
assets/images/avatar_bot.png
ADDED
![]() |
assets/images/avatars/default-avatar-2.svg
ADDED
|
assets/images/avatars/default-avatar.svg
ADDED
|
assets/images/banner_footer.svg
ADDED
|
assets/images/clyde/affraid.svg
ADDED
|
assets/images/clyde/wave.svg
ADDED
|
assets/images/color-picker.svg
ADDED
|
assets/images/editor/transparent_bg.svg
ADDED
|
assets/images/header/ananas.svg
ADDED
|
assets/images/header/background.svg
ADDED
|
assets/images/header/icons-slider/crown.svg
ADDED
|
assets/images/header/icons-slider/hammer.svg
ADDED
|
assets/images/header/icons-slider/leaf.svg
ADDED
|
assets/images/header/icons-slider/partner.svg
ADDED
|
assets/images/header/icons-slider/shape.svg
ADDED
|
assets/images/header/icons-slider/shield.svg
ADDED
|
assets/images/header/icons-slider/star.svg
ADDED
|
assets/images/header/pizza.svg
ADDED
|
assets/images/header/search.svg
ADDED
|
assets/images/header/stars.svg
ADDED
|
assets/images/premium-modal/modal-illustration.webp
ADDED
![]() |
components/clyde/clyde.tsx
ADDED
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
|
3 |
+
export const Clyde = () => {
|
4 |
+
const [isHovered, setIsHovered] = useState(false);
|
5 |
+
return (
|
6 |
+
<svg
|
7 |
+
width="105px"
|
8 |
+
height="105px"
|
9 |
+
viewBox="0 0 262 389"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
onMouseEnter={() => setIsHovered(true)}
|
13 |
+
onMouseLeave={() => setIsHovered(false)}
|
14 |
+
className="cursor-pointer"
|
15 |
+
>
|
16 |
+
<g
|
17 |
+
id="Page-1"
|
18 |
+
stroke="none"
|
19 |
+
strokeWidth="1"
|
20 |
+
fill="none"
|
21 |
+
fillRule="evenodd"
|
22 |
+
>
|
23 |
+
<g
|
24 |
+
id="Group"
|
25 |
+
transform="translate(0.444809, 0.613233)"
|
26 |
+
fillRule="nonzero"
|
27 |
+
>
|
28 |
+
<path
|
29 |
+
d="M255.4368,258.333967 C239.872,234.577167 204.2368,222.186767 204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 206.8992,308.919567 213.1968,314.858767 C219.4944,320.797967 248.6272,324.791567 248.6272,324.791567 C248.6272,324.791567 271.0016,282.090767 255.4368,258.333967 Z"
|
30 |
+
id="Path"
|
31 |
+
fill="#E8EDEC"
|
32 |
+
></path>
|
33 |
+
<path
|
34 |
+
d="M204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 186.4704,238.980367 189.6448,249.425167 C201.9328,244.765967 212.992,238.570767 222.976,230.788367 C212.48,225.053967 204.288,222.237967 204.2368,222.186767 L204.2368,222.186767 Z"
|
35 |
+
id="Path"
|
36 |
+
fill="#BEC9CE"
|
37 |
+
></path>
|
38 |
+
<path
|
39 |
+
d="M220.5696,288.746767 C216.4736,286.903567 213.4528,283.114767 213.4528,283.114767 L214.272,305.796367 C214.272,305.796367 227.6352,320.695567 226.2016,315.268367 C224.768,309.841167 227.7888,291.869967 227.7376,291.050767 C227.6352,290.231567 223.8976,290.231567 220.5696,288.746767 Z"
|
40 |
+
id="Path"
|
41 |
+
fill="#D0DBE2"
|
42 |
+
></path>
|
43 |
+
<path
|
44 |
+
d="M253.5424,321.565967 C252.8768,318.596367 248.2688,319.057167 237.4656,316.445967 C226.6624,313.885967 214.7328,308.253967 214.7328,306.973967 L213.4016,360.682767 C218.4704,366.314767 226.8672,370.717967 236.5952,369.181967 C247.2448,367.441167 253.9008,358.071567 253.5936,352.541967 C253.2864,347.012367 248.32,347.729167 248.32,347.729167 C248.9856,343.172367 245.9648,340.151567 246.2208,339.844367 C246.4256,339.485967 254.2592,324.535567 253.5424,321.565967 Z"
|
45 |
+
id="Path"
|
46 |
+
fill="#FFB818"
|
47 |
+
></path>
|
48 |
+
<path
|
49 |
+
d="M226.7136,313.117967 C220.0064,310.609167 214.784,307.844367 214.784,306.973967 L213.4016,360.682767 C222.6176,370.922767 238.3872,372.868367 248.2688,363.242767 C238.336,368.823567 227.9424,363.908367 227.6864,350.903567 C227.4816,341.482767 232.6528,331.857167 226.7136,313.117967 Z"
|
50 |
+
id="Path"
|
51 |
+
fill="#FF5D12"
|
52 |
+
></path>
|
53 |
+
<path
|
54 |
+
d="M204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 185.3952,235.447567 187.2896,241.591567 L213.9136,286.186767 C220.0576,252.497167 210.0736,229.866767 204.2368,222.186767 L204.2368,222.186767 Z"
|
55 |
+
id="Path"
|
56 |
+
fill="#FFB818"
|
57 |
+
></path>
|
58 |
+
<path
|
59 |
+
d="M191.744,248.605967 C198.8608,245.789967 205.6192,242.359567 211.968,238.468367 C209.6128,230.788367 206.592,225.309967 204.2368,222.237967 L184.2688,231.556367 C184.2688,231.556367 185.3952,235.498767 187.2896,241.642767 L191.5392,248.759567 C191.5904,248.657167 191.6928,248.605967 191.744,248.605967 L191.744,248.605967 Z"
|
60 |
+
id="Path"
|
61 |
+
fill="#FF5D12"
|
62 |
+
></path>
|
63 |
+
<path
|
64 |
+
d="M53.6064,213.124367 C27.8016,213.124367 6.8608,234.065167 6.8608,259.869967 L6.8608,324.893967 C6.8608,350.698767 27.8016,371.639567 53.6064,371.639567 L67.6352,371.639567 C93.44,371.639567 114.3808,350.698767 114.3808,324.893967 L114.3808,259.869967 C114.3808,234.065167 93.44,213.124367 67.6352,213.124367 L53.6064,213.124367 Z"
|
65 |
+
id="Path"
|
66 |
+
fill="#D0DBE2"
|
67 |
+
></path>
|
68 |
+
<path
|
69 |
+
d="M73.3184,213.482767 C47.5136,213.482767 26.5728,234.423567 26.5728,260.228367 L26.5728,325.201167 C26.5728,351.005967 47.5136,371.946767 73.3184,371.946767 L87.3472,371.946767 C113.152,371.946767 134.0928,351.005967 134.0928,325.201167 L134.0928,260.228367 C134.0928,234.423567 113.152,213.482767 87.3472,213.482767 L73.3184,213.482767 Z"
|
70 |
+
id="Path"
|
71 |
+
fill="#BEC9CE"
|
72 |
+
></path>
|
73 |
+
<ellipse
|
74 |
+
id="Oval"
|
75 |
+
fill="#FFB818"
|
76 |
+
cx="18.2272"
|
77 |
+
cy="278.865167"
|
78 |
+
rx="7.936"
|
79 |
+
ry="9.3184"
|
80 |
+
></ellipse>
|
81 |
+
<path
|
82 |
+
d="M10.2912,278.865167 C10.2912,283.985167 13.824,288.183567 18.2272,288.183567 C21.76,288.183567 24.7296,285.469967 25.7536,281.681167 C24.4224,282.858767 22.7328,283.575567 20.9408,283.575567 C16.5888,283.575567 13.0048,279.428367 13.0048,274.257167 C13.0048,273.284367 13.1584,272.311567 13.3632,271.441167 C11.52,273.181967 10.2912,275.895567 10.2912,278.865167 Z"
|
83 |
+
id="Path"
|
84 |
+
fill="#FF5D12"
|
85 |
+
></path>
|
86 |
+
<path
|
87 |
+
d="M16.5376,297.245967 C14.5408,297.245967 12.9024,298.884367 12.9024,300.881167 L12.9024,325.047567 C12.9024,327.044367 14.5408,328.682767 16.5376,328.682767 L19.9168,328.682767 C21.9136,328.682767 23.552,327.044367 23.552,325.047567 L23.552,300.881167 C23.552,298.884367 21.9136,297.245967 19.9168,297.245967 L16.5376,297.245967 Z"
|
88 |
+
id="Path"
|
89 |
+
fill="#FDCC00"
|
90 |
+
></path>
|
91 |
+
<path
|
92 |
+
d="M16.5376,297.245967 C14.5408,297.245967 12.9024,298.884367 12.9024,300.881167 L12.9024,325.047567 C12.9024,327.044367 14.5408,328.682767 16.5376,328.682767 L19.9168,328.682767 C21.9136,328.682767 23.552,327.044367 23.552,325.047567 L23.552,323.357967 L20.6848,323.357967 C18.688,323.357967 17.0496,321.719567 17.0496,319.722767 L17.0496,297.245967 L16.5376,297.245967 Z"
|
93 |
+
id="Path"
|
94 |
+
fill="#FCA800"
|
95 |
+
></path>
|
96 |
+
<path
|
97 |
+
d="M68.352,362.986767 C84.1216,395.601167 205.2096,396.625167 216.1664,361.501967 C236.7488,295.658767 191.232,230.429967 187.136,230.481167 L82.432,227.613967 C82.432,227.613967 48.4864,321.975567 68.352,362.986767 Z"
|
98 |
+
id="Path"
|
99 |
+
fill="#E8EDEC"
|
100 |
+
></path>
|
101 |
+
<path
|
102 |
+
d="M28.0064,255.569167 C21.504,269.239567 19.0464,287.773967 22.3744,304.004367 L66.9184,297.911567 C67.9424,282.397967 84.1216,232.529167 84.1216,232.529167 L60.7232,215.684367 C60.7232,215.684367 43.6736,222.545167 28.0064,255.569167 L28.0064,255.569167 Z"
|
103 |
+
id="Path"
|
104 |
+
fill="#E8EDEC"
|
105 |
+
></path>
|
106 |
+
<path
|
107 |
+
d="M45.2608,228.381967 C55.1936,236.778767 66.56,243.537167 79.104,248.452367 C81.92,239.185167 84.0704,232.477967 84.0704,232.477967 L60.672,215.633167 C60.672,215.684367 54.3232,218.141967 45.2608,228.381967 Z"
|
108 |
+
id="Path"
|
109 |
+
fill="#BEC9CE"
|
110 |
+
></path>
|
111 |
+
<path
|
112 |
+
d="M76.1856,247.274767 C94.0032,254.801167 114.3808,258.692367 136.7552,258.692367 C156.416,258.692367 174.9504,255.313167 191.7952,248.554767 C194.1504,247.633167 196.4032,246.609167 198.7072,245.585167 C193.1264,236.061967 188.3648,230.481167 187.1872,230.481167 L82.432,227.613967 C82.432,227.613967 79.6672,235.293967 76.1856,247.274767 Z"
|
113 |
+
id="Path"
|
114 |
+
fill="#BEC9CE"
|
115 |
+
></path>
|
116 |
+
<path
|
117 |
+
d="M42.6496,231.556367 C47.4624,237.137167 62.1056,256.337167 66.3552,286.135567 C66.3552,286.135567 72.704,243.076367 67.1232,220.241167 L60.7232,215.633167 C60.7232,215.684367 52.6848,218.909967 42.6496,231.556367 L42.6496,231.556367 Z"
|
118 |
+
id="Path"
|
119 |
+
fill="#FFB818"
|
120 |
+
></path>
|
121 |
+
<path
|
122 |
+
d="M45.2608,228.381967 C52.5824,234.577167 60.6208,239.850767 69.376,244.151567 C69.3248,235.498767 68.7104,226.999567 67.072,220.241167 L60.672,215.633167 C60.672,215.684367 54.1696,218.193167 45.2608,228.381967 L45.2608,228.381967 Z"
|
123 |
+
id="Path"
|
124 |
+
fill="#FF5D12"
|
125 |
+
></path>
|
126 |
+
<path
|
127 |
+
d="M133.5808,302.980367 L135.8848,325.661967 C136.704,333.495567 143.6672,339.229967 151.552,338.410767 L193.536,334.109967 C201.3696,333.290767 207.104,326.327567 206.2848,318.442767 L203.9808,295.761167 C203.1616,287.927567 196.1984,282.193167 188.3136,283.012367 L146.3296,287.313167 C138.496,288.132367 132.7616,295.146767 133.5808,302.980367 Z"
|
128 |
+
id="Path"
|
129 |
+
fill="#BEC9CE"
|
130 |
+
></path>
|
131 |
+
<path
|
132 |
+
d="M141.5168,298.628367 L143.872,321.770767 C144.4864,327.658767 149.76,331.959567 155.648,331.396367 L197.888,327.095567 C203.776,326.481167 208.0768,321.207567 207.5136,315.319567 L205.1584,292.177167 C204.544,286.289167 199.2704,281.988367 193.3824,282.551567 L151.1424,286.852367 C145.2032,287.466767 140.9024,292.740367 141.5168,298.628367 Z"
|
133 |
+
id="Path"
|
134 |
+
fill="#D0DBE2"
|
135 |
+
></path>
|
136 |
+
<ellipse
|
137 |
+
id="Oval"
|
138 |
+
fill="#FFB818"
|
139 |
+
transform="translate(160.568126, 307.574545) rotate(-5.730000) translate(-160.568126, -307.574545) "
|
140 |
+
cx="160.568126"
|
141 |
+
cy="307.574545"
|
142 |
+
rx="7.936"
|
143 |
+
ry="9.3184"
|
144 |
+
></ellipse>
|
145 |
+
<path
|
146 |
+
d="M152.4736,308.407567 C152.9856,313.527567 156.928,317.316367 161.28,316.855567 C164.8128,316.497167 167.4752,313.476367 168.1408,309.636367 C166.912,310.967567 165.376,311.837967 163.5328,312.042767 C159.1808,312.503567 155.2384,308.714767 154.7264,303.594767 C154.624,302.621967 154.6752,301.649167 154.8288,300.727567 C153.088,302.673167 152.1664,305.437967 152.4736,308.407567 Z"
|
147 |
+
id="Path"
|
148 |
+
fill="#FF5D12"
|
149 |
+
></path>
|
150 |
+
<path
|
151 |
+
d="M176.5888,303.543567 L193.1776,301.853967 C195.1744,301.649167 196.608,299.857167 196.4032,297.911567 L196.3008,297.041167 C196.096,295.044367 194.304,293.610767 192.3584,293.815567 L175.7696,295.505167 C173.7728,295.709967 172.3392,297.501967 172.544,299.447567 L172.6464,300.317967 C172.8512,302.314767 174.592,303.748367 176.5888,303.543567 Z"
|
152 |
+
id="Path"
|
153 |
+
fill="#A0CF30"
|
154 |
+
></path>
|
155 |
+
<path
|
156 |
+
d="M178.0736,318.033167 L194.6624,316.343567 C196.6592,316.138767 198.0928,314.346767 197.888,312.401167 L197.7856,311.530767 C197.5808,309.533967 195.7888,308.100367 193.8432,308.305167 L177.2544,309.994767 C175.2576,310.199567 173.824,311.991567 174.0288,313.937167 L174.1312,314.807567 C174.2848,316.804367 176.0768,318.237967 178.0736,318.033167 Z"
|
157 |
+
id="Path"
|
158 |
+
fill="#FDCC00"
|
159 |
+
></path>
|
160 |
+
<path
|
161 |
+
d="M175.4624,303.645967 L194.304,301.700367 C195.6864,301.546767 196.6592,300.164367 196.5056,298.525967 L196.4544,298.218767 L177.6128,300.164367 C176.2304,300.317967 175.0016,299.089167 174.848,297.501967 L174.6432,295.607567 C173.2608,295.761167 172.288,297.143567 172.4416,298.781967 L172.6464,300.983567 C172.8512,302.621967 174.08,303.799567 175.4624,303.645967 L175.4624,303.645967 Z"
|
162 |
+
id="Path"
|
163 |
+
fill="#89A520"
|
164 |
+
></path>
|
165 |
+
<path
|
166 |
+
d="M176.9472,318.186767 L195.7888,316.241167 C197.1712,316.087567 198.144,314.705167 197.9904,313.066767 L197.9392,312.759567 L179.0976,314.705167 C177.7152,314.858767 176.4864,313.629967 176.3328,312.042767 L176.128,310.148367 C174.7456,310.301967 173.7728,311.684367 173.9264,313.322767 L174.1312,315.524367 C174.336,317.111567 175.5648,318.289167 176.9472,318.186767 L176.9472,318.186767 Z"
|
167 |
+
id="Path"
|
168 |
+
fill="#FCA800"
|
169 |
+
></path>
|
170 |
+
<ellipse
|
171 |
+
id="Oval"
|
172 |
+
fill="#E8EDEC"
|
173 |
+
cx="136.7552"
|
174 |
+
cy="128.797967"
|
175 |
+
rx="122.368"
|
176 |
+
ry="117.8112"
|
177 |
+
></ellipse>
|
178 |
+
<path
|
179 |
+
d="M147.2,11.3963673 C75.264,5.55956735 14.336,60.1387673 14.336,128.797967 C14.336,202.935567 67.8912,251.524367 146.8416,246.301967 C73.8304,241.745167 35.2768,190.903567 35.2768,128.797967 C35.2768,67.1019673 84.48,16.5163673 147.2,11.3963673 Z"
|
180 |
+
id="Path"
|
181 |
+
fill="#D0DBE2"
|
182 |
+
></path>
|
183 |
+
<path
|
184 |
+
d="M95.8464,9.29716735 C88.9856,13.4955673 86.1696,21.4827673 86.1696,21.4827673 C86.1696,21.4827673 128.9216,22.1995673 141.9264,66.2827673 L201.8304,66.2827673 C201.8304,66.2827673 206.1312,31.6715673 174.2848,10.9355673 C142.7968,-9.49323265 104.5504,3.97236735 95.8464,9.29716735 L95.8464,9.29716735 Z"
|
185 |
+
id="Path"
|
186 |
+
fill="#FFB818"
|
187 |
+
></path>
|
188 |
+
<path
|
189 |
+
d="M96.1024,9.14356735 L95.7952,9.29716735 C88.9344,13.4955673 86.1184,21.4827673 86.1184,21.4827673 C86.1184,21.4827673 128.8704,22.1995673 141.8752,66.2827673 L152.5248,66.2827673 C152.1152,64.7467673 151.6544,63.1595673 151.1424,61.4699673 C138.8032,20.7147673 114.0224,10.6795673 96.1024,9.14356735 L96.1024,9.14356735 Z"
|
190 |
+
id="Path"
|
191 |
+
fill="#FF5D12"
|
192 |
+
></path>
|
193 |
+
<path
|
194 |
+
d="M57.6512,112.874767 C46.8992,144.772367 63.2832,224.849167 154.7776,224.849167 C261.0688,224.849167 259.9424,133.303567 252.4672,107.652367 C240.64,66.8459673 213.7088,57.0155673 174.1824,54.3019673 C112.2816,50.0523673 72.2944,69.4059673 57.6512,112.874767 Z"
|
195 |
+
id="Path"
|
196 |
+
fill="#000000"
|
197 |
+
></path>
|
198 |
+
<path
|
199 |
+
d="M168.448,238.980367 C182.5792,237.444367 208.384,223.159567 189.2352,222.391567 C185.5488,222.237967 192.9216,213.226767 177.3056,213.124367 C159.8464,213.021967 161.792,226.897167 142.7968,229.303567 C123.7504,231.761167 144.0768,241.693967 168.448,238.980367 L168.448,238.980367 Z"
|
200 |
+
id="Path"
|
201 |
+
fill="#E8EDEC"
|
202 |
+
></path>
|
203 |
+
<ellipse
|
204 |
+
id="Oval"
|
205 |
+
fill="#FFB818"
|
206 |
+
cx="25.9584"
|
207 |
+
cy="141.393167"
|
208 |
+
rx="24.832"
|
209 |
+
ry="42.2912"
|
210 |
+
></ellipse>
|
211 |
+
<ellipse
|
212 |
+
id="Oval"
|
213 |
+
fill="#FF5D12"
|
214 |
+
cx="18.944"
|
215 |
+
cy="141.393167"
|
216 |
+
rx="18.944"
|
217 |
+
ry="35.584"
|
218 |
+
></ellipse>
|
219 |
+
<g
|
220 |
+
transform="translate(64.249724, 83.345216)"
|
221 |
+
fill="#474747"
|
222 |
+
id="Shape"
|
223 |
+
>
|
224 |
+
<path d="M172.959876,7.97435136 C173.369476,3.98075136 170.451076,0.396751356 166.457476,0.0383513555 C162.463876,-0.371248644 158.879876,2.54715136 158.521476,6.54075136 C158.111876,10.5343514 161.030276,14.1183514 165.023876,14.4767514 C169.017476,14.8863514 172.550276,11.9679514 172.959876,7.97435136 Z M160.774276,109.247951 C160.108676,110.118351 161.337476,111.193551 162.105476,110.425551 C181.715076,91.4815514 191.187076,49.1903514 178.796676,20.2111514 C177.567876,17.2927514 174.188676,15.9615514 171.270276,17.1903514 C168.300676,18.4703514 166.918276,21.9519514 168.403076,24.9727514 C177.670276,43.9167514 177.311876,69.1071514 170.860676,89.1775514 C166.662276,102.182351 160.671876,109.401551 160.774276,109.247951 L160.774276,109.247951 Z M0.00627565551,56.8191514 C0.723075656,64.9087514 2.66867566,72.9471514 5.63827566,80.6271514 C10.8094757,93.9391514 17.8238757,102.643151 17.6190757,102.387151 C18.9502757,104.127951 21.4078757,104.486351 23.1486757,103.206351 C24.8894757,101.875151 25.2478757,99.4175514 23.9678757,97.6767514 C23.3534757,96.8575514 16.6462757,90.2527514 10.6046757,78.4767514 C4.46067566,66.4447514 2.87347566,56.1535514 2.92467566,56.4607514 C2.56627566,54.5663514 -0.147324344,54.9759514 0.00627565551,56.8191514 Z M29.3950757,104.639951 C26.7326757,105.049551 24.8382757,107.507151 25.2478757,110.220751 C25.6574757,112.883151 28.1150757,114.777551 30.8286757,114.367951 C33.4910757,113.958351 35.3854757,111.500751 34.9758757,108.787151 C34.5662757,106.073551 32.1086757,104.230351 29.3950757,104.639951 Z"></path>
|
225 |
+
</g>
|
226 |
+
<path
|
227 |
+
d="M214.681871,100.902169 C201.516737,89.3061686 180.601537,91.0902023 168.283842,105.029978 C155.118363,93.4335095 134.743599,95.4683743 123.258102,109.089316 C111.772605,122.710258 113.958018,141.612003 126.909166,152.193685 L172.379643,189.540493 L215.003181,145.28004 C227.534789,132.355011 227.464387,112.462126 214.681871,100.902169 Z"
|
228 |
+
id="Path"
|
229 |
+
fill={isHovered ? "#FF5D12" : "#57F287"}
|
230 |
+
></path>
|
231 |
+
<path
|
232 |
+
d="M22.3744,304.004367 C26.0608,321.821967 36.8128,336.823567 57.088,339.383567 C95.1296,344.196367 110.2848,306.973967 110.2848,306.973967 L76.6464,279.018767 C66.048,288.337167 48.3328,294.532367 48.3328,294.532367 L22.3744,304.004367 Z"
|
233 |
+
id="Path"
|
234 |
+
fill="#E8EDEC"
|
235 |
+
></path>
|
236 |
+
<g transform="translate(34.406400, 299.498767)" fill="#D0DBE2">
|
237 |
+
<path
|
238 |
+
d="M75.8272,7.424 C75.776,7.424 75.776,7.424 75.8272,7.424 C75.776,7.3728 75.7248,7.3728 75.7248,7.3216 C75.1616,6.912 72.96,5.3248 65.5872,0 C65.536,0 42.0352,49.1008 0,28.8256 C5.5808,34.6112 13.1072,38.656 22.7328,39.8848 C60.7232,44.6976 75.8784,7.4752 75.8272,7.424 C75.8784,7.4752 75.8272,7.424 75.8272,7.424 L75.8272,7.424 Z"
|
239 |
+
id="Shape"
|
240 |
+
></path>
|
241 |
+
<path
|
242 |
+
d="M75.7248,7.3216 C75.7248,7.3216 75.776,7.3216 75.776,7.3728 C75.776,7.424 75.7248,7.3728 75.7248,7.3216 C75.6736,7.3216 75.6736,7.3216 75.7248,7.3216 Z"
|
243 |
+
id="Path"
|
244 |
+
></path>
|
245 |
+
<path
|
246 |
+
d="M75.776,7.3728 L75.7248,7.3216 C75.7248,7.3728 75.7248,7.3728 75.776,7.3728 Z"
|
247 |
+
id="Path"
|
248 |
+
></path>
|
249 |
+
</g>
|
250 |
+
<path
|
251 |
+
d="M73.728,279.530767 C66.4576,285.418767 57.7024,289.361167 49.0496,292.842767 C46.7456,293.764367 44.4928,294.634767 42.1888,295.505167 C41.5744,295.709967 41.1648,296.324367 41.3696,296.989967 C41.5232,297.553167 42.24,298.065167 42.8544,297.809167 C51.7632,294.481167 60.672,290.948367 68.7616,285.981967 C71.1168,284.548367 73.3184,282.961167 75.4688,281.271567 C75.9808,280.861967 75.8784,279.991567 75.4688,279.581967 C74.9568,279.018767 74.24,279.121167 73.728,279.530767 L73.728,279.530767 Z"
|
252 |
+
id="Path"
|
253 |
+
fill="#BEC9CE"
|
254 |
+
></path>
|
255 |
+
<path
|
256 |
+
d="M158.1056,243.690767 C160.1536,238.519567 157.7472,229.917967 146.2272,234.065167 C134.7072,238.212367 114.688,245.380367 111.7696,244.919567 C108.8,244.407567 110.7456,236.113167 102.3488,231.505167 C96.3584,228.228367 87.9104,229.969167 86.528,235.805967 C85.1456,241.642767 94.464,256.797967 87.9104,265.757967 C84.9408,269.802767 74.2912,277.789967 73.2672,279.172367 C72.2432,280.554767 76.3904,293.866767 86.3232,300.983567 C96.3072,308.100367 110.08,311.991567 112.64,310.711567 C115.2,309.431567 122.7776,292.740367 124.0064,292.074767 C125.2352,291.409167 134.144,290.538767 140.8512,280.042767 C146.6368,271.031567 140.1856,257.156367 140.3392,257.156367 C140.544,257.156367 156.0576,248.861967 158.1056,243.690767 L158.1056,243.690767 Z"
|
257 |
+
id="Path"
|
258 |
+
fill="#FFB818"
|
259 |
+
></path>
|
260 |
+
<path
|
261 |
+
d="M248.32,347.729167 C248.32,347.729167 246.784,353.463567 242.7392,356.791567 C242.7392,356.791567 249.344,354.487567 251.4944,348.394767 C250.0608,347.473167 248.32,347.729167 248.32,347.729167 L248.32,347.729167 Z"
|
262 |
+
id="Path"
|
263 |
+
fill="#FF5D12"
|
264 |
+
></path>
|
265 |
+
<path
|
266 |
+
d="M110.4384,364.317967 C85.9136,367.338767 70.3488,358.020367 62.8736,351.773967 C59.5968,349.009167 54.8352,348.855567 51.456,351.466767 C46.7968,354.999567 46.5408,362.013967 51.0464,365.751567 C60.0064,373.226767 76.544,383.261967 101.0688,383.261967 C123.904,383.261967 145.0496,375.479567 158.4128,356.945167 C166.144,346.193167 168.0896,336.209167 168.2944,335.133967 C163.6352,332.164367 153.6512,332.522767 148.48,336.311567 C148.48,336.260367 144.384,360.170767 110.4384,364.317967 L110.4384,364.317967 Z"
|
267 |
+
id="Path"
|
268 |
+
fill="#82D1FF"
|
269 |
+
></path>
|
270 |
+
<path
|
271 |
+
d="M47.872,357.610767 C47.5136,360.580367 48.5376,363.652367 51.0464,365.751567 C60.0064,373.226767 76.544,383.261967 101.0688,383.261967 C123.904,383.261967 145.0496,375.479567 158.4128,356.945167 C166.144,346.193167 168.0896,336.209167 168.2944,335.133967 C167.424,334.570767 166.3488,334.109967 165.1712,333.802767 C157.1328,358.173967 134.656,376.657167 99.3792,376.657167 C72.2432,376.605967 51.712,362.679567 47.872,357.610767 L47.872,357.610767 Z"
|
272 |
+
id="Path"
|
273 |
+
fill="#51B1FC"
|
274 |
+
></path>
|
275 |
+
<path
|
276 |
+
d="M76.4928,357.969167 C76.4928,357.969167 76.4416,358.020367 76.4928,357.969167 C76.544,357.764367 76.544,357.764367 76.4928,357.969167 Z"
|
277 |
+
id="Path"
|
278 |
+
fill="#51B1FC"
|
279 |
+
></path>
|
280 |
+
<g
|
281 |
+
transform="translate(52.633600, 341.073167)"
|
282 |
+
fill="#51B1FC"
|
283 |
+
id="Shape"
|
284 |
+
>
|
285 |
+
<path d="M0,25.9584 C1.1776,26.88 2.5088,27.8528 3.9424,28.8256 C8.192,24.4224 11.9808,19.5584 15.104,14.2848 C13.6192,13.312 12.3392,12.3392 11.1616,11.4688 C8.2432,16.384 5.4272,19.8656 6.1952,18.8928 C4.3008,21.248 2.2528,23.6544 0,25.9584 Z M18.5856,36.608 C22.2208,31.5904 25.0368,26.0608 27.5456,20.3776 C25.9584,19.8144 24.4736,19.2512 23.0912,18.6368 C18.8928,28.2112 15.0016,33.536 14.1312,34.6624 C15.5136,35.3792 17.0496,35.9936 18.5856,36.608 L18.5856,36.608 Z M36.5056,41.3184 C38.2976,35.4304 39.5264,29.3888 40.192,23.296 C38.5536,23.0912 36.9664,22.8352 35.4304,22.528 C34.816,28.6208 33.5872,34.6112 31.744,40.4992 C33.28,40.8576 34.8672,41.1136 36.5056,41.3184 Z M53.3504,42.0352 C53.76,35.8912 53.8112,29.7472 53.4016,23.6544 C51.7632,23.7568 50.1248,23.808 48.5888,23.808 C48.9984,29.9008 48.9472,36.0448 48.5376,42.1888 C50.1248,42.1376 51.7632,42.1376 53.3504,42.0352 Z M65.6384,40.5504 C67.2256,40.2432 68.8128,39.8848 70.3488,39.4752 C69.632,33.3824 68.5056,27.392 67.072,21.4528 C65.5872,21.8624 64,22.2208 62.3616,22.528 C64.6656,31.744 65.4848,39.0656 65.28,37.6832 C65.4336,38.6048 65.536,39.5264 65.6384,40.5504 Z M81.5616,35.5328 C83.0464,34.8672 84.48,34.1504 85.9136,33.3824 C84.0704,27.648 81.7152,22.016 78.7968,16.6912 C77.4656,17.4592 76.032,18.176 74.496,18.8416 C77.3632,24.1152 79.7184,29.5936 81.5616,35.5328 Z M96.3584,26.1632 C97.5872,25.088 98.7648,23.9616 99.9424,22.784 C96.6656,17.5616 91.9552,12.3392 88.1664,9.2672 C87.1424,10.3936 85.9648,11.52 84.6336,12.6464 C87.6032,14.8992 91.4944,19.2512 93.3376,21.7088 C92.672,20.8384 94.5152,23.0912 96.3584,26.1632 L96.3584,26.1632 Z M110.7456,7.68 C105.6256,4.4032 100.0448,1.8432 94.2592,0 C93.696,1.28 92.928,2.7648 91.9552,4.352 C94.2592,5.0688 96.4608,5.8368 98.56,6.7072 C97.4336,6.2464 102.4,8.1408 108.4416,11.9296 C109.2608,10.4448 110.0288,9.0112 110.7456,7.68 L110.7456,7.68 Z"></path>
|
286 |
+
</g>
|
287 |
+
<path
|
288 |
+
d="M158.1056,243.690767 C158.9248,241.642767 159.0272,239.082767 158.1568,236.983567 C151.1424,247.786767 136.0896,251.575567 134.144,255.978767 C132.096,260.535567 140.9024,279.991567 115.9168,288.183567 C108.3904,290.641167 110.4896,288.797967 96.6144,306.717967 C103.9872,309.994767 110.8992,311.530767 112.6912,310.660367 C115.2512,309.380367 122.8288,292.689167 124.0576,292.023567 C125.2864,291.357967 134.1952,290.487567 140.9024,279.991567 C146.688,270.980367 140.2368,257.105167 140.3904,257.105167 C140.544,257.156367 156.0576,248.861967 158.1056,243.690767 Z"
|
289 |
+
id="Path"
|
290 |
+
fill="#FF5D12"
|
291 |
+
></path>
|
292 |
+
</g>
|
293 |
+
</g>
|
294 |
+
</svg>
|
295 |
+
);
|
296 |
+
};
|
components/clyde/message.tsx
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import { useState } from "react";
|
3 |
+
import { useMount, useUpdateEffect } from "react-use";
|
4 |
+
|
5 |
+
import { Clyde } from "./clyde";
|
6 |
+
|
7 |
+
export const ClydeMessage = ({
|
8 |
+
message,
|
9 |
+
auto = false,
|
10 |
+
onClick,
|
11 |
+
}: {
|
12 |
+
message: string;
|
13 |
+
auto?: boolean;
|
14 |
+
onClick: any;
|
15 |
+
}) => {
|
16 |
+
const [show, setShow] = useState(false);
|
17 |
+
|
18 |
+
useMount(() => {
|
19 |
+
if (auto) {
|
20 |
+
setTimeout(() => {
|
21 |
+
setShow(true);
|
22 |
+
}, 2000);
|
23 |
+
}
|
24 |
+
});
|
25 |
+
|
26 |
+
useUpdateEffect(() => {
|
27 |
+
if (auto && show) {
|
28 |
+
setTimeout(() => {
|
29 |
+
setShow(false);
|
30 |
+
}, 5000);
|
31 |
+
}
|
32 |
+
}, [show]);
|
33 |
+
|
34 |
+
return (
|
35 |
+
<div
|
36 |
+
className="left-0 lg:left-14 max-w-max group absolute top-0 transform -translate-y-[calc(100%-10px)] -z-1"
|
37 |
+
onClick={onClick}
|
38 |
+
>
|
39 |
+
<Clyde />
|
40 |
+
<div
|
41 |
+
className={classNames(
|
42 |
+
"transition-all duration-200 absolute top-2 left-0 bg-white shadow-xl text-sm text-dark-200 font-medium rounded-lg px-3.5 py-1.5 whitespace-nowrap transform group-hover:translate-x-[90px] opacity-0 group-hover:opacity-100",
|
43 |
+
{
|
44 |
+
"translate-x-[90px] opacity-100": show,
|
45 |
+
}
|
46 |
+
)}
|
47 |
+
>
|
48 |
+
{message}
|
49 |
+
<div className="w-[0px] h-[0px] border-t-[7px] border-t-transparent border-b-[7px] border-b-transparent border-r-[7px] border-r-white absolute top-[calc(50%-7px)] -left-[6px]" />
|
50 |
+
</div>
|
51 |
+
{/* Hello<p>lol</p> */}
|
52 |
+
</div>
|
53 |
+
);
|
54 |
+
};
|
components/collapse/index.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ChevronDownIcon } from "@heroicons/react/solid";
|
2 |
+
import classNames from "classnames";
|
3 |
+
import { useState } from "react";
|
4 |
+
import { useIntl } from "react-intl";
|
5 |
+
|
6 |
+
export const Collapse = ({
|
7 |
+
children,
|
8 |
+
title,
|
9 |
+
className,
|
10 |
+
open: defaultOpen = false,
|
11 |
+
onOpenClassName,
|
12 |
+
parentClassName,
|
13 |
+
}: {
|
14 |
+
children: React.ReactNode;
|
15 |
+
title: string | React.ReactNode;
|
16 |
+
className?: string;
|
17 |
+
open?: boolean;
|
18 |
+
onOpenClassName?: string;
|
19 |
+
parentClassName?: string;
|
20 |
+
}) => {
|
21 |
+
const [open, setOpen] = useState(defaultOpen);
|
22 |
+
const intl = useIntl();
|
23 |
+
|
24 |
+
return (
|
25 |
+
<div className={`${parentClassName} w-full`}>
|
26 |
+
<div
|
27 |
+
className={`${className} flex items-center justify-between cursor-pointer transition-all duration-200 ${
|
28 |
+
open && onOpenClassName
|
29 |
+
}`}
|
30 |
+
onClick={() => setOpen(!open)}
|
31 |
+
>
|
32 |
+
{typeof title === "string" ? intl.formatMessage({ id: title }) : title}
|
33 |
+
<div>
|
34 |
+
<ChevronDownIcon
|
35 |
+
className={classNames(
|
36 |
+
"w-5 text-white opacity-50 transition-all duration-200",
|
37 |
+
{
|
38 |
+
"rotate-180": open,
|
39 |
+
}
|
40 |
+
)}
|
41 |
+
/>
|
42 |
+
</div>
|
43 |
+
</div>
|
44 |
+
{open && children}
|
45 |
+
</div>
|
46 |
+
);
|
47 |
+
};
|
components/color-picker/index.tsx
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useRef } from "react";
|
2 |
+
import { useClickAway } from "react-use";
|
3 |
+
import Picker, { useColorPicker } from "react-best-gradient-color-picker";
|
4 |
+
import classNames from "classnames";
|
5 |
+
|
6 |
+
import ColorPickerIcon from "@/assets/images/color-picker.svg";
|
7 |
+
import { IconType } from "@/types/editor";
|
8 |
+
import Image from "next/image";
|
9 |
+
|
10 |
+
export const ColorPicker = ({
|
11 |
+
value,
|
12 |
+
data,
|
13 |
+
gradients = true,
|
14 |
+
full = false,
|
15 |
+
onChange,
|
16 |
+
className,
|
17 |
+
}: {
|
18 |
+
value?: string;
|
19 |
+
data?: any;
|
20 |
+
gradients?: boolean;
|
21 |
+
full?: boolean;
|
22 |
+
onChange: (c: any, datas: any) => void;
|
23 |
+
className?: string;
|
24 |
+
}) => {
|
25 |
+
const [open, setOpen] = useState(false);
|
26 |
+
const ref = useRef(null);
|
27 |
+
|
28 |
+
const [color, setColor] = useState(value ?? "#fff");
|
29 |
+
const { getGradientObject } = useColorPicker(color, setColor);
|
30 |
+
|
31 |
+
useClickAway(ref, () => setOpen(false));
|
32 |
+
|
33 |
+
const renderBackground = (data: any) => {
|
34 |
+
if (data?.gradient?.enabled) {
|
35 |
+
if (data?.gradient?.type === "linearGradient") {
|
36 |
+
return {
|
37 |
+
background: `linear-gradient(${data?.gradient?.colours
|
38 |
+
?.map((e: any) => e.value)
|
39 |
+
.join(", ")})`,
|
40 |
+
};
|
41 |
+
} else {
|
42 |
+
return {
|
43 |
+
background: `radial-gradient(${data?.gradient?.colours
|
44 |
+
?.map((e: any) => e.value)
|
45 |
+
.join(", ")})`,
|
46 |
+
};
|
47 |
+
}
|
48 |
+
}
|
49 |
+
return { backgroundColor: data?.colour ?? data?.color ?? "#fff" };
|
50 |
+
};
|
51 |
+
|
52 |
+
return (
|
53 |
+
<div className="relative ml-[2px]">
|
54 |
+
<div
|
55 |
+
ref={ref}
|
56 |
+
className={classNames(
|
57 |
+
"bg-dark-500 z-[100] md:z-10 lg:rounded-md shadow-lg p-3 border border-dark-600 fixed md:absolute w-full md:w-max md:h-min bottom-0 md:-top-full left-0 transform md:translate-x-7",
|
58 |
+
{
|
59 |
+
"pointer-events-none opacity-0": !open,
|
60 |
+
}
|
61 |
+
)}
|
62 |
+
>
|
63 |
+
{open && (
|
64 |
+
<Picker
|
65 |
+
value={color}
|
66 |
+
hidePresets={true}
|
67 |
+
hideAdvancedSliders={true}
|
68 |
+
hideColorGuide={true}
|
69 |
+
hideInputType={true}
|
70 |
+
onChange={(c: string) => {
|
71 |
+
setColor(c);
|
72 |
+
const datas = getGradientObject();
|
73 |
+
onChange(c, datas);
|
74 |
+
}}
|
75 |
+
className={`${
|
76 |
+
data?.gradient?.enabled
|
77 |
+
? "color__picker_discotools--gradient"
|
78 |
+
: "color__picker_discotools"
|
79 |
+
} ${gradients ? "" : "color__picker without-gradients"}`}
|
80 |
+
/>
|
81 |
+
)}
|
82 |
+
</div>
|
83 |
+
<div
|
84 |
+
className={classNames(
|
85 |
+
`group flex items-center transition-all duration-200 hover:ring-opacity-100 ring-opacity-70 justify-center border-[1px] borderk-dark-600 ring-[1px] ring-white h-8 rounded cursor-pointer ${className}`,
|
86 |
+
{
|
87 |
+
"w-20": !full,
|
88 |
+
"w-full": full,
|
89 |
+
}
|
90 |
+
)}
|
91 |
+
style={data ? renderBackground(data) : { backgroundColor: color }}
|
92 |
+
onClick={() => setOpen(true)}
|
93 |
+
>
|
94 |
+
<Image
|
95 |
+
src={ColorPickerIcon}
|
96 |
+
width={12}
|
97 |
+
height={12}
|
98 |
+
alt="ColorPicker"
|
99 |
+
className="opacity-80"
|
100 |
+
/>
|
101 |
+
</div>
|
102 |
+
</div>
|
103 |
+
);
|
104 |
+
};
|
components/editor-badge/badge-editor.constants.ts
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { BadgeType } from "types/badge";
|
2 |
+
|
3 |
+
export const DEFAULT_VALUE: BadgeType = {
|
4 |
+
colour: "#5865F1",
|
5 |
+
gradient: {
|
6 |
+
enabled: false,
|
7 |
+
colours: [],
|
8 |
+
angle: 0,
|
9 |
+
type: "linear-gradient",
|
10 |
+
},
|
11 |
+
text: {
|
12 |
+
value: "DISCOTOOLS.XYZ",
|
13 |
+
colour: "#ffffff",
|
14 |
+
gradient: {
|
15 |
+
enabled: false,
|
16 |
+
colours: [],
|
17 |
+
angle: 0,
|
18 |
+
type: "linear-gradient",
|
19 |
+
},
|
20 |
+
},
|
21 |
+
icon: {
|
22 |
+
component: "King",
|
23 |
+
position: "left",
|
24 |
+
enabled: true,
|
25 |
+
colour: "#ffffff",
|
26 |
+
},
|
27 |
+
shinyEffect: false,
|
28 |
+
type: "circle",
|
29 |
+
radius: 40,
|
30 |
+
fontFamily: "Montserrat",
|
31 |
+
fontWeight: "700",
|
32 |
+
letterSpacing: 0,
|
33 |
+
border: {
|
34 |
+
width: 0,
|
35 |
+
colour: "#ffffff",
|
36 |
+
},
|
37 |
+
};
|
38 |
+
|
39 |
+
export const RATIONAL_BADGE_WIDTH = 32;
|
components/editor-badge/comps/badge.tsx
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { forwardRef, useEffect, useRef, useState } from "react";
|
2 |
+
|
3 |
+
import { BadgeType } from "@/types/badge";
|
4 |
+
import { FONT_FAMILY } from "@/components/font-family/font-family.constants";
|
5 |
+
|
6 |
+
import { RATIONAL_BADGE_WIDTH } from "../badge-editor.constants";
|
7 |
+
import { Icons } from "@/components/svg/icons";
|
8 |
+
import { IconItem } from "@/types/editor";
|
9 |
+
import { BADGE_COMPONENTS } from "@/components/svg/badges";
|
10 |
+
import classNames from "classnames";
|
11 |
+
import { ShineEffect } from "./shine_effect";
|
12 |
+
|
13 |
+
export const Badge = forwardRef(
|
14 |
+
(
|
15 |
+
{
|
16 |
+
badge,
|
17 |
+
size = "medium",
|
18 |
+
}: {
|
19 |
+
badge: BadgeType;
|
20 |
+
size?: "medium" | "small";
|
21 |
+
},
|
22 |
+
ref
|
23 |
+
) => {
|
24 |
+
const textRef = useRef<any>(null);
|
25 |
+
const svgRef = useRef<any>(null);
|
26 |
+
const badgeContentRef = useRef<any>(null);
|
27 |
+
const [viewBox, setViewBox] = useState<string>("0 0 24 24");
|
28 |
+
|
29 |
+
useEffect(() => {
|
30 |
+
if (!textRef.current) return;
|
31 |
+
if (!badgeContentRef.current) return;
|
32 |
+
|
33 |
+
const textWidth = textRef.current.getBoundingClientRect().width;
|
34 |
+
|
35 |
+
let newBadgeWidth = RATIONAL_BADGE_WIDTH;
|
36 |
+
if (textWidth > RATIONAL_BADGE_WIDTH) {
|
37 |
+
newBadgeWidth = Math.ceil(textWidth / 32) * 32;
|
38 |
+
}
|
39 |
+
if (
|
40 |
+
(findShape?.autoResize || badge.type === "circle") &&
|
41 |
+
newBadgeWidth - textWidth < (badge.type === "circle" ? 6 : 3)
|
42 |
+
)
|
43 |
+
newBadgeWidth += 32;
|
44 |
+
badgeContentRef.current.style.width = `${newBadgeWidth}px`;
|
45 |
+
}, [
|
46 |
+
badge.text,
|
47 |
+
badge.fontFamily,
|
48 |
+
badge.letterSpacing,
|
49 |
+
badge.fontWeight,
|
50 |
+
badge.type,
|
51 |
+
badge?.icon?.component,
|
52 |
+
badge?.icon?.enabled,
|
53 |
+
badge?.icon?.position,
|
54 |
+
]);
|
55 |
+
|
56 |
+
const fontFamily = FONT_FAMILY?.find((f) => f.label === badge?.fontFamily);
|
57 |
+
|
58 |
+
const defaultStyle = {
|
59 |
+
letterSpacing: badge?.letterSpacing,
|
60 |
+
fontWeight: badge?.fontWeight,
|
61 |
+
};
|
62 |
+
|
63 |
+
const findIcon = Icons?.find(
|
64 |
+
(i: IconItem) => badge?.icon?.component === i.name
|
65 |
+
);
|
66 |
+
const findShape = BADGE_COMPONENTS?.find(
|
67 |
+
(b: any) => b.name === badge?.type
|
68 |
+
);
|
69 |
+
|
70 |
+
useEffect(() => {
|
71 |
+
return setViewBox(
|
72 |
+
svgRef?.current?.getAttribute("viewBox") ?? "0 0 200 200"
|
73 |
+
);
|
74 |
+
}, [badge?.icon]);
|
75 |
+
|
76 |
+
const IconComponent = findIcon?.component as any;
|
77 |
+
const ShapeComponent = findShape?.component as any;
|
78 |
+
|
79 |
+
return (
|
80 |
+
<div
|
81 |
+
id={size === "medium" ? "discotools-selected-badge" : ""}
|
82 |
+
ref={ref as any}
|
83 |
+
className="max-w-max flex items-center h-[32px] justify-center relative"
|
84 |
+
>
|
85 |
+
{findShape?.component && (
|
86 |
+
<ShapeComponent.left color={badge?.stringColor ?? badge?.colour} />
|
87 |
+
)}
|
88 |
+
{badge?.shinyEffect && (
|
89 |
+
<>
|
90 |
+
<ShineEffect className="absolute left-[6px] -bottom-[0px] z-1" />
|
91 |
+
<ShineEffect className="absolute right-[6px] -top-[0px] z-1" />
|
92 |
+
</>
|
93 |
+
)}
|
94 |
+
<div
|
95 |
+
className="whitespace-nowrap text-white font-bold flex text-[18px] h-[28px] items-center justify-center text-center transition-all duration-200 relative"
|
96 |
+
ref={badgeContentRef}
|
97 |
+
style={{
|
98 |
+
background: badge?.stringColor ?? badge?.colour,
|
99 |
+
borderRadius: badge.type !== "circle" ? 0 : badge?.radius ?? 0,
|
100 |
+
}}
|
101 |
+
>
|
102 |
+
<div
|
103 |
+
ref={textRef as any}
|
104 |
+
className="flex items-center justify-center gap-2"
|
105 |
+
>
|
106 |
+
{badge?.icon?.enabled &&
|
107 |
+
badge?.icon?.position?.includes("left") &&
|
108 |
+
findIcon?.component &&
|
109 |
+
badge?.icon?.component && (
|
110 |
+
<svg
|
111 |
+
width={16}
|
112 |
+
height={16}
|
113 |
+
fill={badge?.icon?.colour}
|
114 |
+
viewBox={viewBox}
|
115 |
+
style={{ minWidth: 16, minHeight: 16 }}
|
116 |
+
>
|
117 |
+
<IconComponent ref={svgRef as any} />
|
118 |
+
</svg>
|
119 |
+
)}
|
120 |
+
|
121 |
+
{badge?.text?.value && (
|
122 |
+
<p
|
123 |
+
className={`!bg-clip-text text-transparent ${fontFamily?.value}`}
|
124 |
+
style={
|
125 |
+
badge?.text?.gradient?.enabled
|
126 |
+
? {
|
127 |
+
backgroundImage:
|
128 |
+
badge?.text?.stringColor ?? badge?.text?.colour,
|
129 |
+
...defaultStyle,
|
130 |
+
}
|
131 |
+
: {
|
132 |
+
backgroundColor:
|
133 |
+
badge?.text?.stringColor ?? badge?.text?.colour,
|
134 |
+
...defaultStyle,
|
135 |
+
}
|
136 |
+
}
|
137 |
+
>
|
138 |
+
{badge?.text?.value}
|
139 |
+
</p>
|
140 |
+
)}
|
141 |
+
{badge?.icon?.enabled &&
|
142 |
+
badge?.icon?.position?.includes("right") &&
|
143 |
+
findIcon?.component &&
|
144 |
+
badge?.icon?.component && (
|
145 |
+
<svg
|
146 |
+
width={16}
|
147 |
+
height={16}
|
148 |
+
fill={badge?.icon?.colour}
|
149 |
+
viewBox={viewBox}
|
150 |
+
style={{ minWidth: 16, minHeight: 16 }}
|
151 |
+
>
|
152 |
+
<IconComponent ref={svgRef as any} />
|
153 |
+
</svg>
|
154 |
+
)}
|
155 |
+
</div>
|
156 |
+
</div>
|
157 |
+
{findShape?.component && (
|
158 |
+
<ShapeComponent.right color={badge?.stringColor ?? badge?.colour} />
|
159 |
+
)}
|
160 |
+
</div>
|
161 |
+
);
|
162 |
+
}
|
163 |
+
);
|
components/editor-badge/comps/download_button.tsx
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useRef, useState } from "react";
|
2 |
+
import { ChevronDownIcon } from "@heroicons/react/solid";
|
3 |
+
import classNames from "classnames";
|
4 |
+
import { useClickAway } from "react-use";
|
5 |
+
|
6 |
+
export const DownloadButton = ({
|
7 |
+
onSave,
|
8 |
+
}: {
|
9 |
+
onSave: (e?: boolean) => void;
|
10 |
+
}) => {
|
11 |
+
const [dropdown, setDropdown] = useState(false);
|
12 |
+
const ref = useRef(null);
|
13 |
+
|
14 |
+
useClickAway(ref, () => {
|
15 |
+
setDropdown(false);
|
16 |
+
});
|
17 |
+
|
18 |
+
return (
|
19 |
+
<div
|
20 |
+
ref={ref}
|
21 |
+
className="flex justify-start mt-5 bg-darkGreen rounded-md relative"
|
22 |
+
>
|
23 |
+
<button
|
24 |
+
onClick={() => onSave()}
|
25 |
+
className="bg-darkGreen w-full text-white font-semibold p-2 rounded-l-md text-sm font-title relative overflow-hidden group"
|
26 |
+
>
|
27 |
+
<div className="absolute top-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
|
28 |
+
<div className="absolute bottom-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
|
29 |
+
<span className="relative z-1">Download as .zip</span>
|
30 |
+
</button>
|
31 |
+
<div
|
32 |
+
className="bg-dark-600 bg-opacity-10 px-1.5 border-l border-dark-600 border-opacity-20 flex items-center hover:bg-opacity-20 cursor-pointer"
|
33 |
+
onClick={(e) => {
|
34 |
+
e.preventDefault();
|
35 |
+
e.stopPropagation();
|
36 |
+
setDropdown(!dropdown);
|
37 |
+
}}
|
38 |
+
>
|
39 |
+
<ChevronDownIcon className="w-5 h-5 text-white" />
|
40 |
+
</div>
|
41 |
+
<div
|
42 |
+
className={classNames(
|
43 |
+
"bg-darkGreen w-full rounded-md absolute bottom-0 left-0 translate-y-[calc(100%+8px)] transition-all duration-200",
|
44 |
+
{
|
45 |
+
"pointer-events-none opacity-0 !-translate-y-1/2": !dropdown,
|
46 |
+
}
|
47 |
+
)}
|
48 |
+
>
|
49 |
+
<button
|
50 |
+
onClick={() => onSave(true)}
|
51 |
+
className="bg-darkGreen w-full text-white font-semibold p-2 rounded-md text-sm font-title relative overflow-hidden group"
|
52 |
+
>
|
53 |
+
<div className="absolute top-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
|
54 |
+
<div className="absolute bottom-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
|
55 |
+
<span className="relative z-1">Download as Image</span>
|
56 |
+
</button>
|
57 |
+
</div>
|
58 |
+
</div>
|
59 |
+
);
|
60 |
+
};
|
components/editor-badge/comps/header/comps/icons-slider.tsx
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useRef, useState } from "react";
|
2 |
+
import { useInterval } from "react-use";
|
3 |
+
import { motion } from "framer-motion";
|
4 |
+
import Image from "next/image";
|
5 |
+
|
6 |
+
import { sleep } from "utils";
|
7 |
+
import HammerIcon from "assets/images/header/icons-slider/hammer.svg";
|
8 |
+
import ShieldIcon from "assets/images/header/icons-slider/shield.svg";
|
9 |
+
import CrownIcon from "assets/images/header/icons-slider/crown.svg";
|
10 |
+
import PartnerIcon from "assets/images/header/icons-slider/partner.svg";
|
11 |
+
import StarIcon from "assets/images/header/icons-slider/star.svg";
|
12 |
+
import LeafIcon from "assets/images/header/icons-slider/leaf.svg";
|
13 |
+
|
14 |
+
const ICONS = [
|
15 |
+
HammerIcon,
|
16 |
+
ShieldIcon,
|
17 |
+
CrownIcon,
|
18 |
+
PartnerIcon,
|
19 |
+
StarIcon,
|
20 |
+
LeafIcon,
|
21 |
+
];
|
22 |
+
|
23 |
+
const icon = {
|
24 |
+
hidden: {
|
25 |
+
opacity: 0,
|
26 |
+
pathLength: 0,
|
27 |
+
fill: "rgba(255, 255, 255, 0)",
|
28 |
+
},
|
29 |
+
visible: {
|
30 |
+
opacity: 1,
|
31 |
+
pathLength: 1,
|
32 |
+
fill: "rgba(255, 255, 255, 1)",
|
33 |
+
},
|
34 |
+
};
|
35 |
+
|
36 |
+
export const IconsSlider = () => {
|
37 |
+
const [selected, setSelected] = useState(0);
|
38 |
+
const ref = useRef<HTMLDivElement>(null);
|
39 |
+
|
40 |
+
useInterval(async () => {
|
41 |
+
if (ref?.current) {
|
42 |
+
ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50");
|
43 |
+
await sleep(300);
|
44 |
+
ref.current?.classList?.remove("-translate-x-full");
|
45 |
+
ref.current?.classList?.add("translate-x-full");
|
46 |
+
|
47 |
+
setSelected((prev) => (prev + 1) % ICONS.length);
|
48 |
+
// if (this.selected >= this.avatars.length - 1) {
|
49 |
+
// this.selected = 0;
|
50 |
+
// } else {
|
51 |
+
// this.selected += 1;
|
52 |
+
// }
|
53 |
+
await sleep(300);
|
54 |
+
ref.current?.classList?.remove(
|
55 |
+
"translate-x-full",
|
56 |
+
"scale-50",
|
57 |
+
"opacity-0"
|
58 |
+
);
|
59 |
+
}
|
60 |
+
}, 2000);
|
61 |
+
|
62 |
+
return (
|
63 |
+
<div className="w-48 h-32 mx-auto relative z-1 flex items-center justify-center mt-7">
|
64 |
+
{/* <Image
|
65 |
+
src={ShapeIcon}
|
66 |
+
width={32}
|
67 |
+
height={32}
|
68 |
+
alt="Animation shape"
|
69 |
+
className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility"
|
70 |
+
/> */}
|
71 |
+
|
72 |
+
<motion.svg
|
73 |
+
viewBox="0 0 224 70"
|
74 |
+
xmlns="http://www.w3.org/2000/svg"
|
75 |
+
className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility stroke-white stroke-2"
|
76 |
+
>
|
77 |
+
{/* <motion.path
|
78 |
+
d="M154.208 0H39.792l23.074 35-23.074 35h114.416l-23.074-35 23.074-35z"
|
79 |
+
variants={icon}
|
80 |
+
initial="hidden"
|
81 |
+
animate="visible"
|
82 |
+
transition={{
|
83 |
+
default: { duration: 2, ease: "easeInOut" },
|
84 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
85 |
+
}}
|
86 |
+
/> */}
|
87 |
+
<motion.path
|
88 |
+
variants={icon}
|
89 |
+
initial="hidden"
|
90 |
+
animate="visible"
|
91 |
+
transition={{
|
92 |
+
default: { duration: 2, ease: "easeInOut" },
|
93 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
94 |
+
}}
|
95 |
+
d="M183.573 0H39.793l23.073 35-23.074 35h143.781L160.5 35l23.073-35z"
|
96 |
+
fill="#fff"
|
97 |
+
/>
|
98 |
+
<motion.path
|
99 |
+
variants={icon}
|
100 |
+
initial="hidden"
|
101 |
+
animate="visible"
|
102 |
+
transition={{
|
103 |
+
default: { duration: 2, ease: "easeInOut" },
|
104 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
105 |
+
}}
|
106 |
+
d="M28.79 0H9.26l5.05 7.609 4.883 7.608 4.21 6.425L32.324 35l-8.923 13.358-2.525 4.058-6.566 9.975L9.26 70h19.53l23.065-35L28.79 0z"
|
107 |
+
fill="#fff"
|
108 |
+
/>
|
109 |
+
<motion.path
|
110 |
+
variants={icon}
|
111 |
+
initial="hidden"
|
112 |
+
animate="visible"
|
113 |
+
transition={{
|
114 |
+
default: { duration: 2, ease: "easeInOut" },
|
115 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
116 |
+
}}
|
117 |
+
d="M10.775 21.643L0 35l10.775 13.358L21.55 35 10.775 21.643zM194.765 0h19.525l-5.049 7.609-4.881 7.608-4.208 6.425L191.231 35l8.921 13.358 2.524 4.058 6.565 9.975L214.29 70h-19.525l-23.059-35 23.059-35z"
|
118 |
+
fill="#fff"
|
119 |
+
/>
|
120 |
+
<motion.path
|
121 |
+
variants={icon}
|
122 |
+
initial="hidden"
|
123 |
+
animate="visible"
|
124 |
+
transition={{
|
125 |
+
default: { duration: 2, ease: "easeInOut" },
|
126 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
127 |
+
}}
|
128 |
+
d="M212.545 21.7L223.366 35l-10.821 13.3L201.724 35l10.821-13.3z"
|
129 |
+
fill="#fff"
|
130 |
+
/>
|
131 |
+
</motion.svg>
|
132 |
+
<div
|
133 |
+
ref={ref}
|
134 |
+
className="transform transition duration-300 w-[45px] h-[45px] filter drop-shadow-xl origin-center"
|
135 |
+
>
|
136 |
+
<Image
|
137 |
+
src={ICONS[selected]}
|
138 |
+
width={32}
|
139 |
+
height={32}
|
140 |
+
alt="Animation icon, change every 3 seconds"
|
141 |
+
className="w-full h-full"
|
142 |
+
draggable="false"
|
143 |
+
/>
|
144 |
+
</div>
|
145 |
+
<p></p>
|
146 |
+
</div>
|
147 |
+
// <div class="w-64 h-64 mx-auto relative z-1 flex items-center justify-center">
|
148 |
+
// <img src="@/assets/images/shadow_gradient.svg" alt="Linear gradient behind avatar" width="86" height="86" draggable="false" class="h-full absolute left-0 top-0 w-full -z-1 backface-visibility">
|
149 |
+
// <Avatar />
|
150 |
+
// </div>
|
151 |
+
);
|
152 |
+
};
|
components/editor-badge/comps/header/form/advanced.tsx
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
3 |
+
|
4 |
+
import { FontFamilySelector } from "@/components/font-family";
|
5 |
+
import { FontWeight } from "@/components/font-weight";
|
6 |
+
|
7 |
+
import { Switch } from "@/components/switch";
|
8 |
+
import { BadgeType } from "@/types/badge";
|
9 |
+
import { PremiumOverlay } from "@/components/premium/overlay";
|
10 |
+
|
11 |
+
import { Label } from "@/components/label";
|
12 |
+
import { Input } from "@/components/input";
|
13 |
+
|
14 |
+
export const AdvancedForm = ({
|
15 |
+
badge,
|
16 |
+
setBadge,
|
17 |
+
}: {
|
18 |
+
badge: BadgeType;
|
19 |
+
setBadge: (b: BadgeType) => void;
|
20 |
+
}) => {
|
21 |
+
const intl = useIntl();
|
22 |
+
|
23 |
+
return (
|
24 |
+
<div className="grid grid-cols-1 gap-4 px-6 pb-6 pt-5">
|
25 |
+
{badge?.type === "circle" && (
|
26 |
+
<div>
|
27 |
+
<Label className="mb-2">Rounded</Label>
|
28 |
+
<Input
|
29 |
+
value={badge?.radius}
|
30 |
+
type="number"
|
31 |
+
placeholder="Badge radius"
|
32 |
+
className="bg-dark-600 rounded p-3 text-sm text-white placeholder-dark-200"
|
33 |
+
onChange={(newRadius) => {
|
34 |
+
let radius: number = Number(newRadius);
|
35 |
+
if (radius > 100) radius = 100;
|
36 |
+
setBadge({ ...badge, radius: Number(radius) });
|
37 |
+
}}
|
38 |
+
/>
|
39 |
+
</div>
|
40 |
+
)}
|
41 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 relative">
|
42 |
+
<div className="lg:col-span-2">
|
43 |
+
<Label>Font</Label>
|
44 |
+
</div>
|
45 |
+
<div>
|
46 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
47 |
+
<FormattedMessage id="Font Family" />
|
48 |
+
</p>
|
49 |
+
<FontFamilySelector
|
50 |
+
value={badge?.fontFamily}
|
51 |
+
onSelect={(fontFamily) => setBadge({ ...badge, fontFamily })}
|
52 |
+
/>
|
53 |
+
</div>
|
54 |
+
<div>
|
55 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
56 |
+
<FormattedMessage id="Font Weight" />
|
57 |
+
</p>
|
58 |
+
<FontWeight
|
59 |
+
value={badge?.fontWeight}
|
60 |
+
onSelect={(fontWeight) => setBadge({ ...badge, fontWeight })}
|
61 |
+
/>
|
62 |
+
</div>
|
63 |
+
<div className="lg:col-span-2">
|
64 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
65 |
+
<FormattedMessage id="Letter Spacing" />
|
66 |
+
</p>
|
67 |
+
<Input
|
68 |
+
value={badge?.letterSpacing}
|
69 |
+
type="number"
|
70 |
+
placeholder="Letter spacing"
|
71 |
+
className="bg-dark-600 rounded p-3 text-sm text-white placeholder-dark-200"
|
72 |
+
onChange={(newLetterSpacing) => {
|
73 |
+
let letterSpacing: number = Number(newLetterSpacing);
|
74 |
+
if (letterSpacing > 100) letterSpacing = 100;
|
75 |
+
setBadge({
|
76 |
+
...badge,
|
77 |
+
letterSpacing: Number(letterSpacing),
|
78 |
+
});
|
79 |
+
}}
|
80 |
+
/>
|
81 |
+
</div>
|
82 |
+
<PremiumOverlay className="!bg-dark-500 !bg-opacity-80" />
|
83 |
+
</div>
|
84 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
85 |
+
<div className="lg:col-span-2 flex items-center justify-start gap-3">
|
86 |
+
<Label>Shiny Effect</Label>
|
87 |
+
<Switch
|
88 |
+
value={badge?.shinyEffect}
|
89 |
+
onChange={(enabled: boolean) =>
|
90 |
+
setBadge({
|
91 |
+
...badge,
|
92 |
+
shinyEffect: enabled,
|
93 |
+
})
|
94 |
+
}
|
95 |
+
/>
|
96 |
+
</div>
|
97 |
+
</div>
|
98 |
+
</div>
|
99 |
+
);
|
100 |
+
};
|
components/editor-badge/comps/header/form/main.tsx
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
3 |
+
|
4 |
+
import { FontFamilySelector } from "@/components/font-family";
|
5 |
+
import { FontWeight } from "@/components/font-weight";
|
6 |
+
|
7 |
+
import { Switch } from "@/components/switch";
|
8 |
+
import { BadgeType } from "@/types/badge";
|
9 |
+
import { ColorPicker } from "components/color-picker";
|
10 |
+
import { Label } from "@/components/label";
|
11 |
+
import { Input } from "@/components/input";
|
12 |
+
import classNames from "classnames";
|
13 |
+
import { SelectShapes } from "../../select-shapes";
|
14 |
+
import { IconPicker } from "@/components/icon-picker";
|
15 |
+
|
16 |
+
export const MainForm = ({
|
17 |
+
badge,
|
18 |
+
setBadge,
|
19 |
+
}: {
|
20 |
+
badge: BadgeType;
|
21 |
+
setBadge: (b: BadgeType) => void;
|
22 |
+
}) => {
|
23 |
+
const intl = useIntl();
|
24 |
+
|
25 |
+
return (
|
26 |
+
<div className="grid grid-cols-1 gap-4 px-6 pb-6 pt-5">
|
27 |
+
<SelectShapes badge={badge} onChange={setBadge} />
|
28 |
+
<div>
|
29 |
+
<Label className="mb-2">Text</Label>
|
30 |
+
<Input
|
31 |
+
value={badge?.text?.value}
|
32 |
+
placeholder="Badge text"
|
33 |
+
className="bg-dark-600 rounded px-3 py-3 text-sm text-white placeholder-dark-200 w-full"
|
34 |
+
onChange={(value) => {
|
35 |
+
setBadge({
|
36 |
+
...badge,
|
37 |
+
text: { ...badge.text, value: value as string },
|
38 |
+
});
|
39 |
+
}}
|
40 |
+
/>
|
41 |
+
</div>
|
42 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
43 |
+
<div className="lg:col-span-2">
|
44 |
+
<Label>Colors</Label>
|
45 |
+
</div>
|
46 |
+
<div>
|
47 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
48 |
+
<FormattedMessage id="Background" />
|
49 |
+
</p>
|
50 |
+
<ColorPicker
|
51 |
+
data={badge}
|
52 |
+
full
|
53 |
+
value={badge?.stringColor ?? badge?.colour}
|
54 |
+
gradients={badge?.type === "circle"}
|
55 |
+
onChange={(c: any, datas) => {
|
56 |
+
let newBadge = { ...badge, stringColor: c };
|
57 |
+
if (c.includes("gradient")) {
|
58 |
+
const { colors, degrees } = datas;
|
59 |
+
const gradientType = c?.startsWith("linear")
|
60 |
+
? "linearGradient"
|
61 |
+
: "radialGradient";
|
62 |
+
const anglePI =
|
63 |
+
Number(degrees ? degrees?.replace("deg", "") : 90) *
|
64 |
+
(Math.PI / 180);
|
65 |
+
const angleCoords = {
|
66 |
+
x1: Math.round(50 + Math.sin(anglePI) * 50) + "%",
|
67 |
+
y1: Math.round(50 + Math.cos(anglePI) * 50) + "%",
|
68 |
+
x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%",
|
69 |
+
y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%",
|
70 |
+
};
|
71 |
+
|
72 |
+
newBadge["gradient"] = {
|
73 |
+
...newBadge.gradient,
|
74 |
+
enabled: true,
|
75 |
+
colours: colors,
|
76 |
+
angle: angleCoords,
|
77 |
+
type: gradientType,
|
78 |
+
};
|
79 |
+
} else {
|
80 |
+
newBadge = {
|
81 |
+
...newBadge,
|
82 |
+
colour: c,
|
83 |
+
gradient: {
|
84 |
+
...newBadge?.gradient,
|
85 |
+
enabled: false,
|
86 |
+
},
|
87 |
+
};
|
88 |
+
}
|
89 |
+
setBadge(newBadge);
|
90 |
+
}}
|
91 |
+
/>
|
92 |
+
</div>
|
93 |
+
<div>
|
94 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
95 |
+
<FormattedMessage id="Text" />
|
96 |
+
</p>
|
97 |
+
<ColorPicker
|
98 |
+
data={badge?.text}
|
99 |
+
value={badge?.text?.stringColor ?? badge?.text?.colour}
|
100 |
+
full
|
101 |
+
onChange={(c: any, datas) => {
|
102 |
+
let newBadge = {
|
103 |
+
...badge,
|
104 |
+
text: { ...badge.text, stringColor: c },
|
105 |
+
};
|
106 |
+
if (c.includes("gradient")) {
|
107 |
+
const { colors, degrees } = datas;
|
108 |
+
const gradientType = c?.startsWith("linear")
|
109 |
+
? "linearGradient"
|
110 |
+
: "radialGradient";
|
111 |
+
const anglePI =
|
112 |
+
Number(degrees ? degrees?.replace("deg", "") : 90) *
|
113 |
+
(Math.PI / 180);
|
114 |
+
const angleCoords = {
|
115 |
+
x1: Math.round(50 + Math.sin(anglePI) * 50) + "%",
|
116 |
+
y1: Math.round(50 + Math.cos(anglePI) * 50) + "%",
|
117 |
+
x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%",
|
118 |
+
y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%",
|
119 |
+
};
|
120 |
+
newBadge["text"]["gradient"] = {
|
121 |
+
...newBadge?.text?.gradient,
|
122 |
+
enabled: true,
|
123 |
+
colours: colors,
|
124 |
+
angle: angleCoords,
|
125 |
+
type: gradientType,
|
126 |
+
};
|
127 |
+
} else {
|
128 |
+
newBadge = {
|
129 |
+
...newBadge,
|
130 |
+
text: {
|
131 |
+
...newBadge?.text,
|
132 |
+
colour: c,
|
133 |
+
gradient: {
|
134 |
+
...newBadge?.text?.gradient,
|
135 |
+
enabled: false,
|
136 |
+
},
|
137 |
+
},
|
138 |
+
};
|
139 |
+
}
|
140 |
+
setBadge(newBadge);
|
141 |
+
}}
|
142 |
+
/>
|
143 |
+
</div>
|
144 |
+
</div>
|
145 |
+
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
146 |
+
<div className="lg:col-span-4 flex items-center justify-start gap-3">
|
147 |
+
<Label>Icon</Label>
|
148 |
+
<Switch
|
149 |
+
value={badge?.icon?.enabled}
|
150 |
+
onChange={(enabled: boolean) =>
|
151 |
+
setBadge({
|
152 |
+
...badge,
|
153 |
+
icon: {
|
154 |
+
...badge.icon,
|
155 |
+
position: badge?.icon?.position ?? "left",
|
156 |
+
enabled,
|
157 |
+
},
|
158 |
+
})
|
159 |
+
}
|
160 |
+
/>
|
161 |
+
</div>
|
162 |
+
<div>
|
163 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
164 |
+
<FormattedMessage id="Color" />
|
165 |
+
</p>
|
166 |
+
<ColorPicker
|
167 |
+
data={badge?.icon}
|
168 |
+
full
|
169 |
+
className="h-[38px]"
|
170 |
+
value={badge?.icon?.stringColor ?? badge?.icon?.colour}
|
171 |
+
gradients={false}
|
172 |
+
onChange={(c: any, datas) => {
|
173 |
+
setBadge({
|
174 |
+
...badge,
|
175 |
+
icon: {
|
176 |
+
...badge?.icon,
|
177 |
+
position: badge?.icon?.position ?? "left",
|
178 |
+
colour: c,
|
179 |
+
stringColor: c,
|
180 |
+
},
|
181 |
+
});
|
182 |
+
}}
|
183 |
+
/>
|
184 |
+
</div>
|
185 |
+
<div className="flex items-start justify-start gap-4 lg:col-span-3">
|
186 |
+
<div>
|
187 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
188 |
+
<FormattedMessage id="Icon" />
|
189 |
+
</p>
|
190 |
+
<IconPicker
|
191 |
+
icon={badge?.icon?.component}
|
192 |
+
onSelect={(component) => {
|
193 |
+
setBadge({
|
194 |
+
...badge,
|
195 |
+
icon: {
|
196 |
+
...badge?.icon,
|
197 |
+
position: badge?.icon?.position ?? "left",
|
198 |
+
enabled: true,
|
199 |
+
component,
|
200 |
+
},
|
201 |
+
});
|
202 |
+
}}
|
203 |
+
/>
|
204 |
+
</div>
|
205 |
+
<div className="w-full">
|
206 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
207 |
+
<FormattedMessage id="Position" />
|
208 |
+
</p>
|
209 |
+
<div className="flex items-center justify-between">
|
210 |
+
<div
|
211 |
+
className={classNames(
|
212 |
+
"w-full h-[40px] text-sm p-2 text-dark-100 rounded-l-lg border-l border-t border-b border-dark-300 text-center font-semibold cursor-pointer",
|
213 |
+
{
|
214 |
+
"bg-green !text-white bg-opacity-30 border-green":
|
215 |
+
badge?.icon?.position === "left",
|
216 |
+
}
|
217 |
+
)}
|
218 |
+
onClick={() =>
|
219 |
+
setBadge({
|
220 |
+
...badge,
|
221 |
+
icon: { ...badge?.icon, position: "left" },
|
222 |
+
})
|
223 |
+
}
|
224 |
+
>
|
225 |
+
Left
|
226 |
+
</div>
|
227 |
+
<div
|
228 |
+
className={classNames(
|
229 |
+
"w-full h-[40px] text-sm p-2 text-dark-100 border-t border-b border-dark-300 text-center font-semibold cursor-pointer",
|
230 |
+
{
|
231 |
+
"bg-green !text-white bg-opacity-30 border-green":
|
232 |
+
badge?.icon?.position === "right",
|
233 |
+
}
|
234 |
+
)}
|
235 |
+
onClick={() =>
|
236 |
+
setBadge({
|
237 |
+
...badge,
|
238 |
+
icon: { ...badge?.icon, position: "right" },
|
239 |
+
})
|
240 |
+
}
|
241 |
+
>
|
242 |
+
Right
|
243 |
+
</div>
|
244 |
+
<div
|
245 |
+
className={classNames(
|
246 |
+
"w-full h-[40px] text-sm p-2 text-dark-100 rounded-r-lg border-t border-r border-b border-dark-300 text-center font-semibold cursor-pointer",
|
247 |
+
{
|
248 |
+
"bg-green !text-white bg-opacity-30 border-green":
|
249 |
+
badge?.icon?.position === "left-right",
|
250 |
+
}
|
251 |
+
)}
|
252 |
+
onClick={() =>
|
253 |
+
setBadge({
|
254 |
+
...badge,
|
255 |
+
icon: { ...badge?.icon, position: "left-right" },
|
256 |
+
})
|
257 |
+
}
|
258 |
+
>
|
259 |
+
Left & Right
|
260 |
+
</div>
|
261 |
+
</div>
|
262 |
+
</div>
|
263 |
+
</div>
|
264 |
+
</div>
|
265 |
+
</div>
|
266 |
+
);
|
267 |
+
};
|
components/editor-badge/comps/header/index.tsx
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Image from "next/image";
|
2 |
+
|
3 |
+
import { Tag } from "components/tag";
|
4 |
+
import { IconsSlider } from "./comps/icons-slider";
|
5 |
+
|
6 |
+
// import { Editor } from "components/editor";
|
7 |
+
// import { ArrayOfIcons } from "components/svg/icons";
|
8 |
+
|
9 |
+
// import { Navigation } from "components/navigation";
|
10 |
+
|
11 |
+
import Background from "assets/images/header/background.svg";
|
12 |
+
import Stars from "assets/images/header/stars.svg";
|
13 |
+
import Ananas from "assets/images/header/ananas.svg";
|
14 |
+
import Pizza from "assets/images/header/pizza.svg";
|
15 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
16 |
+
|
17 |
+
export const Header = ({ children }: any) => {
|
18 |
+
const intl = useIntl();
|
19 |
+
return (
|
20 |
+
<header className="bg-white relative z-20">
|
21 |
+
<div className="z-1 px-6 pt-28">
|
22 |
+
<div className="container mx-auto relative">
|
23 |
+
<div className="max-w-[1051px] w-full mx-auto text-center">
|
24 |
+
<Tag
|
25 |
+
label={`${intl.formatMessage({ id: "badge.new" })} 🔥`}
|
26 |
+
className="mb-5"
|
27 |
+
>
|
28 |
+
NEW FEATURE AVAILABLE
|
29 |
+
</Tag>
|
30 |
+
<IconsSlider />
|
31 |
+
<div className="max-w-max relative mx-auto md:px-12 pt-3 z-1 mt-2">
|
32 |
+
<h1 className="text-white font-title font-black text-4xl md:text-[70px] !leading-none text-center">
|
33 |
+
<FormattedMessage
|
34 |
+
id="badgeEditor.header.title"
|
35 |
+
values={{ span: () => <br /> }}
|
36 |
+
/>{" "}
|
37 |
+
✨
|
38 |
+
</h1>
|
39 |
+
<Image
|
40 |
+
width={32}
|
41 |
+
height={32}
|
42 |
+
src={Stars}
|
43 |
+
className="absolute top-0 left-0 w-full pointer-events-none"
|
44 |
+
alt="Stars on title"
|
45 |
+
/>
|
46 |
+
</div>
|
47 |
+
<h2 className="text-white font-medium text-center mt-5 text-lg md:text-[26px] tracking-wide">
|
48 |
+
<FormattedMessage id="badgeEditor.header.subtitle" />
|
49 |
+
</h2>
|
50 |
+
<p className="text-white text-opacity-70 text-sm mt-2">
|
51 |
+
<FormattedMessage id="footer.legal2" />
|
52 |
+
</p>
|
53 |
+
</div>
|
54 |
+
{children}
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
<div className="absolute top-0 left-0 h-full max-h-[875px] bg-blue -z-1 w-full">
|
58 |
+
<Image
|
59 |
+
width={32}
|
60 |
+
height={32}
|
61 |
+
alt="Pizza with sauce"
|
62 |
+
src={Pizza}
|
63 |
+
className="w-[90px] lg:w-[120px] opacity-60 lg:opacity-100 absolute select-none left-10 lg:left-20 top-32 lg:top-1/3 pointer-events-none block"
|
64 |
+
/>
|
65 |
+
<Image
|
66 |
+
width={32}
|
67 |
+
height={32}
|
68 |
+
alt="Ananas party!"
|
69 |
+
src={Ananas}
|
70 |
+
className="w-[150px] absolute select-none bottom-20 right-16 pointer-events-none hidden lg:block"
|
71 |
+
/>
|
72 |
+
<Image
|
73 |
+
width={32}
|
74 |
+
height={32}
|
75 |
+
alt="Background header cloud"
|
76 |
+
src={Background}
|
77 |
+
className="w-full absolute select-none bottom-0 left-0 pointer-events-none -z-1 opacity-50"
|
78 |
+
/>
|
79 |
+
</div>
|
80 |
+
{/* <div className="bg-blue h-full w-full absolute top-0 left-0 -z-1">
|
81 |
+
</div> */}
|
82 |
+
</header>
|
83 |
+
);
|
84 |
+
};
|
components/editor-badge/comps/preview.tsx
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useMount, useUpdateEffect } from "react-use";
|
2 |
+
import { domToPng } from "modern-screenshot";
|
3 |
+
|
4 |
+
import { BadgeType } from "@/types/badge";
|
5 |
+
import { useState } from "react";
|
6 |
+
|
7 |
+
export const Preview = ({ badge }: { badge: BadgeType }) => {
|
8 |
+
const [badgeUrl, setBadgeUrl] = useState("");
|
9 |
+
|
10 |
+
useUpdateEffect(() => {
|
11 |
+
setTimeout(() => {
|
12 |
+
domToPng(
|
13 |
+
document.getElementById("discotools-selected-badge") as HTMLElement,
|
14 |
+
{
|
15 |
+
scale: 2,
|
16 |
+
}
|
17 |
+
).then(async (dataUrl) => {
|
18 |
+
setBadgeUrl(dataUrl);
|
19 |
+
});
|
20 |
+
}, 190);
|
21 |
+
}, [badge]);
|
22 |
+
|
23 |
+
useMount(() => {
|
24 |
+
setTimeout(() => {
|
25 |
+
domToPng(
|
26 |
+
document.getElementById("discotools-selected-badge") as HTMLElement,
|
27 |
+
{
|
28 |
+
scale: 2,
|
29 |
+
}
|
30 |
+
).then(async (dataUrl) => {
|
31 |
+
setBadgeUrl(dataUrl);
|
32 |
+
});
|
33 |
+
}, 100);
|
34 |
+
});
|
35 |
+
|
36 |
+
return (
|
37 |
+
<div>{badgeUrl && <img src={badgeUrl} className="h-[19.25px]" />}</div>
|
38 |
+
);
|
39 |
+
};
|
components/editor-badge/comps/select-shapes.tsx
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
3 |
+
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
4 |
+
|
5 |
+
import { Label } from "@/components/label";
|
6 |
+
import { BADGE_COMPONENTS } from "@/components/svg/badges";
|
7 |
+
import { BadgeType } from "@/types/badge";
|
8 |
+
import { useUser } from "@/utils/auth";
|
9 |
+
import { PremiumContext } from "@/components/premium/premium";
|
10 |
+
import { useContext } from "react";
|
11 |
+
import { Premium } from "@/components/premium";
|
12 |
+
|
13 |
+
export const SelectShapes = ({
|
14 |
+
badge,
|
15 |
+
onChange,
|
16 |
+
}: {
|
17 |
+
badge: BadgeType;
|
18 |
+
onChange: (b: BadgeType) => void;
|
19 |
+
}) => {
|
20 |
+
const { user } = useUser();
|
21 |
+
const { setOpen } = useContext(PremiumContext);
|
22 |
+
return (
|
23 |
+
<div>
|
24 |
+
<Label className="mb-2">Shapes</Label>
|
25 |
+
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
|
26 |
+
{BADGE_COMPONENTS.map((component, i) => {
|
27 |
+
const Component = component.mini as any;
|
28 |
+
return (
|
29 |
+
<div
|
30 |
+
key={i}
|
31 |
+
className={classNames(
|
32 |
+
"flex items-center justify-start gap-2 cursor-pointer",
|
33 |
+
{
|
34 |
+
"opacity-50 relative": component.isPremium && !user,
|
35 |
+
}
|
36 |
+
)}
|
37 |
+
onClick={
|
38 |
+
component.isPremium && !user
|
39 |
+
? () => setOpen(true)
|
40 |
+
: () =>
|
41 |
+
onChange({
|
42 |
+
...badge,
|
43 |
+
type:
|
44 |
+
component?.name === badge?.type
|
45 |
+
? "circle"
|
46 |
+
: component.name,
|
47 |
+
})
|
48 |
+
}
|
49 |
+
>
|
50 |
+
<div
|
51 |
+
className={classNames(
|
52 |
+
"w-5 h-5 min-w-[1.25rem] border-2 rounded flex items-center justify-center",
|
53 |
+
{
|
54 |
+
"bg-green bg-opacity-60 border-green":
|
55 |
+
badge?.type === component.name,
|
56 |
+
"border-dark-200": badge?.type !== component.name,
|
57 |
+
}
|
58 |
+
)}
|
59 |
+
>
|
60 |
+
{badge?.type === component.name && (
|
61 |
+
<FontAwesomeIcon
|
62 |
+
icon={faCheck}
|
63 |
+
className="w-3 text-white text-opacity-100"
|
64 |
+
/>
|
65 |
+
)}
|
66 |
+
</div>
|
67 |
+
<Component />
|
68 |
+
</div>
|
69 |
+
);
|
70 |
+
})}
|
71 |
+
</div>
|
72 |
+
</div>
|
73 |
+
);
|
74 |
+
};
|
components/editor-badge/comps/shine_effect.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export const ShineEffect = ({
|
2 |
+
size = 8,
|
3 |
+
className,
|
4 |
+
}: {
|
5 |
+
size?: number;
|
6 |
+
className?: string;
|
7 |
+
}) => {
|
8 |
+
return (
|
9 |
+
<svg
|
10 |
+
width={size}
|
11 |
+
height={size}
|
12 |
+
viewBox="0 0 5 5"
|
13 |
+
version="1.1"
|
14 |
+
className={className}
|
15 |
+
xmlns="http://www.w3.org/2000/svg"
|
16 |
+
>
|
17 |
+
<g
|
18 |
+
id="Page-1"
|
19 |
+
stroke="none"
|
20 |
+
stroke-width="1"
|
21 |
+
fill="none"
|
22 |
+
fill-rule="evenodd"
|
23 |
+
>
|
24 |
+
<g
|
25 |
+
id="Group"
|
26 |
+
transform="translate(-102.000000, 0.000000)"
|
27 |
+
fill="#FFFFFF"
|
28 |
+
fill-rule="nonzero"
|
29 |
+
>
|
30 |
+
<polygon
|
31 |
+
id="Shape"
|
32 |
+
points="104.5 0 105.384 1.616 107 2.5 105.384 3.384 104.5 5 103.616 3.384 102 2.5 103.616 1.616"
|
33 |
+
></polygon>
|
34 |
+
</g>
|
35 |
+
</g>
|
36 |
+
</svg>
|
37 |
+
);
|
38 |
+
};
|
components/editor-badge/comps/tabs.tsx
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import classNames from "classnames";
|
3 |
+
|
4 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
5 |
+
import { faShapes, faAnglesRight } from "@fortawesome/free-solid-svg-icons";
|
6 |
+
import { FormattedMessage } from "react-intl";
|
7 |
+
|
8 |
+
const TABS_ELEMENTS = [
|
9 |
+
{
|
10 |
+
icon: faShapes,
|
11 |
+
name: "badgeEditor.tabs.main",
|
12 |
+
},
|
13 |
+
{
|
14 |
+
icon: faAnglesRight,
|
15 |
+
name: "badgeEditor.tabs.advanced",
|
16 |
+
},
|
17 |
+
];
|
18 |
+
|
19 |
+
export const EditorTabs = ({
|
20 |
+
current,
|
21 |
+
onChange,
|
22 |
+
}: {
|
23 |
+
current: number;
|
24 |
+
onChange: (e: number) => void;
|
25 |
+
}) => {
|
26 |
+
return (
|
27 |
+
// p-2 lg:p-4
|
28 |
+
<div className="bg-dark-600 rounded-t-2xl">
|
29 |
+
<ul className="w-full relative z-1 grid grid-cols-2">
|
30 |
+
{TABS_ELEMENTS.map((tab, index) => (
|
31 |
+
<li
|
32 |
+
key={index}
|
33 |
+
className={classNames(
|
34 |
+
"hover:bg-blue hover:bg-opacity-50 flex items-center gap-5 lg:gap-4 cursor-pointer text-white text-left text-xs lg:text-sm font-semibold tracking-wide p-4 lg:p-5 justify-center uppercase",
|
35 |
+
{
|
36 |
+
"bg-dark-500 hover:!bg-dark-500 hover:!bg-opacity-100":
|
37 |
+
current === index,
|
38 |
+
"first-step rounded-t-2xl rounded-r-none": index === 0,
|
39 |
+
"second-step rounded-t-2xl rounded-l-none": index === 1,
|
40 |
+
}
|
41 |
+
)}
|
42 |
+
onClick={() => onChange(index)}
|
43 |
+
>
|
44 |
+
<FontAwesomeIcon icon={tab.icon} className="w-4 min-w-[1rem]" />
|
45 |
+
<p>
|
46 |
+
<FormattedMessage id={tab.name} />
|
47 |
+
</p>
|
48 |
+
</li>
|
49 |
+
))}
|
50 |
+
</ul>
|
51 |
+
</div>
|
52 |
+
);
|
53 |
+
};
|
components/editor-badge/comps/user_card.tsx
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useMemo } from "react";
|
2 |
+
|
3 |
+
import { BadgeType } from "@/types/badge";
|
4 |
+
import { useUser } from "@/utils/auth";
|
5 |
+
import DefaultAvatar from "assets/images/avatars/default-avatar.svg";
|
6 |
+
import { Moderator } from "@/components/svg/icons/discord";
|
7 |
+
import { Boost2 } from "@/components/svg/icons/discord";
|
8 |
+
import { Preview } from "./preview";
|
9 |
+
import { DownloadButton } from "./download_button";
|
10 |
+
|
11 |
+
export const UserCard = ({
|
12 |
+
badge,
|
13 |
+
onSave,
|
14 |
+
}: {
|
15 |
+
badge: BadgeType;
|
16 |
+
onSave: (e?: boolean) => void;
|
17 |
+
}) => {
|
18 |
+
const { user, loading } = useUser();
|
19 |
+
|
20 |
+
const convertNumberToStringColor = (color: number) => {
|
21 |
+
if (!color) return "#121212";
|
22 |
+
return "#" + color?.toString(16);
|
23 |
+
};
|
24 |
+
|
25 |
+
const bannerStyle = useMemo(() => {
|
26 |
+
let style: any = { backgroundColor: "#121212", height: 60 };
|
27 |
+
|
28 |
+
if (user?.id) {
|
29 |
+
if (user?.banner) {
|
30 |
+
style.backgroundImage = `url(https://cdn.discordapp.com/banners/${user.id}/${user.banner}.gif?size=1024)`;
|
31 |
+
style.height = 120;
|
32 |
+
style.borderLeft = "5px solid #232429";
|
33 |
+
style.borderTop = "5px solid #232429";
|
34 |
+
style.borderRight = "5px solid #232429";
|
35 |
+
} else {
|
36 |
+
// style.backgroundColor = convertNumberToStringColor(user?.accent_color);
|
37 |
+
style.backgroundColor = user?.accent_color;
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
return style;
|
42 |
+
}, [user]);
|
43 |
+
|
44 |
+
return (
|
45 |
+
<div className="bg-dark-500 rounded-lg w-full overflow-hidden">
|
46 |
+
<header
|
47 |
+
className={`w-full rounded-t-lg bg-cover bg-center bg-no-repeat p-6 relative ${
|
48 |
+
loading && "animate-pulse"
|
49 |
+
}`}
|
50 |
+
style={bannerStyle}
|
51 |
+
>
|
52 |
+
<figure
|
53 |
+
style={{
|
54 |
+
backgroundImage: `url(${
|
55 |
+
user?.id
|
56 |
+
? user.avatar
|
57 |
+
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
|
58 |
+
: `https://cdn.discordapp.com/embed/avatars/${
|
59 |
+
user?.id % 5
|
60 |
+
}.png`
|
61 |
+
: DefaultAvatar.src
|
62 |
+
})`,
|
63 |
+
}}
|
64 |
+
className="w-[80px] h-[80px] rounded-full ring-[6px] ring-dark-500 absolute bottom-0 left-5 translate-y-1/2 bg-cover bg-center"
|
65 |
+
>
|
66 |
+
<div className="bg-[#22A55A] w-[16px] h-[16px] rounded-full absolute bottom-1 right-1 ring-[6px] ring-dark-500"></div>
|
67 |
+
</figure>
|
68 |
+
</header>
|
69 |
+
<main className="px-5 pb-5 pt-4 flex flex-col gap-4 items-end">
|
70 |
+
<div className="rounded-md bg-[#111214] p-1.5 flex items-center justify-end gap-2">
|
71 |
+
<svg
|
72 |
+
width={16}
|
73 |
+
height={16}
|
74 |
+
viewBox="0 0 160 189"
|
75 |
+
fill="#FFAC32"
|
76 |
+
style={{ minWidth: 16, minHeight: 16 }}
|
77 |
+
>
|
78 |
+
<Moderator />
|
79 |
+
</svg>
|
80 |
+
<svg
|
81 |
+
width={16}
|
82 |
+
height={16}
|
83 |
+
viewBox="0 0 170 144"
|
84 |
+
fill="#FF73FA"
|
85 |
+
style={{ minWidth: 16, minHeight: 16 }}
|
86 |
+
>
|
87 |
+
<Boost2 />
|
88 |
+
</svg>
|
89 |
+
</div>
|
90 |
+
<div className="bg-[#111214] rounded-lg px-3 pt-3 pb-4 w-full">
|
91 |
+
<p className="text-white font-bold text-xl">
|
92 |
+
{user ? user?.username : "Captain Astro"}
|
93 |
+
</p>
|
94 |
+
<p className="text-white text-sm">
|
95 |
+
{user ? `${user?.username}` : "captainastro"}
|
96 |
+
</p>
|
97 |
+
<div className="w-full h-[1px] bg-[#2E2F35] my-3" />
|
98 |
+
<p className="text-xs uppercase font-bold text-white mb-2">
|
99 |
+
About me
|
100 |
+
</p>
|
101 |
+
<Preview badge={badge} />
|
102 |
+
{/* <p className="text-xs uppercase font-bold text-white mt-3 mb-2">
|
103 |
+
Member since
|
104 |
+
</p> */}
|
105 |
+
<DownloadButton onSave={onSave} />
|
106 |
+
<p className="text-xs text-dark-200 mt-2">
|
107 |
+
Image will be split into multiple parts. You will upload them as
|
108 |
+
server emojis on Discord to use them on your discord Profile.
|
109 |
+
</p>
|
110 |
+
</div>
|
111 |
+
</main>
|
112 |
+
</div>
|
113 |
+
);
|
114 |
+
};
|
components/editor-badge/index.tsx
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useRef } from "react";
|
2 |
+
import { domToPng } from "modern-screenshot";
|
3 |
+
import ImageToPieces from "image-to-pieces";
|
4 |
+
import JSZip from "jszip";
|
5 |
+
import download from "downloadjs";
|
6 |
+
|
7 |
+
import {
|
8 |
+
DEFAULT_VALUE,
|
9 |
+
RATIONAL_BADGE_WIDTH,
|
10 |
+
} from "components/editor-badge/badge-editor.constants";
|
11 |
+
import BackgroundTransparent from "assets/images/editor/transparent_bg.svg";
|
12 |
+
import { API } from "@/utils/api";
|
13 |
+
import { useUser } from "@/utils/auth";
|
14 |
+
|
15 |
+
import { MainForm } from "./comps/header/form/main";
|
16 |
+
import { AdvancedForm } from "./comps/header/form/advanced";
|
17 |
+
import { Badge } from "./comps/badge";
|
18 |
+
import { EditorTabs } from "./comps/tabs";
|
19 |
+
import { UserCard } from "./comps/user_card";
|
20 |
+
|
21 |
+
export default function Editor() {
|
22 |
+
const { user } = useUser();
|
23 |
+
const [badge, setBadge] = useState({
|
24 |
+
...DEFAULT_VALUE,
|
25 |
+
});
|
26 |
+
|
27 |
+
const badgeRef = useRef<any>(null);
|
28 |
+
const [current, setCurrent] = useState(0);
|
29 |
+
|
30 |
+
const handleSave = (hasImage = false) => {
|
31 |
+
domToPng(
|
32 |
+
document.getElementById("discotools-selected-badge") as HTMLElement,
|
33 |
+
{
|
34 |
+
scale: 2,
|
35 |
+
}
|
36 |
+
).then(async (dataUrl) => {
|
37 |
+
if (hasImage) {
|
38 |
+
download(dataUrl, "badge.png", "image/png");
|
39 |
+
// API.downloadBadge();
|
40 |
+
return;
|
41 |
+
}
|
42 |
+
|
43 |
+
if (!badgeRef?.current) return;
|
44 |
+
const badgeWidth = badgeRef.current.getBoundingClientRect().width;
|
45 |
+
const image = new ImageToPieces(
|
46 |
+
null,
|
47 |
+
badgeWidth / RATIONAL_BADGE_WIDTH,
|
48 |
+
1
|
49 |
+
);
|
50 |
+
await image.loadImageByUrl(dataUrl);
|
51 |
+
const tiles = image
|
52 |
+
.getTiles()
|
53 |
+
?.map((tile: any) => tile.data?.replace("data:image/png;base64,", ""));
|
54 |
+
const zip_folder = new JSZip();
|
55 |
+
tiles.forEach((tile: any, i: number) =>
|
56 |
+
zip_folder.file(`${i}.png`, tile, { base64: true })
|
57 |
+
);
|
58 |
+
|
59 |
+
zip_folder.generateAsync({ type: "blob" }).then(function (content) {
|
60 |
+
download(content, "badges.zip");
|
61 |
+
// API.downloadBadge();
|
62 |
+
});
|
63 |
+
});
|
64 |
+
};
|
65 |
+
|
66 |
+
const renderTabs = () => {
|
67 |
+
switch (current) {
|
68 |
+
case 0:
|
69 |
+
return <MainForm badge={badge} setBadge={setBadge} />;
|
70 |
+
case 1:
|
71 |
+
return <AdvancedForm badge={badge} setBadge={setBadge} />;
|
72 |
+
}
|
73 |
+
};
|
74 |
+
|
75 |
+
return (
|
76 |
+
<div className="flex-col lg:flex-row flex max-w-5xl mx-auto gap-5 lg:gap-10 mt-10">
|
77 |
+
<div className="flex-1 bg-dark-500 shadow-lg rounded-2xl">
|
78 |
+
<EditorTabs current={current} onChange={setCurrent} />
|
79 |
+
{renderTabs()}
|
80 |
+
</div>
|
81 |
+
<div className="flex items-start flex-col gap-5 lg:gap-10 lg:max-w-[375px] w-full">
|
82 |
+
<header
|
83 |
+
className="relative p-5 flex items-center justify-center bg-repeat h-[100px] w-full z-10 transition-all duration-200 first-step rounded-2xl"
|
84 |
+
style={{
|
85 |
+
backgroundImage: `url(${BackgroundTransparent.src})`,
|
86 |
+
}}
|
87 |
+
>
|
88 |
+
<Badge ref={badgeRef} badge={badge} />
|
89 |
+
</header>
|
90 |
+
<UserCard badge={badge} onSave={handleSave} />
|
91 |
+
</div>
|
92 |
+
</div>
|
93 |
+
);
|
94 |
+
}
|
components/editor-icons/comps/header/comps/icons-slider.tsx
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useRef, useState } from "react";
|
2 |
+
import { useInterval } from "react-use";
|
3 |
+
import { motion } from "framer-motion";
|
4 |
+
import Image from "next/image";
|
5 |
+
|
6 |
+
import { sleep } from "utils";
|
7 |
+
import HammerIcon from "assets/images/header/icons-slider/hammer.svg";
|
8 |
+
import ShieldIcon from "assets/images/header/icons-slider/shield.svg";
|
9 |
+
import CrownIcon from "assets/images/header/icons-slider/crown.svg";
|
10 |
+
import PartnerIcon from "assets/images/header/icons-slider/partner.svg";
|
11 |
+
import StarIcon from "assets/images/header/icons-slider/star.svg";
|
12 |
+
import LeafIcon from "assets/images/header/icons-slider/leaf.svg";
|
13 |
+
|
14 |
+
const ICONS = [
|
15 |
+
HammerIcon,
|
16 |
+
ShieldIcon,
|
17 |
+
CrownIcon,
|
18 |
+
PartnerIcon,
|
19 |
+
StarIcon,
|
20 |
+
LeafIcon,
|
21 |
+
];
|
22 |
+
|
23 |
+
const icon = {
|
24 |
+
hidden: {
|
25 |
+
opacity: 0,
|
26 |
+
pathLength: 0,
|
27 |
+
fill: "rgba(255, 255, 255, 0)",
|
28 |
+
},
|
29 |
+
visible: {
|
30 |
+
opacity: 1,
|
31 |
+
pathLength: 1,
|
32 |
+
fill: "rgba(255, 255, 255, 1)",
|
33 |
+
},
|
34 |
+
};
|
35 |
+
|
36 |
+
export const IconsSlider = () => {
|
37 |
+
const [selected, setSelected] = useState(0);
|
38 |
+
const ref = useRef<HTMLDivElement>(null);
|
39 |
+
|
40 |
+
useInterval(async () => {
|
41 |
+
if (ref?.current) {
|
42 |
+
ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50");
|
43 |
+
await sleep(300);
|
44 |
+
ref.current?.classList?.remove("-translate-x-full");
|
45 |
+
ref.current?.classList?.add("translate-x-full");
|
46 |
+
|
47 |
+
setSelected((prev) => (prev + 1) % ICONS.length);
|
48 |
+
// if (this.selected >= this.avatars.length - 1) {
|
49 |
+
// this.selected = 0;
|
50 |
+
// } else {
|
51 |
+
// this.selected += 1;
|
52 |
+
// }
|
53 |
+
await sleep(300);
|
54 |
+
ref.current?.classList?.remove(
|
55 |
+
"translate-x-full",
|
56 |
+
"scale-50",
|
57 |
+
"opacity-0"
|
58 |
+
);
|
59 |
+
}
|
60 |
+
}, 2000);
|
61 |
+
|
62 |
+
return (
|
63 |
+
<div className="w-32 h-32 mx-auto relative z-1 flex items-center justify-center mt-7">
|
64 |
+
{/* <Image
|
65 |
+
src={ShapeIcon}
|
66 |
+
width={32}
|
67 |
+
height={32}
|
68 |
+
alt="Animation shape"
|
69 |
+
className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility"
|
70 |
+
/> */}
|
71 |
+
|
72 |
+
<motion.svg
|
73 |
+
viewBox="-1 0 130 124"
|
74 |
+
xmlns="http://www.w3.org/2000/svg"
|
75 |
+
className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility stroke-white stroke-2"
|
76 |
+
>
|
77 |
+
<motion.path
|
78 |
+
d="M115.634914,44.3288323 C113.80147,38.3166291 119.301802,28.5951133 115.634914,23.6490789 C111.968027,18.7030445 100.924726,20.8775941 95.9360531,17.2533448 C90.9900187,13.6717337 89.4550426,2.15943794 83.6988819,0.581823534 C77.9427212,-1.0384291 70.3531168,6.97757468 63.9573827,6.97757468 C57.5616486,6.97757468 50.1852353,-1.89119365 44.2158834,0.368631971 C38.2038508,2.62845844 37.0099804,13.7143719 31.9786704,17.295983 C26.9473588,20.9202324 15.6482285,19.0015121 12.1518948,23.6917171 C8.65555928,28.3819221 14.070615,38.7003731 12.1518948,44.3714705 C10.2331745,50.0423548 0,54.7747716 0,61.042591 C0,67.3530486 10.4890031,71.744786 12.3224468,77.7141379 C14.1558906,83.6834897 8.65555928,93.4476437 12.3224468,98.3936781 C16.0319726,103.339712 27.0326352,101.165163 32.0213078,104.789412 C36.9673422,108.371023 38.5875957,119.883771 44.3437981,121.461385 C50.0999588,123.039 57.6895633,115.065651 64.0852973,115.065651 C70.4810314,115.065651 77.8574448,123.465382 83.8267966,121.461385 C89.8387866,119.457389 91.2032099,108.328385 96.1492442,104.789412 C101.095279,101.165163 112.309132,103.083883 115.848105,98.3936781 C119.34444,93.7034731 113.929385,83.3850221 115.848105,77.7141379 C117.766825,72.0432536 128,67.3104104 128,61.042591 C127.914724,54.7321334 117.468358,50.2981841 115.634914,44.3288323 Z"
|
79 |
+
variants={icon}
|
80 |
+
initial="hidden"
|
81 |
+
animate="visible"
|
82 |
+
transition={{
|
83 |
+
default: { duration: 2, ease: "easeInOut" },
|
84 |
+
fill: { duration: 2, ease: [1, 0, 0.8, 1] },
|
85 |
+
}}
|
86 |
+
/>
|
87 |
+
</motion.svg>
|
88 |
+
<div
|
89 |
+
ref={ref}
|
90 |
+
className="transform transition duration-300 w-[68px] h-[60px] filter drop-shadow-xl origin-center"
|
91 |
+
>
|
92 |
+
<Image
|
93 |
+
src={ICONS[selected]}
|
94 |
+
width={32}
|
95 |
+
height={32}
|
96 |
+
alt="Animation icon, change every 3 seconds"
|
97 |
+
className="w-full h-full"
|
98 |
+
draggable="false"
|
99 |
+
/>
|
100 |
+
</div>
|
101 |
+
<p></p>
|
102 |
+
</div>
|
103 |
+
// <div class="w-64 h-64 mx-auto relative z-1 flex items-center justify-center">
|
104 |
+
// <img src="@/assets/images/shadow_gradient.svg" alt="Linear gradient behind avatar" width="86" height="86" draggable="false" class="h-full absolute left-0 top-0 w-full -z-1 backface-visibility">
|
105 |
+
// <Avatar />
|
106 |
+
// </div>
|
107 |
+
);
|
108 |
+
};
|
components/editor-icons/comps/header/index.tsx
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Image from "next/image";
|
2 |
+
|
3 |
+
import { Tag } from "components/tag";
|
4 |
+
import { IconsSlider } from "./comps/icons-slider";
|
5 |
+
|
6 |
+
// import { Editor } from "components/editor";
|
7 |
+
// import { ArrayOfIcons } from "components/svg/icons";
|
8 |
+
|
9 |
+
// import { Navigation } from "components/navigation";
|
10 |
+
|
11 |
+
import Background from "assets/images/header/background.svg";
|
12 |
+
import Stars from "assets/images/header/stars.svg";
|
13 |
+
import Ananas from "assets/images/header/ananas.svg";
|
14 |
+
import Pizza from "assets/images/header/pizza.svg";
|
15 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
16 |
+
|
17 |
+
export const Header = ({ children }: any) => {
|
18 |
+
const intl = useIntl();
|
19 |
+
return (
|
20 |
+
<header className="bg-white relative z-20">
|
21 |
+
<div className="z-1 px-6 pt-28">
|
22 |
+
<div className="container mx-auto relative">
|
23 |
+
<div className="max-w-[1051px] w-full mx-auto text-center">
|
24 |
+
<Tag
|
25 |
+
label={`${intl.formatMessage({ id: "badge.new" })} 🔥`}
|
26 |
+
className="mb-5"
|
27 |
+
>
|
28 |
+
NEW VERSION RELEASED!
|
29 |
+
</Tag>
|
30 |
+
<IconsSlider />
|
31 |
+
<div className="max-w-max relative mx-auto md:px-12 pt-3 z-1 mt-4">
|
32 |
+
<h1 className="text-white font-title font-black text-4xl md:text-[70px] !leading-none text-center">
|
33 |
+
<FormattedMessage
|
34 |
+
id="iconsEditor.header.title"
|
35 |
+
values={{ span: () => <br /> }}
|
36 |
+
/>{" "}
|
37 |
+
✨
|
38 |
+
</h1>
|
39 |
+
<Image
|
40 |
+
width={32}
|
41 |
+
height={32}
|
42 |
+
src={Stars}
|
43 |
+
className="absolute top-0 left-0 w-full pointer-events-none"
|
44 |
+
alt="Stars on title"
|
45 |
+
/>
|
46 |
+
</div>
|
47 |
+
<h2 className="text-white font-medium text-center mt-5 text-lg md:text-[26px] tracking-wide">
|
48 |
+
<FormattedMessage id="iconsEditor.header.subtitle" />
|
49 |
+
</h2>
|
50 |
+
<p className="text-white text-opacity-70 text-sm mt-2">
|
51 |
+
<FormattedMessage id="footer.legal2" />
|
52 |
+
</p>
|
53 |
+
</div>
|
54 |
+
{children}
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
<div className="absolute top-0 left-0 h-full max-h-[875px] bg-blue -z-1 w-full">
|
58 |
+
<Image
|
59 |
+
width={32}
|
60 |
+
height={32}
|
61 |
+
alt="Pizza with sauce"
|
62 |
+
src={Pizza}
|
63 |
+
className="w-[90px] lg:w-[120px] opacity-60 lg:opacity-100 absolute select-none left-10 lg:left-20 top-32 lg:top-1/3 pointer-events-none block"
|
64 |
+
/>
|
65 |
+
<Image
|
66 |
+
width={32}
|
67 |
+
height={32}
|
68 |
+
alt="Ananas party!"
|
69 |
+
src={Ananas}
|
70 |
+
className="w-[150px] absolute select-none bottom-20 right-16 pointer-events-none hidden lg:block"
|
71 |
+
/>
|
72 |
+
<Image
|
73 |
+
width={32}
|
74 |
+
height={32}
|
75 |
+
alt="Background header cloud"
|
76 |
+
src={Background}
|
77 |
+
className="w-full absolute select-none bottom-0 left-0 pointer-events-none -z-1 opacity-50"
|
78 |
+
/>
|
79 |
+
</div>
|
80 |
+
{/* <div className="bg-blue h-full w-full absolute top-0 left-0 -z-1">
|
81 |
+
</div> */}
|
82 |
+
</header>
|
83 |
+
);
|
84 |
+
};
|
components/editor-icons/comps/icons/icon-selected.tsx
ADDED
@@ -0,0 +1,603 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import {
|
3 |
+
faChevronDown,
|
4 |
+
faChevronRight,
|
5 |
+
faTrash,
|
6 |
+
faCaretDown,
|
7 |
+
faCaretUp,
|
8 |
+
} from "@fortawesome/free-solid-svg-icons";
|
9 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
10 |
+
|
11 |
+
import { ListItem } from "components/editor-icons/comps/list/list-item";
|
12 |
+
import {
|
13 |
+
Icons as ICONS,
|
14 |
+
IconCustomIcon,
|
15 |
+
IconCustomText,
|
16 |
+
} from "components/svg/icons";
|
17 |
+
import { IconItem, IconType } from "@/types/editor";
|
18 |
+
import { ColorPicker } from "components/color-picker";
|
19 |
+
import { Range } from "components/range";
|
20 |
+
import { Input } from "components/input";
|
21 |
+
import { Switch } from "@/components/switch";
|
22 |
+
import { PremiumOverlay } from "@/components/premium/overlay";
|
23 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
24 |
+
import { Label } from "@/components/label";
|
25 |
+
|
26 |
+
export const IconSelected = ({
|
27 |
+
index,
|
28 |
+
totalIcons,
|
29 |
+
icon,
|
30 |
+
current,
|
31 |
+
setCurrent,
|
32 |
+
onDelete,
|
33 |
+
onChange,
|
34 |
+
onChangeOrder,
|
35 |
+
}: {
|
36 |
+
index: number;
|
37 |
+
totalIcons: number;
|
38 |
+
icon: IconType;
|
39 |
+
current?: number | null;
|
40 |
+
setCurrent: (index: number | null) => void;
|
41 |
+
onDelete: (index: number) => void;
|
42 |
+
onChange: (idnex: number, icon: IconType) => void;
|
43 |
+
onChangeOrder: (index: number, value: number) => void;
|
44 |
+
}) => {
|
45 |
+
const findIcon: any = icon?.custom_text?.enabled
|
46 |
+
? IconCustomText
|
47 |
+
: icon?.image
|
48 |
+
? IconCustomIcon
|
49 |
+
: ICONS?.find((i: IconItem) => icon.component === i.name);
|
50 |
+
|
51 |
+
const handleChange = (index: number, icon: any) => {
|
52 |
+
onChange(index, icon);
|
53 |
+
};
|
54 |
+
|
55 |
+
const intl = useIntl();
|
56 |
+
|
57 |
+
return (
|
58 |
+
<div className="flex items-center justify-start gap-3 w-full">
|
59 |
+
<div
|
60 |
+
className={classNames(
|
61 |
+
"bg-dark-600 w-full rounded-lg pl-4 py-4 pr-6 group cursor-pointer transition-all border-2 border-dark-600 duration-200 hover:border-blue",
|
62 |
+
{
|
63 |
+
"!border-blue": current === index,
|
64 |
+
}
|
65 |
+
)}
|
66 |
+
>
|
67 |
+
<div
|
68 |
+
className="tracking-wider flex items-center justify-between gap-4"
|
69 |
+
onClick={() => setCurrent(current === index ? null : index)}
|
70 |
+
>
|
71 |
+
<div className="flex items-center justify-start gap-4">
|
72 |
+
<div className="w-10">
|
73 |
+
{icon?.image ? (
|
74 |
+
<div className="flex items-center justify-center p-2 cursor-pointer rounded-lg relative group border border-dark-200 border-solid">
|
75 |
+
<img
|
76 |
+
src={icon?.image}
|
77 |
+
className="w-full h-full object-contain"
|
78 |
+
/>
|
79 |
+
</div>
|
80 |
+
) : (
|
81 |
+
<ListItem
|
82 |
+
icon={findIcon}
|
83 |
+
fill={findIcon?.defaultColor ?? "#fff"}
|
84 |
+
tooltip={false}
|
85 |
+
hoverable={false}
|
86 |
+
onSelect={() => {}}
|
87 |
+
/>
|
88 |
+
)}
|
89 |
+
</div>
|
90 |
+
<div>
|
91 |
+
<p className="text-white font-semibold">
|
92 |
+
<FormattedMessage id={findIcon?.name} />
|
93 |
+
</p>
|
94 |
+
<p className="text-xs text-dark-200">
|
95 |
+
{findIcon?.tags?.join(", ")}
|
96 |
+
</p>
|
97 |
+
</div>
|
98 |
+
</div>
|
99 |
+
<div className="flex items-center justify-end gap-4">
|
100 |
+
<div
|
101 |
+
className="bg-danger bg-opacity-60 rounded-lg text-white hover:bg-opacity-100 cursor-pointer w-8 h-8 flex items-center justify-center"
|
102 |
+
onClick={(e: any) => {
|
103 |
+
e.preventDefault();
|
104 |
+
e.stopPropagation();
|
105 |
+
onDelete(index);
|
106 |
+
}}
|
107 |
+
>
|
108 |
+
<FontAwesomeIcon icon={faTrash} className="w-4" />
|
109 |
+
</div>
|
110 |
+
<div className="flex items-center justify-end">
|
111 |
+
<FontAwesomeIcon
|
112 |
+
icon={current === index ? faChevronDown : faChevronRight}
|
113 |
+
className="text-dark-100 transition-all duration-200 w-3"
|
114 |
+
/>
|
115 |
+
</div>
|
116 |
+
</div>
|
117 |
+
</div>
|
118 |
+
{current === index && (
|
119 |
+
<div className="border-t border-dark-400 pt-3 mt-4 grid grid-cols-2 gap-4">
|
120 |
+
<div className="col-span-2">
|
121 |
+
<Label className="mb-1">
|
122 |
+
<FormattedMessage id="iconsEditor.editor.customisation.positions" />
|
123 |
+
</Label>
|
124 |
+
<div className="grid grid-cols-2 gap-x-6 gap-y-3">
|
125 |
+
<div className="gap-4 w-full col-span-2 grid grid-cols-3">
|
126 |
+
<div className="flex items-center gap-3 col-span-2">
|
127 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">
|
128 |
+
X
|
129 |
+
</p>
|
130 |
+
<Range
|
131 |
+
value={icon?.position?.x ?? 0}
|
132 |
+
max={
|
133 |
+
icon?.custom_text?.enabled
|
134 |
+
? 1000
|
135 |
+
: icon?.position?.xPath ?? undefined
|
136 |
+
}
|
137 |
+
onChange={(value) => {
|
138 |
+
const newIcon = {
|
139 |
+
...icon,
|
140 |
+
position: { ...icon.position, x: Number(value) },
|
141 |
+
};
|
142 |
+
handleChange(index, newIcon);
|
143 |
+
}}
|
144 |
+
/>
|
145 |
+
</div>
|
146 |
+
<Input
|
147 |
+
value={icon?.position?.x}
|
148 |
+
type="number"
|
149 |
+
placeholder={intl.formatMessage({
|
150 |
+
id: "iconsEditor.editor.customisation.positions.x",
|
151 |
+
})}
|
152 |
+
className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
|
153 |
+
onChange={(newX) => {
|
154 |
+
let x: number = Number(newX);
|
155 |
+
if (x > 100) x = 100;
|
156 |
+
const newIcon = {
|
157 |
+
...icon,
|
158 |
+
position: { ...icon.position, x: x },
|
159 |
+
};
|
160 |
+
handleChange(index, newIcon);
|
161 |
+
}}
|
162 |
+
/>
|
163 |
+
</div>
|
164 |
+
<div className="gap-4 w-full col-span-2 grid grid-cols-3">
|
165 |
+
<div className="flex items-center gap-3 col-span-2">
|
166 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">
|
167 |
+
Y
|
168 |
+
</p>
|
169 |
+
<Range
|
170 |
+
value={icon?.position?.y ?? 0}
|
171 |
+
max={
|
172 |
+
icon?.custom_text?.enabled
|
173 |
+
? 1000
|
174 |
+
: icon?.position?.yPath ?? undefined
|
175 |
+
}
|
176 |
+
onChange={(value) => {
|
177 |
+
const newIcon = {
|
178 |
+
...icon,
|
179 |
+
position: { ...icon.position, y: Number(value) },
|
180 |
+
};
|
181 |
+
handleChange(index, newIcon);
|
182 |
+
}}
|
183 |
+
/>
|
184 |
+
</div>
|
185 |
+
<Input
|
186 |
+
value={icon?.position?.y}
|
187 |
+
type="number"
|
188 |
+
placeholder={intl.formatMessage({
|
189 |
+
id: "iconsEditor.editor.customisation.positions.y",
|
190 |
+
})}
|
191 |
+
className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
|
192 |
+
onChange={(newY) => {
|
193 |
+
let y = Number(newY);
|
194 |
+
if (y > 100) y = 100;
|
195 |
+
const newIcon = {
|
196 |
+
...icon,
|
197 |
+
position: { ...icon.position, y: y },
|
198 |
+
};
|
199 |
+
handleChange(index, newIcon);
|
200 |
+
}}
|
201 |
+
/>
|
202 |
+
</div>
|
203 |
+
<div className="gap-4 w-full col-span-2 grid grid-cols-3">
|
204 |
+
<div className="flex items-center gap-3 col-span-2">
|
205 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">
|
206 |
+
Z
|
207 |
+
</p>
|
208 |
+
<Range
|
209 |
+
value={icon?.position?.angle ?? 0}
|
210 |
+
max={360}
|
211 |
+
onChange={(value) => {
|
212 |
+
const newIcon = {
|
213 |
+
...icon,
|
214 |
+
position: { ...icon.position, angle: Number(value) },
|
215 |
+
};
|
216 |
+
handleChange(index, newIcon);
|
217 |
+
}}
|
218 |
+
/>
|
219 |
+
</div>
|
220 |
+
<Input
|
221 |
+
value={icon?.position?.angle}
|
222 |
+
placeholder={intl.formatMessage({
|
223 |
+
id: "iconsEditor.editor.customisation.angle",
|
224 |
+
})}
|
225 |
+
type="number"
|
226 |
+
className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
|
227 |
+
onChange={(newAngle) => {
|
228 |
+
let angle = Number(newAngle);
|
229 |
+
if (angle > 360) angle = 360;
|
230 |
+
const newIcon = {
|
231 |
+
...icon,
|
232 |
+
position: { ...icon.position, angle: angle },
|
233 |
+
};
|
234 |
+
handleChange(index, newIcon);
|
235 |
+
}}
|
236 |
+
/>
|
237 |
+
</div>
|
238 |
+
{!icon?.custom_text?.enabled && (
|
239 |
+
<div className="gap-4 w-full col-span-2 grid grid-cols-3">
|
240 |
+
<div className="flex items-center gap-3 col-span-2">
|
241 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">
|
242 |
+
<FormattedMessage id="iconsEditor.editor.customisation.scale" />
|
243 |
+
</p>
|
244 |
+
<Range
|
245 |
+
value={icon?.position?.scale ?? 1}
|
246 |
+
min={0}
|
247 |
+
max={2}
|
248 |
+
step={0.1}
|
249 |
+
onChange={(value) => {
|
250 |
+
const newIcon = {
|
251 |
+
...icon,
|
252 |
+
position: {
|
253 |
+
...icon.position,
|
254 |
+
scale: Number(value),
|
255 |
+
},
|
256 |
+
};
|
257 |
+
handleChange(index, newIcon);
|
258 |
+
}}
|
259 |
+
/>
|
260 |
+
</div>
|
261 |
+
<Input
|
262 |
+
value={
|
263 |
+
icon?.position?.scale
|
264 |
+
? icon?.position?.scale * 100
|
265 |
+
: undefined
|
266 |
+
}
|
267 |
+
type="number"
|
268 |
+
placeholder={intl.formatMessage({
|
269 |
+
id: "iconsEditor.editor.customisation.scale",
|
270 |
+
})}
|
271 |
+
className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
|
272 |
+
onChange={(newScale) => {
|
273 |
+
let scale = Number(newScale);
|
274 |
+
if (scale > 100) scale = 100;
|
275 |
+
const newIcon = {
|
276 |
+
...icon,
|
277 |
+
position: {
|
278 |
+
...icon.position,
|
279 |
+
scale: Math.trunc(scale) / 100,
|
280 |
+
},
|
281 |
+
};
|
282 |
+
handleChange(index, newIcon);
|
283 |
+
}}
|
284 |
+
/>
|
285 |
+
</div>
|
286 |
+
)}
|
287 |
+
</div>
|
288 |
+
</div>
|
289 |
+
{!icon?.image && (
|
290 |
+
<div className="col-span-2">
|
291 |
+
<Label className="!mb-2.5">
|
292 |
+
<FormattedMessage
|
293 |
+
id="iconsEditor.editor.customisation.gradientColor"
|
294 |
+
values={{
|
295 |
+
span: (t) => (
|
296 |
+
<span className="text-xs opacity-40">{t}</span>
|
297 |
+
),
|
298 |
+
}}
|
299 |
+
/>
|
300 |
+
</Label>
|
301 |
+
<ColorPicker
|
302 |
+
data={icon}
|
303 |
+
value={icon?.stringColor ?? icon?.colour}
|
304 |
+
onChange={(c: any, datas) => {
|
305 |
+
let newIcon = { ...icon, stringColor: c };
|
306 |
+
if (c.includes("gradient")) {
|
307 |
+
const { colors, degrees } = datas;
|
308 |
+
const gradientType = c?.startsWith("linear")
|
309 |
+
? "linearGradient"
|
310 |
+
: "radialGradient";
|
311 |
+
|
312 |
+
const angle = c?.startsWith("linear")
|
313 |
+
? c?.replace("linear-gradient(", "")?.split("deg")?.[0]
|
314 |
+
: 90;
|
315 |
+
|
316 |
+
newIcon["gradient"] = {
|
317 |
+
...newIcon.gradient,
|
318 |
+
enabled: true,
|
319 |
+
colours: colors,
|
320 |
+
angle,
|
321 |
+
type: gradientType,
|
322 |
+
};
|
323 |
+
} else {
|
324 |
+
newIcon = {
|
325 |
+
...newIcon,
|
326 |
+
colour: c,
|
327 |
+
gradient: {
|
328 |
+
...icon?.gradient,
|
329 |
+
enabled: false,
|
330 |
+
},
|
331 |
+
};
|
332 |
+
}
|
333 |
+
handleChange(index, newIcon);
|
334 |
+
}}
|
335 |
+
/>
|
336 |
+
</div>
|
337 |
+
)}
|
338 |
+
{icon?.custom_text?.enabled && (
|
339 |
+
<>
|
340 |
+
<div className="col-span-2">
|
341 |
+
<Label className="!mb-2.5">
|
342 |
+
<FormattedMessage id="iconsEditor.editor.customisation.customText" />
|
343 |
+
</Label>
|
344 |
+
<div className="grid grid-cols-2 gap-5">
|
345 |
+
<div>
|
346 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
347 |
+
<FormattedMessage id="iconsEditor.editor.customisation.customText.text" />
|
348 |
+
</p>
|
349 |
+
<input
|
350 |
+
type="text"
|
351 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
352 |
+
placeholder="10"
|
353 |
+
value={icon?.custom_text?.text}
|
354 |
+
onChange={({ target }) => {
|
355 |
+
const newIcon = {
|
356 |
+
...icon,
|
357 |
+
custom_text: {
|
358 |
+
...icon.custom_text,
|
359 |
+
text: target.value,
|
360 |
+
},
|
361 |
+
};
|
362 |
+
handleChange(index, newIcon);
|
363 |
+
}}
|
364 |
+
/>
|
365 |
+
</div>
|
366 |
+
<div>
|
367 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
368 |
+
<FormattedMessage id="iconsEditor.editor.customisation.customText.fontSize" />
|
369 |
+
</p>
|
370 |
+
<input
|
371 |
+
type="number"
|
372 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
373 |
+
placeholder="10"
|
374 |
+
value={icon?.custom_text?.size}
|
375 |
+
onChange={({ target }) => {
|
376 |
+
const newIcon = {
|
377 |
+
...icon,
|
378 |
+
custom_text: {
|
379 |
+
...icon.custom_text,
|
380 |
+
size: Number(target.value),
|
381 |
+
},
|
382 |
+
};
|
383 |
+
handleChange(index, newIcon);
|
384 |
+
}}
|
385 |
+
/>
|
386 |
+
</div>
|
387 |
+
</div>
|
388 |
+
</div>
|
389 |
+
</>
|
390 |
+
)}
|
391 |
+
<div className="relative col-span-2 grid grid-cols-2 gap-4">
|
392 |
+
{!icon?.image && (
|
393 |
+
<div>
|
394 |
+
<Label className="!mb-2.5">
|
395 |
+
<FormattedMessage id="iconsEditor.editor.customisation.border" />
|
396 |
+
</Label>
|
397 |
+
<div className="flex items-start justify-start gap-5">
|
398 |
+
<div className="w-full">
|
399 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
400 |
+
<FormattedMessage id="iconsEditor.editor.customisation.width" />
|
401 |
+
</p>
|
402 |
+
<input
|
403 |
+
type="number"
|
404 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
405 |
+
placeholder={
|
406 |
+
intl.formatMessage({
|
407 |
+
id: "iconsEditor.editor.customisation.width",
|
408 |
+
}) + "...px"
|
409 |
+
}
|
410 |
+
min={0}
|
411 |
+
value={icon?.border?.width}
|
412 |
+
onChange={({ target }) => {
|
413 |
+
const newIcon = {
|
414 |
+
...icon,
|
415 |
+
border: {
|
416 |
+
...icon.border,
|
417 |
+
width: target?.value
|
418 |
+
? Number(target.value)
|
419 |
+
: undefined,
|
420 |
+
},
|
421 |
+
};
|
422 |
+
handleChange(index, newIcon);
|
423 |
+
}}
|
424 |
+
/>
|
425 |
+
</div>
|
426 |
+
<div>
|
427 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
428 |
+
<FormattedMessage id="iconsEditor.editor.customisation.color" />
|
429 |
+
</p>
|
430 |
+
<ColorPicker
|
431 |
+
value={icon?.border?.colour}
|
432 |
+
gradients={false}
|
433 |
+
onChange={(c: any) => {
|
434 |
+
const newIcon = {
|
435 |
+
...icon,
|
436 |
+
border: {
|
437 |
+
...icon.border,
|
438 |
+
colour: c,
|
439 |
+
},
|
440 |
+
};
|
441 |
+
handleChange(index, newIcon);
|
442 |
+
}}
|
443 |
+
/>
|
444 |
+
</div>
|
445 |
+
</div>
|
446 |
+
</div>
|
447 |
+
)}
|
448 |
+
<div className="col-span-2">
|
449 |
+
<div className="flex items-center justify-start gap-3 mb-2.5 ">
|
450 |
+
<Label className="!mb-0">
|
451 |
+
<FormattedMessage id="iconsEditor.editor.customisation.shadow" />
|
452 |
+
</Label>
|
453 |
+
<Switch
|
454 |
+
value={icon?.shadow?.enabled}
|
455 |
+
onChange={(enabled: boolean) => {
|
456 |
+
const newIcon = {
|
457 |
+
...icon,
|
458 |
+
shadow: {
|
459 |
+
...icon.shadow,
|
460 |
+
enabled,
|
461 |
+
},
|
462 |
+
};
|
463 |
+
handleChange(index, newIcon);
|
464 |
+
}}
|
465 |
+
/>
|
466 |
+
</div>
|
467 |
+
{icon?.shadow?.enabled && (
|
468 |
+
<div className="gap-5 grid grid-cols-2">
|
469 |
+
<div className="w-full">
|
470 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
471 |
+
X
|
472 |
+
</p>
|
473 |
+
<input
|
474 |
+
type="number"
|
475 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
476 |
+
placeholder={intl.formatMessage({
|
477 |
+
id: "iconsEditor.editor.customisation.shadow.x.placeholder",
|
478 |
+
})}
|
479 |
+
min={0}
|
480 |
+
value={icon?.shadow?.position?.x}
|
481 |
+
onChange={({ target }) => {
|
482 |
+
const newIcon = {
|
483 |
+
...icon,
|
484 |
+
shadow: {
|
485 |
+
...icon.shadow,
|
486 |
+
position: {
|
487 |
+
...icon?.shadow?.position,
|
488 |
+
x: target?.value
|
489 |
+
? Number(target.value)
|
490 |
+
: undefined,
|
491 |
+
},
|
492 |
+
},
|
493 |
+
};
|
494 |
+
handleChange(index, newIcon);
|
495 |
+
}}
|
496 |
+
/>
|
497 |
+
</div>
|
498 |
+
<div className="w-full">
|
499 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
500 |
+
Y
|
501 |
+
</p>
|
502 |
+
<input
|
503 |
+
type="number"
|
504 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
505 |
+
placeholder={intl.formatMessage({
|
506 |
+
id: "iconsEditor.editor.customisation.shadow.y.placeholder",
|
507 |
+
})}
|
508 |
+
min={0}
|
509 |
+
value={icon?.shadow?.position?.y}
|
510 |
+
onChange={({ target }) => {
|
511 |
+
const newIcon = {
|
512 |
+
...icon,
|
513 |
+
shadow: {
|
514 |
+
...icon.shadow,
|
515 |
+
position: {
|
516 |
+
...icon?.shadow?.position,
|
517 |
+
y: target?.value
|
518 |
+
? Number(target.value)
|
519 |
+
: undefined,
|
520 |
+
},
|
521 |
+
},
|
522 |
+
};
|
523 |
+
handleChange(index, newIcon);
|
524 |
+
}}
|
525 |
+
/>
|
526 |
+
</div>
|
527 |
+
<div className="w-full">
|
528 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
529 |
+
<FormattedMessage id="iconsEditor.editor.customisation.shadow.blur" />
|
530 |
+
</p>
|
531 |
+
<input
|
532 |
+
type="number"
|
533 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
534 |
+
placeholder={intl.formatMessage({
|
535 |
+
id: "iconsEditor.editor.customisation.shadow.blur.placeholder",
|
536 |
+
})}
|
537 |
+
min={0}
|
538 |
+
value={icon?.shadow?.position?.blur}
|
539 |
+
onChange={({ target }) => {
|
540 |
+
const newIcon = {
|
541 |
+
...icon,
|
542 |
+
shadow: {
|
543 |
+
...icon.shadow,
|
544 |
+
position: {
|
545 |
+
...icon?.shadow?.position,
|
546 |
+
blur: target?.value
|
547 |
+
? Number(target.value)
|
548 |
+
: undefined,
|
549 |
+
},
|
550 |
+
},
|
551 |
+
};
|
552 |
+
handleChange(index, newIcon);
|
553 |
+
}}
|
554 |
+
/>
|
555 |
+
</div>
|
556 |
+
<div>
|
557 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
558 |
+
<FormattedMessage id="iconsEditor.editor.customisation.color" />
|
559 |
+
</p>
|
560 |
+
<ColorPicker
|
561 |
+
value={icon?.shadow?.colour}
|
562 |
+
gradients={false}
|
563 |
+
onChange={(c: any) => {
|
564 |
+
const newIcon = {
|
565 |
+
...icon,
|
566 |
+
shadow: {
|
567 |
+
...icon.shadow,
|
568 |
+
colour: c,
|
569 |
+
},
|
570 |
+
};
|
571 |
+
handleChange(index, newIcon);
|
572 |
+
}}
|
573 |
+
/>
|
574 |
+
</div>
|
575 |
+
</div>
|
576 |
+
)}
|
577 |
+
</div>
|
578 |
+
<PremiumOverlay />
|
579 |
+
</div>
|
580 |
+
</div>
|
581 |
+
)}
|
582 |
+
</div>
|
583 |
+
{totalIcons > 1 && (
|
584 |
+
<div className="flex flex-col gap-0.5">
|
585 |
+
{index !== 0 && (
|
586 |
+
<FontAwesomeIcon
|
587 |
+
icon={faCaretUp}
|
588 |
+
className="text-dark-200 cursor-pointer hover:text-white w-3"
|
589 |
+
onClick={() => onChangeOrder(index, index - 1)}
|
590 |
+
/>
|
591 |
+
)}
|
592 |
+
{index !== totalIcons - 1 && (
|
593 |
+
<FontAwesomeIcon
|
594 |
+
icon={faCaretDown}
|
595 |
+
className="text-dark-200 cursor-pointer hover:text-white w-3"
|
596 |
+
onClick={() => onChangeOrder(index, index + 1)}
|
597 |
+
/>
|
598 |
+
)}
|
599 |
+
</div>
|
600 |
+
)}
|
601 |
+
</div>
|
602 |
+
);
|
603 |
+
};
|
components/editor-icons/comps/icons/index.tsx
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import { PlusIcon } from "@heroicons/react/solid";
|
3 |
+
|
4 |
+
import { EditorType, IconType } from "@/types/editor";
|
5 |
+
import { Empty } from "@/components/empty";
|
6 |
+
|
7 |
+
import { IconSelected } from "./icon-selected";
|
8 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
9 |
+
|
10 |
+
export const Icons = ({
|
11 |
+
editor,
|
12 |
+
onChange,
|
13 |
+
onStep,
|
14 |
+
}: {
|
15 |
+
editor: EditorType;
|
16 |
+
onChange: (e: EditorType) => void;
|
17 |
+
onStep: (step: number, multiple?: boolean) => void;
|
18 |
+
}) => {
|
19 |
+
const [opened, setOpened] = useState<number | null>(0);
|
20 |
+
const intl = useIntl();
|
21 |
+
|
22 |
+
const handleChange = (index: number, icon: any) => {
|
23 |
+
const newIcons = [...editor.icons];
|
24 |
+
newIcons[index] = icon;
|
25 |
+
onChange({
|
26 |
+
...editor,
|
27 |
+
icons: newIcons,
|
28 |
+
});
|
29 |
+
};
|
30 |
+
|
31 |
+
const handleDeleteIcon = (index: number) => {
|
32 |
+
const newIcons = [...editor.icons];
|
33 |
+
newIcons.splice(index, 1);
|
34 |
+
onChange({
|
35 |
+
...editor,
|
36 |
+
icons: newIcons,
|
37 |
+
});
|
38 |
+
};
|
39 |
+
|
40 |
+
const handleChangeOrder = (index: number, value: number) => {
|
41 |
+
const newIcons = [...editor.icons];
|
42 |
+
const [removed] = newIcons.splice(index, 1);
|
43 |
+
newIcons.splice(value, 0, removed);
|
44 |
+
onChange({
|
45 |
+
...editor,
|
46 |
+
icons: newIcons,
|
47 |
+
});
|
48 |
+
};
|
49 |
+
|
50 |
+
return (
|
51 |
+
<div className="grid grid-cols-1 gap-4">
|
52 |
+
<p className="font-bold tracking-wider text-lg text-white">
|
53 |
+
<FormattedMessage id="iconsEditor.editor.icons.title" />
|
54 |
+
</p>
|
55 |
+
{editor?.icons?.length > 0 ? (
|
56 |
+
<>
|
57 |
+
<div
|
58 |
+
className="border-2 border-dark-300 border-dashed w-full rounded-lg pl-4 py-4 pr-6 group cursor-pointer transition-all duration-200 hover:border-blue group flex items-center justify-start"
|
59 |
+
onClick={() => onStep(0, true)}
|
60 |
+
>
|
61 |
+
<div className="flex items-center justify-start gap-4">
|
62 |
+
<div className="w-10 h-10 flex items-center justify-center bg-dark-200 bg-opacity-20 rounded-lg group-hover:bg-blue transition-all duration-200">
|
63 |
+
<PlusIcon className="text-white w-6" />
|
64 |
+
</div>
|
65 |
+
<div>
|
66 |
+
<p className="text-white font-semibold">
|
67 |
+
<FormattedMessage id="iconsEditor.editor.icons.add.title" />
|
68 |
+
</p>
|
69 |
+
<p className="text-xs text-dark-200">
|
70 |
+
<FormattedMessage id="iconsEditor.editor.icons.add.subtitle" />
|
71 |
+
</p>
|
72 |
+
</div>
|
73 |
+
</div>
|
74 |
+
</div>
|
75 |
+
{editor?.icons?.map((icon: IconType, k) => {
|
76 |
+
return (
|
77 |
+
<IconSelected
|
78 |
+
key={k}
|
79 |
+
totalIcons={editor?.icons?.length}
|
80 |
+
index={k}
|
81 |
+
icon={icon}
|
82 |
+
current={opened}
|
83 |
+
setCurrent={setOpened}
|
84 |
+
onDelete={handleDeleteIcon}
|
85 |
+
onChange={handleChange}
|
86 |
+
onChangeOrder={handleChangeOrder}
|
87 |
+
/>
|
88 |
+
);
|
89 |
+
})}
|
90 |
+
</>
|
91 |
+
) : (
|
92 |
+
<Empty
|
93 |
+
title={intl.formatMessage({
|
94 |
+
id: "iconsEditor.editor.icons.empty.title",
|
95 |
+
})}
|
96 |
+
description={intl.formatMessage({
|
97 |
+
id: "iconsEditor.editor.icons.empty.subtitle",
|
98 |
+
})}
|
99 |
+
button={intl.formatMessage({
|
100 |
+
id: "iconsEditor.editor.icons.empty.cta",
|
101 |
+
})}
|
102 |
+
action={() => onStep(0)}
|
103 |
+
/>
|
104 |
+
)}
|
105 |
+
</div>
|
106 |
+
);
|
107 |
+
};
|
components/editor-icons/comps/list/index.tsx
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/solid";
|
2 |
+
import { useContext, useRef } from "react";
|
3 |
+
|
4 |
+
import { useResults } from "components/search/hooks/useSearch";
|
5 |
+
import { ListItem } from "components/editor-icons/comps/list/list-item";
|
6 |
+
import { IconItem } from "types/editor";
|
7 |
+
import { Empty } from "components/empty";
|
8 |
+
import { login, useUser } from "utils/auth";
|
9 |
+
import { PremiumContext } from "components/premium/premium";
|
10 |
+
import { Premium } from "@/components/premium";
|
11 |
+
import classNames from "classnames";
|
12 |
+
import { toBase64 } from "@/components/upload";
|
13 |
+
import { FormattedMessage } from "react-intl";
|
14 |
+
|
15 |
+
export const ListIcons = ({
|
16 |
+
onSelect,
|
17 |
+
onCustomText,
|
18 |
+
onCustomUpload,
|
19 |
+
}: {
|
20 |
+
onSelect: (e: IconItem) => void;
|
21 |
+
onCustomText: () => void;
|
22 |
+
onCustomUpload: (s: string) => void;
|
23 |
+
}) => {
|
24 |
+
const { user } = useUser();
|
25 |
+
const { setOpen } = useContext(PremiumContext);
|
26 |
+
const inputRef = useRef<HTMLInputElement>(null);
|
27 |
+
|
28 |
+
const { results, page, maxPage, totalItems, setPage } = useResults();
|
29 |
+
|
30 |
+
const handleCustomUpload = (e: any) => {
|
31 |
+
const file = e.target.files[0];
|
32 |
+
if (file) {
|
33 |
+
toBase64(file).then((res: any) => {
|
34 |
+
onCustomUpload(res);
|
35 |
+
});
|
36 |
+
}
|
37 |
+
};
|
38 |
+
|
39 |
+
return (
|
40 |
+
<div>
|
41 |
+
<div className="flex flex-col lg:flex-row gap-y-3 lg:gap-y-0 items-start justify-between">
|
42 |
+
<p className="font-bold tracking-wider text-base text-white">
|
43 |
+
<FormattedMessage
|
44 |
+
id="iconsEditor.editor.listIcons.totalIcons"
|
45 |
+
values={{ total: () => totalItems }}
|
46 |
+
/>
|
47 |
+
</p>
|
48 |
+
<input
|
49 |
+
ref={inputRef}
|
50 |
+
type="file"
|
51 |
+
className="hidden"
|
52 |
+
onChange={handleCustomUpload}
|
53 |
+
/>
|
54 |
+
<div className="flex w-full lg:w-auto items-center justify-end gap-3">
|
55 |
+
<button
|
56 |
+
className={classNames(
|
57 |
+
"w-full lg:w-auto px-3 py-2 hover:bg-opacity-70 text-xs font-semibold text-white rounded-md flex items-center justify-center lg:justify-start gap-2",
|
58 |
+
{
|
59 |
+
"bg-dark-300": !user?.id,
|
60 |
+
"bg-darkGreen": user?.id,
|
61 |
+
}
|
62 |
+
)}
|
63 |
+
onClick={() => inputRef.current?.click()}
|
64 |
+
>
|
65 |
+
{!user && <Premium size="16" tooltip={true} />}
|
66 |
+
<FormattedMessage id="iconsEditor.editor.listIcons.uploadCustomIcon" />
|
67 |
+
</button>
|
68 |
+
<button
|
69 |
+
className={classNames(
|
70 |
+
"w-full lg:w-auto px-3 py-2 hover:bg-opacity-70 text-xs font-semibold text-white rounded-md flex items-center justify-center lg:justify-start gap-2",
|
71 |
+
{
|
72 |
+
"bg-dark-300": !user?.id,
|
73 |
+
"bg-yellow": user?.id,
|
74 |
+
}
|
75 |
+
)}
|
76 |
+
onClick={onCustomText}
|
77 |
+
>
|
78 |
+
{!user && <Premium size="16" tooltip={true} />}
|
79 |
+
<FormattedMessage id="iconsEditor.editor.listIcons.useCustomText" />
|
80 |
+
</button>
|
81 |
+
</div>
|
82 |
+
</div>
|
83 |
+
<div className="flex jlg:items-start flex-wrap justify-start mt-3 gap-2 lg:gap-3">
|
84 |
+
{results?.map((result: any, key: number) => (
|
85 |
+
<ListItem
|
86 |
+
key={key}
|
87 |
+
icon={result}
|
88 |
+
fill={result?.defaultColor}
|
89 |
+
onSelect={(icon) => {
|
90 |
+
// if (icon?.isPremium && !user?.id) return setOpen(true);
|
91 |
+
onSelect(icon);
|
92 |
+
}}
|
93 |
+
/>
|
94 |
+
))}
|
95 |
+
{results?.length === 0 && (
|
96 |
+
<Empty
|
97 |
+
title="Clyde is disappointed..."
|
98 |
+
description="...there is no icon that matches your search :("
|
99 |
+
action={() => {
|
100 |
+
if (!user?.id) return login();
|
101 |
+
// send to api a request;
|
102 |
+
console.log("display Modal and call API");
|
103 |
+
}}
|
104 |
+
button="Ask for a new Icon"
|
105 |
+
/>
|
106 |
+
)}
|
107 |
+
</div>
|
108 |
+
{results?.length > 0 && (
|
109 |
+
<div className="flex items-center justify-between gap-4 mt-10">
|
110 |
+
<button
|
111 |
+
disabled={page <= 1}
|
112 |
+
className="w-full lg:w-auto group border-2 border-darkGreen bg-darkGreen text-white font-medium tracking-wide px-3.5 py-2 text-sm flex items-center justify-center gap-2 rounded-lg hover:bg-opacity-90 hover:border-opacity-0 disabled:bg-dark-100 disabled:bg-opacity-20 disabled:border-dark-100 disabled:border-opacity-0 disabled:text-dark-200 disabled:cursor-not-allowed"
|
113 |
+
onClick={() => setPage(page - 1)}
|
114 |
+
>
|
115 |
+
<ArrowLeftIcon className="w-4 text-white group-disabled:text-dark-200" />
|
116 |
+
<FormattedMessage
|
117 |
+
id="iconsEditor.editor.listIcons.pagination.previous"
|
118 |
+
values={{
|
119 |
+
span: (t) => <span className="hidden lg:inline">{t}</span>,
|
120 |
+
}}
|
121 |
+
/>
|
122 |
+
</button>
|
123 |
+
<div className="hidden items-center justify-center gap-2 lg:flex">
|
124 |
+
<p className="text-dark-100 font-medium text-sm">
|
125 |
+
<FormattedMessage
|
126 |
+
id="iconsEditor.editor.listIcons.pagination.total"
|
127 |
+
values={{
|
128 |
+
page: () => page,
|
129 |
+
maxPage: () => maxPage,
|
130 |
+
}}
|
131 |
+
/>
|
132 |
+
</p>
|
133 |
+
</div>
|
134 |
+
<button
|
135 |
+
disabled={page === maxPage}
|
136 |
+
className="w-full lg:w-auto group border-2 border-darkGreen bg-darkGreen text-white font-medium tracking-wide px-3.5 py-2 text-sm flex items-center justify-center gap-2 rounded-lg hover:bg-opacity-90 hover:border-opacity-0 disabled:bg-dark-100 disabled:bg-opacity-20 disabled:border-dark-100 disabled:border-opacity-0 disabled:text-dark-200 disabled:cursor-not-allowed"
|
137 |
+
onClick={page >= maxPage ? () => {} : () => setPage(page + 1)}
|
138 |
+
>
|
139 |
+
<FormattedMessage
|
140 |
+
id="iconsEditor.editor.listIcons.pagination.next"
|
141 |
+
values={{
|
142 |
+
span: (t) => <span className="hidden lg:inline">{t}</span>,
|
143 |
+
}}
|
144 |
+
/>
|
145 |
+
<ArrowRightIcon className="w-4 text-white group-disabled:text-dark-200" />
|
146 |
+
</button>
|
147 |
+
</div>
|
148 |
+
)}
|
149 |
+
</div>
|
150 |
+
);
|
151 |
+
};
|
components/editor-icons/comps/list/list-item.tsx
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import React, { useEffect, useRef } from "react";
|
3 |
+
|
4 |
+
export const ListItem = ({
|
5 |
+
icon,
|
6 |
+
tooltip = true,
|
7 |
+
hoverable = true,
|
8 |
+
fill = "#b0b0b0",
|
9 |
+
onSelect,
|
10 |
+
}: {
|
11 |
+
icon: any;
|
12 |
+
tooltip?: boolean;
|
13 |
+
hoverable?: boolean;
|
14 |
+
fill?: string;
|
15 |
+
onSelect: (e: any) => void;
|
16 |
+
}) => {
|
17 |
+
const ref = useRef<any>(null);
|
18 |
+
const svgRef = useRef<SVGSVGElement>(null);
|
19 |
+
const [viewBox, setViewBox] = React.useState<string>("0 0 24 24");
|
20 |
+
const renderComponent = () => {
|
21 |
+
const Component = icon?.component as any;
|
22 |
+
return <Component ref={ref} />;
|
23 |
+
};
|
24 |
+
|
25 |
+
useEffect(() => {
|
26 |
+
return setViewBox(ref?.current?.getAttribute("viewBox") ?? "0 0 200 200");
|
27 |
+
});
|
28 |
+
|
29 |
+
return (
|
30 |
+
<div
|
31 |
+
className={classNames(
|
32 |
+
"flex items-center justify-center p-2 cursor-pointer rounded-lg relative group border border-dark-200 border-solid",
|
33 |
+
{
|
34 |
+
"hover:border-yellow": hoverable && icon?.isPremium,
|
35 |
+
"hover:border-dark-100": hoverable && !icon?.isPremium,
|
36 |
+
}
|
37 |
+
)}
|
38 |
+
onClick={() => onSelect(icon)}
|
39 |
+
>
|
40 |
+
{/* {icon?.isPremium && (
|
41 |
+
<div className="w-4 h-4 bg-yellow rounded-full absolute -right-1.5 -top-1 border-[2px] border-dark-500" />
|
42 |
+
)} */}
|
43 |
+
<svg
|
44 |
+
width={24}
|
45 |
+
height={24}
|
46 |
+
fill={fill}
|
47 |
+
ref={svgRef}
|
48 |
+
viewBox={viewBox}
|
49 |
+
xmlns="http://www.w3.org/2000/svg"
|
50 |
+
className="h-auto"
|
51 |
+
>
|
52 |
+
{renderComponent()}
|
53 |
+
</svg>
|
54 |
+
{hoverable && (
|
55 |
+
<div
|
56 |
+
className={classNames(
|
57 |
+
"group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
|
58 |
+
{
|
59 |
+
"bg-yellow": icon?.isPremium,
|
60 |
+
"bg-dark-100": !icon?.isPremium,
|
61 |
+
}
|
62 |
+
)}
|
63 |
+
/>
|
64 |
+
)}
|
65 |
+
{/* {tooltip && (
|
66 |
+
<>
|
67 |
+
<div
|
68 |
+
className={classNames(
|
69 |
+
"group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
|
70 |
+
{
|
71 |
+
"bg-yellow": icon?.isPremium,
|
72 |
+
"bg-dark-100": !icon?.isPremium,
|
73 |
+
}
|
74 |
+
)}
|
75 |
+
/>
|
76 |
+
<div className="opacity-0 transform z-10 pt-1 translate-y-full pointer-events-none group-hover:opacity-100 absolute bottom-0 flex items-center justify-center flex-col">
|
77 |
+
<div className="w-[0px] h-[0px] border-l-[5px] border-l-transparent border-r-[5px] border-r-transparent border-b-[5px] border-b-dark-600 border-opacity-80" />
|
78 |
+
<div className="bg-dark-600 bg-opacity-80 text-xs text-white rounded py-0.5 px-1.5 whitespace-nowrap">
|
79 |
+
{icon?.tags?.join(", ")}
|
80 |
+
</div>
|
81 |
+
</div>
|
82 |
+
</>
|
83 |
+
)} */}
|
84 |
+
</div>
|
85 |
+
);
|
86 |
+
};
|
components/editor-icons/comps/shapes/index.tsx
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useContext } from "react";
|
2 |
+
|
3 |
+
import { EditorType } from "types/editor";
|
4 |
+
import { SHAPES } from "components/svg/shapes";
|
5 |
+
import classNames from "classnames";
|
6 |
+
import { Empty } from "@/components/empty";
|
7 |
+
import { useUser } from "@/utils/auth";
|
8 |
+
|
9 |
+
import { ShapeSelected } from "./shape-selected";
|
10 |
+
import { PremiumContext } from "@/components/premium/premium";
|
11 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
12 |
+
|
13 |
+
export const Shapes = ({
|
14 |
+
editor,
|
15 |
+
onChange,
|
16 |
+
}: {
|
17 |
+
editor: EditorType;
|
18 |
+
onChange: (s: any) => void;
|
19 |
+
}) => {
|
20 |
+
const { user } = useUser();
|
21 |
+
const intl = useIntl();
|
22 |
+
|
23 |
+
const { setOpen } = useContext(PremiumContext);
|
24 |
+
|
25 |
+
return (
|
26 |
+
<>
|
27 |
+
<p className="font-bold tracking-wider text-lg text-white mb-3">
|
28 |
+
<FormattedMessage id="iconsEditor.editor.shape.title" />
|
29 |
+
</p>
|
30 |
+
<div className="flex items-start flex-wrap justify-start mt-3 gap-3">
|
31 |
+
{SHAPES.map((shape, k) => (
|
32 |
+
<div
|
33 |
+
key={k}
|
34 |
+
className={classNames(
|
35 |
+
"relative flex items-center justify-center p-2 cursor-pointer rounded-lg group border border-dark-200 border-solid",
|
36 |
+
{
|
37 |
+
"bg-blue border-blue": editor.shape?.component === shape?.name,
|
38 |
+
// "hover:border-yellow": shape?.isPremium,
|
39 |
+
}
|
40 |
+
)}
|
41 |
+
onClick={() => {
|
42 |
+
// if (!user && shape?.isPremium) {
|
43 |
+
// return setOpen(true);
|
44 |
+
// }
|
45 |
+
onChange({ ...editor.shape, component: shape?.name });
|
46 |
+
}}
|
47 |
+
>
|
48 |
+
{/* {shape?.isPremium && (
|
49 |
+
<div className="w-4 h-4 bg-yellow rounded-full absolute -right-1.5 -top-1 border-[2px] border-dark-500" />
|
50 |
+
)} */}
|
51 |
+
<_ShapeComponent
|
52 |
+
component={shape.component}
|
53 |
+
width={28}
|
54 |
+
height={28}
|
55 |
+
shape={shape}
|
56 |
+
/>
|
57 |
+
<div
|
58 |
+
className={classNames(
|
59 |
+
"group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
|
60 |
+
{
|
61 |
+
// "bg-yellow": shape?.isPremium,
|
62 |
+
"bg-dark-100": !shape?.isPremium,
|
63 |
+
}
|
64 |
+
)}
|
65 |
+
/>
|
66 |
+
</div>
|
67 |
+
))}
|
68 |
+
</div>
|
69 |
+
{editor.shape?.component ? (
|
70 |
+
<ShapeSelected
|
71 |
+
shape={editor?.shape}
|
72 |
+
handleChange={(shape: any) => onChange({ ...editor.shape, ...shape })}
|
73 |
+
onDelete={() => onChange({ ...editor.shape, component: null })}
|
74 |
+
/>
|
75 |
+
) : (
|
76 |
+
<Empty
|
77 |
+
title={intl.formatMessage({
|
78 |
+
id: "iconsEditor.editor.shape.empty.title",
|
79 |
+
})}
|
80 |
+
description={intl.formatMessage({
|
81 |
+
id: "iconsEditor.editor.shape.empty.subtitle",
|
82 |
+
})}
|
83 |
+
className="mt-12"
|
84 |
+
// button={<div onClick={() => onStep(0)}>Add my first Icon</div>}
|
85 |
+
/>
|
86 |
+
)}
|
87 |
+
</>
|
88 |
+
);
|
89 |
+
};
|
90 |
+
|
91 |
+
const _ShapeComponent = ({ component, shape, onClick, ...props }: any) => {
|
92 |
+
const ShapeComponent = component as any;
|
93 |
+
const newShape = {
|
94 |
+
...shape,
|
95 |
+
component: `${shape.name}-small`,
|
96 |
+
};
|
97 |
+
return <ShapeComponent shape={newShape} {...props} />;
|
98 |
+
};
|
components/editor-icons/comps/shapes/shape-selected.tsx
ADDED
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import { useState } from "react";
|
3 |
+
import {
|
4 |
+
faChevronDown,
|
5 |
+
faChevronRight,
|
6 |
+
faTrash,
|
7 |
+
faCaretDown,
|
8 |
+
faCaretUp,
|
9 |
+
} from "@fortawesome/free-solid-svg-icons";
|
10 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
11 |
+
|
12 |
+
import { ColorPicker } from "components/color-picker";
|
13 |
+
import { Range } from "components/range";
|
14 |
+
import { UploadImage } from "@/components/upload";
|
15 |
+
import { Switch } from "@/components/switch";
|
16 |
+
import { login, useUser } from "@/utils/auth";
|
17 |
+
import { Premium } from "@/components/premium";
|
18 |
+
import { PremiumOverlay } from "@/components/premium/overlay";
|
19 |
+
import { FormattedMessage, useIntl } from "react-intl";
|
20 |
+
import { Label } from "@/components/label";
|
21 |
+
|
22 |
+
export const ShapeSelected = ({
|
23 |
+
shape,
|
24 |
+
handleChange,
|
25 |
+
onDelete,
|
26 |
+
}: {
|
27 |
+
shape: any;
|
28 |
+
handleChange: (s: any) => void;
|
29 |
+
onDelete: () => void;
|
30 |
+
}) => {
|
31 |
+
const { user } = useUser();
|
32 |
+
const intl = useIntl();
|
33 |
+
const [open, setOpen] = useState(true);
|
34 |
+
|
35 |
+
return (
|
36 |
+
<div
|
37 |
+
className={classNames(
|
38 |
+
"mt-5 bg-dark-600 w-full rounded-lg pl-4 py-4 pr-6 cursor-pointer transition-all border-2 border-dark-600 duration-200 hover:border-blue",
|
39 |
+
{
|
40 |
+
"border-blue": open,
|
41 |
+
}
|
42 |
+
)}
|
43 |
+
>
|
44 |
+
<div
|
45 |
+
className="tracking-wider flex items-center justify-between gap-4"
|
46 |
+
onClick={() => setOpen(!open)}
|
47 |
+
>
|
48 |
+
<p className="text-white font-semibold">
|
49 |
+
<FormattedMessage id="iconsEditor.editor.shape.update.title" />
|
50 |
+
</p>
|
51 |
+
<div className="flex items-center justify-end gap-4">
|
52 |
+
<div
|
53 |
+
className="bg-danger bg-opacity-60 rounded-lg text-white hover:bg-opacity-100 cursor-pointer w-8 h-8 flex items-center justify-center"
|
54 |
+
onClick={(e: any) => {
|
55 |
+
e.preventDefault();
|
56 |
+
e.stopPropagation();
|
57 |
+
onDelete();
|
58 |
+
}}
|
59 |
+
>
|
60 |
+
<FontAwesomeIcon icon={faTrash} className="w-4" />
|
61 |
+
</div>
|
62 |
+
<div className="w-4 flex items-center justify-end">
|
63 |
+
<FontAwesomeIcon
|
64 |
+
icon={open ? faChevronDown : faChevronRight}
|
65 |
+
className="text-dark-100 transition-all duration-200"
|
66 |
+
/>
|
67 |
+
</div>
|
68 |
+
</div>
|
69 |
+
</div>
|
70 |
+
{open && (
|
71 |
+
<div className="border-t border-dark-400 pt-3 mt-4 grid grid-cols-2 gap-4">
|
72 |
+
<div className="w-full col-span-2">
|
73 |
+
<Label className="!mb-2">
|
74 |
+
<FormattedMessage id="iconsEditor.editor.customisation.rotation" />
|
75 |
+
</Label>
|
76 |
+
<div className="flex items-center gap-3 w-full">
|
77 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">Z</p>
|
78 |
+
<Range
|
79 |
+
value={shape?.position?.angle ?? 0}
|
80 |
+
max={360}
|
81 |
+
onChange={(value) => {
|
82 |
+
const newShape = {
|
83 |
+
...shape,
|
84 |
+
position: { ...shape.position, angle: Number(value) },
|
85 |
+
};
|
86 |
+
handleChange(newShape);
|
87 |
+
}}
|
88 |
+
/>
|
89 |
+
<input
|
90 |
+
type="number"
|
91 |
+
className="w-full bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
92 |
+
placeholder={intl.formatMessage({
|
93 |
+
id: "iconsEditor.editor.customisation.angle",
|
94 |
+
})}
|
95 |
+
value={shape?.position?.angle}
|
96 |
+
onChange={({ target }) => {
|
97 |
+
const newShape = {
|
98 |
+
...shape,
|
99 |
+
position: {
|
100 |
+
...shape.position,
|
101 |
+
angle: target.value
|
102 |
+
? Number(target.value) > 360
|
103 |
+
? 360
|
104 |
+
: Number(target.value)
|
105 |
+
: undefined,
|
106 |
+
},
|
107 |
+
};
|
108 |
+
handleChange(newShape);
|
109 |
+
}}
|
110 |
+
/>
|
111 |
+
</div>
|
112 |
+
</div>
|
113 |
+
{shape?.component === "Square" && (
|
114 |
+
<div className="w-full col-span-2">
|
115 |
+
<Label className="!mb-2">
|
116 |
+
<FormattedMessage id="iconsEditor.editor.customisation.radius" />
|
117 |
+
</Label>
|
118 |
+
<div className="flex items-center gap-3 w-full">
|
119 |
+
<p className="font-semibold uppercase text-dark-200 text-xs">
|
120 |
+
Z
|
121 |
+
</p>
|
122 |
+
<Range
|
123 |
+
value={shape?.radius ?? 0}
|
124 |
+
max={120}
|
125 |
+
onChange={(value) => {
|
126 |
+
const newShape = {
|
127 |
+
...shape,
|
128 |
+
radius: Number(value),
|
129 |
+
};
|
130 |
+
handleChange(newShape);
|
131 |
+
}}
|
132 |
+
/>
|
133 |
+
<input
|
134 |
+
type="number"
|
135 |
+
className="w-full bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
136 |
+
placeholder={intl.formatMessage({
|
137 |
+
id: "iconsEditor.editor.customisation.angle",
|
138 |
+
})}
|
139 |
+
value={shape?.radius}
|
140 |
+
onChange={({ target }) => {
|
141 |
+
const newShape = {
|
142 |
+
...shape,
|
143 |
+
radius: Number(target.value),
|
144 |
+
};
|
145 |
+
handleChange(newShape);
|
146 |
+
}}
|
147 |
+
/>
|
148 |
+
</div>
|
149 |
+
</div>
|
150 |
+
)}
|
151 |
+
<div className="col-span-2">
|
152 |
+
<div className="flex items-center justify-start gap-3 mb-2.5 ">
|
153 |
+
<Label className="">
|
154 |
+
<FormattedMessage
|
155 |
+
id="iconsEditor.editor.customisation.gradientColor"
|
156 |
+
values={{
|
157 |
+
span: (t) => (
|
158 |
+
<span className="text-xs opacity-40">{t}</span>
|
159 |
+
),
|
160 |
+
}}
|
161 |
+
/>
|
162 |
+
</Label>
|
163 |
+
<Switch
|
164 |
+
value={!shape?.transparency}
|
165 |
+
onChange={(transparency: boolean) => {
|
166 |
+
const newShape = {
|
167 |
+
...shape,
|
168 |
+
transparency: !transparency,
|
169 |
+
};
|
170 |
+
handleChange(newShape);
|
171 |
+
}}
|
172 |
+
/>
|
173 |
+
</div>
|
174 |
+
<ColorPicker
|
175 |
+
value={shape?.stringColor ?? shape?.colour}
|
176 |
+
data={shape}
|
177 |
+
onChange={(c: any, datas) => {
|
178 |
+
let newShape = { ...shape, stringColor: c };
|
179 |
+
if (c.includes("gradient")) {
|
180 |
+
const { colors, degrees } = datas;
|
181 |
+
const gradientType = c?.startsWith("linear")
|
182 |
+
? "linearGradient"
|
183 |
+
: "radialGradient";
|
184 |
+
|
185 |
+
const angle = c?.startsWith("linear")
|
186 |
+
? c?.replace("linear-gradient(", "")?.split("deg")?.[0]
|
187 |
+
: 90;
|
188 |
+
|
189 |
+
newShape["gradient"] = {
|
190 |
+
...newShape.gradient,
|
191 |
+
enabled: true,
|
192 |
+
colours: colors,
|
193 |
+
angle,
|
194 |
+
type: gradientType,
|
195 |
+
};
|
196 |
+
} else {
|
197 |
+
newShape = {
|
198 |
+
...newShape,
|
199 |
+
colour: c,
|
200 |
+
gradient: {
|
201 |
+
...shape?.gradient,
|
202 |
+
enabled: false,
|
203 |
+
},
|
204 |
+
};
|
205 |
+
}
|
206 |
+
|
207 |
+
handleChange(newShape);
|
208 |
+
}}
|
209 |
+
/>
|
210 |
+
</div>
|
211 |
+
<div className="col-span-2 grid grid-cols-2 gap-4 relative">
|
212 |
+
<div className="col-span-2">
|
213 |
+
<div className="flex items-center justify-start gap-3 mb-2.5 ">
|
214 |
+
<Label className="">
|
215 |
+
<FormattedMessage id="iconsEditor.editor.customisation.backgroundImage" />
|
216 |
+
</Label>
|
217 |
+
<Switch
|
218 |
+
value={shape?.image?.enabled}
|
219 |
+
onChange={(enabled: boolean) => {
|
220 |
+
const newShape = {
|
221 |
+
...shape,
|
222 |
+
image: {
|
223 |
+
...shape.image,
|
224 |
+
enabled,
|
225 |
+
},
|
226 |
+
};
|
227 |
+
handleChange(newShape);
|
228 |
+
}}
|
229 |
+
/>
|
230 |
+
</div>
|
231 |
+
{shape?.image?.enabled && (
|
232 |
+
<UploadImage
|
233 |
+
values={shape?.image?.urls}
|
234 |
+
onChange={(urls: string) => {
|
235 |
+
const newShape = {
|
236 |
+
...shape,
|
237 |
+
image: {
|
238 |
+
...shape.image,
|
239 |
+
urls,
|
240 |
+
},
|
241 |
+
};
|
242 |
+
handleChange(newShape);
|
243 |
+
}}
|
244 |
+
/>
|
245 |
+
)}
|
246 |
+
</div>
|
247 |
+
<div>
|
248 |
+
<Label className="!mb-2.5">
|
249 |
+
<FormattedMessage id="iconsEditor.editor.customisation.border" />
|
250 |
+
</Label>
|
251 |
+
<div className="flex items-start justify-start gap-5">
|
252 |
+
<div className="w-full">
|
253 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
254 |
+
<FormattedMessage id="iconsEditor.editor.customisation.width" />
|
255 |
+
</p>
|
256 |
+
<input
|
257 |
+
type="number"
|
258 |
+
className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
|
259 |
+
placeholder={
|
260 |
+
intl.formatMessage({
|
261 |
+
id: "iconsEditor.editor.customisation.width",
|
262 |
+
}) + "...px"
|
263 |
+
}
|
264 |
+
min={0}
|
265 |
+
value={shape?.border?.width}
|
266 |
+
onChange={({ target }) => {
|
267 |
+
const newShape = {
|
268 |
+
...shape,
|
269 |
+
border: {
|
270 |
+
...shape.border,
|
271 |
+
width: target?.value
|
272 |
+
? Number(target.value)
|
273 |
+
: undefined,
|
274 |
+
},
|
275 |
+
};
|
276 |
+
handleChange(newShape);
|
277 |
+
}}
|
278 |
+
/>
|
279 |
+
</div>
|
280 |
+
<div>
|
281 |
+
<p className="font-semibold uppercase text-dark-200 text-xs mb-2">
|
282 |
+
<FormattedMessage id="iconsEditor.editor.customisation.color" />
|
283 |
+
</p>
|
284 |
+
<ColorPicker
|
285 |
+
value={shape?.border?.colour}
|
286 |
+
gradients={false}
|
287 |
+
onChange={(c: any) => {
|
288 |
+
const newShape = {
|
289 |
+
...shape,
|
290 |
+
border: {
|
291 |
+
...shape.border,
|
292 |
+
colour: c,
|
293 |
+
},
|
294 |
+
};
|
295 |
+
handleChange(newShape);
|
296 |
+
}}
|
297 |
+
/>
|
298 |
+
</div>
|
299 |
+
</div>
|
300 |
+
</div>
|
301 |
+
<PremiumOverlay />
|
302 |
+
</div>
|
303 |
+
</div>
|
304 |
+
)}
|
305 |
+
</div>
|
306 |
+
);
|
307 |
+
};
|
components/editor-icons/comps/tabs.tsx
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import classNames from "classnames";
|
3 |
+
|
4 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
5 |
+
import {
|
6 |
+
faSearch,
|
7 |
+
faArrowsUpDownLeftRight,
|
8 |
+
faPalette,
|
9 |
+
faShapes,
|
10 |
+
faIcons,
|
11 |
+
} from "@fortawesome/free-solid-svg-icons";
|
12 |
+
import { FormattedMessage } from "react-intl";
|
13 |
+
|
14 |
+
const TABS_ELEMENTS = [
|
15 |
+
{
|
16 |
+
icon: faSearch,
|
17 |
+
name: "iconsEditor.editor.tabs.listIcons",
|
18 |
+
},
|
19 |
+
{
|
20 |
+
icon: faShapes,
|
21 |
+
name: "iconsEditor.editor.tabs.shape",
|
22 |
+
},
|
23 |
+
{
|
24 |
+
icon: faIcons,
|
25 |
+
name: "iconsEditor.editor.tabs.icons",
|
26 |
+
},
|
27 |
+
];
|
28 |
+
|
29 |
+
export const EditorTabs = ({
|
30 |
+
current,
|
31 |
+
onChange,
|
32 |
+
}: {
|
33 |
+
current: number;
|
34 |
+
onChange: (e: number) => void;
|
35 |
+
}) => {
|
36 |
+
return (
|
37 |
+
// p-2 lg:p-4
|
38 |
+
<div className="bg-dark-600 rounded-t-2xl lg:rounded-r-none">
|
39 |
+
<ul className="w-full relative z-1 grid grid-cols-3">
|
40 |
+
{TABS_ELEMENTS.map((tab, index) => (
|
41 |
+
<li
|
42 |
+
key={index}
|
43 |
+
className={classNames(
|
44 |
+
"hover:bg-blue hover:bg-opacity-50 flex items-center gap-5 lg:gap-4 cursor-pointer text-white text-left text-xs lg:text-sm font-semibold tracking-wide p-4 lg:p-5 justify-center uppercase",
|
45 |
+
{
|
46 |
+
"bg-dark-500 hover:!bg-dark-500 hover:!bg-opacity-100":
|
47 |
+
current === index,
|
48 |
+
"second-step rounded-t-2xl rounded-r-none": index === 0,
|
49 |
+
"third-step": index === 1,
|
50 |
+
"fourth-step rounded-t-2xl lg:rounded-t-none rounded-l-none":
|
51 |
+
index === 2,
|
52 |
+
}
|
53 |
+
)}
|
54 |
+
onClick={() => onChange(index)}
|
55 |
+
>
|
56 |
+
<FontAwesomeIcon icon={tab.icon} className="w-4 min-w-[1rem]" />
|
57 |
+
<p>
|
58 |
+
<FormattedMessage id={tab.name} />
|
59 |
+
</p>
|
60 |
+
</li>
|
61 |
+
))}
|
62 |
+
</ul>
|
63 |
+
</div>
|
64 |
+
);
|
65 |
+
};
|