feat: custom app icon (#7196)

Co-authored-by: crazywoola <427733928@qq.com>
This commit is contained in:
Hash Brown
2024-08-19 09:16:33 +08:00
committed by GitHub
parent a0c689c273
commit fbf31b5d52
65 changed files with 1068 additions and 352 deletions

View File

@@ -26,7 +26,13 @@ const AppCard = ({
<div className={cn('group flex col-span-1 bg-white border-2 border-solid border-transparent rounded-lg shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg')}>
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
<div className='relative shrink-0'>
<AppIcon size='large' icon={app.app.icon} background={app.app.icon_background} />
<AppIcon
size='large'
iconType={app.app.icon_type}
icon={app.app.icon}
background={app.app.icon_background}
imageUrl={app.app.icon_url}
/>
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
{appBasicInfo.mode === 'advanced-chat' && (
<ChatBot className='w-3 h-3 text-[#1570EF]' />

View File

@@ -118,6 +118,7 @@ const Apps = ({
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
const onCreate: CreateAppModalProps['onConfirm'] = async ({
name,
icon_type,
icon,
icon_background,
description,
@@ -129,6 +130,7 @@ const Apps = ({
const app = await importApp({
data: export_data,
name,
icon_type,
icon,
icon_background,
description,
@@ -215,8 +217,10 @@ const Apps = ({
</div>
{isShowCreateModal && (
<CreateAppModal
appIconType={currApp?.app.icon_type || 'emoji'}
appIcon={currApp?.app.icon || ''}
appIconBackground={currApp?.app.icon_background || ''}
appIconUrl={currApp?.app.icon_url}
appName={currApp?.app.name || ''}
appDescription={currApp?.app.description || ''}
show={isShowCreateModal}

View File

@@ -2,25 +2,29 @@
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import AppIconPicker from '../../base/app-icon-picker'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Toast from '@/app/components/base/toast'
import AppIcon from '@/app/components/base/app-icon'
import EmojiPicker from '@/app/components/base/emoji-picker'
import { useProviderContext } from '@/context/provider-context'
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import type { AppIconType } from '@/types/app'
export type CreateAppModalProps = {
show: boolean
isEditModal?: boolean
appName: string
appDescription: string
appIconType: AppIconType | null
appIcon: string
appIconBackground: string
appIconBackground?: string | null
appIconUrl?: string | null
onConfirm: (info: {
name: string
icon_type: AppIconType
icon: string
icon_background: string
icon_background?: string
description: string
}) => Promise<void>
onHide: () => void
@@ -29,8 +33,10 @@ export type CreateAppModalProps = {
const CreateAppModal = ({
show = false,
isEditModal = false,
appIcon,
appIconType,
appIcon: _appIcon,
appIconBackground,
appIconUrl,
appName,
appDescription,
onConfirm,
@@ -39,8 +45,12 @@ const CreateAppModal = ({
const { t } = useTranslation()
const [name, setName] = React.useState(appName)
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [emoji, setEmoji] = useState({ icon: appIcon, icon_background: appIconBackground })
const [appIcon, setAppIcon] = useState(
() => appIconType === 'image'
? { type: 'image' as const, fileId: _appIcon, url: appIconUrl }
: { type: 'emoji' as const, icon: _appIcon, background: appIconBackground },
)
const [showAppIconPicker, setShowAppIconPicker] = useState(false)
const [description, setDescription] = useState(appDescription || '')
const { plan, enableBilling } = useProviderContext()
@@ -53,7 +63,9 @@ const CreateAppModal = ({
}
onConfirm({
name,
...emoji,
icon_type: appIcon.type,
icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId,
icon_background: appIcon.type === 'emoji' ? appIcon.background! : undefined,
description,
})
onHide()
@@ -80,7 +92,15 @@ const CreateAppModal = ({
<div className='pt-2'>
<div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.newApp.captionName')}</div>
<div className='flex items-center justify-between space-x-2'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
<AppIcon
size='large'
onClick={() => { setShowAppIconPicker(true) }}
className='cursor-pointer'
iconType={appIcon.type}
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
background={appIcon.type === 'image' ? undefined : appIcon.background}
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
/>
<input
value={name}
onChange={e => setName(e.target.value)}
@@ -106,18 +126,19 @@ const CreateAppModal = ({
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
</div>
</Modal>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
{showAppIconPicker && <AppIconPicker
onSelect={(payload) => {
setAppIcon(payload)
setShowAppIconPicker(false)
}}
onClose={() => {
setEmoji({ icon: appIcon, icon_background: appIconBackground })
setShowEmojiPicker(false)
setAppIcon(appIconType === 'image'
? { type: 'image' as const, url: appIconUrl, fileId: _appIcon }
: { type: 'emoji' as const, icon: _appIcon, background: appIconBackground })
setShowAppIconPicker(false)
}}
/>}
</>
)
}

View File

@@ -7,13 +7,16 @@ import s from './style.module.css'
import cn from '@/utils/classnames'
import ItemOperation from '@/app/components/explore/item-operation'
import AppIcon from '@/app/components/base/app-icon'
import type { AppIconType } from '@/types/app'
export type IAppNavItemProps = {
isMobile: boolean
name: string
id: string
icon_type: AppIconType | null
icon: string
icon_background: string
icon_url: string
isSelected: boolean
isPinned: boolean
togglePin: () => void
@@ -25,8 +28,10 @@ export default function AppNavItem({
isMobile,
name,
id,
icon_type,
icon,
icon_background,
icon_url,
isSelected,
isPinned,
togglePin,
@@ -50,11 +55,11 @@ export default function AppNavItem({
router.push(url) // use Link causes popup item always trigger jump. Can not be solved by e.stopPropagation().
}}
>
{isMobile && <AppIcon size='tiny' icon={icon} background={icon_background} />}
{isMobile && <AppIcon size='tiny' iconType={icon_type} icon={icon} background={icon_background} imageUrl={icon_url} />}
{!isMobile && (
<>
<div className='flex items-center space-x-2 w-0 grow'>
<AppIcon size='tiny' icon={icon} background={icon_background} />
<AppIcon size='tiny' iconType={icon_type} icon={icon} background={icon_background} imageUrl={icon_url} />
<div className='overflow-hidden text-ellipsis whitespace-nowrap' title={name}>{name}</div>
</div>
<div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>

View File

@@ -109,14 +109,16 @@ const SideBar: FC<IExploreSideBarProps> = ({
height: 'calc(100vh - 250px)',
}}
>
{installedApps.map(({ id, is_pinned, uninstallable, app: { name, icon, icon_background } }) => {
{installedApps.map(({ id, is_pinned, uninstallable, app: { name, icon_type, icon, icon_url, icon_background } }) => {
return (
<Item
key={id}
isMobile={isMobile}
name={name}
icon_type={icon_type}
icon={icon}
icon_background={icon_background}
icon_url={icon_url}
id={id}
isSelected={lastSegment?.toLowerCase() === id}
isPinned={is_pinned}