mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
app list modification
This commit is contained in:
98
web/app/components/app/duplicate-modal/index.tsx
Normal file
98
web/app/components/app/duplicate-modal/index.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import s from './style.module.css'
|
||||
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'
|
||||
export type DuplicateAppModalProps = {
|
||||
appName: string
|
||||
icon: string
|
||||
icon_background: string
|
||||
show: boolean
|
||||
onConfirm: (info: {
|
||||
name: string
|
||||
icon: string
|
||||
icon_background: string
|
||||
}) => Promise<void>
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
const DuplicateAppModal = ({
|
||||
appName,
|
||||
icon,
|
||||
icon_background,
|
||||
show = false,
|
||||
onConfirm,
|
||||
onHide,
|
||||
}: DuplicateAppModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [name, setName] = React.useState(appName)
|
||||
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
const [emoji, setEmoji] = useState({ icon, icon_background })
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const submit = () => {
|
||||
if (!name.trim()) {
|
||||
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
|
||||
return
|
||||
}
|
||||
onConfirm({
|
||||
name,
|
||||
...emoji,
|
||||
})
|
||||
onHide()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
className={cn(s.modal, '!max-w-[480px]', 'px-8')}
|
||||
>
|
||||
<span className={s.close} onClick={onHide} />
|
||||
<div className={s.title}>{t('app.duplicateTitle')}</div>
|
||||
<div className={s.content}>
|
||||
<div className={s.subTitle}>{t('explore.appCustomize.subTitle')}</div>
|
||||
<div className='flex items-center justify-between space-x-3'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
|
||||
<input
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow'
|
||||
/>
|
||||
</div>
|
||||
{/* TODO loc */}
|
||||
{isAppsFull && <AppsFull loc='app-duplicate-create' />}
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button disabled={isAppsFull} className='w-24 ml-2' type='primary' onClick={submit}>{t('app.duplicate')}</Button>
|
||||
<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)
|
||||
}}
|
||||
onClose={() => {
|
||||
setEmoji({ icon, icon_background })
|
||||
setShowEmojiPicker(false)
|
||||
}}
|
||||
/>}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default DuplicateAppModal
|
||||
36
web/app/components/app/duplicate-modal/style.module.css
Normal file
36
web/app/components/app/duplicate-modal/style.module.css
Normal file
@ -0,0 +1,36 @@
|
||||
.modal {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal .close {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 25px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: center no-repeat url(~@/app/components/datasets/create/assets/close.svg);
|
||||
background-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal .title {
|
||||
@apply mb-9;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.modal .content {
|
||||
@apply mb-9;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="route" clip-path="url(#clip0_664_8452)">
|
||||
<path id="Icon" d="M5.75 2.5H5.9672C7.49082 2.5 8.25263 2.5 8.54182 2.77364C8.79179 3.01018 8.90257 3.35864 8.83508 3.69611C8.75701 4.08651 8.13505 4.52643 6.89114 5.40627L4.85886 6.84373C3.61495 7.72357 2.99299 8.16349 2.91492 8.5539C2.84743 8.89136 2.95821 9.23982 3.20818 9.47636C3.49737 9.75 4.25918 9.75 5.7828 9.75H6.25M4 2.5C4 3.32843 3.32843 4 2.5 4C1.67157 4 1 3.32843 1 2.5C1 1.67157 1.67157 1 2.5 1C3.32843 1 4 1.67157 4 2.5ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5Z" stroke="#F79009" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_664_8452">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 900 B |
@ -0,0 +1,66 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"viewBox": "0 0 12 12",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "route",
|
||||
"clip-path": "url(#clip0_664_8452)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Icon",
|
||||
"d": "M5.75 2.5H5.9672C7.49082 2.5 8.25263 2.5 8.54182 2.77364C8.79179 3.01018 8.90257 3.35864 8.83508 3.69611C8.75701 4.08651 8.13505 4.52643 6.89114 5.40627L4.85886 6.84373C3.61495 7.72357 2.99299 8.16349 2.91492 8.5539C2.84743 8.89136 2.95821 9.23982 3.20818 9.47636C3.49737 9.75 4.25918 9.75 5.7828 9.75H6.25M4 2.5C4 3.32843 3.32843 4 2.5 4C1.67157 4 1 3.32843 1 2.5C1 1.67157 1.67157 1 2.5 1C3.32843 1 4 1.67157 4 2.5ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "1.25",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "clipPath",
|
||||
"attributes": {
|
||||
"id": "clip0_664_8452"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Route"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Route.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Route'
|
||||
|
||||
export default Icon
|
||||
@ -1 +1,2 @@
|
||||
export { default as Globe01 } from './Globe01'
|
||||
export { default as Route } from './Route'
|
||||
|
||||
38
web/app/components/base/tab-slider-new/index.tsx
Normal file
38
web/app/components/base/tab-slider-new/index.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import type { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
|
||||
type Option = {
|
||||
value: string
|
||||
text: string
|
||||
}
|
||||
type TabSliderProps = {
|
||||
className?: string
|
||||
value: string
|
||||
onChange: (v: string) => void
|
||||
options: Option[]
|
||||
}
|
||||
const TabSliderNew: FC<TabSliderProps> = ({
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(className, 'relative flex')}>
|
||||
{options.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
className={cn(
|
||||
'mr-1 px-3 py-[5px] h-[28px] rounded-lg border-[0.5px] border-transparent text-gray-700 text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-gray-200',
|
||||
value === option.value && 'bg-white border-gray-200 shadow-xs text-primary-600 hover:bg-white',
|
||||
)}
|
||||
>
|
||||
{option.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabSliderNew
|
||||
Reference in New Issue
Block a user