diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..9a0b23b2bb2caf8abd45defa7555b0e2941ed423 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +public/badge-editor.png filter=lfs diff=lfs merge=lfs -text +public/banner.png filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a78bb4ae2a299508cdacdbb9e3adb89ac746c2bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Dockerfile + +# Use an official Node.js runtime as the base image +FROM node:18 + +USER 1000 + +# Set the working directory in the container +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json to the container +COPY --chown=1000 package.json package-lock.json ./ + +# Install dependencies +RUN npm install + +VOLUME /data + +# Copy the rest of the application files to the container +COPY --chown=1000 . . +RUN chmod +x entrypoint.sh + +# Build the Next.js application for production +# RUN npm run build + +# Expose the application port (assuming your app runs on port 3000) +EXPOSE 3000 + +RUN npm run build + +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 3e591aa0cc06836bd62d61550af4b71a428d315d..9c17535f37af4edbebf2690e27634532f706497f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ --- title: Discotools -emoji: 🐢 -colorFrom: gray -colorTo: yellow +emoji: ✨ +colorFrom: blue +colorTo: pink sdk: docker pinned: false +app_port: 3000 +short_description: Generate discord role icon and badge license: mit --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +## This is an old project. diff --git a/assets/images/avatar_bot.png b/assets/images/avatar_bot.png new file mode 100644 index 0000000000000000000000000000000000000000..f7963ac2e0e2eedb6afa3aba6fb415236d7b726e Binary files /dev/null and b/assets/images/avatar_bot.png differ diff --git a/assets/images/avatars/default-avatar-2.svg b/assets/images/avatars/default-avatar-2.svg new file mode 100644 index 0000000000000000000000000000000000000000..87307cdebfc4b746dbdc274e4d241832552596dd --- /dev/null +++ b/assets/images/avatars/default-avatar-2.svg @@ -0,0 +1,79 @@ + + + Group 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/avatars/default-avatar.svg b/assets/images/avatars/default-avatar.svg new file mode 100644 index 0000000000000000000000000000000000000000..1c1495b029a7a4d0971e0d1cd45ea84978dd2be4 --- /dev/null +++ b/assets/images/avatars/default-avatar.svg @@ -0,0 +1,85 @@ + + + Group 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/banner_footer.svg b/assets/images/banner_footer.svg new file mode 100644 index 0000000000000000000000000000000000000000..b4ddf91d12968eea0c5ecae66cd40e2789d24c12 --- /dev/null +++ b/assets/images/banner_footer.svg @@ -0,0 +1,118 @@ + + + Group 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/clyde/affraid.svg b/assets/images/clyde/affraid.svg new file mode 100644 index 0000000000000000000000000000000000000000..dd74e3bddc28ac14fb96a87f3c03dc85e8f8547a --- /dev/null +++ b/assets/images/clyde/affraid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/clyde/wave.svg b/assets/images/clyde/wave.svg new file mode 100644 index 0000000000000000000000000000000000000000..59c38b367f7656d076a0541cf6a9357855b6a4e0 --- /dev/null +++ b/assets/images/clyde/wave.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/color-picker.svg b/assets/images/color-picker.svg new file mode 100644 index 0000000000000000000000000000000000000000..c307b3727f15e31c5f0c81d80db8b50f44a2632f --- /dev/null +++ b/assets/images/color-picker.svg @@ -0,0 +1,9 @@ + + + Shape + + + + + + \ No newline at end of file diff --git a/assets/images/editor/transparent_bg.svg b/assets/images/editor/transparent_bg.svg new file mode 100644 index 0000000000000000000000000000000000000000..4cdb086137c7f3391dcd693548defd43ee640fa9 --- /dev/null +++ b/assets/images/editor/transparent_bg.svg @@ -0,0 +1,11 @@ + + + transparent_bg + + + + + + + + \ No newline at end of file diff --git a/assets/images/header/ananas.svg b/assets/images/header/ananas.svg new file mode 100644 index 0000000000000000000000000000000000000000..cdb82ec56d6d171c6e67f541b61b8b69e70b0cf6 --- /dev/null +++ b/assets/images/header/ananas.svg @@ -0,0 +1,32 @@ + + + Group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/header/background.svg b/assets/images/header/background.svg new file mode 100644 index 0000000000000000000000000000000000000000..8c609437dc3d9e38f685dfd81dc40dcf0d42ae9b --- /dev/null +++ b/assets/images/header/background.svg @@ -0,0 +1,31 @@ + + + background + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/header/icons-slider/crown.svg b/assets/images/header/icons-slider/crown.svg new file mode 100644 index 0000000000000000000000000000000000000000..f750b10365a1be9aaac57df1b3fe41cb0f2cb6bb --- /dev/null +++ b/assets/images/header/icons-slider/crown.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/header/icons-slider/hammer.svg b/assets/images/header/icons-slider/hammer.svg new file mode 100644 index 0000000000000000000000000000000000000000..e23568493a07f5b57ccf0506e12ed898e7fbf18f --- /dev/null +++ b/assets/images/header/icons-slider/hammer.svg @@ -0,0 +1,8 @@ + + + Group + + \ No newline at end of file diff --git a/assets/images/header/icons-slider/leaf.svg b/assets/images/header/icons-slider/leaf.svg new file mode 100644 index 0000000000000000000000000000000000000000..deb90e564053c1d0fdbf798cf76ab9c1b8ede9c6 --- /dev/null +++ b/assets/images/header/icons-slider/leaf.svg @@ -0,0 +1,9 @@ + + + Group + + \ No newline at end of file diff --git a/assets/images/header/icons-slider/partner.svg b/assets/images/header/icons-slider/partner.svg new file mode 100644 index 0000000000000000000000000000000000000000..dbcbcda07d874b9ba7f49495deb2a7a518997726 --- /dev/null +++ b/assets/images/header/icons-slider/partner.svg @@ -0,0 +1,9 @@ + + + Group + + \ No newline at end of file diff --git a/assets/images/header/icons-slider/shape.svg b/assets/images/header/icons-slider/shape.svg new file mode 100644 index 0000000000000000000000000000000000000000..d5fe4affbb7f550f694fef487305671e9d334226 --- /dev/null +++ b/assets/images/header/icons-slider/shape.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/header/icons-slider/shield.svg b/assets/images/header/icons-slider/shield.svg new file mode 100644 index 0000000000000000000000000000000000000000..dfc433aa7fb08e5cbc5b965260a752e3a42be937 --- /dev/null +++ b/assets/images/header/icons-slider/shield.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/assets/images/header/icons-slider/star.svg b/assets/images/header/icons-slider/star.svg new file mode 100644 index 0000000000000000000000000000000000000000..e60d6d543f63c1750e5e63b82a1afb9e0bc59f7e --- /dev/null +++ b/assets/images/header/icons-slider/star.svg @@ -0,0 +1,9 @@ + + + Group + + + + + + \ No newline at end of file diff --git a/assets/images/header/pizza.svg b/assets/images/header/pizza.svg new file mode 100644 index 0000000000000000000000000000000000000000..b60510efe3a1aedb08ce06c534fb4b22c04f9301 --- /dev/null +++ b/assets/images/header/pizza.svg @@ -0,0 +1,31 @@ + + + Group + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/header/search.svg b/assets/images/header/search.svg new file mode 100644 index 0000000000000000000000000000000000000000..0e91176c63a6acd7f308a7e429493aed52493f23 --- /dev/null +++ b/assets/images/header/search.svg @@ -0,0 +1,9 @@ + + + Group + + + + + + \ No newline at end of file diff --git a/assets/images/header/stars.svg b/assets/images/header/stars.svg new file mode 100644 index 0000000000000000000000000000000000000000..9db94556df352a85e008024595787e949a9447f8 --- /dev/null +++ b/assets/images/header/stars.svg @@ -0,0 +1,12 @@ + + + stars + + + + + + + + + \ No newline at end of file diff --git a/assets/images/premium-modal/modal-illustration.webp b/assets/images/premium-modal/modal-illustration.webp new file mode 100644 index 0000000000000000000000000000000000000000..0e913c2cda34828b5161688d3f0c3962117bdbf5 Binary files /dev/null and b/assets/images/premium-modal/modal-illustration.webp differ diff --git a/components/clyde/clyde.tsx b/components/clyde/clyde.tsx new file mode 100644 index 0000000000000000000000000000000000000000..82cac0335c7a5a30a928637abbbdf2163d19bd04 --- /dev/null +++ b/components/clyde/clyde.tsx @@ -0,0 +1,296 @@ +import { useState } from "react"; + +export const Clyde = () => { + const [isHovered, setIsHovered] = useState(false); + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + className="cursor-pointer" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/components/clyde/message.tsx b/components/clyde/message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..97028e177318985c09f91a582406c18d82901aa9 --- /dev/null +++ b/components/clyde/message.tsx @@ -0,0 +1,54 @@ +import classNames from "classnames"; +import { useState } from "react"; +import { useMount, useUpdateEffect } from "react-use"; + +import { Clyde } from "./clyde"; + +export const ClydeMessage = ({ + message, + auto = false, + onClick, +}: { + message: string; + auto?: boolean; + onClick: any; +}) => { + const [show, setShow] = useState(false); + + useMount(() => { + if (auto) { + setTimeout(() => { + setShow(true); + }, 2000); + } + }); + + useUpdateEffect(() => { + if (auto && show) { + setTimeout(() => { + setShow(false); + }, 5000); + } + }, [show]); + + return ( +
+ +
+ {message} +
+
+ {/* Hello

lol

*/} +
+ ); +}; diff --git a/components/collapse/index.tsx b/components/collapse/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8cc558c317cc658992692d8d8170ac64b0304f95 --- /dev/null +++ b/components/collapse/index.tsx @@ -0,0 +1,47 @@ +import { ChevronDownIcon } from "@heroicons/react/solid"; +import classNames from "classnames"; +import { useState } from "react"; +import { useIntl } from "react-intl"; + +export const Collapse = ({ + children, + title, + className, + open: defaultOpen = false, + onOpenClassName, + parentClassName, +}: { + children: React.ReactNode; + title: string | React.ReactNode; + className?: string; + open?: boolean; + onOpenClassName?: string; + parentClassName?: string; +}) => { + const [open, setOpen] = useState(defaultOpen); + const intl = useIntl(); + + return ( +
+
setOpen(!open)} + > + {typeof title === "string" ? intl.formatMessage({ id: title }) : title} +
+ +
+
+ {open && children} +
+ ); +}; diff --git a/components/color-picker/index.tsx b/components/color-picker/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..587fb4accbe15d185af2fcb1212d71427d5877c3 --- /dev/null +++ b/components/color-picker/index.tsx @@ -0,0 +1,104 @@ +import { useState, useRef } from "react"; +import { useClickAway } from "react-use"; +import Picker, { useColorPicker } from "react-best-gradient-color-picker"; +import classNames from "classnames"; + +import ColorPickerIcon from "@/assets/images/color-picker.svg"; +import { IconType } from "@/types/editor"; +import Image from "next/image"; + +export const ColorPicker = ({ + value, + data, + gradients = true, + full = false, + onChange, + className, +}: { + value?: string; + data?: any; + gradients?: boolean; + full?: boolean; + onChange: (c: any, datas: any) => void; + className?: string; +}) => { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + const [color, setColor] = useState(value ?? "#fff"); + const { getGradientObject } = useColorPicker(color, setColor); + + useClickAway(ref, () => setOpen(false)); + + const renderBackground = (data: any) => { + if (data?.gradient?.enabled) { + if (data?.gradient?.type === "linearGradient") { + return { + background: `linear-gradient(${data?.gradient?.colours + ?.map((e: any) => e.value) + .join(", ")})`, + }; + } else { + return { + background: `radial-gradient(${data?.gradient?.colours + ?.map((e: any) => e.value) + .join(", ")})`, + }; + } + } + return { backgroundColor: data?.colour ?? data?.color ?? "#fff" }; + }; + + return ( +
+
+ {open && ( + { + setColor(c); + const datas = getGradientObject(); + onChange(c, datas); + }} + className={`${ + data?.gradient?.enabled + ? "color__picker_discotools--gradient" + : "color__picker_discotools" + } ${gradients ? "" : "color__picker without-gradients"}`} + /> + )} +
+
setOpen(true)} + > + ColorPicker +
+
+ ); +}; diff --git a/components/editor-badge/badge-editor.constants.ts b/components/editor-badge/badge-editor.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..08840253ff20d9ff7494b5cdb38da62ac2190858 --- /dev/null +++ b/components/editor-badge/badge-editor.constants.ts @@ -0,0 +1,39 @@ +import { BadgeType } from "types/badge"; + +export const DEFAULT_VALUE: BadgeType = { + colour: "#5865F1", + gradient: { + enabled: false, + colours: [], + angle: 0, + type: "linear-gradient", + }, + text: { + value: "DISCOTOOLS.XYZ", + colour: "#ffffff", + gradient: { + enabled: false, + colours: [], + angle: 0, + type: "linear-gradient", + }, + }, + icon: { + component: "King", + position: "left", + enabled: true, + colour: "#ffffff", + }, + shinyEffect: false, + type: "circle", + radius: 40, + fontFamily: "Montserrat", + fontWeight: "700", + letterSpacing: 0, + border: { + width: 0, + colour: "#ffffff", + }, +}; + +export const RATIONAL_BADGE_WIDTH = 32; diff --git a/components/editor-badge/comps/badge.tsx b/components/editor-badge/comps/badge.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0f8f77fda5e790701755977a6a6bbcb16a91cd8 --- /dev/null +++ b/components/editor-badge/comps/badge.tsx @@ -0,0 +1,163 @@ +import { forwardRef, useEffect, useRef, useState } from "react"; + +import { BadgeType } from "@/types/badge"; +import { FONT_FAMILY } from "@/components/font-family/font-family.constants"; + +import { RATIONAL_BADGE_WIDTH } from "../badge-editor.constants"; +import { Icons } from "@/components/svg/icons"; +import { IconItem } from "@/types/editor"; +import { BADGE_COMPONENTS } from "@/components/svg/badges"; +import classNames from "classnames"; +import { ShineEffect } from "./shine_effect"; + +export const Badge = forwardRef( + ( + { + badge, + size = "medium", + }: { + badge: BadgeType; + size?: "medium" | "small"; + }, + ref + ) => { + const textRef = useRef(null); + const svgRef = useRef(null); + const badgeContentRef = useRef(null); + const [viewBox, setViewBox] = useState("0 0 24 24"); + + useEffect(() => { + if (!textRef.current) return; + if (!badgeContentRef.current) return; + + const textWidth = textRef.current.getBoundingClientRect().width; + + let newBadgeWidth = RATIONAL_BADGE_WIDTH; + if (textWidth > RATIONAL_BADGE_WIDTH) { + newBadgeWidth = Math.ceil(textWidth / 32) * 32; + } + if ( + (findShape?.autoResize || badge.type === "circle") && + newBadgeWidth - textWidth < (badge.type === "circle" ? 6 : 3) + ) + newBadgeWidth += 32; + badgeContentRef.current.style.width = `${newBadgeWidth}px`; + }, [ + badge.text, + badge.fontFamily, + badge.letterSpacing, + badge.fontWeight, + badge.type, + badge?.icon?.component, + badge?.icon?.enabled, + badge?.icon?.position, + ]); + + const fontFamily = FONT_FAMILY?.find((f) => f.label === badge?.fontFamily); + + const defaultStyle = { + letterSpacing: badge?.letterSpacing, + fontWeight: badge?.fontWeight, + }; + + const findIcon = Icons?.find( + (i: IconItem) => badge?.icon?.component === i.name + ); + const findShape = BADGE_COMPONENTS?.find( + (b: any) => b.name === badge?.type + ); + + useEffect(() => { + return setViewBox( + svgRef?.current?.getAttribute("viewBox") ?? "0 0 200 200" + ); + }, [badge?.icon]); + + const IconComponent = findIcon?.component as any; + const ShapeComponent = findShape?.component as any; + + return ( +
+ {findShape?.component && ( + + )} + {badge?.shinyEffect && ( + <> + + + + )} +
+
+ {badge?.icon?.enabled && + badge?.icon?.position?.includes("left") && + findIcon?.component && + badge?.icon?.component && ( + + + + )} + + {badge?.text?.value && ( +

+ {badge?.text?.value} +

+ )} + {badge?.icon?.enabled && + badge?.icon?.position?.includes("right") && + findIcon?.component && + badge?.icon?.component && ( + + + + )} +
+
+ {findShape?.component && ( + + )} +
+ ); + } +); diff --git a/components/editor-badge/comps/download_button.tsx b/components/editor-badge/comps/download_button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..94b3b9ea8c9f340c1c56dec1d76f7a89acab509f --- /dev/null +++ b/components/editor-badge/comps/download_button.tsx @@ -0,0 +1,60 @@ +import { useRef, useState } from "react"; +import { ChevronDownIcon } from "@heroicons/react/solid"; +import classNames from "classnames"; +import { useClickAway } from "react-use"; + +export const DownloadButton = ({ + onSave, +}: { + onSave: (e?: boolean) => void; +}) => { + const [dropdown, setDropdown] = useState(false); + const ref = useRef(null); + + useClickAway(ref, () => { + setDropdown(false); + }); + + return ( +
+ +
{ + e.preventDefault(); + e.stopPropagation(); + setDropdown(!dropdown); + }} + > + +
+
+ +
+
+ ); +}; diff --git a/components/editor-badge/comps/header/comps/icons-slider.tsx b/components/editor-badge/comps/header/comps/icons-slider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..59954f59e688c31750ff1391a30988c6190bfec2 --- /dev/null +++ b/components/editor-badge/comps/header/comps/icons-slider.tsx @@ -0,0 +1,152 @@ +import { useRef, useState } from "react"; +import { useInterval } from "react-use"; +import { motion } from "framer-motion"; +import Image from "next/image"; + +import { sleep } from "utils"; +import HammerIcon from "assets/images/header/icons-slider/hammer.svg"; +import ShieldIcon from "assets/images/header/icons-slider/shield.svg"; +import CrownIcon from "assets/images/header/icons-slider/crown.svg"; +import PartnerIcon from "assets/images/header/icons-slider/partner.svg"; +import StarIcon from "assets/images/header/icons-slider/star.svg"; +import LeafIcon from "assets/images/header/icons-slider/leaf.svg"; + +const ICONS = [ + HammerIcon, + ShieldIcon, + CrownIcon, + PartnerIcon, + StarIcon, + LeafIcon, +]; + +const icon = { + hidden: { + opacity: 0, + pathLength: 0, + fill: "rgba(255, 255, 255, 0)", + }, + visible: { + opacity: 1, + pathLength: 1, + fill: "rgba(255, 255, 255, 1)", + }, +}; + +export const IconsSlider = () => { + const [selected, setSelected] = useState(0); + const ref = useRef(null); + + useInterval(async () => { + if (ref?.current) { + ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50"); + await sleep(300); + ref.current?.classList?.remove("-translate-x-full"); + ref.current?.classList?.add("translate-x-full"); + + setSelected((prev) => (prev + 1) % ICONS.length); + // if (this.selected >= this.avatars.length - 1) { + // this.selected = 0; + // } else { + // this.selected += 1; + // } + await sleep(300); + ref.current?.classList?.remove( + "translate-x-full", + "scale-50", + "opacity-0" + ); + } + }, 2000); + + return ( +
+ {/* Animation shape */} + + + {/* */} + + + + + +
+ Animation icon, change every 3 seconds +
+

+
+ //
+ // Linear gradient behind avatar + // + //
+ ); +}; diff --git a/components/editor-badge/comps/header/form/advanced.tsx b/components/editor-badge/comps/header/form/advanced.tsx new file mode 100644 index 0000000000000000000000000000000000000000..856a085c9e25143f88d770e317256354e0678b88 --- /dev/null +++ b/components/editor-badge/comps/header/form/advanced.tsx @@ -0,0 +1,100 @@ +import { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { FontFamilySelector } from "@/components/font-family"; +import { FontWeight } from "@/components/font-weight"; + +import { Switch } from "@/components/switch"; +import { BadgeType } from "@/types/badge"; +import { PremiumOverlay } from "@/components/premium/overlay"; + +import { Label } from "@/components/label"; +import { Input } from "@/components/input"; + +export const AdvancedForm = ({ + badge, + setBadge, +}: { + badge: BadgeType; + setBadge: (b: BadgeType) => void; +}) => { + const intl = useIntl(); + + return ( +
+ {badge?.type === "circle" && ( +
+ + { + let radius: number = Number(newRadius); + if (radius > 100) radius = 100; + setBadge({ ...badge, radius: Number(radius) }); + }} + /> +
+ )} +
+
+ +
+
+

+ +

+ setBadge({ ...badge, fontFamily })} + /> +
+
+

+ +

+ setBadge({ ...badge, fontWeight })} + /> +
+
+

+ +

+ { + let letterSpacing: number = Number(newLetterSpacing); + if (letterSpacing > 100) letterSpacing = 100; + setBadge({ + ...badge, + letterSpacing: Number(letterSpacing), + }); + }} + /> +
+ +
+
+
+ + + setBadge({ + ...badge, + shinyEffect: enabled, + }) + } + /> +
+
+
+ ); +}; diff --git a/components/editor-badge/comps/header/form/main.tsx b/components/editor-badge/comps/header/form/main.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d993cf465631563fde41f57c846d2fff51c2f51a --- /dev/null +++ b/components/editor-badge/comps/header/form/main.tsx @@ -0,0 +1,267 @@ +import { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { FontFamilySelector } from "@/components/font-family"; +import { FontWeight } from "@/components/font-weight"; + +import { Switch } from "@/components/switch"; +import { BadgeType } from "@/types/badge"; +import { ColorPicker } from "components/color-picker"; +import { Label } from "@/components/label"; +import { Input } from "@/components/input"; +import classNames from "classnames"; +import { SelectShapes } from "../../select-shapes"; +import { IconPicker } from "@/components/icon-picker"; + +export const MainForm = ({ + badge, + setBadge, +}: { + badge: BadgeType; + setBadge: (b: BadgeType) => void; +}) => { + const intl = useIntl(); + + return ( +
+ +
+ + { + setBadge({ + ...badge, + text: { ...badge.text, value: value as string }, + }); + }} + /> +
+
+
+ +
+
+

+ +

+ { + let newBadge = { ...badge, stringColor: c }; + if (c.includes("gradient")) { + const { colors, degrees } = datas; + const gradientType = c?.startsWith("linear") + ? "linearGradient" + : "radialGradient"; + const anglePI = + Number(degrees ? degrees?.replace("deg", "") : 90) * + (Math.PI / 180); + const angleCoords = { + x1: Math.round(50 + Math.sin(anglePI) * 50) + "%", + y1: Math.round(50 + Math.cos(anglePI) * 50) + "%", + x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%", + y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%", + }; + + newBadge["gradient"] = { + ...newBadge.gradient, + enabled: true, + colours: colors, + angle: angleCoords, + type: gradientType, + }; + } else { + newBadge = { + ...newBadge, + colour: c, + gradient: { + ...newBadge?.gradient, + enabled: false, + }, + }; + } + setBadge(newBadge); + }} + /> +
+
+

+ +

+ { + let newBadge = { + ...badge, + text: { ...badge.text, stringColor: c }, + }; + if (c.includes("gradient")) { + const { colors, degrees } = datas; + const gradientType = c?.startsWith("linear") + ? "linearGradient" + : "radialGradient"; + const anglePI = + Number(degrees ? degrees?.replace("deg", "") : 90) * + (Math.PI / 180); + const angleCoords = { + x1: Math.round(50 + Math.sin(anglePI) * 50) + "%", + y1: Math.round(50 + Math.cos(anglePI) * 50) + "%", + x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%", + y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%", + }; + newBadge["text"]["gradient"] = { + ...newBadge?.text?.gradient, + enabled: true, + colours: colors, + angle: angleCoords, + type: gradientType, + }; + } else { + newBadge = { + ...newBadge, + text: { + ...newBadge?.text, + colour: c, + gradient: { + ...newBadge?.text?.gradient, + enabled: false, + }, + }, + }; + } + setBadge(newBadge); + }} + /> +
+
+
+
+ + + setBadge({ + ...badge, + icon: { + ...badge.icon, + position: badge?.icon?.position ?? "left", + enabled, + }, + }) + } + /> +
+
+

+ +

+ { + setBadge({ + ...badge, + icon: { + ...badge?.icon, + position: badge?.icon?.position ?? "left", + colour: c, + stringColor: c, + }, + }); + }} + /> +
+
+
+

+ +

+ { + setBadge({ + ...badge, + icon: { + ...badge?.icon, + position: badge?.icon?.position ?? "left", + enabled: true, + component, + }, + }); + }} + /> +
+
+

+ +

+
+
+ setBadge({ + ...badge, + icon: { ...badge?.icon, position: "left" }, + }) + } + > + Left +
+
+ setBadge({ + ...badge, + icon: { ...badge?.icon, position: "right" }, + }) + } + > + Right +
+
+ setBadge({ + ...badge, + icon: { ...badge?.icon, position: "left-right" }, + }) + } + > + Left & Right +
+
+
+
+
+
+ ); +}; diff --git a/components/editor-badge/comps/header/index.tsx b/components/editor-badge/comps/header/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a357ce9be6e99baf7de765cec2140e9aba223584 --- /dev/null +++ b/components/editor-badge/comps/header/index.tsx @@ -0,0 +1,84 @@ +import Image from "next/image"; + +import { Tag } from "components/tag"; +import { IconsSlider } from "./comps/icons-slider"; + +// import { Editor } from "components/editor"; +// import { ArrayOfIcons } from "components/svg/icons"; + +// import { Navigation } from "components/navigation"; + +import Background from "assets/images/header/background.svg"; +import Stars from "assets/images/header/stars.svg"; +import Ananas from "assets/images/header/ananas.svg"; +import Pizza from "assets/images/header/pizza.svg"; +import { FormattedMessage, useIntl } from "react-intl"; + +export const Header = ({ children }: any) => { + const intl = useIntl(); + return ( +
+
+
+
+ + NEW FEATURE AVAILABLE + + +
+

+
}} + />{" "} + ✨ +

+ Stars on title +
+

+ +

+

+ +

+
+ {children} +
+
+
+ Pizza with sauce + Ananas party! + Background header cloud +
+ {/*
+
*/} +
+ ); +}; diff --git a/components/editor-badge/comps/preview.tsx b/components/editor-badge/comps/preview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d2307a756dcaf080ca2e5e25b1622114ad9c2866 --- /dev/null +++ b/components/editor-badge/comps/preview.tsx @@ -0,0 +1,39 @@ +import { useMount, useUpdateEffect } from "react-use"; +import { domToPng } from "modern-screenshot"; + +import { BadgeType } from "@/types/badge"; +import { useState } from "react"; + +export const Preview = ({ badge }: { badge: BadgeType }) => { + const [badgeUrl, setBadgeUrl] = useState(""); + + useUpdateEffect(() => { + setTimeout(() => { + domToPng( + document.getElementById("discotools-selected-badge") as HTMLElement, + { + scale: 2, + } + ).then(async (dataUrl) => { + setBadgeUrl(dataUrl); + }); + }, 190); + }, [badge]); + + useMount(() => { + setTimeout(() => { + domToPng( + document.getElementById("discotools-selected-badge") as HTMLElement, + { + scale: 2, + } + ).then(async (dataUrl) => { + setBadgeUrl(dataUrl); + }); + }, 100); + }); + + return ( +
{badgeUrl && }
+ ); +}; diff --git a/components/editor-badge/comps/select-shapes.tsx b/components/editor-badge/comps/select-shapes.tsx new file mode 100644 index 0000000000000000000000000000000000000000..26288c43f2d723500295a591ff7a311beb61c2e6 --- /dev/null +++ b/components/editor-badge/comps/select-shapes.tsx @@ -0,0 +1,74 @@ +import classNames from "classnames"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; + +import { Label } from "@/components/label"; +import { BADGE_COMPONENTS } from "@/components/svg/badges"; +import { BadgeType } from "@/types/badge"; +import { useUser } from "@/utils/auth"; +import { PremiumContext } from "@/components/premium/premium"; +import { useContext } from "react"; +import { Premium } from "@/components/premium"; + +export const SelectShapes = ({ + badge, + onChange, +}: { + badge: BadgeType; + onChange: (b: BadgeType) => void; +}) => { + const { user } = useUser(); + const { setOpen } = useContext(PremiumContext); + return ( +
+ +
+ {BADGE_COMPONENTS.map((component, i) => { + const Component = component.mini as any; + return ( +
setOpen(true) + : () => + onChange({ + ...badge, + type: + component?.name === badge?.type + ? "circle" + : component.name, + }) + } + > +
+ {badge?.type === component.name && ( + + )} +
+ +
+ ); + })} +
+
+ ); +}; diff --git a/components/editor-badge/comps/shine_effect.tsx b/components/editor-badge/comps/shine_effect.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4ba908025004d0f991fc2a57ce9df3e0151094e2 --- /dev/null +++ b/components/editor-badge/comps/shine_effect.tsx @@ -0,0 +1,38 @@ +export const ShineEffect = ({ + size = 8, + className, +}: { + size?: number; + className?: string; +}) => { + return ( + + + + + + + + ); +}; diff --git a/components/editor-badge/comps/tabs.tsx b/components/editor-badge/comps/tabs.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e50bef7ae7f321233d4589d436fdd60c650f141a --- /dev/null +++ b/components/editor-badge/comps/tabs.tsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import classNames from "classnames"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faShapes, faAnglesRight } from "@fortawesome/free-solid-svg-icons"; +import { FormattedMessage } from "react-intl"; + +const TABS_ELEMENTS = [ + { + icon: faShapes, + name: "badgeEditor.tabs.main", + }, + { + icon: faAnglesRight, + name: "badgeEditor.tabs.advanced", + }, +]; + +export const EditorTabs = ({ + current, + onChange, +}: { + current: number; + onChange: (e: number) => void; +}) => { + return ( + // p-2 lg:p-4 +
+
    + {TABS_ELEMENTS.map((tab, index) => ( +
  • onChange(index)} + > + +

    + +

    +
  • + ))} +
+
+ ); +}; diff --git a/components/editor-badge/comps/user_card.tsx b/components/editor-badge/comps/user_card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..217056abb4e9ff7007ccb11b465b01ae025d0a7c --- /dev/null +++ b/components/editor-badge/comps/user_card.tsx @@ -0,0 +1,114 @@ +import { useMemo } from "react"; + +import { BadgeType } from "@/types/badge"; +import { useUser } from "@/utils/auth"; +import DefaultAvatar from "assets/images/avatars/default-avatar.svg"; +import { Moderator } from "@/components/svg/icons/discord"; +import { Boost2 } from "@/components/svg/icons/discord"; +import { Preview } from "./preview"; +import { DownloadButton } from "./download_button"; + +export const UserCard = ({ + badge, + onSave, +}: { + badge: BadgeType; + onSave: (e?: boolean) => void; +}) => { + const { user, loading } = useUser(); + + const convertNumberToStringColor = (color: number) => { + if (!color) return "#121212"; + return "#" + color?.toString(16); + }; + + const bannerStyle = useMemo(() => { + let style: any = { backgroundColor: "#121212", height: 60 }; + + if (user?.id) { + if (user?.banner) { + style.backgroundImage = `url(https://cdn.discordapp.com/banners/${user.id}/${user.banner}.gif?size=1024)`; + style.height = 120; + style.borderLeft = "5px solid #232429"; + style.borderTop = "5px solid #232429"; + style.borderRight = "5px solid #232429"; + } else { + // style.backgroundColor = convertNumberToStringColor(user?.accent_color); + style.backgroundColor = user?.accent_color; + } + } + + return style; + }, [user]); + + return ( +
+
+
+
+
+
+
+
+ + + + + + +
+
+

+ {user ? user?.username : "Captain Astro"} +

+

+ {user ? `${user?.username}` : "captainastro"} +

+
+

+ About me +

+ + {/*

+ Member since +

*/} + +

+ Image will be split into multiple parts. You will upload them as + server emojis on Discord to use them on your discord Profile. +

+
+
+
+ ); +}; diff --git a/components/editor-badge/index.tsx b/components/editor-badge/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..25b02ad592aee5954404ecaffde52394cdb896f3 --- /dev/null +++ b/components/editor-badge/index.tsx @@ -0,0 +1,94 @@ +import { useState, useRef } from "react"; +import { domToPng } from "modern-screenshot"; +import ImageToPieces from "image-to-pieces"; +import JSZip from "jszip"; +import download from "downloadjs"; + +import { + DEFAULT_VALUE, + RATIONAL_BADGE_WIDTH, +} from "components/editor-badge/badge-editor.constants"; +import BackgroundTransparent from "assets/images/editor/transparent_bg.svg"; +import { API } from "@/utils/api"; +import { useUser } from "@/utils/auth"; + +import { MainForm } from "./comps/header/form/main"; +import { AdvancedForm } from "./comps/header/form/advanced"; +import { Badge } from "./comps/badge"; +import { EditorTabs } from "./comps/tabs"; +import { UserCard } from "./comps/user_card"; + +export default function Editor() { + const { user } = useUser(); + const [badge, setBadge] = useState({ + ...DEFAULT_VALUE, + }); + + const badgeRef = useRef(null); + const [current, setCurrent] = useState(0); + + const handleSave = (hasImage = false) => { + domToPng( + document.getElementById("discotools-selected-badge") as HTMLElement, + { + scale: 2, + } + ).then(async (dataUrl) => { + if (hasImage) { + download(dataUrl, "badge.png", "image/png"); + // API.downloadBadge(); + return; + } + + if (!badgeRef?.current) return; + const badgeWidth = badgeRef.current.getBoundingClientRect().width; + const image = new ImageToPieces( + null, + badgeWidth / RATIONAL_BADGE_WIDTH, + 1 + ); + await image.loadImageByUrl(dataUrl); + const tiles = image + .getTiles() + ?.map((tile: any) => tile.data?.replace("data:image/png;base64,", "")); + const zip_folder = new JSZip(); + tiles.forEach((tile: any, i: number) => + zip_folder.file(`${i}.png`, tile, { base64: true }) + ); + + zip_folder.generateAsync({ type: "blob" }).then(function (content) { + download(content, "badges.zip"); + // API.downloadBadge(); + }); + }); + }; + + const renderTabs = () => { + switch (current) { + case 0: + return ; + case 1: + return ; + } + }; + + return ( +
+
+ + {renderTabs()} +
+
+
+ +
+ +
+
+ ); +} diff --git a/components/editor-icons/comps/header/comps/icons-slider.tsx b/components/editor-icons/comps/header/comps/icons-slider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ea495708aeac5e443c2583db6e0de3e44a65dfd9 --- /dev/null +++ b/components/editor-icons/comps/header/comps/icons-slider.tsx @@ -0,0 +1,108 @@ +import { useRef, useState } from "react"; +import { useInterval } from "react-use"; +import { motion } from "framer-motion"; +import Image from "next/image"; + +import { sleep } from "utils"; +import HammerIcon from "assets/images/header/icons-slider/hammer.svg"; +import ShieldIcon from "assets/images/header/icons-slider/shield.svg"; +import CrownIcon from "assets/images/header/icons-slider/crown.svg"; +import PartnerIcon from "assets/images/header/icons-slider/partner.svg"; +import StarIcon from "assets/images/header/icons-slider/star.svg"; +import LeafIcon from "assets/images/header/icons-slider/leaf.svg"; + +const ICONS = [ + HammerIcon, + ShieldIcon, + CrownIcon, + PartnerIcon, + StarIcon, + LeafIcon, +]; + +const icon = { + hidden: { + opacity: 0, + pathLength: 0, + fill: "rgba(255, 255, 255, 0)", + }, + visible: { + opacity: 1, + pathLength: 1, + fill: "rgba(255, 255, 255, 1)", + }, +}; + +export const IconsSlider = () => { + const [selected, setSelected] = useState(0); + const ref = useRef(null); + + useInterval(async () => { + if (ref?.current) { + ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50"); + await sleep(300); + ref.current?.classList?.remove("-translate-x-full"); + ref.current?.classList?.add("translate-x-full"); + + setSelected((prev) => (prev + 1) % ICONS.length); + // if (this.selected >= this.avatars.length - 1) { + // this.selected = 0; + // } else { + // this.selected += 1; + // } + await sleep(300); + ref.current?.classList?.remove( + "translate-x-full", + "scale-50", + "opacity-0" + ); + } + }, 2000); + + return ( +
+ {/* Animation shape */} + + + + +
+ Animation icon, change every 3 seconds +
+

+
+ //
+ // Linear gradient behind avatar + // + //
+ ); +}; diff --git a/components/editor-icons/comps/header/index.tsx b/components/editor-icons/comps/header/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3effd4e189f3e51ca6d96433f0dbc877bdacd891 --- /dev/null +++ b/components/editor-icons/comps/header/index.tsx @@ -0,0 +1,84 @@ +import Image from "next/image"; + +import { Tag } from "components/tag"; +import { IconsSlider } from "./comps/icons-slider"; + +// import { Editor } from "components/editor"; +// import { ArrayOfIcons } from "components/svg/icons"; + +// import { Navigation } from "components/navigation"; + +import Background from "assets/images/header/background.svg"; +import Stars from "assets/images/header/stars.svg"; +import Ananas from "assets/images/header/ananas.svg"; +import Pizza from "assets/images/header/pizza.svg"; +import { FormattedMessage, useIntl } from "react-intl"; + +export const Header = ({ children }: any) => { + const intl = useIntl(); + return ( +
+
+
+
+ + NEW VERSION RELEASED! + + +
+

+
}} + />{" "} + ✨ +

+ Stars on title +
+

+ +

+

+ +

+
+ {children} +
+
+
+ Pizza with sauce + Ananas party! + Background header cloud +
+ {/*
+
*/} +
+ ); +}; diff --git a/components/editor-icons/comps/icons/icon-selected.tsx b/components/editor-icons/comps/icons/icon-selected.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6c9310dd79c4161f3aff882ddebf05c2adf7429b --- /dev/null +++ b/components/editor-icons/comps/icons/icon-selected.tsx @@ -0,0 +1,603 @@ +import classNames from "classnames"; +import { + faChevronDown, + faChevronRight, + faTrash, + faCaretDown, + faCaretUp, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { ListItem } from "components/editor-icons/comps/list/list-item"; +import { + Icons as ICONS, + IconCustomIcon, + IconCustomText, +} from "components/svg/icons"; +import { IconItem, IconType } from "@/types/editor"; +import { ColorPicker } from "components/color-picker"; +import { Range } from "components/range"; +import { Input } from "components/input"; +import { Switch } from "@/components/switch"; +import { PremiumOverlay } from "@/components/premium/overlay"; +import { FormattedMessage, useIntl } from "react-intl"; +import { Label } from "@/components/label"; + +export const IconSelected = ({ + index, + totalIcons, + icon, + current, + setCurrent, + onDelete, + onChange, + onChangeOrder, +}: { + index: number; + totalIcons: number; + icon: IconType; + current?: number | null; + setCurrent: (index: number | null) => void; + onDelete: (index: number) => void; + onChange: (idnex: number, icon: IconType) => void; + onChangeOrder: (index: number, value: number) => void; +}) => { + const findIcon: any = icon?.custom_text?.enabled + ? IconCustomText + : icon?.image + ? IconCustomIcon + : ICONS?.find((i: IconItem) => icon.component === i.name); + + const handleChange = (index: number, icon: any) => { + onChange(index, icon); + }; + + const intl = useIntl(); + + return ( +
+
+
setCurrent(current === index ? null : index)} + > +
+
+ {icon?.image ? ( +
+ +
+ ) : ( + {}} + /> + )} +
+
+

+ +

+

+ {findIcon?.tags?.join(", ")} +

+
+
+
+
{ + e.preventDefault(); + e.stopPropagation(); + onDelete(index); + }} + > + +
+
+ +
+
+
+ {current === index && ( +
+
+ +
+
+
+

+ X +

+ { + const newIcon = { + ...icon, + position: { ...icon.position, x: Number(value) }, + }; + handleChange(index, newIcon); + }} + /> +
+ { + let x: number = Number(newX); + if (x > 100) x = 100; + const newIcon = { + ...icon, + position: { ...icon.position, x: x }, + }; + handleChange(index, newIcon); + }} + /> +
+
+
+

+ Y +

+ { + const newIcon = { + ...icon, + position: { ...icon.position, y: Number(value) }, + }; + handleChange(index, newIcon); + }} + /> +
+ { + let y = Number(newY); + if (y > 100) y = 100; + const newIcon = { + ...icon, + position: { ...icon.position, y: y }, + }; + handleChange(index, newIcon); + }} + /> +
+
+
+

+ Z +

+ { + const newIcon = { + ...icon, + position: { ...icon.position, angle: Number(value) }, + }; + handleChange(index, newIcon); + }} + /> +
+ { + let angle = Number(newAngle); + if (angle > 360) angle = 360; + const newIcon = { + ...icon, + position: { ...icon.position, angle: angle }, + }; + handleChange(index, newIcon); + }} + /> +
+ {!icon?.custom_text?.enabled && ( +
+
+

+ +

+ { + const newIcon = { + ...icon, + position: { + ...icon.position, + scale: Number(value), + }, + }; + handleChange(index, newIcon); + }} + /> +
+ { + let scale = Number(newScale); + if (scale > 100) scale = 100; + const newIcon = { + ...icon, + position: { + ...icon.position, + scale: Math.trunc(scale) / 100, + }, + }; + handleChange(index, newIcon); + }} + /> +
+ )} +
+
+ {!icon?.image && ( +
+ + { + let newIcon = { ...icon, stringColor: c }; + if (c.includes("gradient")) { + const { colors, degrees } = datas; + const gradientType = c?.startsWith("linear") + ? "linearGradient" + : "radialGradient"; + + const angle = c?.startsWith("linear") + ? c?.replace("linear-gradient(", "")?.split("deg")?.[0] + : 90; + + newIcon["gradient"] = { + ...newIcon.gradient, + enabled: true, + colours: colors, + angle, + type: gradientType, + }; + } else { + newIcon = { + ...newIcon, + colour: c, + gradient: { + ...icon?.gradient, + enabled: false, + }, + }; + } + handleChange(index, newIcon); + }} + /> +
+ )} + {icon?.custom_text?.enabled && ( + <> +
+ +
+
+

+ +

+ { + const newIcon = { + ...icon, + custom_text: { + ...icon.custom_text, + text: target.value, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+

+ +

+ { + const newIcon = { + ...icon, + custom_text: { + ...icon.custom_text, + size: Number(target.value), + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+
+ + )} +
+ {!icon?.image && ( +
+ +
+
+

+ +

+ { + const newIcon = { + ...icon, + border: { + ...icon.border, + width: target?.value + ? Number(target.value) + : undefined, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+

+ +

+ { + const newIcon = { + ...icon, + border: { + ...icon.border, + colour: c, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+
+ )} +
+
+ + { + const newIcon = { + ...icon, + shadow: { + ...icon.shadow, + enabled, + }, + }; + handleChange(index, newIcon); + }} + /> +
+ {icon?.shadow?.enabled && ( +
+
+

+ X +

+ { + const newIcon = { + ...icon, + shadow: { + ...icon.shadow, + position: { + ...icon?.shadow?.position, + x: target?.value + ? Number(target.value) + : undefined, + }, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+

+ Y +

+ { + const newIcon = { + ...icon, + shadow: { + ...icon.shadow, + position: { + ...icon?.shadow?.position, + y: target?.value + ? Number(target.value) + : undefined, + }, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+

+ +

+ { + const newIcon = { + ...icon, + shadow: { + ...icon.shadow, + position: { + ...icon?.shadow?.position, + blur: target?.value + ? Number(target.value) + : undefined, + }, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+

+ +

+ { + const newIcon = { + ...icon, + shadow: { + ...icon.shadow, + colour: c, + }, + }; + handleChange(index, newIcon); + }} + /> +
+
+ )} +
+ +
+
+ )} +
+ {totalIcons > 1 && ( +
+ {index !== 0 && ( + onChangeOrder(index, index - 1)} + /> + )} + {index !== totalIcons - 1 && ( + onChangeOrder(index, index + 1)} + /> + )} +
+ )} +
+ ); +}; diff --git a/components/editor-icons/comps/icons/index.tsx b/components/editor-icons/comps/icons/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8411ff6a36809c4f2b95b7f53054d719939fc906 --- /dev/null +++ b/components/editor-icons/comps/icons/index.tsx @@ -0,0 +1,107 @@ +import { useState } from "react"; +import { PlusIcon } from "@heroicons/react/solid"; + +import { EditorType, IconType } from "@/types/editor"; +import { Empty } from "@/components/empty"; + +import { IconSelected } from "./icon-selected"; +import { FormattedMessage, useIntl } from "react-intl"; + +export const Icons = ({ + editor, + onChange, + onStep, +}: { + editor: EditorType; + onChange: (e: EditorType) => void; + onStep: (step: number, multiple?: boolean) => void; +}) => { + const [opened, setOpened] = useState(0); + const intl = useIntl(); + + const handleChange = (index: number, icon: any) => { + const newIcons = [...editor.icons]; + newIcons[index] = icon; + onChange({ + ...editor, + icons: newIcons, + }); + }; + + const handleDeleteIcon = (index: number) => { + const newIcons = [...editor.icons]; + newIcons.splice(index, 1); + onChange({ + ...editor, + icons: newIcons, + }); + }; + + const handleChangeOrder = (index: number, value: number) => { + const newIcons = [...editor.icons]; + const [removed] = newIcons.splice(index, 1); + newIcons.splice(value, 0, removed); + onChange({ + ...editor, + icons: newIcons, + }); + }; + + return ( +
+

+ +

+ {editor?.icons?.length > 0 ? ( + <> +
onStep(0, true)} + > +
+
+ +
+
+

+ +

+

+ +

+
+
+
+ {editor?.icons?.map((icon: IconType, k) => { + return ( + + ); + })} + + ) : ( + onStep(0)} + /> + )} +
+ ); +}; diff --git a/components/editor-icons/comps/list/index.tsx b/components/editor-icons/comps/list/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16300b14fe62a8709c6d1c93134357ac69b7b833 --- /dev/null +++ b/components/editor-icons/comps/list/index.tsx @@ -0,0 +1,151 @@ +import { ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/solid"; +import { useContext, useRef } from "react"; + +import { useResults } from "components/search/hooks/useSearch"; +import { ListItem } from "components/editor-icons/comps/list/list-item"; +import { IconItem } from "types/editor"; +import { Empty } from "components/empty"; +import { login, useUser } from "utils/auth"; +import { PremiumContext } from "components/premium/premium"; +import { Premium } from "@/components/premium"; +import classNames from "classnames"; +import { toBase64 } from "@/components/upload"; +import { FormattedMessage } from "react-intl"; + +export const ListIcons = ({ + onSelect, + onCustomText, + onCustomUpload, +}: { + onSelect: (e: IconItem) => void; + onCustomText: () => void; + onCustomUpload: (s: string) => void; +}) => { + const { user } = useUser(); + const { setOpen } = useContext(PremiumContext); + const inputRef = useRef(null); + + const { results, page, maxPage, totalItems, setPage } = useResults(); + + const handleCustomUpload = (e: any) => { + const file = e.target.files[0]; + if (file) { + toBase64(file).then((res: any) => { + onCustomUpload(res); + }); + } + }; + + return ( +
+
+

+ totalItems }} + /> +

+ +
+ + +
+
+
+ {results?.map((result: any, key: number) => ( + { + // if (icon?.isPremium && !user?.id) return setOpen(true); + onSelect(icon); + }} + /> + ))} + {results?.length === 0 && ( + { + if (!user?.id) return login(); + // send to api a request; + console.log("display Modal and call API"); + }} + button="Ask for a new Icon" + /> + )} +
+ {results?.length > 0 && ( +
+ +
+

+ page, + maxPage: () => maxPage, + }} + /> +

+
+ +
+ )} +
+ ); +}; diff --git a/components/editor-icons/comps/list/list-item.tsx b/components/editor-icons/comps/list/list-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2f0379ec6a705ef33d94b986330179c6143ff01f --- /dev/null +++ b/components/editor-icons/comps/list/list-item.tsx @@ -0,0 +1,86 @@ +import classNames from "classnames"; +import React, { useEffect, useRef } from "react"; + +export const ListItem = ({ + icon, + tooltip = true, + hoverable = true, + fill = "#b0b0b0", + onSelect, +}: { + icon: any; + tooltip?: boolean; + hoverable?: boolean; + fill?: string; + onSelect: (e: any) => void; +}) => { + const ref = useRef(null); + const svgRef = useRef(null); + const [viewBox, setViewBox] = React.useState("0 0 24 24"); + const renderComponent = () => { + const Component = icon?.component as any; + return ; + }; + + useEffect(() => { + return setViewBox(ref?.current?.getAttribute("viewBox") ?? "0 0 200 200"); + }); + + return ( +
onSelect(icon)} + > + {/* {icon?.isPremium && ( +
+ )} */} + + {renderComponent()} + + {hoverable && ( +
+ )} + {/* {tooltip && ( + <> +
+
+
+
+ {icon?.tags?.join(", ")} +
+
+ + )} */} +
+ ); +}; diff --git a/components/editor-icons/comps/shapes/index.tsx b/components/editor-icons/comps/shapes/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bf7ce3b83706653836fd05b387a627116c84ca34 --- /dev/null +++ b/components/editor-icons/comps/shapes/index.tsx @@ -0,0 +1,98 @@ +import { useContext } from "react"; + +import { EditorType } from "types/editor"; +import { SHAPES } from "components/svg/shapes"; +import classNames from "classnames"; +import { Empty } from "@/components/empty"; +import { useUser } from "@/utils/auth"; + +import { ShapeSelected } from "./shape-selected"; +import { PremiumContext } from "@/components/premium/premium"; +import { FormattedMessage, useIntl } from "react-intl"; + +export const Shapes = ({ + editor, + onChange, +}: { + editor: EditorType; + onChange: (s: any) => void; +}) => { + const { user } = useUser(); + const intl = useIntl(); + + const { setOpen } = useContext(PremiumContext); + + return ( + <> +

+ +

+
+ {SHAPES.map((shape, k) => ( +
{ + // if (!user && shape?.isPremium) { + // return setOpen(true); + // } + onChange({ ...editor.shape, component: shape?.name }); + }} + > + {/* {shape?.isPremium && ( +
+ )} */} + <_ShapeComponent + component={shape.component} + width={28} + height={28} + shape={shape} + /> +
+
+ ))} +
+ {editor.shape?.component ? ( + onChange({ ...editor.shape, ...shape })} + onDelete={() => onChange({ ...editor.shape, component: null })} + /> + ) : ( + onStep(0)}>Add my first Icon
} + /> + )} + + ); +}; + +const _ShapeComponent = ({ component, shape, onClick, ...props }: any) => { + const ShapeComponent = component as any; + const newShape = { + ...shape, + component: `${shape.name}-small`, + }; + return ; +}; diff --git a/components/editor-icons/comps/shapes/shape-selected.tsx b/components/editor-icons/comps/shapes/shape-selected.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7b76940b297c9e63e1f549c6a34cee1260f169b8 --- /dev/null +++ b/components/editor-icons/comps/shapes/shape-selected.tsx @@ -0,0 +1,307 @@ +import classNames from "classnames"; +import { useState } from "react"; +import { + faChevronDown, + faChevronRight, + faTrash, + faCaretDown, + faCaretUp, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { ColorPicker } from "components/color-picker"; +import { Range } from "components/range"; +import { UploadImage } from "@/components/upload"; +import { Switch } from "@/components/switch"; +import { login, useUser } from "@/utils/auth"; +import { Premium } from "@/components/premium"; +import { PremiumOverlay } from "@/components/premium/overlay"; +import { FormattedMessage, useIntl } from "react-intl"; +import { Label } from "@/components/label"; + +export const ShapeSelected = ({ + shape, + handleChange, + onDelete, +}: { + shape: any; + handleChange: (s: any) => void; + onDelete: () => void; +}) => { + const { user } = useUser(); + const intl = useIntl(); + const [open, setOpen] = useState(true); + + return ( +
+
setOpen(!open)} + > +

+ +

+
+
{ + e.preventDefault(); + e.stopPropagation(); + onDelete(); + }} + > + +
+
+ +
+
+
+ {open && ( +
+
+ +
+

Z

+ { + const newShape = { + ...shape, + position: { ...shape.position, angle: Number(value) }, + }; + handleChange(newShape); + }} + /> + { + const newShape = { + ...shape, + position: { + ...shape.position, + angle: target.value + ? Number(target.value) > 360 + ? 360 + : Number(target.value) + : undefined, + }, + }; + handleChange(newShape); + }} + /> +
+
+ {shape?.component === "Square" && ( +
+ +
+

+ Z +

+ { + const newShape = { + ...shape, + radius: Number(value), + }; + handleChange(newShape); + }} + /> + { + const newShape = { + ...shape, + radius: Number(target.value), + }; + handleChange(newShape); + }} + /> +
+
+ )} +
+
+ + { + const newShape = { + ...shape, + transparency: !transparency, + }; + handleChange(newShape); + }} + /> +
+ { + let newShape = { ...shape, stringColor: c }; + if (c.includes("gradient")) { + const { colors, degrees } = datas; + const gradientType = c?.startsWith("linear") + ? "linearGradient" + : "radialGradient"; + + const angle = c?.startsWith("linear") + ? c?.replace("linear-gradient(", "")?.split("deg")?.[0] + : 90; + + newShape["gradient"] = { + ...newShape.gradient, + enabled: true, + colours: colors, + angle, + type: gradientType, + }; + } else { + newShape = { + ...newShape, + colour: c, + gradient: { + ...shape?.gradient, + enabled: false, + }, + }; + } + + handleChange(newShape); + }} + /> +
+
+
+
+ + { + const newShape = { + ...shape, + image: { + ...shape.image, + enabled, + }, + }; + handleChange(newShape); + }} + /> +
+ {shape?.image?.enabled && ( + { + const newShape = { + ...shape, + image: { + ...shape.image, + urls, + }, + }; + handleChange(newShape); + }} + /> + )} +
+
+ +
+
+

+ +

+ { + const newShape = { + ...shape, + border: { + ...shape.border, + width: target?.value + ? Number(target.value) + : undefined, + }, + }; + handleChange(newShape); + }} + /> +
+
+

+ +

+ { + const newShape = { + ...shape, + border: { + ...shape.border, + colour: c, + }, + }; + handleChange(newShape); + }} + /> +
+
+
+ +
+
+ )} +
+ ); +}; diff --git a/components/editor-icons/comps/tabs.tsx b/components/editor-icons/comps/tabs.tsx new file mode 100644 index 0000000000000000000000000000000000000000..64da7465db2392f05bdf7e9412d6276ac913b70a --- /dev/null +++ b/components/editor-icons/comps/tabs.tsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import classNames from "classnames"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faSearch, + faArrowsUpDownLeftRight, + faPalette, + faShapes, + faIcons, +} from "@fortawesome/free-solid-svg-icons"; +import { FormattedMessage } from "react-intl"; + +const TABS_ELEMENTS = [ + { + icon: faSearch, + name: "iconsEditor.editor.tabs.listIcons", + }, + { + icon: faShapes, + name: "iconsEditor.editor.tabs.shape", + }, + { + icon: faIcons, + name: "iconsEditor.editor.tabs.icons", + }, +]; + +export const EditorTabs = ({ + current, + onChange, +}: { + current: number; + onChange: (e: number) => void; +}) => { + return ( + // p-2 lg:p-4 +
+
    + {TABS_ELEMENTS.map((tab, index) => ( +
  • onChange(index)} + > + +

    + +

    +
  • + ))} +
+
+ ); +}; diff --git a/components/editor-icons/editor.constants.ts b/components/editor-icons/editor.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..a204ed30c3f6424e4b5584e1e4d8f09265dc3517 --- /dev/null +++ b/components/editor-icons/editor.constants.ts @@ -0,0 +1,50 @@ +import { EditorType } from "types/editor"; + +export const DEFAULT_VALUE: EditorType = { + icons: [ + { + component: "King", + colour: "#FFF", + gradient: { + enabled: false, + colours: [], + angle: 0, + type: "linear-gradient", + }, + border: { + width: 0, + colour: "#FFFFFF", + }, + shadow: { + enabled: false, + colour: "#FEE75C", + position: { + x: 0, + y: 0, + blur: 16, + }, + }, + transparency: 100, + }, + ], + shape: { + component: "Badge", + colour: "#5865F1", + gradient: { + enabled: false, + colours: [], + angle: 90, + type: "linear-gradient", + }, + position: { + angle: 0, + scale: 1, + }, + radius: 40, + border: { + width: 0, + colour: "#ffffff", + }, + transparency: false, + }, +}; diff --git a/components/editor-icons/icon.tsx b/components/editor-icons/icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4d126c6ffaa05188d3ecb10b05c9d1f1b89cddcf --- /dev/null +++ b/components/editor-icons/icon.tsx @@ -0,0 +1,200 @@ +import { Fragment, useRef, useState } from "react"; +import { useMount, useUpdateEffect } from "react-use"; +import { motion } from "framer-motion"; +import saveAsPng from "save-svg-as-png"; + +import { EditorType, IconItem } from "types/editor"; +import Shapes from "@/components/svg/shapes"; +import { Icons } from "components/svg/icons"; +import { Null } from "components/svg/shapes/null"; + +export const Icon = ({ + editor, + size = 200, + id, + onChange, + onChangeTab, +}: { + editor: EditorType; + id?: string; + size?: number; + onChange?: (e: EditorType) => void; + onChangeTab?: (e: number) => void; +}) => { + const shapeRef = useRef(null); + const ComponentShape = + Shapes[editor.shape?.component as keyof typeof Shapes] ?? Null; + + return ( + + {editor?.icons?.map((icon, k) => { + const findIcon = Icons?.find( + (i: IconItem) => icon.component === i.name + ); + const customText = icon?.custom_text?.enabled; + const customImage = icon?.image; + const GradientType = + icon?.gradient?.type === "radialGradient" + ? "radialGradient" + : "linearGradient"; + if (!findIcon && !customText && !customImage) return null; + return ( + + + + {icon?.gradient?.enabled ? ( + <> + {icon?.gradient?.colours?.map((color, c) => ( + + ))} + + ) : ( + + )} + + + <_IconComponent + component={findIcon?.component} + shapeRef={shapeRef} + shape={editor?.shape?.component} + key={k} + index={k} + icon={icon} + onChange={(e: any) => + onChange && + onChange({ + ...editor, + icons: editor.icons.map((i: any, index: number) => + index === k ? e : i + ), + }) + } + onClick={onChangeTab ? () => onChangeTab(2) : undefined} + /> + + ); + })} + + ); +}; + +const _IconComponent = ({ + shapeRef, + component, + icon, + shape, + index, + onChange, + ...props +}: any) => { + const iconRef = useRef(null); + const [position, setPosition] = useState(null); + + useUpdateEffect(() => { + const position = setIconPosition(iconRef, shapeRef); + setPosition(position); + const svg = document.getElementById("discotools-selected-svg"); + saveAsPng.svgAsPngUri(svg).then(() => {}); + }, [shapeRef.current, icon.component, icon?.image]); + + useMount(() => { + const position = setIconPosition(iconRef, shapeRef); + setPosition(position); + }); + + useUpdateEffect(() => { + onChange({ + ...icon, + position, + }); + }, [position]); + + const IconComponent = component as any; + + return ( + + {icon?.custom_text?.enabled ? ( + + {icon?.custom_text?.text} + + ) : icon?.image ? ( + + ) : ( + + )} + + ); +}; + +const setIconPosition = (iconRef: any, shapeRef: any) => { + if (iconRef?.current) { + const shapeBoxW = shapeRef?.current?.getAttribute("viewBox")?.split(" ")[2]; + const shapeBoxH = shapeRef?.current?.getAttribute("viewBox")?.split(" ")[3]; + const iconBoxW = iconRef?.current?.getAttribute("viewBox")?.split(" ")[2]; + const iconBoxH = iconRef?.current?.getAttribute("viewBox")?.split(" ")[3]; + + const position = { + x: shapeBoxW / 2 - iconBoxW / 2, + y: shapeBoxH / 2 - iconBoxH / 2, + xPath: Number(shapeBoxW - iconBoxW), + yPath: Number(shapeBoxH - iconBoxH), + }; + return position; + } +}; diff --git a/components/editor-icons/index.tsx b/components/editor-icons/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a99b1a6ea19c9fa6f48f0774b41ea4ec824add79 --- /dev/null +++ b/components/editor-icons/index.tsx @@ -0,0 +1,317 @@ +import { useContext, useState } from "react"; +import Image from "next/image"; +import { useTour } from "@reactour/tour"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faDownload } from "@fortawesome/free-solid-svg-icons"; +import saveAsPng from "save-svg-as-png"; + +import { EditorType, IconItem } from "types/editor"; +import { Icon } from "./icon"; +import { EditorTabs } from "components/editor-icons/comps/tabs"; +import { ListIcons } from "components/editor-icons/comps/list"; +import { Search } from "components/search"; +import { ClydeMessage } from "components/clyde/message"; +import { Switch } from "components/switch"; +import { Shapes } from "@/components/editor-icons/comps/shapes"; +import { Icons } from "components/editor-icons/comps/icons"; +import { useUser } from "utils/auth"; +import { PremiumContext } from "components/premium/premium"; + +import DefaultAvatar from "assets/images/avatars/default-avatar.svg"; +import DefaultAvatar2 from "assets/images/avatars/default-avatar-2.svg"; +import BackgroundTransparent from "assets/images/editor/transparent_bg.svg"; +import { API } from "@/utils/api"; +import { Premium } from "../premium"; +import { FormattedMessage, useIntl } from "react-intl"; + +export default function Editor({ + editor, + onChange, +}: { + editor: EditorType; + onChange: (e: EditorType) => void; +}) { + const intl = useIntl(); + const { user } = useUser(); + const { setOpen } = useContext(PremiumContext); + const { setIsOpen } = useTour(); + + const [currentTab, setCurrentTab] = useState(0); + const [multiIcons, setMultiIcons] = useState(false); + const [search, setSearch] = useState(""); + + const renderTabs = (currentTab: number) => { + switch (currentTab) { + case 0: + return ( + <> + +
+ setOpen(true) : setMultiIcons} + /> +

+ + {/* {!user?.id ? : ""} */} +

+
+ { + // if (!user?.id) return setOpen(true); + onChange({ + ...editor, + icons: multiIcons + ? [ + ...editor.icons, + { + colour: "#ffffff", + custom_text: { + enabled: true, + text: "10", + size: 160, + }, + }, + ] + : [ + { + ...editor?.icons[0], + component: undefined, + position: undefined, + colour: "#ffffff", + custom_text: { + enabled: true, + text: "10", + size: 160, + }, + }, + ], + }); + if (!multiIcons) setCurrentTab(2); + }} + onCustomUpload={(image) => { + onChange({ + ...editor, + icons: multiIcons + ? [ + ...editor.icons, + { + colour: "#ffffff", + image, + }, + ] + : [ + { + ...editor?.icons[0], + custom_text: undefined, + component: undefined, + position: undefined, + colour: "#ffffff", + image, + }, + ], + }); + if (!multiIcons) setCurrentTab(2); + }} + onSelect={(icon: IconItem) => { + onChange({ + ...editor, + icons: multiIcons + ? [ + ...editor.icons, + { + component: icon.name, + colour: icon?.defaultColor ?? "#fff", + }, + ] + : [ + { + ...editor?.icons[0], + colour: icon?.defaultColor ?? "#fff", + component: icon.name, + position: undefined, + custom_text: undefined, + }, + ], + }); + }} + /> + + ); + case 1: + return ( + <> + + onChange({ ...editor, shape: { ...editor.shape, ...newShape } }) + } + /> + + ); + case 2: + return ( + <> + { + setCurrentTab(tabIndex); + setMultiIcons(user?.id ? allowMultiple || false : false); + }} + /> + + ); + default: + return null; + } + }; + + const handleSaveIcon = () => { + const svg = document.getElementById("discotools-selected-svg"); + + saveAsPng.saveSvgAsPng(svg, "discotools-xyz-icon.png", { + scale: 1.25, + encoderOptions: 1, + }); + // API.download(); + }; + + const previewAvatar = () => { + if (user?.id) { + if (user?.avatar) + return `https://cdn.discordapp.com/avatars/${user?.id}/${user?.avatar}.png`; + else + return `https://cdn.discordapp.com/embed/avatars/${ + user.discriminator % 5 + }.png`; + } + return DefaultAvatar.src; + }; + + const renderColor = () => { + if (editor?.shape?.gradient?.enabled) { + const color = editor?.shape?.gradient?.colours?.[0]?.value; + if (color.includes("rgba")) { + const rgba = color?.split(","); + return `rgb(${rgba[0].split("(")[1]}, ${rgba[1]}, ${rgba[2]})`; + } + return color; + } + if (editor?.shape?.colour?.includes("rgba")) { + const rgba = editor?.shape?.colour?.split(","); + return `rgb(${rgba[0].split("(")[1]}, ${rgba[1]}, ${rgba[2]})`; + } + if (editor?.shape?.colour) { + return editor.shape.colour; + } + return "#fff"; + }; + + return ( +
+ setIsOpen(true)} + /> +
{ + // e.preventDefault(); + // e.stopPropagation(); + }} + > +
+ +
+ {renderTabs(currentTab)} +
+
+
+
+ setCurrentTab(i)} + /> +
+
+
+
+
+ Default Avatar for user +
+
+

+ {user?.username ?? "Captain Astro"} +

+ + +

+ Today at 11:23 pm +

+
+

+ +

+
+
+
+ Second user without custom roles icons +
+
+

+ Rookie +

+

+ Today at 11:23 pm +

+
+

+ user?.username ?? "Captain Astro", + }} + /> +

+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/components/empty/index.tsx b/components/empty/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..eb53185133c089946354ada898ac8f57e3a03d4d --- /dev/null +++ b/components/empty/index.tsx @@ -0,0 +1,46 @@ +import Image from "next/image"; +import ClydeAffraid from "assets/images/clyde/affraid.svg"; + +export const Empty = ({ + title, + description, + button, + className, + action, +}: { + title?: string; + description?: string; + button?: any; + className?: string; + action?: () => void; +}) => { + return ( +
+ {/* No results, clyde is sad :( */} + {title && ( +

+ {title} +

+ )} + {title && ( +

+ {description} +

+ )} + {button && ( +
null} + > + {button} +
+ )} +
+ ); +}; diff --git a/components/font-family/font-family.constants.ts b/components/font-family/font-family.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..80878c1df9aad1d5ad638ccc1c2a98c6bcb3fb5f --- /dev/null +++ b/components/font-family/font-family.constants.ts @@ -0,0 +1,145 @@ +import { + Montserrat, + Cousine, + Roboto, + Lato, + Oswald, + Kanit, + Fugaz_One, + Roboto_Slab, + Libre_Baskerville, + Bree_Serif, + Pacifico, + Permanent_Marker, + Yellowtail, + Scheherazade_New, +} from "next/font/google"; + +const montserrat = Montserrat({ + subsets: ["latin"], + weight: ["400", "700"], +}); + +const cousine = Cousine({ + subsets: ["latin"], + weight: ["400", "700"], +}); + +const roboto = Roboto({ + subsets: ["latin"], + weight: ["400", "700", "100", "300", "500", "900"], +}); + +const lato = Lato({ + subsets: ["latin"], + weight: ["100", "300", "400", "700", "900"], +}); + +const oswald = Oswald({ + subsets: ["latin"], + weight: ["200", "300", "400", "500", "600", "700"], +}); + +const kanit = Kanit({ + subsets: ["latin"], + weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], +}); + +const fugaz_one = Fugaz_One({ + subsets: ["latin"], + weight: ["400"], +}); + +const roboto_slab = Roboto_Slab({ + subsets: ["latin"], + weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], +}); + +const libre_baskerville = Libre_Baskerville({ + subsets: ["latin"], + weight: ["400", "700"], +}); + +const bree_serif = Bree_Serif({ + subsets: ["latin"], + weight: ["400"], +}); + +const pacifico = Pacifico({ + subsets: ["latin"], + weight: ["400"], +}); + +const permanent_marker = Permanent_Marker({ + subsets: ["latin"], + weight: ["400"], +}); + +const yellowtail = Yellowtail({ + subsets: ["latin"], + weight: ["400"], +}); + +const scheherazade_new = Scheherazade_New({ + subsets: ["latin"], + weight: ["400", "700"], +}); + +export const FONT_FAMILY = [ + { + label: "Montserrat", + value: montserrat.className, + }, + { + label: "Cousine", + value: cousine.className, + }, + { + label: "Roboto", + value: roboto.className, + }, + { + label: "Lato", + value: lato.className, + }, + { + label: "Oswald", + value: oswald.className, + }, + { + label: "Kanit", + value: kanit.className, + }, + { + label: "Fugaz One", + value: fugaz_one.className, + }, + { + label: "Roboto Slab", + value: roboto_slab.className, + }, + { + label: "Libre Baskerville", + value: libre_baskerville.className, + }, + { + label: "Bree Serif", + value: bree_serif.className, + }, + { + label: "Pacifico", + value: pacifico.className, + }, + { + label: "Permanent Marker", + value: permanent_marker.className, + }, + { + label: "Yellowtail", + value: yellowtail.className, + }, + { + label: "Scheherazade New", + value: scheherazade_new.className, + }, +]; diff --git a/components/font-family/index.tsx b/components/font-family/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4f638b4eb45d6b3531ae7b782f759978cef2ea51 --- /dev/null +++ b/components/font-family/index.tsx @@ -0,0 +1,57 @@ +import { useRef, useState } from "react"; +import { useClickAway } from "react-use"; +import classNames from "classnames"; +import { ChevronDownIcon } from "@heroicons/react/solid"; + +import { FONT_FAMILY } from "./font-family.constants"; + +export const FontFamilySelector = ({ + value, + onSelect, +}: { + value?: any; + onSelect: (t: string) => void; +}) => { + const [open, setOpen] = useState(false); + const ref = useRef(null); + const selected = FONT_FAMILY.find((f) => f.label === value); + + useClickAway(ref, () => setOpen(false)); + + return ( +
+
setOpen(!open)} + > + {selected?.label} + +
+
+
+ {FONT_FAMILY.map((font, f) => ( +
onSelect(font.label)} + > + {font.label} +
+ ))} +
+
+
+ ); +}; diff --git a/components/font-weight/font-weight.constants.ts b/components/font-weight/font-weight.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..de5ce8e16e1e5fd04c9065371239bee864a372ef --- /dev/null +++ b/components/font-weight/font-weight.constants.ts @@ -0,0 +1,38 @@ +export const FONT_WEIGHT = [ + { + label: "Thin", + value: "100", + }, + { + label: "Extra Light", + value: "200", + }, + { + label: "Light", + value: "300", + }, + { + label: "Regular", + value: "400", + }, + { + label: "Medium", + value: "500", + }, + { + label: "Semi Bold", + value: "600", + }, + { + label: "Bold", + value: "700", + }, + { + label: "Extra Bold", + value: "800", + }, + { + label: "Black", + value: "900", + }, +]; diff --git a/components/font-weight/index.tsx b/components/font-weight/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d75a09c217175a2c51d493a3bc433b6bd57ebc25 --- /dev/null +++ b/components/font-weight/index.tsx @@ -0,0 +1,57 @@ +import { useRef, useState } from "react"; +import { useClickAway } from "react-use"; +import classNames from "classnames"; +import { ChevronDownIcon } from "@heroicons/react/solid"; + +import { FONT_WEIGHT } from "./font-weight.constants"; + +export const FontWeight = ({ + value, + onSelect, +}: { + value?: any; + onSelect: (t: string) => void; +}) => { + const [open, setOpen] = useState(false); + const ref = useRef(null); + const selected = FONT_WEIGHT.find((f) => f.value === value); + + useClickAway(ref, () => setOpen(false)); + + return ( +
+
setOpen(!open)} + > + {selected?.label} + +
+
+
+ {FONT_WEIGHT.map((font, f) => ( +
onSelect(font.value)} + > + {font.label} +
+ ))} +
+
+
+ ); +}; diff --git a/components/footer/index.tsx b/components/footer/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1192e89e8a5988677cfad0b9885494d1f810af06 --- /dev/null +++ b/components/footer/index.tsx @@ -0,0 +1,128 @@ +import Image from "next/image"; +import LinkComp from "next/link"; +import { FormattedMessage } from "react-intl"; +import moment from "moment"; + +import BannerFooter from "assets/images/banner_footer.svg"; +import { SelectLanguages } from "./select-languages"; + +export const Footer = () => { + return ( +
+
+ +
+
+
+
+
+
+ + logo + discotools.xyz + + +
+
+

+ +

+ +
+
+
+
+

+ {moment().format("YYYY")} © DiscoTools.xyz -{" "} + +

+
+
+ Designed and Developed with 💙
+ by{" "} + + en-zo.dev + +
+
+
+
+ {/*
+ + logo + discotools.xyz + + +
+
+ Designed and Developed with 💙 by{" "} + + en-zo.dev + +
*/} +
+ ); +}; diff --git a/components/footer/select-languages.tsx b/components/footer/select-languages.tsx new file mode 100644 index 0000000000000000000000000000000000000000..59ae711eaf5ba71d2230a3e834bdd3c07f1e9e9a --- /dev/null +++ b/components/footer/select-languages.tsx @@ -0,0 +1,70 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCaretDown } from "@fortawesome/free-solid-svg-icons"; +import { useRef, useState } from "react"; +import { useClickAway } from "react-use"; +import router, { useRouter } from "next/router"; + +import FLAGS from "components/langs"; +import classNames from "classnames"; + +export const SelectLanguages = () => { + const { locale } = useRouter(); + + const ref = useRef(null); + const [open, setOpen] = useState(false); + + useClickAway(ref, () => setOpen(false)); + + const country = FLAGS.find((flag) => flag.code === locale) ?? FLAGS[0]; + const SelectedCountry = country?.icon || (() => null); + + return ( +
+
setOpen(!open)} + > + + {country.name} + +
+
+ {FLAGS.map((lang) => { + const Component = lang.icon; + return ( +
{ + // change locale of the app + router.push(router.asPath, router.asPath, { + locale: lang.code, + }); + setOpen(false); + }} + > + + {lang.name} +
+ ); + })} +
+
+ ); +}; diff --git a/components/icon-picker/index.tsx b/components/icon-picker/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dc7358a3b678df8b9af4fc822ef7e752c16ed9a9 --- /dev/null +++ b/components/icon-picker/index.tsx @@ -0,0 +1,137 @@ +import { useEffect, useRef, useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { Icons } from "@/components/svg/icons"; +import { IconItem } from "@/types/editor"; +import { Input } from "@/components/input"; +import { + useResults, + useSingleSearch, +} from "@/components/search/hooks/useSearch"; +import { ListItem } from "@/components/editor-icons/comps/list/list-item"; +import { Empty } from "@/components/empty"; +import { ArrowLeftIcon } from "@heroicons/react/solid"; +import { ArrowRightIcon } from "@heroicons/react/solid"; +import { useClickAway } from "react-use"; +import classNames from "classnames"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTrash } from "@fortawesome/free-solid-svg-icons"; + +export const IconPicker = ({ + icon, + onSelect, + children, +}: { + onSelect: (i?: string) => void; + icon?: string; + children?: React.ReactNode; +}) => { + const [open, setOpen] = useState(false); + const svgRef = useRef(null); + const container = useRef(null); + const intl = useIntl(); + const [viewBox, setViewBox] = useState("0 0 24 24"); + const [search, setSearch] = useState(""); + useSingleSearch(search); + const { results, page, maxPage, totalItems, setPage } = useResults(21); + + const findIcon = Icons?.find((i: IconItem) => icon === i.name); + const IconComponent = findIcon?.component as any; + + useEffect(() => { + return setViewBox( + svgRef?.current?.getAttribute("viewBox") ?? "0 0 200 200" + ); + }, [icon]); + + useClickAway(container, () => setOpen(false)); + + return ( +
+
setOpen(!open)} + > + + + +
+
+ +
+ {results?.map((result: any, key: number) => ( + { + // if (icon?.isPremium && !user?.id) return setOpen(true); + // onSelect(icon); + onSelect(icon.name); + }} + /> + ))} + {results?.length === 0 && ( + { + // if (!user?.id) return login(); + // send to api a request; + }} + button="Ask for a new Icon" + /> + )} +
+ {results?.length > 0 && ( +
+ +
+

+ page, + maxPage: () => maxPage, + }} + /> +

+
+ +
+ )} +
+
+ ); +}; diff --git a/components/input/index.tsx b/components/input/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d6904e4af6f23a756e27a9ed6c334f994bc7105a --- /dev/null +++ b/components/input/index.tsx @@ -0,0 +1,34 @@ +export const Input = ({ + type = "string", + value, + className, + prefix, + placeholder, + onChange, +}: { + type?: string; + placeholder?: string; + prefix?: string; + className?: string; + value?: string | number; + onChange: (e: string | number) => void; +}) => { + return type === "textarea" ? ( +