From a1fa936802ee3044231760c6bdf7cdac19c7c368 Mon Sep 17 00:00:00 2001 From: xuzuodong Date: Mon, 12 Aug 2024 18:02:56 +0800 Subject: [PATCH] fix: lint error --- .../components/app/create-app-modal/index.tsx | 7 +- .../base/app-icon-picker/Uploader.tsx | 157 +++++++-------- .../components/base/app-icon-picker/hooks.tsx | 80 ++++---- .../components/base/app-icon-picker/index.tsx | 184 +++++++++--------- .../components/base/app-icon-picker/utils.ts | 156 +++++++-------- web/app/components/base/app-icon/index.tsx | 16 +- .../components/base/emoji-picker/index.tsx | 14 +- web/types/app.ts | 1 - 8 files changed, 309 insertions(+), 306 deletions(-) diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 6c693f0616ee52..38acf738a6da5d 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -8,6 +8,7 @@ import { } from '@remixicon/react' import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' +import AppIconPicker from '../../base/app-icon-picker' import s from './style.module.css' import cn from '@/utils/classnames' import AppsContext, { useAppContext } from '@/context/app-context' @@ -18,7 +19,6 @@ import { createApp } from '@/service/apps' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import AppIcon from '@/app/components/base/app-icon' -import AppIconPicker from "../../base/app-icon-picker" import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' @@ -63,7 +63,8 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { return isCreatingRef.current = true try { - if (appIcon.type === 'image') throw new Error('unimplemented') + if (appIcon.type === 'image') + throw new Error('unimplemented') const app = await createApp({ name, description, @@ -82,7 +83,7 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } isCreatingRef.current = false - }, [name, notify, t, appMode, appIcon.icon, appIcon.background, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor]) + }, [name, notify, t, appMode, appIcon.icon, appIcon.background, appIcon.type, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor]) return ( void +type UploaderProps = { + className?: string + onImageCropped?: (tempUrl: string, croppedAreaPixels: Area, fileName: string) => void } const Uploader: FC = ({ - className, - onImageCropped, + className, + onImageCropped, }) => { - const [inputImage, setInputImage] = useState<{ file: File, url: string }>() - useEffect(() => { - return () => { - if (inputImage) URL.revokeObjectURL(inputImage.url); - } - }, [inputImage]) + const [inputImage, setInputImage] = useState<{ file: File; url: string }>() + useEffect(() => { + return () => { + if (inputImage) + URL.revokeObjectURL(inputImage.url) + } + }, [inputImage]) - const [crop, setCrop] = useState({ x: 0, y: 0 }) - const [zoom, setZoom] = useState(1) + const [crop, setCrop] = useState({ x: 0, y: 0 }) + const [zoom, setZoom] = useState(1) - const onCropComplete = async (_: Area, croppedAreaPixels: Area) => { - if (!inputImage) return - onImageCropped?.(inputImage.url, croppedAreaPixels, inputImage.file.name) - } + const onCropComplete = async (_: Area, croppedAreaPixels: Area) => { + if (!inputImage) + return + onImageCropped?.(inputImage.url, croppedAreaPixels, inputImage.file.name) + } - const handleLocalFileInput = (e: ChangeEvent) => { - const file = e.target.files?.[0] - if (file) setInputImage({ file, url: URL.createObjectURL(file) }) - } + const handleLocalFileInput = (e: ChangeEvent) => { + const file = e.target.files?.[0] + if (file) + setInputImage({ file, url: URL.createObjectURL(file) }) + } - const { - isDragActive, - handleDragEnter, - handleDragOver, - handleDragLeave, - handleDrop - } = useDraggableUploader((file: File) => setInputImage({ file, url: URL.createObjectURL(file) })) + const { + isDragActive, + handleDragEnter, + handleDragOver, + handleDragLeave, + handleDrop, + } = useDraggableUploader((file: File) => setInputImage({ file, url: URL.createObjectURL(file) })) - const inputRef = createRef() + const inputRef = createRef() - return ( -
-
- { - !inputImage - ? <> - -
- Drop your image here, or  - - ((e.target as HTMLInputElement).value = '')} - accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')} - onChange={handleLocalFileInput} - /> -
-
Supports PNG, JPG, JPEG, WEBP and GIF
- - : - } -
-
- ) + return ( +
+
+ { + !inputImage + ? <> + +
+ Drop your image here, or  + + ((e.target as HTMLInputElement).value = '')} + accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')} + onChange={handleLocalFileInput} + /> +
+
Supports PNG, JPG, JPEG, WEBP and GIF
+ + : + } +
+
+ ) } -export default Uploader \ No newline at end of file +export default Uploader diff --git a/web/app/components/base/app-icon-picker/hooks.tsx b/web/app/components/base/app-icon-picker/hooks.tsx index a164241bba902e..b3f67c0dcae930 100644 --- a/web/app/components/base/app-icon-picker/hooks.tsx +++ b/web/app/components/base/app-icon-picker/hooks.tsx @@ -1,43 +1,43 @@ -import { useCallback, useState } from "react" +import { useCallback, useState } from 'react' export const useDraggableUploader = (setImageFn: (file: File) => void) => { - const [isDragActive, setIsDragActive] = useState(false) - - const handleDragEnter = useCallback((e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - setIsDragActive(true) - }, []) - - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - }, []) - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - setIsDragActive(false) - }, []) - - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - setIsDragActive(false) - - const file = e.dataTransfer.files[0] - - if (!file) - return - - setImageFn(file) - }, []) - - return { - handleDragEnter, - handleDragOver, - handleDragLeave, - handleDrop, - isDragActive, - } + const [isDragActive, setIsDragActive] = useState(false) + + const handleDragEnter = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragActive(true) + }, []) + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + }, []) + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragActive(false) + }, []) + + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragActive(false) + + const file = e.dataTransfer.files[0] + + if (!file) + return + + setImageFn(file) + }, [setImageFn]) + + return { + handleDragEnter, + handleDragOver, + handleDragLeave, + handleDrop, + isDragActive, + } } diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx index 48bcb0a20de86d..ba1fd83c64a8d2 100644 --- a/web/app/components/base/app-icon-picker/index.tsx +++ b/web/app/components/base/app-icon-picker/index.tsx @@ -1,114 +1,116 @@ -import { FC, useState } from "react" -import { useTranslation } from "react-i18next" -import Modal from "../modal" -import cn from '@/utils/classnames' +import type { FC } from 'react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import type { Area } from 'react-easy-crop' +import Modal from '../modal' +import Divider from '../divider' +import Button from '../button' +import { ImagePlus } from '../icons/src/vender/line/images' +import EmojiPicker from '../emoji-picker' +import { useLocalFileUploader } from '../image-uploader/hooks' +import Uploader from './Uploader' import s from './style.module.css' -import Divider from "../divider" -import Button from "../button" -import { ImagePlus } from "../icons/src/vender/line/images" -import EmojiPicker from "../emoji-picker" -import Uploader from "./Uploader" -import { AppIconType, ImageFile } from "@/types/app" -import { useLocalFileUploader } from "../image-uploader/hooks" -import { Area } from "react-easy-crop" -import getCroppedImg from "./utils" +import getCroppedImg from './utils' +import type { AppIconType, ImageFile } from '@/types/app' +import cn from '@/utils/classnames' -interface AppIconPickerProps { - onSelect?: (iconType: AppIconType, icon: string, background?: string) => void - onClose?: () => void - className?: string +type AppIconPickerProps = { + onSelect?: (iconType: AppIconType, icon: string, background?: string) => void + onClose?: () => void + className?: string } const AppIconPicker: FC = ({ - onSelect, - onClose, - className, + onSelect, + onClose, + className, }) => { - const { t } = useTranslation() + const { t } = useTranslation() - const tabs = [ - { key: 'emoji', label: t(`app.iconPicker.emoji`), icon: 🤖 }, - { key: 'image', label: t(`app.iconPicker.image`), icon: }, - ] - const [activeTab, setActiveTab] = useState('emoji') + const tabs = [ + { key: 'emoji', label: t('app.iconPicker.emoji'), icon: 🤖 }, + { key: 'image', label: t('app.iconPicker.image'), icon: }, + ] + const [activeTab, setActiveTab] = useState('emoji') - const [emoji, setEmoji] = useState<{ emoji: string, background: string }>() - const handleSelectEmoji = (emoji: string, background: string) => { - setEmoji({ emoji, background }) - } + const [emoji, setEmoji] = useState<{ emoji: string; background: string }>() + const handleSelectEmoji = (emoji: string, background: string) => { + setEmoji({ emoji, background }) + } - const [uploading, setUploading] = useState() + const [uploading, setUploading] = useState() - const { handleLocalFileUpload } = useLocalFileUploader({ - limit: 3, - disabled: false, - onUpload: (imageFile: ImageFile) => { - if (imageFile.fileId) { - setUploading(false) - onSelect?.('image', imageFile.fileId) - } - } - }) + const { handleLocalFileUpload } = useLocalFileUploader({ + limit: 3, + disabled: false, + onUpload: (imageFile: ImageFile) => { + if (imageFile.fileId) { + setUploading(false) + onSelect?.('image', imageFile.fileId) + } + }, + }) - const [imageCropInfo, setImageCropInfo] = useState<{ tempUrl: string, croppedAreaPixels: Area, fileName: string }>() - const handleImageCropped = async (tempUrl: string, croppedAreaPixels: Area, fileName: string) => { - setImageCropInfo({ tempUrl, croppedAreaPixels, fileName }) - } + const [imageCropInfo, setImageCropInfo] = useState<{ tempUrl: string; croppedAreaPixels: Area; fileName: string }>() + const handleImageCropped = async (tempUrl: string, croppedAreaPixels: Area, fileName: string) => { + setImageCropInfo({ tempUrl, croppedAreaPixels, fileName }) + } - const handleSelect = async () => { - if (activeTab === 'emoji') { - if (emoji) - onSelect?.('emoji', emoji.emoji, emoji.background) - } - else { - if (!imageCropInfo) return - setUploading(true) - const blob = await getCroppedImg(imageCropInfo.tempUrl, imageCropInfo.croppedAreaPixels) - const file = new File([blob], imageCropInfo.fileName, { type: blob.type }) - handleLocalFileUpload(file) - } + const handleSelect = async () => { + if (activeTab === 'emoji') { + if (emoji) + onSelect?.('emoji', emoji.emoji, emoji.background) + } + else { + if (!imageCropInfo) + return + setUploading(true) + const blob = await getCroppedImg(imageCropInfo.tempUrl, imageCropInfo.croppedAreaPixels) + const file = new File([blob], imageCropInfo.fileName, { type: blob.type }) + handleLocalFileUpload(file) } + } - return { }} - isShow - closable={false} - wrapperClassName={className} - className={cn(s.container, '!w-[362px] !p-0')} - > -
-
- {tabs.map(tab => ( - - ))} -
-
+ onClick={() => setActiveTab(tab.key as AppIconType)} + > + {tab.icon}   {tab.label} + + ))} + + - + - - + + - -
- + +
+ - -
- + +
+
} -export default AppIconPicker \ No newline at end of file +export default AppIconPicker diff --git a/web/app/components/base/app-icon-picker/utils.ts b/web/app/components/base/app-icon-picker/utils.ts index 3fd2f970f4241a..0c90e96febda83 100644 --- a/web/app/components/base/app-icon-picker/utils.ts +++ b/web/app/components/base/app-icon-picker/utils.ts @@ -1,98 +1,98 @@ export const createImage = (url: string) => - new Promise((resolve, reject) => { - const image = new Image() - image.addEventListener('load', () => resolve(image)) - image.addEventListener('error', (error) => reject(error)) - image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox - image.src = url - }) + new Promise((resolve, reject) => { + const image = new Image() + image.addEventListener('load', () => resolve(image)) + image.addEventListener('error', error => reject(error)) + image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox + image.src = url + }) export function getRadianAngle(degreeValue: number) { - return (degreeValue * Math.PI) / 180 + return (degreeValue * Math.PI) / 180 } /** * Returns the new bounding area of a rotated rectangle. */ export function rotateSize(width: number, height: number, rotation: number) { - const rotRad = getRadianAngle(rotation) + const rotRad = getRadianAngle(rotation) - return { - width: + return { + width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height), - height: + height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height), - } + } } /** * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop */ export default async function getCroppedImg( - imageSrc: string, - pixelCrop: { x: number; y: number; width: number; height: number }, - rotation = 0, - flip = { horizontal: false, vertical: false } + imageSrc: string, + pixelCrop: { x: number; y: number; width: number; height: number }, + rotation = 0, + flip = { horizontal: false, vertical: false }, ): Promise { - const image = await createImage(imageSrc) - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - - if (!ctx) - throw new Error('Could not create a canvas context') - - const rotRad = getRadianAngle(rotation) - - // calculate bounding box of the rotated image - const { width: bBoxWidth, height: bBoxHeight } = rotateSize( - image.width, - image.height, - rotation - ) - - // set canvas size to match the bounding box - canvas.width = bBoxWidth - canvas.height = bBoxHeight - - // translate canvas context to a central location to allow rotating and flipping around the center - ctx.translate(bBoxWidth / 2, bBoxHeight / 2) - ctx.rotate(rotRad) - ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1) - ctx.translate(-image.width / 2, -image.height / 2) - - // draw rotated image - ctx.drawImage(image, 0, 0) - - const croppedCanvas = document.createElement('canvas') - - const croppedCtx = croppedCanvas.getContext('2d') - - if (!croppedCtx) - throw new Error('Could not create a canvas context') - - // Set the size of the cropped canvas - croppedCanvas.width = pixelCrop.width - croppedCanvas.height = pixelCrop.height - - // Draw the cropped image onto the new canvas - croppedCtx.drawImage( - canvas, - pixelCrop.x, - pixelCrop.y, - pixelCrop.width, - pixelCrop.height, - 0, - 0, - pixelCrop.width, - pixelCrop.height - ) - - return new Promise((resolve, reject) => { - croppedCanvas.toBlob((file) => { - if (file) - resolve(file) - else - reject('Error') - }, 'image/jpeg') - }) + const image = await createImage(imageSrc) + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + if (!ctx) + throw new Error('Could not create a canvas context') + + const rotRad = getRadianAngle(rotation) + + // calculate bounding box of the rotated image + const { width: bBoxWidth, height: bBoxHeight } = rotateSize( + image.width, + image.height, + rotation, + ) + + // set canvas size to match the bounding box + canvas.width = bBoxWidth + canvas.height = bBoxHeight + + // translate canvas context to a central location to allow rotating and flipping around the center + ctx.translate(bBoxWidth / 2, bBoxHeight / 2) + ctx.rotate(rotRad) + ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1) + ctx.translate(-image.width / 2, -image.height / 2) + + // draw rotated image + ctx.drawImage(image, 0, 0) + + const croppedCanvas = document.createElement('canvas') + + const croppedCtx = croppedCanvas.getContext('2d') + + if (!croppedCtx) + throw new Error('Could not create a canvas context') + + // Set the size of the cropped canvas + croppedCanvas.width = pixelCrop.width + croppedCanvas.height = pixelCrop.height + + // Draw the cropped image onto the new canvas + croppedCtx.drawImage( + canvas, + pixelCrop.x, + pixelCrop.y, + pixelCrop.width, + pixelCrop.height, + 0, + 0, + pixelCrop.width, + pixelCrop.height, + ) + + return new Promise((resolve, reject) => { + croppedCanvas.toBlob((file) => { + if (file) + resolve(file) + else + reject(new Error('Could not create a blob')) + }, 'image/jpeg') + }) } diff --git a/web/app/components/base/app-icon/index.tsx b/web/app/components/base/app-icon/index.tsx index 9b6feb91c9bec8..96bd7af146da4d 100644 --- a/web/app/components/base/app-icon/index.tsx +++ b/web/app/components/base/app-icon/index.tsx @@ -1,13 +1,15 @@ 'use client' -import { PropsWithChildren, useState, type FC } from 'react' -import { useAsyncEffect } from "ahooks" +import { useState } from 'react' +import type { FC, PropsWithChildren } from 'react' +import { useAsyncEffect } from 'ahooks' +import Image from 'next/image' import { init } from 'emoji-mart' import data from '@emoji-mart/data' import style from './style.module.css' import classNames from '@/utils/classnames' -import { AppIconType } from "@/types/app" -import { fetchAppIconPreviewUrl } from "@/service/apps" +import type { AppIconType } from '@/types/app' +import { fetchAppIconPreviewUrl } from '@/service/apps' init({ data }) @@ -28,7 +30,7 @@ const AppIconWrapper = ({ background, className, onClick, - children + children, }: PropsWithChildren>) => { const wrapperClassName = classNames( style.appIcon, @@ -63,10 +65,10 @@ const AppIcon: FC = ({ return {iconType === 'emoji' - ? innerIcon || ((icon && icon !== '') ? : ) + ? (innerIcon || ((icon && icon !== '') ? : )) : loading ? '' - : + : App icon } } diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index 5241862cdb9d03..02aa1ea5769caf 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable multiline-ternary */ 'use client' import type { ChangeEvent, FC } from 'react' import React, { useState } from 'react' @@ -11,16 +10,12 @@ import { import cn from '@/utils/classnames' import Divider from '@/app/components/base/divider' import { searchEmoji } from '@/utils/emoji' -import classNames from "@/utils/classnames" declare global { namespace JSX { // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface IntrinsicElements { - 'em-emoji': React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - > + 'em-emoji': React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement > } } } @@ -66,12 +61,11 @@ const EmojiPicker: FC = ({ const [isSearching, setIsSearching] = useState(false) React.useEffect(() => { - if (selectedEmoji && selectedBackground) { + if (selectedEmoji && selectedBackground) onSelect?.(selectedEmoji, selectedBackground) - } - }, [selectedEmoji, selectedBackground]) + }, [onSelect, selectedEmoji, selectedBackground]) - return
+ return
diff --git a/web/types/app.ts b/web/types/app.ts index 1d827d6319730e..ed74182793cbad 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -6,7 +6,6 @@ import type { RerankingModeEnum, WeightedScoreEnum, } from '@/models/datasets' -import { AppIconType } from '@/app/components/base/app-icon-picker' export enum Theme { light = 'light',